制作营销型网站公司怎么在百度推广自己的公司
文章目录
- 1. 堆排序
- 1)建堆的思考(时间复杂度计算)
- 2)利用堆删除思想来进行排序
- 3)实现堆排序(升序-大堆)
- 4)运行效果
- 5)分析建堆的时间复杂度
- 一)向上调整算法【最终近似O(NlogN)】
- 二)向下调整算法【最终近似O(N)】
- 三)选数字排序【O(NlogN)】
- 2. TOP-K问题(小堆)
- 1)基本思路
- 2)实现代码,要建立小堆
- 3)运行效果
1. 堆排序
堆排序可以帮助选数,本质是选择排序。
1)建堆的思考(时间复杂度计算)
利用向下调整算法和向上调整算法完成建堆,第一个数作为一个堆,后面的数依次进行插入,本质是模拟堆插入的过程
升序:建大堆;降序:建小堆
为什么是这样子的呢?拿升序建大堆为例进行讲解:
假设给出一个数组,要使用堆排序实现升序,如果这时建小堆,那么数组第一个数是最小的,那后面的数就需要重新建堆,而一次建堆的时间复杂度本身就是
O(NlogN)
,还需要重复n
次这样的操作,即使看上去高大上,但实际效率十分低。
而采用大堆进行升序时,能够确定最大的数,这样就可以对数组首尾交换,从而实现固定最大的数字。这样原本的堆再利用堆删除的思想,就可以保证堆的关系不会乱,时间复杂度此时变成了:建堆
O(NlogN)
,选数O(N-1)logN
,总的时间复杂度就是相加,就变成了O(NlogN)
,这个算法的量级也就下降了(时间复杂度是判定算法的量级,即使相同时间复杂度的算法,他们的量级也会有区别)
那还有更高效的建堆方法吗?答案就是直接利用向下调整算法!
从倒数第一个非叶子,即最后一个节点的父亲开始调整,这样可以减少一般的时间复杂度,而且调整的方式也会改变,从而使其更快
建堆的两个时间复杂度将在最后进行分析。
2)利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序,下面将根据上述的思路进行升序
3)实现堆排序(升序-大堆)
依托上面堆实现的代码,但要修改向上调整算法和向下调整算法的判定条件为大堆。
#include "Heap.h"void HeapSort(int* a, int n)
{// 建大堆 把a[0]当作一个大堆,其他数据插入 O(NlogN)//for (int i = 1; i < n; i++)//{// // 修改判定条件为大堆,调整符号// AdjustUp(a, i);//}// 只利用向下调整算法,效果更高,时间复杂度是 O(N)// 从倒数第一个非叶子,即最后一个节点的父亲for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}// 升序使用向下调整算法,用堆删除的思路调整堆// O(NlogN)int end = n - 1;while (end > 0){// 先排序大的数据,大的往后面先排好序Swap(&a[0], &a[end]);// 修改判定条件为大堆,然后调整堆AdjustDown(a, end, 0);end--;}
}int main()
{int a[] = { 2,7,0,21,56,786,1,3 };HeapSort(a, sizeof(a) / sizeof(int));for (int i = 0; i < sizeof(a) / sizeof(int); i++){printf("%d ", a[i]);}printf("\n");return 0;
}
4)运行效果
5)分析建堆的时间复杂度
首先要知道满二叉树有以下结论:
2^h-1 -> h=log(N+1)
对于建堆的两种办法:
一)向上调整算法【最终近似O(NlogN)】
二)向下调整算法【最终近似O(N)】
三)选数字排序【O(NlogN)】
2. TOP-K问题(小堆)
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中),因此最佳的方式就是用堆来解决
1)基本思路
思路1: N个数插入大堆里面, Pop k次,时间复杂度是 O(N*logN+K*logN) -> O(N*logN)
,当N远大于k,且N很大时,空间占用会很高,内存不够用,因此方案不太行
思路2: 首先读取前k个值,建立k个数的小堆,再一次读取后面的值,跟堆顶比较;如果比堆顶大,替换堆顶进展,再向下调整。此时的时间复杂度就变成了 O(N*logK)
下面将模拟实现思路2的场景:10亿个随机数字找出最大的前10个。首先向文件写入随机数字,用 fprint
写入文件,再用 fscanf
读取建立小堆,之后开始遍历。为了检验是否有错误,手动进入文件修改5个数字,使其大于10000000000(一百亿),如果5个数都大于10000000000,就证明思路实现正确。
2)实现代码,要建立小堆
#include "Heap.h"void CreateNDate()
{// 造数据int n = 10000000000;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){// +i可以防止过多数据重复int x = (rand() + i) % 10000000000;fprintf(fin, "%d\n", x);}fclose(fin);
}void PrintTopK(const char* file, int k)
{FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen fail\n");exit(-1);}// 要先对向上/向下调整算法判定条件修改// 开辟小堆空间int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail\n");exit(-1);}// 读取前k个数建一个k个数小堆for (int i = 0; i < k; ++i){fscanf(fout, "%d", &minHeap[i]);AdjustUp(minHeap, 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]);}printf("\n");// 使用完就释放空间free(minHeap);minHeap = NULL;// 要记得关闭文件fclose(fout);
}int main()
{// 一百亿个数据要跑很久,可以将数据量减少CreateNDate();PrintTopK("data.txt", 6);return 0;
}
3)运行效果
以上就是堆的全部内容啦!!