zoukankan      html  css  js  c++  java
  • 算法期末备考-第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
    ",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('
    ');
    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
    ",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
    ",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
    ",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
    ");
    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('
    ');
    69     }
    70     return 0 ;
    71 }
    棋盘覆盖

     

  • 相关阅读:
    mysql -- 字符串子串
    eclipse 快捷键
    eclipse git --- add to index
    eclipse -- git 同步
    jquery
    烂泥:KVM、kickstart与NFS集成
    烂泥:KVM与kickstart集成
    烂泥:kickstart无人值守安装CentOS6.5
    烂泥:【解决】Ubuntu下使用SSH连接centos系统很慢
    烂泥:CentOS安装及配置TFTP服务器
  • 原文地址:https://www.cnblogs.com/Osea/p/12132492.html
Copyright © 2011-2022 走看看