开篇 A问题:
给定一个包含32位整数的顺序文件,它至多包含40亿个这样的整数,并且次序是随机的。请查找一个此文件不存在的32位整数(2^32>40亿,所以必然有遗漏)。内存空间只有上百字节以及若干备用文件的磁盘空间可以使用 。
2^32>40亿,所以必然有遗漏(这个貌似有个叫鸽巢原理的玩意)
就折腾了我差不多一个星期,本来如果按位图法的话,我们大约使用4*10(9阶)/(8*10(6阶))~~500MB内存的BITMAP;
就可以排序好,然后只要遍历这个BITMAP,找到第一个bitmap[i] ==0;就说明这个数不存在于这40亿中;
不过题目坑爹的限定了内存空间为几百Byte,;
那我们就只好想些巧妙的办法了;
不过如果你能通过看懂课后的答案,想通的话,你真的很牛;
解题的思路主要还是二分法;
不过我们需要跳出普通的思维,换句话说,二分法并不是什么情况下都需要排序后才能使用;
比如这题,作者就很巧妙的利用了二分法,解决了这个问题;
思路如下:
我们读取记录并从中取出一个数字,将高位为1,以及高位为0两类放到不同文件里(用低位也可以),这个过程不需要多少工作内存,几十个byte足够。(因为每次都只读取一个数字,当然我想如果你愿意的话也可以认真分析到底提供的内存为多少,而一次读够挤爆内存的小数据量也是可以的)
我们假设高位为1的放入文件1中;为0的放入0中;
(1)那么当1中数据量和0一样多时,说明两者中都有未包含的数据,任取一个即可;
(2)如果1数据量于0不同时,我们取数据量小的(可以使得接下来的二分需要处理的数据更少,数据量多的那个则有可能没有不存在的数);
然后递归前面的步骤,最终会找到一个不存在的数字;
那让我们把情况说得简单一些,假设一个文件里头有10个4bit的整数,
1 2 3 4 5 7 6 9 8 10
那么找出遗漏的数字。因为2^4=16 所以文件中的10个数字必定有遗漏
那么我取出一个数字,如果是最高位为1,那么放到一个文件1中,否则放到另外一个文件0中。
理论上最高位为1的4bit数字有多少?2^3=8个
在分拣到文件的过程中,程序有两个计数器,分别记录放入连个文件的数字的个数,如果其中有一个(也可能两个都是)分拣过去的个数小于8个,那么遗漏的数字肯定在这堆里头。
分拣出来
高位为0 1 2 3 4 5 6 7
高位为1 8 9 10
高位为0计数器为 7 高位为1计数器为3显然这两堆都遗漏了数字(比如第一堆遗漏了0)
选择遗漏的那一堆,如此再分拣次高位为1跟为0的……
将二分继续下去,继续二分的是8、9、10这一组,最终结果是这样的:
8(1000)、9(1001)会被分到文件0中,而10(1010)会被分到文件1中,如果没有遗漏的话,这两组都应该有2^1=2个数,但文件1这一组,只有一个数,所以遗漏的数应该在文件1中,而且这个遗漏的数,在这种情况下,只能是11(1011),这样就找到了一个遗漏的数。
1 2 3 4 5 7 6 9 8 10
那么找出遗漏的数字。因为2^4=16 所以文件中的10个数字必定有遗漏
那么我取出一个数字,如果是最高位为1,那么放到一个文件1中,否则放到另外一个文件0中。
理论上最高位为1的4bit数字有多少?2^3=8个
在分拣到文件的过程中,程序有两个计数器,分别记录放入连个文件的数字的个数,如果其中有一个(也可能两个都是)分拣过去的个数小于8个,那么遗漏的数字肯定在这堆里头。
分拣出来
高位为0 1 2 3 4 5 6 7
高位为1 8 9 10
高位为0计数器为 7 高位为1计数器为3显然这两堆都遗漏了数字(比如第一堆遗漏了0)
选择遗漏的那一堆,如此再分拣次高位为1跟为0的……
将二分继续下去,继续二分的是8、9、10这一组,最终结果是这样的:
8(1000)、9(1001)会被分到文件0中,而10(1010)会被分到文件1中,如果没有遗漏的话,这两组都应该有2^1=2个数,但文件1这一组,只有一个数,所以遗漏的数应该在文件1中,而且这个遗漏的数,在这种情况下,只能是11(1011),这样就找到了一个遗漏的数。
只是
如果这样解题的的话,我觉得结果会根据条件分支的不同,得到不同的遗漏的数。
(我不确定);
(磁盘文件可以交替使用(题:
若干备用文件的磁盘空间
),所以占用的空间也不大;)
PS:上网有看到如果涉及负数的话,下面是我看到的帖子;
(
另外再说一下,之所以我之前提出负整数的疑问,是考虑到操作系统的原因,现在大多数操作系统是32位的,int型的整数是32位的,表示范围为:-2^31~2^31-1,也就是int型数据最多能表示2^32=4294967296个数,所以这40亿个32位整数中,肯定会有负整数的。然而int型数据的负整数是用的补码存储的,补码的话,用这种方法会不会有问题?所以我又举了几个补码的例子,发现这种方法依然可行,只是要特别注意边界问题,呵呵,不知道说的有没有道理,待求证……
)
再:
C变位词问题:
直接贴代码吧,我用了c++ 里的
map<string,set<string>>;
#include<map>
#include<vector>
#include<set>
#include<string>
#include<iostream>
#include<cstring>
using namespacestd;
intmain()
{
map<char,int> a;
map<string,set<string> >b;
string s;
int i;
while(cin>>s&& s!="000")
{
stringhr(100,'\0');
for (i=0;i<s.size(); i++)
{
a[s[i]]++;
}
map<char,int>::iterator it = a.begin();
i = 0;
while (it !=a.end())
{
hr[i++]=it->first;
hr[i++]=char(it->second+48);
it++;
}
b[hr].insert(s);
a.clear();
i=0;
}
map<string,set<string>>::iterator it = b.begin();
while(it !=b.end())
{
cout<<it->first<<"";
set<string>::iteratork= it->second.begin();
while(k!=it->second.end())
{
cout<<*k<<"";
k++;
}cout<<endl;
it++;
}
return 0;
}
第八题,很普通的一道题;
n元整数集合,求是否存在一个k元子集,其元素之和不超过t?
很简单,遍历就好;
不过嘛;
经过前面变位词算法的折腾,好像有点开窍,
我是这样考虑的,我们先堆排序(我现在暂时脱书只会写这个,或者你用c++的sort也不错);
因为是从小到大,所以0~k-1的元素相加为s,只要s小于t,则至少说明存在一个k元子集之和不超过t;
2011年11月13日