①概念
1)堆的结构:
1.物理结构 :一维数组
2.逻辑结构 :一颗完全二叉树
2)堆的特性:
1.结构性 :用数组表示的完全二叉树
2.有序性 :任一结点的关键字是对于其子树所有结点的最大值(最小值)
·大顶堆:双亲结点 大于 左右子树上所有结点
·小顶堆:双亲结点 小于 左右子树上所有结点
3.双亲结点与左右孩子结点的关系:
leftchild = parent * 2 + 1
rightchild = parent * 2 + 2
parent = (child - 1) / 2
②堆排序:
既然堆顶结点具有或是最大值或是最小值 的性质,我们也可以利用这一性质来对一个数组进行排序。
1)排序理念:
将给定数组 不断 调整为大顶堆(小顶堆),每一次调整,都能获取到最大值(最小值),将最大值(最小值)放到堆尾 ,对剩余其他结点再次调整,反复进行,直到排成升序(降序)
2)向下调整算法(AdjustDown)
①目的 :给定一个结点,将 以该结点为根结点的完全二叉树 调整成为 大顶堆(小顶堆),且最多调整 这棵完全二叉树的高度次: log2N
②前提条件 :必须满足该结点的左右子树均为大顶堆(小顶堆)
void AdjustDown(int* a, int n, int root){
int parent = root;
//child:左右孩子中较小的孩子的下标,默认为左孩子的下标
int child = 2 * root + 1;
//当双亲结点不是叶子结点时 ,进行循环
while (child < n){
//如果 右孩子存在 并且 右孩子比左孩子小,则将child置为右孩子的下标
if ((child + 1) < n && a[child] > a[child + 1]){
child = child + 1;
}
//将较小孩子与双亲结点比较
if (a[child] < a[parent]){
swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else{
break;
}
}
}
3)堆排序步骤
1.建堆 :o(n)
由于一开始给你的数组所形成的的完全二叉树并不一定符合堆的要求,考虑到向下调整算法的前提条件,我们可以考虑从最后一个非叶子结点开始,逐步将 子树 调整为堆,最后使得整个完全二叉树变成堆。
2.调换-向下调整-调换:o(n*logn)
1)建堆完成后,堆顶即最大值(最小值),将堆顶与堆尾对调,对 除堆尾之外的其他所有结点组成的树 调用AdjustDown, 给定的节点 为 堆顶节点 (此时只有堆顶结点与其左右子树可能不满足堆的要求,树中其他结点一定满足)
2)调用AdjustDown后,又堆顶又得到了最大值(最小值),重复1)中步骤,当树中只剩一个结点时,排序完成
-排升序:建大堆
-排降序:建小堆
void HeapSort(int* a, int n){
//①对任意一个数组建立成小顶堆(建堆复杂度为 o(n))
//从最后一个结点的双亲结点开始调,此结点之后的结点全是叶子结点,无需调用AdjustDown
for (int i = (n - 1 - 1) / 2; i >= 0; i--){
AdjustDown(a, n, i);
}
//②排序
int end = n - 1;
while (end > 0){
//先将堆顶最小的数换到最后面
swap(&a[0], &a[end]);
//再将最小的数去掉,对剩余的数再次建立小顶堆,选出次小的数
AdjustDown(a, end, 0);
end--;
}
}
堆排序时间复杂度为1) 2)过程之和:o(n+nlogn) = o(nlogn)