堆的一些性质
若给定下标为 i 的节点,可得出:
1.父节点下标为 ( i - 1 ) / 2
2.左孩子节点下标为 2 * i + 1,右孩子节点下标为 2 * i + 1
思路:
- 建立堆
- 对堆进行排序操作
- 建大堆,选出最大的数
- 最大的数与最后一个数交换
- 如何选出次小的数?把最后一个数不看做堆里面的,进行向下调整,就可以选出次小的数(效率是O(logN)),以此类推,再重复上面的过程。
1.建堆
建立堆思路1:以插入的方法从头开始用向上调整算法进行调整
- 默认数组第一个数已经在堆里。
- 第二个数是56,比堆顶70要小,进行向上调整算法。
- 第三个数是30插入到56的右子树中,但是30比56小,所以进行向上调整算法。然后以此类推。
代码1:
#include <stdio.h>
#include <assert.h>
void AdjustUp(int* a,int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
int tmp = a[child];
a[child] = a[parent];
a[parent] = tmp;
child = parent;
parent = (child - 1) / 2;
}
}
}
// 对数组进行堆排序
void HeapSort(int* a, int n)
{
assert(a);
//升序
//方法1:构建小堆,类似于插入
for (int i = 1; i < n; ++i)
{
AdjustUp(a, i);
}
}
建立堆思路2:叶子所在的子树不需要调,倒着走最后一颗子树去进行调整
代码2:
void AdjustDown(int* a,int n, int parent)
{
assert(a);
int child = 2 * parent + 1;
while (child > 0)
{
if (child + 1 < n && a[child + 1] < a[child])
{
++child;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
// 对数组进行堆排序
void HeapSort(int* a, int n)
{
assert(a);
//方法2:类似于斐波那契数列
//叶子所在的子树不需要调,倒着走最后一颗子树去进行调整
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
}
2.对堆进行排序操作
排升序,建立大堆(后面再说为什么不建立小堆),假设大堆已经建立好了。
- 交换堆顶与最后一个数。
- 把最后一个数不看做堆里面的,进行向下调整。(图中两个红色圈圈为左、右子树,最后一个数没有看在堆里)
代码:
#include <stdio.h>
#include <assert.h>
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//调成大堆
void AdjustDown(int* a,int n, int parent)
{
assert(a);
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
// 对数组进行堆排序
void HeapSort(int* a, int n)
{
assert(a);
//方法2:类似于斐波那契数列
//叶子所在的子树不需要调,倒着走最后一颗子树去进行调整
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//对堆进行排序
//依次选数,调堆
for (int end = n - 1; end > 0; --end)
{
Swap(&a[end], &a[0]);
//再调堆,选出次小的数
AdjustDown(a, end, 0);//这里传end就会忽略最后一个数
}
}
完整代码:
#include <stdio.h>
#include <assert.h>
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//调成大堆
void AdjustDown(int* a,int n, int parent)
{
assert(a);
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
// 对数组进行堆排序
void HeapSort(int* a, int n)
{
assert(a);
//方法2:类似于斐波那契数列
//叶子所在的子树不需要调,倒着走最后一颗子树去进行调整
//O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//对堆进行排序
//依次选数,调堆
//O(N*logN)
for (int end = n - 1; end > 0; --end)
{
Swap(&a[end], &a[0]);
//再调堆,选出次小的数
AdjustDown(a, end, 0);//这里传end就会忽略最后一个数
}
}
int main()
{
int a[] = { 70,56,30,25,15,10,75 };
int len = sizeof(a) / sizeof(a[0]);
HeapSort(a, len);
for (int i = 0; i < len; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
结果截图
注意:为什么排升序的时候不建立小堆(排降序不建立大堆同理)
排升序,建小堆分析:
-
选出最小的数,放到第一个位置
-
如何选出次小的数呢?根据图,从数15位置开始,剩下的数看成一个堆,但是这之前建立好的堆关系全部都乱套了,只能重新建堆,才能选出次小的数。
堆的关系全乱了
-
建堆的时间复杂度O(N),然后选出次小的数需要N,N-1,…,1步,时间复杂度为O(N*N),这样看来还不如直接遍历选出。
-
建小堆排升序是可以的,但是效率太差了,没有体现出用堆的优势。
插个手动推导的图片吧,清晰点