堆/堆排序/优先级队列

一.堆的性质
1.本身是个数组,但是用完全二叉树的思想来处理数组内容
2.最大堆的根节点是整个数据中的最大元素,最小堆反之
3.下面所有的例子都是最大堆作为参照
4.父节点的值比子节点的值大

下滤: 当前节点和子节点元素进行比较,如果父节点比子节点要小,那么交换父子节点元素的位置,然后一直将原来的父节点下滤,直至没有比它小的子节点为止
上滤:当前节点和父节点比较,如果当前节点比父节点大,那么交换,直至当前节点不比父节点大

比如下图中的数组元素并不满足堆的性质,需要对数组元素进行建堆操作,以满足堆的性质

1.从最后一个非叶子节点开始下滤,一直持续到根节点,那么整个数组就建好堆了
2.元素索引的关系:比如当前节点的索引为index
左子节点的索引:index * 2+1
右子节点的索引:index * 2+2
父节点的索引:(index+1)/2
3.整个完全二叉树最后一个非叶子节点的索引为
最后一个非叶子节点的索引:count/2-1

例子:比如数组 [ 50,40,60,45,27,12,80,100],按照完全二叉树的排布书序是下面这个样子的


wecom-temp-68cf984c41c3424f854f53a6163ffe6b.png

那么对45下滤的情况,结果为


wecom-temp-d8c8448171a4392b68491e979751456e.png

再对60下滤的效果
wecom-temp-ae1f6afa2a6e6e7bd42d6024e83907ae.png

再对40下滤的效果


wecom-temp-4a22b18cef7f8c1380c5ac9bc5537910.png

再对50下滤的效果
wecom-temp-c4fc5bd16bde0b0ed49fb40787e9e8af.png

最后这个数组就建好了一个最大堆, [100,50,80,45,27,12,60,40]

A.原地建堆并且下滤的代码,通过这段代码就完成了数组建堆操作

//上滤
- (void)siftUp:(NSInteger)index
{
    if (index>=_heapArray.count) return;
    id obj = _heapArray[index];
    while(index>0){
        //获取父元素
        NSInteger parentIndex = (index-1)>>1;
        id parent = _heapArray[parentIndex];
        if (parentIndex>=0 &&_compareBlock(obj,parent)<=0) break;
        _heapArray[index] = parent;
        index = parentIndex;
    }
    _heapArray[index] = obj;
}
//原地建堆
- (void)createHeapArray
{
    NSInteger count = _heapArray.count;
    for (NSInteger index = (count>>1)-1; index>=0; index--) {
        [self siftDown:index];
    }
}

B.添加元素后,对最后一个元素进行上滤操作,以维持整个堆的性质

//上滤
- (void)siftUp:(NSInteger)index
{
    if (index>=_heapArray.count) return;
    id obj = _heapArray[index];
    while(index>0){
        //获取父元素
        NSInteger parentIndex = (index-1)>>1;
        id parent = _heapArray[parentIndex];
        if (parentIndex>=0 &&_compareBlock(obj,parent)<=0) break;
        _heapArray[index] = parent;
        index = parentIndex;
    }
    _heapArray[index] = obj;
}

C.删除元素时,移除根元素,并将最后一个元素替换掉根元素,然后对根元素进行下滤,以维持堆的性质

//移除顶部元素
- (NSObject *)removeElementAtTop
{
    if (_heapArray.count==0) return nil;
    if (_heapArray.count==1) {
        NSObject *obj = _heapArray[0];
        [_heapArray removeAllObjects];
        return obj;
    }
    //如果存在多个元素,将最后一个元素替换掉0的位置,并且最后一个置为空,同时第一个元素下滤
    NSObject *fristObj = _heapArray[0];
    NSObject *lastObj = [_heapArray lastObject];
    _heapArray[0] = lastObj;
    [_heapArray removeLastObject];
    [self siftDown:0];
    return fristObj;
}

二.堆排序

1.原理:在我们对数组元素建完堆后,根节点的元素最大,那么我们将根节点和最后一个节点进行交换,然后再对根节点进行下滤,并且将之前找到的最大值排除在外,重复执行此操作,那么整个数组就排好序了

typedef NSInteger(^SortBlock)(NSObject *obj1,NSObject *obj2);
+ (NSArray *)heapSort:(NSArray *)dataArray compareBlock:(SortBlock)sortBlock;

+ (NSArray *)heapSort:(NSArray *)dataArray compareBlock:(SortBlock)sortBlock
{
    if (dataArray==nil || dataArray.count<=1) return dataArray;
    NSMutableArray *marr = dataArray.mutableCopy;
    NSInteger heapCount = marr.count;
    //1.原地建堆:完全二叉树将数组元素逐个填充到完全二叉树,从倒数第一个非叶子节点进行下滤操作,那么最终,第一个元素一定是最大值
    for (NSInteger index = (marr.count>>1)-1; index>=0; index--) {
        [self siftDown:index dataArray:marr compareBlock:sortBlock count:heapCount];
    }
    //2.将第一个最大元素和最后面的那个元素进行交换,再对交换后的元素进行下滤操作,并缩小最大堆的范围,循环执行此操作,那么最中数组就排好序列了
    while (heapCount>1) {
        --heapCount;
        [self swapIndex1:0 index2:heapCount dataArray:marr];
        [self siftDown:0 dataArray:marr compareBlock:sortBlock count:heapCount];
    }
    return marr.copy;
}
//下滤
+ (void)siftDown:(NSInteger)index dataArray:(NSMutableArray *)dataArray compareBlock:(SortBlock)sortBlock count:(NSInteger)count
{
    id markObj = dataArray[index];
    NSInteger half = count>>1;
    while (index<half) {
        NSInteger childIndex = (index<<1)+1;
        id child = dataArray[childIndex];
        NSInteger rightIndex = childIndex+1;
        if (rightIndex<count && sortBlock(dataArray[rightIndex],child)>0) {
            childIndex = rightIndex;
            child = dataArray[childIndex];
        }
        if (sortBlock(markObj,child)>=0) break;
        dataArray[index] = child;
        index = childIndex;
    }
    dataArray[index] = markObj;
}

三.优先级队列
1.比如医院来病人了,那么治疗顺序肯定要按紧急程度治疗,总不能别人都快挂了还要排个队等前面的感冒啥的完了之后再治疗
2.利用堆来实现优先级队列的代码

下面是完整的堆代码

typedef NSInteger(^HeapCompareBlock)(NSObject *obj1,NSObject *obj2);

@interface GYJHeap : NSObject

//堆中元素数量
@property(nonatomic,readonly)NSInteger size;
//指定的构造方法,必须传入对象的比较方式,数据内容可以先不传,后面加
- (instancetype)initWithDefaultArray:(nullable NSArray *)defaultArray CompareBlock:(HeapCompareBlock NS_NOESCAPE)compareBlock;
//给堆原来的基础上添加元素
- (void)continueAddElements:(NSArray *)elements;
//添加一个元素
- (void)addElement:(NSObject *)obj;
//堆是否为空
- (BOOL)isEmpty;
//清空堆
- (void)clear;
//查看最顶部的元素
- (NSObject *)lookTowerTopElement;
//移除顶部元素
- (NSObject *)removeElementAtTop;

@end

#import "GYJHeap.h"

@interface GYJHeap()

@property(nonatomic,strong)NSMutableArray *heapArray;
//元素与元素间比较的block,以确定对象大小关系
@property(nonatomic,copy)HeapCompareBlock compareBlock;

@end

@implementation GYJHeap

//构造方法,必须传入对象的比较方式
- (instancetype)initWithDefaultArray:(nullable NSArray *)defaultArray CompareBlock:(HeapCompareBlock NS_NOESCAPE)compareBlock
{
    if (self = [super init]) {
        _compareBlock = compareBlock;
        if (defaultArray==nil) {
            _heapArray = [[NSMutableArray alloc]init];
        }else{
            _heapArray = defaultArray.mutableCopy;
            [self createHeapArray];
        }
    }
    return self;
}
- (instancetype)init
{
    if (self = [super init]) {
        NSAssert(NO, @"不要使用这个init方法初始化堆,使用另外一个");
    }
    return self;
}
//给堆中添加元素
- (void)continueAddElements:(NSArray *)elements
{
    if (elements==nil || elements.count==0) return;
    for (NSInteger i = 0; i<elements.count; i++) {
        [self addElement:elements[i]];
    }
}
//添加一个元素
- (void)addElement:(NSObject *)obj
{
    if (obj==nil) return;
    [_heapArray addObject:obj];
    [self siftUp:_heapArray.count-1];
}
//堆是否为空
- (BOOL)isEmpty
{
    return _heapArray.count==0?YES:NO;
}
//清空堆
- (void)clear
{
    [_heapArray removeAllObjects];
}
//查看最顶部的元素
- (NSObject *)lookTowerTopElement
{
    return _heapArray.count>0?_heapArray[0]:nil;
}
//移除顶部元素
- (NSObject *)removeElementAtTop
{
    if (_heapArray.count==0) return nil;
    if (_heapArray.count==1) {
        NSObject *obj = _heapArray[0];
        [_heapArray removeAllObjects];
        return obj;
    }
    //如果存在多个元素,将最后一个元素替换掉0的位置,并且最后一个置为空,同时第一个元素下滤
    NSObject *fristObj = _heapArray[0];
    NSObject *lastObj = [_heapArray lastObject];
    _heapArray[0] = lastObj;
    [_heapArray removeLastObject];
    [self siftDown:0];
    return fristObj;
}
- (NSInteger)size;
{
    return _heapArray.count;
}
//下滤
- (void)siftDown:(NSInteger)index
{
    if (index>=_heapArray.count) return;
    id obj = _heapArray[index];
    NSInteger halfIndex = _heapArray.count>>1;
    while (index<halfIndex) {
        //获取左子元素
        NSInteger childIndex = (index<<1)+1;
        id child = _heapArray[childIndex];
        NSInteger rightIndex = childIndex+1;
        //比较左右子元素,确定那个是最大的子元素
        if (rightIndex<_heapArray.count &&_compareBlock(_heapArray[rightIndex],child)>0) {
            child = _heapArray[rightIndex];
            childIndex = rightIndex;
        }
        //如果本身比左右子元素大,那么直接退出
        if (_compareBlock(obj,child)>=0) break;
        _heapArray[index] = child;
        index = childIndex;
    }
    _heapArray[index] = obj;
}
//上滤
- (void)siftUp:(NSInteger)index
{
    if (index>=_heapArray.count) return;
    id obj = _heapArray[index];
    while(index>0){
        //获取父元素
        NSInteger parentIndex = (index-1)>>1;
        id parent = _heapArray[parentIndex];
        if (parentIndex>=0 &&_compareBlock(obj,parent)<=0) break;
        _heapArray[index] = parent;
        index = parentIndex;
    }
    _heapArray[index] = obj;
}
//原地建堆
- (void)createHeapArray
{
    NSInteger count = _heapArray.count;
    for (NSInteger index = (count>>1)-1; index>=0; index--) {
        [self siftDown:index];
    }
}
@end

下面是优先级队列的代码

@interface GYJPriorityQueue : NSObject

//构造方法
- (instancetype)initWithCompareBlock:(HeapCompareBlock)compareBlock;
//元素数量
@property(nonatomic,readonly)NSInteger count;
//批量入队
- (void)enterPriorityQueueDefaultElements:(NSArray *)elements;
//单个入队
- (void)enterPriorityQueue:(NSObject *)element;
//队列是否为空
- (BOOL)isEmpty;
//清空队列
- (void)clear;
//出队最大或者最小权重的元素
- (NSObject *)outPriorityQueue;
//即将出队的元素
- (NSObject *)willOutPriorityQueueElement;

@end

#import "GYJPriorityQueue.h"

@interface GYJPriorityQueue ()

@property(nonatomic,strong)GYJHeap *heap;

@end

@implementation GYJPriorityQueue
//构造方法
- (instancetype)initWithCompareBlock:(HeapCompareBlock)compareBlock
{
    if (self = [super init]) {
        _heap = [[GYJHeap alloc]initWithDefaultArray:nil CompareBlock:compareBlock];
    }
    return self;
}
- (instancetype)init
{
    if (self = [super init]) {
        NSAssert(NO, @"请使用指定的优先级队列构造方法,不要使用init");
    }
    return self;
}
- (NSInteger)count
{
    return _heap.size;
}
//批量入队
- (void)enterPriorityQueueDefaultElements:(NSArray *)elements
{
    [_heap continueAddElements:elements];
}
//单个入队
- (void)enterPriorityQueue:(NSObject *)element
{
    [_heap addElement:element];
}
//队列是否为空
- (BOOL)isEmpty
{
    return _heap.size==0?YES:NO;
}
//清空队列
- (void)clear
{
    [_heap clear];
}
//出队最大或者最小权重的元素
- (NSObject *)outPriorityQueue
{
    return [_heap removeElementAtTop];
}
//即将出队的元素
- (NSObject *)willOutPriorityQueueElement
{
    return [_heap lookTowerTopElement];
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容