堆的基本结构和堆的构造
堆是一个完全二叉树,分为大根堆和小根堆
- 大根堆:每个节点的值都大于或等于其子节点的值
- 小根堆:每个节点的值都小于或等于其子节点的值
根据完全二叉树的性质可以得出:
堆中一个数的左孩子下标 2index+1,右孩子下标 2index+2
堆中一个数的父节点 (index-1)/2
/*
* 堆,一个完全二叉树,分为大根堆和小根堆
* 堆中一个数的左孩子 2*index+1,右孩子 2*index+2
* 堆中一个数的父节点 (index-1)/2
*/
// 两数交换
void swap(int *arr, int s, int f)
{
int tmp = arr[s];
arr[s] = arr[f];
arr[f] = tmp;
}
// heapInsert 添加一个新的数进入堆中(以大根堆为例,小根堆类似) O(logN)
void heapInsert(int *arr, int index) // arr 数组, index 新插入的数的在数组中的位置
{
// 停止条件,1. index位置的数不大于他的父节点。2. 如果index是头结点(0位置),(index-1)/2也是0,同一位置的数相等,也不会大于
while (arr[index] > arr[(index-1)/2]) { // 当前新增加的数和他的父节点比较,如果大于他的父节点,则和他的父节点交换
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2; // 节点与父节点交换后,再以父节点跟父节点的父节点比较
}
}
// heapify 堆化。场景:返回并删除堆中某位置的数(依然保持堆结构) O(logN)
void heapify(int *arr, int index, int heapSize) // index 表示要删除的数的位置(已经和最后一个元素交换了,目前要重新堆化) heapSize 当前堆的大小
{
// 具体方法是将当前最后一个元素与要删除的元素交换,heapSize(堆的大小)-1,表示现在最后一个元素(要删除的元素)不在堆中了
// 要保持依然是堆,从0位置开始,与左右孩子中较大的比较,将较大值放到0位置,以此类推
int left = 2 * index + 1; // 左孩子
while (left < heapSize) { // 下方还有孩子(有左孩子就说明有孩子)
// 两个孩子中的较大值的下标,左孩子 left,右孩子 left+1
int largest = left + 1 < heapSize && arr[left+1] > arr[left] ? left + 1 : left;
// 父节点和较大孩子之间谁大
largest = arr[largest] > arr[index] ? largest : index;
// 如果index大,则不需要继续往下比较了
if (largest == index) {
break;
}
// 否则,交换,并且再向下比较
swap(arr, largest, index);
index = largest;
left = 2 * index + 1;
}
}
// 场景:随便修改堆中的某个值,执行heapInsert和heapify之后,肯定是堆
堆排序
堆排序原理: 把数组中的数进行大根堆化之后(heapSize为堆的大小)可以看出,第一个元素一定是最大的,我们把第一个元素和堆的最后一个元素进行交换,此时最大的元素已经到了他最终的位置,接下来把heapSize--,然后剩余的元素进行大根堆化,再与堆的最后一个元素交换,以此类推。直到heapSize=0时,表示已经排好序。 时间复杂度:O(NlogN)
void heapSort(int *nums, int len)
{
if (nums == NULL || len < 2) {
return;
}
int heapSize = len;
// 建立大根堆方法1:
/*
for (int i = 0; i < len; i++) { // O(N)
heapInsert(nums, i); // O(logN)
}
*/
// 建立大根堆方法2:
for (int i = len - 1; i >= 0; i--) { // O(N)
heapify(nums, i, len); // 从后往前,实际上后面的叶子节点没有叶子节点,不需进行额外操作,效率更高
}
// 首位最大的数和最后一个数交换,并且heapSize--
swap(nums, 0, heapSize - 1);
heapSize--;
// 循环处理,直到heapSize=0
while (heapSize > 0) { // O(N)
heapify(nums, 0, heapSize); // 0位置堆化 O(logN)
swap(nums, 0, heapSize - 1); // O(1)
heapSize--;
}
}
int main()
{
int nums[10] = {12, 34, 15, 2, 5, 14, 16, 10, 9, 1};
printf("Before sort: ");
for (int i = 0; i < 10; i++) {
printf(" %d", nums[i]);
}
printf("\n");
heapSort(nums, 10);
printf("After sort: ");
for (int i = 0; i < 10; i++) {
printf(" %d", nums[i]);
}
printf("\n");
return 0;
}