汉诺塔的非递归算法

文章讨论了汉诺塔问题的传统递归解决方案,指出递归算法在处理大量圆盘时效率较低。作者提出了寻找非递归算法的设想,以减少对系统栈的依赖。通过分析2个盘子的情况,作者发现了一种基于步数模式的非递归策略,该策略根据圆盘数量的奇偶性调整移动步骤,并给出了相应的代码实现。
摘要由CSDN通过智能技术生成

对于汉诺塔问题,我们都普遍认为这个是一个典型的递归问题,然而递归需要使用到系统对应的栈,开销比较大,因此我在想使用非递归算法来解决它,然而网上绝大部分的教程都是自己模拟了一个栈,因此我在考虑写一篇blog记录一下。

问题描述

将一个柱子中的所有圆盘移动到另一个柱子,移动过程需遵守以下规则:每次只能移动一个圆盘,而且只能移动某个柱子上最顶部的圆盘;移动过程中,必须保证每个柱子上的大圆盘都位于小圆盘的下面。
在这里插入图片描述

递归算法

这个算法还算比较好理解,我们计A,B,C盘,那么假设我们要把n圆盘,从A移到C,可以拆分为小问题,我们把上面n-1个的圆盘看成整体,让它先到B,那么最下面的一个移动到C,再把n-1个从B移动到C。
这样就把复杂问题,拆分成更简单的问题,那么退出条件就是只剩下一个圆盘,即为,A->C

#include<iostream>
using namespace std;
//算法设计实验1,汉诺塔问题
//采用递归方法解决
//n个棋盘  A B C为基座   最终达成目标A全部移到C
//性能 T(n)=2T(n-1)+1  1忽略 那么时间复杂度为O(2^n),相当的大,因此对于n较大的时候,运行时间较长
void Hanoi1(int n,char A,char B,char C)
{
	if (n == 1)
	{
		cout  << A << " -> "<< C << endl;
		return;
	}
	Hanoi1(n - 1, A, C, B);//相当于先把上面n-1盘子,移到中间
	cout<< A << " -> " << C << endl;
	Hanoi1(n - 1, B, A, C);//相当于先把上面n-1盘子,移到中间
}
int main()
{
	int n;
	cout << "请选择汉诺塔层数:";
	cin >> n;
	//递归算法
	Hanoi1(n, 'A', 'B', 'C');
	return 0;
}

非递归运行结果

非递归算法

当然我参考的时候发现,很多人模拟了栈作为非递归算法,这种当然可以,但是其核心思想还是用栈的方法,实现递归,本质上和系统的栈思想一致。因此我在考虑用真正非递归的思想来解决这个问题。
由于问题较为复杂,因此咱们先举出比较简单的例子。
假设只有2个盘子
在这里插入图片描述

步骤1234567
移动的盘子0102010
步骤0->20->12->10->21->01->20->2

我们可以发现其中是有规律的:

  1. 如果把三个柱子围成一个环,盘子总数为N,
  2. 如果N为偶数:奇数号盘每次2步;偶数号盘每次1步;
  3. 如果N为奇数:奇数号盘每次1步;偶数号盘每次2步;
  4. 当三个柱子都不为空则优先移动最小的盘子。
#define N 150
#define X dish[i].dnum
#define Y dish[i].lnum
#define SP dish[i].peg
//向右走一步
struct dish
{
	int dnum;	//自身编号
	int lnum;	//下面的盘子
	int peg;	//所在的柱子
}dish[N];
//初始化每个柱的状态为空
int s[3] = {};
//向右走一步
int mov1(int sp, int x)
{
	if (s[(sp + 1) % 3] > x)//要移动的目标是可以承载当前盘子的
	{
		//printf("\nmove1\n");
		printf("%d %d --> %d\n", x, sp, (sp + 1) % 3);
		s[dish[x].peg] = dish[x].lnum; //x盘所在柱子减去当前盘,顶盘变为x盘的下一个
		dish[x].lnum = s[(dish[x].peg + 1) % 3]; //x盘的落点即x转移后的下面的盘
		s[(dish[x].peg + 1) % 3] = dish[x].dnum; //x盘转移到的柱子顶盘值变为x盘值
		dish[x].peg = (dish[x].peg + 1) % 3; //x盘所在柱子变为落点柱子
		return 1; //转移成功
	}
	else return 0; //转移失败
}
//向右走两步
int mov2(int sp, int x)
{
	if (s[(sp + 2) % 3] > x)//要移动的目标是可以承载当前盘子的
	{
		//printf("\nmove2\n");
		printf("%d %d --> %d\n", x, sp, (sp + 2) % 3);
		s[dish[x].peg] = dish[x].lnum; //x盘所在柱子减去当前盘,顶盘变为x盘的下一个
		dish[x].lnum = s[(dish[x].peg + 2) % 3]; //x盘的落点即x转移后的下面的盘
		s[(dish[x].peg + 2) % 3] = dish[x].dnum; //x盘转移到的柱子顶盘值变为x盘值
		dish[x].peg = (dish[x].peg + 2) % 3; //x盘所在柱子变为落点柱子
		return 1; //转移成功
	}
	else return 0; //转移失败
}
int main()
{
	int n;
	cout << "请选择汉诺塔层数:";
	cin >> n;
	int i, change = 1, j;
	for (i = 0; i < n; i++)
	{
		dish[i].dnum = i; //作为盘子的唯一标识,顶层为0,最高级的最大盘子为N-1
		dish[i].lnum = i + 1; //底层盘子的下一层为N,即空柱
		dish[i].peg = 0; //初始化,所有盘子都在A柱	
	}
	s[0] = 0; //当前0柱上最顶端的盘子值
	s[1] = n;
	s[2] = n;//空柱,屏蔽大于n的盘子
	while (s[0] < n || s[1] < n)
	{
		j = 0;
		for (i = 0; s[j] <= n && i < n; i++) //不为空柱则向下找盘子
		{
			if (s[0] >= n && s[1] >= n)
				return;
			if (s[dish[i].peg] != dish[i].dnum) //当上面有盘子时不能移动
				continue;
			if ((n % 2 == 0 && X % 2 == 0) || (n % 2 == 1 && X % 2 == 1))
				change = mov1(SP, X);
			else change = mov2(SP, X);
			if (!change)
			{
				if (s[0] < n && s[1] < n && s[2] < n)
				{
					for (int k = 0; s[k] != 0; k++)
						j = k;
				}
				else j = (j + 1) % 3; //该柱上没有盘子可以移动则更换柱子
			}
		}
	}
	return 0;
}

因此可以得到非递归版本的,汉诺塔答案!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值