八数码问题 八数码问题 分支线界法

八数码问题(又称九宫排字问题)

一、问题描述:

在3×3方格盘上,放有八个数码,剩下第九个为空,每一空格其上下左右的数码可移至空格。问题给定初始位置和目标位置,要求通过一系列的数码移动,将初始位置转化为目标位置。

所谓八数码问题是指这样一种游戏:将分别标有数字1,2,3,…, 8的八块正方形数码牌任意地放在一块 3×3的数码盘上。放牌时要求不能重叠。于是,在3×3的数码盘上出现了一个空格。现在要求按照每次只能将与空格相邻的数码牌与空格交换的原则,将任意摆放的数码盘逐步摆成某种特殊的排列。

二、提出问题:

首先,八数码问题包括一个初始状态(START)和目标状态 (END),所谓解八数码问题就是在两个状态间寻找一系列可过渡状态(START>STATE1>STATE2>...>END)

1、每一个状态及每一次操作的表示方法?

由于 int的表示范围大于1e9,所以我们取一个 int的低 9位,为了方便寻找空格的位置,int的个位我们用来放空格的位置(19)。而前8位,按照行从上到下,列从左到右的顺序依次记录对应位置上的数字。

2、判断是否存在中间状态使START到达END?

用组合数学的方法可以快速地进行判断。两个状态之间是否可达,可以通过计算两者的逆序值,若两者奇偶性相同则可达,不然两个状态不可达。

3、如何寻找一系列的中间状态及遇到的问题?

要寻找这一系列中间状态的方法是搜索,但搜索很容易遇到时间和空间上的问题。

这样的搜索类似于从树根开始向茎再向叶搜索目标叶子一样的树型状。由于其规模的不断扩大,其叶子也愈加茂密,最终的规模会大到无法控制。这样在空间上会大大加大搜索难度,在时间上也要消耗许多。在普通搜索中遇到以下问题:搜索中易出现循环,即访问某一个状态后又来访问该状态。搜索路径不佳便无法得到较好的中间状态集(即中间状态集的元素数量过大)。搜索过程中访问了过多的无用状态,这些状态对最后的结果无帮助。

4、怎样避免重复访问一个状态?

最直接的方法是记录每一个状态访问否,然后再衍生状态时不衍生那些已经访问的状态了。思想是,给每个状态标记一个 flag,若该状态flag=true则不衍生,若为 false则衍生并修改 flag为true。在某些算法描述里,称有两个链表,一个为活链表(待访问),一个为死链表(访问完)。每一次衍生状态时,先判断它是否已在两个链表中,若存在,则不衍生;若不存在,将其放入活链表。对于被衍生的那个状态,放入死链表。为了记录每一个状态是否被访问过,我们需要有足够的空间。八数码问题一共有9!,这个数字并不是很大,但迎面而来的另一个问题是我们如何快速访问这些状态,如果是单纯用链表的话,那么在规模相当大,查找的状态数目十分多的时候就不能快速找到状态,其复杂度为O(n),为了解决这个问题,可以采用哈希函数的方法,使复杂度减为 O(1)。这里的哈希函数是用能对许多全排列问题适用的方法。取n!为基数,状态第 n位的逆序值为哈
希值第 n位数。对于空格,取其(9位置)再乘以8!。

5、如何使搜索只求得最佳的解?

DFS(深度优先搜索)。除了DFS,还有BFS,从概念上讲,两者只是在扩展时的方向不同,DFS向深扩张,而BFS向广扩张。在八数码问题的解集树中,树的深度就表示了从初始态到目标态的步数,DFS一味向深,所以很容易找出深度较大的解。BFS可以保证解的深度最少,因为在未将同一深度的状态全部访问完前,BFS不会去访问更深的状态,因此比较适合八数码问题,至少能解决求最佳解的难题。
6、该如何减少因广搜所扩张的与目标状态及解无关的状态?

前面所说的都是从START状态向 END状态搜索,那么,将 END状态与 START状态倒一下,其实会有另一条搜索路径,但简单的交换 END与START并不能缩小状态膨胀的规模。我们可以将正向与反向的搜索结合起来,这就是双向广度搜索。双向广搜是指同时从 START和END两端搜,当某一端所要访问的一个状态是被另一端访问过的时候,即找到解,搜索结束。它的好处是可以避免广搜后期状态的膨胀。

7、决定一个快的检索策略?

双向广搜能大大减少时间和空间,但在有的情况下我们并不需要空间的节省,这样我们可以把重点放在时间的缩短上。启发式搜索是在路径搜索问题中很实用的搜索方式,通过设计一个好的启发式函数来计算状态的优先级,优先考虑优先级高的状态,可以提早搜索到达目标态的时间。

三、C++代码实现

用A*算法来实现八数码的问题:

算法的步骤如下:
1、初始化两个链表open和closed,将初始状态放入open表中
2、重复下列过程,直至找到目标结点为止,如果open表为空,那么查找失败;
3、从open表中拿出具有最小f值的结点(将这一结点称为BESTNODE),并放入closed表中;
4、如果BESTNODE为目标结点,成功求得解,退出循环;
5、如果BESTNODE不是目标结点,那么产生它的后继结点(此后继结点与其祖先的状态不同),后继结点组成一个链表;
6、对每个后继结点进行以下过程:
7、建立它到BESTNODE的parent指针;
8、如果此结点在open表中,首先将open表中的结点添加进BESTNODE的后继结点链中,然后计算两个结点的g值,如果此结点的g值小于open表中的结点时,open表中的结点改变parent指针,同时将此结点删除;
9、如果此结点在closed表中,首先将closed表中的结点添加进BESTNODE的后继结点中,然后计算两个结点的g值,如果此结点的g值小于closed表中的结点时,closed表中的结点改变parent指针;将closed表中的结点重新放入open表中,同时将此结点删除;
10、如果此结点既不在open表中也不再closed表中,那么添加此结点至BESTNODE的后继结点链中。

#include"stdafx.h"
#include<iostream>
#include<cstdlib>
#include<conio.h>
#definesize3

usingnamespacestd;

//定义二维数组来存储数据表示某一个特定状态
typedefintstatus[size][size];
structSpringLink;

//定义状态图中的结点数据结构
typedefstructNode
{
statusdata;//结点所存储的状态
Node*parent;//指向结点的父亲结点
SpringLink*child;//指向结点的后继结点
intfvalue;//结点的总的路径
intgvalue;//结点的实际路径
inthvalue;//结点的到达目标的苦难程度
Node*next;//指向open或者closed表中的后一个结点
}NNode,*PNode;

//定义存储指向结点后继结点的指针的地址
typedefstructSpringLink
{
Node*pointData;//指向结点的指针
SpringLink*next;//指向兄第结点
}SPLink,*PSPLink;

//定义open表和close表
PNodeopen;
PNodeclosed;

//开始状态与目标状态
statusstartt={2,8,3,1,6,4,7,0,5};
statustarget={1,2,3,8,0,4,7,6,5};
//初始化一个空链表
voidinitLink(PNode&Head)
{
Head=(PNode)malloc(sizeof(NNode));
Head->next=NULL;
}

//判断链表是否为空
boolisEmpty(PNodeHead)
{
if(Head->next==NULL)
returntrue;
else
returnfalse;
}

//从链表中拿出一个数据,通过FNode返回
voidpopNode(PNode&Head,PNode&FNode)
{
if(isEmpty(Head))
{
FNode=NULL;
return;
}
FNode=Head->next;
Head->next=Head->next->next;
FNode->next=NULL;
}
//向结点的(最终)后继结点链表中添加新的子结点
voidaddSpringNode(PNode&Head,PNodenewData)
{
PSPLinknewNode=(PSPLink)malloc(sizeof(SPLink));
newNode->pointData=newData;

newNode->next=Head->child;
Head->child=newNode;
}

//释放状态图中存放结点后继结点地址的空间
//注意传入参数PSPLink引用类型
voidfreeSpringLink(PSPLink&Head)
{
PSPLinktmm;

while(Head!=NULL)
{
tmm=Head;
Head=Head->next;
free(tmm);
}
}

//释放open表与closed表中的资源
voidfreeLink(PNode&Head)
{
PNodetmn;

tmn=Head;
Head=Head->next;
free(tmn);

while(Head!=NULL)
{
//首先释放存放结点后继结点地址的空间
freeSpringLink(Head->child);
tmn=Head;
Head=Head->next;
free(tmn);
}
}

//向普通链表中添加一个结点
voidaddNode(PNode&Head,PNode&newNode)
{
newNode->next=Head->next;
Head->next=newNode;
}


//向非递减排列的链表中添加一个结点
voidaddAscNode(PNode&Head,PNode&newNode)
{
PNodeP;
PNodeQ;

P=Head->next;
Q=Head;
while(P!=NULL&&P->fvalue<newNode->fvalue)
{
Q=P;
P=P->next;
}
//上面判断好位置之后,下面就是简单的插入了
newNode->next=Q->next;
Q->next=newNode;
}


//计算结点额h值,当前节点与目标节点数码错位个数
intcomputeHValue(PNodetheNode)
{
intnum=0;

for(inti=0;i<3;i++)
{
for(intj=0;j<3;j++)
{
if(theNode->data[i][j]!=target[i][j])
num++;
}
}
returnnum;
}

//计算结点的f,g,h值
voidcomputeAllValue(PNode&theNode,PNodeparentNode)
{
if(parentNode==NULL)
theNode->gvalue=0;
else
theNode->gvalue=parentNode->gvalue+1;

theNode->hvalue=computeHValue(theNode);
theNode->fvalue=theNode->gvalue+theNode->hvalue;
}


//初始化函数,进行算法初始条件的设置
voidinitial()
{
//初始化open以及closed表
initLink(open);
initLink(closed);

//初始化起始结点,令初始结点的父节点为空结点
PNodeNULLNode=NULL;
PNodeStart=(PNode)malloc(sizeof(NNode));
for(inti=0;i<3;i++)
{
for(intj=0;j<3;j++)
{
Start->data[i][j]=startt[i][j];
}
}
Start->parent=NULL;
Start->child=NULL;
Start->next=NULL;
computeAllValue(Start,NULLNode);

//起始结点进入open表
addAscNode(open,Start);
}

//将B节点的状态赋值给A结点
voidstatusAEB(PNode&ANode,PNodeBNode)
{
for(inti=0;i<3;i++)
{
for(intj=0;j<3;j++)
{
ANode->data[i][j]=BNode->data[i][j];
}
}
}

//两个结点是否有相同的状态
boolhasSameStatus(PNodeANode,PNodeBNode)
{
for(inti=0;i<3;i++)
{
for(intj=0;j<3;j++)
{
if(ANode->data[i][j]!=BNode->data[i][j])
returnfalse;
}
}
returntrue;
}

//结点与其祖先结点是否有相同的状态
boolhasAnceSameStatus(PNodeOrigiNode,PNodeAnceNode)
{
while(AnceNode!=NULL)
{
if(hasSameStatus(OrigiNode,AnceNode))
returntrue;
AnceNode=AnceNode->parent;
}
returnfalse;
}


//取得方格中空的格子的位置,通过row,col返回。
voidgetPosition(PNodetheNode,int&row,int&col)
{
for(inti=0;i<3;i++)
{
for(intj=0;j<3;j++)
{
if(theNode->data[i][j]==0)
{
row=i;
col=j;
return;
}
}
}
}

//交换两个数字的值
voidchangeAB(int&A,int&B)
{
intC;
C=B;
B=A;
A=C;
}

//检查相应的状态是否在某一个链表中,判断spciNode所指的节点是否在theLink所指的链表中。
//theNodeLink返回在链表中与spciNode状态相同的节点指针,preNode指向相同节点前驱。
boolinLink(PNodespciNode,PNodetheLink,PNode&theNodeLink,PNode&preNode)
{
preNode=theLink;
theLink=theLink->next;

while(theLink!=NULL)
{
if(hasSameStatus(spciNode,theLink))
{
theNodeLink=theLink;
returntrue;
}
preNode=theLink;
theLink=theLink->next;
}
returnfalse;
}


//产生结点的后继结点(与祖先状态不同)链表
//通过spring参数返回节点的后继节点链表
voidGenerateSpringLink(PNodetheNode,PNode&spring)
{
introw;
intcol;

getPosition(theNode,row,col);

//空的格子右边的格子向左移动
if(col!=2)
{
PNoderlNewNode=(PNode)malloc(sizeof(NNode));
statusAEB(rlNewNode,theNode);
changeAB(rlNewNode->data[row][col],rlNewNode->data[row][col+1]);
if(hasAnceSameStatus(rlNewNode,theNode->parent))
{
free(rlNewNode);//与父辈相同,丢弃本结点
}
else
{
rlNewNode->parent=theNode;
rlNewNode->child=NULL;
rlNewNode->next=NULL;
computeAllValue(rlNewNode,theNode);
//将本结点加入后继结点链表
addNode(spring,rlNewNode);
}
}
//空的格子左边的格子向右移动
if(col!=0)
{
PNodelrNewNode=(PNode)malloc(sizeof(NNode));
statusAEB(lrNewNode,theNode);
changeAB(lrNewNode->data[row][col],lrNewNode->data[row][col-1]);
if(hasAnceSameStatus(lrNewNode,theNode->parent))
{
free(lrNewNode);//与父辈相同,丢弃本结点
}
else
{
lrNewNode->parent=theNode;
lrNewNode->child=NULL;
lrNewNode->next=NULL;
computeAllValue(lrNewNode,theNode);
//将本结点加入后继结点链表
addNode(spring,lrNewNode);
}
}
//空的格子上边的格子向下移动
if(row!=0)
{
PNodeudNewNode=(PNode)malloc(sizeof(NNode));
statusAEB(udNewNode,theNode);
changeAB(udNewNode->data[row][col],udNewNode->data[row-1][col]);
if(hasAnceSameStatus(udNewNode,theNode->parent))
{
free(udNewNode);//与父辈相同,丢弃本结点
}
else
{
udNewNode->parent=theNode;
udNewNode->child=NULL;
udNewNode->next=NULL;
八数码问题 八数码问题 分支线界法
computeAllValue(udNewNode,theNode);
//将本结点加入后继结点链表
addNode(spring,udNewNode);
}
}
//空的格子下边的格子向上移动
if(row!=2)
{
PNodeduNewNode=(PNode)malloc(sizeof(NNode));
statusAEB(duNewNode,theNode);
changeAB(duNewNode->data[row][col],duNewNode->data[row+1][col]);
if(hasAnceSameStatus(duNewNode,theNode->parent))
{
free(duNewNode);//与父辈相同,丢弃本结点
}
else
{
duNewNode->parent=theNode;
duNewNode->child=NULL;
duNewNode->next=NULL;
computeAllValue(duNewNode,theNode);
//将本结点加入后继结点链表
addNode(spring,duNewNode);
}
}
}

//输出给定结点的状态
voidoutputStatus(PNodestat)
{
for(inti=0;i<3;i++)
{
for(intj=0;j<3;j++)
{
cout<<stat->data[i][j]<<"";
}
cout<<endl;
}
}

//输出最佳的路径
voidoutputBestRoad(PNodegoal)
{
intdeepnum=goal->gvalue;

if(goal->parent!=NULL)
{
outputBestRoad(goal->parent);
}
cout<<"第"<<deepnum--<<"层的状态:"<<endl;
outputStatus(goal);
}

voidAStar()
{
PNodetmpNode;//指向从open表中拿出并放到closed表中的结点的指针
PNodespring;//tmpNode的后继结点链
PNodetmpLNode;//tmpNode的某一个后继结点
PNodetmpChartNode;
PNodethePreNode;//指向将要从closed表中移到open表中的结点的前一个结点的指针
boolgetGoal=false;//标识是否达到目标状态
longnumcount=1;//记录从open表中拿出结点的序号

initial();//对函数进行初始化
initLink(spring);//对后继链表的初始化
tmpChartNode=NULL;

cout<<"从open表中拿出的结点的状态及相应的值"<<endl;
while(!isEmpty(open))
{
//从open表中拿出f值最小的元素,并将拿出的元素放入closed表中
popNode(open,tmpNode);
addNode(closed,tmpNode);


cout<<"第"<<numcount++<<"个状态是:"<<endl;
outputStatus(tmpNode);
cout<<"其f值为:"<<tmpNode->fvalue<<endl;
cout<<"其g值为:"<<tmpNode->gvalue<<endl;
cout<<"其h值为:"<<tmpNode->hvalue<<endl;


//如果拿出的元素是目标状态则跳出循环
if(computeHValue(tmpNode)==0)
{
getGoal=true;
break;
}

//产生当前检测结点的后继(与祖先不同)结点列表,产生的后继结点的parent属性指向当前检测的结点
GenerateSpringLink(tmpNode,spring);

//遍历检测结点的后继结点链表
while(!isEmpty(spring))
{
popNode(spring,tmpLNode);
//状态在open表中已经存在,thePreNode参数在这里并不起作用
if(inLink(tmpLNode,open,tmpChartNode,thePreNode))
{//此时他们hvalue值一样
addSpringNode(tmpNode,tmpChartNode);
if(tmpLNode->gvalue<tmpChartNode->gvalue)//等价于if(tmpLNode->fvalue<tmpChartNode->fvalue)
{
tmpChartNode->parent=tmpLNode->parent;
tmpChartNode->gvalue=tmpLNode->gvalue;
tmpChartNode->fvalue=tmpLNode->fvalue;
}
free(tmpLNode);
}
//状态在closed表中已经存在
elseif(inLink(tmpLNode,closed,tmpChartNode,thePreNode))
{//此时他们hvalue值一样
addSpringNode(tmpNode,tmpChartNode);
if(tmpLNode->gvalue<tmpChartNode->gvalue)
{
PNodecommu;
tmpChartNode->parent=tmpLNode->parent;
tmpChartNode->gvalue=tmpLNode->gvalue;
tmpChartNode->fvalue=tmpLNode->fvalue;
freeSpringLink(tmpChartNode->child);
tmpChartNode->child=NULL;
popNode(thePreNode,commu);
addAscNode(open,commu);
}
free(tmpLNode);
}
//新的状态即此状态既不在open表中也不在closed表中
else
{
addSpringNode(tmpNode,tmpLNode);
addAscNode(open,tmpLNode);
}
}
}

//目标可达的话,输出最佳的路径
if(getGoal)
{
cout<<endl;
cout<<"路径长度为:"<<tmpNode->gvalue<<endl;
outputBestRoad(tmpNode);
}

//释放结点所占的内存
freeLink(open);
freeLink(closed);
_getch();
}

int_tmain(intargc,_TCHAR*argv[])
{
AStar();
return0;
}

  

爱华网本文地址 » http://www.413yy.cn/a/25101017/359132.html

更多阅读

蛋白质和淀粉分食减肥法 蛋白质减肥法成功经验

蛋白质和淀粉分食减肥法(具体方法转载)【第一周】-无醣?素食?      第一个星期你必须先让身体暖身      爱吃什么就吃什么的你 你的胃不可能接受突然的节食      必须规律吃三餐的人>改成以往食量的2/3      不须规

声明:《八数码问题 八数码问题 分支线界法》为网友煙酒憊分享!如侵犯到您的合法权益请联系我们删除