编程珠玑中关于二分查找的使用

来源:互联网 发布:抓包分析软件 编辑:程序博客网 时间:2024/05/28 22:11

编程珠玑的第二章给我们提出了这样的一个问题。描述如下:

 “ 给定一个包含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;                      //文件中存储的数值的最小值应该大于等于leftlong long int right = 14;                     //文件中存储的最大值应该小于等于 rightlong 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;// 把包含遗漏的元素的那个文件中元素个数赋值给sizeofstream 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

另外程序参考了点击打开链接。





0 0
原创粉丝点击