堆的应用
1. 堆排序
1.1 具体步骤
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
- 建堆
升序:建大堆
降序:建小堆
- 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
排序的具体步骤:
- 交换堆顶元素与堆尾元素
- 交换后的堆尾元素(堆中最后一个元素不看作堆里面的), 向下调整排序
- 一次选出最大/最小, 次大/次小的元素…
注意: 下面的代码不全,没有AdjustDown的实现, AdjustDown实现见链接: 链接 ;下面代码实现的是升序, 要实现降序只需修改AdjustDown实现中的大小于号
1.2 参考代码
void HeapSort(int*a, int n)
{
//1. 向下调整建堆 --- O(n)
//建大堆 --- 升序
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //最后一个结点的父结点
{
AdjustDown(a, n, i);
}
//2. 排序 --- 堆顶元素与堆尾元素交换并向下调整 --- O(nlogn)
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
1.3 分析
建堆的时间复杂度: O(n)
排序的时间复杂度: 类似于向上调整算法 O(nlogn)
所以时间复杂度: O(n + nlogn), 即 O(nlogn)
空间复杂度: O(1)
稳定性:不稳定
2. TOP-K 问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
- 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆 - 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
2.1 实现思路
N个数找最大的前k个:
- 建立一个N个数的大堆, Pop k次(删除堆中的元素),取堆顶元素
- 建立一个k个数的小堆, 依次遍历数据, 若比堆顶数据大, 就替换堆顶元素, 再向下调整, 最后最大的k个元素就在这个小堆里面
推荐思路2
版本1
普通版本
动态开辟一个数组, 往数组中随机写入数据, 同时提前设置几个比较大的数, 建小堆,若比堆顶数据大, 就替换堆顶元素, 再向下调整, 最后最大的k个元素就在这个小堆里面
void topk1()
{
int n = 10000;
int k = 0;
printf("请输入k:>");
scanf("%d", &k);
int* a = (int*)malloc(sizeof(int) * n);
srand(time(0));
if (a == NULL)
{
perror("malloc fail");
return;
}
for (size_t i = 0; i < n; i++)
{
a[i] = rand() % 1000000;
}
//提前设置几个大的数
a[5] = 1000000 + 5;
a[10] = 1000000 + 7;
a[19] = 1000000 + 4;
a[28] = 1000000 + 6;
a[550] = 1000000 + 8;
a[9999] = 1000000 + 9;
a[400] = 1000000 + 1;
a[1000] = 1000000 + 2;
a[678] = 1000000 + 3;
a[3144] = 1000000 + 10;
//建小堆 --- 降序
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, k, i);
}
for (int i = k; i < n; i++)
{
if (a[i] > a[0])
{
Swap(&a[i], &a[0]);
AdjustDown(a, k, 0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
版本2
文件版本
创建一个数组, 将数据提前写入文件中后读取文件中的数据,建小堆将文件数据存入堆中, 通过比较大小找出topk 写入数组中
void topk2()
{
int minHeap[5];
FILE* fout = fopen("data.txt", "r"); //提前将数据写入文件, 以只读方式打开
srand(time(0));
if (fout == NULL)
{
perror("fopen fail");
return;
}
for (int i = 0; i < 5; i++)
{
fscanf(fout, "%d\n", &minHeap[i]); //读取文件中的数据到数组中
}
int k = 5;
//建小堆 --- 降序
for (int i = (k - 1 - 1) / 2; i >= 0; i--) //前k个数建堆
{
AdjustDown(minHeap, k, i);
}
//比较,找出前k个
int val = 0;
while (fscanf(fout, "%d", &val) != EOF)
{
if (val > minHeap[0])
{
minHeap[0] = val;
AdjustDown(minHeap, k, 0);
}
}
for (int i = 0; i < 5; i++)
{
printf("%d ", minHeap[i]);
}
printf("\n");
fclose(fout);
}
版本3
文件版本 + 造数据
创建一个数组, 将文件以写的方式打开,向文件中写入随机数,后开辟一个新的数组读取文件中的数据到数组,建小堆将文件数据存入堆中, 通过比较大小找出topk 写入数组中
void topk3()
{
//1. 造数据
int n,k;
printf("请输入n和k:>");
scanf("%d%d", &n, &k);
srand(time(0));
FILE* fin = fopen("data1.txt", "w");
if (fin == NULL)
{
perror("fopen fail");
return;
}
for (size_t i = 0; i < n; i++)
{
int val = rand() % 100000;
fprintf(fin, "%d\n", val);
}
fclose(fin);
//2. 找topk
FILE* fout = fopen("data1.txt", "r");
if (fout == NULL)
{
perror("fopen fail");
return;
}
int* a = (int*)malloc(sizeof(int) * k);
if (a == NULL)
{
perror("malloc fail");
return;
}
for (int i = 0; i <k; i++)
{
fscanf(fout, "%d\n", &a[i]);
}
//建小堆 --- 降序
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, k, i);
}
int val = 0;
while (fscanf(fout, "%d", &val) != EOF)
{
if (val > a[0])
{
a[0] = val;
AdjustDown(a, k, 0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d ",a[i]);
}
printf("\n");
fclose(fout);
}