魔方求解问题的设计与实现.docx
- 文档编号:26642993
- 上传时间:2023-06-21
- 格式:DOCX
- 页数:32
- 大小:150.76KB
魔方求解问题的设计与实现.docx
《魔方求解问题的设计与实现.docx》由会员分享,可在线阅读,更多相关《魔方求解问题的设计与实现.docx(32页珍藏版)》请在冰豆网上搜索。
魔方求解问题的设计与实现
计算机类毕业论文
魔方求解问题的设计与实现
摘 要
本文介绍一个可以对魔方进行求解的程序。
通过采用专家系统理论中的方法,它可以快速地对输入的魔方状态文件求解。
本程序是魔方求解与动画演示程序的一部分。
程序的核心是一个专家的知识模块。
这个模块是关于魔方的旋转序列的表示。
程序不断地将当前的魔方状态图与这个模块中的符合要求的状态图对比,如果符合要求就调用相应的旋转序列,得到一个新的魔方的状态图。
从而可以快速地找到魔方的求解方法。
程序中用到了关于图搜索的A*算法。
这是一个关于图搜索和结点扩展的通用的算法,但是这个算法并没有规定搜索树的扩展方式。
在这里,搜索树的扩展方式采用广度优先搜索,这样可以找到符合要求的状态图的最佳路径。
在程序中,将广度优先搜索与A*算法相结合来对状态图进行搜索和结点的扩展,在搜索的过程中用到了回朔操作。
有时候有些特殊的魔方状态图程序并不能给出求解的过程。
但这种情况不是很多,程序在大多数情况下可以顺利对魔方求解。
这个问题主要是由采取的专家序列的不同而引起的。
好的专家序列可以减少这种情况的出现。
关键词:
魔方;回朔;最佳路径
Abstract
ThethesisintroducesaprogramforquicklyresolvingtheRubik’sCubeproblem.Byexploitingthemethodsintheexpertsystemtheory,theprogramcangivetherotationsequenceforanyinputtedstatetothedestinationstate.TheprogramisapartofthewholeprogramforautomaticallysolvingtheRubikCubeanddemonstratingitinanimation.
Thecoreoftheprogramisamoduleofexpertknowledge,arepresentationofrotationsequences.Theprogramrepeatedlymatchesthecurrentstatewiththestatesintherepresentation.Ifamathhappens,thecorrespondingrotationoperationwillbecalledandanewstatewillbeobtained,sothatthedestinationstatecanbefoundatlastwithinashorttime.
TheprogramexploitstheA*algorithmforgraphsearching.Itisacommonalgorithmbasedonnodeexpansion,butitdoesnotdefinetheactualexpandingmethodofsearchtrees.Here,thebreadth-firstsearchisusedsothatthebestpathmaybefound.Theprogramintegratesthebreadth-firstsearchwiththeA*algorithmtotraversethestategraphandconductnodeexpansion.Inprogramming,backtrackingisemployedintheprocessofsearching.
Sometimetheprogramcannotgivetheanswertosomespecialinputtedstate.Itisnotbecauseofthealgorithmwhichtheprogramusing,butbecauseofthelimitationoftheexpertknowledge
Key words:
rubikcube;backtracking;bestpath
目 录
第1章 概述1
1.1魔方问题简介1
1.2 关于搜索与存储的问题1
1.3 关于专家系统的简介2
1.4 文件的输入2
1.5 本章小结3
第2章 程序总规划4
2.1 程序的总体设计4
2.2 全局变量的定义5
2.3 各个模块间的调用关系5
2.4 本章小结7
第3章 各个模块的详细设计8
3.1 主模块8
3.2 文件接收模块9
3.3 程序执行模块10
3.4 旋转操作模块12
3.5 搜索模块14
3.6 专家系统模块18
3.7 本章小结20
第4章 程序演示21
结 论24
参考文献25
致 谢26
第1章 概述
1.1魔方问题简介
魔方(RUBIK'SCUBE)是由匈牙利的厄尔诺·鲁毕克在1979年设计的。
他是一个建筑学家,在布达佩斯执教。
同时日本的石毛也独立完成了这个设计。
两人都取得专利。
设计魔方的目的在于帮助人们更好的理解三维空间里的各种运动。
魔方是一个看似简单的玩具,它的每个面有九个格。
我们目标是使每个面的格子颜色都相同。
每个格子实际上是小方块的一部分。
小方块的每个面都可以旋转,角方块有三个面,边方块有两个面。
一共有八个角块,十二个边块和六个中块。
目标是把一个每面都是随机颜色的魔方还原成每面颜色都相同的魔方。
问题是,有无数的方法操作魔方,然而只有极少的方法能够达到目的。
使用盲目的搜索算法是不行的,即使在最大型的计算机也要用上几年的时间。
然而会玩的人在几分钟内就可以将魔方复原,很显然这不是使用盲目搜索算法。
解决魔方问题困难之一就是当你想移动一个方块时,不得不移动其它七个方块(中间方块不动)。
在求解的早期,这不是大问题,但是当大部分方块都到达正确位置时,新的旋转将会破坏已完成的部分。
一些玩魔方的高手都知道许多复杂的旋转方法,这些方法能够只改变少数方块的位置而不影响其它的方块。
而由程序来表示魔方求解的难点就在于,如何将这些复杂的旋转序列带到程序中去,因为盲目的搜索不可能解决魔方问题。
关于这些后面将有详细的介绍。
1.2 关于搜索与存储的问题
在魔方问题的解决过程当中离不开搜索,对魔方状态图的搜索。
但魔方的状态图很大,以致它们不能通过显示图来表示。
正如魔方在求解过程中的状态图。
所以,这些只能用隐式状态空间图来解决,而解决这个问题的关键是如何用公式来表示隐式状态空间图搜索的方法。
因为在魔方问题中状态图相当大,所以盲目搜索将不起作用。
这时必须使用优化算法指导搜索。
使用不同的优化方法,搜索空间将极大的减少,从而达到在有限时间内能够找到结果的程度。
这个问题和其它人工智能问题一样,分为三个设计阶段。
首先我们必须决定如何表示魔方的当前状态。
然后还必须表示对魔方的操作。
最后必须解决如何把这些操作运用到魔方上。
在搜索中也会有一定的优化,由于状态图是按树型结构展开,所以,这里的优化就是指“剪枝”操作。
关于搜索的方法,采用的是A*算法。
在后面回有详细介绍。
对于魔方状态图的存储,在本程序中采用了一个二维数组,在用二维数组存储魔方状态图的时候,考虑到存储空间的问题,虽说,在本程序中只在魔方复原的第一阶段用到了搜索,并且搜索的层数不是很多,但还是只用了一个状态图来表示魔方的当前状态[1],这样,多少可以节省空间,但在求解时间上可能会长一些,但不是很明显。
关于这些在后面会有专门的介绍。
1.3 关于专家系统的简介
前面已经谈到用盲目搜索的方法对魔方问题求解是毫无办法的,因此,只能将一些复杂的旋转序列嵌到程序中去,这样在魔方问题的求解过程当中就可以节省大量的空间和时间,从而提高程序的效率。
这种方法就是我所谈到的专家系统。
专家系统就是将某一个领域的知识进行合理的组织,与计算机程序的合理组合。
专家系统的应用很广,在医疗、工程和商业上都有广泛的应用。
严格地讲,任何具有专家功能的程序都被称为是一个专家系统。
在解决问题中,通过运用知识体达到专家级水平的AI程序叫做知识系统或专家系统。
一个专家系统的主要部分包括两个方面:
知识库和推理引擎。
在本程序中,由于是一个很简单的专家系统,所以只包含了这两个最主要的方面,但一个完整的专家系统的结构应该是,一个“知识工程师”(经常是一个训练过的AI计算机科学家)与应用领域的一个专家(或几个专家)共同工作以便把专家的相关知识表示成一种形式,以使它能被输入到知识库。
这个过程经常由一个知识采集子系统协助[1]。
和其他情况一样,这个子系统检查正在增长的知识库的可能不一致和不完备信息,然后将它们表示给专家以做出决定。
当然,由于本程序并没有涉及到专家系统中的各个方面,只是知识在程序中的简单应用,所以对这些并不详细的讨论。
一个专家系统的基本结构图(图1-1 专家系统的结构)。
1.4 文件的输入
对于魔方文件的输入,在这里简单的介绍一下。
输入时,依次输入上面、左面、前面、右面、下面和后面,各面的颜色状态,在这里对颜色有严格的要求,当然,这种要求可能会对程序的灵活性有一定的影响,但我认为还是值得的。
标准魔方的颜色,我把红色定为上面,绿色定为左面,黄色定为前面,蓝色定为右面,橙色定为下面,白色定为后面。
在这里要注意一点,输入的时候最好是把各面都转到前面来进行输入,在这里只有后面是按照从右到左,从上到下的顺序输入,其它各面当转到前面的时候都是按照从左到右,从上到下的顺序输入。
关于输入的这一点,主要是和我采取的存储方式有关,如果这里不是说的很清楚的话,那么,在后面我将会给出我魔方存储的状态图,看到这张图的时候,相信会对输入方式有更深的理解。
图1-1 专家系统的结构
1.5 本章小结
通过本章能看出魔方在编写及实现中基本的概念和实现方法,先介绍了魔方的玩法及原理,以及关于搜索与存储的问题的初步设想,对于专家系统运行模式比较详细简介,也对文件输入方法进行笼统的介绍。
第2章 程序总规划
2.1 程序的总体设计
这里我考虑到程序应该有一点游戏性,所以当输入一个魔方状态时也可以试着人工来求解,当然在任何时候都可以像电脑寻求帮助。
程序的编写,我更看重的是它的算法,所以采用编写控制台应用程序,可能程序的界面不是很绚丽,但算法的基本功能都能实现,我想这也就算达到了目的。
为了增加程序的可读性,同时,也为了使程序的编写更加顺利,将不同功能的代码分别放在了不同的文件中编写(.cpp文件)。
按照代码功能的不同,将程序分为六个不同的源文件[2]。
其中,main.cpp文件是程序的入口,它主要是对在程序中用到的各种变量进行定义。
在这里有一点需要说明一下,考虑到为了方便程序的编写,我将使用比较频繁的变量定义为全局变量,这样在不同的源文件中,在各个需要它们的地方可以很方便的引用它们。
这样的做法可能是不提倡的,但在这里我觉得是值得的。
由于这样的变量不是很多,所以应该不会对程序的可读性造成多大的影响。
其中,gm.cpp文件的主要功能是将从文件中接受到的魔方各个面的颜色状态存放到魔方的状态图中,状态图变量的定义为全局变量。
关于魔方状态图的实现在后面将会有详细的介绍,在这里就不过多的涉及这个问题。
其中,begin.cpp文件的主要功能是在控制台中打印出程序的操作界面,接受用户的输入,对合理的输入给出相应的操作,对不合理的操作给出相应的提示。
并且,通过调用其它文件中的函数来对魔方进行求解[3],同时判断魔方是否被解出。
其中,changemf.cpp文件的主要功能是实现当对魔方各个面进行操作后,魔方状态图的变化。
可以说,这在程序的设计过程中是个比较费时,但又相当重要的一个环节。
因为只有保证这部分的代码准确无误,才能确保魔方顺利的求解。
其中,包含对魔方六个面的12种操作,每个面有两种操作。
其中,search.cpp文件的主要功能是实现搜索功能,也就是搜索我们想要得到的魔方状态,这主要应用在了魔方复原的第一阶段。
这部分设计的难点是它的数据结构,主要是考虑采用何种结构才能实现当前状态的扩展同时又要将不合要求的状态存入扩展序列。
这是个难点,关于这些后面会有更详细的介绍。
搜索的实现采用了bfs搜索[4],这样可以保证搜索到需要的状态图的路径是最短的。
在搜索的过程中,会有一定的优化。
其中,expert.cpp文件的主要功能是将各种魔方复杂的旋转操作序列合理的组织在程序中,以便可以对魔方顺利求解。
这个文件就是本程序中提到的专家库,就是魔方具体玩法。
关于魔方的玩法有很多,在本程序中我主要研究了所谓的“八角法”,这个文件中就汇集了在这种玩法下的旋转操作序列[5]。
当然,各种玩法都不是健全的,有些极其复杂的情况光靠一种玩法的操作序列是不能求解的,这个我没有考虑,因为,在我目前所实验的魔方的各种状态“八角法”都可以求解。
关于这些,这里不在多说(图2-1给出了程序执行的总体流程图)。
2.2 全局变量的定义
在程序的执行过程中,有些比较重要的全局变量,在这里我简单的谈谈这些。
首先是接受魔方状态文件的数组,用到的是一个12×9的字符型二维数,这个数组为graphmf[12][9]。
同时,还有时刻保存魔方当前状态图的数组,也就是在程序的执行过程中实际操作的数组,这个数组为graphsta[12][9]。
还有一个记录魔方初始状态的数组,这个数组为graphmf1[12][9],这个数组是为了在程序执行时,开始选择的是由人工来解决魔方问题,但在后来选择了帮助,由电脑解决时可以很方便的将魔方的初始状态传给魔方实际操作的数组graphsta。
在程序执行的初期,当接受到魔方状态文件的时候就将graphmf中的内容复制到graphsta和graphmf1中[6]。
可能,这么做不是很容易叫人理解,因为,同一种状态却要两个副本,但我这么做只是为了程序编写的方便。
还有一个很重要的变量,就是对魔方正确的解决序列进行存储的变量。
这里定义了一个队列rc。
在程序的执行过程当中,只要是对魔方合理的正确的操作就都存储在这个队列当中,使魔方的正确解决过程得到保存。
在魔方复原的第一阶段,为了很方便的将搜索于专家系统联系到一起,用到了一个指向函数的指针,定义的形式为void(*p)();。
在expert.cpp中first-stage()函数中可以找到它的具体应用[7]。
2.3 各个模块间的调用关系
程序首先由main.cpp进入,调用begin.cpp中的search_begin();函数,由这个函数开始真正的对输入的魔方状态进行求解。
首先在这个函数中调用gm.cpp中的getgraph()函数、makegraph()函数和search.cpp中change()函数,完成对魔方状态文件的接受和两个副本的拷贝。
然后调用自身的choice()函数,来接收用户的选择,同时,当用户选择的是人工解决的时候,则在用户每一次对魔方操作后,调用change.cpp中的相应的魔方操作函数(因为只是对魔方状态图的操作,所以这里的函数会在后面说明),然后调用search.cpp中的compara()函数,由此来判断魔方是
图2-1 求解程序的流程
否已经被复原。
如果在choice()函数中选择的是由电脑来解决的话,则依次调用expert.cpp中的first_stage()、second_stage()、third_stage()、forth_stage()、fifth_stage()、sixth_stage()、seventh_stage()、eighth_stage(),这几个函数来对魔方进行求解,在这个几个函数的执行过程中,他们分别调用自身的含有解决特定魔方状态下魔方的旋转序列,同时,也会对change.cpp中的魔方操作函数进行调用。
值得一提的是,在first-stage()函数中需要调用search.cpp中的bfs()函数,对魔方的状态图进行搜索,找到符合要求的状态图。
最后,调用搜索文件(search.cpp)中的mission_compelet()函数[8],来判断魔方是否被解出,并且,给出求解的过程。
当然,在这些函数功能的实现过程中,在上面谈到的全局变量都会出现在各自的函数中,在这里就不过多的说明。
2.4 本章小结
在本章中主要是对程序总体设计的游戏性、可读性在程序中怎样实现的进行了详细的解说,在程序的执行过程中,全局变量是如何定义的,以及说明了各模块间是如何相互调用它们之间的关系。
第3章 各个模块的详细设计
3.1 主模块
这个模块比较简单,就是程序的入口和一个简单的操作选择,和一些变量的定义,由于代码不是很长,所以,给出的它的全部代码,其它的在这里就不多说了。
#include"All_Function.h"
#include
#include
#include
#include
usingnamespacestd;
//定义魔方矩阵,全局变量
chargraphmf[12][9];//接收文件的数组
chargraphsta[12][9];//实际操作的数组
chargraphmf1[12][9];//用来记录魔方初始状态的数组
queue
boolsolve_1=false;
void(*p)();//函数指针,用在魔方复原第一阶段的bfs中
/*魔方文件的输入中有六行,对应六个面,必须按照R,G,Y,B,O,W面的顺序输入.*/
voidmain()
{
stringc;
while
(1)
{
search_begin();//求解开始
cout<<"N/n退出,任意键执行下一个魔方文件"< cin>>c; if(c=="N"||c=="n") break; } } 3.2 文件接收模块 文件接收模块(gm.cpp)。 这个模块主要是为了实现对魔方状态文件的接收,和它的副本的创建。 在这里我想先谈谈魔方的状态图,由于魔方有五十四小面,六个“大面”,“大面”分别是指魔方的上、下、左、右、前、后,六个面。 所以,采用一个二维数组来存储这五十四个小面,但它的存储也要遵守一定结构,在这节的最后我会给出它的存储状态图,以方便对存储这个问题的理解。 对于文件的接收也没什么太多的说法,就是有一点需要说明,这就是,在魔方状态图文件中的每一行对应魔方的一个“大面”,而每一个“大面”又有九个“小面”。 主要是由以下的代码来完成这个功能。 chars[6][20]; charfilename[100]; cout<<"请输入魔方文件名: "< cin>>filename; ifstreaminstuf(filename,ios: : in); if(! instuf) { cerr<<"Filecouldnotbeopen."< h=1; abort(); } instuf.getline(s[0],20);//魔方上面数据 instuf.getline(s[1],20);//魔方左面数据 instuf.getline(s[2],20);//魔方前面数据 instuf.getline(s[3],20);//魔方右面数据 instuf.getline(s[4],20);//魔方下面数据 instuf.getline(s[5],20);//魔方后面数据 当魔方文件成功接受后,就将接受数组中的状态存放到保存魔方状态图的数组中去,由于这部分代码很相似,所以,只给出一部分代码。 其中,唯一的不同就是数组的下标不同[9],这主要由存储魔方的状态图的构建决定,图在最后给出。 由以下代码完成这部分功能。 graphmf[0][3]=s[0][0];//魔方上面状态图的构建 graphmf[0][4]=s[0][1]; graphmf[0][5]=s[0][2]; graphmf[1][3]=s[0][3]; graphmf[1][4]=s[0][4]; graphmf[1][5]=s[0][5]; graphmf[2][3]=s[0][6]; graphmf[2][4]=s[0][7]; graphmf[2][5]=s[0][8]; 以上这部分代码就是将每一个“大面”中的状态拷贝到九个数组元素中去,也就是对应的九个“小面”。 当魔方状态文件被成功的接收以后,并且,被保存到魔方状态图数组中后,接下来就是将这个状态拷贝到两个副本中去,由于第一个副本的拷贝并没有在这个模块中出现,所以,先不给出这部分的代码。 这个副本的拷贝是在search.cpp中的change()函数中完成,代码如下: voidchange() { inti,j; for(i=0;i<12;i++) for(j=0;j<9;j++) graphsta[i][j]=graphmf[i][j]; } 下面是第二个副本对魔方上面状态的拷贝: for(i=0;i<12;i++) for(j=0;j<9;j++) graphmf1[i][j]=graphmf[i][j]; 到这里这个模块的主要问题基本已经解决了,当然,由于完成的功能比较简单,只是简单的给出了代码,并简单的说明。 下面在给出魔方状态的构造图之前,先对图中的标记进行简单的说明,表格中的行号和列号分别代表数组中的行标和列标。 U代表魔方上面,为红色;L代表魔方左面,为绿色;F代表魔方前面,为黄色;R代表魔方右面,为蓝色;D代表魔方下面,为橙色;B代表魔方后面,为白色。 以下就是魔方状态的构造图(表3-1 魔方状态构造图),在程序中一个很重要的部分。 3.3 程序执行模块 程序执行模块(begin.cpp)。 这个模块被设计成界面的打印和对用户的输入执行相应的操作,同时,它也是整个程序执行的第一部分,其它的函数调用都是在它的search_begin()函数中来实现的[10]。 在search_begin()函数被主函数调用后,它首先调用自身的choice()函数来打印出界面,同时等待接收用户的输入,当用户输入为人工解决的时候,则给出魔方相应旋转的操作方法,例如: 前面顺时针操作对应字符串[f+]。 其它的操作方法会在用户选择界面中有相应的提示。 并且,会对错误的输入给出相应的提示。 每当用户选择自己操作的时候,每输入一次操作字符串,程序就会调用changemf.cpp中的相应的对魔方状态图操作的函数,例如: 输入[f+],则调用front_1()函数,这个函数主要是通过对魔方状态图中的各个面的颜色以及它在数组中的存储位置的变换来达到动态表示魔方旋转后的正确的状态的目的。 然后,调用changemf.cpp中的display_mf()函数,来显示旋转后魔方的状态图[11]。 关于front_1()函数会在changemf.cpp的详细设计中给出具体的说明。 在用户输入旋转字符串,并且程序执行相应的操作后,程序调用search.cpp中的compara()函数,来判断魔方是否被用户解出,如果解出则给出相应的提示,并且返回到search-begin()函数的调用处,也就是主函数中去。 然后,执行主函数
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 魔方 求解 问题 设计 实现