汉诺塔问题C语言非递归算法

汉诺塔问题(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);
		}
	}
}

这里使用代码分解问题的步骤为:

  1. 是否能够直接移动,无需借助,可以就直接移动并打印出来。不能则下一步
  2. 不能直接解决,则需要分解出两个问题,一是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 
#include 

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;
}

后记

好像我已经很久没更新博客了,这次心血来潮,想分享一下在数据结构学习上的一些心得,本篇文章从构思到编写,花费的时间远远超过我学习这个思路,敲代码的时间。果然长时间不更还是有原因的,希望我这不成熟的文章能够帮助到你一点,天天开心!

你可能感兴趣的:(c/c++,c语言,算法,数据结构,c++)