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

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

  “ 给定一个包含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

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





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值