一、什么是堆
- 堆实际上是一棵完全二叉树;
- 对于n个关键字序列 [ K0,K1,…,K(n-1) ],一般采用数组方式存储;
- 任何一非叶节点的关键字满足如下性质:
- 若ki<=k(2i+1)且ki<=k(2i+2)(0≤i≤ n-1),称之为小根堆;
- 若ki>=k(2i+1)且ki>=k(2i+2)(0≤i≤ n-1),称之为大根堆.
二、堆排序
堆排序思想 充分利用了堆顶关键字最大或最小的特性,从而可以快速选取一组数中的最大值或最小值。
堆排序分为两个步骤:构堆建 和 调整堆。
以大根堆为例,堆排序过程如下:
- 将初始待排序关键字序列(R0,R1....Rn-1)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素与堆尾元素交换,堆的长度减1, 此时得到新的无序区和新的有序区;
- 由于交换后新的堆顶可能违反堆的性质, 因此需要对当前无序区调整为新堆;
- 不断重复过程2和3直到有序区的元素个数为n-1,则整个排序过程完成
三、算法实现
堆的基本操作分为向上调整和向下调整,利用向上调整操作构建堆,堆顶元素和堆尾交换后,堆长度减1,利用向下调整操作从堆顶开始调整生成新堆。
#include <iostream>
using namespace std;
inline void swap(int arr[], int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 向上调整堆 :从堆尾开始向堆顶调整
// arr : 堆数组指针
// n : 堆尾元素位置
void shiftUp(int arr[], int n) {
if (n < 1) {
return;
}
int b = (n - 1) / 2;
int a = n;
while (a > 0) {
if (arr[a] > arr[b]) {
swap(arr, a, b);
a = b;
b = (b - 1) / 2;
} else {
break;
}
}
}
// 向下调整堆 :从堆顶开始向堆尾调整
// arr : 堆数组指针
// n : 堆尾元素位置
void shiftDown(int arr[], int n) {
if (n < 1) {
return;
}
int a, b = 0;
int maxLeaf = (n - 1) / 2;
while (b <= maxLeaf) {
a = 2 * b + 1;
if (a < n && arr[a] < arr[a + 1]) {
a++;
}
if (arr[b] < arr[a]) {
swap(arr, b, a);
b = a;
} else {
break;
}
}
}
// 堆排序算法
// arr : 待排序的数组指针
// len : 数组长度
void heapSort(int* arr, int len) {
int index;
// 构建堆
for (index = 1; index < len; index++) {
shiftUp(arr, index);
}
// 调整堆
for (index = len - 1; index > 0;) {
swap(arr, 0, index);
index--;
shiftDown(arr, index);
}
}
// 测试算法
int main() {
int arr[] = { 213, 43, 43, 123, 45, 52, 67, 234, 452, 5, 67 };
int len = 11;
cout << "Before sorting:" << endl;
for (int i = 0; i < len; i++) {
cout << arr[i] << "\t";
}
cout << endl << endl;
heapSort(arr, len);
cout << "After Sorting:" << endl;
for (int i = 0; i < len; i++) {
cout << arr[i] << "\t";
}
cout << endl;
return 0;
}
执行结果如下:
四、算法分析
堆排序时间复杂度最好和最坏的情况下都是: O(n*log2(n)),堆排序空间复杂度为: O(1)
若关注排序最坏的情况,宜选择堆排序。