NJU-高级算法-汉诺塔

 

Description

汉诺塔问题中限制不能将一层塔直接从最左侧移动到最右侧,也不能直接从最右侧移动到最左侧,而是必须经过中间。求当有N层塔的时候移动步数。

Input

输入第一行为用例个数, 每个测试用例输入的第一行为N。

Output

移动步数。

Sample Input 1

1
2

Sample Output 1

8

 

思路

思路一:递归

倒着看这个问题,把前n-1个盘子看作一个整体,则问题变为如何将处于A柱的压在n-1个盘子下的第n个盘子移动到C柱。

step1. 将前n-1个盘子从A柱移动到B柱

step2. 将前n-1个盘子从B柱移动到C柱

step3. 将第n个盘子从A柱移动到B柱

step4. 将前n-1个盘子从C柱移动到B柱

step5. 将前n-1个盘子从B柱移动到A柱

step6. 将第n个盘子从B柱移动到C柱

step7. 将前n-1个盘子从A柱移动到B柱

step8. 将前n-1个盘子从B柱移动到C柱

可以看到,n-1个盘子只要移动就要连续移动两次,期间第n个盘子移动两次,从而得出递归公式:

hanoi(n) = 3 * hanoi(n-1) + 2

递归边界条件是n==1时,只需将盘子从A移动到B,再从B移动到C,共2次

代码如下:

#include <iostream>
using namespace std;

int hanoi(const int &n) {
	if (n == 1) return 2;
	else return 3 * hanoi(n - 1) + 2;
}

int main()
{
	int t;
	//scanf("%d", &t);
	cin >> t;
	for (int turn = 0; turn < t; turn++) {
		int n;
		//scanf("%d", &n);
		cin >> n;

		int step = 0;
		step = hanoi(n);
		if (turn + 1 == t) {
			printf("%d", step);
		}
		else {
			printf("%d\n", step);
		}

	}
	return 0;
}

 

思路二:非递归——栈

修改后的的汉若塔问题不能在从“左”直接移到“右”,也不能直接“右”直接移到“左”,而是要经过中间的过程。也就是说,实际只有4个动作 “左”到“中”、“中”到“右”、“右”到“中”、“中”到“左”
   现在我们把左、中、右三个地点抽象成栈,依次记为LS、MS和RS。最初所有的塔都在LS上。那么四个动作就可以看作是:某一个栈(src)把栈顶元素弹出,然后压入另一个栈里(dst),作为这一个栈(dst)是栈顶。
  例如,如果是7层塔,在最初时所有的塔都在LS上,LS从栈顶到栈底就依次1~7,如果现在发生了“左”到“中”的动作,这个动作对应的操作是LS栈将栈顶元素1弹出,然后1压入到MS栈中,成为MS的栈顶。其他操作同理。


  一个动作能发生的先决条件是不违反小压大的原则。
  src栈弹出的元素src.top()如果想压入到dst栈中,那么num的值必须小于当前to栈顶。
  还有一个原则不是很明显,但也非常重要,叫相邻不可逆原则,解释如下:
   1. 我们把4个动作依次定义为:L -> M,M -> L、M -> R和R -> M。
   2. 很明显,L -> M和M -> L过程互为逆过程,M -> R和R -> M互为逆过程,
   3. 在修改后的汉若塔游戏中,如果想走出最少步数,那么如何两个相邻的动作都不是互为逆的过程的。举例:如果上一步的动作是L -> M,那么这一步绝不是M -> L,直观地解释为:你在上一步把一个栈顶数从“左”移动到“右”,这一步为什么又要移回去呢?这必然不是取得最小步数的作法。同理,M -> R动作和R -> M动作也不可能相邻发生。


  有了小压大和相邻不可逆序原则后,可以推导出两个十分有用的结论——非递归的方法核心结论:
   1.游戏的第一个动作一定是L -> M,这显而易见的。
   2.在走出最少步数过程中的任何时刻,4个动作只有一个动作不违反小压大和相邻不可逆原则,另外三个动作一定都会违反。
  对于结论2,现在进行简单的证明
  因为游戏的第一个动作已经确定是L -> M,则以后的每一步都会有前一步动作。
  假设前一步的动作是L -> M:
   1. 根据小压大原则,L -> M的动作不会重复发生
   2. 根据相邻不可逆原则,M -> L的动作也不该发生
   3. 根据小压大原则,M -> R和R -> M只有一个达标
  假设前一步的动作是M -> L:
   1. 根据小压大原则,M -> L的动作不会重复发生
   2. 根据相邻不可逆原则,L -> M的动作也不该发生
   3. 根据小压大原则,M -> R和R -> M只有一个达标
  假设前一步的动作是M -> R:
   1. 根据小压大原则,M -> R的动作不会重复发生
   2. 根据相邻不可逆原则,R -> M的动作也不该发生
   3. 根据小压大原则,M -> L和L -> M只有一个达标
  假设前一步的动作是R -> M:
   1. 根据小压大原则,R -> M的动作不会重复发生
   2. 根据相邻不可逆原则,M -> R的动作也不该发生
   3. 根据小压大原则,M -> L和L -> M只有一个达标
  综上所述,每一步只会有一个动作达标,那么只要每一步都根据这两个原则考查所有的动作就可以,那个动作达标就走哪一个动作,反正每一次都只有一个动作满足要求,按顺序走下来即可。

代码如下:

#include <iostream>
#include <stack>
using namespace std;

enum Action
{
	No = 0,
	LtoM = 1,
	MtoL = -1,
	RtoM = 2,
	MtoR = -2
};

/*
 *函数介绍:移动汉诺塔上的圆盘。
 *输入参数:record为上一个移动的步骤;now为即将移动的步骤;src为源栈;dst为目的栈。
 *输出参数:pre记录即将发生的移动步骤;src移动圆盘后的栈;dst移动圆盘后的栈。
 *返回值:若圆盘移动成功,则返回移动歩数1;若失败,则返回移动歩数0。
 */
int moveDisc(Action& record, Action now, stack<int>& src, stack<int>& dst)
{
	/*
	 *一个动作能发生的先决条件是:
	 *1.不违反小压大的原则
	 *	src栈弹出的元素src.top()如果想压入到dst栈中,那么num的值必须小于当前to栈顶
	 *2.相邻不可逆原则
	 *	a. 我们把4个动作依次定义为:L -> M,M -> L、M -> R和R -> M。
    *	b. 很明显,L -> M和M -> L过程互为逆过程,M -> R和R -> M互为逆过程,
    *	c. 在修改后的汉若塔游戏中,如果想走出最少步数,那么如何两个相邻的动作都不是互为逆的过程的。
	 *
	 *综上总结出非递归的方法核心结论:
	 *	1.游戏的第一个动作一定是L -> M,这显而易见的。
   *	2.在走出最少步数过程中的任何时刻,4个动作只有一个动作不违反小压大和相邻不可逆原则,另外三个动作一定都会违反。
	 *
	 *
	 */
	if (!src.empty() && (abs(record) != abs(now)) && (dst.empty() || src.top() < dst.top()))
	{
		dst.push(src.top());
		src.pop();
		/*switch (now)
		{
		case 1:
			cout << "Move " << dst.top() << " from left to mid;" << endl;
			break;
		case -1:
			cout << "Move " << dst.top() << " from mid to left;" << endl;
			break;
		case 2:
			cout << "Move " << dst.top() << " from right to mid;" << endl;
			break;
		case -2:
			cout << "Move " << dst.top() << " from mid to right;" << endl;
			break;
		default:
			break;
		}*/
		record = now;
		return 1;
	}
	return 0;
}

/*
 *函数介绍:解决限制移动方式的汉诺塔问题的C++实现
 *输入参数:layer为汉诺塔的层数
 *输出参数:无
 *返回值:为移动的歩数
 */

int hanoiStack(int layer)
{
	//定义3个栈分别表示左中右三个柱子
	stack<int> lS, mS, rS;
	//先将所有盘子以下层比上层大的顺序压入左栈
	for (int i = layer; i > 0; i--)
	{
		lS.push(i);
	}
	//初始情况,前一次Action为No
	Action record = No;
	int step = 0;

	/*
	 *修改后的的汉若塔问题不能在从“左”直接移到“右”,也不能直接“右”直接移到“右”,而是要经过中间的过程。
	 *也就是说,实际只有4个动作 “左”到“中”、“中”到“右”、“右”到“中”、“中”到“左”
    *现在我们把左、中、右三个地点抽象成栈,依次记为LS、MS和RS。最初所有的塔都在LS上。
     *那么四个动作就可以看作是:
	 *某一个栈(src)把栈顶元素弹出,然后压入另一个栈里(dst),作为这一个栈(dst)是栈顶。
	 */
	while (rS.size() != layer)
	{
		step += moveDisc(record, LtoM, lS, mS);
		step += moveDisc(record, MtoL, mS, lS);
		step += moveDisc(record, RtoM, rS, mS);
		step += moveDisc(record, MtoR, mS, rS);
	}
	return step;
}

int main()
{
    int t;
	//scanf("%d", &t);
	cin >> t;
	for (int turn = 0; turn < t; turn++) {
		int n;
		//scanf("%d", &n);
		cin >> n;

		int step = 0;
		step = hanoiStack(n);
		if (turn + 1 == t) {
			printf("%d", step);
		}
		else {
			printf("%d\n", step);
		}

	}
	return 0;
}

 

参考文章:

用栈来求解汉诺塔问题

用栈求解汉诺塔问题

 

最后再来回顾下经典汉诺塔问题

汉诺塔问题(又称为河内塔问题),是一个大家熟知的问题。在left,mid,right三根柱子上,有n个不同大小的圆盘(假设半径分别为1到n吧),一开始他们都叠在我A上(如图所示),你的目标是在最少的合法移动步数内将所有盘子从left塔移动到right塔。
游戏中的每一步规则如下:
1. 每一步只允许移动一个盘子(从一根柱子最上方到另一个柱子的最上方)
2. 移动的过程中,你必须保证大的盘子不能在小的盘子上方(小的可以放在大的上面,最大盘子下面不能有任何其他大小的盘子)

思路

递归

如果只有1个盘子,则直接将其从left移动到right上;

否则,就执行以下步骤:

step1.前n-1个从left借助right移动到mid上

step2.第n个从left直接移动到right上

step3.前n-1个从mid借助left移动到right上

每轮前n-1个移动2步,第n个移动1步

代码

#include <iostream>
#include <string>
using namespace std;

int hanoiCount(int n) {
	int res = 0;
	if (n == 1) {
		res = 1;
	}
	else {
		/*
		  step1.前n-1个从left借助right移动到mid上
		  step2.第n个从left直接移动到right上
		  step3.前n-1个从mid借助left移动到right上
		  每轮前n-1个移动2步,第n个移动1步
		  */
		res = 2 * hanoiCount(n - 1) + 1;
	}
	return res;
}

void hanoi(int n, string from, string mid, string to) {
	if (n == 1) {
		cout << "move 1 from " << from << " to " << to << endl;
	}
	else {
		hanoi(n - 1, from, to, mid);
		cout << "move " << n << " from " << from << " to " << to << endl;
		hanoi(n - 1, mid, from, to);
	}
}

int main()
{
	int t;
	cin >> t;
	for (int turn = 0; turn < t; turn++) {
		int n;
		cin >> n;
		int res = hanoiCount(n);
		hanoi(n, "left", "mid", "right");
		cout << "Move " << res << " times." << endl;
	}
    return 0;
}

运行结果

 

### 南京大学 NJU-LM 实验室概述 南京大学自然语言处理实验室(NJU-LM)专注于研究和开发先进的自然语言处理技术和应用。该实验室致力于推动多语言处理、机器翻译、对话系统以及大规模预训练模型等方面的发展。 #### 官方网站 官方网址提供了最新的研究成果和技术进展信息,访问者可以获取详细的项目描述、团队成员资料及联系方式等重要资讯[^1]。 #### 主要研究方向 - **多语言互强化效应混合数据集测试**:探索多种语言之间的相互影响及其在开放域信息抽取中的表现[^2]。 - **检索增强生成 (RAG)** :关注于如何利用外部知识库提升生成质量,并跟踪这一领域内的最新动态和发展趋势[^3]。 - **基于LLaMA架构的任务导向型导航与解释生成**:采用改进版的LLaMA框架实现高效路径规划的同时提供易于理解的文字说明[^4]。 #### 科研成果与发表论文 近年来,南京大学 NJU-LM 取得了多项显著成就,在顶级国际会议如 EMNLP 上获得了最佳数据/资源奖项的认可;此外还发表了有关跨语言迁移学习、对话管理等多个主题的重要文章。 ```python import requests from bs4 import BeautifulSoup def get_nju_lm_publications(): url = 'http://nlp.nju.edu.cn/publication.html' response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') publications = [] for item in soup.select('.publication-item'): title = item.find('h3').text.strip() authors = ', '.join([author.text for author in item.select('.authors span')]) venue_year = item.find(class_='venue-year').text.strip() publication_info = { "title": title, "authors": authors, "venue_year": venue_year } publications.append(publication_info) return publications[:5] publications = get_nju_lm_publications() for pub in publications: print(f"{pub['title']} by {pub['authors']}, published at {pub['venue_year']}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值