diff --git a/ebook/zh/12~15.0.md b/ebook/zh/12~15.0.md index 85e40bdd3..eed34f905 100644 --- a/ebook/zh/12~15.0.md +++ b/ebook/zh/12~15.0.md @@ -26,9 +26,9 @@ ###题目如下 -7、腾讯面试题:给40亿个不重复的unsignedint的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中? +7、腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中? -__分析__:1个unsigned int占用4字节,40亿大约是4G个数不到,那么一共大约要用16G的内存空间,如果内存不够大,反复和硬盘交换数据的话,后果不堪设想。 +__分析__:1个unsigned int占用4字节,40亿大约是4G个数,那么一共大约要用16G的内存空间,如果内存不够大,反复和硬盘交换数据的话,后果不堪设想。   那么怎么储存这么多的数据呢?还记得伴随数组么?还是那种思想,利用内存地址代替下标。 @@ -42,11 +42,11 @@ __分析__:1个unsigned int占用4字节,40亿大约是4G个数不到,那   那么69可以表示0.2.6三个数存在,其余的7以下的数不存在,0表示0-7都不存在,255表示0-7都存在,这就是位图算法:通过全部置0,存在置1,这样一种模式来通过连续的地址存贮数据,和检验数据的方法。 -  那么1个 unsigned int代表多少个数呢?1个unsigned int 是一个2^32以内的数,那么也就是这样的1个数,可以表示32个数是否存在。同理申请一个unsigned int的数组a[n]则可以表示连续的(n+1)*32的数。也就是a[0]表示0-31的数是否存在,a[1]表示32-63的数是否存在,依次类推。 +  那么1个 unsigned int代表多少个数呢?1个unsigned int 是一个2^32以内的数,那么也就是这样的1个数,可以表示32个数是否存在。同理申请一个unsigned int的数组a[n]则可以表示连续的 n*32的数。也就是a[0]表示0-31的数是否存在,a[1]表示32-63的数是否存在,依次类推。   这时候需要用多大的内存呢? -    16g/32=512M +    16G/32=512M   512M和16G之间的区别,却是是否一个32位寻址的CPU能否办得到的事儿了,众所周知,32位CPU最大寻址不超过4G,固然,你会说,现在都是64位的CPU之类的云云,但是,对于底层的设计者来说,寻址范围越小越好操控的事实是不争的。 @@ -57,11 +57,11 @@ __当数据超出可接受范围之后…__   当然,下面就要开始说一说,当数据超出了可以接受的范围之后的事情了。比如, 2^66范围的数据检索,也会是一个问题 -  4倍于64位CPU寻址范围,如果加上CPU本身的偏移寄存器占用的资源,可能应该是6-8个64位U的寻址范围,如果反复从内存到硬盘的读写,过程本身就是可怕的。 +  4倍于64位CPU寻址范围,如果加上CPU本身的偏移寄存器占用的资源,可能应该是6-8个64位CPU的寻址范围,如果反复从内存到硬盘的读写,过程本身就是可怕的。   算法,更多的是用来解决瓶颈的,就想现在,根本不用考虑内存超出8M的问题,但是20年前,8086的年代,内存4M,或者内存8M,你怎么处理?固然做软件的不需要完全考虑摩尔定律,但是摩尔定律绝对是影响软件和算法编写者得想法的。 -  再比如,乌克兰俄罗斯的一批压缩高手,比如国内有名的R大,为什么压缩会出现?就是因为,要么存不下,要么传输时间过长。网络再好,64G的高清怎么的也得下载个一段时间吧。海量数据处理,永远是考虑超过了当前硬件条件的时候,该怎么办?! +  再比如,乌克兰俄罗斯的一批压缩高手,比如国内有名的R大,为什么压缩会出现?就是因为,要么存不下,要么传输时间过长。网络再好,64G的高清怎么的也得下遍历n个元素取出等概率载个一段时间吧。海量数据处理,永远是考虑超过了当前硬件条件的时候,该怎么办?!   那么我们可以发现一个更加有趣的问题,如果存不下,但是还要存,怎么办! @@ -94,11 +94,11 @@ __LZW算法的适用范围__ END of WHILE output the code for STRING -  看过上面的适用范围在联想本题,数据有多少种,根据同余模的原理,可以惊人的发现,其实真的非常适合压缩,但是压缩之后,尽管存下了,在查找的时候,势必又需要解码,那么又回到了我们当初学习算法时候,的那句经典话,算法本身,就是为了解决时间和空间的均衡问题,要么时间换空间,要么空间换时间。 +  看过上面的适用范围再联想本题,数据有多少种,根据同余模的原理,可以惊人的发现,其实真的非常适合压缩,但是压缩之后,尽管存下了,在查找的时候,势必又需要解码,那么又回到了我们当初学习算法时的那句经典话,算法本身,就是为了解决时间和空间的均衡问题,要么时间换空间,要么空间换时间。   更多的,请读者自行思考,因为,压缩本身只是想引起读者思考,已经是题外话了~本部分完--__上善若水.qinyu__。 -##第二部分、遍历n个元素取出等概率随机取出其中之一元素 +##第二部分、随机取出其中之一元素 ###问题描述 @@ -114,7 +114,7 @@ __LZW算法的适用范围__   第二个人中签的情况只能在第一个人未中时才有可能,所以他中的概率是4/5 X 1/4 = 1/5(4/5表示第一个人未中,1/4表示在剩下的4个签里中签的概率),所以,第二个人最终的中签概率也是1/5, -  同理,第三个人中签的概率为:第一个人未中的概率X 第二个人未中的概率X第三个人中的概率,即为:4/5 X 3/4 X 1/3 = 1/5, +  同理,第三个人中签的概率为:第一个人未中的概率 * 第二个人未中的概率 * 第三个人中的概率,即为:4/5 * 3/4 * 1/3 = 1/5,   一样的可以求出第四和第五个人的概率都为1/5,也就是说先后顺序不影响公平性。 @@ -140,13 +140,13 @@ __LZW算法的适用范围__   在遍历到第1个元素的时候,即L为1,那么r%L必然为0,所以e为第一个元素,p=100%, -  遍历到第2个元素时,L为2,r%L==0的概率为1/2, 这个时候,第1个元素不被替换的概率为1X(1-1/2)=1/2, +  遍历到第2个元素时,L为2,r%L==0的概率为1/2, 这个时候,第1个元素不被替换的概率为1*(1-1/2)=1/2, -  第1个元素被替换,也就是第2个元素被选中的概率为1/2 = 1/2,你可以看到,只有2时,这两个元素是等概率的机会被选中的。 +  第1个元素被替换,也就是第2个元素被选中的概率为1/2,你可以看到,只有2时,这两个元素是等概率的机会被选中的。 -  继续,遍历到第3个元素的时候,r%L==0的概率为1/3,前面被选中的元素不被替换的概率为1/2 X (1-1/3)=1/3,前面被选中的元素被替换的概率,即第3个元素被选中的概率为1/3 +  继续,遍历到第3个元素的时候,r%L==0的概率为1/3,前面被选中的元素不被替换的概率为1/2*(1-1/3)=1/3,前面被选中的元素被替换的概率,即第3个元素被选中的概率为1/3。 -  归纳法证明,这样走到第L个元素时,这L个元素中任一被选中的概率都是1/L,那么走到L+1时,第L+1个元素选中的概率为1/(L+1), 之前选中的元素不被替换,即继续被选中的概率为1/L X ( 1-1/(L+1) ) = 1/(L+1)。证毕。 +  归纳法证明,这样走到第L个元素时,这L个元素中任一被选中的概率都是1/L,那么走到L+1时,第L+1个元素选中的概率为1/(L+1), 之前选中的元素不被替换,即继续被选中的概率为1/L*(1-1/(L+1)) = 1/(L+1)。证毕。   也就是说,走到文件最后,每一个元素最终被选出的概率为1/n, n为文件中元素的总数。好歹我们是一个技术博客,看不到一丁点代码多少有点遗憾,给出一个选取策略的伪代码,如下: @@ -154,8 +154,8 @@ __伪代码__ Element RandomPick(file): Int length = 1; - While( length <= file.size ) - If( rand() % length == 0) + While (length <= file.size) + If (rand() % length == 0) Picked = File[length]; Length++; Return picked @@ -169,32 +169,30 @@ __伪代码__ 方法: 计数法 -  假设一天之内某个IP访问百度的次数不超过40亿次,则访问次数可以用unsigned表示.用数组统计出每个IP地址出现的次数, 即可得到访问次数最大的IP地址. +  假设一天之内某个IP访问百度的次数不超过40亿次,则访问次数可以用unsigned int表示。用数组统计出每个IP地址出现的 次数, 即可得到访问次数最大的IP地址。 -  IP地址是32位的二进制数,所以共有N=2^32=4G个不同的IP地址, 创建一个unsigned count[N];的数组,即可统计出每个IP的访问次数,而sizeof(count) == 4G*4=16G, 远远超过了32位计算机所支持的内存大小,因此不能直接创建这个数组.下面采用划分法解决这个问题. +  IP地址是32位的二进制数,所以共有N=2^32个不同的IP地址,创建一个大小为N的的数组count,即可统计出每个IP的访问次数,而sizeof(count) == 4G*4 = 16G,远远超过了32位计算机所支持的内存大小,因此不能直接创建这个数组.下面采用划分法解决这个问题。 -  假设允许使用的内存是512M, 512M/4=128M 即512M内存可以统计128M个不同的IP地址的访问次数.而N/128M =4G/128M = 32 ,所以只要把IP地址划分成32个不同的区间,分别统计出每个区间中访问次数最大的IP, 然后就可以计算出所有IP地址中访问次数最大的IP了. +  假设允许使用的内存是512M, 512M/4=128M 即512M内存可以统计128M个不同的IP地址的访问次数。而N/128M = 4G/128M = 32 ,所以只要把IP地址划分成32个不同的区间,分别统计出每个区间中访问次数最大的IP,然后就可以计算出所有IP地址中访问次数最大的IP了. -  因为2^5=32, 所以可以把IP地址的最高5位作为区间编号, 剩下的27为作为区间内的值,建立32个临时文件,代表32个区间,把相同区间的IP地址保存到同一的临时文件中. +  因为2^5=32, 所以可以把IP地址的最高5位作为区间编号,剩下的27为作为区间内的值,建立32个临时文件,代表32个区间,把相同区间的IP地址保存到同一临时文件中. 例如: -ip1=0x1f4e2342 +IP1=0x1f4e2342 -ip1的高5位是id1 = ip1 >>27 = 0x11 = 3 +IP1的高5位是id1 = IP1 >> 27 = 0x11 = 3 -ip1的其余27位是value1 = ip1 &0x07ffffff = 0x074e2342 +IP1的其余27位是value1 = IP1 & 0x07ffffff = 0x074e2342 所以把 value1 保存在tmp3文件中. -由id1和value1可以还原成ip1, 即 ip1 =(id1<<27)|value1 +由id1和value1可以还原成IP1, 即 IP1 =(id1 << 27) | value1   按照上面的方法可以得到32个临时文件,每个临时文件中的IP地址的取值范围属于[0-128M),因此可以统计出每个IP地址的访问次数.从而找到访问次数最大的IP地址 程序源码: -test.cpp是c++源码. - ```cpp #include #include @@ -307,13 +305,13 @@ int main()   回文判断是一类典型的问题,尤其是与字符串结合后呈现出多姿多彩,在实际中使用也比较广泛,而且也是面试题中的常客,所以本文就结合几个典型的例子来体味下回文之趣。 -  回文,英文palindrome,指一个顺着读和反过来读都一样的字符串,比如 madam、我爱我,这样的短句在智力性、趣味性和艺术性上都颇有特色,中国历史上还有很多有趣的回文诗呢 :) +  回文,英文palindrome,指一个顺着读和反过来读都一样的字符串,比如madam、我爱我,这样的短句在智力性、趣味性和艺术性上都颇有特色,中国历史上还有很多有趣的回文诗呢 :) ###一、回文判断   那么,我们的第一个问题就是:__判断一个字串是否是回文__ -  通过对回文字串的考察,最直接的方法显然是将字符串逆转,存入另外一个字符串,然后比较原字符串和逆转后的字符串是否一样,一样就是回文,这个方法的时空复杂度都是 O(n)。 +  通过对回文字串的考察,最直接的方法显然是将字符串逆转,存入另外一个字符串,然后比较原字符串和逆转后的字符串是否一样,一样就是回文,这个方法的时空复杂度都是O(n)。   我们还很容易想到只要从两头开始同时向中间扫描字串,如果直到相遇两端的字符都一样,那么这个字串就是一个回文。我们只需要维护头部和尾部两个扫描指针即可,代码如下: @@ -324,7 +322,7 @@ int main() */ bool IsPalindrome(const char *s, int n) { - if (s == 0 || n < 1) return false; // invalid string + if (s == NULL || n < 1) return false; // invalid string char *front, *back; front = s; back = s + n - 1; // set front and back to the begin and endof the string while (front < back) { @@ -346,12 +344,12 @@ bool IsPalindrome(const char *s, int n) */ bool IsPalindrome2(const char *s, int n) { - if (s == 0 || n < 1) return false; // invalid string + if (s == NULL || n < 1) return false; // invalid string char *first, *second; - int m = ((n>>1) - 1) >= 0 ? (n>>1) - 1 : 0; // m is themiddle point of s + int m = ((n >> 1) - 1) >= 0 ? (n >> 1) - 1 : 0; // m is themiddle point of s first = s + m; second = s + n - 1 - m; while (first >= s) - if (s[first--] !=s[second++]) return false; // not equal, so it's not apalindrome + if (s[first--] != s[second++]) return false; // not equal, so it's not apalindrome return true; // check over, it's a palindrome } ```