码迷,mamicode.com
首页 > 编程语言 > 详细

算法设计与分析 - 李春葆 - 第二版 - pdf->word v3

时间:2019-12-23 18:42:03      阅读:231      评论:0      收藏:0      [点我收藏+]

标签:子集和   若是   排序算法   种子   声明   creat   水果   函数定义   bbb   

   1 1.1 第1章─概论  
   2 
   3 1.1.1 练习题  
   4 1. 下列关于算法的说法中正确的有( )。  
   5 Ⅰ.求解某一类问题的算法是唯一的  
   6 Ⅱ.算法必须在有限步操作之后停止  
   7 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模糊  
   8 Ⅳ.算法执行后一定产生确定的结果  
   9 A. 1个       B.2个           C.3个           D.4个  
  10 2. T(n)表示当输入规模为n时的算法效率,以下算法效率最优的是( )。  
  11 A.T(n)= T(n-1)+1,T(1)=1            B.T(n)= 2n2  
  12 C.T(n)= T(n/2)+1,T(1)=1                D.T(n)=3nlog2n  
  13 3. 什么是算法?算法有哪些特征?  
  14 4. 判断一个大于2的正整数n是否为素数的方法有多种,给出两种算法,说明其中 
  15 一种算法更好的理由。  
  16 5. 证明以下关系成立:  
  171)10n2-2n=?(n2)  
  182)2n+1=?(2n)  
  19 6. 证明O(f(n))+O(g(n))=O(max{f(n),g(n)}) 。  
  20 7. 有一个含n(n>2)个整数的数组a,判断其中是否存在出现次数超过所有元素一 
  21 半的元素。  
  22 8. 一个字符串采用string对象存储,设计一个算法判断该字符串是否为回文。  
  23 9. 有一个整数序列,设计一个算法判断其中是否存在两个元素和恰好等于给定的整 
  24 数k。  
  25 10. 有两个整数序列,每个整数序列中所有元素均不相同。设计一个算法求它们的公 共元素,要求不使用STL的集合算法。  
  26 11. 正整数n(n>1)可以写成质数的乘积形式,称为整数的质因数分解。例如, 12=2*2*318=2*3*311=11。设计一个算法求n这样分解后各个质因数出现的次数,采 用vector向量存放结果。  
  27 12. 有一个整数序列,所有元素均不相同,设计一个算法求相差最小的元素对的个 数。如序列4、12、3的相差最小的元素对的个数是3,其元素对是(12),(23), (34)。  
  28 13. 有一个map<stringint>容器,其中已经存放了较多元素。设计一个算法求出其 中重复的value并且返回重复value的个数。  
  29 14. 重新做第10题,采用map容器存放最终结果。  
  30 15. 假设有一个含n(n>1)个元素的stack<int>栈容器st,设计一个算法出栈从栈顶 
  31 到栈底的第k(1≤k≤n)个元素,其他栈元素不变。    
  32 
  33 算法设计  
  34 
  35 1.1.2 练习题参考答案  
  36 1. 答:由于算法具有有穷性、确定性和输出性,因而Ⅱ、Ⅲ、Ⅳ正确,而解决某一 
  37 类问题的算法不一定是唯一的。答案为C。  
  38 2. 答:选项A的时间复杂度为O(n)。选项B的时间复杂度为O(n2)。选项C的时间 
  39 复杂度为O(log2n)。选项D的时间复杂度为O(nlog2n)。答案为C。  
  40 3. 答:算法是求解问题的一系列计算步骤。算法具有有限性、确定性、可行性、输 
  41 入性和输出性5个重要特征。  
  42 4. 答:两种算法如下:  
  43 #include <stdio.h>  
  44 #include <math.h>  
  45 bool isPrime1(int n) //方法1  
  46 { for (int i=2;i<n;i++)  
  47           if (n%i==0)  
  48                return false;  
  49      return true;  
  50 }  
  51 bool isPrime2(int n) //方法2  
  52 { for (int i=2;i<=(int)sqrt(n);i++)  
  53           if (n%i==0)  
  54                return false;  
  55      return true;  
  56 }  
  57 void main()  
  58 { int n=5;  
  59      printf("%d,%d\n",isPrime1(n),isPrime2(n));  
  60 }  
  61 方法1的时间复杂度为O(n),方法2的时间复杂度为n,所以方法2更好。  
  62 5. 答:(1)当n足够大时,(10n2-2n)/( n2)=10,所以10n2-2n=?(n2)。  
  632)2n+1=2*2n=?(2n)。  
  64 6. 证明:对于任意f1(n)∈O(f(n)) ,存在正常数c1和正常数n1,使得对所有n≥n1, 
  65 有f1(n)≤c1f(n) 。  
  66 类似地,对于任意g1(n)∈O(g(n)) ,存在正常数c2和自然数n2,使得对所有n≥n2, 
  67 有g1(n)≤c2g(n) 。  
  68 令c3=max{c1,c2},n3=max{n1,n2},h(n)= max{f(n),g(n)} 。  
  69 则对所有的n≥n3,有:  
  70 f1(n) +g1(n)≤c1f(n) + c2g(n)≤c3f(n)+c3g(n)=c3(f(n)+g(n))  
  71 ≤c32max{f(n),g(n)}=2c3h(n)=O(max{f(n),g(n)})。  
  72 7. 解:先将a中元素递增排序,再求出现次数最多的次数maxnum,最后判断是否满 
  73 足条件。对应的程序如下:  
  74 #include <stdio.h>  
  75 #include <algorithm>  
  76 using namespace std;  
  77 
  78 2  79 第1章  概论 
  80 
  81 bool solve(int a[],int n,int &x)  
  82 { sort(a,a+n);           //递增排序  
  83      int maxnum=0;           //出现次数最多的次数  
  84      int num=1;  
  85      int e=a[0];  
  86      for (int i=1;i<n;i++)  
  87      { if (a[i]==e)  
  88           { num++;  
  89                if (num>maxnum)  
  90                { maxnum=num;  
  91                     x=e;  
  92                }  
  93           }  
  94           else  
  95           { e=a[i];  
  96                num=1;  
  97           }  
  98      }  
  99      if (maxnum>n/2)  
 100           return true;  
 101      else  
 102           return false;  
 103 }  
 104 void main()  
 105 { int a[]={2,2,2,4,5,6,2};  
 106      int n=sizeof(a)/sizeof(a[0]);  
 107      int x;  
 108      if (solve(a,n,x))  
 109           printf("出现次数超过所有元素一半的元素为%d\n",x);       else  
 110           printf("不存在出现次数超过所有元素一半的元素\n");  }  
 111 上述程序的执行结果如图1.1所示。  
 112 
 113 
 114 
 115 
 116 图1.1  程序执行结果  
 117 8. 解:采用前后字符判断方法,对应的程序如下:  
 118 #include <iostream>  
 119 #include <string>  
 120 using namespace std;  
 121 bool solve(string str)      //判断字符串str是否为回文  { int i=0,j=str.length()-1;  
 122      while (i<j)  
 123      { if (str[i]!=str[j])  
 124                return false;  
 125 
 126 3 127 
 128 算法设计  
 129 
 130           i++; j--;  
 131      }  
 132      return true;  
 133 }  
 134 void main()  
 135 { cout << "求解结果" << endl;  
 136      string str="abcd";  
 137      cout << "  " << str << (solve(str)?"是回文":"不是回文") << endl;  
 138      string str1="abba";  
 139      cout << "  " << str1 << (solve(str1)?"是回文":"不是回文") << endl;  }  
 140 上述程序的执行结果如图1.2所示。  
 141 
 142 
 143 
 144 
 145 
 146 图1.2  程序执行结果  
 147 9. 解:先将a中元素递增排序,然后从两端开始进行判断。对应的程序如下:  
 148 #include <stdio.h>  
 149 #include <algorithm>  
 150 using namespace std;  
 151 bool solve(int a[],int n,int k)  
 152 { sort(a,a+n);           //递增排序  
 153      int i=0, j=n-1;  
 154      while (i<j)           //区间中存在两个或者以上元素  
 155      { if (a[i]+a[j]==k)  
 156                return true;  
 157           else if (a[i]+a[j]<k)  
 158                i++;  
 159           else  
 160                j--;  
 161      }  
 162      return false;  
 163 }  
 164 void main()  
 165 { int a[]={1,2,4,5,3};  
 166      int n=sizeof(a)/sizeof(a[0]);  
 167      printf("求解结果\n");  
 168      int k=9,i,j;  
 169      if (solve(a,n,k,i,j))  
 170           printf("  存在: %d+%d=%d\n",a[i],a[j],k);  
 171      else  
 172           printf("  不存在两个元素和为%d\n",k);  
 173      int k1=10;  
 174      if (solve(a,n,k1,i,j))  
 175           printf("  存在: %d+%d=%d\n",a[i],a[j],k1);  
 176 4 177 第1章  概论 
 178 
 179      else  
 180           printf("  不存在两个元素和为%d\n",k1);  }  
 181 上述程序的执行结果如图1.3所示。  
 182 
 183 
 184 
 185 
 186 
 187 图1.3  程序执行结果  
 188 10. 解:采用集合set<int>存储整数序列,集合中元素默认是递增排序的,再采用二 
 189 路归并算法求它们的交集。对应的程序如下:  
 190 #include <stdio.h>  
 191 #include <set>  
 192 using namespace std;  
 193 void solve(set<int> s1,set<int> s2,set<int> &s3) //求交集s3  
 194 { set<int>::iterator it1,it2;  
 195      it1=s1.begin(); it2=s2.begin();  
 196      while (it1!=s1.end() && it2!=s2.end())  
 197      { if (*it1==*it2)  
 198           { s3.insert(*it1);  
 199                ++it1; ++it2;  
 200           }  
 201           else if (*it1<*it2)  
 202                ++it1;  
 203           else  
 204                ++it2;  
 205      }  
 206 }  
 207 void dispset(set<int> s)      //输出集合的元素  
 208 { set<int>::iterator it;  
 209      for (it=s.begin();it!=s.end();++it)  
 210           printf("%d ",*it);  
 211      printf("\n");  
 212 }  
 213 void main()  
 214 { int a[]={3,2,4,8};  
 215      int n=sizeof(a)/sizeof(a[0]);  
 216      set<int> s1(a,a+n);  
 217      int b[]={1,2,4,5,3};  
 218      int m=sizeof(b)/sizeof(b[0]);  
 219      set<int> s2(b,b+m);  
 220      set<int> s3;  
 221      solve(s1,s2,s3);  
 222      printf("求解结果\n");  
 223      printf("  s1: "); dispset(s1);  
 224 
 225 5 226 
 227 
 228      printf("  s2: "); dispset(s2);       printf("  s3: "); dispset(s3);  }  
 229 上述程序的执行结果如图1.4所示。  
 230  
 231 
 232 算法设计   
 233 
 234 
 235 
 236 
 237 
 238 
 239 图1.4  程序执行结果  
 240 11. 解:对于正整数n,从i=2开始查找其质因数,ic记录质因数i出现的次数,当找 
 241 到这样质因数后,将(i,ic)作为一个元素插入到vector容器v中。最后输出v。对应的 算法如下:  
 242 #include <stdio.h>  
 243 #include <vector>  
 244 using namespace std;  
 245 struct NodeType      //vector向量元素类型  
 246 { int p;           //质因数  
 247      int pc;           //质因数出现次数  
 248 };  
 249 void solve(int n,vector<NodeType> &v) //求n的质因数分解  
 250 { int i=2;  
 251      int ic=0;  
 252      NodeType e;  
 253      do  
 254      { if (n%i==0)  
 255           { ic++;  
 256                n=n/i;  
 257           }  
 258           else  
 259           { if (ic>0)  
 260                { e.p=i;  
 261                     e.pc=ic;  
 262                     v.push_back(e);  
 263                }  
 264                ic=0;  
 265                i++;  
 266           }  
 267      } while (n>1 || ic!=0);  
 268 }  
 269 void disp(vector<NodeType> &v)      //输出v  
 270 { vector<NodeType>::iterator it;  
 271      for (it=v.begin();it!=v.end();++it)  
 272           printf("  质因数%d出现%d次\n",it->p,it->pc);  
 273 }  
 274 
 275 6 276 
 277 
 278 void main()  
 279 { vector<NodeType> v;  
 280      int n=100;  
 281      printf("n=%d\n",n);  
 282      solve(n,v);  
 283      disp(v);  
 284 }  
 285 上述程序的执行结果如图1.5所示。  
 286  
 287 第1章  概论  
 288 
 289 
 290 
 291 
 292 
 293 图1.5  程序执行结果  
 294 12. 解:先递增排序,再求相邻元素差,比较求最小元素差,累计最小元素差的个 
 295 数。对应的程序如下:  
 296 #include <iostream>  
 297 #include <algorithm>  
 298 #include <vector>  
 299 using namespace std;  
 300 int solve(vector<int> &myv)           //求myv中相差最小的元素对的个数  
 301 { sort(myv.begin(),myv.end());     //递增排序  
 302      int ans=1;  
 303      int mindif=myv[1]-myv[0];  
 304      for (int i=2;i<myv.size();i++)  
 305      { if (myv[i]-myv[i-1]<mindif)  
 306           { ans=1;  
 307                mindif=myv[i]-myv[i-1];  
 308           }  
 309           else if (myv[i]-myv[i-1]==mindif)  
 310                ans++;  
 311      }  
 312      return ans;  
 313 }  
 314 void main()  
 315 { int a[]={4,1,2,3};  
 316      int n=sizeof(a)/sizeof(a[0]);  
 317      vector<int> myv(a,a+n);  
 318      cout << "相差最小的元素对的个数: " << solve(myv) << endl;  
 319 }  
 320 上述程序的执行结果如图1.6所示。  
 321 
 322 
 323 
 324 
 325 
 326 7 327 
 328 算法设计  
 329 
 330 图1.6  程序执行结果  
 331 13. 解:对于map<stringint>容器mymap,设计另外一个map<intint>容器tmap, 
 332 将前者的value作为后者的关键字。遍历mymap,累计tmap中相同关键字的次数。一个 参考程序及其输出结果如下:  
 333 #include <iostream>  
 334 #include <map>  
 335 #include <string>  
 336 using namespace std;  
 337 void main()  
 338 { map<string,int> mymap;  
 339      mymap.insert(pair<string,int>("Mary",80));  
 340      mymap.insert(pair<string,int>("Smith",82));  
 341      mymap.insert(pair<string,int>("John",80));  
 342      mymap.insert(pair<string,int>("Lippman",95));  
 343      mymap.insert(pair<string,int>("Detial",82));  
 344      map<string,int>::iterator it;  
 345      map<int,int> tmap;  
 346      for (it=mymap.begin();it!=mymap.end();it++)  
 347           tmap[(*it).second]++;  
 348      map<intint>::iterator it1;  
 349      cout << "求解结果" << endl;  
 350      for (it1=tmap.begin();it1!=tmap.end();it1++)  
 351           cout << "  " << (*it1).first << ": " << (*it1).second << "次\n";  
 352 }  
 353 上述程序的执行结果如图1.7所示。  
 354 
 355 
 356 
 357 
 358 
 359 
 360 图1.7  程序执行结果  
 361 14. 解:采用map<intint>容器mymap存放求解结果,第一个分量存放质因数,第 
 362 二个分量存放质因数出现次数。对应的程序如下:  
 363 #include <stdio.h>  
 364 #include <map>  
 365 using namespace std;  
 366 void solve(int n,map<int,int> &mymap) //求n的质因数分解  
 367 { int i=2;  
 368      int ic=0;  
 369      do  
 370      { if (n%i==0)  
 371           { ic++;  
 372                n=n/i;  
 373           }  
 374 8 375 第1章  概论 
 376 
 377           else  
 378           { if (ic>0)  
 379                     mymap[i]=ic;  
 380                ic=0;  
 381                i++;  
 382           }  
 383      } while (n>1 || ic!=0);  
 384 }  
 385 void disp(map<int,int> &mymap) //输出mymap  
 386 { map<int,int>::iterator it;  
 387      for (it=mymap.begin();it!=mymap.end();++it)  
 388           printf("  质因数%d出现%d次\n",it->first,it->second);  }  
 389 void main()  
 390 { map<int,int> mymap;  
 391      int n=12345;  
 392      printf("n=%d\n",n);  
 393      solve(n,mymap);  
 394      disp(mymap);  
 395 }  
 396 上述程序的执行结果如图1.8所示。  
 397 
 398 
 399 
 400 
 401 
 402 
 403 图1.8  程序执行结果  
 404 15. 解:栈容器不能顺序遍历,为此创建一个临时tmpst栈,将st的k个元素出栈并 
 405 进栈到tmpst中,再出栈tmpst一次得到第k个元素,最后将栈tmpst的所有元素出栈并进 栈到st中。对应的程序如下:  
 406 #include <stdio.h>  
 407 #include <stack>  
 408 using namespace std;  
 409 int solve(stack<int> &st,int k)      //出栈第k个元素  
 410 { stack<int> tmpst;  
 411      int e;  
 412      for (int i=0;i<k;i++)           //出栈st的k个元素并进tmpst栈  
 413      { e=st.top();  
 414           st.pop();  
 415           tmpst.push(e);  
 416      }  
 417      e=tmpst.top();                    //求第k个元素  
 418      tmpst.pop();  
 419      while (!tmpst.empty())           //将tmpst的所有元素出栈并进栈st  
 420      { st.push(tmpst.top());  
 421           tmpst.pop();  
 422 9 423 
 424 算法设计  
 425 
 426      }  
 427      return e;  
 428 }  
 429 void disp(stack<int> &st)           //出栈st的所有元素  { while (!st.empty())  
 430      { printf("%d ",st.top());  
 431           st.pop();  
 432      }  
 433      printf("\n");  
 434 }  
 435 void main()  
 436 { stack<int> st;  
 437      printf("进栈元素1,2,3,4\n");  
 438      st.push(1);  
 439      st.push(2);  
 440      st.push(3);  
 441      st.push(4);  
 442      int k=3;  
 443      int e=solve(st,k);  
 444      printf("出栈第%d个元素是: %d\n",k,e);  
 445      printf("st中元素出栈顺序: ");  
 446      disp(st);  
 447 }  
 448 上述程序的执行结果如图1.9所示。  
 449 
 450 
 451 
 452 
 453 
 454 图1.9  程序执行结果  
 455 1.2 第2章─递归算法设计技术  
 456 
 457 1.2.1 练习题  
 458 1. 什么是直接递归和间接递归?消除递归一般要用到什么数据结构?  
 459 2. 分析以下程序的执行结果:  
 460 #include <stdio.h>  
 461 void f(int n,int &m)  
 462 { if (n<1) return;  
 463      else  
 464      { printf("调用f(%d,%d)前,n=%d,m=%d\n",n-1,m-1,n,m);  
 465           n--; m--;  
 466           f(n-1,m);  
 467           printf("调用f(%d,%d)后:n=%d,m=%d\n",n-1,m-1,n,m);  
 468      }  
 469 10 470 第1章  概论 
 471 
 472 }  
 473 void main()  
 474 { int n=4,m=4;  
 475      f(n,m);  
 476 }  
 477 3. 采用直接推导方法求解以下递归方程:  
 478 T(1)=1  
 479 T(n)=T(n-1)+n      当n>1  
 480 4. 采用特征方程方法求解以下递归方程:  
 481 H(0)=0  
 482 H(1)=1  
 483 H(2)=2  
 484 H(n)=H(n-1)+9H(n-2)-9H(n-3) 当n>2  
 485 5. 采用递归树方法求解以下递归方程:  
 486 T(1)=1  
 487 T(n)=4T(n/2)+n      当n>1  
 488 6. 采用主方法求解以下题的递归方程。  
 489 T(n)=1                当n=1  
 490 T(n)=4T(n/2)+n2      当n>1  
 491 7. 分析求斐波那契f(n)的时间复杂度。  
 492 8. 数列的首项a1=0,后续奇数项和偶数项的计算公式分别为a2n=a2n-1+2,a2n+1=a2n- 
 493 1+a2n-1,写出计算数列第n项的递归算法。  
 494 9. 对于一个采用字符数组存放的字符串str,设计一个递归算法求其字符个数(长 
 495 度)。  
 496 10. 对于一个采用字符数组存放的字符串str,设计一个递归算法判断str是否为回 
 497 文。  
 498 11. 对于不带头结点的单链表L,设计一个递归算法正序输出所有结点值。  
 499 12. 对于不带头结点的单链表L,设计一个递归算法逆序输出所有结点值。  
 500 13. 对于不带头结点的非空单链表L,设计一个递归算法返回最大值结点的地址(假 
 501 设这样的结点唯一)。  
 502 14. 对于不带头结点的单链表L,设计一个递归算法返回第一个值为x的结点的地 
 503 址,没有这样的结点时返回NULL。  
 504 15. 对于不带头结点的单链表L,设计一个递归算法删除第一个值为x的结点。  
 505 16. 假设二叉树采用二叉链存储结构存放,结点值为int类型,设计一个递归算法求 二叉树bt中所有叶子结点值之和。  
 506 17. 假设二叉树采用二叉链存储结构存放,结点值为int类型,设计一个递归算法求 二叉树bt中所有结点值大于等于k的结点个数。  
 507 18. 假设二叉树采用二叉链存储结构存放,所有结点值均不相同,设计一个递归算法 求值为x的结点的层次(根结点的层次为1),没有找到这样的结点时返回0。  
 508 
 509 
 510 11 511 
 512 算法设计  
 513 
 514 1.2.2  练习题参考答案  
 515 1. 答:一个f函数定义中直接调用f函数自己,称为直接递归。一个f函数定义中调 
 516 用g函数,而g函数的定义中调用f函数,称为间接递归。消除递归一般要用栈实现。  
 517 2. 答:递归函数f(n,m)中,n是非引用参数,m是引用参数,所以递归函数的状态为 
 518 (n)。程序执行结果如下:  
 519 调用f(3,3)前,n=4,m=4  
 520 调用f(1,2)前,n=2,m=3  
 521 调用f(0,1)后,n=1,m=2  
 522 调用f(2,1)后,n=3,m=2  
 523 3. 解:求T(n)的过程如下:  
 524 T(n)=T(n-1)+n=[T(n-2)+n-1)]+n=T(n-2)+n+(n-1)  
 525     =T(n-3)+n+(n-1)+(n-2)  
 526     = 527     =T(1)+n+(n-1)+…+2  
 528     =n+(n-1)+ +…+2+1=n(n+1)/2=O(n2)。  
 529 4. 解:整数一个常系数的线性齐次递推式,用xn代替H(n),有:xn=xn-1+9xn-2-9xn-3 530 两边同时除以xn-3,得到:x3=x2+9x-9,即x3-x2-9x+9=0 531 x3-x2-9x+9=x(x2-9)-(x2-9)=(x-1)(x2-9)=(x-1)(x+3)(x-3)=0。得到r1=1,r2=-3,r3=3  
 532 则递归方程的通解为:H(n)=c1+c2(-3)n+c33n  
 533 代入H(0)=0,有c1+c2+c3=0  
 534 代入H(1)=1,有c1-3c2+3c3=1  
 535 代入H(2)=2,有c1+9c2+9c3=2  
 536 求出:c1=-1/4,c2=-1/12,c3=1/3,H(n)=c1+c2(-3)n+c33n=((?1)    。  
 537 
 538 
 539 
 540 
 541 
 542 
 543 
 544 
 545 
 546 
 547 
 548 
 549 
 550 
 551 
 552 
 553 
 554 
 555 12 556 
 557 
 558 
 559 
 560 
 561 
 562 高度h为log2n+1 
 563  
 564 
 565 
 566 
 567 
 568 
 569 
 570 (n/22) 
 571 …… 
 572  
 573 
 574 
 575 
 576 
 577 (n/2) 
 578 
 579 (n/22) 
 580  581  
 582 第1章  概论 
 583 
 584 n 
 585 (n/2)    (n/2)    (n/2) 
 586 
 587 (n/22) 
 588  589  
 590 
 591 
 592 
 593 n 
 594 2n 
 595 
 596 22n  
 597 
 598 1    1    n 
 599 图1.10 一棵递归树  
 600 6. 解:采用主方法求解,这里a=4,b=2,f(n)=n2。  
 601 因此,??=    =n2,它与f(n)一样大,满足主定理中的情况(2),所以T(n)= 
 602 log2n) 
 603 7. 解:设求斐波那契f(n)的时间为T(n),有以下递推式:  
 604 T(1)=T(2)  
 605 T(n)=T(n-1)+T(n-2)+1       当n>2  
 606 其中,T(n)式中加1表示一次加法运算的时间。  
 607 不妨先求T1(1)=T1(2)=1,T1(n)=T1(n-1)+T1(n-2),按《教程》例2.14的方法可以求 
 608 出:  
 609 n    n 
 610 T1(n)=    ≈    =  
 611 
 612 所以T(n)=T1(n)+1≈    +1=O(φn),其中φ= 613 8. 解:设f(m)计算数列第m项值。  
 614 当m为偶数时,不妨设m=2n,则2n-1=m-1,所以有f(m)=f(m-1)+2 615 当m为奇数时,不妨设m=2n+1,则2n-1=m-2,2n=m-1,所以有f(m)=f(m-2)+f(m- 
 616 1)-1 617 对应的递归算法如下:  
 618 int f(int m)  
 619 { if (m==1) return 0;  
 620      if (m%2==0)  
 621           return f(m-1)+2;  
 622      else  
 623           return f(m-2)+f(m-1)-1;  
 624 }  
 625 9. 解:设f(str)返回字符串str的长度,其递归模型如下:  
 626 f(str)=0           当*str=\0 627 f(str)=f(str+1)+1  其他情况  
 628 对应的递归程序如下:  
 629 13 630 
 631 算法设计  
 632 
 633 #include <iostream>  
 634 using namespace std;  
 635 int Length(char *str)           //求str的字符个数  
 636 { if (*str==\0)  
 637           return 0;  
 638      else  
 639           return Length(str+1)+1;  
 640 }  
 641 void main()  
 642 { char str[]="abcd";  
 643      cout << str << "的长度: " << Length(str) << endl;  }  
 644 上述程序的执行结果如图1.11所示。  
 645 
 646 
 647 
 648 
 649 图1.11  程序执行结果  
 650 10. 解:设f(str,n)返回含n个字符的字符串str是否为回文,其递归模型如下:  
 651 f(str,n)=true                当n=0或者n=1时  
 652 f(str,n)=flase                当str[0]≠str[n-1]时  
 653 f(str,n)=f(str+1,n-2)      其他情况  
 654 对应的递归算法如下:  
 655 #include <stdio.h>  
 656 #include <string.h>  
 657 bool isPal(char *str,int n)      //str回文判断算法  
 658 { if (n==0 || n==1)  
 659           return true;  
 660      if (str[0]!=str[n-1])  
 661           return false;  
 662      return isPal(str+1,n-2);  
 663 }  
 664 void disp(char *str)  
 665 { int n=strlen(str);  
 666      if (isPal(str,n))  
 667           printf("  %s是回文\n",str);  
 668      else  
 669           printf("  %s不是回文\n",str);  
 670 }  
 671 void main()  
 672 { printf("求解结果\n");  
 673      disp("abcba");  
 674      disp("a");  
 675      disp("abc");  
 676 }  
 677 
 678 14 679 
 680 
 681 上述程序的执行结果如图1.12所示。  
 682  
 683 第1章  概论  
 684 
 685 
 686 
 687 
 688 
 689 
 690 图1.12  程序执行结果  
 691 11. 解:设f(L)正序输出单链表L的所有结点值,其递归模型如下:  
 692 f(L) ≡ 不做任何事情           当L=NULL  
 693 f(L) ≡ 输出L->data; f(L->next); 当L≠NULL时  
 694 对应的递归程序如下:  
 695 #include "LinkList.cpp"           //包含单链表的基本运算算法  
 696 void dispLink(LinkNode *L)      //正序输出所有结点值  
 697 { if (L==NULL) return;  
 698      else  
 699      { printf("%d ",L->data);  
 700           dispLink(L->next);  
 701      }  
 702 }  
 703 void main()  
 704 { int a[]={1,2,5,2,3,2};  
 705      int n=sizeof(a)/sizeof(a[0]);  
 706      LinkNode *L;  
 707      CreateList(L,a,n);           //由a[0..n-1]创建不带头结点的单链表       printf("正向L: ");   
 708      dispLink(L); printf("\n");  
 709      Release(L);                //销毁单链表  
 710 }  
 711 上述程序的执行结果如图1.13所示。  
 712 
 713 
 714 
 715 
 716 图1.13  程序执行结果  
 717 12. 解:设f(L)逆序输出单链表L的所有结点值,其递归模型如下:  
 718 f(L) ≡ 不做任何事情           当L=NULL  
 719 f(L) ≡ f(L->next); 输出L->data 当L≠NULL时  
 720 对应的递归程序如下:  
 721 #include "LinkList.cpp"           //包含单链表的基本运算算法  
 722 void Revdisp(LinkNode *L)      //逆序输出所有结点值  
 723 { if (L==NULL) return;  
 724 15 725 
 726 
 727      else  
 728      { Revdisp(L->next);  
 729           printf("%d ",L->data);  
 730      }  
 731 }  
 732 void main()  
 733 { int a[]={1,2,5,2,3,2};  
 734      int n=sizeof(a)/sizeof(a[0]);  
 735      LinkNode *L;  
 736      CreateList(L,a,n);  
 737      printf("反向L: ");   
 738      Revdisp(L); printf("\n");  
 739      Release(L);  
 740 }  
 741 上述程序的执行结果如图1.14所示。  
 742  
 743 
 744 算法设计   
 745 
 746 
 747 
 748 
 749 图1.14  程序执行结果  
 750 13. 解:设f(L)返回单链表L中值最大结点的地址,其递归模型如下:  
 751 f(L) = L                          当L只有一个结点时  
 752 f(L) = MAX{f(L->next),L->data} 其他情况  
 753 对应的递归程序如下:  
 754 #include "LinkList.cpp"           //包含单链表的基本运算算法  
 755 LinkNode *Maxnode(LinkNode *L) //返回最大值结点的地址  
 756 { if (L->next==NULL)  
 757           return L;                //只有一个结点时  
 758      else  
 759      { LinkNode *maxp;  
 760           maxp=Maxnode(L->next);  
 761           if (L->data>maxp->data)  
 762                return L;  
 763           else  
 764                return maxp;  
 765      }  
 766 }  
 767 void main()  
 768 { int a[]={1,2,5,2,3,2};  
 769      int n=sizeof(a)/sizeof(a[0]);  
 770      LinkNode *L,*p;  
 771      CreateList(L,a,n);  
 772      p=Maxnode(L);  
 773      printf("最大结点值: %d\n",p->data);  
 774      Release(L);  
 775 
 776 16 777 
 778 
 779 }  
 780 上述程序的执行结果如图1.15所示。  
 781  
 782 第1章  概论  
 783 
 784 
 785 
 786 
 787 图1.15  程序执行结果  
 788 14. 解:设f(L,x)返回单链表L中第一个值为x的结点的地址,其递归模型如下:  
 789 f(L,x) = NULL            当L=NULL时  
 790 f(L,x) = L                当L≠NULL且L->data=x时  
 791 f(L,x) = f(L->next,x)      其他情况  
 792 对应的递归程序如下:  
 793 #include "LinkList.cpp"                     //包含单链表的基本运算算法  
 794 LinkNode *Firstxnode(LinkNode *L,int x) //返回第一个值为x的结点的地址  
 795 { if (L==NULL) return NULL;  
 796      if (L->data==x)  
 797           return L;  
 798      else  
 799           return Firstxnode(L->next,x);  
 800 }  
 801 void main()  
 802 { int a[]={1,2,5,2,3,2};  
 803      int n=sizeof(a)/sizeof(a[0]);  
 804      LinkNode *L,*p;  
 805      CreateList(L,a,n);  
 806      int x=2;  
 807      p=Firstxnode(L,x);  
 808      printf("结点值: %d\n",p->data);  
 809      Release(L);  
 810 }  
 811 上述程序的执行结果如图1.16所示。  
 812 
 813 
 814 
 815 
 816 图1.16  程序执行结果  
 817 15. 解:设f(L,x)删除单链表L中第一个值为x的结点,其递归模型如下:  
 818 f(L,x) ≡ 不做任何事情            当L=NULL  
 819 f(L,x) ≡ 删除L结点,L=L->next  当L≠NULL且L->data=x  
 820 f(L,x) ≡ f(L->next,x)            其他情况  
 821 对应的递归程序如下:  
 822 
 823 17 824 
 825 算法设计  
 826 
 827 #include "LinkList.cpp"                //包含单链表的基本运算算法  
 828 void Delfirstx(LinkNode *&L,int x) //删除单链表L中第一个值为x的结点  { if (L==NULL) return;  
 829      if (L->data==x)  
 830      { LinkNode *p=L;  
 831           L=L->next;  
 832           free(p);  
 833      }  
 834      else  
 835           Delfirstx(L->next,x);  
 836 }  
 837 void main()  
 838 { int a[]={1,2,5,2,3,2};  
 839      int n=sizeof(a)/sizeof(a[0]);  
 840      LinkNode *L;  
 841      CreateList(L,a,n);  
 842      printf("删除前L: "); DispList(L);  
 843      int x=2;  
 844      printf("删除第一个值为%d的结点\n",x);  
 845      Delfirstx(L,x);  
 846      printf("删除后L: "); DispList(L);  
 847      Release(L);  
 848 }  
 849 上述程序的执行结果如图1.17所示。  
 850 
 851 
 852 
 853 
 854 
 855 图1.17  程序执行结果  
 856 16. 解:设f(bt)返回二叉树bt中所有叶子结点值之和,其递归模型如下:  
 857 f(bt)=0                          当bt=NULL  
 858 f(bt)=bt->data                     当bt≠NULL且bt结点为叶子结点  
 859 f(bt)=f(bt->lchild)+f(bt->rchild)  其他情况  
 860 对应的递归程序如下:  
 861 #include "Btree.cpp"           //包含二叉树的基本运算算法  
 862 int LeafSum(BTNode *bt)           //二叉树bt中所有叶子结点值之和  
 863 { if (bt==NULL) return 0;  
 864      if (bt->lchild==NULL && bt->rchild==NULL)  
 865           return bt->data;  
 866      int lsum=LeafSum(bt->lchild);  
 867      int rsum=LeafSum(bt->rchild);  
 868      return lsum+rsum;  
 869 }  
 870 void main()  
 871 
 872 18 873 第1章  概论 
 874 
 875 { BTNode *bt;  
 876      Int a[]={5,2,3,4,1,6};      //先序序列  
 877      Int b[]={2,3,5,1,4,6};      //中序序列  
 878      int n=sizeof(a)/sizeof(a[0]);  
 879      bt=CreateBTree(a,b,n);      //由a和b构造二叉链bt       printf("二叉树bt:"); DispBTree(bt); printf("\n");       printf("所有叶子结点值之和: %d\n",LeafSum(bt));  
 880      DestroyBTree(bt);           //销毁树bt  
 881 }  
 882 上述程序的执行结果如图1.18所示。  
 883 
 884 
 885 
 886 
 887 
 888 图1.18  程序执行结果  
 889 17. 解:设f(bt,k)返回二叉树bt中所有结点值大于等于k的结点个数,其递归模型 
 890 如下:  
 891 f(bt,k)=0                                当bt=NULL  
 892 f(bt,k)=f(bt->lchild,k)+f(bt->rchild,k)+1  当bt≠NULL且bt->data≥k  
 893 f(bt,k)=f(bt->lchild,k)+f(bt->rchild,k)      其他情况  
 894 对应的递归程序如下:  
 895 #include "Btree.cpp"                //包含二叉树的基本运算算法  
 896 int Nodenum(BTNode *bt,int k)      //大于等于k的结点个数  
 897 { if (bt==NULL) return 0;  
 898      int lnum=Nodenum(bt->lchild,k);  
 899      int rnum=Nodenum(bt->rchild,k);  
 900      if (bt->data>=k)  
 901           return lnum+rnum+1;  
 902      else  
 903           return lnum+rnum;  
 904 }  
 905 void main()  
 906 { BTNode *bt;  
 907      Int a[]={5,2,3,4,1,6};  
 908      Int b[]={2,3,5,1,4,6};  
 909      int n=sizeof(a)/sizeof(a[0]);  
 910      bt=CreateBTree(a,b,n);           //由a和b构造二叉链bt  
 911      printf("二叉树bt:"); DispBTree(bt); printf("\n");  
 912      int k=3;  
 913      printf("大于等于%d的结点个数: %d\n",k,Nodenum(bt,k));  
 914      DestroyBTree(bt);                //销毁树bt  
 915 }  
 916 上述程序的执行结果如图1.19所示。  
 917 
 918 
 919 19 920 
 921 算法设计  
 922 
 923 
 924 
 925 
 926 
 927 
 928 图1.19  程序执行结果  
 929 18. 解:设f(bt,x,h)返回二叉树bt中x结点的层次,其中h表示bt所指结点的层 
 930 次,初始调用时,bt指向根结点,h置为1。其递归模型如下:  
 931 f(bt,x,h)=0                     当bt=NULL  
 932 f(bt,x,h)=h                     当bt≠NULL且bt->data=x  
 933 f(bt,x,h) =l                     当l=f(bt->lchild,x,h+1)≠0  
 934 f(bt,x,h) =f(bt->rchild,x,h+1) 其他情况  
 935 对应的递归程序如下:  
 936 #include "Btree.cpp"                     //包含二叉树的基本运算算法  
 937 int Level(BTNode *bt,int x,int h)      //求二叉树bt中x结点的层次  
 938 { //初始调用时:bt为根,h为1  
 939      if (bt==NULL) return 0;  
 940      if (bt->data==x)                     //找到x结点,返回h  
 941           return h;  
 942      else  
 943      { int l=Level(bt->lchild,x,h+1); //在左子树中查找  
 944           if (l!=0)                          //在左子树中找到,返回其层次l  
 945                return l;  
 946           else  
 947                return Level(bt->rchild,x,h+1);//返回在右子树的查找结果  
 948      }  
 949 }  
 950 void main()  
 951 { BTNode *bt;  
 952      Int a[]={5,2,3,4,1,6};  
 953      Int b[]={2,3,5,1,4,6};  
 954      int n=sizeof(a)/sizeof(a[0]);  
 955      bt=CreateBTree(a,b,n);                //由a和b构造二叉链bt  
 956      printf("二叉树bt:"); DispBTree(bt); printf("\n");  
 957      int x=1;  
 958      printf("%d结点的层次: %d\n",x,Level(bt,x,1));  
 959      DestroyBTree(bt);                     //销毁树bt  
 960 }  
 961 上述程序的执行结果如图1.20所示。  
 962 
 963 
 964 
 965 
 966 
 967 图1.20  程序执行结果  
 968 20 969 第1章  概论 1.3 第3章─分治法  
 970 
 971 1.3.1  练习题  
 972 1. 分治法的设计思想是将一个难以直接解决的大问题分割成规模较小的子问题,分 
 973 别解决子问题,最后将子问题的解组合起来形成原问题的解。这要求原问题和子问题 
 974 ( )。  
 975 A.问题规模相同,问题性质相同  
 976 B.问题规模相同,问题性质不同  
 977 C.问题规模不同,问题性质相同  
 978 D.问题规模不同,问题性质不同  
 979 2. 在寻找n个元素中第k小元素问题中,如快速排序算法思想,运用分治算法对n 
 980 个元素进行划分,如何选择划分基准?下面( )答案解释最合理。  
 981 A.随机选择一个元素作为划分基准  
 982 B.取子序列的第一个元素作为划分基准  
 983 C.用中位数的中位数方法寻找划分基准  
 984 D.以上皆可行。但不同方法,算法复杂度上界可能不同  
 985 3. 对于下列二分查找算法,以下正确的是( )。  
 986 A.  
 987 int binarySearch(int a[], int n, int x)  
 988 { int low=0, high=n-1;  
 989      while(low<=high)  
 990      { int mid=(low+high)/2;  
 991           if(x==a[mid]) return mid;  
 992           if(x>a[mid]) low=mid;  
 993                else high=mid;  
 994      }  
 995      return1;  
 996 }  
 997 B.  
 998 int binarySearch(int a[], int n, int x)  
 999 { int low=0, high=n-1;  
1000      while(low+1!=high)  
1001      { int mid=(low+high)/2;  
1002           if(x>=a[mid]) low=mid;  
1003                else high=mid;  
1004      }  
1005      if(x==a[low]) return low;  
1006      else return1;  
1007 }  
1008 C.  
1009 int binarySearch (int a[], int n, int x)  
1010 { int low=0, high=n-1;  
1011      while(low<high-1)  
1012      { int mid=(low+high)/2;  
1013 211014 
1015 算法设计  
1016 
1017           if(x<a[mid])   
1018                high=mid;  
1019           else low=mid;  
1020      }  
1021      if(x==a[low]) return low;  
1022      else return1;  
1023 }  
1024 D.  
1025 int binarySearch(int a[], int n, int x)  
1026 { if(n > 0 && x >= a[0])  
1027      { int low = 0, high = n-1;  
1028           while(low < high)  
1029           { int mid=(low+high+1)/2;  
1030                if(x < a[mid])   
1031                     high=mid-1;  
1032                else low=mid;  
1033           }  
1034           if(x==a[low]) return low;  
1035      }  
1036      return1;  
1037 }  
1038 4. 快速排序算法是根据分治策略来设计的,简述其基本思想。  
1039 5. 假设含有n个元素的待排序的数据a恰好是递减排列的,说明调用QuickSort(a, 
1040 0,n-1)递增排序的时间复杂度为O(n2)。  
1041 6. 以下哪些算法采用分治策略:  
10421)堆排序算法  
10432)二路归并排序算法  
10443)折半查找算法  
10454)顺序查找算法  
1046 7. 适合并行计算的问题通常表现出哪些特征?  
1047 8. 设有两个复数x=a+bi和y=c+di。复数乘积xy可以使用4次乘法来完成,即 
1048 xy=(ac-bd)+(ad+bc)i。设计一个仅用3次乘法来计算乘积xy的方法。  
1049 9. 有4个数组a、b、c和d,都已经排好序,说明找出这4个数组的交集的方法。  
1050 10. 设计一个算法,采用分治法求一个整数序列中的最大最小元素。  
1051 11. 设计一个算法,采用分治法求xn。  
1052 12. 假设二叉树采用二叉链存储结构进行存储。设计一个算法采用分治法求一棵二叉 
1053 树bt的高度。  
1054 13. 假设二叉树采用二叉链存储结构进行存储。设计一个算法采用分治法求一棵二叉 
1055 树bt中度为2的结点个数。  
1056 14. 有一种二叉排序树,其定义是空树是一棵二叉排序树,若不空,左子树中所有结 
1057 点值小于根结点值,右子树中所有结点值大于根结点值,并且左右子树都是二叉排序树。 现在该二叉排序树采用二叉链存储,采用分治法设计查找值为x的结点地址,并分析算法 的最好的平均时间复杂度。  
1058 
1059 221060 第1章  概论 
1061 
1062 15. 设有n个互不相同的整数,按递增顺序存放在数组a[0..n-1]中,若存在一个下标 i(0≤i<n),使得a[i]=i。设计一个算法以O(log2n)时间找到这个下标i。  
1063 16. 请你模仿二分查找过程设计一个三分查找算法。分析其时间复杂度。  
1064 17. 对于大于1的正整数n,可以分解为n=x1*x2*…*xm,其中xi≥2。例如,n=12时 
1065 有8种不同的分解式:12=1212=6*212=4*312=3*412=3*2*212=2*61066 12=2*3*212=2*2*3,设计一个算法求n的不同分解式个数。  
1067 18. 设计一个基于BSP模型的并行算法,假设有p台处理器,计算整数数组a[0..n-1] 的所有元素之和。并分析算法的时间复杂度。  
1068 1.3.2  练习题参考答案  
1069 1. 答:C。  
1070 2. 答:D。  
1071 3. 答:以a[]={12345}为例说明。选项A中在查找5时出现死循环。选项B 
1072 中在查找5时返回-1。选项C中在查找5时返回-1。选项D正确。  
1073 4. 答:对于无序序列a[low..high]进行快速排序,整个排序为“大问题”。选择其中的 
1074 一个基准base=a[i](通常以序列中第一个元素为基准),将所有小于等于base的元素移动 到它的前面,所有大于等于base的元素移动到它的后面,即将基准归位到a[i],这样产生 a[low..i-1]和a[i+1..high]两个无序序列,它们的排序为“小问题”。当a[low..high]序列只 
1075 有一个元素或者为空时对应递归出口。  
1076 所以快速排序算法就是采用分治策略,将一个“大问题”分解为两个“小问题”来求 
1077 解。由于元素都是在a数组中,其合并过程是自然产生的,不需要特别设计。  
1078 5. 答:此时快速排序对应的递归树高度为O(n),每一次划分对应的时间为O(n),所 
1079 以整个排序时间为O(n2)。  
1080 6. 答:其中二路归并排序和折半查找算法采用分治策略。  
1081 7. 答:适合并行计算的问题通常表现出以下特征:  
10821)将工作分离成离散部分,有助于同时解决。例如,对于分治法设计的串行算 
1083 法,可以将各个独立的子问题并行求解,最后合并成整个问题的解,从而转化为并行算 
1084 法。  
10852)随时并及时地执行多个程序指令。  
10863)多计算资源下解决问题的耗时要少于单个计算资源下的耗时。  
1087 8. 答:xy=(ac-bd)+((a+b)(c+d)-ac-bd)i。由此可见,这样计算xy只需要3次乘法(即 
1088 ac、bd和(a+b)(c+d)乘法运算)。  
1089 9. 答:采用基本的二路归并思路,先求出a、b的交集ab,再求出c、d的交集cd, 
1090 最后求出ab和cd的交集,即为最后的结果。也可以直接采用4路归并方法求解。  
1091 10. 解:采用类似求求一个整数序列中的最大次大元素的分治法思路。对应的程序如 
1092 下:  
1093 #include <stdio.h>  
1094 #define max(x,y) ((x)>(y)?(x):(y))  
1095 #define min(x,y) ((x)<(y)?(x):(y))  
1096 
1097 231098 
1099 算法设计  
1100 
1101 void MaxMin(int a[],int low,int high,int &maxe,int &mine) //求a中最大最小元素  { if (low==high)                    //只有一个元素  
1102      { maxe=a[low];  
1103           mine=a[low];  
1104      }  
1105      else if (low==high-1)           //只有两个元素  
1106      { maxe=max(a[low],a[high]);  
1107           mine=min(a[low],a[high]);  
1108      }  
1109      else                               //有两个以上元素  
1110      { int mid=(low+high)/2;  
1111           int lmaxe,lmine;  
1112           MaxMin(a,low,mid,lmaxe,lmine);  
1113           int rmaxe,rmine;  
1114           MaxMin(a,mid+1,high,rmaxe,rmine);  
1115           maxe=max(lmaxe,rmaxe);  
1116           mine=min(lmine,rmine);  
1117      }  
1118 }  
1119 void main()  
1120 { int a[]={4,3,1,2,5};  
1121      int n=sizeof(a)/sizeof(a[0]);  
1122      int maxe,mine;  
1123      MaxMin(a,0,n-1,maxe,mine);  
1124      printf("Max=%d, Min=%d\n",maxe,mine);  
1125 }  
1126 上述程序的执行结果如图1.21所示。  
1127 
1128 
1129 
1130 
1131 
1132 图1.21  程序执行结果  
1133 11. 解:设f(x,n)=xn,采用分治法求解对应的递归模型如下:  
1134 f(x,n)=x                           当n=1  
1135 f(x,n)=f(x,n/2)*f(x,n/2)            当n为偶数时  
1136 f(x,n)=f(x,(n-1)/2)*f(x,(n-1)/2)*x  当n为奇数时  
1137 对应的递归程序如下:  
1138 #include <stdio.h>  
1139 double solve(double x,int n)          //求x^n  
1140 { double fv;  
1141      if (n==1) return x;  
1142      if (n%2==0)  
1143      { fv=solve(x,n/2);  
1144           return fv*fv;  
1145      }  
1146 
1147 241148 第1章  概论 
1149 
1150      else  
1151      { fv=solve(x,(n-1)/2);  
1152           return fv*fv*x;  
1153      }  
1154 }  
1155 void main()  
1156 { double x=2.0;  
1157      printf("求解结果:\n");  
1158      for (int i=1;i<=10;i++)  
1159           printf("  %g^%d=%g\n",x,i,solve(x,i));  }  
1160 上述程序的执行结果如图1.22所示。  
1161 
1162 
1163 
1164 
1165 
1166 
1167 
1168 
1169 
1170 图1.22  程序执行结果  
1171 12. 解:设f(bt)返回二叉树bt的高度,对应的递归模型如下:  
1172 f(bt)=0                                    当bt=NULL  
1173 f(bt)=MAX{f(bt->lchild),f(bt->rchild)}+1  其他情况  
1174 对应的程序如下:  
1175 #include "Btree.cpp"                     //包含二叉树的基本运算算法  int Height(BTNode *bt)                     //求二叉树bt的高度  
1176 { if (bt==NULL) return 0;  
1177      int lh=Height(bt->lchild);           //子问题1  
1178      int rh=Height(bt->rchild);           //子问题2  
1179      if (lh>rh) return lh+1;                //合并  
1180      else return rh+1;  
1181 }  
1182 void main()  
1183 { BTNode *bt;  
1184      Int a[]={5,2,3,4,1,6};  
1185      Int b[]={2,3,5,1,4,6};  
1186      int n=sizeof(a)/sizeof(a[0]);  
1187      bt=CreateBTree(a,b,n);                //由a和b构造二叉链bt  
1188      printf("二叉树bt:"); DispBTree(bt); printf("\n");  
1189      printf("bt的高度: %d\n",Height(bt));  
1190      DestroyBTree(bt);                     //销毁树bt  
1191 }  
1192 
1193 251194 
1195 
1196 
1197 上述程序的执行结果如图1.23所示。  
1198  
1199 
1200 算法设计   
1201 
1202 
1203 
1204 
1205 
1206 图1.23  程序执行结果  
1207 13. 解:设f(bt)返回二叉树bt中度为2的结点个数,对应的递归模型如下:  
1208 f(bt)=0                               当bt=NULL  
1209 f(bt)=f(bt->lchild)+f(bt->rchild)+1      若bt≠NULL且bt为双分支结点  
1210 f(bt)=f(bt->lchild)+f(bt->rchild)       其他情况  
1211 对应的算法如下:  
1212 #include "Btree.cpp"                //包含二叉树的基本运算算法  
1213 int Nodes(BTNode *bt)                //求bt中度为2的结点个数  
1214 { int n=0;  
1215      if (bt==NULL) return 0;  
1216      if (bt->lchild!=NULL && bt->rchild!=NULL)  
1217           n=1;  
1218      return Nodes(bt->lchild)+Nodes(bt->rchild)+n;  
1219 }  
1220 void main()  
1221 { BTNode *bt;  
1222      Int a[]={5,2,3,4,1,6};  
1223      Int b[]={2,3,5,1,4,6};  
1224      int n=sizeof(a)/sizeof(a[0]);  
1225      bt=CreateBTree(a,b,n);           //由a和b构造二叉链bt  
1226      printf("二叉树bt:"); DispBTree(bt); printf("\n");  
1227      printf("bt中度为2的结点个数: %d\n",Nodes(bt));  
1228      DestroyBTree(bt);                //销毁树bt  
1229 }  
1230 上述程序的执行结果如图1.24所示。  
1231 
1232 
1233 
1234 
1235 
1236 图1.24  程序执行结果  
1237 14. 解:设f(bt,x)返回在二叉排序树bt得到的值为x结点的地址,若没有找到返回 
1238 空,对应的递归模型如下:  
1239 f(bt,x)=NULL            当bt=NULL  
1240 f(bt,x)=bt                 当bt≠NULL且x=bt->data  
1241 f(bt,x)=f(bt->lchild,x)      当x>bt->data  
1242 
1243 261244 第1章  概论 
1245 f(bt,x)=f(bt->rchild,x)      当x<bt->data  
1246 对应的程序如下:  
1247 #include "Btree.cpp"                //包含二叉树的基本运算算法  
1248 BTNode *Search(BTNode *bt,Int x)      //在二叉排序树bt查找的值为x结点  { if (bt==NULL) return NULL;  
1249      if (x==bt->data) return bt;  
1250      if (x<bt->data) return Search(bt->lchild,x);  
1251      else return Search(bt->rchild,x);  
1252 }  
1253 void main()  
1254 { BTNode *bt;  
1255      Int a[]={4,3,2,8,6,7,9};  
1256      Int b[]={2,3,4,6,7,8,9};  
1257      int n=sizeof(a)/sizeof(a[0]);  
1258      bt=CreateBTree(a,b,n);           //构造一棵二叉排序树bt  
1259      printf("二叉排序树bt:"); DispBTree(bt); printf("\n");  
1260      int x=6;  
1261      BTNode *p=Search(bt,x);  
1262      if (p!=NULL)  
1263           printf("找到结点: %d\n",p->data);  
1264      else  
1265           printf("没有找到结点\n",x);  
1266      DestroyBTree(bt);                //销毁树bt  
1267 }  
1268 上述程序的执行结果如图1.25所示。  
1269 
1270 
1271 
1272 
1273 
1274 图1.25  程序执行结果  
1275 Search(bt,x)算法采用的是减治法,最好的情况是某个结点左右子树高度大致相同, 
1276 其平均执行时间T(n)如下:  
1277 T(n)=1           当n=1  
1278 T(n)=T(n/2)+1  当n>1  
1279 可以推出T(n)=O(log2n),其中n为二叉排序树的结点个数。  
1280 15. 解:采用二分查找方法。a[i]=i时表示该元素在有序非重复序列a中恰好第i大。 
1281 对于序列a[low..high],mid=(low+high)/2,若a[mid]=mid表示找到该元素;若a[mid]>mid 说明右区间的所有元素都大于其位置,只能在左区间中查找;若a[mid]<mid说明左区间 
1282 的所有元素都小于其位置,只能在右区间中查找。对应的程序如下:  
1283 #include <stdio.h>  
1284 int Search(int a[],int n)      //查找使得a[i]=i  
1285 { int low=0,high=n-1,mid;  
1286 271287 
1288 算法设计  
1289 
1290      while (low<=high)  
1291      { mid=(low+high)/2;  
1292           if (a[mid]==mid)      //查找到这样的元素  
1293                return mid;  
1294           else if (a[mid]<mid) //这样的元素只能在右区间中出现                 low=mid+1;  
1295           else                     //这样的元素只能在左区间中出现                 high=mid-1;  
1296      }  
1297      return -1;  
1298 }  
1299 void main()  
1300 { int a[]={-2,-1,2,4,6,8,9};  
1301      int n=sizeof(a)/sizeof(a[0]);  
1302      int i=Search(a,n);  
1303      printf("求解结果\n");  
1304      if (i!=-1)  
1305           printf("  存在a[%d]=%d\n",i,i);  
1306      else  
1307           printf("  不存在\n");  
1308 }  
1309 上述程序的执行结果如图1.26所示。  
1310 
1311 
1312 
1313 
1314 
1315 图1.26  程序执行结果  
1316 16. 解:对于有序序列a[low..high],若元素个数少于3个,直接查找。若含有更多的 
1317 元素,将其分为a[low..mid1-1]、a[mid1+1..mid2-1]、a[mid2+1..high]子序列,对每个子序 列递归查找,算法的时间复杂度为O(log3n),属于O(log2n)级别。对应的算法如下:  
1318 #include <stdio.h>  
1319 int Search(int a[],int low,int high,int x)     //三分查找  
1320 { if (high<low)                               //序列中没有元素  
1321           return -1;  
1322      else if (high==low)                         //序列中只有1个元素  
1323      { if (x==a[low])  
1324                return low;  
1325           else  
1326                return -1;  
1327      }  
1328      if (high-low<2)                          //序列中只有2个元素  
1329      { if (x==a[low])  
1330                return low;  
1331           else if (x==a[low+1])  
1332                return low+1;  
1333           else  
1334 281335 第1章  概论 
1336 
1337                return -1;  
1338      }  
1339      int length=(high-low+1)/3;                //每个子序列的长度       int mid1=low+length;  
1340      int mid2=high-length;  
1341      if (x==a[mid1])  
1342           return mid1;  
1343      else if (x<a[mid1])  
1344           return Search(a,low,mid1-1,x);  
1345      else if (x==a[mid2])  
1346           return mid2;  
1347      else if (x<a[mid2])  
1348           return Search(a,mid1+1,mid2-1,x);  
1349      else  
1350           return Search(a,mid2+1,high,x);  
1351 }  
1352 void main()  
1353 { int a[]={1,3,5,7,9,11,13,15};  
1354      int n=sizeof(a)/sizeof(a[0]);  
1355      printf("求解结果\n");  
1356      int x=13;  
1357      int i=Search(a,0,n-1,x);  
1358      if (i!=-1)  
1359           printf("  a[%d]=%d\n",i,x);  
1360      else  
1361           printf("  不存在%d\n",x);  
1362      int y=10;  
1363      int j=Search(a,0,n-1,y);  
1364      if (j!=-1)  
1365           printf("  a[%d]=%d\n",j,y);  
1366      else  
1367           printf("  不存在%d\n",y);  
1368 }  
1369 上述程序的执行结果如图1.27所示。  
1370 
1371 
1372 
1373 
1374 
1375 图1.27  程序执行结果  
1376 17. 解:设f(n)表示n的不同分解式个数。有:  f(1)=1,作为递归出口  
1377 f(2)=1,分解式为:2=2  
1378 f(3)=1,分解式为:3=3  
1379 f(4)=2,分解式为:4=44=2*2  
1380 
1381 291382 
1383 算法设计  
1384 f(6)=3,分解式为:6=66=2*36=3*2,即f(6)=f(1)+f(2)+f(3)  
1385 以此类推,可以看出f(n)为n的所有因数的不同分解式个数之和,即f(n)= 
1386 ∑??%??=0??(??/??)。对应的程序如下:  
1387 #include <stdio.h>  
1388 #define MAX 101  
1389 int solve(int n)           //求n的不同分解式个数  
1390 { if (n==1) return 1;  
1391      else  
1392      { int sum=0;  
1393           for (int i=2;i<=n;i++)  
1394                if (n%i==0)  
1395                     sum+=solve(n/i);  
1396                return sum;  
1397      }  
1398 }  
1399 void main()  
1400 { int n=12;  
1401      int ans=solve(n);  
1402      printf("结果: %d\n",ans);  
1403 }  
1404 上述程序的执行结果如图1.28所示。  
1405 
1406 
1407 
1408 
1409 图1.28  程序执行结果  
1410 18.  解:对应的并行算法如下:  
1411 int Sum(int a[],int s,int t,int p,int i) //处理器i执行求和  
1412 { int j,s=0;  
1413      for (j=s;j<=t;j++)  
1414           s+=a[j];  
1415      return s;  
1416 }  
1417 int ParaSum(int a[],int s,int t,int p,int i)  
1418 { int sum=0,j,k=0,sj;  
1419      for (j=0;j<p;j++)                     //for循环的各个子问题并行执行  
1420      { sj=Sum(a,k,k+n/p-1,p,j);  
1421           k+=n/p;  
1422      }  
1423      sum+=sj;  
1424      return sum;  
1425 }  
1426 每个处理器的执行时间为O(n/p),同步开销为O(p),所以该算法的时间复杂度为 
1427 O(n/p+p)。  
1428 
1429 301430 第1章  概论 1.4 第4章─蛮力法  
1431 
1432 1.4.1  练习题  
1433 1. 简要比较蛮力法和分治法。  
1434 2. 在采用蛮力法求解时什么情况下使用递归?  
1435 3. 考虑下面这个算法,它求的是数组a中大小相差最小的两个元素的差。请对这个 
1436 算法做尽可能多的改进。  
1437 #define INF 99999  
1438 #define abs(x) (x)<0?-(x):(x)      //求绝对值宏  
1439 int Mindif(int a[],int n)  
1440 { int dmin=INF;  
1441      for (int i=0;i<=n-2;i++)  
1442           for (int j=i+1;j<=n-1;j++)  
1443           { int temp=abs(a[i]-a[j]);  
1444                if (temp<dmin)  
1445                     dmin=temp;  
1446           }  
1447      return dmin;  
1448 }  
1449 4. 给定一个整数数组A=(a0,a1,…an-1),若i<j且ai>aj,则<ai,aj>就为一个逆序 
1450 对。例如数组(31452)的逆序对有<31>,<32>,<42>,<52>。设计一 个算法采用蛮力法求A中逆序对的个数即逆序数。  
1451 5. 对于给定的正整数n(n>1), 采用蛮力法求1!+2!+…+n!,并改进该算法提高效 
1452 率。  
1453 6. 有一群鸡和一群兔,它们的只数相同,它们的脚数都是三位数,且这两个三位数 的各位数字只能是0、12345。设计一个算法用蛮力法求鸡和兔的只数各是多 少?它们的脚数各是多少?  
1454 7. 有一个三位数,个位数字比百位数字大,而百位数字又比十位数字大,并且各位 数字之和等于各位数字相乘之积,设计一个算法用穷举法求此三位数。  
1455 8. 某年级的同学集体去公园划船,如果每只船坐10人,那么多出2个座位;如果每 只船多坐2人,那么可少租1只船,设计一个算法用蛮力法求该年级的最多人数?  
1456 9. 已知:若一个合数的质因数分解式逐位相加之和等于其本身逐位相加之和,则称 这个数为Smith数。如4937775=3*5*5*65837,而3+5+5+6+5+8+3+7=421457 4+9+3+7+7+7+5=42,所以4937775是Smith数。求给定一个正整数N,求大于N的最小 Smith数。  
1458 输入:若干个case,每个case一行代表正整数N,输入0表示结束  
1459 输出:大于N的最小Smith数  
1460 输入样例:  
1461 4937774  
1462 0  
1463 样例输出:  
1464 311465 
1466 算法设计  
1467 
1468 4937775  
1469 10. 求解涂棋盘问题。小易有一块n*n的棋盘,棋盘的每一个格子都为黑色或者白 
1470 色,小易现在要用他喜欢的红色去涂画棋盘。小易会找出棋盘中某一列中拥有相同颜色的 最大的区域去涂画,帮助小易算算他会涂画多少个棋格。  
1471 输入描述:输入数据包括n+1行:第一行为一个整数n(1≤ n≤50),即棋盘的大 小,接下来的n行每行一个字符串表示第i行棋盘的颜色,W表示白色,B表示黑色。  
1472 输出描述:输出小易会涂画的区域大小。  
1473 输入例子:  
1474 3  
1475 BWW  
1476 BBB  
1477 BWB  
1478 输出例子:  
1479 3  
1480 11. 给定一个含n(n>1)个整数元素的a,所有元素不相同,采用蛮力法求出a中所 有元素的全排列。  
1481 1.4.2  练习题参考答案  
1482 1. 答:蛮力法是一种简单直接地解决问题的方法,适用范围广,是能解决几乎所有 问题的一般性方法,常用于一些非常基本、但又十分重要的算法(排序、查找、矩阵乘法 和字符串匹配等),蛮力法主要解决一些规模小或价值低的问题,可以作为同样问题的更 
1483 高效算法的一个标准。而分治法采用分而治之思路,把一个复杂的问题分成两个或更多的 相同或相似的子问题,再把子问题分成更小的子问题直到问题解决。分治法在求解问题 
1484 时,通常性能比蛮力法好。  
1485 2. 答:如果用蛮力法求解的问题可以分解为若干个规模较小的相似子问题,此时可 以采用递归来实现算法。  
1486 3. 解:上述算法的时间复杂度为O(n2),采用的是最基本的蛮力法。可以先对a中元 素递增排序,然后依次比较相邻元素的差,求出最小差,改进后的算法如下:  
1487 #include <stdio.h>  
1488 #include <algorithm>  
1489 using namespace std;  
1490 int Mindif1(int a[],int n)  
1491 { sort(a,a+n);                //递增排序  
1492      int dmin=a[1]-a[0];  
1493      for (int i=2;i<n;i++)  
1494      { int temp=a[i]-a[i-1];  
1495           if (temp<dmin)  
1496                dmin=temp;  
1497      }  
1498      return dmin;  
1499 }  
1500 
1501 321502 第1章  概论 
1503 
1504 上述算法的主要时间花费在排序上,算法的时间复杂度为O(nlog2n)。  
1505 4. 解:采用两重循环直接判断是否为逆序对,算法的时间复杂度为O(n2),比第3章 
1506 实验3算法的性能差。对应的算法如下:  
1507 int solve(int a[],int n)      //求逆序数  
1508 { int ans=0;  
1509      for (int i=0;i<n-1;i++)  
1510           for (int j=i+1;j<n;j++)  
1511                if (a[i]>a[j])  
1512                     ans++;  
1513      return ans;  
1514 }  
1515 5. 解:直接采用蛮力法求解算法如下:  
1516 long f(int n)                     //求n!  
1517 { long fn=1;  
1518      for (int i=2;i<=n;i++)  
1519           fn=fn*i;  
1520      return fn;  
1521 }  
1522 long solve(int n)                //求1!+2!+…+n!  
1523 { long ans=0;  
1524      for (int i=1;i<=n;i++)  
1525           ans+=f(i);  
1526      return ans;  
1527 }  
1528 实际上,f(n)=f(n-1)*n,f(1)=1,在求f(n)时可以利用f(n-1)的结果。改进后的算法如 
1529 下:  
1530 long solve1(int n)            //求1!+2!+…+n!  
1531 { long ans=0;  
1532      long fn=1;  
1533      for (int i=1;i<=n;i++)  
1534      { fn=fn*i;  
1535           ans+=fn;  
1536      }  
1537      return ans;  
1538 }  
1539 6. 解:设鸡脚数为y=abc,兔脚数为z=def,有1≤a,d≤50≤b,c,e,f≤5,采 用6重循环,求出鸡只数x1=y/2(y是2的倍数),兔只数x2=z/4(z是4的倍数),当 x1=x2时输出结果。对应的程序如下:  
1540 #include <stdio.h>  
1541 void solve()  
1542 { int a,b,c,d,e,f;  
1543      int x1,x2,y,z;  
1544      for (a=1;a<=5;a++)  
1545           for (b=0;b<=5;b++)  
1546                for (c=0;c<=5;c++)  
1547 
1548 331549 
1550 算法设计  
1551 
1552                     for (d=1;d<=5;d++)  
1553                          for (e=0;e<=5;e++)  
1554                               for (f=0;f<=5;f++)  
1555                               { y=a*100+b*10+c;      //鸡脚数  
1556                                    z=d*100+e*10+f;      //兔脚数  
1557                                    if (y%2!=0 || z%4!=0)  
1558                                         continue;  
1559                                    x1=y/2;                //鸡只数  
1560                                    x2=z/4;                //兔只数  
1561                                    if (x1==x2)  
1562                                         printf("  鸡只数:%d,兔只数:%d,鸡脚数:%d,                                                兔脚数:%d\n",x1,x2,y,z);  
1563                               }  
1564 }  
1565 void main()  
1566 { printf("求解结果\n");  
1567      solve();  
1568 }  
1569 上述程序的执行结果如图1.29所示。  
1570 
1571 
1572 
1573 
1574 
1575 
1576 
1577 
1578 
1579 
1580 
1581 
1582 
1583 图1.29 程序执行结果  
1584 7.     解:设该三位数为x=abc,有1≤a≤90≤b,c≤9,满足c>a,a>b, 
1585 a+b+c=a*b*c。对应的程序如下:  
1586 #include <stdio.h>  
1587 void solve()  
1588 { int a,b,c;  
1589      for (a=1;a<=9;a++)  
1590           for (b=0;b<=9;b++)  
1591                for (c=0;c<=9;c++)  
1592                { if (c>a && a>b && a+b+c==a*b*c)  
1593                          printf("  %d%d%d\n",a,b,c);  
1594                }  
1595 }  
1596 void main()  
1597 
1598 341599 
1600 
1601 { printf("求解结果\n");  
1602      solve();  
1603 }  
1604 上述程序的执行结果如图1.30所示。  
1605  
1606 第1章  概论  
1607 
1608 
1609 
1610 
1611 
1612 图1.30 程序执行结果  
1613 8. 解:设该年级的人数为x,租船数为y。因为每只船坐10人正好多出2个座位,则 
1614 x=10*y-2;因为每只船多坐2人即12人时可少租1只船(没有说恰好全部座位占满),有 x+z=12*(y-1),z表示此时空出的座位,显然z<12。让y从1到100(实际上y取更大范围 的结果是相同的)、z从0到11枚举,求出最大的x即可。对应的程序如下:  
1615 #include <stdio.h>  
1616 int solve()  
1617 { int x,y,z;  
1618      for (y=1;y<=100;y++)  
1619           for (z=0;z<12;z++)  
1620                if (10*y-2==12*(y-1)-z)  
1621                     x=10*y-2;  
1622      return x;  
1623 }  
1624 void main()  
1625 { printf("求解结果\n");  
1626      printf("  最多人数:%d\n",solve());  
1627 }  
1628 上述程序的执行结果如图1.31所示。  
1629 
1630 
1631 
1632 
1633 
1634 图1.31 程序执行结果  
1635 9. 解:采用蛮力法求出一个正整数n的各位数字和sum1,以及n的所有质因数的数 
1636 字和sum2,若sum1=sum2,即为Smitch数。从用户输入的n开始枚举,若是Smitch 
1637 数,输出,本次结束,否则n++继续查找大于n的最小Smitch数。对应的完整程序如 
1638 下:  
1639 #include <stdio.h>  
1640 int Sum(int n)          //求n的各位数字和  
1641 { int sum=0;  
1642      while (n>0)  
1643 351644 
1645 算法设计  
1646 
1647      { sum+=n%10;  
1648           n=n/10;  
1649      }  
1650      return sum;  
1651 }  
1652 bool solve(int n)      //判断n是否为Smitch数  
1653 { int m=2;  
1654      int sum1=Sum(n);  
1655      int sum2=0;  
1656      while (n>=m)  
1657      { if (n%m==0) //找到一个质因数m  
1658           { n=n/m;  
1659                sum2+=Sum(m);  
1660           }  
1661           else  
1662                m++;  
1663      }  
1664      if (sum1==sum2)  
1665           return true;  
1666      else  
1667           return false;  
1668 }  
1669 void main()  
1670 { int n;  
1671      while (true)  
1672      { scanf("%d",&n);  
1673           if (n==0) break;  
1674           while (!solve(n))  
1675                n++;  
1676           printf("%d\n",n);  
1677      }  
1678 }  
1679 10. 解:采用蛮力法,统计每一列相邻相同颜色的棋格个数countj,在countj中求最 大值。对应的程序如下:  
1680 #include <stdio.h>  
1681 #define MAXN 51  
1682 //问题表示  
1683 int n;  
1684 char board[MAXN][MAXN];  
1685 int getMaxArea()                     //蛮力法求解算法  
1686 { int maxArea=0;  
1687      for (int j=0; j<n; j++)  
1688      { int countj=1;  
1689           for (int i=1; i<n; i++)      //统计第j列中相同颜色相邻棋格个数  
1690           { if (board[i][j]==board[i-1][j])  
1691                     countj++;  
1692                else  
1693                     countj=1;  
1694           }  
1695 
1696 361697 第1章  概论 
1698 
1699           if (countj>maxArea)  
1700                maxArea=countj;  
1701      }  
1702      return maxArea;  
1703 }  
1704 int main()  
1705 { scanf("%d",&n);  
1706      for (int i=0;i<n;i++)  
1707           scanf("%s",board[i]);  
1708      printf("%d\n",getMaxArea());  
1709      return 0;  
1710 }  
1711 11. 解:与《教程》中求全排列类似,但需要将求1~n的全排列改为按下标0~n-1 求a的全排列(下标从0开始)。采用非递归的程序如下:  
1712 #include <stdio.h>  
1713 #include <vector>  
1714 using namespace std;  
1715 vector<vector<int> > ps;                          //存放全排列  
1716 void Insert(vector<int> s,int a[],int i,vector<vector<int> > &ps1)  
1717 //在每个集合元素中间插入i得到ps1  
1718 { vector<int> s1;  
1719      vector<int>::iterator it;  
1720      for (int j=0;j<=i;j++)                          //在s(含i个整数)的每个位置插入a[i]  
1721      { s1=s;  
1722           it=s1.begin()+j;                          //求出插入位置  
1723           s1.insert(it,a[i]);                         //插入整数a[i]  
1724           ps1.push_back(s1);                          //添加到ps1中  
1725      }  
1726 }  
1727 void Perm(int a[],int n)                          //求a[0..n-1]的所有全排列  
1728 { vector<vector<int> > ps1;                     //临时存放子排列  
1729      vector<vector<int> >::iterator it;           //全排列迭代器  
1730      vector<int> s,s1;  
1731      s.push_back(a[0]);  
1732      ps.push_back(s);                               //添加{a[0]}集合元素  
1733      for (int i=1;i<n;i++)                          //循环添加a[1]~a[n-1]  
1734      { ps1.clear();                               //ps1存放插入a[i]的结果  
1735           for (it=ps.begin();it!=ps.end();++it)  
1736                Insert(*it,a,i,ps1);                //在每个集合元素中间插入a[i]得到ps1  
1737           ps=ps1;  
1738      }  
1739 }  
1740 void dispps()                                         //输出全排列ps  
1741 { vector<vector<int> >::reverse_iterator it;     //全排列的反向迭代器  
1742      vector<int>::iterator sit;                     //排列集合元素迭代器  
1743      for (it=ps.rbegin();it!=ps.rend();++it)  
1744      { for (sit=(*it).begin();sit!=(*it).end();++sit)  
1745                printf("%d",*sit);  
1746           printf("  ");  
1747 
1748 371749 
1750 算法设计  
1751 
1752      }  
1753      printf("\n");  
1754 }  
1755 void main()  
1756 { int a[]={2,5,8};  
1757      int n=sizeof(a)/sizeof(a[0]);  
1758      printf("a[0~%d]的全排序如下:\n  ",n-1);       Perm(a,n);  
1759      dispps();  
1760 }  
1761 上述程序的执行结果如图1.32所示。  
1762 
1763 
1764 
1765 
1766 
1767 图1.32 程序执行结果  
1768 1.5 第5章─回溯法  
1769 
1770 1.5.1  练习题  
1771 1. 回溯法在问题的解空间树中,按( )策略,从根结点出发搜索解空间树。  
1772 A.广度优先  B.活结点优先  C.扩展结点优先      D.深度优先  
1773 2. 关于回溯法以下叙述中不正确的是( )。  
1774 A.回溯法有“通用解题法”之称,它可以系统地搜索一个问题的所有解或任意解  
1775 B.回溯法是一种既带系统性又带有跳跃性的搜索算法  
1776 C.回溯算法需要借助队列这种结构来保存从根结点到当前扩展结点的路径  
1777 D.回溯算法在生成解空间的任一结点时,先判断该结点是否可能包含问题的解,如果 
1778 肯定不包含,则跳过对该结点为根的子树的搜索,逐层向祖先结点回溯  
1779 3. 回溯法的效率不依赖于下列哪些因素( )。  
1780 A.确定解空间的时间            B.满足显约束的值的个数  
1781 C.计算约束函数的时间            D.计算限界函数的时间   
1782 4. 下面( )函数是回溯法中为避免无效搜索采取的策略。  
1783 A.递归函数  B.剪枝函数      C.随机数函数  D.搜索函数  
1784 5.回溯法的搜索特点是什么?   
1785 6. 用回溯法解0/1背包问题时,该问题的解空间是何种结构?用回溯法解流水作业调 
1786 度问题时,该问题的解空间是何种结构?  
1787 7. 对于递增序列a[]={12345},采用例5.4的回溯法求全排列,以1、2开头 
1788 的排列一定最先出现吗?为什么?  
1789 8. 考虑n皇后问题,其解空间树为由1、2、…、n构成的n!种排列所组成。现用回 
1790 
1791 381792 第1章  概论 
1793 
1794 溯法求解,要求:  
17951)通过解搜索空间说明n=3时是无解的。  
17962)给出剪枝操作。  
17973)最坏情况下在解空间树上会生成多少个结点?分析算法的时间复杂度。  
1798 9. 设计一个算法求解简单装载问题,设有一批集装箱要装上一艘载重量为W的轮 
1799 船,其中编号为i(0≤i≤n-1)的集装箱的重量为wi。现要从n个集装箱中选出若干装上 轮船,使它们的重量之和正好为W。如果找到任一种解返回true,否则返回false。  
1800 10. 给定若干个正整数a0、a0 、…、an-1 ,从中选出若干数,使它们的和恰好为k, 要求找选择元素个数最少的解。   
1801 11. 设计求解有重复元素的排列问题的算法,设有n个元素a[]={a0,a1,…,an-1), 其中可能含有重复的元素,求这些元素的所有不同排列。如a[]={112},输出结果是 (112),(121),(211)。  
1802 12. 采用递归回溯法设计一个算法求1~n的n个整数中取出m个元素的排列,要求每个 元素最多只能取一次。例如,n=3,m=2的输出结果是(12),(13),(21), (23),(31),(32)。  
1803 13. 对于n皇后问题,有人认为当n为偶数时,其解具有对称性,即n皇后问题的解个 数恰好为n/2皇后问题的解个数的2倍,这个结论正确吗?请编写回溯法程序对n=468、10的情况进行验证。  
1804 14. 给定一个无向图,由指定的起点前往指定的终点,途中经过所有其他顶点且只经 过一次,称为哈密顿路径,闭合的哈密顿路径称作哈密顿回路(Hamiltonian cycle)。设计 一个回溯算法求无向图的所有哈密顿回路。  
1805 1.5.2  练习题参考答案  
1806 1. 答:D。  
1807 2. 答:回溯算法是采用深度优先遍历的,需要借助系统栈结构来保存从根结点到当 
1808 前扩展结点的路径。答案为C。  
1809 3. 答:回溯法解空间是虚拟的,不必确定整个解空间。答案为A。  
1810 4. 答:B。  
1811 5. 答:回溯法在解空间树中采用深度优先遍历方式进行解搜索,即用约束条件和限 
1812 界函数考察解向量元素x[i]的取值,如果x[i]是合理的就搜索x[i]为根结点的子树,如果 
1813 x[i]取完了所有的值,便回溯到x[i-1]。  
1814 6. 答:用回溯法解0/1背包问题时,该问题的解空间是子集树结构。用回溯法解流水 作业调度问题时,该问题的解空间是排列树结构。  
1815 7. 答:是的。对应的解空间是一棵排列树,如图1.33所示给出前面3层部分,显然 最先产生的排列是从G结点扩展出来的叶子结点,它们就是以1、2开头的排列。  
1816 
1817 
1818 
1819 
1820 
1821 391822 
1823 
1824 
1825 
1826 1 
1827  
1828 
1829 算法设计  
1830 
1831 A 
1832 23    4 
1833  
1834 
1835 
1836 
1837 
1838 5  
1839 
1840 2 
1841  
1842 B    C 
1843 34    5 
1844  
1845 D    E    F  
1846 G    H    I    J 
1847 图1.33  部分解空间树  
1848 8. 答:(1)n=3时的解搜索空间如图1.34所示,不能得到任何叶子结点,所有无 
1849 解。  
18502)剪枝操作是任何两个皇后不能同行、同列和同两条对角线。  
18513)最坏情况下每个结点扩展n个结点,共有nn个结点,算法的时间复杂度为 
1852 O(nn)。  
1853 (*,*,*)  
1854 
1855 
1856 
1857 (1,*,*) 
1858     
1859 (1,3,*) 
1860 
1861 
1862  
1863 
1864 
1865 (2,*,*) 
1866  
1867 
1868 
1869 
1870 (3,*,*) 
1871     
1872 (3,1,*) 
1873  
1874 图1.34  3皇后问题的解搜索空间  
1875 9. 解:用数组w[0..n-1]存放n个集装箱的重量,采用类似判断子集和是否存在解的 
1876 方法求解。对应完整的求解程序如下:  
1877 #include <stdio.h>  
1878 #define MAXN 20                     //最多集装箱个数  
1879 //问题表示  
1880 int n=5,W;  
1881 int w[]={2,9,5,6,3};  
1882 int count;                          //全局变量,累计解个数  
1883 void dfs(int tw,int rw,int i)      //求解简单装载问题  
1884 { if (i>=n)                          //找到一个叶子结点  
1885      { if (tw==W)                //找到一个满足条件的解,输出它  
1886                count++;  
1887      }  
1888      else                               //尚未找完  
1889      { rw-=w[i];                     //求剩余的集装箱重量和  
1890           if (tw+w[i]<=W)           //左孩子结点剪枝:选取满足条件的集装箱w[i]  
1891                dfs(tw+w[i],rw,i+1); //选取第i个集装箱  
1892           if (tw+rw>=W)                //右孩子结点剪枝:剪除不可能存在解的结点  
1893                dfs(tw,rw,i+1);      //不选取第i个集装箱,回溯  
1894      }  
1895 }  
1896 bool solve()                          //判断简单装载问题是否存在解  
1897 
1898 401899 第1章  概论 
1900 
1901 { count=0;  
1902      int rw=0;  
1903      for (int j=0;j<n;j++)           //求所有集装箱重量和rw            rw+=w[j];  
1904      dfs(0,rw,0);                     //i从0开始  
1905      if (count>0)  
1906           return true;  
1907      else  
1908           return false;  
1909 }  
1910 void main()  
1911 { printf("求解结果\n");  
1912      W=4;  
1913      printf("  W=%d时%s\n",W,(solve()?"存在解":"没有解"));       W=10;  
1914      printf("  W=%d时%s\n",W,(solve()?"存在解":"没有解"));       W=12;  
1915      printf("  W=%d时%s\n",W,(solve()?"存在解":"没有解"));       W=21;  
1916      printf("  W=%d时%s\n",W,(solve()?"存在解":"没有解"));  }  
1917 本程序执行结果如图1.35所示。  
1918 
1919 
1920 
1921 
1922 
1923 
1924 图1.35  程序执行结果  
1925 10. 解:这是一个典型的解空间为子集树的问题,采用子集树的回溯算法框架。当找 
1926 到一个解后通过选取的元素个数进行比较求最优解minpath。对应的完整程序如下:  
1927 #include <stdio.h>  
1928 #include <vector>  
1929 using namespace std;  
1930 //问题表示  
1931 int a[]={1,2,3,4,5};                //设置为全局变量  
1932 int n=5,k=9;    
1933 vector<int> minpath;                //存放最优解  
1934 //求解结果表示  
1935 int minn=n;                          //最多选择n个元素  
1936 void disppath()                     //输出一个解  
1937 { printf("  选择的元素:");  
1938      for (int j=0;j<minpath.size();j++)  
1939           printf("%d ",minpath[j]);  
1940      printf("元素个数=%d\n",minn);  
1941 }  
1942 
1943 411944 
1945 算法设计  
1946 
1947 void dfs(vector<int> path,int sum,int start) //求解算法  
1948 { if (sum==k)                     //如果找到一个解,不一定到叶子结点       { if (path.size()<minn)  
1949           { minn=path.size();  
1950                minpath=path;  
1951           }  
1952           return;  
1953      }  
1954      if (start>=n) return;           //全部元素找完,返回  
1955      dfs(path,sum,start+1);           //不选择a[start]  
1956      path.push_back(a[start]);      //选择a[start]  
1957      dfs(path,sum+a[start],start+1);  
1958 }  
1959 void main()  
1960 { vector<int> path;                //path存放一个子集  
1961      dfs(path,0,0);  
1962      printf("最优解:\n");  
1963      disppath();  
1964 }  
1965 上述程序的执行结果如图1.36所示。  
1966 
1967 
1968 
1969 
1970 
1971 图1.36  程序执行结果  
1972 11. 解:在回溯法求全排列的基础上,增加元素的重复性判断。例如,对于a[]={11973 12},不判断重复性时输出(112),(121),(112),(121), (211),(211),共6个,有3个是重复的。重复性判断是这样的,对于在扩 展a[i]时,仅仅将与a[i..j-1]没有出现的元素a[j]交换到a[i]的位置,如果出现,对应的排 列已经在前面求出了。对应的完整程序如下:  
1974 #include <stdio.h>  
1975 bool ok(int a[],int i,int j)     //ok用于判别重复元素  
1976 { if (j>i)  
1977      { for(int k=i;k<j;k++)  
1978                if (a[k]==a[j])  
1979                     return false;  
1980      }  
1981      return true;  
1982 }  
1983 void swap(int &x,int &y)      //交换两个元素  
1984 { int tmp=x;  
1985      x=y; y=tmp;  
1986 }  
1987 void dfs(int a[],int n,int i) //求有重复元素的排列问题  
1988 { if (i==n)  
1989 
1990 421991 第1章  概论 
1992 
1993      { for(int j=0;j<n;j++)  
1994                printf("%3d",a[j]);  
1995           printf("\n");  
1996      }  
1997      else  
1998      { for (int j=i;j<n;j++)  
1999                if (ok(a,i,j))     //选取与a[i..j-1]不重复的元素a[j]                 { swap(a[i],a[j]);  
2000                     dfs(a,n,i+1);  
2001                     swap(a[i],a[j]);  
2002                }  
2003      }  
2004 }  
2005 void main()  
2006 { int a[]={1,2,1,2};  
2007      int n=sizeof(a)/sizeof(a[0]);  
2008      printf("序列(");  
2009      for (int i=0;i<n-1;i++)  
2010           printf("%d ",a[i]);  
2011      printf("%d)的所有不同排列:\n",a[n-1]);  
2012      dfs(a,n,0);  
2013 }  
2014 上述程序的执行结果如图1.37所示。  
2015 
2016 
2017 
2018 
2019 
2020 
2021 
2022 图1.37  程序执行结果  
2023 12. 解:采用求全排列的递归框架。选取的元素个数用i表示(i从1开始),当i>m 
2024 时达到一个叶子结点,输出一个排列。为了避免重复,用used数组实现,used[i]=0表示 没有选择整数i,used[i]=1表示已经选择整数i。对应的完整程序如下:  
2025 #include <stdio.h>  
2026 #include <string.h>  
2027 #define MAXN 20  
2028 #define MAXM 10  
2029 int m,n;  
2030 int x[MAXM];                               //x[1..m]存放一个排列  
2031 bool used[MAXN];  
2032 void dfs(int i)                          //求n个元素中m个元素的全排列  
2033 { if (i>m)  
2034      { for (int j=1;j<=m;j++)  
2035                printf("  %d",x[j]);      //输出一个排列  
2036           printf("\n");  
2037 
2038 432039 
2040 算法设计  
2041 
2042      }  
2043      else  
2044      { for (int j=1;j<=n;j++)  
2045           { if (!used[j])  
2046                { used[j]=true;           //修改used[i]  
2047                     x[i]=j;                //x[i]选择j  
2048                     dfs(i+1);                //继续搜索排列的下一个元素                      used[j]=false;          //回溯:恢复used[i]  
2049                }  
2050           }  
2051      }  
2052 }  
2053 void main()  
2054 { n=4,m=2;  
2055      memset(used,0,sizeof(used));          //初始化为0  
2056      printf("n=%d,m=%d的求解结果\n",n,m);  
2057      dfs(1);                               //i从1开始  
2058 }  
2059 上述程序的执行结果如图1.38所示。  
2060 
2061 
2062 
2063 
2064 
2065 
2066 
2067 
2068 
2069 
2070 图1.38  程序执行结果  
2071 13. 解:这个结论不正确。验证程序如下:  
2072 #include <stdio.h>  
2073 #include <stdlib.h>  
2074 #define MAXN 10  
2075 int q[MAXN];  
2076 bool place(int i)                //测试第i行的q[i]列上能否摆放皇后  
2077 { int j=1;  
2078      if (i==1) return true;  
2079      while (j<i)                //j=1~i-1是已放置了皇后的行  
2080      { if ((q[j]==q[i]) || (abs(q[j]-q[i])==abs(j-i)))   
2081                //该皇后是否与以前皇后同列,位置(j,q[j])与(i,q[i])是否同对角线                 return false;  
2082           j++;  
2083      }  
2084      return true;  
2085 }  
2086 442087 第1章  概论 
2088 
2089 int Queens(int n)                //求n皇后问题的解个数  
2090 { int count=0,k;               //计数器初始化  
2091      int i=1;                     //i为当前行  
2092      q[1]=0;                     //q[i]为皇后i的列号  
2093      while (i>0)  
2094      { q[i]++;                //移到下一列  
2095           while (q[i]<=n && !place(i))  
2096                q[i]++;  
2097           if (q[i]<=n)  
2098           { if (i==n)  
2099                     count++;      //找到一个解计数器count加1  
2100                else  
2101                {  
2102                     i++;; q[i]=0;  
2103                }  
2104           }  
2105           else i--;                //回溯  
2106      }  
2107      return count;  
2108 }  
2109 void main()  
2110 { printf("验证结果如下:\n");  
2111      for (int n=4;n<=10;n+=2)  
2112           if (Queens(n)==2*Queens(n/2))  
2113                printf("  n=%d: 正确\n",n);  
2114           else  
2115                printf("  n=%d: 错误\n",n);  
2116 }  
2117 上述程序的执行结果如图1.39所示。从执行结果看出结论是不正确的。  
2118 
2119 
2120 
2121 
2122 
2123 
2124 图1.39  程序执行结果  
2125 14. 解:假设给定的无向图有n个顶点(顶点编号从0到n-1),采用邻接矩阵数组a 
21260/1矩阵)存放,求从顶点v出发回到顶点v的哈密顿回路。采用回溯法,解向量为 
2127 x[0..n],x[i]表示第i步找到的顶点编号(i=n-1时表示除了起点v外其他顶点都查找了), 初始时将起点v存放到x[0],i从1开始查找,i>0时循环:为x[i]找到一个合适的顶点, 当i=n-1时,若顶点x[i]到顶点v有边对应一个解;否则继续查找下一个顶点。如果不能 为x[i]找到一个合适的顶点,则回溯。采用非递归回溯框架(与《教程》中求解n皇后问 题的非递归回溯框架类似)的完整程序如下:  
2128 #include <stdio.h>  
2129 #define MAXV 10  
2130 
2131 452132 
2133 算法设计  
2134 
2135 //求解问题表示  
2136 int n=5;                          //图中顶点个数  
2137 int a[MAXV][MAXV]={{0,1,1,1,0},{1,0,0,1,1},{1,0,0,0,1},{1,1,0,0,1},{0,1,1,1,0}};                                     //邻接矩阵数组  
2138 //求解结果表示  
2139 int x[MAXV];  
2140 int count;  
2141 void dispasolution()           //输出一个解路径  
2142 { for (int i=0;i<=n-1;i++)  
2143           printf("(%d,%d) ",x[i],x[i+1]);  
2144      printf("\n");  
2145 }  
2146 bool valid(int i)                //判断顶点第i个顶点x[i]的有效性  
2147 { if (a[x[i-1]][x[i]]!=1)      //x[i-1]到x[i]没有边,返回false  
2148           return false;  
2149      for (int j=0;j<=i-1;j++)  
2150            if (x[i]==x[j])      //顶点i重复出现,返回false  
2151                 return false;  
2152      return true;   
2153 }  
2154 void Hamiltonian(int v)           //求从顶点v出发的哈密顿回路  
2155 { x[0]=v;                     //存放起点  
2156      int i=1;  
2157      x[i]=-1;                     //从顶点-1+1=0开始试探  
2158      while (i>0)                //尚未回溯到头,循环  
2159      { x[i]++;  
2160           while (!valid(i) && x[i]<n)  
2161                x[i]++;           //试探一个顶点x[i]  
2162           if (x[i]<n)           //找到一个有效的顶点x[i]  
2163           { if (i==n-1)      //达到叶子结点  
2164                { if (a[x[i]][v]==1)   
2165                     { x[n]=v; //找到一个解  
2166                          printf("    第%d个解: ",count++);  
2167                          dispasolution();  
2168                     }  
2169                }  
2170                else  
2171                {  
2172                     i++; x[i]=-1;  
2173                }  
2174           }  
2175           else  
2176                i--;                //回溯  
2177      }  
2178 }  
2179 void main()  
2180 { printf("求解结果\n");  
2181      for (int v=0;v<n;v++)  
2182      { printf("  从顶点%d出发的哈密顿回路:\n",v);  
2183           count=1;  
2184 
2185 462186 第1章  概论 
2187 
2188           Hamiltonian(v);      //从顶点v出发  
2189      }  
2190 }  
2191 上述程序对如图1.40所示的无向图求从每个顶点出发的哈密顿回路,程序执行结果 如图1.41所示。  
2192 1  
2193 
2194 
2195 0 
2196  
2197 
2198 
2199 3 
2200 
2201 
2202 2 
2203  
2204 
2205 
2206 4  
2207 
2208 图1.40  一个无向图  
2209 
2210 
2211 
2212 
2213 
2214 
2215 
2216 
2217 
2218 
2219 
2220 
2221 
2222 
2223 
2224 
2225 
2226 图1.41  程序执行结果  
2227 1.6 第6章─分枝限界法  
2228 
2229 1.6.1  练习题  
2230 1. 分枝限界法在问题的解空间树中,按( )策略,从根结点出发搜索解空间树。  
2231 A.广度优先  B.活结点优先  C.扩展结点优先      D. 深度优先  
2232 2. 常见的两种分枝限界法为( )。  
2233 A.广度优先分枝限界法与深度优先分枝限界法  
2234 
2235 472236 
2237 算法设计  
2238 
2239 B.队列式(FIFO)分枝限界法与堆栈式分枝限界法  
2240 C.排列树法与子集树法  
2241 D.队列式(FIFO)分枝限界法与优先队列式分枝限界法  
2242 3. 分枝限界法求解0/1背包问题时,活结点表的组织形式是( )。  
2243 A.小根堆       B.大根堆       C.栈                 D.数组  
2244 4. 采用最大效益优先搜索方式的算法是( )。  
2245 A.分支界限法  B.动态规划法  C.贪心法            D.回溯法  
2246 5. 优先队列式分枝限界法选取扩展结点的原则是( )。  
2247 A.先进先出  B.后进先出      C.结点的优先级      D.随机  
2248 6. 简述分枝限界法的搜索策略。  
2249 7. 有一个0/1背包问题,其中n=4,物品重量为(4753),物品价值为(402250 422512),背包最大载重量W=10,给出采用优先队列式分枝限界法求最优解的过程。  8. 有一个流水作业调度问题,n=4,a[]={51097},b[]={7598},给出采 
2251 用优先队列式分枝限界法求一个解的过程。  
2252 9. 有一个含n个顶点(顶点编号为0~n-1)的带权图,采用邻接矩阵数组A表示, 
2253 采用分枝限界法求从起点s到目标点t的最短路径长度,以及具有最短路径长度的路径条 数。  
2254 10. 采用优先队列式分枝限界法求解最优装载问题。给出以下装载问题的求解过程和 结果:n=5,集装箱重量为w=(52643),限重为W=10。在装载重量相同时,最 优装载方案是集装箱个数最少的方案。  
2255 1.6.2  练习题参考答案  
2256 1. 答:A。  
2257 2. 答:D。  
2258 3. 答:B。  
2259 4. 答:A。  
2260 5. 答:C。  
2261 6. 答:分枝限界法的搜索策略是广度优先遍历,通过限界函数可以快速找到一个解 
2262 或者最优解。  
2263 7. 答:求解过程如下:  
22641)根结点1进队,对应结点值:e.i=0,e.w=0,e.v=0,e.ub=76,x:[0000]。  
22652)出队结点1:左孩子结点2进队,对应结点值:e.no=2,e.i=1,e.w=42266 e.v=40,e.ub=76,x:[1000];右孩子结点3进队,对应结点值:e.no=3,e.i=12267 e.w=0,e.v=0,e.ub=57,x:[0000]。  
22683)出队结点2:左孩子超重;右孩子结点4进队,对应结点值:e.no=4,e.i=22269 e.w=4,e.v=40,e.ub=69,x:[1000]。  
22704)出队结点4:左孩子结点5进队,对应结点值:e.no=5,e.i=3,e.w=92271 e.v=65,e.ub=69,x:[1010];右孩子结点6进队,对应结点值:e.no=6,e.i=32272 e.w=4,e.v=40,e.ub=52,x:[1000]。  
2273 482274 第1章  概论 
2275 
22765)出队结点5:产生一个解,maxv= 65,bestx:[1010]。  
22776)出队结点3:左孩子结点8进队,对应结点值:e.no=8,e.i=2,e.w=72278 e.v=42,e.ub=57,x:[0100];右孩子结点9被剪枝。  
22797)出队结点8:左孩子超重;右孩子结点10被剪枝。  
22808)出队结点6:左孩子结点11超重;右孩子结点12被剪枝。  
22819)队列空,算法结束,产生的最优解:maxv= 65,bestx:[1010]。  
2282 8. 答:求解过程如下:  
22831)根结点1进队,对应结点值:e.i=0,e.f1=0,e.f2=0,e.lb=29, x:[0002284 0]。  
22852)出队结点1:扩展结点如下:  
2286 进队(j=1):结点2,e.i=1,e.f1=5,e.f2=12,e.lb=27,x:[1000]。  
2287 进队(j=2):结点3,e.i=1,e.f1=10,e.f2=15,e.lb=34,x:[2000]。  
2288 进队(j=3):结点4,e.i=1,e.f1=9,e.f2=18,e.lb=29,x:[3000]。  
2289 进队(j=4):结点5,e.i=1,e.f1=7,e.f2=15,e.lb=28,x:[4000]。  
22903)出队结点2:扩展结点如下:  
2291 进队(j=2):结点6,e.i=2,e.f1=15,e.f2=20,e.lb=32,x:[1200]。  
2292 进队(j=3):结点7,e.i=2,e.f1=14,e.f2=23,e.lb=27,x:[1300]。  
2293 进队(j=4):结点8,e.i=2,e.f1=12,e.f2=20,e.lb=26,x:[1400]。  
22944)出队结点8:扩展结点如下:  
2295 进队(j=2):结点9,e.i=3,e.f1=22,e.f2=27,e.lb=31,x:[1420]。  
2296 进队(j=3):结点10,e.i=3,e.f1=21,e.f2=30,e.lb=26,x:[1430]。  
22975)出队结点10,扩展一个j=2的子结点,有e.i=4,到达叶子结点,产生的一个解 
2298 是e.f1=31,e.f2=36,e.lb=31,x=[1432]。  
2299 该解对应的调度方案是:第1步执行作业1,第2步执行作业4,第3步执行作业 
2300 3,第4步执行作业2,总时间=362301 9.  解:采用优先队列式分枝限界法求解,队列中结点的类型如下:  
2302 struct NodeType  
2303 { int vno;                                    //顶点的编号  
2304      int length;                               //当前结点的路径长度  
2305      bool operator<(const NodeType &s) const //重载<关系函数  
2306      { return length>s.length;  }           //length越小越优先  
2307 };  
2308 从顶点s开始广度优先搜索,找到目标点t后比较求最短路径长度及其路径条数。对 应的完整程序如下:  
2309 #include <stdio.h>  
2310 #include <queue>  
2311 using namespace std;  
2312 #define MAX 11  
2313 #define INF 0x3f3f3f3f  
2314 //问题表示  
2315 int A[MAX][MAX]={                               //一个带权有向图  
2316 
2317 492318 
2319 算法设计  
2320 
2321           {014,INF,INF},  
2322           {INF,0,INF,15},  
2323           {INF,INF,0,INF,1},  
2324           {INF,INF,203},  
2325           {INF,INF,INF,INF,INF} };  
2326 int n=5;  
2327 //求解结果表示  
2328 int bestlen=INF;                               //最优路径的路径长度  
2329 int bestcount=0;                               //最优路径的条数  
2330 struct NodeType  
2331 { int vno;                                    //顶点的编号  
2332      int length;                               //当前结点的路径长度  
2333      bool operator<(const NodeType &s) const //重载>关系函数  
2334      { return length>s.length;  }           //length越小越优先  
2335 };  
2336 void solve(int s,int t)                     //求最短路径问题  
2337 { NodeType e,e1;                          //定义2个结点  
2338      priority_queue<NodeType> qu;            //定义一个优先队列qu  
2339      e.vno=s;                                    //构造根结点  
2340      e.length=0;  
2341      qu.push(e);                               //根结点进队  
2342      while (!qu.empty())                         //队不空循环  
2343      { e=qu.top(); qu.pop();                //出队结点e作为当前结点  
2344           if (e.vno==t)                          //e是一个叶子结点  
2345           { if (e.length<bestlen)           //比较找最优解  
2346                { bestcount=1;  
2347                     bestlen=e.length;           //保存最短路径长度  
2348                }  
2349                else if (e.length==bestlen)  
2350                     bestcount++;  
2351           }  
2352           else                                    //e不是叶子结点  
2353           { for (int j=0; j<n; j++)           //检查e的所有相邻顶点  
2354                     if (A[e.vno][j]!=INF && A[e.vno][j]!=0)   //顶点e.vno到顶点j有边                      { if (e.length+A[e.vno][j]<bestlen)     //剪枝  
2355                          { e1.vno=j;  
2356                               e1.length=e.length+A[e.vno][j];  
2357                               qu.push(e1);                  //有效子结点e1进队  
2358                          }  
2359                     }  
2360           }  
2361      }  
2362 }  
2363 void main()  
2364 { int s=0,t=4;  
2365      solve(s,t);  
2366      if (bestcount==0)  
2367           printf("顶点%d到%d没有路径\n",s,t);  
2368      else  
2369      { printf("顶点%d到%d存在路径\n",s,t);  
2370 
2371 502372 第1章  概论 
2373 
2374           printf("   最短路径长度=%d,条数=%d\n", bestlen,bestcount);            //输出:5 3  
2375      }  
2376 }  
2377 上述程序的执行结果如图1.39所示。  
2378 
2379 
2380 
2381 
2382 
2383 图1.39  程序执行结果  
2384 10.     解:采用优先队列式分枝限界法求解。设计优先队列 
2385 priority_queue<NodeType>,并设计优先队列的关系比较函数Cmp,指定按结点的ub值进 行比较,即ub值越大的结点越先出队。对应的完整程序如下:  
2386 #include <stdio.h>  
2387 #include <queue>  
2388 using namespace std;  
2389 #define MAXN 21                          //最多的集装箱数  
2390 //问题表示  
2391 int n=5;  
2392 int W=10;  
2393 int w[]={0,5,2,6,4,3};                     //集装箱重量,不计下标0的元素  
2394 //求解结果表示  
2395 int bestw=0;                               //存放最大重量,全局变量  
2396 int bestx[MAXN];                          //存放最优解,全局变量  
2397 int Count=1;                               //搜索空间中结点数累计,全局变量  
2398 typedef struct   
2399 { int no;                               //结点编号  
2400      int i;                               //当前结点在解空间中的层次  
2401      int w;                               //当前结点的总重量  
2402      int x[MAXN];                          //当前结点包含的解向量  
2403      int ub;                               //上界  
2404 } NodeType;  
2405 struct Cmp                               //队列中关系比较函数  
2406 { bool operator()(const NodeType &s,const NodeType &t)  
2407      { return (s.ub<t.ub) || (s.ub==t.ub && s.x[0]>t.x[0]);  
2408           //ub越大越优先,当ub相同时x[0]越小越优先  
2409      }  
2410 };  
2411 void bound(NodeType &e)                     //计算分枝结点e的上界  
2412 { int i=e.i+1;  
2413      int r=0;                               //r为剩余集装箱的重量  
2414      while (i<=n)  
2415      { r+=w[i];  
2416           i++;  
2417      }  
2418      e.ub=e.w+r;  
2419 
2420 512421 
2422 算法设计  
2423 
2424 }  
2425 void Loading()                              //求装载问题的最优解  
2426 { NodeType e,e1,e2;                     //定义3个结点  
2427      priority_queue<NodeType,vector<NodeType>,Cmp > qu; //定义一个优先队列qu  
2428      e.no=Count++;                          //设置结点编号  
2429      e.i=0;                               //根结点置初值,其层次计为0  
2430      e.w=0;  
2431      for (int j=0; j<=n; j++)           //初始化根结点的解向量  
2432           e.x[j]=0;  
2433      bound(e);                               //求根结点的上界  
2434      qu.push(e);                          //根结点进队  
2435      while (!qu.empty())                    //队不空循环  
2436      { e=qu.top(); qu.pop();           //出队结点e作为当前结点  
2437           if (e.i==n)                     //e是一个叶子结点  
2438           { if ((e.w>bestw) || (e.w==bestw && e.x[0]<bestx[0])) //比较找最优解                 { bestw=e.w;           //更新bestw  
2439                     for (int j=0;j<=e.i;j++)  
2440                          bestx[j]=e.x[j]; //复制解向量e.x->bestx  
2441                }  
2442           }  
2443           else                               //e不是叶子结点  
2444           { if (e.w+w[e.i+1]<=W)      //检查左孩子结点  
2445                { e1.no=Count++;          //设置结点编号  
2446                     e1.i=e.i+1;           //建立左孩子结点  
2447                     e1.w=e.w+w[e1.i];  
2448                     for (int j=0; j<=e.i; j++)  
2449                          e1.x[j]=e.x[j]; //复制解向量e.x->e1.x  
2450                     e1.x[e1.i]=1;           //选择集装箱i  
2451                     e1.x[0]++;           //装入集装箱数增1  
2452                     bound(e1);           //求左孩子结点的上界  
2453                     qu.push(e1);           //左孩子结点进队  
2454                }  
2455                e2.no=Count++;               //设置结点编号  
2456                e2.i=e.i+1;                //建立右孩子结点  
2457                e2.w=e.w;   
2458                for (int j=0; j<=e.i; j++) //复制解向量e.x->e2.x  
2459                     e2.x[j]=e.x[j];  
2460                e2.x[e2.i]=0;                //不选择集装箱i  
2461                bound(e2);                //求右孩子结点的上界  
2462                if (e2.ub>bestw)           //若右孩子结点可行,则进队,否则被剪枝                      qu.push(e2);  
2463           }  
2464      }  
2465 }  
2466 void disparr(int x[],int len)           //输出一个解向量  
2467 { for (int i=1;i<=len;i++)  
2468           printf("%2d",x[i]);  
2469 }  
2470 void dispLoading()                          //输出最优解  
2471 { printf("  X=[");  
2472 
2473 522474 第1章  概论 
2475 
2476      disparr(bestx,n);  
2477      printf("],装入总价值为%d\n",bestw);  
2478 }  
2479 void main()  
2480 { Loading();  
2481      printf("求解结果:\n");  
2482      dispLoading();                         //输出最优解  }  
2483 上述程序的执行结果如图1.40所示。  
2484 
2485 
2486 
2487 
2488 
2489 图1.40  程序执行结果  
2490 1.7  第7章─贪心法  
2491 
2492 1.7.1 练习题  
2493 1. 下面是贪心算法的基本要素的是( )。  
2494 A.重叠子问题  B.构造最优解  C.贪心选择性质      D.定义最优解  
2495 2. 下面问题( )不能使用贪心法解决。  
2496 A.单源最短路径问题 B.n皇后问题  C.最小花费生成树问题  D.背包问题  
2497 3. 采用贪心算法的最优装载问题的主要计算量在于将集装箱依其重量从小到大排 
2498 序,故算法的时间复杂度为( )。  
2499 A.O(n)           B.O(n2)       C.O(n3)       D.O(nlog2n)  
2500 4. 关于0/ 1背包问题以下描述正确的是( )。  
2501 A.可以使用贪心算法找到最优解  
2502 B.能找到多项式时间的有效算法  
2503 C.使用教材介绍的动态规划方法可求解任意0-1背包问题  
2504 D.对于同一背包与相同的物品,做背包问题取得的总价值一定大于等于做0/1背包问 
2505 2506 5. 一棵哈夫曼树共有215个结点,对其进行哈夫曼编码,共能得到( )个不同的码 
2507 字。  
2508 A.107                B.108           C.214           D.215  
2509 6. 求解哈夫曼编码中如何体现贪心思路?  
2510 7. 举反例证明0/1背包问题若使用的算法是按照vi/wi的非递减次序考虑选择的物 
2511 品,即只要正在被考虑的物品装得进就装入背包,则此方法不一定能得到最优解(此题说 明0/1背包问题与背包问题的不同)。  
2512 
2513 532514 
2515 算法设计  
2516 
2517 8. 求解硬币问题。有1分、2分、5分、10分、50分和100分的硬币各若干枚,现 在要用这些硬币来支付W元,最少需要多少枚硬币。  
2518 9. 求解正整数的最大乘积分解问题。将正整数n分解为若干个互不相同的自然数之 和,使这些自然数的乘积最大。  
2519 10. 求解乘船问题。有n个人,第i个人体重为wi(0≤i<n)。每艘船的最大载重量均 为C,且最多只能乘两个人。用最少的船装载所有人。  
2520 11. 求解会议安排问题。有一组会议A和一组会议室B,A[i]表示第i个会议的参加人 数,B[j]表示第j个会议室最多可以容纳的人数。当且仅当A[i]≤B[j]时,第j个会议室可 以用于举办第i个会议。给定数组A和数组B,试问最多可以同时举办多少个会议。例 如,A[]={123},B[]={324},结果为3;若A[]={3431},B[]={1226},结果为2.  
2521 12. 假设要在足够多的会场里安排一批活动,n个活动编号为1~n,每个活动有开始 时间bi和结束时间ei(1≤i≤n)。设计一个有效的贪心算法求出最少的会场个数。  
2522 13. 给定一个m×n的数字矩阵,计算从左到右走过该矩阵且经过的方格中整数最小的 路径。一条路径可以从第1列的任意位置出发,到达第n列的任意位置,每一步为从第i 列走到第i+1列相邻行(水平移动或沿45度斜线移动),如图1.41所示。第1行和最后一 行看作是相邻的,即应当把这个矩阵看成是一个卷起来的圆筒。  
2523 
2524     
2525             
2526             
2527     
2528 
2529 
2530 
2531 图1.41 每一步的走向  
2532 两个略有不同的5×6的数字矩阵的最小路径如图1.42所示,只有最下面一行的数不 
2533 同。右边矩阵的路径利用了第一行与最后一行相邻的性质。  
2534 输入:包含多个矩阵,每个矩阵的第一行为两个数m和n,分别表示矩阵的行数和列 
2535 数,接下来的m×n个整数按行优先的顺序排列,即前n个数组成第一行,接下的n个数 组成第2行,依此类推。相邻整数间用一个或多个空格分隔。注意这些数不一定是正数。 输入中可能有一个或多个矩阵描述,直到输入结束。每个矩阵的行数在1到10之间,列 数在1到100之间。  
2536 输出:对每个矩阵输出两行,第一行为最小整数之和的路径,路径由n个整数组成, 表示路径经过的行号,如果这样的路径不止一条,输出字典序最小一条。   
2537 
2538 
2539 3     4     1     2     8     6     
2540 6     1     8     2     7     4     
2541 5     9     3     9     9     5     
2542 8     4     1     3         2         6     
2543                                 
2544                             
2545                             
2546 
2547 
2548 
2549 
2550 
2551  
2552 
2553 
2554 3     4     1     2     8     6 
2555 6     1     8     2     7     4 
2556 5     9     3     9     9     5 
2557 8     4     1     3     2     6 
2558 3     7     2     1     2     3 
2559 542560 第1章  概论 
2561 
2562 6 1 8 2 7 4  
2563 5 9 3 9 9 5  
2564 8 4 1 3 2 6  
2565 3 7 2 8 6 4  
2566 输出结果:  
2567 1 2 3 4 4 5  
2568 16  
2569 1.7.2 练习题参考答案  
2570 1. 答:C。  
2571 2. 答:n皇后问题的解不满足贪心选择性质。答案为B。  
2572 3. 答:D。  
2573 4. 答:由于背包问题可以取物品的一部分,所以总价值一定大于等于做0/1背包问 
2574 题。答案为D。  
2575 5.     答:这里n=215,哈夫曼树中n1=0,而n0=n2+1,n=n0+n1+n2=2n0-12576 n0=(n+1)/2=108。答案为B。  
2577 6. 答:在构造哈夫曼树时每次都是将两棵根结点最小的树合并,从而体现贪心的思 
2578 路。  
2579 7. 证明:例如,n=3,w={322},v={744},W=4时,由于7/3最大,若按题 目要求的方法,只能取第一个,收益是7。而此实例的最大的收益应该是8,取第2、3  个物品。  
2580 8. 解:用结构体数组A存放硬币数据,A[i].v存放硬币i的面额,A[i].c存放硬币i的 枚数。采用贪心思路,首先将数组A按面额递减排序,再兑换硬币,每次尽可能兑换面额 大的硬币。对应的完整程序如下:  
2581 #include <stdio.h>  
2582 #include <algorithm>  
2583 using namespace std;  
2584 #define min(x,y) ((x)<(y)?(x):(y))  
2585 #define MAX 21  
2586 //问题表示  
2587 int n=7;  
2588 struct NodeType  
2589 { int v;                          //面额  
2590      int c;                          //枚数  
2591      bool operator<(const NodeType &s)  
2592      {                               //用于按面额递减排序  
2593           return s.v<v;  
2594      }  
2595 };  
2596 NodeType A[]={{1,12},{2,8},{5,6},{50,10},{10,8},{200,1},{100,4}};  
2597 int W;  
2598 //求解结果表示  
2599 int ans=0;                          //兑换的硬币枚数  
2600 void solve()                          //兑换硬币  
2601 
2602 552603 
2604 算法设计  
2605 
2606 { sort(A,A+n);                     //按面额递减排序  
2607      for (int i=0;i<n;i++)  
2608      { int t=min(W/A[i].v,A[i].c); //使用硬币i的枚数            if (t!=0)  
2609                printf("  支付%3d面额: %3d枚\n",A[i].v,t);            W-=t*A[i].v;                //剩余的金额  
2610           ans+=t;  
2611           if (W==0) break;  
2612      }  
2613 }  
2614 void main()  
2615 { W=325;                          //支付的金额  
2616      printf("支付%d分:\n",W);  
2617      solve();  
2618      printf("最少硬币的个数:  %d枚\n",ans);  
2619 }  
2620 上述程序的执行结果如图1.43所示。  
2621 
2622 
2623 
2624 
2625 
2626 
2627 
2628 图1.43  程序执行结果  
2629 9. 解:采用贪心方法求解。用a[0..k]存放n的分解结果:  
26301)n≤4时可以验证其分解成几个正整数的和的乘积均小于n,没有解。  
26312)n>4时,把n分拆成若干个互不相等的自然数的和,分解数的个数越多乘积越 
2632 大。为此让n的分解数个数尽可能多(体现贪心的思路),把n分解成从2开始的连续的 自然数之和。例如,分解n为a[0]=2,a[1]=3,a[2]=4,…,a[k]=k+2(共有k+1个分解 
2633 数),用m表示剩下数,这样的分解直到m≤a[k]为止,即m≤k+2。对剩下数m的处理分 为如下两种情况:  
2634 ① m<k+2:将m平均分解到a[k..i](对应的分解数个数为m)中,即从a[k]开始往前 的分解数增加1(也是贪心的思路,分解数越大加1和乘积也越大)。  
2635 ② m=k+2:将a[0..k-1] (对应的分解数个数为k)的每个分解数增加1,剩下的2增 加到a[k]中,即a[k]增加2。  
2636 对应的完整程序如下:  
2637 #include <stdio.h>  
2638 #include <string.h>  
2639 #define MAX 20  
2640 //问题表示  
2641 int n;  
2642 //求解结果表示  
2643 
2644 562645 第1章  概论 
2646 
2647 int a[MAX];                     //存放被分解的数  
2648 int k=0;                          //a[0..k]存放被分解的数  
2649 void solve()                     //求解n的最大乘积分解问题  
2650 { int i;  
2651      int sum=1;  
2652      if (n<4)                     //不存在最优方案,直接返回  
2653           return;  
2654      else  
2655      { int m=n;                //m表示剩下数  
2656           a[0]=2;                //第一个数从2开始  
2657           m-=a[0];                //减去已经分解的数  
2658           k=0;  
2659           while (m>a[k])          //若剩下数大于最后一个分解数,则继续分解  
2660           { k++;                //a数组下标+1  
2661                a[k]=a[k-1]+1;     //按2、3、4递增顺序分解  
2662                m-=a[k];           //减去最新分解的数  
2663           }  
2664           if (m<a[k])           //若剩下数小于a[k],从a[k]开始往前的数+1  
2665           { for (i=0; i<m; i++)  
2666                     a[k-i]+=1;  
2667           }  
2668           if (m==a[k])           //若剩下数等于a[k],则a[k]的值+2,之前的数+1            { a[k]+=2;  
2669                for (i=0; i<k; i++)  
2670                     a[i]+=1;  
2671           }  
2672      }  
2673 }  
2674 void main()  
2675 { n=23;  
2676      memset(a,0,sizeof(a));  
2677      solve();  
2678      printf("%d的最优分解方案\n",n);  
2679      int mul=1;  
2680      printf("  分解的数: ");  
2681      for (int i=0;i<=k;i++)  
2682           if (a[i]!=0)  
2683           { printf("%d ",a[i]);  
2684                mul*=a[i];  
2685           }  
2686      printf("\n  乘积最大值: %d\n",mul);  
2687 }  
2688 上述程序的执行结果如图1.44所示。  
2689 
2690 
2691 
2692 
2693 
2694 
2695 
2696 572697 
2698 算法设计  
2699 
2700 图1.44  程序执行结果  
2701 10. 解:采用贪心思路,首先按体重递增排序;再考虑前后的两个人(最轻者和最重 
2702 者),分别用i、j指向:若w[i]+w[j]≤C,说明这两个人可以同乘(执行i++,j--),否则 w[j]单乘(执行j--),若最后只剩余一个人,该人只能单乘。  
2703 对应的完整程序如下:  
2704 #include <stdio.h>  
2705 #include <algorithm>  
2706 using namespace std;  
2707 #define MAXN 101  
2708 //问题表示  
2709 int n=7;  
2710 int w[]={50,65,58,72,78,53,82};  
2711 int C=150;  
2712 //求解结果表示  
2713 int bests=0;  
2714 void Boat()                //求解乘船问题  
2715 { sort(w,w+n);           //递增排序  
2716     int i=0;  
2717     int j=n - 1;  
2718      while (i<=j)  
2719      { if(i==j)           //剩下最后一个人  
2720           { printf("  一艘船: %d\n",w[i]);  
2721              bests++;  
2722              break;  
2723          }  
2724          if (w[i]+w[j]<=C) //前后两个人同乘  
2725           { printf("  一艘船: %d %d\n",w[i],w[j]);  
2726              bests++;  
2727              i++;  
2728              j--;  
2729          }  
2730           else                //w[j]单乘  
2731           { printf("  一艘船: %d\n",w[j]);  
2732              bests++;  
2733              j--;  
2734          }  
2735     }  
2736 }  
2737 void main()  
2738 { printf("求解结果:\n");  
2739      Boat();  
2740      printf("最少的船数=%d\n",bests);  
2741 }  
2742 上述程序的执行结果如图1.45所示。  
2743 
2744 
2745 
2746 582747 第1章  概论 
2748 
2749 
2750 
2751 
2752 
2753 
2754 
2755 
2756 图1.45  程序执行结果  
2757 11. 解:采用贪心思路。每次都在还未安排的容量最大的会议室安排尽可能多的参会 
2758 人数,即对于每个会议室,都安排当前还未安排的会议中,参会人数最多的会议。若能容 纳下,则选择该会议,否则找参会人数次多的会议来安排,直到找到能容纳下的会议。  
2759 对应的完整程序如下:  
2760 #include <stdio.h>  
2761 #include <algorithm>  
2762 using namespace std;  
2763 //问题表示  
2764 int n=4;                     //会议个数  
2765 int m=4;                     //会议室个数  
2766 int A[]={3,4,3,1};  
2767 int B[]={1,2,2,6};  
2768 //求解结果表示  
2769 int ans=0;  
2770 void solve()                //求解算法  
2771 { sort(A,A+n);           //递增排序  
2772      sort(B,B+m);           //递增排序  
2773      int i=n-1,j=m-1;      //从最多人数会议和最多容纳人数会议室开始  
2774      for(i;i>=0;i--)  
2775      { if(A[i]<=B[j] && j>=0)  
2776           { ans++;      //不满足条件,增加一个会议室  
2777                j--;  
2778           }  
2779      }  
2780 }  
2781 void main()  
2782 { solve();  
2783      printf("%d\n",ans);     //输出2  
2784 }  
2785 12. 解:与《教程》例7.2类似,会场对应蓄栏,只是这里仅仅求会场个数,即最大 兼容活动子集的个数。对应的完整程序如下:  
2786 #include <stdio.h>  
2787 #include <string.h>  
2788 #include <algorithm>  
2789 using namespace std;  
2790 #define MAX 51  
2791 //问题表示  
2792 struct Action                               //活动的类型声明  
2793 
2794 592795 
2796 算法设计  
2797 
2798 { int b;                               //活动起始时间  
2799      int e;                               //活动结束时间  
2800      bool operator<(const Action &s) const //重载<关系函数  
2801      { if (e==s.e)                     //结束时间相同按开始时间递增排序  
2802                return b<=s.b;  
2803           else                               //否则按结束时间递增排序  
2804                return e<=s.e;  
2805      }  
2806 };  
2807 int n=5;  
2808 Action A[]={{0},{1,10},{2,4},{3,6},{5,8},{4,7}}; //下标0不用  
2809 //求解结果表示  
2810 int ans;                                    //最少会场个数  
2811 void solve()                               //求解最大兼容活动子集  
2812 { bool flag[MAX];                     //活动标志  
2813      memset(flag,0,sizeof(flag));  
2814      sort(A+1,A+n+1);                     //A[1..n]按指定方式排序  
2815      ans=0;                               //会场个数  
2816      for (int j=1;j<=n;j++)  
2817      { if (!flag[j])  
2818           { flag[j]=true;  
2819                int preend=j;                //前一个兼容活动的下标  
2820                for (int i=preend+1;i<=n;i++)  
2821                { if (A[i].b>=A[preend].e && !flag[i])  
2822                     { preend=i;  
2823                          flag[i]=true;  
2824                     }  
2825                }  
2826                ans++;                     //增加一个最大兼容活动子集  
2827           }  
2828      }  
2829 }  
2830 void main()  
2831 { solve();  
2832      printf("求解结果\n");  
2833      printf("    最少会场个数: %d\n",ans); //输出4  
2834 }  
2835 13. 解:采用贪心思路。从第1列开始每次查找a[i][j]元素上、中、下3个对应数中 的最小数。对应的程序如下:  
2836 #include <stdio.h>  
2837 #define M 12  
2838 #define N 110  
2839 int m=5, n=6;  
2840 int a[M][N]={{3,4,1,2,8,6},{6,1,8,2,7,4},{5,9,3,9,9,5},{8,4,1,3,2,6},{3,7,2,8,6,4}};  
2841 int minRow,minCol;  
2842 int minValue(int i, int j)  
2843           //求a[i][j]有方上、中、下3个数的最小数,同时要把行标记录下来  
2844 { int s = (i == 0) ? m - 1 : i - 1;  
2845      int x = (i == m - 1) ? 0 : i + 1;  
2846 
2847 602848 第1章  概论 
2849 
2850      minRow = s;  
2851      minRow = a[i][j+1] < a[minRow][j+1] ? i : minRow;  
2852      minRow = a[x][j+1] < a[minRow][j+1] ? x : minRow;  
2853      minRow = a[minRow][j+1] == a[s][j+1] && minRow > s ? s : minRow;  
2854      minRow = a[minRow][j+1] == a[i][j+1] && minRow > i ? i : minRow;  
2855      minRow = a[minRow][j+1] == a[x][j+1] && minRow > x ? x : minRow;  
2856      return a[minRow][j+1];  
2857 }  
2858 void solve()  
2859 { int i,j,min;  
2860      for (j=n-2; j>=0; j--)  
2861           for (i=0; i<m; i++)  
2862                a[i][j]+= minValue(i,j);  
2863      min=a[0][0];  
2864      minRow=0;  
2865      for (i=1; i<m; i++)          //在第一列查找最小代价的行  
2866           if (a[i][0]<min)  
2867           { min=a[i][0];  
2868                minRow=i;  
2869           }  
2870      for (j=0; j<n; j++)  
2871      { printf("%d",minRow+1);  
2872           if (j<n-1) printf(" ");  
2873           minValue(minRow, j);  
2874      }  
2875      printf("\n%d\n",min);   
2876 }  
2877 void main()  
2878 {  
2879      solve();  
2880 }  
2881 1.8 第8章─动态规划  
2882 1.8.1 练习题  
2883 1. 下列算法中通常以自底向上的方式求解最优解的是( )。  
2884 A.备忘录法       B.动态规划法       C.贪心法       D.回溯法  
2885 2. 备忘录方法是( )算法的变形。   
2886 A.分治法            B.回溯法            C.贪心法       D.动态规划法  
2887 3. 下列是动态规划算法基本要素的是( )。  
2888 A.定义最优解       B.构造最优解       C.算出最优解  D.子问题重叠性质  
2889 4. 一个问题可用动态规划算法或贪心算法求解的关键特征是问题的( )。  
2890 A.贪心选择性质  B.重叠子问题       C.最优子结构性质  D.定义最优解  
2891 5. 简述动态规划法的基本思路。  
2892 6. 简述动态规划法与贪心法的异同。  
2893 
2894 612895 
2896 算法设计  
2897 
2898 7. 简述动态规划法与分治法的异同。  
2899 8. 下列算法中哪些属于动态规划算法?  
29001)顺序查找算法  
29012)直接插入排序算法  
29023)简单选择排序算法  
29034)二路归并排序算法  
2904 9. 某个问题对应的递归模型如下:  
2905 f(1)=1  
2906 f(2)=2  
2907 f(n)=f(n-1)+f(n-2)+…+f(1)+1      当n>2时  
2908 可以采用如下递归算法求解:  
2909 long f(int n)  
2910 { if (n==1) return 1;  
2911      if (n==2) return 2;  
2912      long sum=1;  
2913      for (int i=1;i<=n-1;i++)  
2914           sum+=f(i);  
2915      return sum;  
2916 }  
2917 但其中存在大量的重复计算,请采用备忘录方法求解。  
2918 10. 第3章中的实验4采用分治法求解半数集问题,如果直接递归求解会存在大量重 
2919 复计算,请改进该算法。  
2920 11. 设计一个时间复杂度为O(n2)的算法来计算二项式系数Cnk(k≤n)。二项式系数 
2921 Cnk的求值过程如下:  
2922 ??0??=1                  
2923 ????       
2924 ?? 
2925 ???1 
2926 12. 一个机器人只能向下和向右移动,每次只能移动一步,设计一个算法求它从 
292700)移动到(m,n)有多少条路径。  
2928 13. 两种水果杂交出一种新水果,现在给新水果取名,要求这个名字中包含了以前两 
2929 种水果名字的字母,并且这个名字要尽量短。也就是说以前的一种水果名字arr1是新水果 名字arr的子序列,另一种水果名字arr2也是新水果名字arr的子序列。设计一个算法求 
2930 arr。  
2931 例如:输入以下3组水果名称:  
2932 apple peach  
2933 ananas banana  
2934 pear peach  
2935 输出的新水果名称如下:  
2936 
2937 622938 第1章  概论 
2939 
2940 appleach  
2941 bananas  
2942 pearch  
2943 1.8.2 练习题参考答案  
2944 1. 答:B。  
2945 2. 答:D。  
2946 3. 答:D。  
2947 4. 答:C。  
2948 5. 答:动态规划法的基本思路是将待求解问题分解成若干个子问题,先求子问题的 
2949 解,然后从这些子问题的解得到原问题的解。  
2950 6. 答:动态规划法的3个基本要素是最优子结构性质、无后效性和重叠子问题性 
2951 质,而贪心法的两个基本要素是贪心选择性质和最优子结构性质。所以两者的共同点是都 要求问题具有最优子结构性质。  
2952 两者的不同点如下:  
29531)求解方式不同,动态规划法是自底向上的,有些具有最优子结构性质的问题只 
2954 能用动态规划法,有些可用贪心法。而贪心法是自顶向下的。  
29552)对子问题的依赖不同,动态规划法依赖于各子问题的解,所以应使各子问题最 
2956 优,才能保证整体最优;而贪心法依赖于过去所作过的选择,但决不依赖于将来的选择, 也不依赖于子问题的解。  
2957 7. 答:两者的共同点是将待求解的问题分解成若干子问题,先求解子问题,然后再 从这些子问题的解得到原问题的解。   
2958 两者的不同点是:适合于用动态规划法求解的问题,分解得到的各子问题往往不是相 互独立的(重叠子问题性质),而分治法中子问题相互独立;另外动态规划法用表保存已 求解过的子问题的解,再次碰到同样的子问题时不必重新求解,而只需查询答案,故可获 得多项式级时间复杂度,效率较高,而分治法中对于每次出现的子问题均求解,导致同样 的子问题被反复求解,故产生指数增长的时间复杂度,效率较低。  
2959 8. 答:判断算法是否具有最优子结构性质、无后效性和重叠子问题性质。(2)、(3) 和(4)均属于动态规划算法。  
2960 9. 解:设计一个dp数组,dp[i]对应f(i)的值,首先dp的所有元素初始化为0,在计 算f(i)时,若dp[0]>0表示f(i)已经求出,直接返回dp[i]即可,这样避免了重复计算。对应 的算法如下:  
2961 long dp[MAX];      //dp[n]保存f(n)的计算结果  
2962 long f1(int n)  
2963 { if (n==1)  
2964      { dp[n]=1;  
2965           return dp[n];  
2966      }  
2967      if (n==2)  
2968      { dp[n]=2;  
2969           return dp[n];  
2970 632971 
2972 算法设计  
2973 
2974      }  
2975      if (dp[n]>0) return dp[n];  
2976      long sum=1;  
2977      for (int i=1;i<=n-1;i++)  
2978           sum+=f1(i);  
2979      dp[n]=sum;  
2980      return dp[n];  
2981 }  
2982 10. 解:设计一个数组a,其中a[i]=f(i),首先将a的所有元素初始化为0,当a[i]>0 时表示对应的f(i)已经求出,直接返回就可以了。对应的完整程序如下:  
2983 #include <stdio.h>  
2984 #include <string.h>  
2985 #define MAXN 201  
2986 //问题表示  
2987 int n;  
2988 int a[MAXN];  
2989 int fa(int i)                //求a[i]  
2990 { int ans=1;  
2991      if (a[i]>0)  
2992           return a[i];  
2993      for(int j=1;j<=i/2;j++)  
2994           ans+=fa(j);  
2995      a[i]=ans;  
2996      return ans;  
2997 }  
2998 int solve(int n)           //求set(n)的元素个数  
2999 { memset(a,0,sizeof(a));  
3000      a[1]=1;  
3001      return fa(n);  
3002 }  
3003 void main()  
3004 { n=6;  
3005      printf("求解结果\n");  
3006      printf("  n=%d时半数集元素个数=%d\n",n,solve(n));  
3007 }  
3008 11. 解:定义C(i,j)=Cij,i≥j。则有如下递推计算公式:C(i,j)=C(i-1,j-1)+C(i- 1,j),初始条件为C(i,0)=1,C(i,i)=1。可以根据初始条件由此递推关系计算C(n,k), 即Cnk。对应的程序如下:  
3009 #include <stdio.h>  
3010 #define MAXN 51  
3011 #define MAXK 31  
3012 //问题表示  
3013 int n,k;  
3014 //求解结果表示  
3015 int C[MAXN][MAXK];  
3016 void solve()  
3017 { int i,j;  
3018 
3019 643020 第1章  概论 
3021 
3022      for (i=0;i<=n;i++)  
3023      { C[i][i]=1;  
3024           C[i][0]=1;  
3025      }  
3026      for (i=1;i<=n;i++)  
3027           for (j=1;j<=k;j++)  
3028                C[i][j]=C[i-1][j-1]+C[i-1][j];  
3029 }  
3030 void main()  
3031 { n=5,k=3;  
3032      solve();  
3033      printf("%d\n",C[n][k]); //输出10  
3034 }  
3035 显然,solve()算法的时间复杂度为O(n2)。  
3036 12. 解:设从(00)移动到(i,j)的路径条数为dp[i][j],由于机器人只能向下和 
3037 向右移动,不同于迷宫问题(迷宫问题由于存在后退,不满足无后效性,不适合用动态规 划法求解)。对应的状态转移方程如下:  
3038 dp[0][j]=1  
3039 dp[i][0]=1  
3040 dp[i][j]=dp[i][j-1]+dp[i-1][j]          i、j>0  
3041 最后结果是dp[m][n]。对应的程序如下:  
3042 #include <stdio.h>  
3043 #include <string.h>  
3044 #define MAXX 51  
3045 #define MAXY 51  
3046 //问题表示  
3047 int m,n;  
3048 //求解结果表示  
3049 int dp[MAXX][MAXY];  
3050 void solve()  
3051 { int i,j;  
3052      dp[0][0]=0;  
3053      memset(dp,0,sizeof(dp));  
3054      for (i=1;i<=m;i++)  
3055           dp[i][0]=1;  
3056      for (j=1;j<=n;j++)  
3057           dp[0][j]=1;  
3058      for (i=1;i<=m;i++)  
3059           for (j=1;j<=n;j++)  
3060                dp[i][j]=dp[i][j-1]+dp[i-1][j];  
3061 }  
3062 void main()  
3063 { m=5,n=3;  
3064      solve();  
3065      printf("%d\n",dp[m][n]);  
3066 }  
3067 13. 解:本题目的思路是求arr1和arr2字符串的最长公共子序列,基本过程参见《教 
3068 653069 
3070 算法设计  
3071 
3072 程》第8章8.5节。对应的完整程序如下:  
3073 #include <iostream>  
3074 #include <string.h>  
3075 #include <vector>  
3076 #include <string>  
3077 using namespace std;  
3078 #define max(x,y) ((x)>(y)?(x):(y))  
3079 #define MAX 51                              //序列中最多的字符个数  
3080 //问题表示  
3081 int m,n;  
3082 string arr1,arr2;  
3083 //求解结果表示  
3084 int dp[MAX][MAX];                          //动态规划数组  
3085 vector<char> subs;                          //存放LCS  
3086 void LCSlength()                          //求dp  
3087 { int i,j;  
3088      for (i=0;i<=m;i++)                     //将dp[i][0]置为0,边界条件  
3089           dp[i][0]=0;  
3090      for (j=0;j<=n;j++)                     //将dp[0][j]置为0,边界条件  
3091           dp[0][j]=0;  
3092      for (i=1;i<=m;i++)  
3093           for (j=1;j<=n;j++)                //两重for循环处理arr1、arr2的所有字符  
3094           { if (arr1[i-1]==arr2[j-1]) //比较的字符相同  
3095                     dp[i][j]=dp[i-1][j-1]+1;  
3096                else                          //比较的字符不同  
3097                     dp[i][j]=max(dp[i][j-1],dp[i-1][j]);  
3098           }  
3099 }  
3100 void Buildsubs()                          //由dp构造从subs  
3101 { int k=dp[m][n];                     //k为arr1和arr2的最长公共子序列长度  
3102      int i=m;  
3103      int j=n;  
3104      while (k>0)                          //在subs中放入最长公共子序列(反向)  
3105           if (dp[i][j]==dp[i-1][j]) i--;  
3106           else if (dp[i][j]==dp[i][j-1]) j--;  
3107           else  
3108           { subs.push_back(arr1[i-1]); //subs中添加arr1[i-1]  
3109                i--; j--; k--;  
3110           }  
3111 }  
3112 void main()  
3113 { cin >> arr1 >> arr2;                //输入arr1和arr2  
3114      m=arr1.length();                     //m为arr1的长度  
3115      n=arr2.length();                     //n为arr2的长度  
3116      LCSlength();                          //求出dp  
3117      Buildsubs();                          //求出LCS  
3118      cout << "求解结果" << endl;  
3119      cout << "    arr: ";  
3120      vector<char>::reverse_iterator rit;  
3121 
3122 663123 第1章  概论 
3124 
3125      for (rit=subs.rbegin();rit!=subs.rend();++rit)  
3126           cout << *rit;  
3127      cout << endl;  
3128      cout << "    长度: " << dp[m][n] << endl;  
3129 }  
3130 改为如下:  
3131 13. 解:本题目的思路是先求arr1和arr2字符串的最长公共子序列,基本过程参见 
3132 《教程》第8章8.5节,再利用递归输出新水果取名。  
3133 算法中设置二维动态规划数组dp,dp[i][j]表示arr1[0..i-1](i个字母)和arr2[0..j-1] 
3134 (j个字母)中最长公共子序列的长度。另外设置二维数组b,b[i][j]表示arr1和arr2比较 的3种情况:b[i][j]=0表示arr1[i-1]=arr2[j-1],b[i][j]=1表示arr1[i-1]≠arr2[j-1]并且dp[i- 
3135 1][j]>dp[i][j-1],b[i][j]=2表示arr1[i-1]≠arr2[j-1]并且dp[i-1][j]≤dp[i][j-1]。  
3136 对应的完整程序如下:  
3137 #include <stdio.h>  
3138 #include <string.h>  
3139 #define MAX 51                                   //序列中最多的字符个数  
3140 //问题表示  
3141 int m,n;  
3142 char arr1[MAX],arr2[MAX];  
3143 //求解结果表示  
3144 int dp[MAX][MAX];                               //动态规划数组  
3145 int b[MAX][MAX];                               //存放arr1与arr2比较的3种情况  
3146 void Output(int i,int j)                     //利用递归输出新水果取名  
3147 { if (i==0 && j==0)                          //输出完毕  
3148          return;  
3149      if(i==0)                                    //arr1完毕,输出arr2的剩余部分  
3150      { Output(i,j-1);  
3151          printf("%c",arr2[j-1]);  
3152           return;  
3153      }  
3154      else if(j==0)                               //arr2完毕,输出arr1的剩余部分  
3155      { Output(i-1,j);  
3156          printf("%c",arr1[i-1]);  
3157           return;  
3158      }  
3159      if (b[i][j]==0)                          //arr1[i-1]=arr2[j-1]的情况  
3160      { Output(i-1,j-1);  
3161          printf("%c",arr1[i-1]);  
3162           return;  
3163      }  
3164      else if(b[i][j]==1)  
3165      { Output(i-1,j);  
3166          printf("%c",arr1[i-1]);  
3167           return;  
3168      }  
3169      else  
3170      { Output(i,j-1);  
3171 
3172 673173 
3174 算法设计  
3175 
3176          printf("%c",arr2[j-1]);  
3177           return;  
3178      }  
3179 }  
3180 void LCSlength()                               //求dp  
3181 { int i,j;  
3182      for (i=0;i<=m;i++)                          //将dp[i][0]置为0,边界条件  
3183           dp[i][0]=0;  
3184      for (j=0;j<=n;j++)                          //将dp[0][j]置为0,边界条件  
3185           dp[0][j]=0;  
3186      for (i=1;i<=m;i++)  
3187           for (j=1;j<=n;j++)                     //两重for循环处理arr1、arr2的所有字符            { if (arr1[i-1]==arr2[j-1])      //比较的字符相同:情况0  
3188                { dp[i][j]=dp[i-1][j-1]+1;  
3189                     b[i][j]=0;  
3190                }  
3191                else if (dp[i-1][j]>dp[i][j-1]) //情况1  
3192                { dp[i][j]=dp[i-1][j];  
3193                     b[i][j]=1;  
3194                }  
3195                else                               //dp[i-1][j]<=dp[i][j-1]:情况2  
3196                { dp[i][j]=dp[i][j-1];  
3197                     b[i][j]=2;  
3198                }  
3199           }  
3200 }  
3201 void main()  
3202 { int t;                                    //输入测试用例个数  
3203      printf("测试用例个数: ");  
3204      scanf("%d",&t);  
3205      while(t--)  
3206      { scanf("%s",arr1);  
3207           scanf("%s",arr2);  
3208          memset(b,-1,sizeof(b));  
3209           m=strlen(arr1);                     //m为arr1的长度  
3210           n=strlen(arr2);                     //n为arr2的长度  
3211           LCSlength();                          //求出dp  
3212           printf("结果: "); Output(m,n);      //输出新水果取名  
3213           printf("\n");  
3214     }  
3215 }  
3216 上述程序的一次执行结果如图1.46所示。  
3217 
3218 
3219 
3220 
3221 
3222 
3223 
3224 683225 第1章  概论 
3226 
3227 
3228 
3229 
3230 
3231 
3232 
3233 
3234 
3235 图1.46  程序的一次执行结果  
3236 13. 解:本题目的思路是求arr1和arr2字符串的最长公共子序列,基本过程参见《教 
3237 程》第8章8.5节。对应的完整程序如下:  
3238 
3239 然后再用递归思想,逐一输出,得到的就是最后答案。  
3240 #include <iostream>  
3241 #include <string.h>  
3242 #include <vector>  
3243 #include <string>  
3244 using namespace std;  
3245 #define max(x,y) ((x)>(y)?(x):(y))  
3246 #define MAX 51                              //序列中最多的字符个数  
3247 //问题表示  
3248 int m,n;  
3249 string arr1,arr2;  
3250 //求解结果表示  
3251 int dp[MAX][MAX];                          //动态规划数组  
3252 vector<char> subs;                          //存放LCS  
3253 void LCSlength()                          //求dp  
3254 { int i,j;  
3255      for (i=0;i<=m;i++)                     //将dp[i][0]置为0,边界条件  
3256           dp[i][0]=0;  
3257      for (j=0;j<=n;j++)                     //将dp[0][j]置为0,边界条件  
3258           dp[0][j]=0;  
3259      for (i=1;i<=m;i++)  
3260           for (j=1;j<=n;j++)                //两重for循环处理arr1、arr2的所有字符            { if (arr1[i-1]==arr2[j-1]) //比较的字符相同  
3261                     dp[i][j]=dp[i-1][j-1]+1;  
3262                else                          //比较的字符不同  
3263                     dp[i][j]=max(dp[i][j-1],dp[i-1][j]);  
3264           }  
3265 }  
3266 void Buildsubs()                          //由dp构造从subs  
3267 { int k=dp[m][n];                     //k为arr1和arr2的最长公共子序列长度  
3268      int i=m;  
3269      int j=n;  
3270      while (k>0)                          //在subs中放入最长公共子序列(反向)  
3271           if (dp[i][j]==dp[i-1][j]) i--;  
3272           else if (dp[i][j]==dp[i][j-1]) j--;  
3273 
3274 693275 
3276 算法设计  
3277 
3278           else  
3279           { subs.push_back(arr1[i-1]); //subs中添加arr1[i-1]                 i--; j--; k--;  
3280           }  
3281 }  
3282 void main()  
3283 { cin >> arr1 >> arr2;                //输入arr1和arr2  
3284      m=arr1.length();                     //m为arr1的长度  
3285      n=arr2.length();                     //n为arr2的长度  
3286      LCSlength();                          //求出dp  
3287      Buildsubs();                          //求出LCS  
3288      cout << "求解结果" << endl;  
3289      cout << "    arr: ";  
3290      vector<char>::reverse_iterator rit;  
3291      for (rit=subs.rbegin();rit!=subs.rend();++rit)  
3292           cout << *rit;  
3293      cout << endl;  
3294      cout << "    长度: " << dp[m][n] << endl;  
3295 }  
3296 上述程序的一次执行结果如图1.46所示。  
3297 
3298 
3299 
3300 
3301 
3302 
3303 图1.46  程序的一次执行结果  
3304 1.9 第9章─图算法设计  
3305 
3306 1.9.1 练习题  
3307 1. 以下不属于贪心算法的是( )。  
3308 A.Prim算法       B.Kruskal算法      C.Dijkstra算法      D.深度优先遍历  
3309 2. 一个有n个顶点的连通图的生成树是原图的最小连通子图,且包含原图中所有n 
3310 个顶点,并且有保持图联通的最少的边。最大生成树就是权和最大生成树,现在给出一个 无向带权图的邻接矩阵为{{04503},{40423},{54020},{02201},{33010}},其中权为0表示没有边。一个图为求这个图的最大生 
3311 成树的权和是( )。  
3312 A.11       B.12       C.13       D.14       E.15  
3313 3. 某个带权连通图有4个以上的顶点,其中恰好有2条权值最小的边,尽管该图的 最小生成树可能有多个,而这2条权值最小的边一定包含在所有的最小生成树中吗?如果 有3条权值最小的边呢?  
3314 
3315 703316 第1章  概论 
3317 
3318 4. 为什么TSP问题采用贪心算法求解不一定得到最优解?  
3319 5. 求最短路径的4种算法适合带权无向图吗?  
3320 6. 求单源最短路径的算法有Dijkstra算法、Bellman-Ford算法和SPFA算法,比较这 
3321 些算法的不同点。  
3322 7. 有人这样修改Dijkstra算法以便求一个带权连通图的单源最长路径,将每次选择 
3323 dist最小的顶点u改为选择最大的顶点u,将按路径长度小进行调整改为按路径长度大调 整。这样可以求单源最长路径吗?  
3324 8. 给出一种方法求无环带权连通图(所有权值非负)中从顶点s到顶点t的一条最长 简单路径。  
3325 9. 一个运输网络如图1.47所示,边上数字为(c(i,j),b(i,j)),其中c(i,j)表示容 量,b(i,j)表示单位运输费用。给出从1、2、3位置运输货物到位置6的最小费用最大流 的过程。  
3326 10. 本教程中的Dijkstra算法采用邻接矩阵存储图,算法时间复杂度为O(n2)。请你从 各个方面考虑优化该算法,用于求源点v到其他顶点的最短路径长度。  
3327 11. 有一个带权有向图G(所有权为正整数),采用邻接矩阵存储。设计一个算法求 其中的一个最小环。   
3328 1 
3329 (6,4) 
3330 2 
3331  
3332 (4,2) 
3333 
3334 (3,5) 
3335  
3336 
3337 
3338 4 
3339  
3340 
3341 
3342 (12,3) 
3343  
3344 
3345 
3346 
3347 6  
3348 
3349 
3350 3 
3351  
3352 (3,4) 
3353  
3354 
3355 (1,6) 
3356 (2,3) 
3357  
3358 5 
3359  
3360 (9,12)  
3361 图1.47  一个运输网络  
3362 1.9.2 练习题参考答案  
3363 1. 答:D。  
3364 2. 答:采用类似Kurskal算法来求最大生成树,第1步取最大边(02),第2步取 
3365 边(01),第3步取边(04),第4步取最大边(13),得到的权和为14。答案为 
3366 D。  
3367 3. 答:这2条权值最小的边一定包含在所有的最小生成树中,因为按Kurskal算法一 定首先选中这2条权值最小的边。如果有3条权值最小的边,就不一定了,因为首先选中 这3条权值最小的边有可能出现回路。  
3368 4. 答:TSP问题不满足最优子结构性质,如(01230)是整个问题的最优 解,但(0120)不一定是子问题的最优解。  
3369 5. 答:都适合带权无向图求最短路径。  
3370 6.     答:Dijkstra算法不适合存在负权边的图求单源最短路径,其时间复杂度为 
3371 O(n2)。Bellman-Ford算法和SPFA算法适合存在负权边的图求单源最短路径,但图中不能 
3372 
3373 713374 
3375 算法设计  
3376 
3377 存在权值和为负的环。Bellman-Ford算法的时间复杂度为O(ne),而SPFA算法的时间复 杂度为O(e),所以SPFA算法更优。  
3378 7. 答:不能。Dijkstra算法本质上是一种贪心算法,而求单源最长路径不满足贪心选 择性质。  
3379 8. 答:Bellman-Ford算法和SPFA算法适合存在负权边的图求单源最短路径。可以 将图中所有边权值改为负权值,求出从顶点s到顶点t的一条最短简单路径,它就是原来 图中从顶点s到顶点t的一条最长简单路径。  
3380 9. 答:为该运输网络添加一个虚拟起点0,它到1、2、3位置运输费用为0,容量分 别为到1、2、3位置运输容量和,如图1.48所示,起点s=0,终点t=63381 
3382 
3383 
3384 0 
3385  
3386 
3387 (10,0) 
3388 (6,0) 
3389  
3390 1 
3391 (6,4) 
3392 2 
3393  
3394 (4,2) 
3395 
3396 (3,5) 
3397  
3398 
3399 
3400 4 
3401  
3402 
3403 
3404 (12,3) 
3405  
3406 
3407 
3408 
3409 6  
3410 
3411 (3,0) 
3412  
3413 
3414 
3415 3 
3416  
3417 (3,4) 
3418  
3419 
3420 (1,6) 
3421 (2,3) 
3422  
3423 5 
3424  
3425 (9,12)  
3426 图1.48  添加一个虚拟起点的运输网络  
3427 首先初始化f为零流,最大流量maxf=0,最小费用mincost=0,采用最小费用最大流 
3428 算法求解过程如下:  
34291)k=0,求出w如下:  
3430 
3431 0      0      0      0      ∞      ∞      ∞  
34320      ∞      ∞      2      43433 ∞      ∞      05      43434 ∞      ∞      ∞      0      6      33435 ∞      ∞      ∞      ∞      03  
3436 ∞      ∞      ∞      ∞      ∞      0      12  
3437 ∞      ∞      ∞      ∞      ∞      ∞      0  
3438 
3439 
3440 
3441 
3442 
3443 
3444 求出从起点0到终点6的最短路径为0→146,求出最小调整量?=4,f[4][6]调整 为4,f[1][4]调整为4,f[0][1]调整为4,mincost=20,maxf=434452)k=1,求出w如下:  
3446 
3447 0      0      0      0      ∞      ∞      ∞  
3448 0      0      ∞      ∞      ∞      43449 ∞      ∞      05      43450 ∞      ∞      ∞      0      6      33451 ∞      -2      ∞      ∞      03  
3452 ∞      ∞      ∞      ∞      ∞      0      12  
3453 ∞      ∞      ∞      ∞      -30  
3454 
3455 
3456 
3457 
3458 
3459 
3460 求出从起点0到终点6的最短路径为0→246,求出最小调整量?=3,f[4][6]调整 
3461 
3462 723463 第1章  概论 
3464 
3465 为7,f[2][4]调整为3,f[0][2]调整为3,mincost=44,maxf=4+3=734663)k=2,求出w如下:  
3467 
3468 0      0      0      0      ∞      ∞      ∞  
3469 0      0      ∞      ∞      ∞      43470 00      ∞      ∞      43471 ∞      ∞      ∞      0      6      33472 ∞      -2      -503  
3473 ∞      ∞      ∞      ∞      ∞      0      12  
3474 ∞      ∞      ∞      ∞      -30  
3475 
3476 
3477 
3478 
3479 
3480 
3481 求出从起点0到终点6的最短路径为0→346,求出最小调整量?=1,f[4][6]调整 为8,f[3][4]调整为1,f[0][3]调整为1,mincost=53,maxf=7+1=834824)k=3,求出w如下:  
3483 
3484 0      0      0      0      ∞      ∞      ∞  
3485 0      0      ∞      ∞      ∞      43486 00      ∞      ∞      43487 0      ∞      ∞      033488 ∞      -2      -5      -6      03  
3489 ∞      ∞      ∞      ∞      ∞      0      12  
3490 ∞      ∞      ∞      ∞      -30  
3491 
3492 
3493 
3494 
3495 
3496 
3497 求出从起点0到终点6的最短路径为0→356,求出最小调整量?=2,f[5][6]调整 为2,f[3][5]调整为2,f[0][3]调整为3,mincost=83,maxf=8+2=1034985)k=4,求出w如下:  
3499 
3500 0      0      0      ∞      ∞      ∞      ∞  
3501 0      0      ∞      ∞      ∞      43502 00      ∞      ∞      43503 0      ∞      ∞      0      ∞      ∞      ∞  
3504 ∞      -2      -5      -6      03  
3505 ∞      ∞      ∞      -30      12  
3506 ∞      ∞      ∞      ∞      -3      -12      0  
3507 
3508 
3509 
3510 
3511 
3512 
3513 求出从起点0到终点6的最短路径为0→156,求出最小调整量?=6,f[5][6]调整 为8,f[1][5]调整为6,f[0][1]调整为10,mincost=179,maxf=10+6=1635146)k=5,求出w如下:  
3515 
3516 00      ∞      ∞      ∞      ∞  
3517 0      0      ∞      ∞      ∞      ∞      ∞  
3518 00      ∞      ∞      43519 0      ∞      ∞      0      ∞      ∞      ∞  
3520 ∞      -2      -5      -6      03  
3521 ∞      -4      ∞      -30      12  
3522 ∞      ∞      ∞      ∞      -3      -12      0  
3523 
3524 
3525 
3526 
3527 
3528 
3529 求出从起点0到终点6的最短路径为0→156,求出最小调整量?=1,f[5][6]调整 
3530 
3531 733532 
3533 算法设计  
3534 
3535 为9,f[2][5]调整为1,f[0][2]调整为4,mincost=195,maxf=16+1=1735367)k=6,求出的w中没有增广路径,调整结束。对应的最大流如下:  
3537 
3538 0      10      4      3      0      0      0  
3539 0      0      0      0      4      6      0  
3540 0      0      0      0      3      1      0  
3541 0      0      0      0      1      2      0  
3542 0      0      0      0      0      0      8  
3543 0      0      0      0      0      0      9  
3544 0      0      0      0      0      0      0  
3545 
3546 
3547 
3548 
3549 
3550 
3551 
3552 最终结果,maxf=17,mincost=195。即运输的最大货物量为17,对应的最小总运输费 用为195。  
3553 10. 解:从两个方面考虑优化:  
35541)在Dijkstra算法中,当求出源点v到顶点u的最短路径长度后,仅仅调整从顶 
3555 点u出发的邻接点的最短路径长度,而教程中的Dijkstra算法由于采用邻接矩阵存储图, 需要花费O(n)的时间来调整顶点u出发的邻接点的最短路径长度,如果采用邻接表存储 
3556 图,可以很快查找到顶点u的所有邻接点并进行调整,时间为O(MAX(图中顶点的出 
3557 度))。  
35582)求目前一个最短路径长度的顶点u时,教科书上的Dijkstra算法采用简单比较 
3559 方法,可以改为采用优先队列(小根堆)求解。由于最多e条边对应的顶点进队,对应的 
3560 时间为O(log2e)。  
3561 对应的完整程序和测试数据算法如下:  
3562 #include "Graph.cpp"                //包含图的基本运算算法  
3563 #include <queue>  
3564 #include <string.h>  
3565 using namespace std;  
3566 ALGraph *G;                          //图的邻接表存储结构,作为全局变量  
3567 struct Node                          //声明堆中结点类型  
3568 { int i;                          //顶点编号  
3569      int v;                          //dist[i]值  
3570      friend bool operator<(const Node &a,const Node &b) //定义比较运算符  
3571      {   return  a.v > b.v; }  
3572 };  
3573 void Dijkstra(int v,int dist[])      //改进的Dijkstra算法  
3574 { ArcNode *p;  
3575      priority_queue<Node> qu;      //创建小根堆  
3576      Node e;  
3577      int S[MAXV];                     //S[i]=1表示顶点i在S中, S[i]=0表示顶点i在U中  
3578      int i,j,u,w;  
3579      memset(S,0sizeof(S));  
3580      p=G->adjlist[v].firstarc;  
3581      for (i=0;i<G->n;i++) dist[i]=INF;  
3582      while (p!=NULL)  
3583      { w=p->adjvex;  
3584 
3585 743586 第1章  概论 
3587 
3588           dist[w]=p->weight;           //距离初始化  
3589           e.i=w; e.v=dist[w];          //将v的出边顶点进队qu  
3590           qu.push(e);  
3591           p=p->nextarc;  
3592      }  
3593      S[v]=1;                          //源点编号v放入S中  
3594      for (i=0;i<G->n-1;i++)           //循环直到所有顶点的最短路径都求出  
3595      { e=qu.top(); qu.pop();      //出队e  
3596           u=e.i;                     //选取具有最小最短路径长度的顶点u  
3597           S[u]=1;                     //顶点u加入S中  
3598           p=G->adjlist[u].firstarc;  
3599           while (p!=NULL)           //考察从顶点u出发的所有相邻点  
3600           { w=p->adjvex;  
3601                if (S[w]==0)           //考虑修改不在S中的顶点w的最短路径长度                      if (dist[u]+p->weight<dist[w])  
3602                     { dist[w]=dist[u]+p->weight; //修改最短路径长度  
3603                          e.i=w; e.v=dist[w];  
3604                          qu.push(e); //修改最短路径长度的顶点进队  
3605                     }  
3606                p=p->nextarc;  
3607           }  
3608      }  
3609 }  
3610 void Disppathlength(int v,int dist[]) //输出最短路径长度  
3611 { printf("从%d顶点出发的最短路径长度如下:\n",v);  
3612      for (int i=0;i<G->n;++i)  
3613           if (i!=v)  
3614                printf("  到顶点%d: %d\n",i,dist[i]);  
3615 }  
3616 void main()  
3617 { int A[MAXV][MAXV]={  
3618           {0466,INF,INF,INF},  
3619           {INF,01,INF,7,INF,INF},  
3620           {INF,INF,0,INF,64,INF},  
3621           {INF,INF,20,INF,5,INF},  
3622           {INF,INF,INF,INF,0,INF,6},  
3623           {INF,INF,INF,INF,108},  
3624           {INF,INF,INF,INF,INF,INF,0}};  
3625      int n=7, e=12;  
3626      CreateAdj(G,A,n,e);           //建立图的邻接表  
3627      printf("图G的邻接表:\n");  
3628      DispAdj(G);                     //输出邻接表  
3629      int v=0;  
3630      int dist[MAXV];  
3631      Dijkstra(v,dist);                //调用Dijkstra算法  
3632      Disppathlength(v,dist);      //输出结果  
3633      DestroyAdj(G);                    //销毁图的邻接表  
3634 }  
3635 上述程序的执行结果如图1.49所示。  
3636 
3637 753638 
3639 算法设计  
3640 
3641 
3642 
3643 
3644 
3645 
3646 
3647 
3648 
3649 
3650 
3651 
3652 图1.49 程序执行结果  
3653 其中Dijkstra算法的时间复杂度为O(n(log2e+MAX(顶点的出度)),一般图中最大顶点 
3654 出度远小于e,所以进一步简化时间复杂度为O(nlog2e)。  
3655 11. 有一个带权有向图G(所有权为正整数),采用邻接矩阵存储。设计一个算法求 
3656 其中的一个最小环。  
3657 解:利用Floyd算法求出所有顶点对之间的最短路径,若顶点i到j有最短路径,而 
3658 图中又存在顶点j到i的边,则构成一个环,在所有环中比较找到一个最小环并输出。对 应的程序如下:  
3659 #include "Graph.cpp"                     //包含图的基本运算算法  
3660 #include <vector>  
3661 using namespace std;  
3662 void Dispapath(int path[][MAXV],int i,int j)  
3663 //输出顶点i到j的一条最短路径  
3664 { vector<int> apath;                     //存放一条最短路径中间顶点(反向)   
3665      int k=path[i][j];  
3666      apath.push_back(j);                    //路径上添加终点  
3667      while (k!=-1 && k!=i)                //路径上添加中间点  
3668      { apath.push_back(k);  
3669           k=path[i][k];  
3670      }  
3671      apath.push_back(i);                    //路径上添加起点  
3672      for (int s=apath.size()-1;s>=0;s--) //输出路径上的中间顶点  
3673           printf("%d→",apath[s]);  
3674 }  
3675 int Mincycle(MGraph g,int A[MAXV][MAXV],int &mini,int &minj)  
3676 //在图g和A中的查找一个最小环  
3677 { int i,j,min=INF;  
3678      for (i=0;i<g.n;i++)  
3679           for (j=0;j<g.n;j++)  
3680                if (i!=j && g.edges[j][i]<INF)  
3681                { if (A[i][j]+g.edges[j][i]<min)  
3682                     { min=A[i][j]+g.edges[j][i];  
3683                          mini=i; minj=j;  
3684 
3685 763686 第1章  概论 
3687 
3688                     }  
3689                }  
3690      return min;  
3691 }  
3692 void Floyd(MGraph g)                     //Floyd算法求图g中的一个最小环  { int A[MAXV][MAXV],path[MAXV][MAXV];  
3693      int i,j,k,min,mini,minj;  
3694      for (i=0;i<g.n;i++)  
3695           for (j=0;j<g.n;j++)   
3696           { A[i][j]=g.edges[i][j];  
3697                if (i!=j && g.edges[i][j]<INF)  
3698                     path[i][j]=i;           //顶点i到j有边时  
3699                else  
3700                     path[i][j]=-1;          //顶点i到j没有边时  
3701           }  
3702      for (k=0;k<g.n;k++)                              //依次考察所有顶点  
3703      { for (i=0;i<g.n;i++)  
3704                for (j=0;j<g.n;j++)  
3705                     if (A[i][j]>A[i][k]+A[k][j])  
3706                     { A[i][j]=A[i][k]+A[k][j]; //修改最短路径长度  
3707                          path[i][j]=path[k][j];   //修改最短路径  
3708                     }  
3709      }  
3710      min=Mincycle(g,A,mini,minj);  
3711      if (min!=INF)  
3712      { printf("图中最小环:");  
3713           Dispapath(path,mini,minj);           //输出一条最短路径  
3714           printf("%d, 长度:%d\n",mini,min);  
3715      }  
3716      else printf("  图中没有任何环\n");  
3717 }  
3718 void main()  
3719 { MGraph g;  
3720      int A[MAXV][MAXV]={ {0,5,INF,INF},{INF,0,1,INF},  
3721                           {3,INF,0,2}, {INF,4,INF,0}};  
3722      int n=4, e=5;  
3723      CreateMat(g,A,n,e);               //建立图的邻接矩阵  
3724      printf("图G的邻接矩阵:\n");  
3725      DispMat(g);                     //输出邻接矩阵  
3726      Floyd(g);  
3727 }  
3728 上述程序的执行结果如图1.50所示。  
3729 
3730 
3731 
3732 
3733 
3734 
3735 
3736 773737 
3738 算法设计  
3739 
3740 
3741 
3742 
3743 
3744 
3745 
3746 
3747 图1.50 程序执行结果  
3748 1.10 第10章─计算几何  
3749 
3750 1.10.1 练习题  
3751 1. 对如图1.51所示的点集A,给出采用Graham扫描算法求凸包的过程及结果。  
3752 
3753         7             
3754 0                 a                     
3755                                     
3756                                     
3757                                     
3758                     a6         a     4         
3759     a8                                             
3760                 a9                                 
3761                         a     3             a     2     
3762                                             
3763     a1     1                             
3764                             a     1         
3765 1                                         
3766                                     
3767 1         2     3     4     5     6     78     9         10 
3768 
3769 
3770 
3771 
3772 
3773 
3774 
3775 
3776 图1.51 一个点集A  
3777 2. 对如图1.51所示的点集A,给出采用分治法求最近点对的过程及结果。  
3778 3. 对如图1.51所示的点集A,给出采用旋转卡壳法求最远点对的结果。  
3779 4. 对应3个点向量p1、p2、p3,采用S(p1,p2,p3)=(p2-p1)?(p3-p1)/2求它们构成的三 
3780 角形面积,请问什么情况下计算结果为正?什么情况下计算结果为负?  
3781 5. 已知坐标为整数,给出判断平面上一点p是否在一个逆时针三角形 p1-p2-p3 内部 
3782 的算法。  
3783 1.10.2 练习题参考答案  
3784 1. 答:采用Graham扫描算法求凸包的过程及结果如下:  
3785 求出起点a0(11)。  
3786 排序后:a0(11) a1(81) a2(94) a3(54) a4(87) a5(56) a10(710) a9(35)  
3787 a6(37) a7(410) a8(16) a11(03)。  
3788 先将a0(11)进栈,a1(81)进栈,a2(94)进栈。  
3789 处理点a3(54):a3(54)进栈。  
3790 处理点a4(87):a3(54)存在右拐关系,退栈,a4(87)进栈。  
3791 
3792 783793 第1章  概论 
3794 
3795 处理点a5(56):a5(56)进栈。  
3796 处理点a10(710):a5(56)存在右拐关系,退栈,a10(710)进栈。  
3797 处理点a9(35):a9(35)进栈。  
3798 处理点a6(37):a9(35)存在右拐关系,退栈,a6(37)进栈。  
3799 处理点a7(410):a6(37)存在右拐关系,退栈,a7(410)进栈。  
3800 处理点a8(16):a8(16)进栈。  
3801 处理点a11(03):a11(03)进栈。  
3802 结果:n=8,凸包的顶点:a0(11) a1(81) a2(94) a4(87) a10(710) a7(410)  
3803 a8(16) a11(03)。  
3804 2. 答:求解过程如下:  
3805 排序前:(11) (81) (94) (54) (87) (56) (37) (410) (16) (35) (710)  (03)。按x坐标排序后:(03) (11) (16) (37) (35) (410) (54) (56) (710)  (81) (87) (94)。按y坐标排序后:(11) (81) (03) (54) (94) (35) (16)  (56) (37) (87) (410) (710)。  
38061)中间位置midindex=5,左部分:(03) (11) (16) (37) (35) (410);右 部分:(54) (56) (710) (81) (87) (94);中间部分点集为 (03) (37) (410)  (54) (56) (710) (87)。  
38072)求解左部分:(03) (11) (16) (37) (35) (410)。  
3808 中间位置=2,划分为左部分1:(03) (11) (16),右部分1:(37) (35) (410)  
3809 处理左部分1:点数少于4:求出最近距离=2.23607,即(03)和(11)之间的距离。  
3810 处理右部分1:点数少于4:求出最近距离=2,即(37)和(35)之间的距离。  
3811 再考虑中间部分(中间部分最近距离=2.23)求出左部分d1=238123)求解右部分:(54) (56) (710) (81) (87) (94)。  
3813 中间位置=8,划分为左部分2:(54) (56) (710),右部分2:(81) (87) (93814 4)。  
3815 处理左部分2:点数少于4,求出最近距离=2,即 (54)和(56)之间的距离。  
3816 处理右部分2:点数少于4,求出最近距离=3.16228,即(81)和(94)之间的距离。  
3817 再考虑中间部分(中间部分为空)求出右部分d2=238184)求解中间部分点集:(03) (37) (410) (54) (56) (710) (87)。求出最 
3819 近距离d3=53820 最终结果为:d=MIN{d1,d2,d3)=23821 3. 答:采用旋转卡壳法求出两个最远点对是(11)和(710),最远距离为 10.823822 4. 答:当三角形p1-p2-p3逆时针方向时,如图1.52所示,p2-p1在p3-p1的顺时针方 向上,(p2-p1)?(p3-p1)>0,对应的面积(p2-p1)?(p3-p1)/2为正。  
3823 当三角形p1-p2-p3顺时针方向时,如图1.53所示,p2-p1在p3-p1的逆时针方向上, 
3824 (p2-p1)?(p3-p1)<0,对应的面积(p2-p1)?(p3-p1)/2为负。  
3825 
3826 
3827 
3828 793829 
3830 
3831 
3832 
3833     p3-p1 
3834     p1     p2-p1 
3835         
3836 
3837 
3838 
3839 
3840 
3841  
3842 
3843 算法设计  
3844 
3845 
3846     p2-p1 
3847     p1     p3-p1 
3848         
3849 
3850 
3851 
3852 
3853  
3854 图1.52  p1-p2-p3逆时针方向图      图1.53  p1-p2-p3逆时针方向  
3855 5. 答:用S(p1,p2,p3)=(p2-p1)?(p3-p1)/2求三角形p1、p2、p3带符号的的面积。如图 
3856 1.54所示,若S(p,p2,p3)和S(p,p3,p1)和S(p,p1,p2)(3个三角形的方向均为逆时针 方向)均大于0,表示p在该三角形内部。  
3857 p3  
3858 
3859 
3860 p1 
3861  
3862 
3863 
3864 p 
3865  
3866 
3867 
3868 
3869 p2  
3870 图1.54  一个点p和一个三角形  
3871 对应的程序如下:  
3872 #include "Fundament.cpp"                     //包含向量基本运算算法  
3873 double getArea(Point p1,Point p2,Point p3)     //求带符号的面积  
3874 {  
3875      return Det(p2-p1,p3-p1);  
3876 }  
3877 bool Intrig(Point p,Point p1,Point p2,Point p3) //判断p是否在三角形p1p2p3的内部  { double area1=getArea(p,p2,p3);  
3878      double area2=getArea(p,p3,p1);  
3879      double area3=getArea(p,p1,p2);  
3880      if (area1>0 && area2>0 && area3>0)  
3881           return true;  
3882      else  
3883           return false;  
3884 }  
3885 void main()  
3886 { printf("求解结果\n");  
3887      Point p1(0,0);  
3888      Point p2(5,-4);  
3889      Point p3(4,3);  
3890      Point p4(3,1);  
3891      Point p5(-1,1);  
3892      printf("  p1:"); p1.disp(); printf("\n");  
3893      printf("  p2:"); p2.disp(); printf("\n");  
3894      printf("  p3:"); p3.disp(); printf("\n");  
3895      printf("  p4:"); p4.disp(); printf("\n");  
3896 
3897 803898 第1章  概论 
3899 
3900      printf("  p5:"); p5.disp(); printf("\n");  
3901      printf("  p1p2p3三角形面积: %g\n",getArea(p1,p2,p3));  
3902      printf("  p4在p1p2p3三角形内部: %s\n",Intrig(p4,p1,p2,p3)?"":"不是");       printf("  p5在p1p2p3三角形内部: %s\n",Intrig(p5,p1,p2,p3)?"":"不是");  }  
3903 上述程序的执行结果如图1.55所示。  
3904 
3905 
3906 
3907 
3908 
3909 
3910 
3911 
3912 图1.55 程序执行结果  
3913 1.11 第11章─计算复杂性理论  
3914 
3915 1.11.1 练习题  
3916 1. 旅行商问题是NP问题吗?  
3917 A.否            B.是            C.至今尚无定论  
3918 2. 下面有关P问题,NP问题和NPC问题,说法错误的是( )。  
3919 A.如果一个问题可以找到一个能在多项式的时间里解决它的算法,那么这个问题就属 
3920 于P问题  
3921 B.NP问题是指可以在多项式的时间里验证一个解的问题  
3922 C.所有的P类问题都是NP问题  
3923 D.NPC问题不一定是个NP问题,只要保证所有的NP问题都可以约化到它即可  
3924 3. 对于《教程》例11.2设计的图灵机,分别给出执行f(32)和f(23)的瞬像演变过 
3925 程。  
3926 4. 什么是P类问题?什么是NP类问题?  
3927 5. 证明求两个m行n列的二维矩阵相加的问题属于P类问题。  
3928 6. 证明求含有n个元素的数据序列中求最大元素的问题属于P类问题。  
3929 7. 设计一个确定性图灵机M,用于计算后继函数S(n)=n+1(n为一个二进制数),并 
3930 给出求1010001的后继函数值的瞬像演变过程。  
3931 1.11.2 练习题参考答案  
3932 1. 答:B。  
3933 2. 答:D。  
3934 3. 答:(1)执行f(32)时,输入带上的初始信息为000100B,其瞬像演变过程如 
3935 
3936 813937 
3938 
3939 
3940 下:  
3941  
3942 
3943 算法设计   
3944 q0000100B?Bq100100B? B0q10100B?B00q1100B? B001q200B?B00q3110B?  
3945 B0q30110B?Bq300110B?q3B00110B?Bq000110B?BBq10110B?BB0q1110B?  
3946 BB01q210B?BB011q20B?BB01q311B?BB0q3111B?BBq30111B?BBq00111B?  
3947 BBB1q211B?BBB11q21B?BBB111q2B?BBB11q41B?BBB1q41BB?  
3948 BBBq41BBB?BBBq4BBBB?BBB0q6BBB  
3949 最终带上有一个0,计算结果为1。  
39502)执行f(23)时,输入带上的初始信息为001000B,其瞬像演变过程如下:  
3951 q0001000B?Bq001000B?B0q11000B?B01q2000B?B0q31100B?Bq301100B?  
3952 q3 B01100B?Bq001100B?BBq11100B?BB1q2100B?BB11q200B?BB1q3100B?  
3953 BBq31100B?Bq3B1100B?BBq01100B?BBBq5100B?BBBBq500B?  
3954 BBBBBq50B?BBBBBBq5B?BBBBBBBq6  
3955 最终带上有零个0,计算结果为0。  
3956 4. 答:用确定性图灵机以多项式时间界可解的问题称为P类问题。用非确定性图灵 
3957 机以多项式时间界可解的问题称为NP类问题。  
3958 5. 答:求两个m行n列的二维矩阵相加的问题对应的算法时间复杂度为O(mn),所 
3959 以属于P类问题。  
3960 6. 答:求含有n个元素的数据序列中最大元素的问题的算法时间复杂度为O(n),所 
3961 以属于P类问题。  
3962 7. 解: q0为初始状态,q3为终止状态,读写头初始时注视最右边的格。δ动作函数 
3963 如下:  
3964 δ(q0,0)→(q1,1,L)  
3965 δ(q0,1)→(q2,0,L)  
3966 δ(q0,B)→(q3,B,R)  
3967 δ(q1,0)→(q1,0,L)  
3968 δ(q1,1)→(q1,1,L)  
3969 δ(q1,B)→(q3,B,L)  
3970 δ(q2,0)→(q1,1,L)  
3971 δ(q2,1)→(q2,0,L)  
3972 δ(q2,B)→(q3,B,L)  
3973 求10100010的后继函数值的瞬像演变过程如下:  
3974 B1010001q00B?B101000q111B?B10100q1011B?B1010q10011B?B101q100011B  
3975 ?B10q1100011B?B1q10100011B?Bq110100011B?q1B10100011B  
3976 ?q3BB10100011B  
3977 其结果为10100011。  
3978 
3979 
3980 
3981 
3982 
3983 823984 第1章  概论 
3985 1.12  第12章─概率算法和近似算法  
3986 
3987 1.12.1  练习题  
3988 1. 蒙特卡罗算法是( )的一种。  
3989 A.分枝限界算法  B.贪心算法      C.概率算法           D.回溯算法  
3990 2. 在下列算法中有时找不到问题解的是( )。  
3991 A.蒙特卡罗算法  B.拉斯维加斯算法  C.舍伍德算法  D.数值概率算法  
3992 3. 在下列算法中得到的解未必正确的是( )。  
3993 A.蒙特卡罗算法  B.拉斯维加斯算法  C.舍伍德算法  D.数值概率算法  
3994 4. 总能求得非数值问题的一个解,且所求得的解总是正确的是( )。  
3995 A.蒙特卡罗算法  B.拉斯维加斯算法  C.数值概率算法 D.舍伍德算法  
3996 5. 目前可以采用( )在多项式级时间内求出旅行商问题的一个近似最优解。  
3997 A.回溯法            B.蛮力法            C.近似算法      D.都不可能  
3998 6. 下列叙述错误的是(  )。  
3999 A.概率算法的期望执行时间是指反复解同一个输入实例所花的平均执行时间  
4000 B.概率算法的平均期望时间是指所有输入实例上的平均期望执行时间  
4001 C.概率算法的最坏期望事件是指最坏输入实例上的期望执行时间  
4002 D.概率算法的期望执行时间是指所有输入实例上的所花的平均执行时间  
4003 7. 下列叙述错误的是(  )。  
4004 A.数值概率算法一般是求数值计算问题的近似解  
4005 B.Monte Carlo总能求得问题的一个解,但该解未必正确  
4006 C.Las Vegas算法的一定能求出问题的正确解  
4007 D.Sherwood算法的主要作用是减少或是消除好的和坏的实例之间的差别  
4008 8. 近似算法和贪心法有什么不同?  
4009 9. 给定能随机生成整数1到5的函数rand5(),写出能随机生成整数1到7的函数 
4010 rand7()。  
4011 1.12.2  练习题参考答案  
4012 1. 答:C。  
4013 2. 答:B。  
4014 3. 答:A。  
4015 4. 答:D。  
4016 5. 答:C。  
4017 6. 答:对概率算法通常讨论平均的期望时间和最坏的期望时间,前者指所有输入实 
4018 例上平均的期望执行时间,后者指最坏的输入实例上的期望执行时间。答案为D。  
4019 7. 答:一旦用拉斯维加斯算法找到一个解,那么这个解肯定是正确的,但有时用拉 
4020 斯维加斯算法可能找不到解。答案为C。  
4021 8. 答:近似算法不能保证得到最优解。贪心算法不一定是近似算法,如果可以证明 
4022 
4023 834024 
4025 算法设计  
4026 
4027 决策既不受之前决策的影响,也不影响后续决策,则贪心算法就是确定的最优解算法。  
4028 9. 解:通过rand5()*5+rand5()产生6,789,…,26272829,30这25个整 
4029 数,每个整数x出现的概率相等,取前面3*7=21个整数,舍弃后面的4个整数,将{678}转化成1,{91011}转化成2,以此类推,即由y= (x-3)/3为最终结果。对应的程 序如下:  
4030 #include <stdio.h>  
4031 #include <stdlib.h>                    //包含产生随机数的库函数  
4032 #include <time.h>  
4033 int rand5()                          //产生一个[1,5]的随机数  
4034 { int a=1,b=5;  
4035      return rand()%(b-a+1)+a;  
4036 }  
4037 int rand7()                          //产生一个[1,7]的随机数  
4038 { int x;  
4039      do  
4040      {  
4041           x=rand5()*5+rand5();  
4042      } while (x>26);  
4043      int y=(x-3)/3;  
4044      return y;  
4045 }  
4046 void main()  
4047 { srand((unsigned)time(NULL));     //随机种子  
4048      for (int i=1;i<=20;i++)           //输出20个[1,7]的随机数  
4049           printf("%d ",rand7());  
4050      printf("\n");  
4051 }  
4052 上述程序的一次执行结果如图1.56所示。  
4053 
4054 
4055 
4056 
4057 图1.56  程序执行结果  
4058 
4059 
4060 
4061 
4062 
4063 
4064 
4065 
4066 
4067 
4068 
4069 
4070 84   

算法设计与分析 - 李春葆 - 第二版 - pdf->word v3

标签:子集和   若是   排序算法   种子   声明   creat   水果   函数定义   bbb   

原文地址:https://www.cnblogs.com/JUSTDOINS/p/12085050.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!