本文的目標是要作出優先隊列和堆排序兩個Demo。node
徹底二叉樹的定義是創建在滿二叉樹定義的基礎上的,而滿二叉樹又是創建在二叉樹的基礎上的。git
大體瞭解一下概念github
一、樹是一對多的數據結構,從一個根結點開始,生長出它的子結點,而每個子結點又生長出各自的子結點,成爲子樹。若是某個結點再也不生長出子結點了,它就成爲葉子。 二、二叉樹每一個結點最多隻有兩棵子樹,並且左右子樹是有順序的,不可顛倒。算法
三、滿二叉樹的全部分支結點都既有左子樹又有右子樹,而且全部葉子都在同一層。滿二叉樹看起來的感受很完美,沒有任何缺失。數組
四、徹底二叉樹不必定是滿的,但它自上而下,自左而右來看的話,是連續沒有缺失的。數據結構
i (1 <= i <= n)
的結點與一樣深度的滿二叉樹中編號爲i
的結點在二叉樹中位置徹底相同,則這棵二叉樹稱爲徹底二叉樹。性質ui
徹底二叉樹有好幾條性質,其中有一條在本文中須要用到:atom
對於一棵有n
個結點的徹底二叉樹,對其結點按層序編號,從上到下,從左到右,對任一結點i (1 = i <= n)
有:spa
i = 1
,則結點i
是二叉樹的根,無父結點;若是i > 1
,則其父結點的位置是⌊i / 2⌋
(向下取整)。2i > n
,則結點i
無左孩子(結點i
爲葉子結點);不然其左孩子是結點2i
。2i + 1 > n
,則結點i
無右孩子;不然其右孩子是結點2i + 1
。存儲結構線程
得益於二叉樹的嚴格定義,咱們只須要把徹底二叉樹按層序遍歷依次把結點存入一維數組中,其數組下標就可以體現出父子結點關係來(數組第0位不使用)。
徹底二叉樹的概念大體瞭解後,下面進入正題,看看如何把它用起來。
爲何使用徹底二叉樹來實現優先隊列?
優先隊列按數據結構的不一樣有幾種實現方式:
綜合了入隊和出隊兩個操做來看,使用徹底二叉樹來實現的優先隊列在時間效率上是最高的。
隊列的操做
一個隊列應該提供兩個關鍵的方法:入隊列和出隊列。
typedef NSComparisonResult(^JXPriorityQueueComparator)(id obj1, id obj2);
@interface JXPriorityQueue : NSObject
/// 定義元素的比較邏輯
@property (nonatomic, copy) JXPriorityQueueComparator comparator;
/// 入列
- (void)enQueue:(id)element;
/// 出列
- (id)deQueue;
@end
複製代碼
入列
入列總的來講分爲兩步:
- (void)enQueue:(id)element {
// 添加到末尾
[self.data addObject:element];
// 上游元素以維持堆有序
[self swimIndex:self.tailIndex];
}
/// 上游,傳入須要上游的元素位置,以及容許上游的最頂位置
- (void)swimIndex:(NSInteger)index {
// 暫存須要上游的元素
id temp = self.data[index];
// parent的位置爲本元素位置的1/2
for (NSInteger parentIndex = index / 2; parentIndex >= 1; parentIndex /= 2) {
// 上游條件是本元素大於parent,不然不上游
if (self.comparator(temp, self.data[parentIndex]) != NSOrderedDescending) {
break;
}
// 把parent拉下來
self.data[index] = self.data[parentIndex];
// 上游本元素
index = parentIndex;
}
// 本元素進入目標位置
self.data[index] = temp;
}
複製代碼
這裏關鍵在於如何上游元素:
出列
把優先值最大的元素(根結點)出列,可分爲以下步驟:
- (id)deQueue {
if (self.count == 0) {
return nil;
}
// 取根元素
id element = self.data[1];
// 交換隊首和隊尾元素
[self swapIndexA:1 indexB:self.tailIndex];
[self.data removeLastObject];
if (self.data.count > 1) {
// 下沉剛剛交換上來的隊尾元素,維持堆有序狀態
[self sinkIndex:1];
}
return element;
}
/// 交換元素
- (void)swapIndexA:(NSInteger)indexA indexB:(NSInteger)indexB {
id temp = self.data[indexA];
self.data[indexA] = self.data[indexB];
self.data[indexB] = temp;
}
/// 下沉,傳入須要下沉的元素位置,以及容許下沉的最底位置
- (void)sinkIndex:(NSInteger)index {
// 暫存須要下沉的元素
id temp = self.data[index];
// maxChildIndex指向最大的子結點,默認指向左子結點,左子結點的位置爲本結點位置*2
for (NSInteger maxChildIndex = index * 2; maxChildIndex <= self.tailIndex; maxChildIndex *= 2) {
// 若是存在右子結點,而且左子結點比右子結點小
if (maxChildIndex < self.tailIndex && (self.comparator(self.data[maxChildIndex], self.data[maxChildIndex + 1]) == NSOrderedAscending)) {
// 指向右子結點
++ maxChildIndex;
}
// 下沉條件是本元素小於child,不然不下沉
if (self.comparator(temp, self.data[maxChildIndex]) != NSOrderedAscending) {
break;
}
// 不然
// 把最大子結點元素上游到本元素位置
self.data[index] = self.data[maxChildIndex];
// 標記本元素須要下沉的目標位置,爲最大子結點原位置
index = maxChildIndex;
}
// 本元素進入目標位置
self.data[index] = temp;
}
複製代碼
這裏關鍵在於如何把剪枝後的樹從新調整爲大頂堆,在下沉方法中:
堆排序是對簡單選擇排序的一種改進,改進後的效果很是明顯。選擇排序的時間複雜度是n²
,堆排序是nlog₂n
。
堆排序總的來講分爲兩個步驟:
NSMutableArray+JXHeapSort.h
typedef NSComparisonResult(^JXSortComparator)(id obj1, id obj2);
typedef void(^JXSortExchangeCallback)(id obj1, id obj2);
typedef void(^JXSortCutCallback)(id obj, NSInteger index);
@interface NSMutableArray (JXHeapSort)
// 堆排序
- (void)jx_heapSortUsingComparator:(JXSortComparator)comparator didExchange:(JXSortExchangeCallback)exchangeCallback didCut:(JXSortCutCallback)cutCallback;
@end
複製代碼
NSMutableArray+JXHeapSort.m
@implementation NSMutableArray (JXHeapSort)
/// 堆排序
- (void)jx_heapSortUsingComparator:(JXSortComparator)comparator didExchange:(JXSortExchangeCallback)exchangeCallback didCut:(JXSortCutCallback)cutCallback {
// 排序過程當中不使用第0位
[self insertObject:[NSNull null] atIndex:0];
// 構造大頂堆
// 遍歷全部非終結點,把以它們爲根結點的子樹調整成大頂堆
// 最後一個非終結點位置在本隊列長度的一半處
for (NSInteger index = self.count / 2; index > 0; index --) {
// 根結點下沉到合適位置
[self sinkIndex:index bottomIndex:self.count - 1 usingComparator:comparator didExchange:exchangeCallback];
}
// 徹底排序
// 從整棵二叉樹開始,逐漸剪枝
for (NSInteger index = self.count - 1; index > 1; index --) {
// 每次把根結點放在列尾,下一次循環時將會剪掉
[self jx_exchangeWithIndexA:1 indexB:index didExchange:exchangeCallback];
if (cutCallback) {
cutCallback(self[index], index - 1);
}
// 下沉根結點,從新調整爲大頂堆
[self sinkIndex:1 bottomIndex:index - 1 usingComparator:comparator didExchange:exchangeCallback];
}
// 排序完成後刪除佔位元素
[self removeObjectAtIndex:0];
}
/// 下沉,傳入須要下沉的元素位置,以及容許下沉的最底位置
- (void)sinkIndex:(NSInteger)index bottomIndex:(NSInteger)bottomIndex usingComparator:(JXSortComparator)comparator didExchange:(JXSortExchangeCallback)exchangeCallback {
for (NSInteger maxChildIndex = index * 2; maxChildIndex <= bottomIndex; maxChildIndex *= 2) {
// 若是存在右子結點,而且左子結點比右子結點小
if (maxChildIndex < bottomIndex && (comparator(self[maxChildIndex], self[maxChildIndex + 1]) == NSOrderedAscending)) {
// 指向右子結點
++ maxChildIndex;
}
// 若是最大的子結點元素小於本元素,則本元素沒必要下沉了
if (comparator(self[maxChildIndex], self[index]) == NSOrderedAscending) {
break;
}
// 不然
// 把最大子結點元素上游到本元素位置
[self jx_exchangeWithIndexA:index indexB:maxChildIndex didExchange:exchangeCallback];
// 標記本元素須要下沉的目標位置,爲最大子結點原位置
index = maxChildIndex;
}
}
/// 交換兩個元素
- (void)jx_exchangeWithIndexA:(NSInteger)indexA indexB:(NSInteger)indexB didExchange:(JXSortExchangeCallback)exchangeCallback {
id temp = self[indexA];
self[indexA] = self[indexB];
self[indexB] = temp;
if (exchangeCallback) {
exchangeCallback(temp, self[indexA]);
}
}
@end
複製代碼
在Demo中,nodeArray
是一個UILabel
數組:
@property (nonatomic, strong) NSMutableArray<UILabel *> *nodeArray;
複製代碼
對這個數組進行排序,並藉助信號量在線程間通訊,控制排序速度:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// 定時發出信號,以容許繼續交換
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.6 repeats:YES block:^(NSTimer * _Nonnull timer) {
dispatch_semaphore_signal(sema);
}];
[self.nodeArray jx_heapSortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
// 比較兩個結點
return [self compareWithNodeA:obj1 nodeB:obj2];
} didExchange:^(id obj1, id obj2) {
// 交換兩結點
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self exchangeNodeA:obj1 nodeB:obj2];
} didCut:^(id obj, NSInteger index) {
// 剪枝
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self cutNode:obj index:index];
}];
複製代碼
優先隊列:https://github.com/JiongXing/JXPriorityQueue 堆排序:https://github.com/JiongXing/JXHeapSort 排序算法比較 : https://github.com/JiongXing/JXSort