1. 有一个文件,如何在不知道有多少行的情况下读取该文件,从中随机选择并输出一行
当我们读取第 i (i > 0) 行时,以 1 / i 的概率选择第 i 行,并替换掉原来选的行。
即总选择第一行,并以概率 1 / 2 选择第 2 行,以概率 1 / 3 选择第 3 行,依次类推。
到文件结束时,每个行被选中的概率都相等。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define MAX_LINE_LEN 4096 int main() { srand(time(NULL)); const char *filename = "input.txt"; FILE * file = fopen(filename, "r"); char line_buffer[MAX_LINE_LEN]; char selection[MAX_LINE_LEN]; int i = 1; while(fgets(line_buffer, MAX_LINE_LEN, file)) { if(rand()%i == 0) strcpy(selection, line_buffer); ++i; } puts(selection); fclose(file); return 0; }
简单推一下。
到
1 行时,没问题,跳过。
到第
2 行时,第
2 行被选中的概率是
1 / 2,那第
1 行被选中的概率也是
1 / 2 。
到第
3 行时,第
3 行被选中的概率是
1 / 3,第
1 行和第
2 行被选中的概率是
(1 / 2) * (2 / 3),依次递推。
到第
i 行时,第
1 ~ i 行每行被选中的概率都是
1 / i ,到文件最后一行也是这样。
2. 有一个文件,如何在不知道有多少行的情况下读取该文件,从中随机选择并输出k行(假设保证k小于文件总行数)
先读入第 1 ~ k 行保存,以后每次读入第 i 行,都以 k / i 的概率把刚读入的一行随机替换之前保存的 k 行中的一行。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define MAX_LINE_LEN 4096 int main() { int k = 5; srand(time(NULL)); char line_buffer[MAX_LINE_LEN]; char **selections = (char **)malloc(k*sizeof(char*)); for(int i = 0; i < k; ++i) selections[i] = (char *)malloc(MAX_LINE_LEN*sizeof(char)); const char *filename = "input.txt"; FILE * file = fopen(filename, "r"); // 先读取 1 ~ k 行 for(int i = 0; i < k; ++i) fgets(selections[i], MAX_LINE_LEN, file); int i = k+1; while(fgets(line_buffer, MAX_LINE_LEN, file)) { if(rand()%i < k) { // 随机替换已有的某一行 int j = rand()%k; strcpy(selections[j], line_buffer); } ++i; } for(int i = 0; i < k; ++i) puts(selections[i]); fclose(file); for(int i = 0; i < k; ++i) free(selections[i]); free(selections); return 0; }
简单推一下。
设
1 ~ i ( i >= k ) 行每行被选中的概率都为
k / i ,当我们读取第
i + 1 行时,以
k / (i+1) 的概率保留该行,并随机替换已保存的某一行(已保存的每行被替换掉的概率是
1 / k )。这样,第
i + 1 行被选取的概率是
k / (i + 1) ,其他行被选取的概率为
(
k / i ) * (1 - k / ( i + 1 ) ) + ( k / i) * (k / ( i + 1 )) * ( 1 - 1 / k ),
前面的是第
i + 1 行不被保留时的情况,后面的是第
i+1 行保留并把该行替换掉的情况,最终结果也是
k / ( i + 1 ),所以到第
i + 1 行为止,每行被选取的概率仍然都相同。到文件结尾同样满足。