汉诺塔问题(Hanoi)——C语言非递归算法
文章目录
- 汉诺塔问题(Hanoi)——C语言非递归算法
- 前言:
- Hanoi汉诺塔问题解决思想
- 代码实现
- 模拟栈函数
- 定义栈元素及其栈
- 栈的基本运算
- 汉诺塔实现函数
- 完整代码
- 后记
前言:
汉诺塔(Hanoi)问题是学习递归算法时一个很经典的例子,通过递归算法解决,在C站上很多很多,今日就跟着鸡翅一起学习一下非递归算法吧!这次使用的是栈堆
的数据结构。
Hanoi汉诺塔问题解决思想
这里使用的非递归算法,本质上是模仿递归中大问题化为小问题解决的思想,使用栈堆来将大问题分解为几个按顺序解决的小问题。问:如何模仿?
答:
首先,举个例子,假设塔的数量为 n=3, 三根柱子分别命名为 a,b,c,要想全部搬到c柱,我们就必须先将最底下的盘子移动到c柱。所以我们遇到第一个的问题是,如何第3个(n)盘子从a柱借助b柱移动c柱,因为大不能压小,每次只能移动最上面的一个,所以解决这个问题的**基础(base)**应该是把上面两个(n-1)盘子从a借助c移动到b,这样我们就可以直接移动最地下的盘子,问题便得到解决。解决第一个问题之后,接下来的问题为将在b柱上的2(n-1)个盘子借助a柱移动到c柱子(next),要想解决这第二个问题,就必须将上面第一个盘子借助c柱移动到a柱,这样就可以直接移动最底下的盘子,以此类推,将每一个大问题分解为可以执行的小问题逐步解决。
第n个盘子从a柱借助b柱移动到c柱
将上面n-1个盘子借助c柱放到b柱
第n-1个盘子从b柱借助a柱移动到c柱
将上面n-2个盘子放到a柱
...
直接移动第n个盘子到c柱 问题解决
代码实现
模拟栈函数
先写一些模拟栈的基本运算函数以及构建栈
定义栈元素及其栈
typedef struct{ //先定义栈元素
int n;
//n表示当前是第几个圆盘
char a,b,c;
// a,b,c代表三个柱子,a为当前圆盘所在柱子, b为可以借助的柱子, c为目标柱子,即要放到的那个位置的柱子
bool flag;
//代表此时圆盘可以移动不
}elem;
typedef struct {
int top; // top标记栈顶元素
elem e[64];
}stack;
这里有一点需要说明,这里的a,b,c柱子只需要认为是该盘子所在的柱子,可以借助的柱子以及目标柱子即可,这样可以方便分解问题。
栈的基本运算
// 栈定义函数 st为栈类型变量
void init(stack* &st)
{
st = (stack*) malloc (sizeof(stack));
st->top = -1;
}
// 栈销毁函数
void free_stack(stack* &st)
{
free(st);
}
// 出栈函数
void pop(stack* &st,elem &e)
{
e = st->e[st->top];
st->top--;
}
// 进栈函数
void push(stack* &st,elem &e)
{
st->top++;
st->e[st->top] = e;
}
// 判断是否为空栈
bool empty(stack* &st)
{
return st->top != -1;
}
这里是栈的基本运算函数,由于我懒
汉诺塔实现函数
void Hanoi(stack* &st, int n) //n为盘子的数量
{
init(st); //先初始化
elem e, e_next, e_finish, e_base;
//e为当前需要解决的问题,e_next为当前解决完之火需要解决的问题
//e_finish用来标记打印e问题已经完成, e_base是为了能直接解决e问题所需要解决的问题
e.a = 'a', e.b = 'b', e.c='c',e.n = n;
// 第一个需要解决的问题永远都是将第n个盘子由a柱子借助b柱子移动到c柱子
if(e.n == 1)
e.flag = true;
else
e.flag = false;
push(st,e); //将问题入栈
while(empty(st)) //当栈空就已经全部解决
{
pop(st,e); //解决栈顶的基础问题
if(e.flag == true) //此问题已经能够解决就打印出来,
printf("\t把第%d盘从%c放到%c上\n",e.n,e.a,e.c);
else //不能解决就分解问题
{
// 无法直接解决e问题,需要借助b柱子,将上面n-1个盘子移动到b柱
e_base.n = e.n -1;
e_base.a = e.a;
e_base.b = e.c;
e_base.c = e.b;
//因为移动到b柱子,如果无法直接解决,需要借助c柱子,所以这里b,c对调
if(e_base.n == 1)
e_base.flag = true;
else
e_base.flag = false;
// 解决base问题之后,第n个盘子上面已经没有盘子,e问题就能直接解决了,这里的flag直接为true
e_finish.n = e.n;
e_finish.a = e.a;
e_finish.b = e.b;
e_finish.c = e.c;
e_finish.flag = true;
e_next.n = e.n - 1;
e_next.a = e.b;
e_next.b = e.a;
// 因为e是无法直接解决的,需要借助b柱子解决,所以当e完成之后,第n-1个盘子是在b柱子,
//所以这里的a,b互相调换, 此时第n-1个盘子在b柱上
e_next.c = e.c;
if(e_next.n == 1) //如果是第一个就能直接解决
e_next.flag = true;
else
e_next.flag = false;
//因为栈先入后出的特性,这里入栈顺序颠倒
push(st,e_next);
push(st,e_finish);
push(st,e_base);
}
}
}
这里使用代码分解问题的步骤为:
- 是否能够直接移动,无需借助,可以就直接移动并打印出来。不能则下一步
- 不能直接解决,则需要分解出两个问题,一是base,二是next,并有一个finish产生。
base:这是能够直接解决1问题的基础,1问题无法直接解决,则需要借助其他柱子间接解决,base就是间接解决的步骤。如由a柱子借助b柱子移动到c柱子,如何将上面所有盘子移动到b柱子,让最底下的盘子能够直接移动,这就是base问题。
next:当一个问题无法直接解决,就说明上面就有其他盘子压着,所有盘子还没有移动到c柱子,仍要继续移动盘子,继续产生e问题
finish: 因为base完成之后,第n个盘子上面已经完全没有盘子了,可以直接移动,在代码中的作用是用来打印出此问题已经解决。
3.结束
Created with Raphaël 2.3.0 问题e 能否直接解决 结束 分解问题 能否直接解决 ... yes no yes no
完整代码
#include <stdio.h>
#include <malloc.h>
typedef struct{ //先定义栈元素
int n;
//n表示当前是第几个圆盘
char a,b,c;
// a,b,c代表三个柱子,a为当前圆盘所在柱子, b为可以借助的柱子, c为目标柱子,即要放到的那个位置的柱子
bool flag;
//代表此时圆盘可以移动不
}elem;
typedef struct {
int top; // top标记栈顶元素
elem e[64];
}stack;
// 栈定义函数
void init(stack* &st)
{
st = (stack*) malloc (sizeof(stack));
st->top = -1;
}
// 栈销毁函数
void free_stack(stack* &st)
{
free(st);
}
// 出栈函数
void pop(stack* &st,elem &e)
{
e = st->e[st->top];
st->top--;
}
// 进栈函数
void push(stack* &st,elem &e)
{
st->top++;
st->e[st->top] = e;
}
// 判断是否为空栈
bool empty(stack* &st)
{
return st->top != -1;
}
void Hanoi(stack* &st, int n) //n为盘子的数量
{
init(st); //先初始化
elem e, e_next, e_finish, e_base;
//e为当前需要解决的问题,e_next为当前解决完之火需要解决的问题
//e_finish用来标记打印e问题已经完成, e_base是为了能直接解决e问题所需要解决的问题
e.a = 'a', e.b = 'b', e.c='c',e.n = n;
// 第一个需要解决的问题永远都是将第n个盘子由a柱子借助b柱子移动到c柱子
if(e.n == 1)
e.flag = true;
else
e.flag = false;
push(st,e); //将问题入栈
while(empty(st)) //当栈空就已经全部解决
{
pop(st,e); //解决栈顶的基础问题
if(e.flag == true) //此问题已经能够解决就打印出来,
printf("\t把第%d盘从%c放到%c上\n",e.n,e.a,e.c);
else //不能解决就分解问题
{
// 无法直接解决e问题,需要借助b柱子,将上面n-1个盘子移动到b柱
e_base.n = e.n -1;
e_base.a = e.a;
e_base.b = e.c;
e_base.c = e.b;
//因为移动到b柱子,如果无法直接解决,需要借助c柱子,所以这里b,c对调
if(e_base.n == 1)
e_base.flag = true;
else
e_base.flag = false;
// 解决base问题之后,第n个盘子上面已经没有盘子,e问题就能直接解决了,这里的flag直接为true
e_finish.n = e.n;
e_finish.a = e.a;
e_finish.b = e.b;
e_finish.c = e.c;
e_finish.flag = true;
e_next.n = e.n - 1;
e_next.a = e.b;
e_next.b = e.a;
// 因为e是无法直接解决的,需要借助b柱子解决,所以当e完成之后,第n-1个盘子是在b柱子,
//所以这里的a,b互相调换, 此时第n-1个盘子在b柱上
e_next.c = e.c;
if(e_next.n == 1) //如果是第一个就能直接解决
e_next.flag = true;
else
e_next.flag = false;
//因为栈先入后出的特性,这里入栈顺序颠倒
push(st,e_next);
push(st,e_finish);
push(st,e_base);
}
}
}
int main()
{
stack* st; //st为栈变量
int n=3; // n为数量
Hanoi(st,n);
return 0;
}
后记
好像我已经很久没更新博客了,这次心血来潮,想分享一下在数据结构学习上的一些心得,本篇文章从构思到编写,花费的时间远远超过我学习这个思路,敲代码的时间。果然长时间不更还是有原因的,希望我这不成熟的文章能够帮助到你一点,天天开心!