递归思路:
第一,把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柱上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)、自底向上地计算最优解的值
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
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)
-----------------------------------------------------------------------------------------
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
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]; //已知最优解步数
#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;
}
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;
}