堆/堆排序/優(yōu)先級隊列

一.堆的性質(zhì)
1.本身是個數(shù)組,但是用完全二叉樹的思想來處理數(shù)組內(nèi)容
2.最大堆的根節(jié)點是整個數(shù)據(jù)中的最大元素,最小堆反之
3.下面所有的例子都是最大堆作為參照
4.父節(jié)點的值比子節(jié)點的值大

下濾: 當前節(jié)點和子節(jié)點元素進行比較,如果父節(jié)點比子節(jié)點要小,那么交換父子節(jié)點元素的位置,然后一直將原來的父節(jié)點下濾,直至沒有比它小的子節(jié)點為止
上濾:當前節(jié)點和父節(jié)點比較,如果當前節(jié)點比父節(jié)點大,那么交換,直至當前節(jié)點不比父節(jié)點大

比如下圖中的數(shù)組元素并不滿足堆的性質(zhì),需要對數(shù)組元素進行建堆操作,以滿足堆的性質(zhì)

1.從最后一個非葉子節(jié)點開始下濾,一直持續(xù)到根節(jié)點,那么整個數(shù)組就建好堆了
2.元素索引的關系:比如當前節(jié)點的索引為index
左子節(jié)點的索引:index * 2+1
右子節(jié)點的索引:index * 2+2
父節(jié)點的索引:(index+1)/2
3.整個完全二叉樹最后一個非葉子節(jié)點的索引為
最后一個非葉子節(jié)點的索引:count/2-1

例子:比如數(shù)組 [ 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

最后這個數(shù)組就建好了一個最大堆, [100,50,80,45,27,12,60,40]

A.原地建堆并且下濾的代碼,通過這段代碼就完成了數(shù)組建堆操作

//上濾
- (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.添加元素后,對最后一個元素進行上濾操作,以維持整個堆的性質(zhì)

//上濾
- (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.刪除元素時,移除根元素,并將最后一個元素替換掉根元素,然后對根元素進行下濾,以維持堆的性質(zhì)

//移除頂部元素
- (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.原理:在我們對數(shù)組元素建完堆后,根節(jié)點的元素最大,那么我們將根節(jié)點和最后一個節(jié)點進行交換,然后再對根節(jié)點進行下濾,并且將之前找到的最大值排除在外,重復執(zhí)行此操作,那么整個數(shù)組就排好序了

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.原地建堆:完全二叉樹將數(shù)組元素逐個填充到完全二叉樹,從倒數(shù)第一個非葉子節(jié)點進行下濾操作,那么最終,第一個元素一定是最大值
    for (NSInteger index = (marr.count>>1)-1; index>=0; index--) {
        [self siftDown:index dataArray:marr compareBlock:sortBlock count:heapCount];
    }
    //2.將第一個最大元素和最后面的那個元素進行交換,再對交換后的元素進行下濾操作,并縮小最大堆的范圍,循環(huán)執(zhí)行此操作,那么最中數(shù)組就排好序列了
    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;
}

三.優(yōu)先級隊列
1.比如醫(yī)院來病人了,那么治療順序肯定要按緊急程度治療,總不能別人都快掛了還要排個隊等前面的感冒啥的完了之后再治療
2.利用堆來實現(xiàn)優(yōu)先級隊列的代碼

下面是完整的堆代碼

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

@interface GYJHeap : NSObject

//堆中元素數(shù)量
@property(nonatomic,readonly)NSInteger size;
//指定的構造方法,必須傳入對象的比較方式,數(shù)據(jù)內(nèi)容可以先不傳,后面加
- (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

下面是優(yōu)先級隊列的代碼

@interface GYJPriorityQueue : NSObject

//構造方法
- (instancetype)initWithCompareBlock:(HeapCompareBlock)compareBlock;
//元素數(shù)量
@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, @"請使用指定的優(yōu)先級隊列構造方法,不要使用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
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內(nèi)容