经典算法大全51例——1.河内之塔(又称汉诺塔)

算法目录合集

地址

   算法目录合集

说明

  该地址指向所有由本人自己所经历的算法习题(也有可能仅仅是一个入门的案例或者是经典案例),仅仅为我做过而且比较有意思的,也许还会有一些我自己想出来的,出于兴趣写在这里,具体格式我会在下面列明,总目录也会在这里出现,方便查阅以及自己进行复习回顾.

题目

  河内之塔(Towers of Hanoi)是法国人M.Claus(Lucas)于1883年从泰国带至法国的,河内之塔为越战时北越的首都,即现在的胡志明市;1883年法国数学家Edouar Lucas曾提及这个故事,据说创世纪时Benares有一座波罗教塔,是由三支钻石棒所支撑,开始时神在第一根棒上放置64个由上至下依由小到大排列的金盘(Disc),并命令僧侣将所有的金盘从第一根石棒移至第三根石棒,且搬运过程中遵守大盘子在小盘子之下的原则,若每日仅搬一个盘子,则当盘子全数搬运完毕之时,此塔将毁损,而也就是世界末日的来临之时。

背景

  在以前未曾接触计算机语言的时候接触这个益智游戏是在初中,当时觉得挺有意思。当时玩了3层,4层,5层,6层……对于当时的脑袋来说,就是生推,后来慢慢找到规律了,最下面的是最重要的——即最底层的长条,只有他动了,才是一切的开始。
汉诺塔小游戏视图
  也就是说——为了让最下面的移动到C,就得先把上面的移动到B,其实这就是递归的思想,只不过那时候懂个啥的递归,就当成普通规律去玩儿了,也没得总结。

小游戏

   点击尝试汉诺塔小游戏

原理分析

  分析思路之前还是要说一说递归,什么是递归呢?

递归的介绍

  • 以编程的角度来看,递归指的是方法定义中调用方法本身的现象
  • 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
  • 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算

递归的注意事项

  • 递归一定要有出口。否则内存溢出
  • 递归虽然有出口,但是递归的次数也不宜过多。否则内存溢出

  简单来说就是,我需要求N之前是需要知道N-1的,虽然我不知道,但是我可以假装我知道,这样一直往前推,就推到了N=1 的情况,自然就出来了。

  对于本题目,最重要的一步是什么呢?肯定是把n号盘子从A挪到了C,怎么挪呢?那肯定要思考他的前一步啊,那么他的前一步是什么呢?当然是这个整体从A挪到B为n号盘子腾地方了,那么这一步之后要做什么呢,这也就是最重要的地方了假装N-1已经完成了 我们需要做的就是把这(N-1)一整体从B(为什么从B呢?因为A只剩下n号盘子了,C也没有盘子,所以这一整体肯定在B啊)挪到C就行了,怎么挪呢?这个问题好熟悉,请回到这一段的第一句话(没错,就是怎么挪呢那一句),重读一边,你自己就知道怎么挪了。
  总结一下,也就是说n号盘子从A到C其实是居中的一步,因为他之前一步是(N-1)的整体从A到B,他之后的一步是这个整体从B到C;而至于这个整体是如何从A到B,又是如何从B到C的,那就继续把(N-2)再看成一个整体,逐渐进行分割,我相信你脑中已经有了思路了,哈哈哈,我不管,反正我有了,所以代码来了……

代码实现——Java

import java.io.*;

/**
 * @author g55zhw93
 */
public class Hanoi {
	public static void main(String[] args) throws IOException {
		int n;
		//定义count计数器来计算一共移动了多少次
		int count =0;
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));

		System.out.print("请输入盘数:");
		n = Integer.parseInt(br.readLine());

		Hanoi hanoi = new Hanoi();
		System.out.println("一共移动了" + hanoi.move(n, 'A', 'B', 'C', count) + "步");
	}

	/**
	 *
	 * @param a 无特殊意义,仅为表示柱的名字"A柱";
	 * @param b 无特殊意义,仅为表示柱的名字"B柱";
	 * @param c 无特殊意义,仅为表示柱的名字"C柱";
	 * @param n n为盘子的编号;
	 *
	 * @return 返回值为移动次数
	 */
	public int move(int n, char a, char b, char c,int count) {
		if (n == 1) {
			//每一次在控制台打印都意味着移动了一次,所以同时count需要加1;
			count++;
			System.out.println("盘 " + n + " 由 " + a + " 移至 " + c);
		} else {
			count = move(n - 1, a, c, b,count);
			count++;
			System.out.println("盘 " + n + " 由 " + a + " 移至 " + c);
			count = move(n - 1, b, a, c,count);
		}
		return count;
	}
}

官方题解

分析

  如果柱子标为ABC,要由A搬至C,在只有一个盘子时,就将它直接搬至C,当有两个盘子,就将B当作辅助柱。如果盘数超过两个,将第三个以下的盘子遮起来,就很简单了,每次处理两个盘子,也就是:A->B,A->C,B->C这三个步骤,而被遮住的部分,其实就是进入程式的递回处理。事实上,若有n个盘子,则先移动完毕所需次数为(2n) -1,所以当盘数为64时,则所需次数为(264) -1 = 18446744073709551615为5.05390248594782e+16年,也就是约5000世纪,如果对这数字没什么概念,就假设每秒钟搬一个盘子好了,也要约5850亿年左右。

代码——C语言

#include <stdio.h>

void hanoi(int n, int A, int B, int C)
{
    if(n == 1)
        printf("Move sheet %d from %c to %c\n", n, A, C);
    else
    {
        hanoi(n-1, A, C, B);
        printf("Move sheet %d from %c to %c\n", n, A, C);
        hanoi(n-1, B, A, C);
    }
}

int main(void)
{
    int n;
    printf("请输入盘数: \n");
    scanf("%d", &n);
    hanoi(n, 'A', 'B', 'C');
    return 0;
}

猜你喜欢

转载自blog.csdn.net/g55zhw93/article/details/108002906