编程珠玑的第二章给我们提出了这样的一个问题。描述如下:
“ 给定一个包含32位整数的顺序文件,它至多包含40亿个这样的整数,并且次序是随机的。请查找一个此文件不存在的32位整数。”
根据鸽巢原理,32为整数,可以有2^32>40亿个数,所以一定存在一个整数,它在这40亿个整数中不存在。这里分两种解法。1:在内存充足的情况下,我们可以使用位图的方法,初始化2^32个位,都为零,依次读入40亿个整数,整数存在,位标1,不存在标0,最后检查位图中为零的下标,就是我们遗漏的数。但是这样做的缺点很明显。程序至少需要2^32/8个字节。相当于500MB内存。2:在内存只有几百个字节的情况下,书中给了我们解决方法,使用二分法(这里的二分法不要求文件中的元素有序,很巧妙的使用哦!)
首先给出文件中最大数和最小数。对于一个32位整数(为了方便,我们默认为无符号的,有符号的比较复杂,我没有想出解决办法哈。囧)最小值是0,最大值是2^32-1。使用二分法解决上述问题步骤如下:
step1:依次读入顺序文件(这里我要特别说明下,这里的顺序文件是指数据的存储方式是顺利的,并不是里面的数据是有序的!我看过很多人写的关于这道题的答案,都是默认数据有序。这是不对的,也是违背了作者的本意)
step2:判断读入的数据在二分法的哪个区间,如果读入的数据left=<temp && temp<= right,那么这个数属于左区间,写入到文件file1中,否则写入文件file2中
step3:判断file1和file2中那个元素少,元素少的那个一定包含我们需要找的遗漏数。那我们就对这个包含元素少的文件重新使用二分法。直到最后的文件中包含的元素只有一个,那么这个元素-1就是我们需要找的那个数(其实遗漏的数字很多,但是使用这个方法只可以找出一个出来)。
举个例子来说。为了方便,我们用数组举例子int a[9]={0,1,2,4,5,6,7,8,9}.我们使用上述方法的求解过程如下:这里left = 0,right = 9,第一次使用二分法,程序把数组分为两个部分。第一部分是{0,1,2,4,},第二部分是{5,6,7,8,9}。由于第一部分的元素较少。那么对第一部分继续使用二分法。注意这时候的right = (left+right)/2 = 4.5.。继续使用二分法,将{0,1,2,4}又分为两个部分{0,1,2}和{4},找到最终的文件{4},只包含一个元素,这个元素左边的数,即4-1就是3,即是我们需要找的数。程序的复杂度是o(n+n/2+n/4+n/8+n/16) = o(2n)。
代码如下:
#include <iostream>
#include <fstream>
#include <cmath>
#include <cstdlib>
using namespace std;
int main()
{
//分别是读入的文件,写入的两个文件名
string infilename = "file.txt"; //初始化读入文件的名称.
long long int temp;
long long int left = 0; //文件中存储的数值的最小值应该大于等于left
long long int right = 14; //文件中存储的最大值应该小于等于 right
long int non_existent; //这个就是文件中不存在的那个数字,找到并输出
int file1count,file2count; //记录二分法,两个部分中每个含有的元素个数
int size; //record the final number of the file.while size < 1,break;
while(true){
//每次重新读取文件的时候,两个二份文件的数目都要归零.
file1count = file2count = 0;
ifstream infile(infilename); //每次读入的文件都是经过二分法后元素较少的那个文件,当然第一次读取的是整个文件
if (!infile.is_open()){
cerr << "读取文件操作失败" << endl;
exit(0);
}
ofstream outfile1("file1.txt"); //将经过二分法筛选的元素分别存入file1.txt和file2.txt中
ofstream outfile2("file2.txt");
if (!outfile1.is_open() || !outfile2.is_open()){
cerr << "写入文件操作失败" << endl;
exit(0);
}
while(infile >> temp){
if (temp >= left && temp <= (left+right)/2){
file1count ++;
outfile1 << temp << " ";
}
else {
file2count ++;
outfile2 << temp << " ";
}
}
infile.close();
outfile1.close();
outfile2.close();
if (file1count < file2count){ //遗漏的整数在outfile1中至少包含一个
size = file1count; // 把包含遗漏的元素的那个文件中元素个数赋值给size
ofstream outfile("temp.txt",ios::out); //并且把包含遗漏元素的文件作为新的读入文件
ifstream infile("file1.txt",ios::in|ios::_Nocreate);
if (!outfile.is_open() || !infile.is_open()){
cerr << "读取文件出现错误";
exit(1);
}
right = (left + right)/2; //二分法的经典步骤
while(infile >> temp)
{
outfile << temp << " ";
}
outfile.close();
infile.close();
}
else { //同理
size = file2count;
ofstream outfile("temp.txt",ios::out);
ifstream infile("file2.txt",ios::in|ios::_Nocreate);
if (!outfile.is_open() || !infile.is_open()){
cerr << "读取文件出现错误";
exit(1);
}
left = (left + right)/2;
while(infile >> temp)
{
outfile << temp << " ";
}
outfile.close();
infile.close();
}
infilename = "temp.txt";
if (size <= 1){ //当文件里面的整数小于一个时候,循环结束了。这时候需要找出,漏掉的那个数,可能不止一个,需要判断漏掉的那个数是在temp.txt存贮的左边还是右边.然后输出,经过测试应该是左边。哈哈,程序完成了
infile.open("temp.txt");
if (!infile.is_open()){
cerr << "读取文件失败";
exit(1);
}
infile >> temp; //当最终二分到只剩下一个元素时,将这个元素减去一就是我们需要找的那个文件中没有的那个数
non_existent = temp - 1;
break;
}
}
cout << "找到一个文件中不存在的数: " << non_existent << endl;
return 0;
}
程序运行需要的内存很小哦。虽然我没用四十亿个数据测试,但是我用的小数据测试结果是正确的。如果错误,敬请提出,谢谢,QQ1527927373
另外程序参考了点击打开链接。