堆的定义
1、堆一定是完全二叉树(除了最后一层,其他层一定是满的)
2、堆分为“大根堆”、“小根堆”,“大根堆”的父节点一定大于叶子节点,“小根堆”的父节点一定小于叶子节点。
堆一般使用数组来存储,节省空间,可以不用存储左右子节点,从下标为1的数组开始,可以直接通过根节点 * 2 和 *2 + 1来访问。
建堆
建堆有两种方式,
第一种是从第一个父节点开始,不断往上堆化,直到根节点
void swapArr(int a[], int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
void Heapify(int a[], int i, int n) {
while (true) {
int max = i;
if (max * 2 <= n && a[max] < a[i * 2]) max = i * 2;
if (max * 2 + 1 <= n && a[max] < a[i * 2 + 1]) max = i * 2 + 1;
if (max == i)break;
swapArr(a, max, i);
i = max;
}
}
void BuildHeap(int a[], int n) {
for (int i = n / 2; i >= 1; --i) {
Heapify(a, i, n);
}
}
第二种是将第一个数作为堆,然后不断往堆尾插入数据,然后从下往上堆化,直到最后一个叶子节点,代码如下。
void InsertBuildHeap(int a[], int n) {
for (int i = 2; i <= n; ++i) {
while (i / 2 > 0 && a[i] > a[i / 2])
{
swapArr(a, i, i / 2);
i = i / 2;
}
}
}
堆的插入
首先将数据插入堆尾,然后自下而上堆化,参考第二种建堆方式。
删除堆顶
我们首先将堆尾数据覆盖掉堆顶数据,然后自上而下堆化
堆排序
如果前面的建堆和删除堆顶理解了后,堆排序也就很简单了,每一次堆化1到n的数据后,最大值或最小值都在根节点,将根节点和堆尾节点交换,然后继续堆化1到n-1的数据,一直到根节点结束。
void HeapSort(int a[], int n) {
BuildHeap(a, n);
int k = n;
while (k > 1) {
swapArr(a, 1, k);
--k;
Heapify(a, 1, k);
}
}
复杂度分析
1、建堆过程的时间复杂度是 O(n),排序过程的时间复杂度是 O(nlogn),堆排序整体的时间复杂度是 O(nlogn)。
2、排序过程中只涉及交换操作,生成常量的空间,所以是原地排序。
3、在排序过程中,每一次会将最后一个数据与第一个数据相交换,所以是不稳定排序。