
本文只是自己的笔记,并不具有任何指导意义。
为了了解很多都使用了递归,而不是自己进行压栈解决。
代码的初衷是便于了解,网上大神优化过的代码很多,也不建议在项目中copy本文代码。
目录
- 堆的结构
- 满二叉树
- 完全二叉树
- 数组与完全二叉树
- 大根堆&&小根堆
- 用数组,建立大根堆二叉树
- 向下调整
- 堆排序
堆的结构
堆实际上是一颗完全二叉树形式的数组
满二叉树
对于国内的满二叉树
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树。
从图形形态上看,满二叉树外观上是一个三角形

国内的满二叉树属于完全二叉树
对于国外的满二叉树
满二叉树的结点要么是叶子结点,度为0,要么是度为2的结点,不存在度为1的结点。

完全二叉树
在满二叉树的基础上,最后一层所有的结点都连续集中在最左边,这就是完全二叉树。

数组与完全二叉树
假如从下标从1开始存储,则编号为i的结点的主要关系为:
双亲:下取整 (i/2)
左孩子:2i
右孩子:2i+1
假如从下标从0开始存储,则编号为i的结点的主要关系为:
双亲:下取整 ((i-1)/2)
左孩子:2i+1
右孩子:2i+2
这个规律,通常用来对通过指定下标获得相关节点下标。
大根堆&&小根堆
解决最值问题时,堆的调整复杂度远低于其余结构。
大根堆
任一节点的关键码均大小于等于它的左右孩子的关键码,位于堆顶节点的关键码最大

小根堆
任一节点的关键码均小于等于它的左右孩子的关键码,位于堆顶节点的关键码最小。

优先级队列用大小堆的方式更容易实现
假如我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具备较高的优先级,这样我们即可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。
对于这种最值问题,堆的调整复杂度远低于其余结构。
而使用大根堆的方式,每次新入队元素最多只要要堆整个结构进行LogN次的调整,便可以让堆结构重归有序。
40亿的量级甚至只要要32次调整即可以实现。
用数组,建立大根堆二叉树
将数组中元素依次放入完全二叉树中,若大于父节点则依次比对交换。保证时刻处于大根堆排序
第i个数字被插入时排序的时间复杂度与高叉树高度相等,即O(Logi)。
所有数字都插入依次的时间复杂度收敛于O(N)
//大根堆排序func maximumHeapSort(arr:inout [Int]) { if arr.count < 2 { return } //大根堆排序 for i in 0..<arr.count { heapInsert(arr: &arr, index: i) }}//分段大根堆排序func heapInsert(arr:inout [Int] ,index:Int){ //当前节点位置 var currentIndex = index //父节点位置 var parentIndex = (index - 1)/2 //假如当前节点大于父节点,则进行交换而后继续检查 while arr[currentIndex] > arr[parentIndex] { arr.swapAt(currentIndex, parentIndex) currentIndex = parentIndex parentIndex = (currentIndex - 1)/2 } }向下调整
在一个大根堆中,某个位置的数被改变(并且变小)了。重新对堆数组进行调整
比对该位置与其左右子节点,并且与较大的一个进行交换,依次向下进行。
func heapify (arr:inout Array<Int>,index:Int,heapSize:Int){ var currentIndex = index; var left=2*currentIndex+1//左节点位置 var right=left+1//右节点位置 var largest = currentIndex //最大位置暂定为current while left<=heapSize {//保证左节点不越界 largest = right<=heapSize && arr[right] > arr[left] ?right:left //左右节点的最大值位置(右节点越界则取左) largest = arr[largest]>arr[currentIndex] ? largest:currentIndex if largest == currentIndex { break //假如当前已经为最大位置,则结束 } arr.swapAt(currentIndex, largest)//交换当前位置与左右两端最大位置 currentIndex = largest//将当前位置下移 left=2*currentIndex+1//左节点新位置 right=left+1//右节点新位置 }}堆排序
先构建出一个大根堆,而后依次将头部最大值转移到有效数组的最后一位,并且将排序区域前移。

func heapSort(arr:inout [Int]) { maximumHeapSort(arr:&arr)//先构建出一个大根堆 let size = arr.count for i in 0..<arr.count { arr.swapAt(size-1-i, 0) //将大根堆头部最大值,移到有效数组末尾。 heapify(arr: &arr, index: 0, heapSize: size-i-2)//将数组有效size前移,重新调整成大根堆 }}其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐渐递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。
最后
本文主要是自己的学习与总结。假如文内存在纰漏、万望留言斧正。假如愿意补充以及不吝赐教小弟会更加感激。
参考资料
左神牛课网算法课
用数组存储完全二叉树时,结点的索引(数组下标)与其父子结点索引的关系.
【数据结构】堆结构小根堆,大根堆,插入,删除等操作的实现
堆排序中建堆过程的时间复杂度O(n)的证实
堆——神奇的优先队列(上) 【经典】
图解排序算法(三)之堆排序