第 2 章 第 2 题 找“ 重数/漏数 ”问题 二分法实现

问题分析

  输入:一个包含了4 300 000 000个32位整数的文件( 已排序 其中可能有重复出现的数字 )

  输出:一个在这个文件中重复出现过了的数字

  约束:内存消耗应尽量小

解答思路

  分析题目,可以得知:

  1. 32位整数最多能包含4294967296个整数 < 目标文件中整数的个数( 43亿 ),因此可知必然存在重复的数字

  2. 我们只需要找出一个重复出现过的整数即可

  我们可以首先将文件拆分为两个部分,一个部分包含区间 [0 - 2150000000) 的数,另一部分包含区间 [2150000000 - 4300000000) 的数。那么这两个区间必然有一个区间中存在的数的个数多于2150000000。然后,我们可以接着分下去。假设是区间 [0 - 2150000000) 中有大于2150000000个数字,那么它的子区间[0 -  1075000000) 和  [1075000000 - 215000) 中必然也有一个其中的数的个数多于1075000000。如此继续拆分区间直到找出一个只有一个元素的区间为止 - 它就是我们要找的那个整数。

效率分析

  空间:主要占用的是磁盘空间,对内存空间需求很小。

  时间:T(n) = T(n/2) + n,故时间复杂度为 O(log2N)。N为输入文件中整数的个数。但要注意:处理的对象是磁盘文件,故不要对时间效率有什么期望。

代码实现

  说明:为了方便测试,下面代码只用于查找一个包含10个以上整数的文件( 且每个数都小于等于9 )。如果需要换成正式版代码,请将宏N预设为2150000000,min初始化为0,max初始化为4299999999( 详见代码的注释部分 ):

  1 #include <iostream>
  2 #include <fstream>
  3 #include <string>
  4 
  5 using namespace std;
  6 
  7 // 正式版修改: N改为2150000000
  8 #define N 4 
  9 
 10 /*
 11  * 该函数将文件流io对应文件覆盖拷贝到文件流iot所对应文件
 12  * 最后再重置文件流
 13 */
 14 void copyfile(fstream &io, fstream &iot, int t)
 15 {
 16     int num;
 17     string filename = "data";
 18     string temfile1 = "1";
 19     string temfile2 = "2";
 20 
 21     io.close();
 22     io.clear();
 23     io.open(filename.c_str(), ofstream::out | ofstream::trunc);
 24     iot.close();
 25     iot.clear();
 26     if (t == 1) {
 27         iot.open(temfile1.c_str());
 28     }
 29     if (t == 2) {
 30         iot.open(temfile2.c_str());
 31     }
 32 
 33     while (iot >> num) {
 34         io << num;
 35         io << " ";
 36     }
 37 
 38     io.close();
 39     io.clear();
 40     io.open(filename.c_str());
 41     iot.close();
 42     iot.clear();
 43     if (t == 1) iot.open(temfile1.c_str(), ofstream::out | ofstream::trunc);
 44     if (t == 2) iot.open(temfile2.c_str(), ofstream::out | ofstream::trunc);
 45 }
 46 
 47 int main()
 48 {
 49     fstream io, io1, io2;
 50     
 51     /*
 52      * 打开三个文件,data是数据文件,1和2是两个空文件用来暂存数据。
 53     */
 54     string filename = "data";    
 55     string temfile1 = "1";
 56     string temfile2 = "2";
 57     io.open(filename.c_str());
 58     if (!io) return 1;
 59     io1.open(temfile1.c_str(), ofstream::out);
 60     if (!io1) return 1;
 61     io2.open(temfile2.c_str(), ofstream::out);
 62     if (!io2) return 1;
 63 
 64     // 正式版修改: n = 2150000000, max = 4299999999, min = 0。
 65     long long n = 4, num, max = 9, min = 0;
 66     long long p=0, q=0;
 67     int tag = 0;
 68 
 69     while (1) {
 70         p=0; q=0;
 71         /*
 72          * 将文件分为两部分,小于等于n的数存放进文件1,大于n的存放进文件2。
 73         */
 74         while (io >> num) {
 75             if (num <= n) {
 76                 p++;
 77                 io1 << num;
 78                 io1 << " ";
 79             }
 80             else if (num > n) {
 81                 q++;
 82                 io2 << num;
 83                 io2 << " ";
 84             }
 85         }
 86     
 87         /*
 88          * 将下一次循环要处理的数据转移到data中去
 89         */
 90         if (p > (n-min+1) ) {
 91             copyfile(io, io1, 1);
 92             max = n;
 93             n = (min + max)/2;
 94         }
 95         else if ( q > (max-n) ) { 
 96             copyfile(io, io2, 2);    
 97             min = n;
 98             n = (min + max)/2;
 99         }
100 
101         if (p ==0) {
102             io2 >> num;
103             cout << "Find: " << num << endl;
104             return 0;
105         }
106         if (q == 0) {
107             io1 >> num;
108             cout << "Find: " << num << endl;
109             return 0;
110         }
111 
112         /*
113          * 当最后分到的区间只有两个单位长度:
114          * 我通过设定标记,让数据再分最后一次后,data文件中的数据必然就是搜索到的结果。输出结果即可。
115         */
116         if (max == (min +1)) {
117             tag++;
118             if (tag != 2) continue;
119             io >> num;
120             cout << "Find: " << num << endl;
121             io.close();
122             return 0;
123         }        
124 
125         /*
126          * 重置两个暂存文件的文件流
127         */
128         io1.close();
129         io1.clear();
130         io1.open(temfile1.c_str(), ofstream::out);
131         io2.close();
132         io2.clear();
133         io2.open(temfile2.c_str(), ofstream::out);
134     }
135 
136     return 0;
137 }

运行测试

  1. 先建立好三个文件:

  2. 其中1和2是两个空文件,只是用来暂时存放数据的,而data存放我们需要处理的数据,其内容如下( 测试版程序 ):

  3. 编译源代码并运行:

说明

  1. 该程序要反复对上GB甚至十几GB的文件进行IO操作,故程序时间效率不高。另一方面想,需要处理的数据如此庞大,而所用内存只几百KB,自然要付出很大的时间代价

  2. 程序代码有些复杂,虽然我测试的几组数据都正确运行了,但也许还有漏洞,恳请读者指正。

原文地址:https://www.cnblogs.com/scut-fm/p/3332526.html