今天还是继续讨论第一章(这效率-_-|||)。其实已经看到第三章了,只不过看到后面越发觉得不太对劲,这书不该这么看啊,怎么能一过了之呢?太对不起作者了好吧(虽然这本书很薄看起来很容易看完的样子)。更何况,而且明明很多都没有弄懂,于是有了现在这个验证篇。
1.利用gcov对书中素数例子进行验证
尽管知道书上说的肯定是对的,可是还是想自己动手试试看结果。
首先,编译 加入gcov的参数
g++ prime.cpp -fprofie-arcs -ftest-coverage -o prime
运行prime后 ,代码覆盖率的测试数据已经收集在了gcda文件中,再用gcov 生成结果。
gcov prime
下面是书上关于素数几个例子的验证。
P1 简单版本
999: 7:int prime(int n){
999: 8: int i;
78190: 9: for( i=2;i<n;i++){
78022: 10: if(n%i==0){
831: 11: return 0;
-: 12: }}
168: 14: return 1;}
P2 只检验平方根
5456: 6:int root(int n){ //被调用5456次和for循环次数一样
5456: 7: return (int) sqrt((float)n);
-: 8:}
999: 10:int prime(int n){
999: 11: int i;
5456: 12: for( i=2;i<=root(n);i++){
5288: 13: if(n%i==0){
831: 14: return 0;
-: 15: }}
168: 17: return 1;}
-: 23:int main()
1: 24:{
1: 25: int i,n;
1: 26: n=1000;
1000: 27: for(i = 2; i <= n; i++)
999: 28: if (prime(i))
168: 29: cout<<i<<endl;
1: 31:return 0;
3: 32:}
999: 6:int root(int n){
999: 7: return (int) sqrt((float)n);
-: 8:}
999: 11:int prime(int n){
999: 12: int i,bound;//用bound存储root开方结果,减少root被调用次数,这里root在被调用999次
999: 13: bound= root(n);
5456: 14: for( i=2;i<=bound;i++){
5288: 15: if(n%i==0){
831: 16: return 0;
-: 17: }}
168: 19: return 1;}
-: 24:int main()
1: 25:{ int i,n;
1: 27: n=1000;
1000: 28: for(i = 2; i <= n; i++)
999: 29: if (prime(i)) {
168: 31: cout<<i<<endl; }
1: 34:return 0;
3: 35:}
P4 特殊情况 2、3、5
由于3/4的合数都包含了2,3,5,因此特殊考虑。不过下面这个程序有问题
265: 6:int root(int n){
265: 7: return (int) sqrt((float)n);
-: 8:}
999: 12:int prime(int n){
999: 13: int i,bound;
999: 15: if(n%2==0) return 0;
499: 16: if(n%3==0) return 0;
332: 17: if(n%5==0) return 0;
-: 18:
265: 19: bound= root(n);
1695: 20: for( i=7;i<=bound;i = i+2){
1530: 21: if(n%i==0){
100: 22: return 0;
-: 23: }}
165: 25: return 1;
-: 26:}
-: 27:
-: 30:int main()
1: 31:{ int i,n;
1: 33: n=1000;
1000: 34: for(i = 2; i <= n; i++)
999: 35: if (prime(i)) {
165: 36: cout<<i<<endl;
-: 37: }
1: 38:return 0;
3: 39:}
999: 11:int prime(int n){
999: 12: int i,bound;
-: 13:
999: 14: if(n%2==0) return (n==2);
499: 15: if(n%3==0) return (n==3);
332: 16: if(n%5==0) return (n==5);
-: 17:
1695: 18: for( i=7;i*i<=n;i = i+2){
1530: 19: if(n%i==0){
100: 20: return 0;
-: 21: }}
165: 23: return 1;
-: 24:}
-: 25:
-: 27:int main()
1: 28:{
1: 29: int i,n;
1: 30: n=1000;
1000: 31: for(i = 2; i <= n; i++)
999: 32: if (prime(i)) {
168: 33: cout<<i<<endl; //上面这里是165次,没有检验2,3,5,这里将这个三个数也考虑进来了
-: 34: }
1: 36:return 0;
3: 37:}
P6 用乘法替代开方
for( i=7;i*i<=n;i = i+2)
速度加快了。
习题2 中利用埃氏筛选法,n这里我取的还是1000为了和上面的做比较
1: 6:int main(){
1: 7: int i, p, n;
1: 8:char x[1002];
1: 10: n = 1000;
-: 11:
1001: 12:for (i = 1; i <= n; i++)
1000: 14: x[i] = 1;
1: 15: x[1] = 0;
1: 16: x[n+1] = 1;
1: 18: p = 2;
169: 20: while (p <= n) {
168: 21: cout<<p<<" ";
2126: 23: for (i = 2*p; i <= n; i = i+p)
1958: 25: x[i] = 0;
999: 27: do
999: 28: p++;
-: 30: while (x[p] == 0);
-: 31: }
2.求解习题1 (很有意思的一道题, 我可以说我之所以想写这篇文章主要是为了这道题。)
题目:假设数组X[1...1000]中散布着随机实数。下面这个例程计算最小和最大值。
Max:= Min:=X[1]
for I:=2 to 1000 do
if X[I] >Max then Max := X[I]
if X[I]<Min then Min := X[I]
B.C.Dull 先生注意到,如果一个元素是新的最大值,则这个元素不可能是最小值。因而他把两次比较写成
if X[I] >Max then Max := X[I]
else if X[I]<Min then Min := X[I]
这样平均起来将节省多少次比较?先猜答案,再通过实现和监控程序性能功能来找出答案。你猜的怎么样?
我当然没有猜到答案,看了书上给的答案后还是觉得不太清楚,可能还是和翻译有关吧。然后,自己先动手验证了一番。
-: 1:#include<stdio.h>
-: 2:#include<stdlib.h>
-: 3:#include<iostream>
-: 4:#include<time.h>
-: 5:#define random() (rand()/(double)(RAND_MAX))
-: 6:using namespace std;
-: 7:
1: 8:void MinMax(double a[],int n){
1: 9: double max=a[0],min=a[0];
1000: 10: for(int i=1;i<n;i++){
999: 11: if(a[i]>max) max=a[i];
999: 12: if(a[i]<min) min=a[i]; //999
-: 13: }
1: 14: cout<<"original min:"<<min<<"original max:"<<max<<endl;
-: 15:}
-: 16:
1: 17:void MinMax2(double a[],int n){
1: 18: double max=a[0],min=a[0];
1000: 19: for(int i=1;i<n;i++){
999: 20: if(a[i]>max) max=a[i];
995: 21: else if(a[i]<min) min=a[i];} //995
1: 23: cout<<"Optimized min:"<<min<<"original max:"<<max<<endl;
-: 24:}
-: 25:
1: 26:int main(){
1: 27: int n=1000;
1: 28: double a[1000];
1: 29: srand((int)time(0));
1001: 30: for(int i=0;i<n;i++){
1000: 31: a[i]=rand();}
1: 33: MinMax(a,n);
1: 34: MinMax2(a,n);
1: 35:return 0;
3: 36:}
我也在N=1000的条件下将程序运行了15遍,排序后的赋值数依次为:
3 4 4 5 5 6 7 7 7 7 7 8 9 9 9
我这里的结果是6.47,果然和理论给的期望值6.485很接近。
那么到底为什么答案是这样呢?
书上是这么说的,knuth在The Art of Computer Programming,Volume1:Foundamental Algorthms的1.2.10中说明了该算法在平均情况下少进行Hn-1次赋值,其中
Hn=1+1/2+1/3+...+1/n,是一个调和级数。而该调和级数对于特定的n值收敛于ln(n)+r,r是欧拉常数约等于0.577。于是有 ln(1000)+0.577-1 =6.485。
也就是说比较1000的情况下,平均省7次左右。
究竟为什么是调和级数呢?我没有看那本书,但网上关于这个题的说法不多,有个听起来有点意思,说的是减少的次数等于第一个条件命中的次数(?)。第n个数为最大值的概率是1/n,那么所有第一个条件命中的次数概率和为:1/n+1/(n-1)+...+1/1=ln(n)+r。是这样么?
还有一个人用了更为严谨的证明如下:
令随机变量Xi对应于Ai>MAX这个事件有:Xi={1 如果 Ai>MAX ,0 如果Ai<MAX}
令随机变量X表示少比较的总次数
X=X2+X2+...+Xn
问题转化为求X的期望值
因为A是随机排列,Ai是前i个值中最大值的概率为1/i
E(Xi)=1*(1/i)+0*(1-1/i)=1/i
E(X)=E[X2+X2+...+Xn]=E(X2)+E(X3)+E(X4)+...+E(Xn) =1/2+1/3+...+1/n=ln(n)+O(1)