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

   1 1.11 章─概論
   2 1.1.1    練習題
   3 1.    下列關於算法的說法中正確的有( )。Ⅰ.求解某一類問題的算法是惟一的
   4 Ⅱ.算法必須在有限步操做以後中止
   5 Ⅲ.算法的每一步操做必須是明確的,不能有歧義或含義模糊Ⅳ.算法執行後必定產生肯定的結果
   6 A. 1 個    B.2 個    C.3 個    D.4   7 2.    T(n)表示當輸入規模爲 n 時的算法效率,如下算法效率最優的是( )。
   8 A.T(n)= T(n-1)+1,T(1)=1    B.T(n)= 2n2
   9 C.T(n)= T(n/2)+1,T(1)=1    D.T(n)=3nlog2n
  10 3.    什麼是算法?算法有哪些特徵?
  11 4.    判斷一個大於 2 的正整數 n 是否爲素數的方法有多種,給出兩種算法,說明其中一種算法更好的理由。
  12 5.    證實如下關係成立:
  131)10n2-2n=(n2)
  142)2n+1=(2n)
  15 6. 證實 O(f(n))+O(g(n))=O(max{f(n),g(n)})  。
  16 7.    有一個含 n(n>2)個整數的數組 a,判斷其中是否存在出現次數超過全部元素一半的元素。
  17 8.    一個字符串採用 string 對象存儲,設計一個算法判斷該字符串是否爲迴文。
  18 9.    有一個整數序列,設計一個算法判斷其中是否存在兩個元素和剛好等於給定的整數 k。
  19 10.    有兩個整數序列,每一個整數序列中全部元素均不相同。設計一個算法求它們的公共元素,要求不使用 STL 的集合算法。
  20 11.    正整數 n(n>1)能夠寫成質數的乘積形式,稱爲整數的質因數分解。例如, 12=2*2*318=2*3*311=11。設計一個算法求 n 這樣分解後各個質因數出現的次數,採用 vector 向量存放結果。
  21 12.    有一個整數序列,全部元素均不相同,設計一個算法求相差最小的元素對的個數。如序列 4123 的相差最小的元素對的個數是 3,其元素對是(12),(23),
  2234)。
  23 13.    有一個 map<stringint>容器,其中已經存放了較多元素。設計一個算法求出其中重複的 value 而且返回重複 value 的個數。
  24 14.    從新作第 10 題,採用 map 容器存放最終結果。
  25 15.    假設有一個含 n(n>1)個元素的 stack<int>棧容器 st,設計一個算法出棧從棧頂到棧底的第 k(1≤k≤n)個元素,其餘棧元素不變。
  26  
  27 
  28 1.1.2    練習題參考答案
  29 1.    答:因爲算法具備有窮性、肯定性和輸出性,於是Ⅱ、Ⅲ、Ⅳ正確,而解決某一類問題的算法不必定是惟一的。答案爲 C。
  30 2.    答:選項 A 的時間複雜度爲 O(n)。選項 B 的時間複雜度爲 O(n2)。選項 C 的時間複雜度爲 O(log2n)。選項 D 的時間複雜度爲 O(nlog2n)。答案爲 C。
  31 3.    答:算法是求解問題的一系列計算步驟。算法具備有限性、肯定性、可行性、輸入性和輸出性 5 個重要特徵。
  32 4.    答:兩種算法以下:
  33 #include <stdio.h> #include <math.h> 
  34 bool isPrime1(int n) //方法 1 
  35 {    for (int i=2;i<n;i++) 
  36           if (n%i==0) 
  37                return false; 
  38      return true; 
  39 } 
  40 bool isPrime2(int n) //方法 2 
  41 {    for (int i=2;i<=(int)sqrt(n);i++) 
  42           if (n%i==0) 
  43                return false; 
  44      return true; 
  45 } 
  46 void main() 
  47 {    int n=5; 
  48      printf("%d,%d\n",isPrime1(n),isPrime2(n)); 
  49 } 
  50 方法 1 的時間複雜度爲 O(n),方法 2 的時間複雜度爲 n,因此方法 2 更好。
  51 5. 答:(1)當 n 足夠大時,(10n2-2n)/( n2)=10,因此 10n2-2n=(n2)。
  522)2n+1=2*2n=(2n)。
  53 6.    證實:對於任意 f1(n)∈O(f(n)) ,存在正常數 c1 和正常數 n1,使得對全部 n≥n1, 有 f1(n)≤c1f(n) 。
  54 相似地,對於任意 g1(n)∈O(g(n))    ,存在正常數 c2 和天然數 n2,使得對全部 n≥n2, 有 g1(n)≤c2g(n) 。
  55 令 c3=max{c1,c2},n3=max{n1,n2},h(n)= max{f(n),g(n)} 。則對全部的 n≥n3,有:
  56 f1(n) +g1(n)≤c1f(n) + c2g(n)≤c3f(n)+c3g(n)=c3(f(n)+g(n))
  57 ≤c32max{f(n),g(n)}=2c3h(n)=O(max{f(n),g(n)})。
  58 7.    解:先將 a 中元素遞增排序,再求出現次數最多的次數 maxnum,最後判斷是否知足條件。對應的程序以下:
  59 #include <stdio.h> #include <algorithm> using namespace std; 
  60  
  61 bool solve(int a[],int n,int &x) 
  62 { 
  63      sort(a,a+n);   
  64 int maxnum=0;           //遞增排序 
  65      //出現次數最多的次數 
  66      int num=1;     
  67      int e=a[0];     
  68      for (int i=1;i<n;i++) 
  69      {    if (a[i]==e) 
  70           {    num++; 
  71                if (num>maxnum) 
  72                {    maxnum=num; 
  73                     x=e; 
  74                } 
  75           } 
  76           else 
  77           {    e=a[i]; 
  78                num=1; 
  79           } 
  80      } 
  81      if (maxnum>n/2) 
  82           return true; 
  83      else 
  84           return false; 
  85 } 
  86 void main() 
  87 {    int a[]={2,2,2,4,5,6,2}; 
  88      int n=sizeof(a)/sizeof(a[0]); 
  89      int x; 
  90      if (solve(a,n,x)) 
  91           printf("出現次數超過全部元素一半的元素爲%d\n",x); 
  92      else 
  93           printf("不存在出現次數超過全部元素一半的元素\n"); 
  94 } 
  95 上述程序的執行結果如圖 1.1 所示。
  961.1    程序執行結果
  97 8.    解:採用先後字符判斷方法,對應的程序以下:
  98 #include <iostream> #include <string> using namespace std; 
  99 bool solve(string str)    //判斷字符串 str 是否爲迴文 
 100 {    int i=0,j=str.length()-1; 
 101      while (i<j) 
 102      {    if (str[i]!=str[j]) 
 103                return false; 
 104  
 105 
 106           i++; j--; 
 107      } 
 108      return true; 
 109 } 
 110 void main() 
 111 {    cout << "求解結果" << endl; 
 112      string str="abcd"; 
 113      cout << " " << str << (solve(str)?"是迴文":"不是迴文") << endl; 
 114      string str1="abba"; 
 115      cout << " " << str1 << (solve(str1)?"是迴文":"不是迴文") << endl; 
 116 } 
 117 上述程序的執行結果如圖 1.2 所示。
 1181.2    程序執行結果
 119 9.    解:先將 a 中元素遞增排序,而後從兩端開始進行判斷。對應的程序以下:
 120 #include <stdio.h> #include <algorithm> using namespace std; 
 121 bool solve(int a[],int n,int k) 
 122 {    sort(a,a+n);        //遞增排序 
 123      int i=0, j=n-1; 
 124      while (i<j)        //區間中存在兩個或者以上元素 
 125      {    if (a[i]+a[j]==k) 
 126                return true; 
 127           else if (a[i]+a[j]<k) 
 128                i++; 
 129           else 
 130                j--; 
 131      } 
 132      return false; 
 133 } 
 134 void main() 
 135 {    int a[]={1,2,4,5,3}; 
 136      int n=sizeof(a)/sizeof(a[0]); 
 137      printf("求解結果\n"); 
 138      int k=9,i,j; 
 139      if (solve(a,n,k,i,j)) 
 140           printf(" 存在: %d+%d=%d\n",a[i],a[j],k); 
 141      else 
 142           printf(" 不存在兩個元素和爲%d\n",k); 
 143      int k1=10; 
 144      if (solve(a,n,k1,i,j)) 
 145           printf(" 存在: %d+%d=%d\n",a[i],a[j],k1); 
 146  
 147      else 
 148           printf(" 不存在兩個元素和爲%d\n",k1); 
 149 } 
 150 上述程序的執行結果如圖 1.3 所示。
 1511.3 程序執行結果
 152 10.    解:採用集合 set<int>存儲整數序列,集合中元素默認是遞增排序的,再採用二路歸併算法求它們的交集。對應的程序以下:
 153 #include <stdio.h> #include <set> 
 154 using namespace std; 
 155 void solve(set<int> s1,set<int> s2,set<int> &s3) //求交集 s3 
 156 {    set<int>::iterator it1,it2; 
 157      it1=s1.begin(); it2=s2.begin(); 
 158      while (it1!=s1.end() && it2!=s2.end()) 
 159      {    if (*it1==*it2) 
 160           {    s3.insert(*it1); 
 161                ++it1; ++it2; 
 162           } 
 163           else if (*it1<*it2) 
 164                ++it1; 
 165           else 
 166                ++it2; 
 167      } 
 168 } 
 169 void dispset(set<int> s)        //輸出集合的元素 
 170 {    set<int>::iterator it; 
 171      for (it=s.begin();it!=s.end();++it) 
 172           printf("%d ",*it); 
 173      printf("\n"); 
 174 } 
 175 void main() 
 176 {    int a[]={3,2,4,8}; 
 177      int n=sizeof(a)/sizeof(a[0]); 
 178      set<int> s1(a,a+n); 
 179      int b[]={1,2,4,5,3}; 
 180      int m=sizeof(b)/sizeof(b[0]); 
 181      set<int> s2(b,b+m); 
 182      set<int> s3; 
 183      solve(s1,s2,s3); 
 184      printf("求解結果\n"); 
 185      printf(" s1: "); dispset(s1); 
 186  
 187 
 188      printf("  s2: "); dispset(s2); 
 189      printf("  s3: "); dispset(s3); 
 190 } 
 191 上述程序的執行結果如圖 1.4 所示。
 1921.4 程序執行結果
 193 11.    解:對於正整數 n,從 i=2 開始查找其質因數,ic 記錄質因數 i 出現的次數,當找到這樣質因數後,將(i,ic)做爲一個元素插入到 vector 容器 v 中。最後輸出 v。對應的算法以下:
 194 #include <stdio.h> #include <vector> using namespace std; 
 195 struct NodeType        //vector 向量元素類型 
 196 {    int p;            //質因數 
 197      int pc;        //質因數出現次數 
 198 }; 
 199 void solve(int n,vector<NodeType> &v) //求 n 的質因數分解 
 200 {    int i=2; 
 201      int ic=0; 
 202      NodeType e; 
 203      do 
 204      {    if (n%i==0) 
 205           {    ic++; 
 206                n=n/i; 
 207           } 
 208           else 
 209           {    if (ic>0) 
 210                {    e.p=i; 
 211                     e.pc=ic; 
 212                     v.push_back(e); 
 213                } 
 214                ic=0; 
 215                i++; 
 216           } 
 217      } while (n>1 || ic!=0); 
 218 } 
 219 void disp(vector<NodeType> &v)    //輸出 v 
 220 {    vector<NodeType>::iterator it; 
 221      for (it=v.begin();it!=v.end();++it) 
 222           printf(" 質因數%d 出現%d 次\n",it->p,it->pc); 
 223 } 
 224  
 225 void main() 
 226 {    vector<NodeType> v; 
 227      int n=100; 
 228      printf("n=%d\n",n); 
 229      solve(n,v); 
 230      disp(v); 
 231 } 
 232 上述程序的執行結果如圖 1.5 所示。
 2331.5 程序執行結果
 234 12.    解:先遞增排序,再求相鄰元素差,比較求最小元素差,累計最小元素差的個數。對應的程序以下:
 235 #include <iostream> #include <algorithm> #include <vector> using namespace std; 
 236 int solve(vector<int> &myv)        //求 myv 中相差最小的元素對的個數 
 237 {    sort(myv.begin(),myv.end());    //遞增排序 
 238      int ans=1; 
 239      int mindif=myv[1]-myv[0]; 
 240      for (int i=2;i<myv.size();i++) 
 241      {    if (myv[i]-myv[i-1]<mindif) 
 242           {    ans=1; 
 243                mindif=myv[i]-myv[i-1]; 
 244           } 
 245           else if (myv[i]-myv[i-1]==mindif) 
 246                ans++; 
 247      } 
 248      return ans; 
 249 } 
 250 void main() 
 251 {    int a[]={4,1,2,3}; 
 252      int n=sizeof(a)/sizeof(a[0]); 
 253      vector<int> myv(a,a+n); 
 254      cout << "相差最小的元素對的個數: " << solve(myv) << endl; 
 255 } 
 256 上述程序的執行結果如圖 1.6 所示。
 257  
 258 
 2591.6 程序執行結果
 260 13.    解:對於 map<stringint>容器 mymap,設計另一個 map<intint>容器 tmap, 將前者的 value 做爲後者的關鍵字。遍歷 mymap,累計 tmap 中相同關鍵字的次數。一個參考程序及其輸出結果以下:
 261 #include <iostream> #include <map> #include <string> using namespace std; void main() 
 262 {    map<string,int> mymap; 
 263      mymap.insert(pair<string,int>("Mary",80)); 
 264      mymap.insert(pair<string,int>("Smith",82)); 
 265      mymap.insert(pair<string,int>("John",80)); 
 266      mymap.insert(pair<string,int>("Lippman",95)); 
 267      mymap.insert(pair<string,int>("Detial",82)); 
 268      map<string,int>::iterator it; 
 269      map<int,int> tmap; 
 270      for (it=mymap.begin();it!=mymap.end();it++) 
 271           tmap[(*it).second]++; 
 272      map<intint>::iterator it1; 
 273      cout << "求解結果" << endl; 
 274      for (it1=tmap.begin();it1!=tmap.end();it1++) 
 275           cout << " " << (*it1).first << ": " << (*it1).second << "次\n"; 
 276 } 
 277 上述程序的執行結果如圖 1.7 所示。
 2781.7 程序執行結果
 279 14.    解:採用 map<intint>容器 mymap 存放求解結果,第一個份量存放質因數,第二個份量存放質因數出現次數。對應的程序以下:
 280 #include <stdio.h> #include <map> 
 281 using namespace std; 
 282 void solve(int n,map<int,int> &mymap) //求 n 的質因數分解 
 283  
 284 
 285  
 286       
 287      else 
 288 {     if (ic>0) 
 289                     mymap[i]=ic; 
 290                ic=0; 
 291                i++; 
 292           }     
 293      } while (n>1 || ic!=0); 
 294 } 
 295 void disp(map<int,int> &mymap) //輸出 mymap 
 296 {    map<int,int>::iterator it; 
 297      for (it=mymap.begin();it!=mymap.end();++it) 
 298           printf(" 質因數%d 出現%d 次\n",it->first,it->second); 
 299 } 
 300 void main() 
 301 {    map<int,int> mymap; 
 302      int n=12345; 
 303      printf("n=%d\n",n); 
 304      solve(n,mymap); 
 305      disp(mymap); 
 306 } 
 307 上述程序的執行結果如圖 1.8 所示。
 3081.8 程序執行結果
 309 15.    解:棧容器不能順序遍歷,爲此建立一個臨時 tmpst 棧,將 st 的 k 個元素出棧並進棧到 tmpst 中,再出棧 tmpst 一次獲得第 k 個元素,最後將棧 tmpst 的全部元素出棧並進棧到 st 中。對應的程序以下:
 310 #include <stdio.h> #include <stack> using namespace std; 
 311 int solve(stack<int> &st,int k)    //出棧第 k 個元素 
 312 {    stack<int> tmpst; 
 313      int e; 
 314      for (int i=0;i<k;i++)        //出棧 st 的 k 個元素並進 tmpst 棧 
 315      {    e=st.top(); 
 316           st.pop(); 
 317           tmpst.push(e); 
 318      } 
 319      e=tmpst.top();                //求第 k 個元素 
 320      tmpst.pop(); 
 321      while (!tmpst.empty())        //將 tmpst 的全部元素出棧並進棧 st 
 322      {    st.push(tmpst.top()); 
 323           tmpst.pop(); 
 324  
 325 
 326      } 
 327      return e; 
 328 } 
 329 void disp(stack<int> &st)        //出棧 st 的全部元素 
 330 {    while (!st.empty()) 
 331      {    printf("%d ",st.top()); 
 332           st.pop(); 
 333      } 
 334      printf("\n"); 
 335 } 
 336 void main() 
 337 {    stack<int> st; 
 338      printf("進棧元素 1,2,3,4\n"); 
 339      st.push(1); 
 340      st.push(2); 
 341      st.push(3); 
 342      st.push(4); 
 343      int k=3; 
 344      int e=solve(st,k); 
 345      printf("出棧第%d 個元素是: %d\n",k,e); 
 346      printf("st 中元素出棧順序: "); 
 347      disp(st); 
 348 } 
 349 上述程序的執行結果如圖 1.9 所示。
 3501.9    程序執行結果
 351 1.22 章─遞歸算法設計技術
 352 1.2.1    練習題
 353 1.    什麼是直接遞歸和間接遞歸?消除遞歸通常要用到什麼數據結構?
 354 2.    分析如下程序的執行結果:
 355 #include <stdio.h> 
 356 void f(int n,int &m) 
 357 {    if (n<1) return; 
 358      else 
 359      {    printf("調用f(%d,%d)前,n=%d,m=%d\n",n-1,m-1,n,m); 
 360           n--; m--; 
 361           f(n-1,m); 
 362           printf("調用f(%d,%d)後:n=%d,m=%d\n",n-1,m-1,n,m); 
 363      } 
 364  
 365 } 
 366 void main() 
 367 {    int n=4,m=4; 
 368      f(n,m); 
 369 } 
 370 3.    採用直接推導方法求解如下遞歸方程:
 371 T(1)=1
 372 T(n)=T(n-1)+n    當 n>1
 373 4.    採用特徵方程方法求解如下遞歸方程:
 374 H(0)=0
 375 H(1)=1
 376 H(2)=2
 377 H(n)=H(n-1)+9H(n-2)-9H(n-3) 當 n>2
 378 5.    採用遞歸樹方法求解如下遞歸方程:
 379 T(1)=1
 380 T(n)=4T(n/2)+n    當 n>1
 381 6.    採用主方法求解如下題的遞歸方程。
 382 T(n)=1    當 n=1
 383 T(n)=4T(n/2)+n2    當 n>1
 384 7.    分析求斐波那契 f(n)的時間複雜度。
 385 8.    數列的首項 a1=0,後續奇數項和偶數項的計算公式分別爲 a2n=a2n-1+2,a2n+1=a2n- 1+a2n-1,寫出計算數列第 n 項的遞歸算法。
 386 9.    對於一個採用字符數組存放的字符串 str,設計一個遞歸算法求其字符個數(長度)。
 387 10.    對於一個採用字符數組存放的字符串 str,設計一個遞歸算法判斷 str 是否爲迴文。
 388 11.    對於不帶頭結點的單鏈表 L,設計一個遞歸算法正序輸出全部結點值。
 389 12.    對於不帶頭結點的單鏈表 L,設計一個遞歸算法逆序輸出全部結點值。
 390 13.    對於不帶頭結點的非空單鏈表 L,設計一個遞歸算法返回最大值結點的地址(假設這樣的結點惟一)。
 391 14.    對於不帶頭結點的單鏈表 L,設計一個遞歸算法返回第一個值爲 x 的結點的地址,沒有這樣的結點時返回 NULL。
 392 15.    對於不帶頭結點的單鏈表 L,設計一個遞歸算法刪除第一個值爲 x 的結點。
 393 16.    假設二叉樹採用二叉鏈存儲結構存放,結點值爲 int 類型,設計一個遞歸算法求二叉樹 bt 中全部葉子結點值之和。
 394 17.    假設二叉樹採用二叉鏈存儲結構存放,結點值爲 int 類型,設計一個遞歸算法求二叉樹 bt 中全部結點值大於等於 k 的結點個數。
 395 18.    假設二叉樹採用二叉鏈存儲結構存放,全部結點值均不相同,設計一個遞歸算法求值爲 x 的結點的層次(根結點的層次爲 1),沒有找到這樣的結點時返回 0 396  
 397 
 398 1.2.2    練習題參考答案
 399 1.    答:一個 f 函數定義中直接調用 f 函數本身,稱爲直接遞歸。一個 f 函數定義中調用 g 函數,而 g 函數的定義中調用 f 函數,稱爲間接遞歸。消除遞歸通常要用棧實現。
 400 2.    答:遞歸函數f(n,m)中,n是非引用參數,m是引用參數,因此遞歸函數的狀態爲
 401 (n)。程序執行結果以下:
 402 調用f(3,3)前,n=4,m=4 調用f(1,2)前,n=2,m=3 調用f(0,1)後,n=1,m=2 調用f(2,1)後,n=3,m=2 
 403 3.    解:求 T(n)的過程以下:
 404 T(n)=T(n-1)+n=[T(n-2)+n-1)]+n=T(n-2)+n+(n-1)
 405 =T(n-3)+n+(n-1)+(n-2)
 406 = 407 =T(1)+n+(n-1)+…+2
 408 =n+(n-1)+ +…+2+1=n(n+1)/2=O(n2)。
 409 4.    解:整數一個常係數的線性齊次遞推式,用 xn 代替 H(n),有:xn=xn-1+9xn-2-9xn-3, 兩邊同時除以 xn-3,獲得:x3=x2+9x-9,即 x3-x2-9x+9=0 410 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
 411 則遞歸方程的通解爲:H(n)=c1+c2(-3)n+c33n 代入 H(0)=0,有 c1+c2+c3=0
 412 代入 H(1)=1,有 c1-3c2+3c3=1
 413 代入 H(2)=2,有 c1+9c2+9c3=2
 414  
 415 ( ‒ 1)n ‒ 1
 416 
 417  
 418  
 419 n ‒ 1    1
 420 
 421  
 422 求出:c1=-1/4,c2=-1/12,c3=1/3,H(n)=c1+c2(-3)n+c33n=(    4    + 1)3
 423  
 4244 425  
 426 5.    解:構造的遞歸樹如圖 1.10 所示,第 1 層的問題規模爲 n,第 2 的層的子問題的問題規模爲 n/2,依此類推,當展開到第 k+1 層,其規模爲 n/2k=1,因此遞歸樹的高度爲log2n+1 427 第1層有1個結點,其時間爲n,第2層有4個結點,其時間爲4(n/2)=2n,依次類推,第k 層有4k-1個結點,每一個子問題規模爲n/2k-1,其時間爲4k-1(n/2k-1)=2k-1n。葉子結點的個數爲n 個,其時間爲n。將遞歸樹每一層的時間加起來,可得:
 428 T(n)=n+2n+…+ 2k-1n+…+n≈𝑛 ∗ 2log2n=O(n2)。
 429  
 430 
 431 
 432 (n/2)
 433  
 434 n         n
 435 
 436 (n/2)    (n/2)    (n/2)     2n
 437  
 438 
 439  
 440 高度h爲log 2n+1
 441  
 442 (n/22)
 443  
 444 (n/22)
 445  
 446 (n/22)
 447  
 448 (n/22)
 449  
 450 
 451       22n
 452  
 453 …    …    …    …
 454 
 455 1    1    1    1         n
 456 
 4571.10  一棵遞歸樹
 458 6.    解:採用主方法求解,這裏 a=4,b=2,f(n)=n2。
 459 logba    log24    2
 460  
 461 所以,𝑛
 462 logba
 463  
 464 =𝑛
 465 2
 466  
 467 =n ,它與 f(n)同樣大,知足主定理中的狀況(2),因此 T(n)=O(
 468  
 469 𝑛    log2n)=O(n log2n)。
 470 7.    解:設求斐波那契 f(n)的時間爲 T(n),有如下遞推式:
 471 T(1)=T(2)
 472 T(n)=T(n-1)+T(n-2)+1    當 n>2
 473 其中,T(n)式中加 1 表示一次加法運算的時間。
 474 不妨先求 T1(1)=T1(2)=1,T1(n)=T1(n-1)+T1(n-2),按《教程》例 2.14 的方法能夠求
 475  
 476 出:
 477 11 478  
 479 
 480 5 n
 481  
 482 
 483 11 484  
 485 
 486 5 n
 487  
 488 
 489 11 490  
 491 
 492 5 n
 493  
 494 T1(n)=
 495  
 496        ≈
 497  
 498  = 
 499  
 500         
 501         
 502 11 503  
 504         
 505         
 506 5 n
 507  
 508 因此 T(n)=T1(n)+1 509  
 510 
 511  
 512 +1=O(φn),其中 φ= 513  
 514     
 515     
 516 8.    解:設 f(m)計算數列第 m 項值。
 517 當 m 爲偶數時,不妨設 m=2n,則 2n-1=m-1,因此有 f(m)=f(m-1)+2 518 當 m 爲奇數時,不妨設 m=2n+1,則 2n-1=m-2,2n=m-1,因此有 f(m)=f(m-2)+f(m-
 519 1)-1 520 對應的遞歸算法以下:
 521 int f(int m) 
 522 {    if (m==1) return 0; 
 523      if (m%2==0) 
 524           return f(m-1)+2; 
 525      else 
 526           return f(m-2)+f(m-1)-1; 
 527 } 
 528 9.    解:設 f(str)返回字符串 str 的長度,其遞歸模型以下:
 529 f(str)=0    當*str='\0' 530 f(str)=f(str+1)+1    其餘狀況
 531 對應的遞歸程序以下:
 532  
 533 
 534 #include <iostream> using namespace std; 
 535 int Length(char *str)        //求str的字符個數 
 536 {    if (*str=='\0') 
 537           return 0; 
 538      else 
 539           return Length(str+1)+1; 
 540 } 
 541 void main() 
 542 {    char str[]="abcd"; 
 543      cout << str << "的長度: " << Length(str) << endl; 
 544 } 
 545 上述程序的執行結果如圖 1.11 所示。
 5461.11    程序執行結果
 547 10.    解:設 f(str,n)返回含 n 個字符的字符串 str 是否爲迴文,其遞歸模型以下:
 548 f(str,n)=true    當 n=0 或者 n=1 549 f(str,n)=flase    當 str[0]≠str[n-1]時
 550 f(str,n)=f(str+1,n-2)    其餘狀況
 551 對應的遞歸算法以下: #include <stdio.h> #include <string.h> 
 552 bool isPal(char *str,int n)    //str 迴文判斷算法 
 553 {    if (n==0 || n==1) 
 554           return true; 
 555      if (str[0]!=str[n-1]) 
 556           return false; 
 557      return isPal(str+1,n-2); 
 558 } 
 559 void disp(char *str) 
 560 {    int n=strlen(str); 
 561      if (isPal(str,n)) 
 562           printf(" %s是迴文\n",str); 
 563      else 
 564           printf(" %s不是迴文\n",str); 
 565 } 
 566 void main() 
 567 {    printf("求解結果\n"); 
 568      disp("abcba"); 
 569      disp("a"); 
 570      disp("abc"); 
 571 } 
 572  
 573 上述程序的執行結果如圖 1.12 所示。
 5741.12    程序執行結果
 575 11.    解:設 f(L)正序輸出單鏈表 L 的全部結點值,其遞歸模型以下:
 576 f(L)  ≡ 不作任何事情    當 L=NULL f(L) ≡ 輸出 L->data; f(L->next);    當 L≠NULL 時對應的遞歸程序以下:
 577 #include "LinkList.cpp"        //包含單鏈表的基本運算算法 
 578 void dispLink(LinkNode *L)    //正序輸出全部結點值 
 579 {    if (L==NULL) return; 
 580      else 
 581      {    printf("%d ",L->data); 
 582           dispLink(L->next); 
 583      } 
 584 } 
 585 void main() 
 586 {    int a[]={1,2,5,2,3,2}; 
 587      int n=sizeof(a)/sizeof(a[0]); 
 588      LinkNode *L; 
 589      CreateList(L,a,n);        //由a[0..n-1]建立不帶頭結點的單鏈表 
 590      printf("正向L: ");  
 591      dispLink(L); printf("\n"); 
 592      Release(L);            //銷燬單鏈表 
 593 } 
 594 上述程序的執行結果如圖 1.13 所示。
 5951.13    程序執行結果
 596 12.    解:設 f(L)逆序輸出單鏈表 L 的全部結點值,其遞歸模型以下:
 597 f(L)  ≡ 不作任何事情    當 L=NULL f(L) ≡ f(L->next); 輸出 L->data    當 L≠NULL 時對應的遞歸程序以下:
 598 #include "LinkList.cpp"        //包含單鏈表的基本運算算法 
 599 void Revdisp(LinkNode *L)    //逆序輸出全部結點值 
 600 {    if (L==NULL) return; 
 601  
 602 
 603      else 
 604      {    Revdisp(L->next); 
 605           printf("%d ",L->data); 
 606      } 
 607 } 
 608 void main() 
 609 {    int a[]={1,2,5,2,3,2}; 
 610      int n=sizeof(a)/sizeof(a[0]); 
 611      LinkNode *L; 
 612      CreateList(L,a,n); 
 613      printf("反向L: ");  
 614      Revdisp(L); printf("\n"); 
 615      Release(L); 
 616 } 
 617 上述程序的執行結果如圖 1.14 所示。
 6181.14 程序執行結果
 619 13.    解:設 f(L)返回單鏈表 L 中值最大結點的地址,其遞歸模型以下:
 620 f(L) = L    當 L 只有一個結點時
 621 f(L) = MAX{f(L->next),L->data}    其餘狀況
 622 對應的遞歸程序以下:
 623 #include "LinkList.cpp"        //包含單鏈表的基本運算算法 
 624 LinkNode *Maxnode(LinkNode *L) //返回最大值結點的地址 
 625 {    if (L->next==NULL) 
 626           return L;            //只有一個結點時 
 627      else 
 628      {     LinkNode *maxp; 
 629           maxp=Maxnode(L->next); 
 630           if (L->data>maxp->data) 
 631                return L; 
 632           else 
 633                return maxp; 
 634      }     
 635 }         
 636 void main() 
 637 {    int a[]={1,2,5,2,3,2}; 
 638      int n=sizeof(a)/sizeof(a[0]); 
 639      LinkNode *L,*p; 
 640      CreateList(L,a,n); 
 641      p=Maxnode(L); 
 642      printf("最大結點值: %d\n",p->data); 
 643      Release(L); 
 644  
 645 } 
 646 上述程序的執行結果如圖 1.15 所示。
 6471.15    程序執行結果
 648 14.    解:設 f(L,x)返回單鏈表 L 中第一個值爲 x 的結點的地址,其遞歸模型以下:
 649 f(L,x) = NULL    當 L=NULL 時
 650 f(L,x) = L    當 L≠NULL 且 L->data=x 時
 651 f(L,x) =  f(L->next,x)    其餘狀況
 652 對應的遞歸程序以下:
 653 #include "LinkList.cpp"                //包含單鏈表的基本運算算法 
 654 LinkNode *Firstxnode(LinkNode *L,int x) //返回第一個值爲 x 的結點的地址 
 655 {    if (L==NULL) return NULL; 
 656      if (L->data==x) 
 657           return L; 
 658      else 
 659           return Firstxnode(L->next,x); 
 660 } 
 661 void main() 
 662 {    int a[]={1,2,5,2,3,2}; 
 663      int n=sizeof(a)/sizeof(a[0]); 
 664      LinkNode *L,*p; 
 665      CreateList(L,a,n); 
 666      int x=2; 
 667      p=Firstxnode(L,x); 
 668      printf("結點值: %d\n",p->data); 
 669      Release(L); 
 670 } 
 671 上述程序的執行結果如圖 1.16 所示。
 6721.16    程序執行結果
 673 15.    解:設 f(L,x)刪除單鏈表 L 中第一個值爲 x 的結點,其遞歸模型以下:
 674 f(L,x)  ≡ 不作任何事情    當 L=NULL
 675 f(L,x) ≡ 刪除 L 結點,L=L->next    當 L≠NULL 且 L->data=x f(L,x) ≡ f(L->next,x)    其餘狀況
 676 對應的遞歸程序以下:
 677  
 678 
 679 #include "LinkList.cpp"            //包含單鏈表的基本運算算法 
 680 void Delfirstx(LinkNode *&L,int x) //刪除單鏈表 L 中第一個值爲 x 的結點 
 681 {    if (L==NULL) return; 
 682      if (L->data==x) 
 683      {    LinkNode *p=L; 
 684           L=L->next; 
 685           free(p); 
 686      } 
 687      else 
 688           Delfirstx(L->next,x); 
 689 } 
 690 void main() 
 691 {    int a[]={1,2,5,2,3,2}; 
 692      int n=sizeof(a)/sizeof(a[0]); 
 693      LinkNode *L; 
 694      CreateList(L,a,n); 
 695      printf("刪除前L: "); DispList(L); 
 696      int x=2; 
 697      printf("刪除第一個值爲%d的結點\n",x); 
 698      Delfirstx(L,x); 
 699      printf("刪除後L: "); DispList(L); 
 700      Release(L); 
 701 } 
 702 上述程序的執行結果如圖 1.17 所示。
 7031.17 程序執行結果
 704 16.    解:設 f(bt)返回二叉樹 bt 中全部葉子結點值之和,其遞歸模型以下:
 705 
 706 f(bt)=0        當 bt=NULL
 707 f(bt)=bt->data        當 bt≠NULL 且 bt 結點爲葉子結點
 708 f(bt)=f(bt->lchild)+f(bt->rchild)        其餘狀況
 709 對應的遞歸程序以下:        
 710 #include "Btree.cpp"               //包含二叉樹的基本運算算法 
 711 int LeafSum(BTNode *bt)          //二叉樹 bt 中全部葉子結點值之和 
 712 {    if (bt==NULL) return 0; 
 713      if (bt->lchild==NULL && bt->rchild==NULL) 
 714           return bt->data; 
 715      int lsum=LeafSum(bt->lchild); 
 716      int rsum=LeafSum(bt->rchild); 
 717      return lsum+rsum; 
 718 } 
 719 void main() 
 720  
 721 
 722 {     BTNode *bt;     
 723      Int a[]={5,2,3,4,1,6};       //先序序列 
 724      Int b[]={2,3,5,1,4,6};       //中序序列 
 725      int n=sizeof(a)/sizeof(a[0]); 
 726      bt=CreateBTree(a,b,n);    //由a和b構造二叉鏈bt 
 727      printf("二叉樹bt:"); DispBTree(bt); printf("\n"); 
 728      printf("全部葉子結點值之和: %d\n",LeafSum(bt)); 
 729      DestroyBTree(bt);        //銷燬樹bt 
 730 } 
 731 上述程序的執行結果如圖 1.18 所示。
 7321.18 程序執行結果
 733 17.    解:設 f(bt,k)返回二叉樹 bt 中全部結點值大於等於 k 的結點個數,其遞歸模型以下:
 734 f(bt,k)=0    當 bt=NULL
 735 f(bt,k)=f(bt->lchild,k)+f(bt->rchild,k)+1    當 bt≠NULL 且 bt->data≥k f(bt,k)=f(bt->lchild,k)+f(bt->rchild,k)    其餘狀況
 736 對應的遞歸程序以下:
 737 #include "Btree.cpp"                //包含二叉樹的基本運算算法 
 738 int Nodenum(BTNode *bt,int k)        //大於等於 k 的結點個數 
 739 {    if (bt==NULL) return 0; 
 740      int lnum=Nodenum(bt->lchild,k); 
 741      int rnum=Nodenum(bt->rchild,k); 
 742      if (bt->data>=k) 
 743           return lnum+rnum+1; 
 744      else 
 745           return lnum+rnum; 
 746 } 
 747 void main() 
 748 {     BTNode *bt;     
 749      Int a[]={5,2,3,4,1,6};     
 750      Int b[]={2,3,5,1,4,6};     
 751      int n=sizeof(a)/sizeof(a[0]);     
 752      bt=CreateBTree(a,b,n);            //由a和b構造二叉鏈bt 
 753      printf("二叉樹bt:"); DispBTree(bt); printf("\n"); 
 754      int k=3; 
 755      printf("大於等於%d的結點個數: %d\n",k,Nodenum(bt,k)); 
 756      DestroyBTree(bt);            //銷燬樹bt 
 757 } 
 758 上述程序的執行結果如圖 1.19 所示。
 759  
 760 
 761  
 762 
 7631.19 程序執行結果
 764 18.    解:設 f(bt,x,h)返回二叉樹 bt 中 x 結點的層次,其中 h 表示 bt 所指結點的層次,初始調用時,bt 指向根結點,h 置爲 1。其遞歸模型以下:
 765 f(bt,x,h)=0    當 bt=NULL
 766 f(bt,x,h)=h    當 bt≠NULL 且 bt->data=x
 767 f(bt,x,h) =l    當 l=f(bt->lchild,x,h+1)≠0 f(bt,x,h) =f(bt->rchild,x,h+1)    其餘狀況
 768 對應的遞歸程序以下:
 769 #include "Btree.cpp"                    //包含二叉樹的基本運算算法 
 770 int Level(BTNode *bt,int x,int h)        //求二叉樹 bt 中 x 結點的層次 
 771 {    //初始調用時:bt 爲根,h 爲 1 
 772      if (bt==NULL) return 0; 
 773      if (bt->data==x)                //找到 x 結點,返回 h 
 774           return h; 
 775      else 
 776      {    int l=Level(bt->lchild,x,h+1); //在左子樹中查找 
 777           if (l!=0)                    //在左子樹中找到,返回其層次 l 
 778                return l; 
 779           else 
 780                return Level(bt->rchild,x,h+1);//返回在右子樹的查找結果 
 781      } 
 782 } 
 783 void main() 
 784 {    BTNode *bt; 
 785      Int a[]={5,2,3,4,1,6}; 
 786      Int b[]={2,3,5,1,4,6}; 
 787      int n=sizeof(a)/sizeof(a[0]); 
 788      bt=CreateBTree(a,b,n);            //由 a 和 b 構造二叉鏈 bt 
 789      printf("二叉樹 bt:"); DispBTree(bt); printf("\n"); 
 790      int x=1; 
 791      printf("%d 結點的層次: %d\n",x,Level(bt,x,1)); 
 792      DestroyBTree(bt);                //銷燬樹 bt 
 793 } 
 794 上述程序的執行結果如圖 1.20 所示。
 7951.20 程序執行結果
 796  
 797 1.33 章─分治法
 798 1.3.1    練習題
 799 1.    分治法的設計思想是將一個難以直接解決的大問題分割成規模較小的子問題,分別解決子問題,最後將子問題的解組合起來造成原問題的解。這要求原問題和子問題
 800 ( )。
 801 A.問題規模相同,問題性質相同B.問題規模相同,問題性質不一樣C.問題規模不一樣,問題性質相同D.問題規模不一樣,問題性質不一樣
 802 2.    在尋找 n 個元素中第 k 小元素問題中,如快速排序算法思想,運用分治算法對 n
 803 個元素進行劃分,如何選擇劃分基準?下面( )答案解釋最合理。
 804 A.    隨機選擇一個元素做爲劃分基準
 805 B.    取子序列的第一個元素做爲劃分基準C.用中位數的中位數方法尋找劃分基準
 806 D.以上皆可行。但不一樣方法,算法複雜度上界可能不一樣
 807 3.    對於下列二分查找算法,如下正確的是( )。
 808 A.
 809 int binarySearch(int a[], int n, int x) 
 810 {    int low=0, high=n-1; 
 811      while(low<=high) 
 812      {    int mid=(low+high)/2; 
 813           if(x==a[mid]) return mid; 
 814           if(x>a[mid]) low=mid; 
 815                else high=mid; 
 816      } 
 817      return1; 
 818 } 
 819 B.
 820 int binarySearch(int a[], int n, int x) 
 821 {    int low=0, high=n-1; 
 822      while(low+1!=high) 
 823      {    int mid=(low+high)/2; 
 824           if(x>=a[mid]) low=mid; 
 825                else high=mid; 
 826      } 
 827      if(x==a[low]) return low; 
 828      else return1; 
 829 } 
 830 C.
 831 int binarySearch (int a[], int n, int x) 
 832 {    int low=0, high=n-1; 
 833      while(low<high-1) 
 834      {    int mid=(low+high)/2; 
 835  
 836 
 837           if(x<a[mid])  
 838                high=mid; 
 839  
 840       
 841 }     else low=mid; 
 842      if(x==a[low]) return low; 
 843      else return1; 
 844 } 
 845 D.
 846 int binarySearch(int a[], int n, int x) 
 847 {    if(n > 0 && x >= a[0]) 
 848      {    int low = 0, high = n-1; 
 849           while(low < high) 
 850           {    int mid=(low+high+1)/2; 
 851                if(x < a[mid])  
 852                     high=mid-1; 
 853                else low=mid; 
 854           } 
 855           if(x==a[low]) return low; 
 856      } 
 857      return1; 
 858 } 
 859 4.    快速排序算法是根據分治策略來設計的,簡述其基本思想。
 860 5.    假設含有 n 個元素的待排序的數據 a 剛好是遞減排列的,說明調用 QuickSort(a, 0,n-1)遞增排序的時間複雜度爲 O(n2)。
 861 6.    如下哪些算法採用分治策略:
 8621)    堆排序算法
 8632)    二路歸併排序算法
 8643)    折半查找算法
 8654)    順序查找算法
 866 7.    適合並行計算的問題一般表現出哪些特徵?
 867 8.    設有兩個複數 x=a+bi 和 y=c+di。複數乘積 xy 可使用 4 次乘法來完成,即
 868 xy=(ac-bd)+(ad+bc)i。設計一個僅用 3 次乘法來計算乘積 xy 的方法。
 869 9.    有 4 個數組 a、b、c 和 d,都已經排好序,說明找出這 4 個數組的交集的方法。
 870 10.    設計一個算法,採用分治法求一個整數序列中的最大最小元素。
 871 11.    設計一個算法,採用分治法求 xn。
 872 12.    假設二叉樹採用二叉鏈存儲結構進行存儲。設計一個算法採用分治法求一棵二叉樹 bt 的高度。
 873 13.    假設二叉樹採用二叉鏈存儲結構進行存儲。設計一個算法採用分治法求一棵二叉樹 bt 中度爲 2 的結點個數。
 874 14.    有一種二叉排序樹,其定義是空樹是一棵二叉排序樹,若不空,左子樹中全部結點值小於根結點值,右子樹中全部結點值大於根結點值,而且左右子樹都是二叉排序樹。如今該二叉排序樹採用二叉鏈存儲,採用分治法設計查找值爲 x 的結點地址,並分析算法的最好的平均時間複雜度。
 875  
 876 15.    設有 n 個互不相同的整數,按遞增順序存放在數組 a[0..n-1]中,若存在一個下標i(0≤i<n),使得 a[i]=i。設計一個算法以 O(log2n)時間找到這個下標 i。
 877 16.    請你模仿二分查找過程設計一個三分查找算法。分析其時間複雜度。
 878 17.    對於大於 1 的正整數 n,能夠分解爲 n=x1*x2*…*xm,其中 xi≥2。例如,n=12 時有 8 種 不 同 的 分 解 式 :12=1212=6*212=4*312=3*412=3*2*212=2*612=2*3*212=2*2*3,設計一個算法求 n 的不一樣分解式個數。
 879 18.    設計一個基於 BSP 模型的並行算法,假設有 p 臺處理器,計算整數數組 a[0..n-1]
 880 的全部元素之和。並分析算法的時間複雜度。
 881 1.3.2    練習題參考答案
 882 1.    答:C。
 883 2.    答:D。
 884 3.    答:以 a[]={12345}爲例說明。選項 A 中在查找 5 時出現死循環。選項 B
 885 中在查找 5 時返回-1。選項 C 中在查找 5 時返回-1。選項 D 正確。
 886 4.    答:對於無序序列 a[low..high]進行快速排序,整個排序爲「大問題」。選擇其中的一個基準 base=a[i](一般以序列中第一個元素爲基準),將全部小於等於 base 的元素移動到它的前面,全部大於等於 base 的元素移動到它的後面,即將基準歸位到 a[i],這樣產生a[low..i-1]和 a[i+1..high]兩個無序序列,它們的排序爲「小問題」。當 a[low..high]序列只有一個元素或者爲空時對應遞歸出口。
 887 因此快速排序算法就是採用分治策略,將一個「大問題」分解爲兩個「小問題」來求解。因爲元素都是在 a 數組中,其合併過程是天然產生的,不須要特別設計。
 888 5.    答:此時快速排序對應的遞歸樹高度爲 O(n),每一次劃分對應的時間爲 O(n),因此整個排序時間爲 O(n2)。
 889 6.    答:其中二路歸併排序和折半查找算法採用分治策略。
 890 7.    答:適合並行計算的問題一般表現出如下特徵:
 8911)    將工做分離成離散部分,有助於同時解決。例如,對於分治法設計的串行算法,能夠將各個獨立的子問題並行求解,最後合併成整個問題的解,從而轉化爲並行算法。
 8922)    隨時並及時地執行多個程序指令。
 8933)    多計算資源下解決問題的耗時要少於單個計算資源下的耗時。
 894 8.    答:xy=(ac-bd)+((a+b)(c+d)-ac-bd)i。因而可知,這樣計算 xy 只須要 3 次乘法(即
 895 ac、bd 和(a+b)(c+d)乘法運算)。
 896 9.    答:採用基本的二路歸併思路,先求出 a、b 的交集 ab,再求出 c、d 的交集 cd, 最後求出 ab 和 cd 的交集,即爲最後的結果。也能夠直接採用 4 路歸併方法求解。
 897 10.    解:採用相似求求一個整數序列中的最大次大元素的分治法思路。對應的程序以下:
 898 #include <stdio.h> 
 899 #define max(x,y) ((x)>(y)?(x):(y)) 
 900 #define min(x,y) ((x)<(y)?(x):(y)) 
 901  
 902 
 903 void MaxMin(int a[],int low,int high,int &maxe,int &mine) //求a中最大最小元素 
 904 {     if (low==high)                    //只有一個元素 
 905      {    maxe=a[low];             
 906           mine=a[low];             
 907      }             
 908      else if (low==high-1)        //只有兩個元素 
 909      {    maxe=max(a[low],a[high]); 
 910           mine=min(a[low],a[high]); 
 911      } 
 912      else                        //有兩個以上元素 
 913      {     int mid=(low+high)/2; 
 914           int lmaxe,lmine; 
 915           MaxMin(a,low,mid,lmaxe,lmine); 
 916           int rmaxe,rmine; 
 917           MaxMin(a,mid+1,high,rmaxe,rmine); 
 918           maxe=max(lmaxe,rmaxe); 
 919           mine=min(lmine,rmine); 
 920      }     
 921 }         
 922 void main() 
 923 {    int a[]={4,3,1,2,5}; 
 924      int n=sizeof(a)/sizeof(a[0]); 
 925      int maxe,mine; 
 926      MaxMin(a,0,n-1,maxe,mine); 
 927      printf("Max=%d, Min=%d\n",maxe,mine); 
 928 } 
 929 上述程序的執行結果如圖 1.21 所示。
 9301.21 程序執行結果
 931 11.    解:設 f(x,n)=xn,採用分治法求解對應的遞歸模型以下:
 932 
 933 f(x,n)=x        當 n=1
 934 f(x,n)=f(x,n/2)*f(x,n/2)        當 n 爲偶數時
 935 f(x,n)=f(x,(n-1)/2)*f(x,(n-1)/2)*x        當 n 爲奇數時
 936 對應的遞歸程序以下:        
 937 #include <stdio.h> 
 938 double solve(double x,int n)     
 939      
 940 //求x^n 
 941 {    double fv; 
 942      if (n==1) return x; 
 943      if (n%2==0) 
 944      {    fv=solve(x,n/2); 
 945           return fv*fv; 
 946      } 
 947  
 948      else 
 949      {    fv=solve(x,(n-1)/2); 
 950           return fv*fv*x; 
 951      } 
 952 } 
 953 void main() 
 954 {    double x=2.0; 
 955      printf("求解結果:\n"); 
 956      for (int i=1;i<=10;i++) 
 957           printf(" %g^%d=%g\n",x,i,solve(x,i)); 
 958 } 
 959 上述程序的執行結果如圖 1.22 所示。
 9601.22 程序執行結果
 961 12.    解:設 f(bt)返回二叉樹 bt 的高度,對應的遞歸模型以下:
 962 
 963 f(bt)=0
 964 f(bt)=MAX{f(bt->lchild),f(bt->rchild)}+1
 965 對應的程序以下:
 966 #include "Btree.cpp"                    
 967 
 968      當 bt=NULL
 969 其餘狀況
 970 
 971 //包含二叉樹的基本運算算法 
 972 int Height(BTNode *bt)            
 973 {    if (bt==NULL) return 0; 
 974      int lh=Height(bt->lchild);        
 975      //求二叉樹bt的高度 
 976 //子問題1 
 977      int rh=Height(bt->rchild);            //子問題2 
 978      if (lh>rh) return lh+1;      
 979      else return rh+1; 
 980 } 
 981 void main()          //合併 
 982 { 
 983  
 984  
 985  
 986      BTNode *bt; 
 987 Int a[]={5,2,3,4,1,6}; 
 988 Int b[]={2,3,5,1,4,6}; 
 989 int n=sizeof(a)/sizeof(a[0]); 
 990 bt=CreateBTree(a,b,n);            
 991 
 992      
 993 
 994 //由a和b構造二叉鏈bt 
 995      printf("二叉樹bt:"); DispBTree(bt); printf("\n"); 
 996      printf("bt的高度: %d\n",Height(bt)); 
 997  
 998 }     DestroyBTree(bt);                //銷燬樹bt 
 999  
1000 
1001 上述程序的執行結果如圖 1.23 所示。
10021.23 程序執行結果
1003 13.    解:設 f(bt)返回二叉樹 bt 中度爲 2 的結點個數,對應的遞歸模型以下:
1004 
1005 f(bt)=0            當 bt=NULL
1006 f(bt)=f(bt->lchild)+f(bt->rchild)+1            若 bt≠NULL 且 bt 爲雙分支結點
1007 f(bt)=f(bt->lchild)+f(bt->rchild)            其餘狀況
1008 對應的算法以下:            
1009 #include "Btree.cpp"                    //包含二叉樹的基本運算算法 
1010 int Nodes(BTNode *bt)                 //求 bt 中度爲 2 的結點個數 
1011 {    int n=0; 
1012      if (bt==NULL) return 0; 
1013      if (bt->lchild!=NULL && bt->rchild!=NULL) 
1014           n=1; 
1015      return Nodes(bt->lchild)+Nodes(bt->rchild)+n; 
1016 } 
1017 void main() 
1018 {     BTNode *bt;     
1019      Int a[]={5,2,3,4,1,6};     
1020      Int b[]={2,3,5,1,4,6};     
1021      int n=sizeof(a)/sizeof(a[0]);     
1022      bt=CreateBTree(a,b,n);            //由a和b構造二叉鏈bt 
1023      printf("二叉樹bt:"); DispBTree(bt); printf("\n"); 
1024      printf("bt中度爲2的結點個數: %d\n",Nodes(bt)); 
1025      DestroyBTree(bt);            //銷燬樹bt 
1026 } 
1027 上述程序的執行結果如圖 1.24 所示。
10281.24 程序執行結果
1029 14.    解:設 f(bt,x)返回在二叉排序樹 bt 獲得的值爲 x 結點的地址,若沒有找到返回空,對應的遞歸模型以下:
1030 f(bt,x)=NULL    當 bt=NULL
1031 f(bt,x)=bt    當 bt≠NULL 且 x=bt->data f(bt,x)=f(bt->lchild,x)    當 x>bt->data
1032  
1033 f(bt,x)=f(bt->rchild,x)    當 x<bt->data
1034 對應的程序以下:
1035 #include "Btree.cpp"                //包含二叉樹的基本運算算法 
1036 BTNode *Search(BTNode *bt,Int x)    //在二叉排序樹 bt 查找的值爲 x 結點 
1037 {    if (bt==NULL) return NULL; 
1038      if (x==bt->data) return bt; 
1039      if (x<bt->data) return Search(bt->lchild,x); 
1040      else return Search(bt->rchild,x); 
1041 } 
1042 void main() 
1043 {    BTNode *bt; 
1044      Int a[]={4,3,2,8,6,7,9}; 
1045      Int b[]={2,3,4,6,7,8,9}; 
1046      int n=sizeof(a)/sizeof(a[0]); 
1047      bt=CreateBTree(a,b,n);        //構造一棵二叉排序樹 bt 
1048      printf("二叉排序樹 bt:"); DispBTree(bt); printf("\n"); 
1049      int x=6; 
1050      BTNode *p=Search(bt,x); 
1051      if (p!=NULL) 
1052           printf("找到結點: %d\n",p->data); 
1053      else 
1054           printf("沒有找到結點\n",x); 
1055      DestroyBTree(bt);            //銷燬樹 bt 
1056 } 
1057 上述程序的執行結果如圖 1.25 所示。
10581.25 程序執行結果
1059 Search(bt,x)算法採用的是減治法,最好的狀況是某個結點左右子樹高度大體相同, 其平均執行時間 T(n)以下:
1060 T(n)=1    當 n=1 T(n)=T(n/2)+1    當 n>1
1061 能夠推出 T(n)=O(log2n),其中 n 爲二叉排序樹的結點個數。
1062 15.    解:採用二分查找方法。a[i]=i 時表示該元素在有序非重複序列 a 中剛好第 i 大。對於序列 a[low..high],mid=(low+high)/2,若 a[mid]=mid 表示找到該元素;若 a[mid]>mid 說明右區間的全部元素都大於其位置,只能在左區間中查找;若 a[mid]<mid 說明左區間的全部元素都小於其位置,只能在右區間中查找。對應的程序以下:
1063 #include <stdio.h> 
1064 int Search(int a[],int n)    //查找使得 a[i]=i 
1065 {    int low=0,high=n-1,mid; 
1066  
1067 
1068      while (low<=high)     
1069      {    mid=(low+high)/2;     
1070           if (a[mid]==mid)        //查找到這樣的元素 
1071                return mid;     
1072           else if (a[mid]<mid)     //這樣的元素只能在右區間中出現 
1073                low=mid+1;     
1074           else                     //這樣的元素只能在左區間中出現 
1075                high=mid-1;     
1076      }     
1077      return -1;     
1078 }         
1079 void main() 
1080 {    int a[]={-2,-1,2,4,6,8,9}; 
1081      int n=sizeof(a)/sizeof(a[0]); 
1082      int i=Search(a,n); 
1083      printf("求解結果\n"); 
1084      if (i!=-1) 
1085           printf(" 存在a[%d]=%d\n",i,i); 
1086      else 
1087           printf(" 不存在\n"); 
1088 } 
1089 上述程序的執行結果如圖 1.26 所示。
10901.26 程序執行結果
1091 16.    解:對於有序序列 a[low..high],若元素個數少於 3 個,直接查找。若含有更多的元素,將其分爲 a[low..mid1-1]、a[mid1+1..mid2-1]、a[mid2+1..high]子序列,對每一個子序列遞歸查找,算法的時間複雜度爲 O(log3n),屬於 O(log2n)級別。對應的算法以下:
1092 #include <stdio.h> 
1093 int Search(int a[],int low,int high,int x)    //三分查找 
1094  
1095                return -1; 
1096      } 
1097      int length=(high-low+1)/3;            //每一個子序列的長度 
1098      int mid1=low+length; 
1099      int mid2=high-length; 
1100      if (x==a[mid1]) 
1101           return mid1; 
1102      else if (x<a[mid1]) 
1103           return Search(a,low,mid1-1,x); 
1104      else if (x==a[mid2]) 
1105           return mid2; 
1106      else if (x<a[mid2]) 
1107           return Search(a,mid1+1,mid2-1,x); 
1108      else 
1109           return Search(a,mid2+1,high,x); 
1110 } 
1111 void main() 
1112 {    int a[]={1,3,5,7,9,11,13,15}; 
1113      int n=sizeof(a)/sizeof(a[0]); 
1114      printf("求解結果\n"); 
1115      int x=13; 
1116      int i=Search(a,0,n-1,x); 
1117      if (i!=-1) 
1118           printf(" a[%d]=%d\n",i,x); 
1119      else 
1120           printf(" 不存在%d\n",x); 
1121      int y=10; 
1122      int j=Search(a,0,n-1,y); 
1123      if (j!=-1) 
1124           printf(" a[%d]=%d\n",j,y); 
1125      else 
1126           printf(" 不存在%d\n",y); 
1127 } 
1128 上述程序的執行結果如圖 1.27 所示。
11291.27 程序執行結果
1130 17.    解:設 f(n)表示 n 的不一樣分解式個數。有: f(1)=1,做爲遞歸出口
1131 f(2)=1,分解式爲:2=2
1132 f(3)=1, 分 解 式 爲 :3=3 f(4)=2,分解式爲:4=44=2*2
1133  
1134 
1135 f(6)=3,分解式爲:6=66=2*36=3*2,即 f(6)=f(1)+f(2)+f(3)
1136 以此類推,能夠看出 f(n)爲 n 的全部因數的不一樣分解式個數之和,即 f(n)=
1137 ∑    𝑓(𝑛/𝑖)。對應的程序以下:
1138 #include <stdio.h> #define MAX 101 
1139 int solve(int n)        //求 n 的不一樣分解式個數 
1140 {    if (n==1) return 1; 
1141      else 
1142      {    int sum=0; 
1143           for (int i=2;i<=n;i++) 
1144                if (n%i==0) 
1145                     sum+=solve(n/i); 
1146                return sum; 
1147      } 
1148 } 
1149 void main() 
1150 {    int n=12; 
1151      int ans=solve(n); 
1152      printf("結果: %d\n",ans); 
1153 } 
1154 上述程序的執行結果如圖 1.28 所示。
11551.28 程序執行結果
1156 18.    解:對應的並行算法以下:
1157 int Sum(int a[],int s,int t,int p,int i) //處理器i執行求和 
1158 {    int j,s=0; 
1159      for (j=s;j<=t;j++) 
1160           s+=a[j]; 
1161      return s; 
1162 } 
1163 int ParaSum(int a[],int s,int t,int p,int i) 
1164 {    int sum=0,j,k=0,sj; 
1165      for (j=0;j<p;j++)                //for循環的各個子問題並行執行 
1166      {    sj=Sum(a,k,k+n/p-1,p,j); 
1167           k+=n/p; 
1168      } 
1169      sum+=sj; 
1170      return sum; 
1171 } 
1172 每一個處理器的執行時間爲O(n/p),同步開銷爲O(p),因此該算法的時間複雜度爲
1173 O(n/p+p)。
1174  
1175 1.44 章─蠻力法
1176 1.4.1    練習題
1177 1.    簡要比較蠻力法和分治法。
1178 2.    在採用蠻力法求解時什麼狀況下使用遞歸?
1179 3.    考慮下面這個算法,它求的是數組 a 中大小相差最小的兩個元素的差。請對這個算法作儘量多的改進。
1180 #define INF 99999 
1181 #define abs(x) (x)<0?-(x):(x)        //求絕對值宏 
1182 int Mindif(int a[],int n) 
1183 {    int dmin=INF; 
1184      for (int i=0;i<=n-2;i++) 
1185           for (int j=i+1;j<=n-1;j++) 
1186           {    int temp=abs(a[i]-a[j]); 
1187                if (temp<dmin) 
1188                     dmin=temp; 
1189           } 
1190      return dmin; 
1191 } 
1192 4.    給定一個整數數組 A=(a0,a1,…an-1),若 i<j 且 ai>aj,則<ai,aj>就爲一個逆序對。例如數組(31452)的逆序對有<31>,<32>,<42>,<52>。設計一個算法採用蠻力法求 A 中逆序對的個數即逆序數。
1193 5.    對於給定的正整數 n(n>1), 採用蠻力法求 1!+2!+…+n!,並改進該算法提升效率。
1194 6.    有一羣雞和一羣兔,它們的只數相同,它們的腳數都是三位數,且這兩個三位數的各位數字只能是 012345。設計一個算法用蠻力法求雞和兔的只數各是多
1195 少?它們的腳數各是多少?
1196 7.    有一個三位數,個位數字比百位數字大,而百位數字又比十位數字大,而且各位數字之和等於各位數字相乘之積,設計一個算法用窮舉法求此三位數。
1197 8.    某年級的同窗集體去公園划船,若是每隻船坐 10 人,那麼多出 2 個座位;若是每隻船多坐 2 人,那麼可少租 1 只船,設計一個算法用蠻力法求該年級的最多人數?
1198 9.    已知:若一個合數的質因數分解式逐位相加之和等於其自己逐位相加之和,則稱這 個 數 爲 Smith 數 。 如 4937775=3*5*5*65837, 而 3+5+5+6+5+8+3+7=424+9+3+7+7+7+5=42,因此 4937775 是 Smith 數。求給定一個正整數 N,求大於 N 的最小Smith 數。
1199 輸入:若干個 case,每一個 case 一行表明正整數 N,輸入 0 表示結束輸出:大於 N 的最小 Smith 數
1200 輸入樣例:
1201 4937774
1202 0
1203 樣例輸出:
1204  
1205 
1206 4937775
1207 10.    求解塗棋盤問題。小易有一塊 n*n 的棋盤,棋盤的每個格子都爲黑色或者白色,小易如今要用他喜歡的紅色去塗畫棋盤。小易會找出棋盤中某一列中擁有相同顏色的最大的區域去塗畫,幫助小易算算他會塗畫多少個棋格。
1208 輸入描述:輸入數據包括 n+1 行:第一行爲一個整數 n(1≤    n≤50),即棋盤的大小,接下來的 n 行每行一個字符串表示第 i 行棋盤的顏色,'W'表示白色,'B'表示黑色。
1209 輸出描述:輸出小易會塗畫的區域大小。輸入例子:
1210 3
1211 BWW BBB BWB
1212 輸出例子:
1213 3
1214 11.    給定一個含 n(n>1)個整數元素的 a,全部元素不相同,採用蠻力法求出 a 中全部元素的全排列。
1215 1.4.2    練習題參考答案
1216 1.    答:蠻力法是一種簡單直接地解決問題的方法,適用範圍廣,是能解決幾乎全部問題的通常性方法,經常使用於一些很是基本、但又十分重要的算法(排序、查找、矩陣乘法和字符串匹配等),蠻力法主要解決一些規模小或價值低的問題,能夠做爲一樣問題的更高效算法的一個標準。而分治法採用分而治之思路,把一個複雜的問題分紅兩個或更多的相同或類似的子問題,再把子問題分紅更小的子問題直到問題解決。分治法在求解問題 時,一般性能比蠻力法好。
1217 2.    答:若是用蠻力法求解的問題能夠分解爲若干個規模較小的類似子問題,此時能夠採用遞歸來實現算法。
1218 3.    解:上述算法的時間複雜度爲 O(n2),採用的是最基本的蠻力法。能夠先對 a 中元素遞增排序,而後依次比較相鄰元素的差,求出最小差,改進後的算法以下:
1219 #include <stdio.h> #include <algorithm> using namespace std; 
1220 int Mindif1(int a[],int n) 
1221 {    sort(a,a+n);            //遞增排序 
1222      int dmin=a[1]-a[0]; 
1223      for (int i=2;i<n;i++) 
1224      {    int temp=a[i]-a[i-1]; 
1225           if (temp<dmin) 
1226                dmin=temp; 
1227      } 
1228      return dmin; 
1229 } 
1230  
1231 上述算法的主要時間花費在排序上,算法的時間複雜度爲 O(nlog2n)。
1232 4.    解:採用兩重循環直接判斷是否爲逆序對,算法的時間複雜度爲 O(n2),比第 3 章實驗 3 算法的性能差。對應的算法以下:
1233 int solve(int a[],int n)        //求逆序數 
1234 {    int ans=0; 
1235      for (int i=0;i<n-1;i++) 
1236           for (int j=i+1;j<n;j++) 
1237                if (a[i]>a[j]) 
1238                     ans++; 
1239      return ans; 
1240 } 
1241 5.    解:直接採用蠻力法求解算法以下:
1242 long f(int n)                //求n! 
1243 {    long fn=1; 
1244      for (int i=2;i<=n;i++) 
1245           fn=fn*i; 
1246      return fn; 
1247 } 
1248 long solve(int n)            //求1!+2!+…+n! 
1249 {    long ans=0; 
1250      for (int i=1;i<=n;i++) 
1251           ans+=f(i); 
1252      return ans; 
1253 } 
1254 實際上,f(n)=f(n-1)*n,f(1)=1,在求 f(n)時能夠利用 f(n-1)的結果。改進後的算法如
1255 下:
1256 long solve1(int n)            //求1!+2!+…+n! 
1257 {    long ans=0; 
1258      long fn=1; 
1259      for (int i=1;i<=n;i++) 
1260      {    fn=fn*i; 
1261           ans+=fn; 
1262      } 
1263      return ans; 
1264 } 
1265 6.    解:設雞腳數爲 y=abc,兔腳數爲 z=def,有 1≤a,d≤50≤b,c,e,f≤5,採
12666 重循環,求出雞隻數 x1=y/2(y 是 2 的倍數),兔只數 x2=z/4(z 是 4 的倍數),當
1267 x1=x2 時輸出結果。對應的程序以下:
1268 #include <stdio.h> 
1269 void solve() 
1270 {    int a,b,c,d,e,f; 
1271      int x1,x2,y,z; 
1272      for (a=1;a<=5;a++) 
1273           for (b=0;b<=5;b++) 
1274                for (c=0;c<=5;c++) 
1275  
1276 
1277                     for (d=1;d<=5;d++)     
1278                          for (e=0;e<=5;e++)     
1279                               for (f=0;f<=5;f++)     
1280                               {    y=a*100+b*10+c;          //雞腳數 
1281                                    z=d*100+e*10+f;          //兔腳數 
1282                                    if (y%2!=0 || z%4!=0)     
1283                                         continue;     
1284                                    x1=y/2;            //雞隻數 
1285                                    x2=z/4;            //兔只數 
1286                                    if (x1==x2) 
1287                                         printf(" 雞隻數:%d,兔只數:%d,雞腳數:%d, 
1288                                                   兔腳數:%d\n",x1,x2,y,z); 
1289                               }     
1290 }                             
1291 void main() 
1292 {    printf("求解結果\n"); 
1293      solve(); 
1294 } 
1295 上述程序的執行結果如圖 1.29 所示。
12961.29  程序執行結果
1297 7.    解:設該三位數爲 x=abc,有 1≤a≤90≤b,c≤9,知足 c>a,a>b, a+b+c=a*b*c。對應的程序以下:
1298 #include <stdio.h> 
1299 void solve() 
1300 {    int a,b,c; 
1301      for (a=1;a<=9;a++) 
1302           for (b=0;b<=9;b++) 
1303                for (c=0;c<=9;c++) 
1304                {    if (c>a && a>b && a+b+c==a*b*c) 
1305                          printf("  %d%d%d\n",a,b,c); 
1306                } 
1307 } 
1308 void main() 
1309  
1310 {    printf("求解結果\n"); 
1311      solve(); 
1312 } 
1313 上述程序的執行結果如圖 1.30 所示。
13141.30 程序執行結果
1315 8.    解:設該年級的人數爲 x,租船數爲 y。由於每隻船坐 10 人正好多出 2 個座位,則x=10*y-2;由於每隻船多坐 2 人即 12 人時可少租 1 只船(沒有說剛好所有座位佔滿),有 x+z=12*(y-1),z 表示此時空出的座位,顯然 z<12。讓 y 從 1100(實際上 y 取更大範圍的結果是相同的)、z 從 011 枚舉,求出最大的 x 便可。對應的程序以下:
1316 #include <stdio.h> 
1317 int solve() 
1318 {    int x,y,z; 
1319      for (y=1;y<=100;y++) 
1320           for (z=0;z<12;z++) 
1321                if (10*y-2==12*(y-1)-z) 
1322                     x=10*y-2; 
1323      return x; 
1324 } 
1325 void main() 
1326 {    printf("求解結果\n"); 
1327      printf(" 最多人數:%d\n",solve()); 
1328 } 
1329 上述程序的執行結果如圖 1.31 所示。
13301.31 程序執行結果
1331 9.    解:採用蠻力法求出一個正整數 n 的各位數字和 sum1,以及 n 的全部質因數的數字和 sum2,若 sum1=sum2,即爲 Smitch 數。從用戶輸入的 n 開始枚舉,如果 Smitch
1332 數,輸出,本次結束,不然 n++繼續查找大於 n 的最小 Smitch 數。對應的完整程序以下:
1333 #include <stdio.h> 
1334 int Sum(int n)        //求n的各位數字和 
1335 {    int sum=0; 
1336      while (n>0) 
1337  
1338 
1339      {    sum+=n%10; 
1340           n=n/10; 
1341      } 
1342      return sum; 
1343 } 
1344 bool solve(int n)    //判斷n是否爲Smitch數 
1345 {    int m=2; 
1346      int sum1=Sum(n); 
1347      int sum2=0; 
1348      while (n>=m) 
1349      {    if (n%m==0) //找到一個質因數m 
1350           {    n=n/m; 
1351                sum2+=Sum(m); 
1352           } 
1353           else 
1354                m++; 
1355      } 
1356      if (sum1==sum2) 
1357           return true; 
1358      else 
1359           return false; 
1360 } 
1361 void main() 
1362 {    int n; 
1363      while (true) 
1364      {    scanf("%d",&n); 
1365           if (n==0) break; 
1366           while (!solve(n)) 
1367                n++; 
1368           printf("%d\n",n); 
1369      } 
1370 } 
1371 10.    解:採用蠻力法,統計每一列相鄰相同顏色的棋格個數 countj,在 countj 中求最大值。對應的程序以下:
1372 #include <stdio.h> #define MAXN 51 
1373 //問題表示int n; 
1374 char board[MAXN][MAXN]; 
1375 int getMaxArea()                //蠻力法求解算法 
1376 {    int maxArea=0; 
1377      for (int j=0; j<n; j++) 
1378      {    int countj=1; 
1379           for (int i=1; i<n; i++)    //統計第j列中相同顏色相鄰棋格個數 
1380           {    if (board[i][j]==board[i-1][j]) 
1381                     countj++; 
1382                else 
1383                     countj=1; 
1384           } 
1385  
1386           if (countj>maxArea) 
1387                maxArea=countj; 
1388      } 
1389      return maxArea; 
1390 } 
1391 int main() 
1392 {    scanf("%d",&n); 
1393      for (int i=0;i<n;i++) 
1394           scanf("%s",board[i]); 
1395      printf("%d\n",getMaxArea()); 
1396      return 0; 
1397 } 
1398 11.    解:與《教程》中求全排列相似,但須要將求 1~n 的全排列改成按下標 0~n-1
1399 求 a 的全排列(下標從 0 開始)。採用非遞歸的程序以下:
1400 #include <stdio.h> #include <vector> using namespace std; 
1401 vector<vector<int> > ps;                        //存放全排列 
1402 void Insert(vector<int> s,int a[],int i,vector<vector<int> > &ps1) 
1403 //在每一個集合元素中間插入i獲得ps1 
1404 {    vector<int> s1; 
1405      vector<int>::iterator it;     
1406      for (int j=0;j<=i;j++)                           //在s(含i個整數)的每一個位置插入a[i] 
1407      {    s1=s;             
1408           it=s1.begin()+j;                            //求出插入位置 
1409           s1.insert(it,a[i]);                         //插入整數a[i] 
1410           ps1.push_back(s1);                          //添加到ps1中 
1411      }             
1412 }             
1413 void Perm(int a[],int n)                              //求a[0..n-1]的全部全排列 
1414 {    vector<vector<int> > ps1;                       //臨時存放子排列 
1415      vector<vector<int> >::iterator it;               //全排列迭代器 
1416      vector<int> s,s1;             
1417      s.push_back(a[0]);             
1418      ps.push_back(s);                                 //添加{a[0]}集合元素 
1419      for (int i=1;i<n;i++)                              //循環添加a[1]~a[n-1] 
1420      {    ps1.clear();                                //ps1存放插入a[i]的結果 
1421           for (it=ps.begin();it!=ps.end();++it)     
1422                Insert(*it,a,i,ps1);                    //在每一個集合元素中間插入a[i]獲得ps1 
1423           ps=ps1;     
1424      }         
1425 }             
1426 void dispps()                                //輸出全排列 ps 
1427 {    vector<vector<int> >::reverse_iterator it;    //全排列的反向迭代器 
1428      vector<int>::iterator sit;                //排列集合元素迭代器 
1429      for (it=ps.rbegin();it!=ps.rend();++it) 
1430      {    for (sit=(*it).begin();sit!=(*it).end();++sit) 
1431                printf("%d",*sit); 
1432           printf(" "); 
1433  
1434 
1435      } 
1436      printf("\n"); 
1437 } 
1438 void main() 
1439 {    int a[]={2,5,8}; 
1440      int n=sizeof(a)/sizeof(a[0]); 
1441      printf("a[0~%d]的全排序以下:\n ",n-1); 
1442      Perm(a,n); 
1443      dispps(); 
1444 } 
1445 上述程序的執行結果如圖 1.32 所示。
14461.32  程序執行結果
1447 1.55 章─回溯法
1448 1.5.1    練習題
1449 1.    回溯法在問題的解空間樹中,按( )策略,從根結點出發搜索解空間樹。
1450 A.    廣度優先    B.活結點優先    C.擴展結點優先    D.深度優先
1451 2.    關於回溯法如下敘述中不正確的是( )。
1452 A.回溯法有「通用解題法」之稱,它能夠系統地搜索一個問題的全部解或任意解 B.回溯法是一種既帶系統性又帶有跳躍性的搜索算法
1453 C.回溯算法須要藉助隊列這種結構來保存從根結點到當前擴展結點的路徑
1454 D.回溯算法在生成解空間的任一結點時,先判斷該結點是否可能包含問題的解,若是確定不包含,則跳過對該結點爲根的子樹的搜索,逐層向祖先結點回溯
1455 3.    回溯法的效率不依賴於下列哪些因素( )。
1456 A.    肯定解空間的時間    B.知足顯約束的值的個數
1457 C.計算約束函數的時間    D.計算限界函數的時間
1458 4.    下面( )函數是回溯法中爲避免無效搜索採起的策略。
1459 A.遞歸函數    B.剪枝函數    C.隨機數函數    D.搜索函數5.回溯法的搜索特色是什麼?
1460 6.    用回溯法解 0/1 揹包問題時,該問題的解空間是何種結構?用回溯法解流水做業調度問題時,該問題的解空間是何種結構?
1461 7.    對於遞增序列 a[]={12345},採用例 5.4 的回溯法求全排列,以 12 開頭的排列必定最早出現嗎?爲何?
1462 8.    考慮 n 皇后問題,其解空間樹爲由 12、…、n 構成的 n!種排列所組成。現用回
1463  
1464 溯法求解,要求:
14651)    經過解搜索空間說明 n=3 時是無解的。
14662)    給出剪枝操做。
14673)    最壞狀況下在解空間樹上會生成多少個結點?分析算法的時間複雜度。
1468 9.    設計一個算法求解簡單裝載問題,設有一批集裝箱要裝上一艘載重量爲 W 的輪船,其中編號爲 i(0≤i≤n-1)的集裝箱的重量爲 wi。現要從 n 個集裝箱中選出若干裝上輪船,使它們的重量之和正好爲 W。若是找到任一種解返回 true,不然返回 false1469 10.    給定若干個正整數a0、a0 、…、an-1 ,從中選出若干數,使它們的和剛好爲k, 要求找選擇元素個數最少的解。
1470 11.    設計求解有重複元素的排列問題的算法,設有 n 個元素 a[]={a0,a1,…,an-1), 其中可能含有重複的元素,求這些元素的全部不一樣排列。如 a[]={112},輸出結果是
1471112),(121),(211)。
1472 12.    採用遞歸回溯法設計一個算法求1~n的n個整數中取出m個元素的排列,要求每一個元素最多隻能取一次。例如,n=3,m=2的輸出結果是(12),(13),(21),
147323),(31),(32)。
1474 13.    對於n皇后問題,有人認爲當n爲偶數時,其解具備對稱性,即n皇后問題的解個數剛好爲n/2皇后問題的解個數的2倍,這個結論正確嗎?請編寫回溯法程序對n=461475 8、10的狀況進行驗證。
1476 14.    給定一個無向圖,由指定的起點前往指定的終點,途中通過全部其餘頂點且只通過一次,稱爲哈密頓路徑,閉合的哈密頓路徑稱做哈密頓迴路(Hamiltonian  cycle)。設計一個回溯算法求無向圖的全部哈密頓迴路。
1477 1.5.2    練習題參考答案
1478 1.    答:D。
1479 2.    答:回溯算法是採用深度優先遍歷的,須要藉助系統棧結構來保存從根結點到當前擴展結點的路徑。答案爲 C。
1480 3.    答:回溯法解空間是虛擬的,沒必要肯定整個解空間。答案爲 A。
1481 4.    答:B。
1482 5.    答:回溯法在解空間樹中採用深度優先遍歷方式進行解搜索,即用約束條件和限界函數考察解向量元素 x[i]的取值,若是 x[i]是合理的就搜索 x[i]爲根結點的子樹,若是x[i]取完了全部的值,便回溯到 x[i-1]。
1483 6.    答:用回溯法解 0/1 揹包問題時,該問題的解空間是子集樹結構。用回溯法解流水做業調度問題時,該問題的解空間是排列樹結構。
1484 7.    答:是的。對應的解空間是一棵排列樹,如圖 1.33 所示給出前面 3 層部分,顯然最早產生的排列是從 G 結點擴展出來的葉子結點,它們就是以 12 開頭的排列。
1485  
1486 
1487  
1488 
14891.33 部分解空間樹
1490 8.    答:(1)n=3 時的解搜索空間如圖 1.34 所示,不能獲得任何葉子結點,全部無解。
14912)    剪枝操做是任何兩個皇后不能同行、同列和同兩條對角線。
14923)    最壞狀況下每一個結點擴展 n 個結點,共有 nn 個結點,算法的時間複雜度爲
1493 O(nn)。
1494 
1495 
1496 
14971.34 3 皇后問題的解搜索空間
1498 9.    解:用數組 w[0..n-1]存放 n 個集裝箱的重量,採用相似判斷子集和是否存在解的方法求解。對應完整的求解程序以下:
1499 #include <stdio.h> 
1500 #define MAXN 20                         //最多集裝箱個數 
1501 //問題表示             
1502 int n=5,W;             
1503 int w[]={2,9,5,6,3};             
1504 int count;                              //全局變量,累計解個數 
1505 void dfs(int tw,int rw,int i)        //求解簡單裝載問題 
1506 {    if (i>=n)                    //找到一個葉子結點 
1507 
1508 
1509 
1510 
1511 
1512 
1513 
1514 bool solve()                    //判斷簡單裝載問題是否存在解 
1515  
1516 
1517 { 
1518  
1519  
1520      count=0; 
1521 int rw=0; 
1522 for (int j=0;j<n;j++)   
1523      rw+=w[j];     
1524      
1525 //求全部集裝箱重量和rw 
1526      dfs(0,rw,0);                      //i從0開始 
1527      if (count>0)         
1528           return true;         
1529      else         
1530           return false;         
1531 }             
1532 void main() 
1533 {    printf("求解結果\n"); 
1534      W=4; 
1535      printf(" W=%d時%s\n",W,(solve()?"存在解":"沒有解")); 
1536      W=10; 
1537      printf(" W=%d時%s\n",W,(solve()?"存在解":"沒有解")); 
1538      W=12; 
1539      printf(" W=%d時%s\n",W,(solve()?"存在解":"沒有解")); 
1540      W=21; 
1541      printf(" W=%d時%s\n",W,(solve()?"存在解":"沒有解")); 
1542 } 
1543 本程序執行結果如圖 1.35 所示。
15441.35 程序執行結果
1545 10.    解:這是一個典型的解空間爲子集樹的問題,採用子集樹的回溯算法框架。當找到一個解後經過選取的元素個數進行比較求最優解 minpath。對應的完整程序以下:
1546 #include <stdio.h> #include <vector> using namespace std; 
1547 //問題表示 
1548 int a[]={1,2,3,4,5};                    //設置爲全局變量 
1549 int n=5,k=9;   
1550 vector<int> minpath;                    //存放最優解 
1551 //求解結果表示 
1552 int minn=n;                            //最多選擇n個元素 
1553 void disppath()                         //輸出一個解 
1554 {    printf(" 選擇的元素:"); 
1555      for (int j=0;j<minpath.size();j++) 
1556           printf("%d ",minpath[j]); 
1557      printf("元素個數=%d\n",minn); 
1558 } 
1559  
1560 
1561 void dfs(vector<int> path,int sum,int start) //求解算法 
1562 {    if (sum==k)                //若是找到一個解,不必定到葉子結點 
1563      {    if (path.size()<minn) 
1564           {    minn=path.size(); 
1565                minpath=path; 
1566           } 
1567           return; 
1568      } 
1569      if (start>=n) return;        //所有元素找完,返回 
1570      dfs(path,sum,start+1);        //不選擇a[start] 
1571      path.push_back(a[start]);    //選擇a[start] 
1572      dfs(path,sum+a[start],start+1); 
1573 } 
1574 void main() 
1575 {     vector<int> path;                 //path存放一個子集 
1576      dfs(path,0,0);         
1577      printf("最優解:\n");         
1578      disppath();         
1579 }             
1580 上述程序的執行結果如圖 1.36 所示。
15811.36    程序執行結果
1582 11.    解:在回溯法求全排列的基礎上,增長元素的重複性判斷。例如,對於 a[]={11583 12},不判斷重複性時輸出(112),(121),(112),(121),
1584211),(211),共 6 個,有 3 個是重複的。重複性判斷是這樣的,對於在擴展 a[i]時,僅僅將與 a[i..j-1]沒有出現的元素 a[j]交換到 a[i]的位置,若是出現,對應的排列已經在前面求出了。對應的完整程序以下:
1585 #include <stdio.h> 
1586 bool ok(int a[],int i,int j)    //ok用於判別重複元素 
1587 {    if (j>i) 
1588      {    for(int k=i;k<j;k++) 
1589                if (a[k]==a[j]) 
1590                     return false; 
1591      } 
1592      return true; 
1593 } 
1594 void swap(int &x,int &y)        //交換兩個元素 
1595 {    int tmp=x; 
1596      x=y; y=tmp; 
1597 } 
1598 void dfs(int a[],int n,int i)    //求有重複元素的排列問題 
1599 {    if (i==n) 
1600  
1601 
1602  
1603  
1604  
1605      { 
1606  
1607  
1608 }     for(int j=0;j<n;j++) 
1609      printf("%3d",a[j]); printf("\n"); 
1610      else     
1611      {     for (int j=i;j<n;j++) 
1612                if (ok(a,i,j))    //選取與a[i..j-1]不重複的元素a[j] 
1613                {    swap(a[i],a[j]); 
1614                     dfs(a,n,i+1); 
1615                     swap(a[i],a[j]); 
1616                } 
1617      }     
1618 }         
1619 void main() 
1620 {    int a[]={1,2,1,2}; 
1621      int n=sizeof(a)/sizeof(a[0]); 
1622      printf("序列("); 
1623      for (int i=0;i<n-1;i++) 
1624           printf("%d ",a[i]); 
1625      printf("%d)的全部不一樣排列:\n",a[n-1]); 
1626      dfs(a,n,0); 
1627 } 
1628 上述程序的執行結果如圖 1.37 所示。
16291.37 程序執行結果
1630 12.    解:採用求全排列的遞歸框架。選取的元素個數用 i 表示(i 從 1 開始),當 i>m時達到一個葉子結點,輸出一個排列。爲了不重複,用 used 數組實現,used[i]=0 表示沒有選擇整數 i,used[i]=1 表示已經選擇整數 i。對應的完整程序以下:
1631 #include <stdio.h> #include <string.h> #define MAXN 20 #define MAXM 10 
1632 int m,n; 
1633  
1634 
1635  
1636  
1637      } 
1638 else 
1639 {     
1640 for (int j=1;j<=n;j++)     
1641  
1642       
1643      {    if (!used[j]) 
1644      {    used[j]=true;           //修改used[i] 
1645                     x[i]=j;                 //x[i]選擇j 
1646                     dfs(i+1);               //繼續搜索排列的下一個元素 
1647  
1648  
1649  
1650  
1651 }      
1652  
1653  
1654 }      
1655  
1656 }      
1657 }     used[j]=false;          //回溯:恢復used[i] 
1658 voi
1659 { 
1660      d main() 
1661 n=4,m=2; 
1662 memset(used,0,sizeof(used));     
1663      
1664 //初始化爲0 
1665      printf("n=%d,m=%d的求解結果\n",n,m); 
1666      dfs(1);                        //i從1開始 
1667 } 
1668 上述程序的執行結果如圖 1.38 所示。
16691.38    程序執行結果
1670 13.    解:這個結論不正確。驗證程序以下:
1671 #include <stdio.h> #include <stdlib.h> #define MAXN 10 
1672 int q[MAXN]; 
1673 bool place(int i)            //測試第i行的q[i]列上可否擺放皇后 
1674 {    int j=1; 
1675      if (i==1) return true; 
1676      while (j<i)            //j=1~i-1是已放置了皇后的行 
1677      {    if ((q[j]==q[i]) || (abs(q[j]-q[i])==abs(j-i)))  
1678                //該皇后是否與之前皇后同列,位置(j,q[j])與(i,q[i])是否同對角線 
1679                return false; 
1680           j++; 
1681      } 
1682      return true; 
1683 } 
1684  
1685 
1686 int Queens(int n)                 //求n皇后問題的解個數 
1687 {    int count=0,k;               //計數器初始化 
1688      int i=1;                      //i爲當前行 
1689      q[1]=0;                      //q[i]爲皇后i的列號 
1690      while (i>0)             
1691      {    q[i]++;                 //移到下一列 
1692 
1693  
1694       
1695      while (q[i]<=n && !place(i)) 
1696      q[i]++; 
1697           if (q[i]<=n) 
1698           {    if (i==n) 
1699                     count++;    //找到一個解計數器count加1 
1700                else 
1701                { 
1702                     i++;; q[i]=0; 
1703                } 
1704           } 
1705           else i--;            //回溯 
1706      }     
1707      return count; 
1708 } 
1709 void main() 
1710 {    printf("驗證結果以下:\n"); 
1711      for (int n=4;n<=10;n+=2) 
1712           if (Queens(n)==2*Queens(n/2)) 
1713                printf(" n=%d: 正確\n",n); 
1714           else 
1715                printf(" n=%d: 錯誤\n",n); 
1716 } 
1717 上述程序的執行結果如圖1.39所示。從執行結果看出結論是不正確的。
17181.39 程序執行結果
1719 14.    解:假設給定的無向圖有 n 個頂點(頂點編號從 0 到 n-1),採用鄰接矩陣數組 a
17200/1 矩陣)存放,求從頂點 v 出發回到頂點 v 的哈密頓迴路。採用回溯法,解向量爲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 皇后問題的非遞歸回溯框架相似)的完整程序以下:
1721 #include <stdio.h> #define MAXV 10 
1722  
1723 
1724 //求解問題表示 
1725 int n=5;                    //圖中頂點個數 
1726 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}}; 
1727                                    //鄰接矩陣數組 
1728 //求解結果表示int x[MAXV]; int count; 
1729 void dispasolution()        //輸出一個解路徑 
1730 {    for (int i=0;i<=n-1;i++) 
1731           printf("(%d,%d) ",x[i],x[i+1]); 
1732      printf("\n"); 
1733 } 
1734 bool valid(int i)            //判斷頂點第i個頂點x[i]的有效性 
1735 {    if (a[x[i-1]][x[i]]!=1)    //x[i-1]到x[i]沒有邊,返回false 
1736           return false; 
1737      for (int j=0;j<=i-1;j++) 
1738            if (x[i]==x[j])    //頂點i重複出現,返回false 
1739                 return false; 
1740      return true;  
1741 } 
1742 void Hamiltonian(int v)              //求從頂點v出發的哈密頓迴路 
1743 {    x[0]=v;                      //存放起點 
1744      int i=1;             
1745      x[i]=-1;                      //從頂點-1+1=0開始試探 
1746      while (i>0)                  //還沒有回溯到頭,循環 
1747 
1748      {     x[i]++; 
1749           while (!valid(i) && x[i]<n) 
1750                x[i]++;        //試探一個頂點x[i] 
1751           if (x[i]<n)        //找到一個有效的頂點x[i] 
1752           {    if (i==n-1)    //達到葉子結點 
1753                {    if (a[x[i]][v]==1)  
1754                     {    x[n]=v; //找到一個解 
1755                          printf(" 第%d個解: ",count++); 
1756                          dispasolution(); 
1757                     } 
1758                } 
1759                else 
1760                { 
1761                     i++; x[i]=-1; 
1762                } 
1763           } 
1764           else 
1765                i--;            //回溯 
1766      }     
1767 }         
1768 void main() 
1769 {    printf("求解結果\n"); 
1770      for (int v=0;v<n;v++) 
1771      {    printf(" 從頂點%d出發的哈密頓迴路:\n",v); 
1772           count=1; 
1773  
1774           Hamiltonian(v);        //從頂點v出發 
1775      } 
1776 } 
1777 上述程序對如圖 1.40 所示的無向圖求從每一個頂點出發的哈密頓迴路,程序執行結果如圖 1.41 所示。
1778 
17791.40    一個無向圖
17801.41    程序執行結果
1781 1.66 章─分枝限界法
1782 1.6.1    練習題
1783 1.    分枝限界法在問題的解空間樹中,按( )策略,從根結點出發搜索解空間樹。
1784 A.    廣度優先    B.活結點優先    C.擴展結點優先    D. 深度優先
1785 2.    常見的兩種分枝限界法爲( )。
1786 A.    廣度優先分枝限界法與深度優先分枝限界法
1787  
1788 
1789 B.    隊列式(FIFO)分枝限界法與堆棧式分枝限界法C.排列樹法與子集樹法
1790 D.隊列式(FIFO)分枝限界法與優先隊列式分枝限界法
1791 3.    分枝限界法求解 0/1 揹包問題時,活結點表的組織形式是( )。A.小根堆    B.大根堆    C.棧    D.數組
1792 4.    採用最大效益優先搜索方式的算法是( )。
1793 A.    分支界限法    B.動態規劃法    C.貪心法    D.回溯法
1794 5.    優先隊列式分枝限界法選取擴展結點的原則是( )。
1795 A.    先進先出    B.後進先出    C.結點的優先級    D.隨機
1796 6.    簡述分枝限界法的搜索策略。
1797 7.    有一個 0/1 揹包問題,其中 n=4,物品重量爲(4753),物品價值爲(401798 422512),揹包最大載重量 W=10,給出採用優先隊列式分枝限界法求最優解的過程。
1799 8. 有一個流水做業調度問題,n=4,a[]={51097},b[]={7598},給出採用優先隊列式分枝限界法求一個解的過程。
1800 9.    有一個含 n 個頂點(頂點編號爲 0~n-1)的帶權圖,採用鄰接矩陣數組 A 表示, 採用分枝限界法求從起點 s 到目標點 t 的最短路徑長度,以及具備最短路徑長度的路徑條數。
1801 10.    採用優先隊列式分枝限界法求解最優裝載問題。給出如下裝載問題的求解過程和結果:n=5,集裝箱重量爲 w=(52643),限重爲 W=10。在裝載重量相同時,最優裝載方案是集裝箱個數最少的方案。
1802 1.6.2    練習題參考答案
1803 1.    答:A。
1804 2.    答:D。
1805 3.    答:B。
1806 4.    答:A。
1807 5.    答:C。
1808 6.    答:分枝限界法的搜索策略是廣度優先遍歷,經過限界函數能夠快速找到一個解或者最優解。
1809 7.    答:求解過程以下:
18101)根結點 1 進隊,對應結點值:e.i=0,e.w=0,e.v=0,e.ub=76,x:[0000]。
18112)    出隊結點 1:左孩子結點 2 進隊,對應結點值:e.no=2,e.i=1,e.w=4, e.v=40,e.ub=76,x:[1000];右孩子結點 3 進隊,對應結點值:e.no=3,e.i=1, e.w=0,e.v=0,e.ub=57,x:[0000]。
18123)    出隊結點 2:左孩子超重;右孩子結點 4 進隊,對應結點值:e.no=4,e.i=2, e.w=4,e.v=40,e.ub=69,x:[1000]。
18134)    出隊結點 4:左孩子結點 5 進隊,對應結點值:e.no=5,e.i=3,e.w=9, e.v=65,e.ub=69,x:[1010];右孩子結點 6 進隊,對應結點值:e.no=6,e.i=3, e.w=4,e.v=40,e.ub=52,x:[1000]。
1814  
18155)    出隊結點 5:產生一個解,maxv= 65,bestx:[1010]。
18166)    出隊結點 3:左孩子結點 8 進隊,對應結點值:e.no=8,e.i=2,e.w=7, e.v=42,e.ub=57,x:[0100];右孩子結點 9 被剪枝。
18177)    出隊結點 8:左孩子超重;右孩子結點 10 被剪枝。
18188)    出隊結點 6:左孩子結點 11 超重;右孩子結點 12 被剪枝。
18199)    隊列空,算法結束,產生的最優解:maxv= 65,bestx:[1010]。
1820 8.    答:求解過程以下:
18211)根結點 1 進隊,對應結點值:e.i=0,e.f1=0,e.f2=0,e.lb=29,    x:[0001822  
1823 0]。
1824  
18252)    出隊結點 1:擴展結點以下:
1826 進隊(j=1):結點 2,e.i=1,e.f1=5,e.f2=12,e.lb=27,x:[1000]。進隊(j=2):結點 3,e.i=1,e.f1=10,e.f2=15,e.lb=34,x:[2000]。進隊(j=3):結點 4,e.i=1,e.f1=9,e.f2=18,e.lb=29,x:[3000]。進隊(j=4):結點 5,e.i=1,e.f1=7,e.f2=15,e.lb=28,x:[4000]。
18273)    出隊結點 2:擴展結點以下:
1828 進隊(j=2):結點 6,e.i=2,e.f1=15,e.f2=20,e.lb=32,x:[1200]。進隊(j=3):結點 7,e.i=2,e.f1=14,e.f2=23,e.lb=27,x:[1300]。進隊(j=4):結點 8,e.i=2,e.f1=12,e.f2=20,e.lb=26,x:[1400]。
18294)    出隊結點 8:擴展結點以下:
1830 進隊(j=2):結點 9,e.i=3,e.f1=22,e.f2=27,e.lb=31,x:[1420]。進隊(j=3):結點 10,e.i=3,e.f1=21,e.f2=30,e.lb=26,x:[1430]。
18315)    出隊結點 10,擴展一個 j=2 的子結點,有 e.i=4,到達葉子結點,產生的一個解
1832  
1833 是 e.f1=31,e.f2=36,e.lb=31,x=[1432]。
1834 該解對應的調度方案是:第 1 步執行做業 1,第 2 步執行做業 4,第 3 步執行做業3,第 4 步執行做業 2,總時間=361835 9.    解:採用優先隊列式分枝限界法求解,隊列中結點的類型以下:
1836 struct NodeType 
1837 {     int vno;                                     //頂點的編號 
1838      int length;                                 //當前結點的路徑長度 
1839      bool operator<(const NodeType &s) const //重載<關係函數 
1840      {    return length>s.length;  }        //length越小越優先 
1841 }; 
1842 從頂點 s 開始廣度優先搜索,找到目標點 t 後比較求最短路徑長度及其路徑條數。對應的完整程序以下:
1843 #include <stdio.h> #include <queue> using namespace std; #define MAX 11 
1844 #define INF 0x3f3f3f3f 
1845 //問題表示 
1846 int A[MAX][MAX]={                        //一個帶權有向圖 
1847  
1848 
1849           {014,INF,INF}, 
1850           {INF,0,INF,15}, 
1851           {INF,INF,0,INF,1}, 
1852           {INF,INF,203}, 
1853           {INF,INF,INF,INF,INF} }; 
1854 
1855 int n=5; 
1856 //求解結果表示     
1857 int bestlen=INF;                                   //最優路徑的路徑長度 
1858 int bestcount=0;                                   //最優路徑的條數 
1859 struct NodeType                             
1860 {    int vno;                                     //頂點的編號 
1861      int length;                                   //當前結點的路徑長度 
1862      bool operator<(const NodeType &s) const //重載>關係函數 
1863      {    return length>s.length;  }        //length越小越優先 
1864 }; 
1865 void solve(int s,int t)                    //求最短路徑問題 
1866 {    NodeType e,e1;                        //定義2個結點 
1867      priority_queue<NodeType> qu;            //定義一個優先隊列qu 
1868      e.vno=s;                                     //構造根結點 
1869      e.length=0;                         
1870      qu.push(e);                                 //根結點進隊 
1871      while (!qu.empty())                         //隊不空循環 
1872      {    e=qu.top(); qu.pop();                 //出隊結點e做爲當前結點 
1873           if (e.vno==t)                          //e是一個葉子結點 
1874           {    if (e.length<bestlen)            //比較找最優解 
1875                {    bestcount=1;     
1876                     bestlen=e.length;            //保存最短路徑長度 
1877                }     
1878                else if (e.length==bestlen)     
1879                     bestcount++;     
1880           }     
1881           else                                    //e不是葉子結點 
1882           {     for (int j=0; j<n; j++)        //檢查e的全部相鄰頂點 
1883                     if (A[e.vno][j]!=INF && A[e.vno][j]!=0) //頂點e.vno到頂點j有邊 
1884                     {    if (e.length+A[e.vno][j]<bestlen)    //剪枝 
1885                          {    e1.vno=j; 
1886                               e1.length=e.length+A[e.vno][j]; 
1887                               qu.push(e1);                //有效子結點e1進隊 
1888                          }     
1889                     }         
1890           }                 
1891      }                     
1892 }                         
1893 void main() 
1894 {    int s=0,t=4; 
1895      solve(s,t); 
1896      if (bestcount==0) 
1897           printf("頂點%d到%d沒有路徑\n",s,t); 
1898      else 
1899      {    printf("頂點%d到%d存在路徑\n",s,t); 
1900  
1901           printf(" 最短路徑長度=%d,條數=%d\n", bestlen,bestcount); 
1902           //輸出:5 3 
1903      } 
1904 } 
1905 上述程序的執行結果如圖 1.39 所示。
19061.39 程序執行結果
1907 10.    解:採用優先隊列式分枝限界法求解。設計優先隊列priority_queue<NodeType>,並設計優先隊列的關係比較函數 Cmp,指定按結點的 ub 值進行比較,即 ub 值越大的結點越先出隊。對應的完整程序以下:
1908 #include <stdio.h> #include <queue> using namespace std; 
1909 #define MAXN 21                              //最多的集裝箱數 
1910 //問題表示                     
1911 int n=5;                     
1912 int W=10;                     
1913 int w[]={0,5,2,6,4,3};                         //集裝箱重量,不計下標0的元素 
1914 //求解結果表示                     
1915 int bestw=0;                                //存放最大重量,全局變量 
1916 int bestx[MAXN];                            //存放最優解,全局變量 
1917 int Count=1;                                //搜索空間中結點數累計,全局變量 
1918 typedef struct                      
1919 {    int no;                                //結點編號 
1920      int i;                                   //當前結點在解空間中的層次 
1921      int w;                                   //當前結點的總重量 
1922      int x[MAXN];                           //當前結點包含的解向量 
1923      int ub;                                //上界 
1924 } NodeType;                     
1925 struct Cmp                                   //隊列中關係比較函數 
1926 {    bool operator()(const NodeType &s,const NodeType &t) 
1927      {    return (s.ub<t.ub) || (s.ub==t.ub && s.x[0]>t.x[0]); 
1928           //ub越大越優先,當ub相同時x[0]越小越優先 
1929      } 
1930 }; 
1931  
1932 
1933 }     
1934 void Loading()                              //求裝載問題的最優解 
1935 {    NodeType e,e1,e2;                      //定義3個結點 
1936      priority_queue<NodeType,vector<NodeType>,Cmp > qu; //定義一個優先隊列qu 
1937      e.no=Count++;                          //設置結點編號 
1938      e.i=0;                                   //根結點置初值,其層次計爲0 
1939      e.w=0;                     
1940      for (int j=0; j<=n; j++)            //初始化根結點的解向量 
1941           e.x[j]=0; 
1942      bound(e);                        //求根結點的上界 
1943      qu.push(e);                    //根結點進隊 
1944      while (!qu.empty())                //隊不空循環 
1945      {    e=qu.top(); qu.pop();        //出隊結點e做爲當前結點 
1946           if (e.i==n)                //e是一個葉子結點 
1947           {    if ((e.w>bestw) || (e.w==bestw && e.x[0]<bestx[0])) //比較找最優解 
1948                {    bestw=e.w;            //更新bestw 
1949  
1950  
1951       
1952  
1953       
1954  
1955       
1956  
1957 }     for (int j=0;j<=e.i;j++) 
1958      bestx[j]=e.x[j]; //複製解向量e.x->bestx 
1959           }         
1960           else                        //e不是葉子結點 
1961           {     if (e.w+w[e.i+1]<=W)        //檢查左孩子結點 
1962                {    e1.no=Count++;        //設置結點編號 
1963                     e1.i=e.i+1;        //創建左孩子結點 
1964                     e1.w=e.w+w[e1.i]; 
1965                     for (int j=0; j<=e.i; j++) 
1966                          e1.x[j]=e.x[j];    //複製解向量e.x->e1.x 
1967                     e1.x[e1.i]=1;        //選擇集裝箱i 
1968                     e1.x[0]++;               //裝入集裝箱數增1 
1969                     bound(e1);               //求左孩子結點的上界 
1970                     qu.push(e1);               //左孩子結點進隊 
1971                }                 
1972                e2.no=Count++;               //設置結點編號 
1973                e2.i=e.i+1;                  //創建右孩子結點 
1974                e2.w=e.w;              
1975                for (int j=0; j<=e.i; j++)     //複製解向量e.x->e2.x 
1976                     e2.x[j]=e.x[j];     
1977                e2.x[e2.i]=0;                //不選擇集裝箱i 
1978                bound(e2);                    //求右孩子結點的上界 
1979                if (e2.ub>bestw)             //若右孩子結點可行,則進隊,不然被剪枝 
1980                     qu.push(e2);     
1981           }         
1982      }             
1983 }                 
1984 void disparr(int x[],int len)            //輸出一個解向量 
1985 {    for (int i=1;i<=len;i++) 
1986           printf("%2d",x[i]); 
1987 } 
1988 void dispLoading()                    //輸出最優解 
1989 {    printf(" X=["); 
1990  
1991      disparr(bestx,n); 
1992      printf("],裝入總價值爲%d\n",bestw); 
1993 } 
1994 void main() 
1995 {    Loading(); 
1996      printf("求解結果:\n"); 
1997      dispLoading();                    //輸出最優解 
1998 } 
1999 上述程序的執行結果如圖 1.40 所示。
20001.40    程序執行結果
2001 1.77 章─貪心法
2002 1.7.1    練習題
2003 1.    下面是貪心算法的基本要素的是( )。
2004 A.    重疊子問題    B.構造最優解    C.貪心選擇性質    D.定義最優解
2005 2.    下面問題( )不能使用貪心法解決。
2006 A.    單源最短路徑問題    B.n 皇后問題    C.最小花費生成樹問題    D.揹包問題
2007 3.    採用貪心算法的最優裝載問題的主要計算量在於將集裝箱依其重量從小到大排序,故算法的時間複雜度爲( )。
2008 A.O(n)    B.O(n2)    C.O(n3)    D.O(nlog2n)
2009 4.    關於 0/ 1 揹包問題如下描述正確的是( )。A.可使用貪心算法找到最優解
2010 B.    能找到多項式時間的有效算法
2011 C.    使用教材介紹的動態規劃方法可求解任意 01 揹包問題
2012 D.    對於同一揹包與相同的物品,作揹包問題取得的總價值必定大於等於作 0/1 揹包問
2013 2014 5.    一棵哈夫曼樹共有 215 個結點,對其進行哈夫曼編碼,共能獲得( )個不一樣的碼
2015 字。
2016 A.107    B.108    C.214    D.215
2017 6.    求解哈夫曼編碼中如何體現貪心思路?
2018 7.    舉反例證實 0/1 揹包問題若使用的算法是按照 vi/wi 的非遞減次序考慮選擇的物品,即只要正在被考慮的物品裝得進就裝入揹包,則此方法不必定能獲得最優解(此題說明 0/1 揹包問題與揹包問題的不一樣)。
2019  
2020 
2021 8.    求解硬幣問題。有 1 分、2 分、5 分、10 分、50 分和 100 分的硬幣各若干枚,如今要用這些硬幣來支付 W 元,最少須要多少枚硬幣。
2022 9.    求解正整數的最大乘積分解問題。將正整數 n 分解爲若干個互不相同的天然數之和,使這些天然數的乘積最大。
2023 10.    求解乘船問題。有 n 我的,第 i 我的體重爲 wi(0≤i<n)。每艘船的最大載重量均爲 C,且最多隻能乘兩我的。用最少的船裝載全部人。
2024 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.
2025 12.    假設要在足夠多的會場裏安排一批活動,n 個活動編號爲 1~n,每一個活動有開始時間 bi 和結束時間 ei(1≤i≤n)。設計一個有效的貪心算法求出最少的會場個數。
2026 13.    給定一個 m×n 的數字矩陣,計算從左到右走過該矩陣且通過的方格中整數最小的路徑。一條路徑能夠從第 1 列的任意位置出發,到達第 n 列的任意位置,每一步爲從第 i 列走到第 i+1 列相鄰行(水平移動或沿 45 度斜線移動),如圖 1.41 所示。第 1 行和最後一行看做是相鄰的,即應當把這個矩陣當作是一個捲起來的圓筒。
2027 
2028 
20291.41 每一步的走向
2030 兩個略有不一樣的 5×6 的數字矩陣的最小路徑如圖 1.42 所示,只有最下面一行的數不一樣。右邊矩陣的路徑利用了第一行與最後一行相鄰的性質。
2031 輸入:包含多個矩陣,每一個矩陣的第一行爲兩個數 m 和 n,分別表示矩陣的行數和列數,接下來的 m×n 個整數按行優先的順序排列,即前 n 個數組成第一行,接下的 n 個數組成第 2 行,依此類推。相鄰整數間用一個或多個空格分隔。注意這些數不必定是正數。輸入中可能有一個或多個矩陣描述,直到輸入結束。每一個矩陣的行數在 110 之間,列數在 1100 之間。
2032 輸出:對每一個矩陣輸出兩行,第一行爲最小整數之和的路徑,路徑由 n 個整數組成, 表示路徑通過的行號,若是這樣的路徑不止一條,輸出字典序最小一條。
2033 
2034 3    4    1    2    8    6
20351.42  兩個數字矩陣的最小路徑
2036 6    1    8    2    7    4
2037 5    9    3    9    9    5
2038 8    4    1    3    2    6
2039 3    7    2    1    2    3
2040  
2041 
2042 6 1 8 2 7 4
2043 5 9 3 9 9 5
2044 8 4 1 3 2 6
2045 3 7 2 8 6 4
2046 輸出結果:
2047 1 2 3 4 4 5
2048 16
2049 1.7.2    練習題參考答案
2050 1.    答:C。
2051 2.    答:n 皇后問題的解不知足貪心選擇性質。答案爲 B。
2052 3.    答:D。
2053 4.    答:因爲揹包問題能夠取物品的一部分,因此總價值必定大於等於作 0/1 揹包問題。答案爲 D。
2054 5.    答:這裏 n=215,哈夫曼樹中 n1=0,而 n0=n2+1,n=n0+n1+n2=2n0-1, n0=(n+1)/2=108。答案爲 B。
2055 6.    答:在構造哈夫曼樹時每次都是將兩棵根結點最小的樹合併,從而體現貪心的思路。
2056 7. 證實:例如,n=3,w={322},v={744},W=4 時,因爲 7/3 最大,若按題目要求的方法,只能取第一個,收益是 7。而此實例的最大的收益應該是 8,取第 23
2057 個物品。
2058 8.    解:用結構體數組 A 存放硬幣數據,A[i].v 存放硬幣 i 的面額,A[i].c 存放硬幣 i 的枚數。採用貪心思路,首先將數組 A 按面額遞減排序,再兌換硬幣,每次儘量兌換面額大的硬幣。對應的完整程序以下:
2059 #include <stdio.h> #include <algorithm> using namespace std; 
2060 #define min(x,y) ((x)<(y)?(x):(y)) #define MAX 21 
2061 //問題表示int n=7; 
2062 struct NodeType 
2063 {     int v;                              //面額 
2064      int c;                              //枚數 
2065      bool operator<(const NodeType &s) 
2066      {                            //用於按面額遞減排序 
2067           return s.v<v; 
2068      } 
2069 }; 
2070 NodeType A[]={{1,12},{2,8},{5,6},{50,10},{10,8},{200,1},{100,4}}; 
2071 int W; 
2072 //求解結果表示 
2073  
2074 
2075 {    sort(A,A+n);                //按面額遞減排序 
2076      for (int i=0;i<n;i++) 
2077      {    int t=min(W/A[i].v,A[i].c); //使用硬幣i的枚數 
2078           if (t!=0) 
2079                printf(" 支付%3d面額: %3d枚\n",A[i].v,t); 
2080           W-=t*A[i].v;            //剩餘的金額 
2081           ans+=t; 
2082           if (W==0) break; 
2083      } 
2084 } 
2085 void main() 
2086 {    W=325;                        //支付的金額 
2087      printf("支付%d分:\n",W); 
2088      solve(); 
2089      printf("最少硬幣的個數: %d枚\n",ans); 
2090 } 
2091 上述程序的執行結果如圖 1.43 所示。
20921.43 程序執行結果
2093 9.    解:採用貪心方法求解。用 a[0..k]存放 n 的分解結果:
20941)    n≤4 時能夠驗證其分解成幾個正整數的和的乘積均小於 n,沒有解。
20952)    n>4 時,把 n 分拆成若干個互不相等的天然數的和,分解數的個數越多乘積越大。爲此讓 n 的分解數個數儘量多(體現貪心的思路),把 n 分解成從 2 開始的連續的天然數之和。例如,分解 n 爲 a[0]=2,a[1]=3,a[2]=4,…,a[k]=k+2(共有 k+1 個分解數),用 m 表示剩下數,這樣的分解直到 m≤a[k]爲止,即 m≤k+2。對剩下數 m 的處理分爲以下兩種狀況:
2096 ① m<k+2:將 m 平均分解到 a[k..i](對應的分解數個數爲 m)中,即從 a[k]開始往前的分解數增長 1(也是貪心的思路,分解數越大加 1 和乘積也越大)。
2097 ② m=k+2:將 a[0..k-1] (對應的分解數個數爲 k)的每一個分解數增長 1,剩下的 2 增長到 a[k]中,即 a[k]增長 22098 對應的完整程序以下:
2099 #include <stdio.h> #include <string.h> #define MAX 20 
2100 //問題表示int n; 
2101 //求解結果表示 
2102  
2103 
2104 int a[MAX];              
2105 int k=0;                  
2106 void solve()                  
2107  
2108      //存放被分解的數 
2109 //a[0..k]存放被分解的數 
2110 //求解n的最大乘積分解問題 
2111 {    int i;         
2112      int sum=1;         
2113      if (n<4)                      //不存在最優方案,直接返回 
2114           return;         
2115      else         
2116      {    int m=n;                 //m表示剩下數 
2117           a[0]=2;                 //第一個數從2開始 
2118           m-=a[0];                 //減去已經分解的數 
2119           k=0;         
2120           while (m>a[k])          //若剩下數大於最後一個分解數,則繼續分解 
2121           {     k++;                //a數組下標+1 
2122                a[k]=a[k-1]+1;     //按二、三、4遞增順序分解 
2123                m-=a[k];            //減去最新分解的數 
2124           }     
2125           if (m<a[k])             //若剩下數小於a[k],從a[k]開始往前的數+1 
2126           {    for (i=0; i<m; i++) 
2127                     a[k-i]+=1; 
2128           } 
2129           if (m==a[k])        //若剩下數等於a[k],則a[k]的值+2,以前的數+1 
2130           {    a[k]+=2; 
2131                for (i=0; i<k; i++) 
2132                     a[i]+=1; 
2133           } 
2134      }     
2135 }         
2136 void main() 
2137 {    n=23; 
2138      memset(a,0,sizeof(a)); 
2139      solve(); 
2140      printf("%d的最優分解方案\n",n); 
2141      int mul=1; 
2142      printf(" 分解的數: "); 
2143      for (int i=0;i<=k;i++) 
2144           if (a[i]!=0) 
2145           {    printf("%d ",a[i]); 
2146                mul*=a[i]; 
2147           } 
2148      printf("\n 乘積最大值: %d\n",mul); 
2149 } 
2150 上述程序的執行結果如圖 1.44 所示。
2151  
2152 
21531.44 程序執行結果
2154 10.    解:採用貪心思路,首先按體重遞增排序;再考慮先後的兩我的(最輕者和最重者),分別用 i、j 指向:若 w[i]+w[j]≤C,說明這兩我的能夠同乘(執行 i++,j--),不然 w[j]單乘(執行 j--),若最後只剩餘一我的,該人只能單乘。
2155 對應的完整程序以下:
2156 #include <stdio.h> #include <algorithm> using namespace std; #define MAXN 101 
2157 //問題表示int n=7; 
2158 int w[]={50,65,58,72,78,53,82}; 
2159 int C=150; 
2160 //求解結果表示int bests=0; 
2161 void Boat()         
2162 {    sort(w,w+n);   
2163      int i=0;      
2164      //求解乘船問題 
2165 //遞增排序 
2166      int j=n - 1;         
2167      while (i<=j)         
2168      {    if(i==j)            //剩下最後一我的 
2169           {    printf(" 一艘船: %d\n",w[i]); 
2170               bests++; 
2171               break; 
2172           } 
2173           if (w[i]+w[j]<=C) //先後兩我的同乘 
2174           {    printf(" 一艘船: %d %d\n",w[i],w[j]); 
2175               bests++; 
2176               i++; 
2177               j--; 
2178           } 
2179           else            //w[j]單乘 
2180           {    printf(" 一艘船: %d\n",w[j]); 
2181               bests++; 
2182               j--; 
2183           } 
2184     } 
2185 } 
2186 void main() 
2187 {    printf("求解結果:\n"); 
2188      Boat(); 
2189      printf("最少的船數=%d\n",bests); 
2190 } 
2191 上述程序的執行結果如圖 1.45 所示。
2192  
2193 
2194  
2195 
21961.45 程序執行結果
2197 11.    解:採用貪心思路。每次都在還未安排的容量最大的會議室安排儘量多的參會人數,即對於每一個會議室,都安排當前還未安排的會議中,參會人數最多的會議。若能容納下,則選擇該會議,不然找參會人數次多的會議來安排,直到找到能容納下的會議。
2198 對應的完整程序以下:
2199 #include <stdio.h> #include <algorithm> using namespace std; 
2200 //問題表示 
2201 int n=4;                      //會議個數 
2202 int m=4;                      //會議室個數 
2203 int A[]={3,4,3,1}; 
2204 int B[]={1,2,2,6}; 
2205 //求解結果表示int ans=0; 
2206 void solve()            //求解算法 
2207 {    sort(A,A+n);        //遞增排序 
2208      sort(B,B+m);        //遞增排序 
2209      int i=n-1,j=m-1;    //從最多人數會議和最多容納人數會議室開始 
2210      for(i;i>=0;i--) 
2211      {     if(A[i]<=B[j] && j>=0) 
2212           {    ans++;        //不知足條件,增長一個會議室 
2213                j--; 
2214           } 
2215      }     
2216 }         
2217 void main() 
2218 {     solve();     
2219      printf("%d\n",ans);     //輸出2 
2220 }         
2221 12.    解:與《教程》例 7.2 相似,會場對應蓄欄,只是這裏僅僅求會場個數,即最大兼容活動子集的個數。對應的完整程序以下:
2222 #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define MAX 51 
2223 //問題表示 
2224 struct Action                        //活動的類型聲明 
2225  
2226 
2227 { 
2228      int b;      
2229 int e;           
2230       
2231       
2232       
2233       
2234      //活動起始時間 
2235 //活動結束時間 
2236      bool operator<(const Action &s) const //重載<關係函數 
2237      {     if (e==s.e)                       //結束時間相同按開始時間遞增排序 
2238                return b<=s.b;             
2239           else                               //不然按結束時間遞增排序 
2240                return e<=s.e;             
2241      }                 
2242 };                     
2243 int n=5; 
2244 Action A[]={{0},{1,10},{2,4},{3,6},{5,8},{4,7}};    //下標0不用 
2245 //求解結果表示 
2246 int ans;                                     //最少會場個數 
2247 void solve()                                //求解最大兼容活動子集 
2248 {    bool flag[MAX];                         //活動標誌 
2249      memset(flag,0,sizeof(flag)); 
2250      sort(A+1,A+n+1);                       //A[1..n]按指定方式排序 
2251      ans=0;                            //會場個數 
2252      for (int j=1;j<=n;j++) 
2253      {    if (!flag[j]) 
2254           {    flag[j]=true; 
2255                int preend=j;            //前一個兼容活動的下標 
2256                for (int i=preend+1;i<=n;i++) 
2257                {    if (A[i].b>=A[preend].e && !flag[i]) 
2258  
2259  
2260       
2261  
2262       
2263  
2264       
2265  
2266      { 
2267  
2268 }     preend=i; 
2269 flag[i]=true; 
2270                }         
2271                ans++;                        //增長一個最大兼容活動子集 
2272           }     
2273      }         
2274 }             
2275 void main() 
2276 {    solve(); 
2277      printf("求解結果\n"); 
2278      printf(" 最少會場個數: %d\n",ans); //輸出4 
2279 } 
2280 13.    解:採用貪心思路。從第 1 列開始每次查找 a[i][j]元素上、中、下 3 個對應數中的最小數。對應的程序以下:
2281 #include <stdio.h> #define M 12 #define N 110 
2282 int m=5, n=6; 
2283 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}}; 
2284 int minRow,minCol; 
2285 int minValue(int i, int j) 
2286           //求a[i][j]有方上、中、下3個數的最小數,同時要把行標記錄下來 
2287 {    int s = (i == 0) ? m - 1 : i - 1; 
2288      int x = (i == m - 1) ? 0 : i + 1; 
2289  
2290      minRow = s; 
2291      minRow = a[i][j+1] < a[minRow][j+1] ? i : minRow; 
2292      minRow = a[x][j+1] < a[minRow][j+1] ? x : minRow; 
2293      minRow = a[minRow][j+1] == a[s][j+1] && minRow > s ? s : minRow; 
2294      minRow = a[minRow][j+1] == a[i][j+1] && minRow > i ? i : minRow; 
2295      minRow = a[minRow][j+1] == a[x][j+1] && minRow > x ? x : minRow; 
2296      return a[minRow][j+1]; 
2297 } 
2298 void solve() 
2299 {    int i,j,min; 
2300      for (j=n-2; j>=0; j--) 
2301           for (i=0; i<m; i++) 
2302                a[i][j]+= minValue(i,j); 
2303      min=a[0][0]; 
2304      minRow=0; 
2305      for (i=1; i<m; i++)        //在第一列查找最小代價的行 
2306           if (a[i][0]<min) 
2307           {    min=a[i][0]; 
2308                minRow=i; 
2309           } 
2310      for (j=0; j<n; j++) 
2311      {    printf("%d",minRow+1); 
2312           if (j<n-1) printf(" "); 
2313           minValue(minRow, j); 
2314      } 
2315      printf("\n%d\n",min);   
2316 } 
2317 void main() 
2318 { 
2319      solve(); 
2320 } 
2321 
2322 1.88 章─動態規劃
2323 1.8.1    練習題
2324 1.    下列算法中一般以自底向上的方式求解最優解的是( )。
2325 A.    備忘錄法    B.動態規劃法    C.貪心法    D.回溯法
2326 2.    備忘錄方法是( )算法的變形。
2327 A.    分治法    B.回溯法    C.貪心法    D.動態規劃法
2328 3.    下列是動態規劃算法基本要素的是( )。
2329 A.    定義最優解    B.構造最優解    C.算出最優解    D.子問題重疊性質
2330 4.    一個問題可用動態規劃算法或貪心算法求解的關鍵特徵是問題的( )。
2331 A.    貪心選擇性質    B.重疊子問題    C.最優子結構性質    D.定義最優解
2332 5.    簡述動態規劃法的基本思路。
2333 6.    簡述動態規劃法與貪心法的異同。
2334  
2335 
2336 7.    簡述動態規劃法與分治法的異同。
2337 8.    下列算法中哪些屬於動態規劃算法?
23381)    順序查找算法
23392)    直接插入排序算法
23403)    簡單選擇排序算法
23414)    二路歸併排序算法
2342 9.    某個問題對應的遞歸模型以下:
2343 f(1)=1
2344 f(2)=2
2345 f(n)=f(n-1)+f(n-2)+…+f(1)+1    當 n>22346 能夠採用以下遞歸算法求解:
2347 long f(int n) 
2348 {    if (n==1) return 1; 
2349      if (n==2) return 2; 
2350      long sum=1; 
2351      for (int i=1;i<=n-1;i++) 
2352           sum+=f(i); 
2353      return sum; 
2354 } 
2355 但其中存在大量的重複計算,請採用備忘錄方法求解。
2356 10.    第 3 章中的實驗 4 採用分治法求解半數集問題,若是直接遞歸求解會存在大量重複計算,請改進該算法。
2357 11.    設計一個時間複雜度爲 O(n2)的算法來計算二項式係數C k   (k≤n)。二項式係數
2358 Ck   的求值過程以下:
2359  
2360 𝐶0 = 1
2361 𝐶𝑖 = 1
2362 𝐶𝑗 = 𝐶𝑗 ‒ 1 + 𝐶 𝑗
2363  
2364 
2365 
2366 
2367 當 i≥j
2368  
2369 𝑖    𝑖 ‒ 1    𝑖 ‒ 1
2370 12.    一個機器人只能向下和向右移動,每次只能移動一步,設計一個算法求它從
237100)移動到(m,n)有多少條路徑。
2372 13.    兩種水果雜交出一種新水果,如今給新水果取名,要求這個名字中包含了之前兩種水果名字的字母,而且這個名字要儘可能短。也就是說之前的一種水果名字 arr1 是新水果名字 arr 的子序列,另外一種水果名字 arr2 也是新水果名字 arr 的子序列。設計一個算法求arr。
2373 例如:輸入如下 3 組水果名稱:
2374 apple peach ananas banana pear peach
2375 輸出的新水果名稱以下:
2376  
2377 
2378 appleach bananas pearch
2379 1.8.2    練習題參考答案
2380 1.    答:B。
2381 2.    答:D。
2382 3.    答:D。
2383 4.    答:C。
2384 5.    答:動態規劃法的基本思路是將待求解問題分解成若干個子問題,先求子問題的解,而後從這些子問題的解獲得原問題的解。
2385 6.    答:動態規劃法的 3 個基本要素是最優子結構性質、無後效性和重疊子問題性質,而貪心法的兩個基本要素是貪心選擇性質和最優子結構性質。因此二者的共同點是都要求問題具備最優子結構性質。
2386 二者的不一樣點以下:
23871)    求解方式不一樣,動態規劃法是自底向上的,有些具備最優子結構性質的問題只能用動態規劃法,有些可用貪心法。而貪心法是自頂向下的。
23882)    對子問題的依賴不一樣,動態規劃法依賴於各子問題的解,因此應使各子問題最優,才能保證總體最優;而貪心法依賴於過去所做過的選擇,但決不依賴於未來的選擇, 也不依賴於子問題的解。
2389 7.    答:二者的共同點是將待求解的問題分解成若干子問題,先求解子問題,而後再從這些子問題的解獲得原問題的解。
2390 二者的不一樣點是:適合於用動態規劃法求解的問題,分解獲得的各子問題每每不是相互獨立的(重疊子問題性質),而分治法中子問題相互獨立;另外動態規劃法用表保存已求解過的子問題的解,再次碰到一樣的子問題時沒必要從新求解,而只需查詢答案,故可得到多項式級時間複雜度,效率較高,而分治法中對於每次出現的子問題均求解,致使一樣的子問題被反覆求解,故產生指數增加的時間複雜度,效率較低。
2391 8.    答:判斷算法是否具備最優子結構性質、無後效性和重疊子問題性質。(2)、(3)和(4)均屬於動態規劃算法。
2392 9.    解:設計一個 dp 數組,dp[i]對應 f(i)的值,首先 dp 的全部元素初始化爲 0,在計算 f(i)時,若 dp[0]>0 表示 f(i)已經求出,直接返回 dp[i]便可,這樣避免了重複計算。對應的算法以下:
2393 long dp[MAX];    //dp[n]保存f(n)的計算結果 
2394 long f1(int n) 
2395 {    if (n==1) 
2396      {    dp[n]=1; 
2397           return dp[n]; 
2398      } 
2399      if (n==2) 
2400      {    dp[n]=2; 
2401           return dp[n]; 
2402  
2403 
2404      } 
2405      if (dp[n]>0) return dp[n]; 
2406      long sum=1; 
2407      for (int i=1;i<=n-1;i++) 
2408           sum+=f1(i); 
2409      dp[n]=sum; 
2410      return dp[n]; 
2411 } 
2412 10.    解:設計一個數組 a,其中 a[i]=f(i),首先將 a 的全部元素初始化爲 0,當 a[i]>0
2413 時表示對應的 f(i)已經求出,直接返回就能夠了。對應的完整程序以下:
2414 #include <stdio.h> #include <string.h> #define MAXN 201 
2415 //問題表示int n; 
2416 int a[MAXN]; 
2417 int fa(int i)            //求a[i] 
2418 {    int ans=1; 
2419      if (a[i]>0) 
2420           return a[i]; 
2421      for(int j=1;j<=i/2;j++) 
2422           ans+=fa(j); 
2423      a[i]=ans; 
2424      return ans; 
2425 } 
2426 int solve(int n)        //求set(n)的元素個數 
2427 {    memset(a,0,sizeof(a)); 
2428      a[1]=1; 
2429      return fa(n); 
2430 } 
2431 void main() 
2432 {    n=6; 
2433      printf("求解結果\n"); 
2434      printf(" n=%d時半數集元素個數=%d\n",n,solve(n)); 
2435 } 
2436 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), 即Ck   。對應的程序以下:
2437 #include <stdio.h> #define MAXN 51 #define MAXK 31 
2438 //問題表示int n,k; 
2439 //求解結果表示 
2440 int C[MAXN][MAXK]; 
2441 void solve() 
2442 {    int i,j; 
2443  
2444      for (i=0;i<=n;i++) 
2445      {    C[i][i]=1; 
2446           C[i][0]=1; 
2447      } 
2448      for (i=1;i<=n;i++) 
2449           for (j=1;j<=k;j++) 
2450                C[i][j]=C[i-1][j-1]+C[i-1][j]; 
2451 } 
2452 void main() 
2453 {    n=5,k=3; 
2454      solve(); 
2455      printf("%d\n",C[n][k]); //輸出10 
2456 } 
2457 顯然,solve()算法的時間複雜度爲 O(n2)。
2458 12.    解:設從(00)移動到(i,j)的路徑條數爲 dp[i][j],因爲機器人只能向下和向右移動,不一樣於迷宮問題(迷宮問題因爲存在後退,不知足無後效性,不適合用動態規劃法求解)。對應的狀態轉移方程以下:
2459 dp[0][j]=1
2460 dp[i][0]=1
2461 dp[i][j]=dp[i][j-1]+dp[i-1][j]    i、j>0
2462 最後結果是 dp[m][n]。對應的程序以下:
2463 #include <stdio.h> #include <string.h> #define MAXX 51 #define MAXY 51 
2464 //問題表示int m,n; 
2465 //求解結果表示 
2466 int dp[MAXX][MAXY]; 
2467 void solve() 
2468 {    int i,j; 
2469      dp[0][0]=0; 
2470      memset(dp,0,sizeof(dp)); 
2471      for (i=1;i<=m;i++) 
2472           dp[i][0]=1; 
2473      for (j=1;j<=n;j++) 
2474           dp[0][j]=1; 
2475      for (i=1;i<=m;i++) 
2476           for (j=1;j<=n;j++) 
2477                dp[i][j]=dp[i][j-1]+dp[i-1][j]; 
2478 } 
2479 void main() 
2480 {    m=5,n=3; 
2481      solve(); 
2482      printf("%d\n",dp[m][n]); 
2483 } 
2484 13.    解:本題目的思路是求 arr1 和 arr2 字符串的最長公共子序列,基本過程參見《教
2485  
2486 
2487 程》第 88.5 節。對應的完整程序以下:
2488 #include <iostream> #include <string.h> #include <vector> #include <string> using namespace std; 
2489 #define max(x,y) ((x)>(y)?(x):(y)) 
2490 #define MAX 51                              //序列中最多的字符個數 
2491 //問題表示                         
2492 int m,n;                         
2493 string arr1,arr2;                         
2494 //求解結果表示                         
2495 int dp[MAX][MAX];                    //動態規劃數組vector<char> subs;                    // 存 放 LCS void LCSlength()                    //求dp 
2496 {    int i,j; 
2497      for (i=0;i<=m;i++)                //將dp[i][0]置爲0,邊界條件 
2498           dp[i][0]=0; 
2499      for (j=0;j<=n;j++)                //將dp[0][j]置爲0,邊界條件 
2500           dp[0][j]=0; 
2501      for (i=1;i<=m;i++) 
2502           for (j=1;j<=n;j++)            //兩重for循環處理arr一、arr2的全部字符 
2503           {    if (arr1[i-1]==arr2[j-1]) //比較的字符相同 
2504                     dp[i][j]=dp[i-1][j-1]+1; 
2505                else                    //比較的字符不一樣 
2506  
2507       
2508       
2509 }          dp[i][j]=max(dp[i][j-1],dp[i-1][j]); 
2510 }             
2511 
2512 void Buildsubs()                           //由dp構造從subs 
2513 {    int k=dp[m][n];                         //k爲arr1和arr2的最長公共子序列長度 
2514      int i=m;                     
2515      int j=n;                     
2516      while (k>0)                            //在subs中放入最長公共子序列(反向) 
2517 
2518           if (dp[i][j]==dp[i-1][j]) i--; 
2519           else if (dp[i][j]==dp[i][j-1]) j--; 
2520           else 
2521           {    subs.push_back(arr1[i-1]); //subs中添加arr1[i-1] 
2522                i--; j--; k--; 
2523  
2524 }          } 
2525 voi
2526 {     d main() 
2527 cin >> arr1 >> arr2;     
2528      
2529      
2530      
2531 //輸入arr1和arr2 
2532      m=arr1.length();                       //m爲arr1的長度 
2533      n=arr2.length();                       //n爲arr2的長度 
2534      LCSlength();                           //求出dp 
2535      Buildsubs();                           //求出LCS 
2536      cout << "求解結果" << endl; 
2537      cout << " arr: "; 
2538      vector<char>::reverse_iterator rit; 
2539  
2540      for (rit=subs.rbegin();rit!=subs.rend();++rit) 
2541           cout << *rit; 
2542      cout << endl; 
2543      cout << " 長 度 : " << dp[m][n] << endl; 
2544 } 
2545 改成以下:
2546 13.    解:本題目的思路是先求 arr1 和 arr2 字符串的最長公共子序列,基本過程參見
2547 《教程》第 88.5 節,再利用遞歸輸出新水果取名。
2548 算法中設置二維動態規劃數組 dp,dp[i][j]表示 arr1[0..i-1](i 個字母)和 arr2[0..j-1]
2549 (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- 1][j]>dp[i][j-1],b[i][j]=2 表示 arr1[i-1]≠arr2[j-1]而且 dp[i-1][j]≤dp[i][j-1]。
2550 對應的完整程序以下: #include <stdio.h> #include <string.h> 
2551 #define MAX 51                            //序列中最多的字符個數 
2552 //問題表示int m,n; 
2553 char arr1[MAX],arr2[MAX]; 
2554 //求解結果表示 
2555 int dp[MAX][MAX];                                //動態規劃數組 
2556 int b[MAX][MAX];                                 //存放arr1與arr2比較的3種狀況 
2557 void Output(int i,int j)                         //利用遞歸輸出新水果取名 
2558 {    if (i==0 && j==0)                           //輸出完畢 
2559           return;                     
2560      if(i==0)                                     //arr1完畢,輸出arr2的剩餘部分 
2561      {    Output(i,j-1); 
2562           printf("%c",arr2[j-1]); 
2563           return; 
2564      } 
2565      else if(j==0)                        //arr2完畢,輸出arr1的剩餘部分 
2566      {    Output(i-1,j); 
2567           printf("%c",arr1[i-1]); 
2568           return; 
2569      } 
2570      if (b[i][j]==0)                        //arr1[i-1]=arr2[j-1]的狀況 
2571      {    Output(i-1,j-1); 
2572           printf("%c",arr1[i-1]); 
2573           return; 
2574      } 
2575      else if(b[i][j]==1) 
2576      {    Output(i-1,j); 
2577           printf("%c",arr1[i-1]); 
2578           return; 
2579      } 
2580      else 
2581      {    Output(i,j-1); 
2582  
2583 
2584           printf("%c",arr2[j-1]); 
2585           return; 
2586      }         
2587 }         
2588 void LCSlength()                                 //求dp 
2589 {    int i,j;         
2590      for (i=0;i<=m;i++)                          //將dp[i][0]置爲0,邊界條件 
2591           dp[i][0]=0;         
2592      for (j=0;j<=n;j++)                          //將dp[0][j]置爲0,邊界條件 
2593           dp[0][j]=0;         
2594      for (i=1;i<=m;i++)         
2595           for (j=1;j<=n;j++)                     //兩重for循環處理arr一、arr2的全部字符 
2596           {    if (arr1[i-1]==arr2[j-1])          //比較的字符相同:狀況0 
2597                {    dp[i][j]=dp[i-1][j-1]+1; 
2598                     b[i][j]=0; 
2599                } 
2600                else if (dp[i-1][j]>dp[i][j-1]) //狀況1 
2601                {    dp[i][j]=dp[i-1][j]; 
2602                     b[i][j]=1; 
2603                } 
2604                else                               //dp[i-1][j]<=dp[i][j-1]:狀況2 
2605                {     dp[i][j]=dp[i][j-1]; 
2606                     b[i][j]=2; 
2607                }     
2608           }         
2609 }                 
2610 void main() 
2611 {    int t;                                //輸入測試用例個數 
2612      printf("測試用例個數: "); 
2613      scanf("%d",&t); 
2614      while(t--) 
2615      {    scanf("%s",arr1); 
2616           scanf("%s",arr2); 
2617           memset(b,-1,sizeof(b)); 
2618  
2619  
2620       
2621  
2622      m=strlen(arr1); 
2623 n=strlen(arr2); 
2624 LCSlength();        
2625  
2626       
2627  
2628       
2629  
2630       
2631  
2632      //m爲arr1的長度 
2633 //n爲arr2的長度 
2634 //求出dp 
2635           printf("結果: "); Output(m,n);          //輸出新水果取名 
2636           printf("\n");         
2637     } 
2638 } 
2639 上述程序的一次執行結果如圖 1.46 所示。
2640  
2641 
2642  
2643 
26441.46 程序的一次執行結果
2645 13. 解:本題目的思路是求 arr1 和 arr2 字符串的最長公共子序列,基本過程參見《教程》第 88.5 節。對應的完整程序以下:
2646 
2647 而後再用遞歸思想,逐一輸出,獲得的就是最後答案。
2648 #include <iostream> #include <string.h> #include <vector> #include <string> using namespace std; 
2649 #define max(x,y) ((x)>(y)?(x):(y)) 
2650 #define MAX 51                              //序列中最多的字符個數 
2651 //問題表示                         
2652 int m,n;                         
2653 string arr1,arr2;                         
2654 //求解結果表示                         
2655 int dp[MAX][MAX];                    //動態規劃數組vector<char> subs;                    // 存 放 LCS void LCSlength()                    //求dp 
2656 {    int i,j; 
2657      for (i=0;i<=m;i++)                //將dp[i][0]置爲0,邊界條件 
2658           dp[i][0]=0; 
2659      for (j=0;j<=n;j++)                //將dp[0][j]置爲0,邊界條件 
2660           dp[0][j]=0; 
2661      for (i=1;i<=m;i++) 
2662           for (j=1;j<=n;j++)            //兩重for循環處理arr一、arr2的全部字符 
2663           {    if (arr1[i-1]==arr2[j-1]) //比較的字符相同 
2664                     dp[i][j]=dp[i-1][j-1]+1; 
2665                else                    //比較的字符不一樣 
2666  
2667       
2668       
2669 }          dp[i][j]=max(dp[i][j-1],dp[i-1][j]); 
2670 }             
2671 
2672 void Buildsubs()                           //由dp構造從subs 
2673 {    int k=dp[m][n];                         //k爲arr1和arr2的最長公共子序列長度 
2674      int i=m;                     
2675      int j=n;                     
2676      while (k>0)                            //在subs中放入最長公共子序列(反向) 
2677 
2678           if (dp[i][j]==dp[i-1][j]) i--; 
2679           else if (dp[i][j]==dp[i][j-1]) j--; 
2680  
2681 
2682  
2683  
2684       
2685  
2686      else 
2687 {    subs.push_back(arr1[i-1]); //subs中添加arr1[i-1] 
2688      i--; j--; k--; 
2689  
2690 }          } 
2691 voi
2692 {     d main() 
2693 cin >> arr1 >> arr2;     
2694      
2695      
2696      
2697 //輸入arr1和arr2 
2698      m=arr1.length();                       //m爲arr1的長度 
2699      n=arr2.length();                       //n爲arr2的長度 
2700      LCSlength();                           //求出dp 
2701      Buildsubs();                           //求出LCS 
2702      cout << "求解結果" << endl; 
2703      cout << " arr: "; 
2704      vector<char>::reverse_iterator rit; 
2705      for (rit=subs.rbegin();rit!=subs.rend();++rit) 
2706           cout << *rit; 
2707      cout << endl; 
2708      cout << " 長 度 : " << dp[m][n] << endl; 
2709 } 
2710 上述程序的一次執行結果如圖 1.46 所示。
27111.46    程序的一次執行結果
2712 1.99 章─圖算法設計
2713 1.9.1    練習題
2714 1.    如下不屬於貪心算法的是( )。
2715 A.Prim 算法    B.Kruskal 算法    C.Dijkstra 算法    D.深度優先遍歷
2716 2.    一個有 n 個頂點的連通圖的生成樹是原圖的最小連通子圖,且包含原圖中全部 n 個頂點,而且有保持圖聯通的最少的邊。最大生成樹就是權和最大生成樹,如今給出一個無向帶權圖的鄰接矩陣爲{{04503},{40423},{54020},{02201},{33010}},其中權爲 0 表示沒有邊。一個圖爲求這個圖的最大生成樹的權和是( )。
2717 A.11    B.12    C.13    D.14    E.15
2718 3.    某個帶權連通圖有 4 個以上的頂點,其中剛好有 2 條權值最小的邊,儘管該圖的最小生成樹可能有多個,而這 2 條權值最小的邊必定包含在全部的最小生成樹中嗎?若是有 3 條權值最小的邊呢?
2719  
2720 4.    爲何 TSP 問題採用貪心算法求解不必定獲得最優解?
2721 5.    求最短路徑的 4 種算法適合帶權無向圖嗎?
2722 6.    求單源最短路徑的算法有 Dijkstra 算法、Bellman-Ford 算法和 SPFA 算法,比較這些算法的不一樣點。
2723 7.    有人這樣修改 Dijkstra 算法以便求一個帶權連通圖的單源最長路徑,將每次選擇dist 最小的頂點 u 改成選擇最大的頂點 u,將按路徑長度小進行調整改成按路徑長度大調整。這樣能夠求單源最長路徑嗎?
2724 8.    給出一種方法求無環帶權連通圖(全部權值非負)中從頂點 s 到頂點 t 的一條最長簡單路徑。
2725 9.    一個運輸網絡如圖 1.47 所示,邊上數字爲(c(i,j),b(i,j)),其中 c(i,j)表示容量,b(i,j)表示單位運輸費用。給出從 123 位置運輸貨物到位置 6 的最小費用最大流的過程。
2726 10.    本教程中的 Dijkstra 算法採用鄰接矩陣存儲圖,算法時間複雜度爲 O(n2)。請你從各個方面考慮優化該算法,用於求源點 v 到其餘頂點的最短路徑長度。
2727 11.    有一個帶權有向圖 G(全部權爲正整數),採用鄰接矩陣存儲。設計一個算法求其中的一個最小環。
2728 
27291.47    一個運輸網絡
2730 1.9.2    練習題參考答案
2731 1.    答:D。
2732 2.    答:採用相似 Kurskal 算法來求最大生成樹,第 1 步取最大邊(02),第 2 步取
2733 邊(01),第 3 步取邊(04),第 4 步取最大邊(13),獲得的權和爲 14。答案爲 D。
2734 3.    答:這 2 條權值最小的邊必定包含在全部的最小生成樹中,由於按 Kurskal 算法必定首先選中這 2 條權值最小的邊。若是有 3 條權值最小的邊,就不必定了,由於首先選中這 3 條權值最小的邊有可能出現迴路。
2735 4.    答:TSP 問題不知足最優子結構性質,如(01230)是整個問題的最優解,但(0120)不必定是子問題的最優解。
2736 5.    答:都適合帶權無向圖求最短路徑。
2737 6.    答:Dijkstra 算法不適合存在負權邊的圖求單源最短路徑,其時間複雜度爲
2738 O(n2)。Bellman-Ford 算法和 SPFA 算法適合存在負權邊的圖求單源最短路徑,但圖中不能
2739  
2740 
2741 存在權值和爲負的環。Bellman-Ford 算法的時間複雜度爲 O(ne),而 SPFA 算法的時間複雜度爲 O(e),因此 SPFA 算法更優。
2742 7.    答:不能。Dijkstra 算法本質上是一種貪心算法,而求單源最長路徑不知足貪心選擇性質。
2743 8.    答:Bellman-Ford 算法和 SPFA 算法適合存在負權邊的圖求單源最短路徑。能夠將圖中全部邊權值改成負權值,求出從頂點 s 到頂點 t 的一條最短簡單路徑,它就是原來圖中從頂點 s 到頂點 t 的一條最長簡單路徑。
2744 9.    答:爲該運輸網絡添加一個虛擬起點 0,它到 123 位置運輸費用爲 0,容量分別爲到 123 位置運輸容量和,如圖 1.48 所示,起點 s=0,終點 t=62745 
27461.48 添加一個虛擬起點的運輸網絡
2747 首先初始化 f 爲零流,最大流量 maxf=0,最小費用 mincost=0,採用最小費用最大流算法求解過程以下:
27481)    k=0,求出 w 以下:
2749 
2750 
2751 
2752 
2753 求出從起點 0 到終點 6 的最短路徑爲 0146,求出最小調整量=4,f[4][6]調整爲 4,f[1][4]調整爲 4,f[0][1]調整爲 4,mincost=20,maxf=427542)    k=1,求出 w 以下:
2755 
2756 
2757 
2758 
2759 求出從起點 0 到終點 6 的最短路徑爲 0246,求出最小調整量=3,f[4][6]調整
2760  
27617,f[2][4]調整爲 3,f[0][2]調整爲 3,mincost=44,maxf=4+3=727623)    k=2,求出 w 以下:
2763 
2764 
2765 
2766 
2767 求出從起點 0 到終點 6 的最短路徑爲 0346,求出最小調整量=1,f[4][6]調整爲 8,f[3][4]調整爲 1,f[0][3]調整爲 1,mincost=53,maxf=7+1=827684)    k=3,求出 w 以下:
2769 
2770 
2771 
2772 
2773 求出從起點 0 到終點 6 的最短路徑爲 0356,求出最小調整量=2,f[5][6]調整爲 2,f[3][5]調整爲 2,f[0][3]調整爲 3,mincost=83,maxf=8+2=1027745)    k=4,求出 w 以下:
2775 
2776 
2777 
2778 
2779 求出從起點 0 到終點 6 的最短路徑爲 0156,求出最小調整量=6,f[5][6]調整爲 8,f[1][5]調整爲 6,f[0][1]調整爲 10,mincost=179,maxf=10+6=1627806)    k=5,求出 w 以下:
2781 
2782 
2783 
2784 
2785 求出從起點 0 到終點 6 的最短路徑爲 0156,求出最小調整量=1,f[5][6]調整
2786  
2787 
27889,f[2][5]調整爲 1,f[0][2]調整爲 4,mincost=195,maxf=16+1=1727897)    k=6,求出的 w 中沒有增廣路徑,調整結束。對應的最大流以下:
2790 
2791 
2792 
2793 
2794 
2795 最終結果,maxf=17,mincost=195。即運輸的最大貨物量爲 17,對應的最小總運輸費用爲 1952796 10.    解:從兩個方面考慮優化:
27971)    在 Dijkstra 算法中,當求出源點 v 到頂點 u 的最短路徑長度後,僅僅調整從頂點 u 出發的鄰接點的最短路徑長度,而教程中的 Dijkstra 算法因爲採用鄰接矩陣存儲圖, 須要花費 O(n)的時間來調整頂點 u 出發的鄰接點的最短路徑長度,若是採用鄰接表存儲圖,能夠很快查找到頂點 u 的全部鄰接點並進行調整,時間爲 O(MAX(圖中頂點的出
2798 度))。
27992)    求目前一個最短路徑長度的頂點 u 時,教科書上的 Dijkstra 算法採用簡單比較方法,能夠改成採用優先隊列(小根堆)求解。因爲最多 e 條邊對應的頂點進隊,對應的時間爲 O(log2e)。
2800 對應的完整程序和測試數據算法以下:
2801 #include "Graph.cpp" 
2802 #include <queue> #include <string.h> 
2803 using namespace std;                    //包含圖的基本運算算法 
2804 ALGraph *G;                            //圖的鄰接表存儲結構,做爲全局變量 
2805 struct Node                            //聲明堆中結點類型 
2806 {    int i;                              //頂點編號 
2807      int v;                              //dist[i]值 
2808      friend bool operator<(const Node &a,const Node &b) //定義比較運算符 
2809      { return a.v > b.v; } 
2810 }; 
2811 void Dijkstra(int v,int dist[])    //改進的Dijkstra算法 
2812 {    ArcNode *p; 
2813      priority_queue<Node> qu;        //建立小根堆 
2814      Node e; 
2815      int S[MAXV];                //S[i]=1表示頂點i在S中, S[i]=0表示頂點i在U中 
2816      int i,j,u,w; 
2817      memset(S,0sizeof(S)); 
2818      p=G->adjlist[v].firstarc; 
2819      for (i=0;i<G->n;i++) dist[i]=INF; 
2820      while (p!=NULL) 
2821      {    w=p->adjvex; 
2822  
2823           dist[w]=p->weight;        //距離初始化 
2824           e.i=w; e.v=dist[w];        //將v的出邊頂點進隊qu 
2825           qu.push(e); 
2826           p=p->nextarc; 
2827      } 
2828      S[v]=1;                    //源點編號v放入S中 
2829      for (i=0;i<G->n-1;i++)        //循環直到全部頂點的最短路徑都求出 
2830      {    e=qu.top(); qu.pop();    //出隊e 
2831           u=e.i;                    //選取具備最小最短路徑長度的頂點u 
2832           S[u]=1;                //頂點u加入S中 
2833           p=G->adjlist[u].firstarc; 
2834           while (p!=NULL)            //考察從頂點u出發的全部相鄰點 
2835           {    w=p->adjvex; 
2836                if (S[w]==0)        //考慮修改不在S中的頂點w的最短路徑長度 
2837                     if (dist[u]+p->weight<dist[w]) 
2838  
2839  
2840       
2841  
2842       
2843  
2844       
2845  
2846      { 
2847  
2848      dist[w]=dist[u]+p->weight; //修改最短路徑長度 
2849 e.i=w; e.v=dist[w]; 
2850 qu.push(e); //修改最短路徑長度的頂點進隊 
2851                     }     
2852                p=p->nextarc; 
2853           }     
2854      }         
2855 }             
2856 void Disppathlength(int v,int dist[])    //輸出最短路徑長度 
2857 {    printf("從%d頂點出發的最短路徑長度以下:\n",v); 
2858      for (int i=0;i<G->n;++i) 
2859           if (i!=v) 
2860                printf(" 到頂點%d: %d\n",i,dist[i]); 
2861 } 
2862 void main() 
2863 {    int A[MAXV][MAXV]={ 
2864           {0466,INF,INF,INF}, 
2865           {INF,01,INF,7,INF,INF}, 
2866           {INF,INF,0,INF,64,INF}, 
2867           {INF,INF,20,INF,5,INF}, 
2868           {INF,INF,INF,INF,0,INF,6}, 
2869           {INF,INF,INF,INF,108}, 
2870           {INF,INF,INF,INF,INF,INF,0}}; 
2871      int n=7, e=12; 
2872      CreateAdj(G,A,n,e);        //創建圖的鄰接表 
2873      printf("圖G的鄰接表:\n"); 
2874      DispAdj(G);                //輸出鄰接表 
2875      int v=0; 
2876      int dist[MAXV]; 
2877      Dijkstra(v,dist);            //調用Dijkstra算法 
2878      Disppathlength(v,dist);        //輸出結果 
2879      DestroyAdj(G);                //銷燬圖的鄰接表 
2880 } 
2881 上述程序的執行結果如圖 1.49 所示。
2882  
2883 
2884  
2885 
28861.49  程序執行結果
2887 其中 Dijkstra 算法的時間複雜度爲 O(n(log2e+MAX(頂點的出度)),通常圖中最大頂點出度遠小於 e,因此進一步簡化時間複雜度爲 O(nlog2e)。
2888 11.    有一個帶權有向圖 G(全部權爲正整數),採用鄰接矩陣存儲。設計一個算法求其中的一個最小環。
2889 解:利用 Floyd 算法求出全部頂點對之間的最短路徑,若頂點 i 到 j 有最短路徑,而圖中又存在頂點 j 到 i 的邊,則構成一個環,在全部環中比較找到一個最小環並輸出。對應的程序以下:
2890 #include "Graph.cpp"                    //包含圖的基本運算算法#include <vector> 
2891 using namespace std; 
2892 void Dispapath(int path[][MAXV],int i,int j) 
2893 //輸出頂點 i 到 j 的一條最短路徑 
2894 {     vector<int> apath;                     //存放一條最短路徑中間頂點(反向)  
2895      int k=path[i][j];                 
2896      apath.push_back(j);                    //路徑上添加終點 
2897      while (k!=-1 && k!=i)                    //路徑上添加中間點 
2898      {    apath.push_back(k);             
2899           k=path[i][k];             
2900      }             
2901      apath.push_back(i);                    //路徑上添加起點 
2902      for (int s=apath.size()-1;s>=0;s--) //輸出路徑上的中間頂點 
2903           printf("%d→",apath[s]); 
2904 } 
2905 int Mincycle(MGraph g,int A[MAXV][MAXV],int &mini,int &minj) 
2906 //在圖 g 和 A 中的查找一個最小環 
2907 {    int i,j,min=INF; 
2908      for (i=0;i<g.n;i++) 
2909           for (j=0;j<g.n;j++) 
2910                if (i!=j && g.edges[j][i]<INF) 
2911                {    if (A[i][j]+g.edges[j][i]<min) 
2912                     {    min=A[i][j]+g.edges[j][i]; 
2913                          mini=i; minj=j; 
2914  
2915 
2916                     } 
2917                } 
2918      return min; 
2919 } 
2920 void Floyd(MGraph g)                //Floyd 算法求圖 g 中的一個最小環 
2921 {    int A[MAXV][MAXV],path[MAXV][MAXV]; 
2922      int i,j,k,min,mini,minj; 
2923      for (i=0;i<g.n;i++) 
2924           for (j=0;j<g.n;j++)  
2925           {    A[i][j]=g.edges[i][j]; 
2926                if (i!=j && g.edges[i][j]<INF) 
2927                     path[i][j]=i;           //頂點i到j有邊時 
2928                else             
2929                     path[i][j]=-1;          //頂點i到j沒有邊時 
2930           }                 
2931      for (k=0;k<g.n;k++)                        //依次考察全部頂點 
2932      {    for (i=0;i<g.n;i++) 
2933                for (j=0;j<g.n;j++) 
2934                     if (A[i][j]>A[i][k]+A[k][j]) 
2935                     {    A[i][j]=A[i][k]+A[k][j];    //修改最短路徑長度 
2936                          path[i][j]=path[k][j];    //修改最短路徑 
2937                     } 
2938      } 
2939      min=Mincycle(g,A,mini,minj); 
2940      if (min!=INF) 
2941      {    printf("圖中最小環:"); 
2942           Dispapath(path,mini,minj);        //輸出一條最短路徑 
2943           printf("%d, 長度:%d\n",mini,min); 
2944      } 
2945      else printf(" 圖中沒有任何環\n"); 
2946 } 
2947 void main() 
2948 {    MGraph g; 
2949      int A[MAXV][MAXV]={ {0,5,INF,INF},{INF,0,1,INF}, 
2950                           {3,INF,0,2}, {INF,4,INF,0}}; 
2951      int n=4, e=5;     
2952      CreateMat(g,A,n,e);               //創建圖的鄰接矩陣 
2953      printf("圖G的鄰接矩陣:\n");         
2954      DispMat(g);                       //輸出鄰接矩陣 
2955      Floyd(g);         
2956 }             
2957 上述程序的執行結果如圖 1.50 所示。
2958  
2959 
2960  
2961 
29621.50  程序執行結果
2963 1.1010 章─計算幾何
2964 1.10.1    練習題
2965 1.    對如圖 1.51 所示的點集 A,給出採用 Graham 掃描算法求凸包的過程及結果。
2966 
2967 10
2968 9
2969 8
2970 7
2971 6
2972 5
2973 4
2974 3
2975 2
2976 1
2977 1   2  3   4  5   6  7   8   9  10
2978 
29791.51 一個點集 A
2980 2.    對如圖 1.51 所示的點集 A,給出採用分治法求最近點對的過程及結果。
2981 3.    對如圖 1.51 所示的點集 A,給出採用旋轉卡殼法求最遠點對的結果。
2982 4.    對應 3 個點向量 p一、p二、p3,採用 S(p1,p2,p3)=(p2-p1)(p3-p1)/2 求它們構成的三角形面積,請問什麼狀況下計算結果爲正?什麼狀況下計算結果爲負?
2983 5.    已知座標爲整數,給出判斷平面上一點 p 是否在一個逆時針三角形 p1-p2-p3 內部的算法。
2984 1.10.2    練習題參考答案
2985 1.    答:採用 Graham 掃描算法求凸包的過程及結果以下: 求出起點 a0(11)。
2986 排序後:a0(11) a1(81) a2(94) a3(54) a4(87) a5(56) a10(710) a9(35) a6(37) a7(410) a8(16) a11(03)。
2987 先將 a0(11)進棧,a1(81)進棧,a2(94)進棧。處理點 a3(54):a3(54)進棧。
2988 處理點 a4(87):a3(54)存在右拐關係,退棧,a4(87)進棧。
2989  
2990 處理點 a5(56):a5(56)進棧。
2991 處理點 a10(710):a5(56)存在右拐關係,退棧,a10(710)進棧。處理點 a9(35):a9(35)進棧。
2992 處理點 a6(37):a9(35)存在右拐關係,退棧,a6(37)進棧。處理點 a7(410):a6(37)存在右拐關係,退棧,a7(410)進棧。處理點 a8(16):a8(16)進棧。
2993 處理點 a11(03):a11(03)進棧。
2994 結果:n=8,凸包的頂點:a0(11) a1(81) a2(94) a4(87) a10(710) a7(410) a8(16) a11(03)。
2995 2.    答:求解過程以下:
2996 排序前:(11) (81) (94) (54) (87) (56) (37) (410) (16) (35) (710)
2997 (03)。按 x 座標排序後:(03) (11) (16) (37) (35) (410) (54) (56) (710)
2998 (81) (87) (94)。按 y 座標排序後:(11) (81) (03) (54) (94) (35) (16) (56) (37) (87) (410) (710)。
29991)中間位置 midindex=5,左部分:(03) (11) (16) (37) (35) (410);右部分:(54) (56) (710) (81) (87) (94);中間部分點集爲 (03) (37) (410) (54) (56) (710) (87)。
30002)求解左部分:(03) (11) (16) (37) (35) (410)。
3001 中間位置=2,劃分爲左部分 1:(03) (11) (16),右部分 1:(37) (35) (410) 處理左部分 1:點數少於 4:求出最近距離=2.23607,即(03)和(11)之間的距離。處理右部分 1:點數少於 4:求出最近距離=2,即(37)和(35)之間的距離。
3002 再考慮中間部分(中間部分最近距離=2.23)求出左部分 d1=230033)求解右部分:(54) (56) (710) (81) (87) (94)。
3004 中間位置=8,劃分爲左部分 2:(54) (56) (710),右部分 2:(81) (87) (93005  
3006 4)。
3007  
3008 處理左部分 2:點數少於 4,求出最近距離=2,即 (54)和(56)之間的距離。
3009 處理右部分 2:點數少於 4,求出最近距離=3.16228,即(81)和(94)之間的距離。再考慮中間部分(中間部分爲空)求出右部分 d2=230104)求解中間部分點集:(03) (37) (410) (54) (56) (710) (87)。求出最
3011  
3012 近距離 d3=53013 最終結果爲:d=MIN{d1,d2,d3)=23014 3.    答:採用旋轉卡殼法求出兩個最遠點對是(11)和(710),最遠距離爲
3015 10.823016 4.    答:當三角形 p1-p2-p3 逆時針方向時,如圖 1.52 所示,p2-p1 在 p3-p1 的順時針方向上,(p2-p1)(p3-p1)>0,對應的面積(p2-p1)(p3-p1)/2 爲正。
3017 當三角形 p1-p2-p3 順時針方向時,如圖 1.53 所示,p2-p1 在 p3-p1 的逆時針方向上, (p2-p1)(p3-p1)<0,對應的面積(p2-p1)(p3-p1)/2 爲負。
3018  
3019 
3020       
3021 
30221.52   p1-p2-p3 逆時針方向圖    圖 1.53 p1-p2-p3 逆時針方向
3023 5.    答:用 S(p1,p2,p3)=(p2-p1)(p3-p1)/2 求三角形 p一、p二、p3 帶符號的的面積。如圖
3024 1.54 所示,若 S(p,p2,p3)和 S(p,p3,p1)和 S(p,p1,p2)(3 個三角形的方向均爲逆時針方向)均大於 0,表示 p 在該三角形內部。
3025 p3
3026 
3027 
3028 
3029 p1
3030 p2
3031 
30321.54 一個點 p 和一個三角形
3033 對應的程序以下:
3034 #include "Fundament.cpp"                    //包含向量基本運算算法 
3035 double getArea(Point p1,Point p2,Point p3)    //求帶符號的面積 
3036 { 
3037      return Det(p2-p1,p3-p1); 
3038 } 
3039 bool Intrig(Point p,Point p1,Point p2,Point p3) //判斷 p 是否在三角形 p1p2p3 的內部 
3040 {    double area1=getArea(p,p2,p3); 
3041      double area2=getArea(p,p3,p1); 
3042      double area3=getArea(p,p1,p2); 
3043      if (area1>0 && area2>0 && area3>0) 
3044           return true; 
3045      else 
3046           return false; 
3047 } 
3048 void main() 
3049 {    printf("求解結果\n"); 
3050      Point p1(0,0); 
3051      Point p2(5,-4); 
3052      Point p3(4,3); 
3053      Point p4(3,1); 
3054      Point p5(-1,1); 
3055      printf("  p1:"); p1.disp(); printf("\n"); 
3056      printf("  p2:"); p2.disp(); printf("\n"); 
3057      printf("  p3:"); p3.disp(); printf("\n"); 
3058      printf("  p4:"); p4.disp(); printf("\n"); 
3059  
3060      printf(" p5:"); p5.disp(); printf("\n"); 
3061      printf(" p1p2p3三角形面積: %g\n",getArea(p1,p2,p3)); 
3062      printf("  p4在p1p2p3三角形內部: %s\n",Intrig(p4,p1,p2,p3)?"":"不是"); 
3063      printf("  p5在p1p2p3三角形內部: %s\n",Intrig(p5,p1,p2,p3)?"":"不是"); 
3064 } 
3065 上述程序的執行結果如圖 1.55 所示。
30661.55  程序執行結果
3067 1.1111 章─計算複雜性理論
3068 1.11.1    練習題
3069 1.    旅行商問題是 NP 問題嗎?
3070 A.否    B.是    C.至今尚無定論
3071 2.    下面有關 P 問題,NP 問題和 NPC 問題,說法錯誤的是( )。
3072 A.若是一個問題能夠找到一個能在多項式的時間裏解決它的算法,那麼這個問題就屬於 P 問題
3073 B.NP 問題是指能夠在多項式的時間裏驗證一個解的問題C.全部的 P 類問題都是 NP 問題
3074 D.NPC 問題不必定是個 NP 問題,只要保證全部的 NP 問題均可以約化到它便可
3075 3.    對於《教程》例 11.2 設計的圖靈機,分別給出執行 f(32)和 f(23)的瞬像演變過程。
3076 4.    什麼是 P 類問題?什麼是 NP 類問題?
3077 5.    證實求兩個 m 行 n 列的二維矩陣相加的問題屬於 P 類問題。
3078 6.    證實求含有 n 個元素的數據序列中求最大元素的問題屬於 P 類問題。
3079 7.    設計一個肯定性圖靈機 M,用於計算後繼函數 S(n)=n+1(n 爲一個二進制數),並給出求 1010001 的後繼函數值的瞬像演變過程。
3080 1.11.2    練習題參考答案
3081 1.    答:B。
3082 2.    答:D。
3083 3.    答:(1)執行 f(32)時,輸入帶上的初始信息爲 000100B,其瞬像演變過程如
3084  
3085 
3086  
3087 下:
3088 q0000100B  B0q30110B  BB01q210B 
3089  
3090 
3091 Bq100100B  Bq300110B  BB011q20B 
3092  
3093 
3094 B0q10100B  q3B00110B  BB01q311B 
3095  
3096 
3097 B00q1100B  Bq000110B  BB0q3111B 
3098  
3099 
3100 B001q200B  BBq10110B 
3101 BBq30111B 
3102  
3103 
3104 B00q3110B  BB0q1110B 
3105 BBq00111B 
3106  
3107 BBB1q211B 
3108  
3109 BBB11q21B 
3110  
3111 BBB111q2B 
3112  
3113 BBB11q41B 
3114  
3115 BBB1q41BB 
3116  
3117 BBBq41BBB 
3118  
3119 BBBq4BBBB 
3120  
3121 BBB0q6BBB
3122  
3123 最終帶上有一個 0,計算結果爲 131242)執行 f(23)時,輸入帶上的初始信息爲 001000B,其瞬像演變過程以下:
3125  
3126 q0001000B 
3127  
3128 Bq001000B 
3129  
3130 B0q11000B 
3131  
3132 B01q2000B 
3133  
3134 B0q31100B 
3135  
3136 Bq301100B 
3137  
3138 q3 B01100B  BBq31100B 
3139  
3140 Bq001100B  Bq3B1100B 
3141  
3142 BBq11100B  BBq01100B 
3143  
3144 BB1q2100B  BBBq5100B 
3145  
3146 BB11q200B  BBBBq500B 
3147  
3148 BB1q3100B 
3149  
3150 BBBBBq50B 
3151  
3152 BBBBBBq5B 
3153  
3154 BBBBBBBq6
3155  
3156 最終帶上有零個 0,計算結果爲 03157 4.    答:用肯定性圖靈機以多項式時間界可解的問題稱爲 P 類問題。用非肯定性圖靈機以多項式時間界可解的問題稱爲 NP 類問題。
3158 5.    答:求兩個 m 行 n 列的二維矩陣相加的問題對應的算法時間複雜度爲 O(mn),因此屬於 P 類問題。
3159 6.    答:求含有 n 個元素的數據序列中最大元素的問題的算法時間複雜度爲 O(n),因此屬於 P 類問題。
3160 7.    解: q0 爲初始狀態,q3 爲終止狀態,讀寫頭初始時注視最右邊的格。δ 動做函數以下:
3161 δ(q0,0)→(q1,1,L) δ(q0,1)→(q2,0,L) δ(q0,B)→(q3,B,R) δ(q1,0)→(q1,0,L) δ(q1,1)→(q1,1,L) δ(q1,B)→(q3,B,L) δ(q2,0)→(q1,1,L) δ(q2,1)→(q2,0,L) δ(q2,B)→(q3,B,L)
316210100010 的後繼函數值的瞬像演變過程以下:
3163  
3164 B1010001q00B 
3165  
3166 B101000q111B 
3167  
3168 B10100q1011B 
3169  
3170 B1010q10011B 
3171  
3172 B101q100011B
3173  
3174       B10q1100011B 
3175        q3BB10100011B
3176  
3177 B1q10100011B 
3178  
3179 Bq110100011B 
3180  
3181 q1B10100011B
3182  
3183 其結果爲 101000113184  
3185 1.1212 章─機率算法和近似算法
3186 1.12.1    練習題
3187 1.    蒙特卡羅算法是( )的一種。
3188 A.    分枝限界算法    B.貪心算法    C.機率算法    D.回溯算法
3189 2.    在下列算法中有時找不到問題解的是( )。
3190 A.    蒙特卡羅算法    B.拉斯維加斯算法    C.舍伍德算法    D.數值機率算法
3191 3.    在下列算法中獲得的解未必正確的是( )。
3192 A.    蒙特卡羅算法    B.拉斯維加斯算法    C.舍伍德算法    D.數值機率算法
3193 4.    總能求得非數值問題的一個解,且所求得的解老是正確的是( )。
3194 A.    蒙特卡羅算法    B.拉斯維加斯算法    C.數值機率算法    D.舍伍德算法
3195 5.    目前能夠採用( )在多項式級時間內求出旅行商問題的一個近似最優解。
3196 A.    回溯法    B.蠻力法    C.近似算法    D.都不可能
3197 6.    下列敘述錯誤的是( )。
3198 A.機率算法的指望執行時間是指反覆解同一個輸入實例所花的平均執行時間B.機率算法的平均指望時間是指全部輸入實例上的平均指望執行時間
3199 C.機率算法的最壞指望事件是指最壞輸入實例上的指望執行時間
3200 D.機率算法的指望執行時間是指全部輸入實例上的所花的平均執行時間
3201 7.    下列敘述錯誤的是( )。
3202 A.數值機率算法通常是求數值計算問題的近似解B.Monte Carlo 總能求得問題的一個解,但該解未必正確
3203 C.Las Vegas 算法的必定能求出問題的正確解
3204 D.Sherwood 算法的主要做用是減小或是消除好的和壞的實例之間的差異
3205 8.    近似算法和貪心法有什麼不一樣?
3206 9.    給定能隨機生成整數 15 的函數 rand5(),寫出能隨機生成整數 17 的函數rand7()。
3207 1.12.2    練習題參考答案
3208 1.    答:C。
3209 2.    答:B。
3210 3.    答:A。
3211 4.    答:D。
3212 5.    答:C。
3213 6.    答:對機率算法一般討論平均的指望時間和最壞的指望時間,前者指全部輸入實例上平均的指望執行時間,後者指最壞的輸入實例上的指望執行時間。答案爲 D。
3214 7.    答:一旦用拉斯維加斯算法找到一個解,那麼這個解確定是正確的,但有時用拉斯維加斯算法可能找不到解。答案爲 C。
3215 8.    答:近似算法不能保證獲得最優解。貪心算法不必定是近似算法,若是能夠證實
3216  
3217 
3218 決策既不受以前決策的影響,也不影響後續決策,則貪心算法就是肯定的最優解算法。
3219 9. 解:經過 rand5()*5+rand5()產生 6789,…,262728293025 個整數,每一個整數 x 出現的機率相等,取前面 3*7=21 個整數,捨棄後面的 4 個整數,將{678}轉化成 1,{91011}轉化成 2,以此類推,即由 y= (x-3)/3 爲最終結果。對應的程序以下:
3220 #include <stdio.h> 
3221 #include <stdlib.h> 
3222 #include <time.h>                    //包含產生隨機數的庫函數 
3223 int rand5()                            //產生一個[1,5]的隨機數 
3224 {    int a=1,b=5;                 
3225      return rand()%(b-a+1)+a; 
3226 } 
3227 int rand7()                    //產生一個[1,7]的隨機數 
3228 {    int x; 
3229      do 
3230      { 
3231           x=rand5()*5+rand5(); 
3232      } while (x>26); 
3233      int y=(x-3)/3; 
3234      return y; 
3235 } 
3236 void main() 
3237 {     srand((unsigned)time(NULL));     //隨機種子 
3238      for (int i=1;i<=20;i++)          //輸出 20 個[1,7]的隨機數 
3239           printf("%d ",rand7());     
3240      printf("\n");     
3241 }         
3242 上述程序的一次執行結果如圖 1.56 所示。
32431.56    程序執行結果
相關文章
相關標籤/搜索