算法設計與分析 - 李春葆 - 第二版 - pdf->word v3

   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. 有一個整數序列,全部元素均不相同,設計一個算法求相差最小的元素對的個 數。如序列四、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的回溯法求全排列,以一、2開頭 
1788 的排列必定最早出現嗎?爲何?  
1789 8. 考慮n皇后問題,其解空間樹爲由一、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結點擴展出來的葉子結點,它們就是以一、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,取第二、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;     //按二、三、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循環處理arr一、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循環處理arr一、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循環處理arr一、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)表示單位運輸費用。給出從一、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,它到一、2、3位置運輸費用爲0,容量分 別爲到一、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個點向量p一、p二、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求三角形p一、p二、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   
相關文章
相關標籤/搜索