汉诺塔递归问题进阶

版权声明:[email protected] https://blog.csdn.net/lytwy123/article/details/85175929

1.问题描述

在这里插入图片描述
在这里插入图片描述

2.算法分析

相信大家对汉诺塔经典的递归问题都有所了解,给你ABC三个柱子,n个盘子放在起始柱子A上,盘子的排列从上至下依次有小到大排列。小的盘子必须放在大的盘子上面。我们每次递归前都将n-1个盘子借助C柱子移动至B柱子,然后我们再将最大的盘子移动至C柱子即可。
可以定义一个汉诺塔函数:

void hanoi(int A, int B, int C, int n){
   if (n == 1){
        move(A,C);
        return;
    }
    hanoi(A,C,B,n - 1);
    move(A,C);
    hanoi(B,A,C,n - 1);
}

A表示起始柱子,B表示终点柱子,C表示搬运盘子过程中需要借助的柱子,n表示要搬运的盘子数量。
我们可以将三个柱子作为栈来看待,
在这里插入图片描述
每次拿栈顶的最小的盘子pop出来移动即可。
递归方式;

#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;
stack<int> S[3];
long long count = 0;
long long sum = 0;
void move(int x, int y){
    long long temp = S[x].top();
    S[x].pop();
    S[y].push(temp);
    count++;
    sum += temp;
    // cout << x << "-->" << y << endl;
}

void hanoi(int A, int B, int C, int n){
   if (n == 1){
        move(A,C);
        return;
    }
    hanoi(A,C,B,n - 1);
    move(A,C);
    hanoi(B,A,C,n - 1);
}
int main(){
    int n;
    scanf("%d", &n);
    for (int i = n ; i >= 1 ; i--){
        S[0].push(i);		//S[0]表示的是0号柱子指的就是最初的柱子,我们将盘子压栈进去
    }
    hanoi(0,1,2,n);
     printf("%lld %lld\n",count,sum);
    return 0;
}

显然题目中要求我们计算蒜头君最少搬运次数,上述程序我使用count来计数,每使用一次move那么就相当于搬运一次,而每次搬运盘子所消耗的体力就相当于栈中弹出的盘子的多少号即可。
这么做虽然是正确的,但是当输入60的时候时间会超限制,如下:
在这里插入图片描述
这是因为递归层数越来越大的时候,往往时间效率就会越来越低。我们是否可以降低时间效率呢。那么我们可以采用记忆化的方式,因为递归的话,每一次算大的都要从小算到大,我们就可以弄一个数组来存储他们,这样就不用浪费重复计算的时间了。不过,我们首先要找到递推的公式:
假设我们设定一个f[n]来表示搬运的次数,那么我们可以发现,当盘子数为1的时候只需要搬运一次。即f[1] = 1
而参照上述递归,我们首先将n-1个盘子搬运到B柱子上,在将A柱子上的盘子搬运到C柱子上这里就搬运一次,然后我们在将B柱子上的n-1个盘子搬到C柱子上。
那么我们可以将递推式子可以写成如下:
f[n] = f[n - 1] + 1 + f[n - 1]
再来分析蒜头君所需的力量:
假设一个数组g[n]来表示搬运所消耗的力量,当盘子数为1时,蒜头君搬运一次,所花费的力气就是盘子的编号。那么搬运n个盘子,花费的力气,如上搬运次数一样,只不过我们在中间搬运的盘子的力气等于n,n就代表着盘子编号即是蒜头君算花费的力气。
递推式:
g[n] = g[n - 1] + n + g[n - 1]
那么程序可以这样写出:

#include <iostream>
#include <cstdio>
using namespace std;

long long f[65],g[65]; //f[n]代表的是移动盘子的最小次数,g[n]代表的是移动盘子所需的力气 

int main(){
	int n;
	scanf("%d", &n);
	f[1] = 1; 
	for (int i = 2; i <= n; i++){
		f[i] = 2 * f[i - 1] + 1;
	}
	g[1] = 1;
	for (int i = 2; i <= n; i++){
		g[i] = 2 * g[i - 1] + i;
	}
	printf("%I64d %I64d\n",f[n],g[n]);  //这里的输出控制格式在windows下使用%I64d,Linux下使用%lld
	return 0;
}

3.总结

在递归式子所需要的层数过多的时候,时间效率往往不能使我们满意,这时候我们可以找它们的递推式子,然后通过递推式子求解。这样才会使得程序越来越高效。
欢迎关注www.lyxueit.com

猜你喜欢

转载自blog.csdn.net/lytwy123/article/details/85175929