十、堆排序(不稳定)
步骤:1、构造初始化堆,将给定无序序列构造成一个大顶堆。
首先给定的是无序序列,从最后一个非叶子节点开始,第一个非叶子结点 arr.length/2-1 从左到右,从上到下及进行调整。
证明:n个节点,x个非叶子节点,y个叶子节点。x+y=n -> x=y-1 ,
初始状态:
进行从下到上的调整:(最后导致结构混乱,然后重新调整)
2、将堆顶元素与末尾元素进行交换,使末尾元素最大。将剩余元素重新构成一个堆,反复执行,得到一个有序序列。(每次组成大顶堆的时候,交换根节点和最后一个节点,然后最大的就放到最后,然后重新创建大顶堆)
堆是具有以下性质的完全二叉树:
(一般升序采用大顶堆,降序采用小顶堆)
每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
最后按照堆中的结点按层进行编号:
// 堆使用一个数组表示, 它可以当作一个完全二叉树,除了最底层之外,其它层都是满的,
// 并且最底层也是从左到右填充的。
//
// 当堆的下标(数组的下标)从1开始时比较好计算。因为:
// 1. 当父结点为i时, 左孩子为2i, 右孩子为2i+1;
// 2. 当孩子结点的下标为j时,父结点的下标为j/2 (根结点为父结点);
// 3. 一个结点的下标为k时, 则它所有的深度为 floor(logK);
// 4. 当一个堆共n个元素时,它的高度为floor(logN).
//
// 1
// / \
// 2 3
// / \ / \
// 4 5 6 7
// / \ / \ / \ / \
// 8 9 10 11 12 13 14 15
//
//
// 但是,数组的下标都是从0开始的,所以呢,我们下代码时,还是需要从0开始,而此时:
// 1. 当父结点的下标为i时,左孩子为2i+1, 右孩子为2*(i+1)
// 2. 当孩子结点的下标为j时,父结点的下标为(j-1)/2.
//
// 0
// / \
// 1 2
// / \ / \
// 3 4 5 6
// / \ / \ / \ / \
// 7 8 9 10 11 12 13 14
#define left(x) 2*x+1; //获得左节点在数组中的下标
#define right(x) 2 * (x + 1); //获得右节点在数组中的下标
void MaxHeap(int a[],int i,int low,int high) { // i 表示根节点。low和high表示这段数组
int l = left(i);
int r = right(i);
int largest; //保存数组中最大数的变量
// int tmp; //用于交换
//完成最大值的交换
if (l <= high && a[l] > a[i]) {
largest = l;
}
else {
largest = i;
}
if (r <= high && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
swap1(a[i],a[largest]);
MaxHeap(a,largest,low,high );
}
}
//建立一个堆 从下向上建立大顶堆
void BuildMaxHeap(int a[], int length) {
for (int i = length / 2 - 1; i >= 0; i--) {
MaxHeap(a, i, 0, length - 1);
}
}
//堆排序
void HeapSort(int a[],int length) {
BuildMaxHeap( a,length);
for (int i = length - 1; i >= 1; i--) {
swap1(a[0],a[i]); //交换堆顶元素和最后一个
MaxHeap(a,0,0,i-1);
}
}
///
参考:
https://blog.csdn.net/zhangsy_csdn/article/details/91483600
https://www.bilibili.com/video/BV1bz411e7vY
https://blog.csdn.net/aaa958099161/article/details/90923288
https://www.cnblogs.com/yinheyi/p/10836167.html