数组变堆:
上面的情况是在输入时直接对数组进行堆处理,下面是对一个给定的数组进行堆排序。
输入一个简单的无序数组:
int a[] = {9,0,3,5,6,7,8,2}
这个数组用二叉树表示为
改变此二叉树为堆有两种思路;第一种是向下调整,第二种是向上调整。
向下调整的思想为:
先对最后一个非叶子结点进行向下调整
对非叶子结点的上一个结点进行向下调整,直到调整到根节点结束
最终就会调整成一个最小堆。
向上调整思想:
代码实现:
//向上对数组建堆处理
void HeapInit1(int* a, int n) {
for (int i = 0; i < n; i++) {
AdjustUp(a, i);
}
}
//向下对数组建堆处理
void HeapInit2(int* a, int n) {
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
}
向上调整算法复杂度分析:
我们用一颗如图所示的满二叉树进行算法复杂度分析
第一层不用调整从第二层开始,第二层有21个结点,每个结点最坏的情况是调整1次**,所以**第二层次数为21*1,第三层有2^2个结点,每个结点最坏的情况是调整2次,以此类推。
假设向上调整次数设为T(h),T(h) = 2^1*1 + 2^2*2 + 2^3*3 + …… + 2^(h-1)*(h-1)
T(h) = 2^1*1 + 2^2*2 + 2^3*3 + …… + 2^(h-1)*(h-2)+ 2^h*(h-1)
2T(h) = 2^2*1 + 2^3*2 + …… + 2^(h-1)*(h-1)
用 2T(h) - T(h)
得到 T(h) = -2^0+(-2^1*1) + (-2^2)+(-2^3) + …… + (-2^(h-1)) + 2^h*(h-1)+ 2^0
T(h) = -(2^h-1) + 2^h*(h-1)+1 = 2^h*(h-2) + 2
结点个数N = 2^h-1, 层数h = log(N+1)
所以得到 T(n) = (N+1)(log(N+1)-2)+2
算法复杂度 O(n) = nlogn
向下调整复杂度分析:
第一层有20个结点**而最坏情况需要**向下移动h-1层**,第二层有**21个结点最坏情况下需要向下移动h-1层…倒数第二层有2^h-1个结点需要向下移动1层。
所以根据这种情况可以设移动的总步数为 T(n) = 2^0*(n-1) + 2^1*(n-2) + …… + 2^(h-2)*1
2T(n) = 2^1*(n-1) + 2^2*(n-1) + …… + 2^(h-2)*2 + 2^(h-1)*1
继续采用错位相减得到 T(n) = 2^h -1 -h = n - log(n+1) ≈ n
所以时间复杂度为 O(n) = n
排序:
我们采用向下调整这一思路,先对数组进行建堆,然后将堆的根节点与最后一个结点进行交换,最后对新的堆进行向下调整。
代码如下:
void HeapSort(int* a, int n) {
for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
AdjustDown(a, n, i);
}//向下调整建立大堆
size_t end = n - 1;
while (end > 0) {
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
**算法复杂度分析:**建堆的算法复杂度为O(N),每次交换然后再进行向下调整的算法复杂度为O(NlogN),所以堆排序的算法复杂度为O(NlogN),由于直接在数组上进行操作空间复杂度为O(1)。
TOP-K问题
TOP-K问题就是在N个数中找到前K个数,一般来说TOP-K问题数据量较大穷举会浪费资源,我们可以采用堆排序的思想解决此问题。
算法思路:
void TOP_K(int* a, int n, int k) {
int* tmp = (int*)malloc(sizeof(int) * k);
assert(tmp);
for (int i = 0; i < k; ++i)
{
tmp[i] = a[i];
}
// 建小堆
for (int j = (k - 1 - 1) / 2; j >= 0; --j)
{
AdjustDown_S(tmp, k, j);
}
for (int i = k; i < n; i++) {
if (tmp[0] < a[i]) {
tmp[0] = a[i];
AdjustDown_S(tmp, k, 0);
}
}
for (int i = 0; i < k; i++) {
printf("%d ", tmp[i]);
}
printf("\n");
free(tmp);
}
每次建立的堆的最小进行比对,最后堆中剩下的就是前K个数据。
算法复杂度分析:
先对K个数建堆算法复杂度为O(K),然后N-K个数和根数据比较,然后恢复堆属性,最坏情况下算法复杂度为O(log(K)*(N-K)),所以最后整个算法的算法复杂度为O(K+log(K)(N-K)),空间复杂度为O(1),和建立大堆pop出K个数据相比较由于K一般比较小所以算法复杂度较低。