汉诺塔Hanoi 递归 & 非递归 & 4柱汉诺塔

来源:互联网 发布:微盘源码 编辑:程序博客网 时间:2024/05/31 06:23

递归思路:

第一,把a上的n-1个盘通过c移动到b。
第二,把a上的最下面的盘移到c。
第三,因为n-1个盘全在b上了,所以把b当做a重复以上步骤就好了。

这边还找到一个ppt有助于对汉诺塔的理解,可以下载来看看

非递归:

1)手动模拟栈的状态

#include<stdio.h>
 
#define MAXSTACK 10   /* 栈的最大深度 */
int c = 1; /* 一个全局变量,表示目前移动的步数 */
struct hanoi { /* 存储汉诺塔的结构,包括盘的数目和三个盘的名称 */
    int n;
    char x, y, z;
};
void move(char x, int n, char y) /* 移动函数,表示把某个盘从某根针移动到另一根针 */
{
    printf("%d. Move disk %d from %c to %cn", c++, n, x, y);
}
void hanoi(int n, char x, char y, char z) /* 汉诺塔的递归算法 */
{
    if (1 == n)
        move(x, 1, z);
    else {
        hanoi(n - 1, x, z, y);
        move(x, n, z);
        hanoi(n - 1, y, x, z);
    }
}
void push(struct hanoi *p, int top, char x, char y, char z,int n)
{
    p[top+1].n = n - 1; 
    p[top+1].x = x; 
    p[top+1].y = y; 
    p[top+1].z = z; 
}
void unreverse_hanoi(struct hanoi *p) /* 汉诺塔的非递归算法 */
{
    int top = 0;
    while (top >= 0) {
        while (p[top].n > 1) { /* 向左走到尽头 */
            push(p, top, p[top].x, p[top].z, p[top].y, p[top].n);
            top++;
        }
        if (p[top].n == 1) { /* 叶子结点 */
            move(p[top].x, 1, p[top].z);
            top--;
        }
        if (top >= 0) { /* 向右走一步 */
            move(p[top].x, p[top].n, p[top].z);
            top--;
            push(p, top, p[top+1].y, p[top+1].x, p[top+1].z, p[top+1].n);
            top++;
        }
    }
}
int main(void)
{
    struct hanoi p[MAXSTACK];
    printf("reverse program:n");
    hanoi(3, 'x''y''z');
    printf("unreverse program:n");
     
    c = 1;
    p[0].n = 3;
    p[0].x = 'x', p[0].y = 'y', p[0].z = 'z';
    unreverse_hanoi(p);
    return 0;
}

2)

……

4柱汉诺塔

算法思想:

用如下算法移动盘子(记为FourPegsHanoi):
1)、将A柱上n个盘子划分为上下两部分,下方部分共有k(1≤k≤n)个盘子,上方部分共有n - k个盘子。
2)、将A柱上面部分n–k个盘子使用FourPegsHanoi算法经过C、D柱移至B柱。
3)、将A柱剩余的k个盘子使用ThreePegsHanoi算法经过C柱移至D柱。
4)、将B柱上的n–k个盘子使用FourPegsHanoi算法经过A、C柱移至D柱。
 
ThreePegsHanoi算法如下(设三个柱子分别为A、B、C,A柱上共有k个盘子):
1)、将A柱上方k-1个盘子使用ThreePegsHanoi算法经过B柱移至C柱。
2)、将C柱上最后一个盘子直接移至C盘。
3)、将B柱上k-1个盘子使用ThreePegsHanoi算法经过A柱移至C柱。

算法步骤:

根据动态规划的四个步骤,求解如下:
1)、最优子结构性质:
   四柱汉诺塔问题的最优解是用最少的移动次数将A柱上的盘子全部移到D柱上。当盘子总数为i时,我们不妨设使用FourPegsHanoi的最少移动次数为f(i)。相应的ThreePegsHanoi 算法移动次数为g(k),由于g(k)=2g(k-1)+1=2k -1,当k确定时,g(k)也是不变的。
   f(i)为最优解时,其子问题f(i-k)也必为最优解。如果f(i-k)不是最优解,那么存在f’(i-k) < f(i-k)。用f’(i-k)替换f(i-k)将产生一个比f(i)更优的解。这与f(i)为最优解是矛盾的。所以本问题具有最优子结构性质。
 
2)、递归地定义问题的最优解:
根据上述FourPegsHanoi算法得到最少移动次数f(i):


3)、自底向上地计算最优解的值
求四柱汉诺塔最小移动次数伪代码:
数组下标从0开始,数组m,s大小为n+1。数组m存储计算最小移动次数的中间值。数组s存储每步最小移动次数所对应的分割值k。
-----------------------------------------------------------------------------------------
MinMovements ( n ):
      m[0,0] ← s[0] ← 0 ▹为了方便计算增加了f(0) = m[0,s[0]] = 0   
      for i ← 1 to n
           do min ← ∞
                 for k ← 1 to i
                      do m[i , k] ← 2 * m[i – k , s[i – k]] + 2k – 1
                            if m[i , k] < min
                                  then min ← m[i , k]
                                        s[i] = k
      return m[n , s[n]] & s
-----------------------------------------------------------------------------------------
4)、根据计算信息构造最优解:
MinMovements求出的数组s中,s[i]是f(i)所对应的最优分割位置。根据数组s来构造移动盘子的最佳序列,伪代码如下:
-----------------------------------------------------------------------------------------
FourPegsHanoi (i , src, temp1, temp2, dest):
if i = 1
then move(src , dest)
else FourPegsHanoi(i – s[i] , src , temp2 , dest , temp1)
ThreePegsHanoi(s[i] , src , temp2 , dest)
                  FourPegsHanoi(i – s[i] , temp1 , src , temp2 , dest)
----------------------------------------------------------------------------------------
ThreePegsHanoi(k , src , temp, dest):
           if k = 1
then move(src , dest)
                 else ThreePegsHanoi(k - 1, src , dest , temp)
                        move(src , dest)
                        ThreePegsHanoi(k - 1, temp , src , dest)
-----------------------------------------------------------------------------------------

算法分析:

1、时间复杂度
MinMovements算法的时间复杂度为:
T(n) = 1 + 2 + ... + n = n(n+1)/2 = O(n2)
2、空间复杂度
MinMovements算法占用的空间为m 和 s数组的大小:
即 (n+1)2 + (n+1) = O(n2)
通过分析m数组中记录了一些与结果不相关的数据,所以通过对MinMovements进行改进,可使占用空间减小为O(n)。
MinMovements ( n ):
      m[0] ← s[0] ← 0      
      for i ← 1 to n
           do m[i] ← ∞
                 for k ← 1 to i
                      do q ← 2 * m[i – k] + 2k – 1
                            if q < m[i]
                                  then m[i] ← q
                                        s[i] ← k
      return m[n] & s

代码:

#include <stdio.h> 
#include <limits.h> 

#define N 40 

int sum, //统计步数 
f[N]={0,1}, //最优解步数 
g[N]={0,1}; //划分方案 


int calc(int n) 

int i, t, 
best_part, //最优划分方案 
min=INT_MAX, //最优解步数 
last_n_part=g[n-1]; //n-1 时的最优划分方案 
if (f[n]) return f[n]; //已知最优解步数 
//寻找最优划分方案,i 从 n/2 到 last_n_step 
for (i=n/2; i>=last_n_part; i--) 

t = (calc(n-i)<<1) + (1<<i) - 1; 
if (t<min) min=t, best_part=i; 

return g[n]=best_part, min;



//3汉诺塔子问题
void move_3(int n, char a, char b, char c) 

sum++; 
if (n<2) 

printf("%c-->%c\n", a, c); 
return; 

move_3(--n, a, c, b); 
printf("%c-->%c\n", a, c); 
move_3(n, b, a, c); 



void move(int n, char a, char b, char c, char d) 

if (n<2) 

sum++; 
printf("%c-->%c\n", a, d); 
return; 

calc(n); 
move(n-g[n], a, c, d, b); 
move_3(g[n], a, c, d); 
move(n-g[n], b, a, c, d); 


int main(void) 

int n; 
printf("请输入盘子的个数:");
scanf("%d", &n); 
printf("\n");
if (n<1 || n>=N) 

printf("n 必须是 1~%d中的整数\n", N-1); 
return 1; 

move(n, 'A', 'B', 'C', 'D'); 
printf("步数:%d\n", sum); 
return 0; 
}
0 0
原创粉丝点击