1、概述
堆排序是指利用堆这种数据结构所设计的一种排序算法。
堆是一个近似完全二叉树,并同时满足堆积的性质:子节点的键值或索引总是大于/小于它的父结点。
堆又分为 大顶堆 和小顶堆,大顶堆的特点是 该完全二叉树的根节点比其左右节点都要大,小顶堆的特点是 在小顶堆中根节点比左右子节点的值都要小。
1)大顶堆
假设根节点的编号是 i ,那么该根节点的左孩子的编号是 2i,右孩子的编号是 2i+1。
根节点和子节点的关系为:k(i)>=k(2i)、k(i)>=k(2i+1) i>=1
2)小顶堆
假设根节点的编号是 i ,那么该根节点的左孩子的编号是 2i,右孩子的编号是 2i+1。
根节点和子节点的关系为:k(i)<=k(2i)、k(i)<=k(2i+1) i>=1
2、算法原理
思想:将无序的序列构建成大顶堆/小顶堆,最后将排好序的堆转换成大顶堆/小顶堆的层次遍历的序列,即为所求
步骤:
1)最大堆调整Max_Heapify:将堆的末端子节点作调整,使得子结点永远小于父结点,其目的是保持最大堆的性质,是创建最大堆的核心子程序
2)创建最大堆Build_Max_Heap:将堆所有数据重新排序。其目的是将一个数组改造成最大堆,接受数组和堆大小两个参数,Build-Max-Heap 将自下而上的调用 Max-Heapify 来改造数组,建立最大堆
3)堆排序HeapSort:移除位在第一个数据的根节点,并做最大堆调整的递归运算
3、举例
以 [62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]为例
1)先将上述序列从左往右存入完全二叉树中
2)构建 大顶堆
(1)先对子树进行调整,将其调整为大顶堆。找到完全二叉树最下方最小的以62为根节点的子树,先比较62的两个节点,93最大,由于62<93,所以交换62与93的位置。该子树的大顶堆构建完成
(2)以(1)的方式调整以47为根节点的子树,调整完成后,再分别调整以88和58为根节点的子树
(3)对整棵树进行调整,最终大顶堆构建完成
3)对大顶堆进行排序
(1)将大顶堆的第一个值(整个序列中最大的值)与大顶堆的最后一个值进行交换。
(2)交换后,最后一个值作为整个序列中最大值,将此值从大顶堆中剔除,再将剩下的元素再次进行调整为大顶堆。
4、算法实现
从小到大的排序思路:把一堆数字调整成大顶堆->堆顶元素和末尾元素交换->去掉末尾元素,继续大顶堆调整->重复以上动作
堆是通过数组实现的:
1)父结点i的左子节点的位置---(2*i+1)
2)父结点i的右子节点的位置---(2*i+2)
3)子结点i的父结点的位置---floor((i-1)/2) floor函数的作用是向下取整
func heapSort(_ array : inout Array<Int>){
//1、构建大顶堆
//从二叉树的一边的最后一个结点开始
for i in (0...(array.count/2-1)).reversed() {
//从第一个非叶子结点从下至上,从右至左调整结构
SortSummary.adjustHeap(&array, i, array.count)
}
//2、调整堆结构+交换堆顶元素与末尾元素
for j in (1...(array.count-1)).reversed() {
//将堆顶元素与末尾元素进行交换
array.swapAt(0, j)
//重新对堆进行调整
SortSummary.adjustHeap(&array, 0, j)
}
}
//调整大顶堆(仅是调整过程,建立在大顶堆以构建的基础上)
func adjustHeap(_ array : inout Array<Int>, _ i : Int, _ length : Int){
var i = i
//取出当前元素i
let tmp = array[i]
var k = 2*i+1
//从i结点的左子节点开始,也就是2i+1处开始
while k < length {
//如果左子节点小于右子节点,k指向右子节点
if k+1<length && array[k]<array[k+1]{
k += 1
}
//如果子节点大于父结点,将子节点值赋给父结点,不用进行交换
if array[k]>tmp {
array[i] = array[k]
//记录当前结点
i = k
}else{
break
}
//下一个结点
k = k*2+1
}
//将tmp值放到最终的位置
array[i] = tmp
}
5、时间复杂度
1)在构建堆的过程中,由于是是完全二叉树从最下层最右边非终端节点开始构建,将它与子节点进行比较,对于每个非终端节点而言,最多进行两次比较和交换操作,因此构建堆的时间复杂度为O(n)
2)在整个排序过程中,第 i 次去堆顶记录重建堆需要时间为logi ,并且需要取 n - 1次堆记录,所以重建堆的时间复杂度为O(nlogn)。
3)堆的时间复杂度为O(nlogn)
注:排序的具体实现代码在 SortSummary.swift 文件里 调用是在 ViewController.swift