算法期末備考-第7練-遞歸與分治


遞歸與分治

Hanoi塔問題

 

請觀察上圖即可,圖片所顯示其實就是我們處理hanoi塔的三步。

(注意:圖片事網上找來的,漢諾塔問題是從 “A” 借助 “C” 轉移到 “B” )

假設f(x) : 把x個盤子 全部從A借助C轉移到B時 所用的步數。

以上圖舉例子。

1、首先先把4個盤子通過B轉移到C 操作步數為:f(4)

2、然后把最底層的盤子(編號為5)移動到B 操作步數為:1

3、最后把4個盤子通過A轉移到B 操作步數為:f(4)

 

通過上述例子:

可得到:f(5) = 2*f(4)+1

 

隨着盤子的增多,問題其實僅僅是從底層多加了delta層,但解決的步驟依舊一樣。

 

遞歸計算

 1 #include<iostream>
 2 using namespace std;
 3  4 int Hanoi(int x){
 5     if( x == 1 )
 6         return 1 ;
 7     else
 8         return Hanoi(x-1) * 2 + 1 ;
 9 }
10 int main()
11 {
12     int n ;
13     cin >> n ;
14     cout << Hanoi(n) << endl ;
15     return 0;
16 }
遞歸計算漢諾塔

 


 

遞歸記錄路徑

 

 1 #include<iostream>
 2 using namespace std;
 3 //打印其路徑
 4 void Move( char u , char v ){
 5     printf("%c -> %c\n",u,v);
 6 }
 7  8 //起點為A , 過程中借助C , 最后到達B
 9 //( A -> C -> B )
10 void Hanoi( int n , char A , char B , char C ){
11     if( n > 0 ){
12         Hanoi( n - 1 , A , C , B );
13         Move( A , B );
14         Hanoi( n - 1 , C , B , A );
15     }
16 }
17 int main()
18 {
19     int n ;
20     cin >> n ;
21     Hanoi(n,'A','B','C');
22     return 0;
23 }
遞歸計算並記錄路徑-漢諾塔

 

雖然代碼非常簡短,但是其遞歸過程比較復雜。

 

針對每一堆盤子來說,都是分三步,

通過 得知自己所在的位置,借助哪根柱子,最后要移動到哪一個柱子。

但是對於過程中的第一步,其實是遞歸后的結果。

Hanoi( n - 1 , A , C , B ); =>……
Move( A , B );
Hanoi( n - 1 , C , B , A );

 

由這句話往下到下一層

Hanoi( n - 1 , A , C , B )
                                                                        {   => …… (***)
                                        { Hanoi( n - 3 , A , C , B ) => {
   {  Hanoi( n - 2 , A , B , C )    =>  { Move ( A , C )                {
=> {  Move ( A , C )                    { Hanoi( n - 3 , C , B , A )
   {  Hanoi( n - 2 , B , A , C )
 

 

一直往下遞歸,直到如上所示的.

"(***)" 作為第一句,然后回溯依次執行。

……


對於遞歸函數的設計,我們需要做到“整體把握”

但對於具體實現的過程,一定要明確其中的“具體過程”。

如果把該程序比做一棵遞歸樹(三叉樹),打印到屏幕的第一句執行的必定是整棵樹的最左下角的葉子結點。

 

對於問題的闡述:(以圖作為例子)
f(n) -> f(n-1) + 1 + f(n-1)
f(n-1) -> f(n-2) + 1 + f(n-2)
……
        f(2) -> f(1) + "1" + f(1)
        f(0) + 1 + f(0) + "1" + f(0) + 1 + f(0)
              ---
         第一個執行的操作

 

 

 


 

 

切面條

一根高筋拉面,中間切一刀,可以得到2根面條。
如果先對折1次,中間切一刀,可以得到3根面條。
如果連續對折2次,中間切一刀,可以得到5根面條。
那么,連續對折10次,中間切一刀,會得到多少面條呢?

 

題目提及到了“對折”一詞。
​
必定是會使面條加倍
f(0) = 2
f(1) = 3
f(2) = 5
f(3) = 9
f(4) = 17
……
肯定是圍繞着冪次進行找規律。
所以答案就是f(n) = 2^n + 1

 

補充的內容,並不是考點。

快速冪

 

 

算法步驟如上所示:

其實僅僅是利用任何數字轉變成2進制后,

每一個位置上權值要么為‘1’,要么為‘0’的特點。

 

我們可以通過基數和指數相互配合,基數 和底數 同時進行左移。

若當前指數所對應的位置是'1'

ans 必須乘以當前底數

若當前指數所對應的位置為'0'

不執行任何操作

若指數所對應的位置越界時則算法結束,當前對應的ans=pow( base , n )

 

 1 #include<iostream>
 2 using namespace std;
 3  4 //快速冪函數
 5 int qpow( int a , int b ){
 6     int ans = 1 ;
 7     //把目標次冪b ->轉化成2進制.
 8     while( b ){
 9         //如果當前是b最末尾為1答案進行累成
10         if( b % 2 == 1 ){
11             ans = ans * a ;
12         }
13         //基數和次冪同時移位
14         b = b / 2 ;
15         a = a * a ;
16     }
17     //輸出答案
18     return ans ;
19 }
20 int main()
21 {
22     int n = 10 , Base = 2;
23     cout << qpow( Base , n ) + 1 << endl ;
24     return 0;
25 }
快速冪

 

 


 

平面上的直線

【題意】
      平面上有 n 條直線,最多可以把整個平面分成多少份?

    

 


L(0) = 1
L(1) = 2
L(2) = 4
L(3) = 7

 

通過觀察可以得知:

L(n) = L(n-1) + n

= L(n-2) + n-1 + n

= ……

= 1 + ​Sn

 


 

排列問題

 

 

給定 n 個元素,求出這 n 個元素的全排列。

 1 //排列問題
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N = 20 ;
 6 int a[N] ;
 7 void Perm( int S , int E ){
 8     if( S == E ){
 9         for( int i = 1 ; i <= E ; i++ ){
10             printf("%3d",a[i]);
11         }
12         putchar('\n');
13         return ;
14     }
15     for( int i = S ; i <= E ; i++ ){
16         swap( a[S] , a[i] );
17         Perm( S + 1 , E );
18         swap( a[S] , a[i] );
19     }
20 }
21 int main()
22 {
23     int n = 3 ;
24     for( int i = 1 ; i <=n ; i++ )  a[i] = i ;
25     Perm( 1 , n );
26     return 0 ;
27 }
排列問題

 


 

整數划分問題

【題目描述】

將正整數 n 划分為一系列正整數的和:

6=6 6=5+1 6=4+2=4+1+1 6=3+3=3+2+1=3+1+1+1 6=2+2+2=2+2+1+1=2+1+1+1+1 6=1+1+1+1+1+1+1

共11 種情況

書本P14頁有具體的推導過程及解釋

 1 //整數划分問題 - 公式法
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 int solve( int n , int m ){
 6     if( ( n < 1 ) || ( m < 1 ) )
 7         return 0 ;
 8     else if( ( n==1) || ( m==1) )
 9         return 1 ;
10     else if( n < m )
11         return solve( n , n );
12     else if( n == m )
13         return solve( n , m-1 ) + 1 ;
14     else
15         return solve( n , m - 1 ) + solve( n - m , m );
16 }
17 int main()
18 {
19     printf("%d\n",solve(6,6));
20     return 0 ;
21 }
整數划分-公式法

 


 

模擬多項式乘法即可。

具體原理:https://blog.csdn.net/Z_sea/article/details/86529635

 1 //整數划分-母函數
 2 #include<cstdio>
 3 using namespace std;
 4 const int N = 1e3 + 10 ;
 5 int f[N] , tmp[N] ;
 6 int main()
 7 {
 8     int n = 6 ;
 9     f[0] = 1 ;
10     for( int i = 1 ; i <= n ; i ++ ){
11         for( int j = 0 ; j <= n ; j ++ ){
12             for( int k = i ; k <= n ; k += i ){
13                 tmp[j+k] += f[j] ;
14             }
15         }
16         for( int j = 0 ; j <= n ; j++ ){
17             f[j] += tmp[j] ;
18             tmp[j] = 0 ;
19         }
20     }
21     printf("%d\n",f[n]);
22     return 0 ;
23 }
整數划分-母函數

 


 

二分搜索技術

 

 

【題目描述】

給定已排好序的 n 個元素 a[0:n-1],在其中查找一個特定元素 x。

 1 #include<cstdio>
 2 using namespace std;
 3 const int N = 1e3 ;
 4 int a[N] = { 1 , 2 , 3 , 6 , 7 , 11 };
 5 int main()
 6 {
 7     int x ;
 8     bool flag = false ;
 9     scanf("%d",&x);
10     int L = 0 , R = 6 ;
11     while( L <= R ){
12         int Mid = ( L + R ) / 2 ;
13         if( a[Mid] == x ){
14             printf("Index : %d\n",Mid);
15             flag = true ;
16         }
17 18         if( x < a[Mid] ) R = Mid - 1 ;
19         else L = Mid + 1 ;
20     }
21     if( !flag ){
22         printf("Not Found\n");
23     }
24     return 0 ;
25 }
26 /*
27 1
28 Index : 0
29 30 10
31 Not Found
32 */
二分搜索

 


 

棋盤覆蓋

 

      

 

 

 

 

 

【分析】

分四個部分,如果是特殊點標注的部分,則另外3個部分的頂角位置標注,再進行分治。

最后問題會一直標記,問題規模不斷減少,直到為一個格子時則返回。

【具體代碼】

 1 //棋盤覆蓋
 2 #include<cstdio>
 3 const int N = 8 ;
 4 //t(x,y) -> top-left :左上角的坐標
 5 //s(x,y) -> special  :特殊標識的坐標
 6 //L     :   討論當前方格的邊長
 7 //tag   :   時間戳
 8 int chessboard[N][N] ;
 9 int tag = 1 ;
10 void f( int tx , int ty , int sx , int sy , int L ){
11 12     if( L == 1 ){
13         return ;
14     }
15     int t = tag ++ ;
16     int len = L / 2 ;
17 18     //判斷特殊點是否在左上部分?
19     if( sx < tx + len && sy < ty + len ){
20         //繼續分治
21         f( tx , ty , sx , sy , len );
22     }else{
23         //填上序號,並繼續分治
24         chessboard[tx+len-1][ty+len-1] = t ;
25         f( tx , ty , tx+len-1 , ty+len-1 , len );
26     }
27 28     //判斷特殊點是否在右上部分?
29     if( sx < tx + len &&  ty + len <= sy ){
30         //繼續分治
31         f( tx , ty + len , sx , sy , len );
32     }else{
33         //填上序號,並繼續分治
34         chessboard[tx+len-1][ty+len] = t ;
35         f( tx , ty + len , tx+len-1, ty+len , len );
36     }
37 38     //判斷特殊點是否在左下部分?
39     if( tx + len <= sx && sy < ty + len ){
40         //繼續分治
41         f( tx + len , ty , sx , sy , len );
42     }else{
43         //填上序號,並繼續分治
44         chessboard[tx+len][ty+len-1] = t ;
45         f( tx + len , ty , tx+len , ty+len-1 , len );
46     }
47 48     //判斷特殊點是否在右下部分?
49     if( tx + len <= sx && ty + len <= sy ){
50         //繼續分治
51         f( tx + len , ty + len , sx , sy , len );
52     }else{
53         //填上序號,並繼續分治
54         chessboard[tx+len][ty+len] = t ;
55         f( tx + len , ty + len , tx+len , ty+len , len );
56     }
57 58 59 }
60 int main()
61 {
62     //棋盤初始左上角,初始特殊點,邊長為8
63     f( 0 , 0 , 0 , 1 , 8 );
64     for( int i = 0 ; i < 8 ; i++ ){
65         for( int j = 0 ; j < 8 ; j++ ){
66             printf("%4d",chessboard[i][j]);
67         }
68         putchar('\n');
69     }
70     return 0 ;
71 }
棋盤覆蓋

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM