编程珠玑第二章习题答案

转载自互联网,并做了修改

1、提供单词和词典,找到该单词的所有变位词,可以事先花时间和空间处理下该词典。

       为了找出给定单词的所有变位词,首先是计算给定单词的标识。如果不允许预处理,只能够顺序读取文件,然后求出读入文件的标识然后与给定单词的标志比较,然后得出结论。

      如果允许预处理,那么就预先对字典进行按标识的排序,然后进行二分搜索,然后找到给定单词的在字典中的起点start和终点end。然后end-start+1得到改单词的所有变位词个数。同时也可以求出它的所有变位词。  



2、给定一个4300000000个32位整数的顺序文件,请问你如何可以找到一个至少出现两次的整数

因为2^32>4300000000所以肯定存在至少出现两次的整数

而且是顺序文件,更提醒我们要用二分查找法

方法1:

(1)把所有整数(N个)看成二进制表示法,将第一位bit为1的数目和第一位bit为0的比较,必有一个数目大于等于另一个。把要找的重复的数的该位设置为1或者是0(取决于该位1或0哪个多设置为哪个)在数目大的那堆数字中继续比较第二bit位,按照1的方法比较,以此类推最后能得到重复出现的数字。这种思路与之前找40亿个数中遗失的数思路一样。

方法2:

由于4.3G>32位的整数空间,根据鸽笼原理,肯定会有重复的整数。注意二分查找的时候缩小的是数值的范围,因为输入是顺序的,所以当缩小数值范围的时候,我们也不用再一次的遍历整个文件了,只用搜索到数值的范围就为止。所以这种方案不能够保证每次迭代都讲整数数目减半。因为存在数字相等的情况。所以O(log(2)n)趟搜索的最坏情况下的时间复杂度是O(nlog(2)n).比如当所有的数字都相等的时候就是最坏的一个情况。

每次缩小范围的时候有可能比较大的那一个部分包含了大部分甚至是所有的数,下一次扫描就仍然需要n次,由于总共需要logn次扫描,所以结果是O(nlogn)。

搜索范围从所有的32位正整数开始(全部当成unsigned int,简化问题),即[0, 2^32),中间值即为2^31。然后遍历文件,如果小于2^31的整数个数大于N/2=2^31,则调整搜索范围为[0, 2^31],反之亦然;因为是顺序文件,下一次遍历的时候只用遍历到最后一个2^31就可以了,接下来继续按上面步骤进行,直到得到最后的结果。

例子:数组[4,2,5,1,3,6,3,7,0,7],根据范围,最多就是3bit。首先从3位的整数空间内搜索。第一次的范围为[0,8),遍历过后发现[0,4)范围内的整数个数为5,于是调整为搜索[0,4)范围内的整数。第二次发现[2, 4)范围内的证书为3,大于2,于是调整为[2, 4)。再经过第三次的遍历,找出3为重复出现的整数。因为是没有排序的数组,所以肯定只有每次都遍历完整个数组所以这个的时间复杂度就是O(nlog(2)n)。

方法3

因为上面的方法在最坏的情况下时间复杂度是O(nlog(2)n)。所以改进方法,不用考虑过多的重复元素,可……以把运行时间缩短为线性时间。如果知道了当前数值范围内的m个整数中一定有重复元素,并不是把原有文件里这个范围内的整数写到新的文件里去”,而是“把原有文件里这个范围内的整数选取前m+1(m=n/2,n/4...)个写到新的文件里”即可。然后下一次的二分搜索在这个新的文件夹中进行。这样就保证了在数值表示范围减半的同时,搜索的元素的个数也同时减半了。这既可至少找到一个重复元素,也可满足等比数列的要求,需要搜索的元素的个数依次为n,n/2,n/4……根据等比数列的计算方法得到2n-1,所以这种方法的时间复杂度是O(2n-1)。之所以能够有这种改进的方法就因为输入是排序了的。


3、两个向量的转置算法,i,n的最大公约数怎么出现在程序中。

参考:http://www.cnblogs.com/yjf512/archive/2010/11/16/1878146.html

i,n的最大公约数其实就是从头开始进行a[i]=a[2i] a[2i]=a[3i]的次数

待移动的数组假设为a,长度为len,需要移动rotate位,编程珠玑上说的:用的时候,务必小心!书中给的是len =12,rotate=3,是整数倍的关系,直接可以循环3次实现移位,但是这是特殊情况,对于任意情况,应该考虑不是整数倍的时候怎么移动,举个例子:

数组0~11,一个12个数,这个比较有代表性,可以移动4,5,8次来满足各种情况,
1)对于4次,是12的的整数倍,就像书中写的那样,循环4次就可以完成,每次都刚好移完整个串,代码是这样的:

复制代码
 1         for (int i = 0; i < 4; i++) {
 2             temp = a[i];
 3             before = i;
 4             while (true) {
 5                 next = (before + rotate) % len;
 6                 if (next == i) {
 7                     break;
 8                 } else {
 9                     a[before] = a[next];
10                     before = next;
11                 }
12             }
13             a[before] = temp;
14         }
复制代码

每次取到等于i的时候,就从temp里取出该轮的第一个值,这是个循环,跳出,进入下一轮;
2)当移动5位的时候,不是12的整数倍,这时,会一直循环,只有到最后一个数的移动,才会使next值等于i,因为next怎么加rotate,都不会是len的整数倍,

这时,整个循环只要1次就行了,会一直在while里循环,直到最后取出temp值才会跳出,这时已经移位结束了。则这部分代码就是上面的4-->1;


4、三个旋转向量算法

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <time.h>  
  4. #include <windows.h>  
  5.   
  6. void reverse1(char c[], int i, int n)  
  7. {  
  8.     int p = 0;  
  9.     int count = 0;  
  10.     if(i > 0 && n>=i)  
  11.     {  
  12.         while(count!=n)  
  13.         {  
  14.             int t = c[p];  
  15.             int q = p + i;  
  16.             while((q%n)!=p)  
  17.             {  
  18.                 c[(q-i)%n] = c[q%n];  
  19.                 count++;  
  20.                 q+=i;  
  21.             }  
  22.             c[(q-i)%n] = t;  
  23.             count++;  
  24.             p++;  
  25.         }  
  26.           
  27.     }  
  28.     //p为i,n最大公约数,即从第一个数开始进行c[i]=c[2i]  c[2i]=c[3i]循环的次数  
  29.     printf("%d",p);  
  30. }  
  31. void reverse2(char c[], int i, int j)  
  32. {  
  33.     char p;  
  34.     while(i<j)  
  35.     {  
  36.         p = c[i];  
  37.         c[i] = c[j];  
  38.         c[j] = p;  
  39.         i++;  
  40.         j--;  
  41.     }  
  42. }  
  43. int main()  
  44. {  
  45.     int i = 0;  
  46.     char c[] ={'a','b','c','d','e','f','g','h'};  
  47.     DWORD start, stop;  
  48.     start = GetTickCount();  
  49.     reverse1(c,3,8);  
  50.     stop = GetTickCount();  
  51.     printf("time: %lld ms\n", stop - start);  
  52.     for(i = 0; i < 8;i++)  
  53.     {  
  54.         printf("%c",c[i]);  
  55.     }  
  56.     printf("%c",'\n');  
  57.     char d[] ={'a','b','c','d','e','f','g','h'};  
  58.     start = GetTickCount();  
  59.     reverse2(d,0,2);  
  60.     reverse2(d,3,7);  
  61.     reverse2(d,0,7);  
  62.     stop = GetTickCount();  
  63.     printf("time: %lld ms\n", stop - start);  
  64.     for(i = 0; i < 8;i++)  
  65.     {  
  66.         printf("%c",d[i]);  
  67.     }  
  68.     printf("%c",'\n');  
  69.     return 1;  
  70. }  

5、将向量abc转为cba

我想到的是在两向量转置的基础上abc->bac->cba

网上百度的方法是对abc取abr cr 再取(arbrcrr  r表示对向量的转置。

6、给定某一个名字的按钮编码,返回匹配集合

这个题目跟查找变位词差不多,给每个名字编码,将编码一样的放在一起,并顺序排列,查找的时候二分查找就可以了。

7、第7题其实就是把行列作为标识。也就是对于矩阵中的每个元素进行标志。每个元素的标志就是它们的行号和列号。先按列排序,那么第一列的所有元素就排在最前面,也就是第一行。接下来是第二列,第三列……然后再各列内按行排序。这样就实现了一个矩阵的转置。最后使用程序删除标志就可以了


8、n个元素的实数集,确认是否有k个元素的子集可以让总和不超过t

将n个元素从小到大排序,计算头k个数和,如果包含k个最小元素的子集的和不超过t时,那么就肯定存在和不超过t的k元子集存在。

法1:利用快速排序,对元素进行排序,排序时间复杂度是nlogn,然后取前k个,所以在正比于nlogn时间内确定是不是有这个子集。

法2:用全部排序。比如原集合存放在数组A[0,...,n-1]中,定义辅助数组B[0,...,k-1],先将A中的前k个元素排序放在B中。A中从第k+1个元素(A[k])开始,用插入排序的方法,插入到B数组中(B中最大的自然会被丢弃),最后B中就是最小的k个。时间复杂度是O(kn)。

法3:构建一个最小堆,取其前k个最小的数。 也可以构建一个k个元素的最大堆来获得k个最小的元素。

时间复杂度是O(nlogk)。

法4:《算法导论》第九章中位数和顺序统计学的9.2节中以期望线性时间找出n元素集合中第k小的元素。若知道第k小元素了,以此作为游标,在线性时间内将k个最小元素都找到。两个线性时间满足题目的正比于n的时间复杂度的要求。这带来另一个问题如何找到出n元集合中第k小的元素呢?(见算法导论)

法5:改进快速排序不用排序出全部的数,只需要快速排序+二分搜索在线性时间找到第K小的数同时从这个数开始往左都是最小的k个数。时间复杂度是O(n).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值