陕西师范大学实验报告
课题名称 算法分析与设计
项目名称 分治法 汉诺塔问题
学 院 计算机科学学院
专 业 计算机科学与技术
指导老师 王小明
小组人员 刘永军高富雷武子超
报告时间 2013/11/28
2013/11/28
分治法之汉诺塔问题
目录
一、 设计目的... 3
二、 设计内容... 3
1. 任务描述... 3
i. 汉诺塔问题简介... 3
ii. 设计任务简介... 3
2. 分治法算法的实现过程... 4
三、 流程图... 6
四、 测试结果... 7
五、 总结... 7
附:程序源代码... 7
一、 设计目的
1、掌握分治法的思想;
2、掌握分治法的典型问题,如汉诺塔问题以及其他问题;
3、进一步多级调度的基本思想和算法设计方法;
4、提高分析与解决问题的能力。
二、 设计内容
1. 任务描述
i. 汉诺塔问题简介
在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神大梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从大梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
ii. 设计任务简介
其实算法非常简单,当盘子的个数为n时,移动的次数应等于2^n - 1(有兴趣的可以自己证明试试看)。后来一位美国学者发现一种出人意料的简单方法,只要轮流进行两步操作就可以了。首先把三根柱子按顺序排成品字型,把所有的圆盘按从大到小的顺序放在柱子A上,根据圆盘的数量确定柱子的排放顺序:若n为偶数,按顺时针方向依次摆放 A B C;
若n为奇数,按顺时针方向依次摆放 A C B。
(1)按顺时针方向把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,则把它移动到C;若圆盘1在柱子C,则把它移动到A。
(2)接着,把另外两根柱子上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上,当两根柱子都非空时,移动较小的圆盘。这一步没有明确规定移动哪个圆盘,你可能以为会有多种可能性,其实不然,可实施的行动是唯一的。
(3)反复进行(1)(2)操作,最后就能按规定完成汉诺塔的移动。
所以结果非常简单,就是按照移动规则向一个方向移动金片:
如3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C。
2. 分治法算法的实现过程
首先把三根柱子按顺序排成品字型,把所有的圆盘按从大到小的顺序放在柱子A上,根据圆盘的数量确定柱子的排放顺序:
若n为偶数,按顺时针方向依次摆放 A B C;
若n为奇数,按顺时针方向依次摆放 A C B。
(1)按顺时针方向把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,则把它移动到C;若圆盘1在柱子C,则把它移动到A。
(2)接着,把另外两根柱子上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上,当两根柱子都非空时,移动较小的圆盘。这一步没有明确规定移动哪个圆盘,你可能以为会有多种可能性,其实不然,可实施的行动是唯一的。
(3)反复进行(1)(2)操作,最后就能按规定完成汉诺塔的移动。
所以结果非常简单,就是按照移动规则向一个方向移动金片:
如是解决,我们就可以将n个盘分治成1个,2个,3个盘来讨论,
如1阶汉诺塔的移动: A→C;
如2阶汉诺塔的移动:A→B, A→C, B→C;
如3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C。
通过最简单的,最少阶数汉诺塔的移动,我们更具上面的讲解,联想到更多的阶数,不难看出其中的规律!
因此,我们通过将多阶汉诺塔分解成少数像3阶一样的汉诺塔,从繁化简,分而治之。解决汉诺塔问题。
算法如下:
void move(char a,int n,char c)
{
printf("%c-->%c\n",a,c);
}
void hanoi(int n, char a, char b, char c, int &time)
{
if (n==0)
return;
if (n==1)
{
move(a,1,c);
time++;
}
else
{
hanoi(n-1,a,c,b, time);
move(a,n,c);
time++;
hanoi(n-1,b,a,c, time);
}
}
int main()
{
int n;
printf("请输入汉诺塔的盘数: ");
scanf("%d",&n);
int time = 0;
printf("%d个盘的汉诺塔移动方法是\n:",n);
hanoi(n,'a','b','c',time);
printf("移动了%d次\n", time);
getchar();
getchar();
return 0;
}
算法分析:
我们通过递归调用,主函数main()调用hanoi()函数,hanoi()函数在调用move()函数,以及hanoi()函数对自身的调用,在hanoi()函数自身调用中参数a,b的交换,实现了品字形排列,顺序逆序的实现,从而解决问题。
通过time参数的计算,我可可以知道该函数的时间复杂度是o(2^n)。
其实我们通过数学归纳法就可以知道N阶汉诺塔需要移动的次数为2^n-1。
三、 流程图
四、 测试结果
五、 总结
这次的设计的主要内容是分治算法,汉诺塔问题不难,但却是典型的分治算法中递归调用案例之一。因此通过这次练习,让我们小组更加深刻的认识到分治法的优越性以及优中求优,对算法加以修正,做到最好。
附:程序源代码
// 汉罗塔.cpp : 定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include <stdio.h>
#include <stdlib.h>
void move(char a,int n,char c)
{
printf("%c-->%c\n",a,c);
}
void hanoi(int n, char a, char b, char c, int &time)
{
if (n==0)
return;
if (n==1)
{
move(a,1,c);
time++;
}
else
{
hanoi(n-1,a,c,b, time);
move(a,n,c);
time++;
hanoi(n-1,b,a,c, time);
}
}
int main()
{
int n;
printf("请输入汉诺塔的盘数: ");
scanf("%d",&n);
int time = 0;
printf("%d个盘的汉诺塔移动方法是\n:",n);
hanoi(n,'a','b','c',time);
printf("移动了%d次\n", time);
getchar();
getchar();
return 0;
}
第二篇:汉诺塔问题实验报告
1.实验目的:
通过本实验,掌握复杂性问题的分析方法,了解汉诺塔游戏的时间复杂性和空间复杂性。
2.问题描述:
汉诺塔问题来自一个古老的传说:在世界刚被创建的时候有一座钻石宝塔(塔A),其上有64个金碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着这座塔有另外两个钻石宝塔(塔B和塔C)。从世界创始之日起,婆罗门的牧师们就一直在试图把塔A上的碟子移动到塔C上去,其间借助于塔B的帮助。每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。当牧师们完成任务时,世界末日也就到了。
3.算法设计思想:
对于汉诺塔问题的求解,可以通过以下三个步骤实现:
(1)将塔A上的n-1个碟子借助塔C先移到塔B上。
(2)把塔A上剩下的一个碟子移到塔C上。
(3)将n-1个碟子从塔B借助于塔A移到塔C上。
4.实验步骤:
1. 用c++ 或c语言设计实现汉诺塔游戏;
2. 让盘子数从2 开始到7进行实验,记录程序运行时间和递归调用次数;
3. 画出盘子数n和运行时间t 、递归调用次数m的关系图,并进行分析。
5.代码设计:
Hanio.cpp
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
void hanoi(int n,char x,char y,char z)
{
if(n==1)
{
printf("从%c->搬到%c\n",x,z);
}
else
{
hanoi(n-1,x,z,y);
printf("从%c->%c搬到\n",x,z);
hanoi(n-1,y,x,z);
}
}
void main()
{
int m ;
printf("input the number of diskes:");
scanf("%d",&m);
printf("The step to moving %3d diskes:",m);
hanoi(m,'a','b','c');
}
自定义头文件
:#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
结果如下:
6.递归应用中的Hanoi塔问题分析
1)Hanoi塔问题中函数调用时系统所做工作
一个函数在运行期调用另一个函数时,在运行被调用函数之前,系统先完成3件事:
①将所有的实参、返回地址等信息传递给被调用函数保存。
②为被调用函数的局部变量分配存储区;
③将控制转移到被调用函数的入口。
从被调用函数返回调用函数前,系统也应完成3件事:
①保存被调用函数的结果;
②释放被调用函数的数据区;
③依照被调用函数保存的返回地址将控制转移到调用函数。
当有多个函数构成嵌套调用时,按照“后调用先返回”的原则(LIFO),上述函数之间的信息传递和控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为其在栈顶分配一个存储区,每当从一个函数退出时,就释放其存储区,因此当前运行函数的数据区必在栈顶。堆栈特点:LIFO,除非转移或中断,堆栈内容的存或取表现出线性表列的性质。正是如此,程序不要求跟踪当前进入堆栈的真实单元,而只要用一个具有自动递增或自动递减功能的堆栈计数器,便可正确指出最后一次信息在堆栈中存放的地址。
一个递归函数的运行过程类型于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。因此,和每次调用相关的一个重要的概念是递归函数运行的“层次”。假设调用该递归函数的主函数为第0层,则从主函数调用递归函数为进入第1层;从第i层递归调用本函数为进入下一层,即i+1层。反之,退出第i层递归应返回至上一层,即i-1层。为了保证递归函数正确执行,系统需设立一个“递归工作栈”,作为整个递归函数运行期间使用的数据存储区。每一层递归所需信息构成一个“工作记录”,其中包括所有实参、所有局部变量以及上一层的返回地址。每进入一层递归,就产生一个新的工作记录压入栈顶。每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必是递归工作栈栈顶的工作记录,称这个记录为“活动记录”,并称指示活动记录的栈顶指针为“当前环境指针”。
2)Hanoi塔问题递归程序的复杂度分析
① 运行hanoi程序的时间
程序 hanoi.c 在硬件环境为赛扬 400MHz、内存128M的计算平台(不同机器运行时间有一定差别)运行,可得出如下时间结果:
盘子数 时间结果
<=12个 <=1秒
14个 2秒
16个 13秒
20个 204秒
② 时间复杂度
程序所花时间正比于所输出的信息行数目,而信息行的数目则等价于盘子的移动次数。考察程序,设盘子移动次数为moves(n),则:
moves(n)=
用迭代方法计算公式,得到结果moves(n)=2n-1。因此,hanoi函数的时间复杂度为O(2 n) 。
③ 空间复杂度
从每个塔上移走盘子时是按照LIFO进行,因此可以把每个塔表示成一个堆栈。3座塔在任何时候总共拥有的盘子都是n个。如果使用链表形式的堆栈,只需申请n个元素所需要的空间。如果使用的是基于公式化描述的堆栈,塔1和塔2的容量都必须是n,而塔3的容量是n-1,因此所需要的空间总数为3n-1。
Hanoi塔问题的复杂性是以n为指数的函数,因此在可以接受的范围内,只能解决n值比较小(n<=30)的hanoi问题。对于这个较小的n值,堆栈在空间需求上的差别相当小,可以随意使用。
7、结论
通过对上述递归在Hanoi塔问题上的应用分析,我们可以得出如下结论:
1、递归调用过程中,在程序执行之前无法知道控制这种调用栈的规模,因为这一规模取决于递归调用的次序。在这种情况下,程序的地址空间可能动态变化;
2、递归应用于程序设计时,结构清晰、程序易读,编制和调试程序很方便,不需要用户自行管理递归工作栈。但递归应用于计算机时需要占用大量系统资源(包括堆栈、软中断和存贮空间等),并消耗大量处理时间。因此,可以考虑采用并行计算进行处理,但
3、递归是串行的,其第n步运算依赖于第n-1步运算,所以在计算机软件理论上不存在递归问题并行计算的可能性。实际上是否存在并行递归计算有待进一步探讨。
8、总结
通过对汉诺塔算法的分析让我更清楚的认识到了不同的算法对程序性能的影响,也让我明白掌握了算法将会有助于提高软件的开发。