堆排序(Heap Sort)
标签
非稳定排序、原地排序、比较排序
基本思想
直接选择排序中,第一次选择经过了$n - 1$次比较,只是从排序码序列中选出了一个最小的排序码,而没有保存其他中间比较结果。所以后一趟排序时又要重复许多比较操作,降低了效率。J. Willioms和Floyd在1964年提出了堆排序方法,避免这一缺点。
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆的性质
(1)性质:完全二叉树或者是近似完全二叉树;
(2)分类:大顶堆:父节点不小于子节点键值,小顶堆:父节点不大于子节点键值;
(3)左右孩子:没有大小的顺序。
(4)堆的存储
一般都用数组来存储堆,i结点的父结点下标就为$(i – 1) / 2$。它的左右子结点下标分别为$2 ∗ i + 1$和$2 ∗ i + 2$。如第0个结点左右子结点下标分别为1和2。
(5)堆的操作
- 建立: 以最小堆为例,如果以数组存储元素时,一个数组具有对应的树表示形式,但树并不满足堆的条件,需要重新排列元素,可以建立“堆化”的树。
- 插入: 将一个新元素插入到表尾,即数组末尾时,如果新构成的二叉树不满足堆的性质,需要重新排列元素,进行堆的调整。
- 删除: 堆排序中,删除一个元素总是发生在堆顶,因为堆顶的元素是最小的(小顶堆中)。表中最后一个元素用来填补空缺位置,之后树被更新以满足堆条件。
算法描述
- 步骤1:将初始待排序关键字序列$(R_1, R_2, …, R_n)$构建成大顶堆,此堆为初始的无序区;
- 步骤2:将堆顶元素$R[1]$与最后一个元素$R[n]$交换,此时得到新的无序区$(R_1, R_2, …, R_{n-1})$和新的有序区$(Rn)$,且满足$R[1,2…n-1] <= R[n]$;
- 步骤3:由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区$(R1,R2, …, Rn-1)$调整为新堆,然后再次将$R[1]$与无序区最后一个元素交换,得到新的无序区$(R_1, R_2, …, R_{n-2})和新的有序区(R_{n-1},R_n)。不断重复此过程直到有序区的元素个数为$n - 1$,则整个排序过程完成。
动图演示
时间复杂度
由于每次重新恢复堆的时间复杂度为$O(log n)$,共$n - 1$次堆调整操作,再加上前面建立堆时$n / 2$次向下调整,每次调整时间复杂度也为$O(log n)$。两次次操作时间相加还是$O(nlog n)$。故堆排序的时间复杂度为$O(nlog n)$。
最坏情况:如果待排序数组是有序的,仍然需要$O(nlog n)$复杂度的比较操作,只是少了移动的操作;
最好情况:如果待排序数组是逆序的,不仅需要$O(nlog n)$复杂度的比较操作,而且需要$O(nlog n)$复杂度的交换操作。总的时间复杂度还是$O(nlog n)$。因此,堆排序和快速排序在效率上是差不多的,但是堆排序一般优于快速排序的重要一点是,数据的初始分布情况对堆排序的效率没有大的影响。
平均情况:$O(nlog n)$
空间复杂度
没有额外的空间消耗。
算法示例
线性建堆【自下向上调整】
#include <stdio.h> #include <stdlib.h> #include <time.h> #define swap(a, b) { \ __type(a) __temp = a; \ a = b, b = __temp; \ } void downUpdate(int *arr, int n, int ind) { while ((ind << 1) <= n) { int temp = ind, l = ind << 1, r = ind << 1 | 1; if (arr[l] > arr[temp]) temp = l; if (r <= n && arr[r] > arr[temp]) temp = r; if (temp == ind) break; swap(arr[ind], arr[temp]); ind = temp; } return; } void heap_sort(int *arr, int n) { arr -= 1; //arr[0]->arr[1] for (int i = n >> 1; i >= 1; i--) { downUpdate(arr, n, i); } for (int i = n - 1; i > 1; i--) { swap(arr[i], arr[1]); downUpdate(arr, n - 1, i - 1); } return; } void output(int *arr, int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("]\n"); return; } int main() { srand(time(0)); #define MAX_N 20 int *arr = (int *)malloc(sizeof(int) * (MAX_N + 1)); for (int i = 0; i < MAX_N; i++) { arr[i] = rand() % 100; } output(arr, MAX_N); head_sort(arr, MAX_N); output(arr, MAX_N); #undef MAX_N return 0; }
参考资料:
https://blog.csdn.net/coolwriter/article/details/78732728
https://blog.csdn.net/weixin_41190227/article/details/86600821
https://www.cnblogs.com/itsharehome/p/11058010.html