算法之递归法(汉诺塔问题)

递归法

特性(直接或间接地调用自己本身)

  • 求解规模为n的问题可以转化为一个或多个结构相同、规模较小的问题,然后从这些小问题的解能方便地构造出大问题的解
  • 递归调用的次数必须是有限的
  • 必须有结束递归的条件(边界条件)来终止递归

计算递推式通常有3种方法

  1. 替换方法
    • 要求首先猜测递推式的解,然后用归纳法证明
  2. 迭代方法
    • 将递推式先转换成一个和式,然后计算该和式,得到渐进复杂度
  3. 公式法(主方法)
    • 对于形如以下的递归方程,可以使用公式法,更加方便快捷地得到解

𝑻(𝒏)=𝒂𝑻(𝒏/𝒃)+𝒇(𝒏)

式中 :

  • 𝒂≥𝟏和𝒃>𝟏是常数
  • 𝒇(𝒏)是一个渐近正函数
  • 𝒏/𝒃 指 ⌊𝒏/𝒃⌋或⌈𝒏/𝒃⌉



迭代推导 :

Formula_iteration.png

Master theorem :

master_theorem.png

Case1 :

case1

Case2 :

case2

Case3 :

case3




递归法应用举例

  1. 汉诺塔问题( 把大象放进冰箱需要几步? )

    问题是这样的 :

    • 话说印度教的主神大梵天创造世界的时候,在印度北部佛教圣地贝拿勒斯神庙里,安放了一块黄铜板,板上插着三根金刚石柱子,在其中一根柱子上从下往上按照大小顺序摞着64片黄金圆盘
    • 大梵天命令婆罗门按照以下规则把圆盘按大小顺序重新摆放在另一根柱子上 :
      • 在三根柱子之间一次只能移动一个圆盘。
      • 移动的时候始终只能小圆盘压着大圆盘
      • 盘子只能在三个柱子上存放



    寺院里的僧侣依照一个古老的预言,以上述规则移动这些盘子;预言说当这些盘子移动完毕,世界就会灭亡。这个传说叫做梵天寺之塔问题

    示意图 :

    hannuota


思路 :

  1. 先将最上面的63个盘子想办法按照规则都移动到 Z 柱子上去
  2. 再把最下面的第64个盘子移到 Y 柱子上
  3. 最后把 Z 柱子上的63个盘子想办法按照规则移动到 Y 柱子上去

难点 :
上述提到的想办法

下面根据一个XMind脑图试着理解 :


设柱子的编号为 A , B , C,一共有n个盘子

XMind汉诺塔.png

注 : 问题的实质其实就是每次移动盘子的时候从 n 到 n-1 最后到 1,一直到最容易移动的那一步


代码实现 : (C语言)

#include <stdio.h>

//定义要递归的函数Hanoi
//这里的形参 A , B , C 并不是 A柱, B柱, C柱,而是算法中需要抽象的三个柱子
void Hanoi(int n,char A, char B, char C)
{
    /*  思路 : 
     *
     *  设盘子从上到下编号依次递增 1 , 2 , 3 , 4......,柱子的编号为 A , B , C
     *
     *  如果只有一个盘子
     *  直接将A上的盘子从A移到C
     *
     *  否则 :
     *  先将  A 柱子上的 n-1 个盘子借助 C 移到 B
     *  直接将A柱子上的盘子移到 C
     *  最后将 B 上的 n-1 个盘子借助 A 移到 C
     */
    if(1 == n)
    {
        printf("将编号为%d的盘子从%c移到%c\n",n,A,C);
    }
    else{
        //!!以下的柱子的编号并不都是真实的柱子编号!!
    
        //将A柱子的n-1个盘子借助C柱子移动到B柱子
        Hanoi(n-1,A,C,B);
        
        //将A柱子上的盘子移到 C
        printf("将编号为%d的盘子从%c移到%c \n",n,A,C);
        
        //将B柱子的n-1个盘子借助A柱子移动到C柱子
        Hanoi(n-1,B,A,C);
    }
}

int main()
{
    //设盘子从上到下编号依次递增 1 , 2 , 3 , 4......,柱子的编号为 A , B , C
    
    //设一共有n个盘子
    int n;

    printf("Please input number of plate : ");
    scanf("%d",&n);

    Hanoi(n,'A','B','C');

    return 0;
}

汉诺塔运行结果

我们不难发现,原来很复杂的问题,通过递归的思想,计算机可以告诉我们每一步该怎么做。


分析汉诺塔问题,移动盘子所花费的时间可表示为 :

汉诺塔花费时间


经过归纳法证明 :

汉诺塔计算结果


也就是说,如果真的要完成64个盘子的移动的话,需要2的64次方减1步,假设1s移动一次的话,需要5800亿年,而宇宙诞生到现在才只有138亿年,太阳的寿命还剩50亿年,所以我们并不需要为世界会毁灭这件事情担忧,因为那已经是宇宙毁灭之后的事儿了。【手动狗头】



  1. 斐波那契数列问题

#include <stdio.h>

int main() {
    int n;
    printf("Please input a number(this number is Fibonacci's item) : ");
    scanf("%d",&n);
    printf("斐波那契数列的第%d项是%d\n",n,Fibonacci(n));
    return 0;
}

int Fibonacci(int n){
    if(n <= 1){
        return 0;
    }
    else if(n == 2){
        return 1;
    }else{
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}


#include <stdio.h>

int main()
{
    int i, n, t1 = 0, t2 = 1, nextTerm;

    printf("输出几项: ");
    scanf("%d", &n);

    printf("斐波那契数列: ");

    for (i = 1; i <= n; ++i)
    {   
        printf("%d, ", t1);
        nextTerm = t1 + t2;
        t1 = t2;
        t2 = nextTerm;
    }
    return 0;
}
  1. 八皇后问题

JS代码

function queen(a, cur) {
  if (cur == a.length) {
    console.log(a);
    return
  };
  for (var i = 0; i < a.length; i++) {
    a[cur] = i;
    var flag = true;
    for (var j = 0; j < cur; j++) {
      var ab = i - a[j];
      if (a[j] == i || (ab > 0 ? ab : -ab) == cur - j) {
        flag = false;
        break
      };
    };
    if (flag) {
      queen(a, cur + 1)
    };
  };
};
queen([1, 1, 1, 1, 1, 1, 1, 1], 0)

C

#include<stdio.h>

//定义一共有8行8列
#define line 8

void queen(int i,int j);
int check(int i,int j);
//定义棋盘
int chess[line][line];

//情况数,避开case关键字
int cas=0;
//定义行和列的数字
int xx,yy;


//主函数
int main(){
    queen(0,0);
    printf("%d\n",cas);
    return 0;
}

//定义递归函数
void queen(int i,int j){

    //越界
    if(j >= line){
        return ;
    }

    //如果能放
    if(check(i,j) == 1){
        //放皇后
        chess[i][j] = 1;
        //如果是最后一行,记录情况
        if(i == line - 1){
            cas++;
//            下面是输出每种棋盘结果,供测试
//             for (xx=0;xx<8;xx++)
//
//                 for(yy=0;yy<8;yy++){
//
//                printf("%d",chess[xx][yy]);
//
//                if(yy==7)
//
//                    printf("\n");
//                }
//             printf("\n");
//            上面是输出结果
        }

        else{
            //不是最后一行就分析下一行
            queen(i+1,0);
        }
    }
    //如果此位置不能放,就置空(0),判断旁边的格子。
    //如果此位置能放,走到这里就意味着上面的代码全部执行了,把皇后拿走(置零),再讨论其他情况,拿旁边位置试探。
    chess[i][j]=0;
    queen(i,j+1);

}

int check(int i,int j){
    int k;
    for(k = 0;k < line;k++){
        if(chess[i][k] == 1)
            return 0;//0=不能放
    }
    for(k = 0;k < line;k++){
        if(chess[k][j] == 1)
            return 0;
    }
    for(k =- line;k <= line;k++){//两对角线

        if(i + k >=0 && i + k < line && j + k >= 0 && j + k < line)//从左上到右下对角线

            if(chess[i + k][j + k] == 1)
                return 0;

        if(i - k >= 0 && i - k < line && j + k >=0 && j + k < line)//从左下到右上对角线

            if(chess[i - k][j + k] == 1)
                return 0;

    }
    return 1;
}



后续还会持续更新,不足之处还请大家指出,一起讨论。


参考资料 :

https://www.bilibili.com/video/BV1gJ41177fX?from=search&seid=16055103741994381080

https://www.bilibili.com/video/BV11s41167h6?p=57

https://mooc1-1.chaoxing.com/coursedata/toPreview?courseId=205932802&dataId=168391232&objectId=3d96da9f2c588bf19da19b027209dcb6

https://www.cnblogs.com/cnnnnnn/p/8506883.html

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值