汉诺塔问题

汉诺塔问题(Hanoi)


汉诺塔问题,一直是递归问题的典范。同时这个问题也随着时间的发展而推陈出新,一步步考验着人类的智商。在这里做一些总结,但因笔者才疏学浅。如有不足希望指正。

最基本的问题

题目描述:

汉诺塔由编号为1到n大小不同的圆盘和三根柱子a,b,c组成。开始时,这n个圆盘由大到小依次套在a柱上,如图所示。要求把a柱上n个圆盘按下述规则移到c柱上:

  1. 一次只能移一个圆盘,它必须位于某个柱子的顶部;
  2. 圆盘只能在三个柱上存放;
  3. 任何时刻不允许大盘压小盘。

将这n个盘子从a柱移动到c柱上,最少需要移动多少次?

Solution:

直接递归就可以了,假设最初在a号柱子上有N个盘子,并且由上到下半径依次增大,那么我们可以把上面的n-1个盘子看成一个整体,而我们现在要做的就是把n-1个盘子从a移动到b,然后把最大的盘子由a移动到c再把那n-1个盘子由b移动到c。

其实随着思考的深入,你会发现,其实在 汉诺塔 问题中,我们其实想要实现的就是把n个盘子从一根柱子移动到另一个柱子上,而我们只不过不得不借助另一个柱子来帮助实现而已。

所以,现在我们的原问题就可以转化为一个较简单的子问题,也就是把剩下的n-1个盘子,由一根柱子,转移到另根柱子就可以了。

现在我们可以无限的递归下去,直到只剩下一个盘子,那么答案显然就是1了。这样我们再一点点的回溯回去,就可以得到答案了。

Code:

#include
void hanoi(int n,char a,char b,char c)
{
    if(n==1)
    {
        printf("%d:%c->%c\n",1,a,c);
    }
    else
    {
        hanoi(n-1,a,c,b);//用 b 柱作为协助过度,将 a 上的(n-1)片移到 c 上 
        printf("%d:%c->%c\n",n,a,c);
        hanoi(n-1,b,a,c);//用 c 柱作为协助过度,将 b 上的(n-1)片移到 a 上 
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    hanoi(n,'A','B','C');
    return 0;
}

简单的变形

  • 原问题中,我们是n个盘子,移动到三根柱子上,上面的代码是用了递归的思想进行了路径的输出。

当我们想要知道方案数的时候,不妨可以打表看一下:

n 方案数
3 7
4 15
5 31
6 63

这个时候好像就可以知道好像可以递推出n个盘子的公式:
f(n)=2f(n1)+1 n 的范围是 n3
这是因为,设f(n)表示n个盘子从一根柱子移动到另一根柱子的方案数,那么可以这样理解 :先把n-1个盘子由a->b :f(n-1);再把最大的一个由a->c : +1;再把n-1个盘子由a->c : f(n-1);
so the ans is it.

  • 现在我们换一个角度思考,假设我们有3个盘子,n根柱子,那么结果会有什么变化?

Thinking……………………………………………………………………………………………………..

事实上答案并没有变化,还是 f(n)=2f(n1)+1
这个也很好理解?不对吧。。
往下看
P.S. 细心的你会发现,f(n)=2^n-1;


四柱汉诺塔问题

首先要说明的是,四柱汉诺塔不仅仅是多了一个柱子那么简单,所以现在我们先用正常的思路去思考一下该怎么去实现这个过程。

经过上面的分析我们可以肯定的是,只要一个柱子周围有两个空柱子[或者有半径较大的盘子]就可以把经由那两个柱子的协助来进行移动。所以对于四柱问题我们不妨这样做:

  1. 从A借助C、D将n-2个盘子移动到B上
  2. 将第n-1个盘子移动到C
  3. 将最大的盘子移动到D
  4. 将第n-1个盘子由C移动到D
  5. 从B借助A、C将n-2个盘子移动到D上

所以我们设 F(n)为移动的方案数,可以得到,F(n)=2*F(n-2)+3;

然后分类讨论一下得到一般公式

  • F(n)=4*2^(n/2)-3; n%2!=0
  • F(n)=6*2^(n/2-1)-3; n%2==0

问题到这里就可以说解决成功了。

但是我们是否可以扩展到M根柱子呢?

类似的原理,进行同样的操作。我们可以肯定的是这个思路一定是正确的,但它是不是最优的呢?

其实不然,对于四柱问题我们的想法是保留两个盘子进行移动,那如果5个、6个柱子呢???

当盘子增多时,多余的只有一个盘子的柱子是可以加以利用的。虽然这么做加多了每步移动的次数,但从另一方面减少了递归的数量。所以这个问题就变得复杂起来了。

因为问题需要递归,算法的流程本身也需要递归

1914年,一个名叫J.S.Frame的人提出了一种解决此问题的方法,名叫Frame算法:

在这里只把递归方程列出来:

F(n)=min{2*F(n-r)+2^r-1} (1<=r<=n)

并且其时间复杂度为O(sqrt(2 * n) * 2^sqrt(2*n) )


更改移动方式问题

改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到下盘的上面。现在有N个圆盘,至少多少次移动才能把这些圆盘从最左边移到最右边?

有了以上的经验,做这道题还是比较容易的。我们已经知道可以把上面的(N-x)个盘子看做一个整体,然后只需要找出最少的移动方案然后列出表达式即可。

这道题的唯一不同就是不可以直接从两边移动,但仔细想想这只限制了我们不能将最大的盘子从最左边移动到最右边而已。对于整体而言其实并没有限制,也就是说我们可以一步把一个整体移过去。(想想为什么?)

方案如下:

  1. 将n-1个盘子从A移动到C
  2. 将最大的盘子从A移动到B
  3. 将n-1个盘子从C移动到A
  4. 将最大的盘子从B移动到C
  5. 将n-1个盘子从A移动到C

同样设f(n)表示将n个盘子从A移动到C的最少步数,可以看出步骤1、3、5是f(n-1),2、4都是1

因此f(n)=3*f(n-1)+2;

But ,我们也可以用一些小方法。。。哼哼。。

因为一定要将最大的盘子从A移动到C,所以+2是一定的,但我们如果不确定f(n-1)前面的系数的话可以设一个x然后代入样例,解得x就能够轻松推出公式(我不会告诉你我是先求出x然后再去验证的^O^)

P.S.当然这样的递推式都能用数学公式表达,如有兴趣可以自己推导一下。


询问每点的移动次数

用1,2,…,n表示n个盘子,称为1号盘,2号盘,…。号数大盘子就大。在移动过程中发现,有的圆盘移动次数多,有的少 。 告之盘子总数和盘号,计算该盘子的移动次数.

这道题和之前的差不多少,只是要求的是对于第i个盘子,则我们不需要考虑下面的盘子。

所以可以得到f(n)=2*f(n-1);

其中 f(1)=1;

然后答案就是f(n-m+1);

[看不懂也不要问我,我已经不知道自己在说些什么了。。。]


你可能感兴趣的:(学习札记,递归)