1.top-k问题
1.1思路分析
TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大 。比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。对于 Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 ( 可能 数据都不能一下子全部加载到内存中) 。在我们之前的解决方法中,堆的物理结构都是数组,假如我们要在100亿个人中找出前十个有钱的人,暂且不论每个人的数据是结构体,就把每个人的数据看做一个整数。
100亿个整数有400亿个字节。我们把1000 000 000看做(1024^3),也就是十亿字节是一个g,四百亿字节就是40个g,一个简单的找前十有钱人功能就需要运行内存为40个g的电脑,空间需求未免也太大了。
我们以共有N个数据,找前k个举例 (N远大于k)
思路一:
假如电脑运行内存足够大,我们建立出了这样一个大堆,依据上文C语言数据结构基础笔记——树、二叉树简介-CSDN博客中的讲解,用向上遍历的向下调整算法建大堆,时间复杂度是O(n),再pop k次,由于每次pop都会经历向下调整算法,故总复杂度应该为N+klogN
思路二:
建立一个由k个树构成的小堆,保证根部是先存在堆中最小的数据,然后遍历数据,遇到比根更小的数据就交换并且实行向下调整算法,时间复杂度应N-k+k+Nlogk(+k是将k个数据建堆的消耗,N-k是遍历剩下元素的消耗)
对比两种思路,时间复杂度接近,但是思路二的空间复杂度为O(1),思路二的空间复杂度为O(N)
因此,思路二是最佳选择。
总结就是:找最大的k个用小堆,找最小的k个用大堆(这样才能挑出最不符合条件的来被替换掉)
1.2代码实现
我们以在文件中读取和输入为背景,实现将文件中的top_k个数据给输出
首先,作为程序员,一定是不能去手动给文件中输入一万个随机值作为测试用例的。
我们封装一段函数来创造样本,输入到本地的data.txt中去
void CreatFile(void) {const char* filename = "data.txt";int number = 100;FILE* pf = fopen(filename, "w");//初始化时间种子srand((unsigned int)time(NULL));for (int i = 0; i < number; i++) {fprintf(pf, "%d\n", rand());}fclose(pf);
}
先只创造100个即可
(创造测试用例也有许多技巧,在之后的测试中会有体现)
创造好样本后,我们在topk函数中建k个数的小堆,运用文件读写的方法先写入K个数据,再依次遍历并进行交换
void topk(int k,int number) {//建立数组空间HeapDataType* minheap = (HeapDataType*)malloc(sizeof(HeapDataType) * k);if (minheap == NULL) {perror("malloc fail!");exit(1);}//打开文件,将k个数据先放入数组const char* filename = "data.txt";FILE* pf = fopen(filename, "r");for (int i = 0; i < k; i++) {fscanf(pf, "%d", &minheap[i]);}//使用向上遍历的向下调整算法建小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--) {AdjustDown(minheap, k, i);}//此时k个数据的小堆已经建好,开始遍历数组for (int i = 1; i <= number-k ; i++) {int x = 0;fscanf(pf, "%d", &x);if (x > minheap[0]) {Swap(&x, &minheap[0]);AdjustDown(minheap, k, 0);}}fclose(pf);for (int i = 0; i < k; i++) {printf("%d ", minheap[i]);}
}int main() {int k = 0;int number = 0;printf("请输入k值和数据总量:\n");scanf("%d,%d", &k,&number);// CreatFile(number);topk(k,number);return 0;
}
我们再将样本调小,k输入5,number输入10
注意,输入数据时两个数据之间需要是英文的逗号,中文的逗号会被视作两个字符而导致number不能正确读入
由此可见,我们成功的完成了任务,输出了10个数中最大的五个。
可是有没有可能,我们的代码在数据量如此小的时候能跑过,当数据达到100万时会挂掉呢?
我们将k调到10,number调到100万,新的问题又出现了,我怎么才知道输出的就是最大的10个呢,100万个数据没法使用肉眼比较。
测试小技巧:
控制用例范围,
//将生成随机数的语句调整为
fprintf(pf, "%d\n", (rand() + i)%1000000);
再在文件中手动设置出最大的几个样本,用于检测.(随机加入了11111和23456或者123123等)
第一次使用creat函数,第二次使用topk函数 ,这样更便于调试
topk问题成功解决。