二叉树–堆(下卷)
如果有还没看过上卷的,可以看这篇,链接如下:
http://t.csdnimg.cn/HYhax
向上调整算法
堆的插⼊
将新数据插⼊到数组的尾上,再进⾏向上调整算法,直到满⾜堆。
💡 向上调整算法
• 先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
• 插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可
代码如下:
//交换两个元素
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//堆的向上调整算法
void AdjustUp(HPDataType* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)//此时已经在头结点的位置,不是在根节点,不用等于0
{
if (arr[child] < arr[parent])
{
Swap( &arr[parent],&arr[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
计算向上调整算法建堆时间复杂度
因为堆是完全⼆叉树,⽽满⼆叉树也是完全⼆叉树,此处为了简化使⽤满⼆叉树来证明(时间复杂度本 来看的就是近似值,多⼏个结点不影响最终结果)
分析:
第1层, 2 0 个结点,需要向上移动0层
第2层, 2 1 个结点,需要向上移动1层
第3层, 2 2 个结点,需要向上移动2层
第4层, 2 3 个结点,需要向上移动3层
…
第h层, 2 h−1 个结点,需要向上移动h-1层
则需要移动结点总的移动步数为:每层结点个数 * 向上调整次数(第⼀层调整次数为0)
由此可得:
💡 向上调整算法建堆时间复杂度为: O(n ∗ log n)
节点数量少的调整次数少;节点数量多的调整次数多
向下调整算法
堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后⼀个数据⼀换,然后删除数组最后⼀个数据,再进⾏ 向下调整算法。
向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。
💡 向下调整算法
• 将堆顶元素与堆中最后⼀个元素进⾏交换
• 删除堆中最后⼀个元素
• 将堆顶元素向下调整到满⾜堆特性为⽌
代码如下:
//堆的向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{
int child = 2 * parent + 1;//左孩子
while (child<n)
{
if (child + 1 && arr[child] > arr[child + 1])
{
child++;//比较孩子大小
}
if (arr[parent] > arr[child])
{
Swap( &arr[child],&arr[parent]);
}
else
{
break;
}
}
}
分析:
第1层, 2 0 个结点,需要向下移动h-1层
第2层, 2 1个结点,需要向下移动h-2层
第3层, 2 2 个结点,需要向下移动h-3层
第4层, 2 3 个结点,需要向下移动h-4层
…
第h-1层, 2 h−2 个结点,需要向下移动1层
则需要移动结点总的移动步数为:每层结点个数 * 向下调整次数
💡 向下调整算法建堆时间复杂度为: O(n)
节点数量少的调整次数多;节点数量多的调整次数少
堆的应⽤
堆排序
版本⼀:基于已有数组建堆、取堆顶元素完成排序版本
// 1、需要堆的数据结构
// 2、空间复杂度 O(N)
void HeapSort(int* a, int n)
{
HP hp;
for(int i = 0; i < n; i++)
{
HPPush(&hp,a[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
a[i++] = HPTop(&hp);
HPPop(&hp);
}
HPDestroy(&hp);
}
该版本有⼀个前提,必须提供有现成的数据结构堆
版本⼆:数组建堆,⾸尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次⼤的数据
void HeapSort(int* arr, int n)
{
//建堆
//升序--大堆
//降序--小堆
//向上调整算法
//for (int i = 0; i < n; i++)
//{
// AdjustUp(arr, i);
//}
// 堆的向下调整算法
for (int i=(n - 1 - 1) / 2; i >= 0; i--)//从最后一个节点的父节点开始调整
{
AdjustDown(arr, i, n);
}
//循环数据,每次减少一个进行对比
int end = 0;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr,0 ,end);
end--;
}
}
堆排序时间复杂度计算
分析:
第1层,20 个结点,交换到根结点后,需要向下 移动0层
第2层, 21个结点,交换到根结点后,需要向下 移动1层
第3层,22 个结点,交换到根结点后,需要向下 移动2层
第4层, 23个结点,交换到根结点后,需要向下 移动3层
…
第h层,2 h-1个结点,交换到根结点后,需要向 下移动h-1层
通过分析发现,堆排序第⼆个循环中的向下调整与建堆中的向上调整算法时间复杂度计算⼀致,此处 不再赘述。因此,堆排序的时间复杂度为 O(n + n ∗ log n) ,即 O(n log n)
💡 堆排序时间复杂度为: O(n log n)
TOP-K问题
思路如下:
TOP-K问题:即求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。
⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等
对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了 (可能数据都不能⼀下⼦全部加载到内存中)。最佳的⽅式就是⽤堆来解决,基本思路如下:
1)⽤数据集合中前K个元素来建堆
前k个最⼤的元素,则建⼩堆
前k个最⼩的元素,则建⼤堆
2)⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素
将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元 素
代码如下:
void CreateNDate()
{
// 造数据
int n = 100000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < n; ++i)
{
int x = (rand()+i) % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void topk()
{
printf("请输⼊k:>");
int k = 0;
scanf("%d", &k);
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
int val = 0;
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc error");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &minheap[i]);
}
// 建k个数据的⼩堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(minheap, k, i);
}
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
// 读取剩余数据,⽐堆顶的值⼤,就替换他进堆
if (x > minheap[0])
{
minheap[0] = x;
AdjustDown(minheap, k, 0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d ", minheap[i]);
}
fclose(fout);
}
哈哈哈相信你在数据结构的学习道路上又进了一步,继续努力加油加油!!!未来的你一定会感谢现在努力的自己。你在三四月做的事情,在七八月会有回报!!