本文目录一览:
- 1、求八数码问题算法,并说明下该算法优缺点,要算法,不是源代码(可以没有)。
- 2、哪位高手帮我做做这道题吧,人工智能的,O(∩_∩)O谢谢啦,题在下面!
- 3、求8数码A或A*算法(用C语言)
- 4、问: 40 人工智能及其应用期末作业 用A*算法解决下面的八数码难题。试定义估价函数,启发函数,
- 5、八数码问题的问题,有解条件以及求解算法(宽度优先搜索)
- 6、八数码问题的扩展节点数,生成节点数怎么求
求八数码问题算法,并说明下该算法优缺点,要算法,不是源代码(可以没有)。
八数码问题
一.八数码问题
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。-8数码问题搜索树
八数码问题一般使用搜索法来解。搜索法有广度优先搜索法、深度优先搜索法、A*算法等。这里通过用不同方法解八数码问题来比较一下不同搜索法的效果。
二.搜索算法基类
1.八数码问题的状态表示
八数码问题的一个状态就是八个数字在棋盘上的一种放法。每个棋子用它上面所标的数字表示,并用0表示空格,这样就可以将棋盘上棋子的一个状态存储在一个一维数组p[9]中,存储的顺序是从左上角开始,自左至右,从上到下。也可以用一个二维数组来存放。-8数码问题搜索树
2.结点
搜索算法中,问题的状态用结点描述。结点中除了描述状态的数组p[9]外,还有一个父结点指针last,它记录了当前结点的父结点编号,如果一个结点v是从结点u经状态变化而产生的,则结点u就是结点v的父结点,结点v的last记录的就是结点u的编号。在到达目标结点后,通过last 可以找出搜索的路径。-8数码问题搜索树
3.类的结构
在C++中用类来表示结点,类将结点有关的数据操作封装在一起。
不同的搜索算法具有一定共性,也有各自的个性,因此这里将不同搜索算法的共有的数据和功能封装在一个基类中,再通过继承方式实现不同的搜索算法。
4.结点扩展规则
搜索就是按照一定规则扩展已知结点,直到找到目标结点或所有结点都不能扩展为止。
八数码问题的结点扩展应当遵守棋子的移动规则。按照棋子移动的规则,每一次可以将一个与空格相邻棋子移动到空格中,实际上可以看作是空格作相反移动。空格移动的方向可以是右、下、左、上,当然不能移出边界。棋子的位置,也就是保存状态的数组元素的下标。空格移动后,它的位置发生变化,在不移出界时,空格向右、下、左和上移动后,新位置是原位置分别加上1、3、-1、-3,如果将空格向右、下、左和上移动分别用0、1、2、3表示,并将-3、3、-1、1放在静态数组d[4]中,空格位置用spac表示,那么空格向方向i移动后,它的位置变为spac+d[i]。空格移动所产生的状态变化,反映出来则是将数组p[]中,0的新位置处的数与0交换位置。-8数码问题搜索树
5.八数码问题的基类
八数码问题的基类及其成员函数的实现如下:
#define Num 9
class TEight
{
public:
TEight(){}
TEight(char *fname); //用文件数据构造节点
virtual void Search()=0; //搜索
protected:
int p[Num];
int last,spac;
static int q[Num],d[],total;
void Printf();
bool operator==(const TEight T);
bool Extend(int i);
};
int TEight::q[Num];//储存目标节点
int TEight::d[]={1,3,-1,-3};//方向
int TEight::total=0;//步数
TEight::TEight(char *fname)
{
ifstream fin;
fin.open(fname,ios::in);
if(!fin)
{
cout"不能打开数据文件!"endl;
return;
}
int i;
for(i=0;iNum;)//得到源节点
finp[i++];
finspac;
for(i=0;iNum;)//得到目标节点
finq[i++];
fin.close();
last=-1;
total=0;
}
void TEight::Printf()//把路径打印到结果文件
{
ofstream fout;
fout.open("eight_result.txt",ios::ate|ios::app);
fouttotal++"t";
for(int i=0;iNum;)
fout" "p[i++];
foutendl;
fout.close();
}
bool TEight::operator==(const TEight T)//判断两个状态是否相同
{
for(int i=0;iNum;)
if(T.p[i]!=p[i++])
return 0;
return 1;
}
bool TEight::Extend(int i)//扩展
{
if(i==0 spac%3==2 || i==1 spac5
|| i==2 spac%3==0 || i==3 spac3)
return 0;
int temp=spac;
spac+=d[i];
p[temp]=p[spac];
p[spac]=0;
return 1;
}
数据文件的结构:
一共三行,第一行是用空格隔开的九个数字0~8,这是初始状态。第二行是一个数字,空格(数字0)的位置,第三行也是用空格隔开的九个数字0~8,这是目标状态。
三.线性表
搜索法在搜索过程中,需要使用一个队列存储搜索的中间结点,为了在找到目标结点后,能够找到从初始结点到目标结点的路径,需要保留所有搜索过的结点。另一方面,不同问题甚至同一问题的不同搜索方法中,需要存储的结点数量相差很大,所以这里采用链式线性表作为存储结构,同时,为适应不同问题,线性表设计成类模板形式。-8数码问题搜索树
templateclass Type class TList; //线性表前视定义
templateclass Type class TNode //线性表结点类模板
{
friend class TListType;
public:
TNode(){}
TNode(const Type dat);
private:
TNodeType* Next;
Type Data;
};
templateclass Type class TList
{
public:
TList(){Last=First=0;Length=0;} //构造函数
int Getlen()const{return Length;} //成员函数,返回线性表长度
int Append(const Type T); //成员函数,从表尾加入结点
int Insert(const Type T,int k); //成员函数,插入结点
Type GetData(int i); //成员函数,返回结点数据成员
void SetData(const Type T,int k); //成员函数,设置结点数据成员
private:
TNodeType *First,*Last; //数据成员,线性表首、尾指针
int Length; //数据成员,线性表长度
};
templateclass Type int TListType::Append(const Type T)
{
Insert(T,Length);
return 1;
}
templateclass Type int TListType::Insert(const Type T,int k)
{
TNodeType *p=new TNodeType;
p-Data=T;
if(First)
{
if(k=0)
{
p-Next=First;
First=p;
}
if(kLength-1)
{
Last-Next=p;
Last=Last-Next;
Last-Next=0;
}
if(k0 kLength)
{
k--;
TNodeType *q=First;
while(k--0)
q=q-Next;
p-Next=q-Next;
q-Next=p;
}
}
else
{
First=Last=p;
First-Next=Last-Next=0;
}
Length++;
return 1;
}
templateclass Type Type TListType::GetData(int k)
{
TNodeType *p=First;
while(k--0)
p=p-Next;
return p-Data;
}
templateclass Type void TListType::SetData(const Type T,int k)
{
TNodeType *p=First;
while(k--0)
p=p-Next;
p-Data=T;
}
线性表单独以头文件形式存放。
四.广度优先搜索法
在搜索法中,广度优先搜索法是寻找最短路经的首选。
1.广度优先搜索算法的基本步骤
1)建立一个队列,将初始结点入队,并设置队列头和尾指针
2)取出队列头(头指针所指)的结点进行扩展,从它扩展出子结点,并将这些结点按扩展的顺序加入队列。
3)如果扩展出的新结点与队列中的结点重复,则抛弃新结点,跳至第六步。
4)如果扩展出的新结点与队列中的结点不重复,则记录其父结点,并将它加入队列,更新队列尾指针。
5)如果扩展出的结点是目标结点,则输出路径,程序结束。否则继续下一步。
6)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。
2.搜索路径的输出
搜索到目标结点后,需要输出搜索的路径。每个结点有一个数据域last,它记录了结点的父结点,因此输出搜索路径时,就是从目标结点Q出发,根据last找到它的父结点,再根据这个结点的last找到它的父结点,....,最后找到初始结点。搜索的路径就是从初始结点循相反方向到达目标结点的路径。-8数码问题搜索树
3.广度优先搜索法TBFS类的结构
广度优先搜索法TBFS类是作为TEight类的一个子类。其类的结构和成员函数的实现如下:
class TBFS:public TEight
{
public:
TBFS(){}
TBFS(char *fname):TEight(fname){}
virtual void Search();
private:
void Printl(TListTBFS L);
int Repeat(TListTBFS L);
int Find();
};
void TBFS::Printl(TListTBFS L)
{
TBFS T=*this;
if(T.last==-1)
return;
else
{
T=L.GetData(T.last);
T.Printl(L);
T.Printf();
}
}
int TBFS::Repeat(TListTBFS L)
{
int n=L.Getlen();
int i;
for(i=0;in;i++)
if(L.GetData(i)==*this)
break;
return i;
}
int TBFS::Find()
{
for(int i=0;iNum;)
if(p[i]!=q[i++])
return 0;
return 1;
}
void TBFS::Search()
{
TBFS T=*this;
TListTBFS L;
L.Append(T);
int head=0,tail=0;
while(head=tail)
{
for(int i=0;i4;i++)
{
T=L.GetData(head);
if(T.Extend(i) T.Repeat(L)tail)
{
T.last=head;
L.Append(T);
tail++;
}
if(T.Find())
{
T.Printl(L);
T.Printf();
return;
}
}
head++;
}
}
4.广度优先搜索法的缺点
广度优先搜索法在有解的情形总能保证搜索到最短路经,也就是移动最少步数的路径。但广度优先搜索法的最大问题在于搜索的结点数量太多,因为在广度优先搜索法中,每一个可能扩展出的结点都是搜索的对象。随着结点在搜索树上的深度增大,搜索的结点数会很快增长,并以指数形式扩张,从而所需的存储空间和搜索花费的时间也会成倍增长。-8数码问题搜索树
五、A*算法
1.启发式搜索
广度优先搜索和双向广度优先搜索都属于盲目搜索,这在状态空间不大的情况下是很合适的算法,可是当状态空间十分庞大时,它们的效率实在太低,往往都是在搜索了大量无关的状态结点后才碰到解答,甚至更本不能碰到解答。-8数码问题搜索树
搜索是一种试探性的查寻过程,为了减少搜索的盲目性引,增加试探的准确性,就要采用启发式搜索了。所谓启发式搜索就是在搜索中要对每一个搜索的位置进行评估,从中选择最好、可能容易到达目标的位置,再从这个位置向前进行搜索,这样就可以在搜索中省略大量无关的结点,提高了效率。-8数码问题搜索树
2.A*算法
A*算法是一种常用的启发式搜索算法。
在A*算法中,一个结点位置的好坏用估价函数来对它进行评估。A*算法的估价函数可表示为:
f'(n) = g'(n) + h'(n)
这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值(也称为最小耗费或最小代价),h'(n)是n到目标的最短路经的启发值。由于这个f'(n)其实是无法预先知道的,所以实际上使用的是下面的估价函数:-8数码问题搜索树
f(n) = g(n) + h(n)
其中g(n)是从初始结点到节点n的实际代价,h(n)是从结点n到目标结点的最佳路径的估计代价。在这里主要是h(n)体现了搜索的启发信息,因为g(n)是已知的。用f(n)作为f'(n)的近似,也就是用g(n)代替g'(n),h(n)代替h'(n)。这样必须满足两个条件:(1)g(n)=g'(n)(大多数情况下都是满足的,可以不用考虑),且f必须保持单调递增。(2)h必须小于等于实际的从当前节点到达目标节点的最小耗费h(n)=h'(n)。第二点特别的重要。可以证明应用这样的估价函数是可以找到最短路径的。-8数码问题搜索树
3.A*算法的步骤
A*算法基本上与广度优先算法相同,但是在扩展出一个结点后,要计算它的估价函数,并根据估价函数对待扩展的结点排序,从而保证每次扩展的结点都是估价函数最小的结点。
A*算法的步骤如下:
1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。
2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。
3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。-8数码问题搜索树
4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。
5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。
4.八数码问题的A*算法的估价函数
估价函数中,主要是计算h,对于不同的问题,h有不同的含义。那么在八数码问题中,h的含意是各什么?八数码问题的一个状态实际上是数字0~8的一个排列,用一个数组p[9]来存储它,数组中每个元素的下标,就是该数在排列中的位置。例如,在一个状态中,p[3]=7,则数字7的位置是3。如果目标状态数字3的位置是8,那么数字7对目标状态的偏移距离就是3,因为它要移动3步才可以回到目标状态的位置。-8数码问题搜索树
八数码问题中,每个数字可以有9个不同的位置,因此,在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离:-8数码问题搜索树
0 1 2 3 4 5 6 7 8
0 0 1 2 1 2 3 2 3 4
1 1 0 1 2 1 2 3 2 3
2 2 1 0 3 2 1 4 3 2
3 1 2 3 0 1 2 1 2 3
4 2 1 2 1 0 1 2 1 2
5 3 2 1 2 1 0 3 2 1
6 2 3 4 1 2 3 0 1 2
7 3 2 3 2 1 2 1 0 1
8 4 3 2 3 2 1 2 1 0
例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。
估价函数中的h就是全体数字偏移距离之和。显然,要计算两个不同状态中同一数字的偏移距离,需要知道该数字在每个状态中的位置,这就要对数组p[9]进行扫描。由于状态发生变化,个数字的位置也要变化,所以每次计算h都沿线扫描数组,以确定每个数字在数组中的位置。为了简化计算,这里用一个数组存储状态中各个数字的位置,并让它在状态改变时随着变化,这样就不必在每次计算h时,再去扫描状态数组。-8数码问题搜索树
例如,某个状态中,数字5的位置是8,如果用数组r[9]存储位置,那么就有r[5]=8。
现在用数组r[9]存储当前状态的数字位置,而用s[9]存储目标状态的数字位置,那么当前状态数字i对目标状态的偏移距离就是矩阵中r[i]行s[i]列对应的值。
5.A*算法的类结构
A*算法的类声明如下:
class TAstar:public TEight
{
public:
TAstar(){} //构造函数
TAstar(char *fname); //带参数构造函数
virtual void Search(); //A*搜索法
private:
int f,g,h; //估价函数
int r[Num]; //存储状态中各个数字位置的辅助数组
static int s[Num]; //存储目标状态中各个数字位置的辅助数组
static int e[]; //存储各个数字相对距离的辅助数组
void Printl(TListTAstar L); //成员函数,输出搜索路径
int Expend(int i); //成员函数,A*算法的状态扩展函数
int Calcuf(); //成员函数,计算估价函数
void Sort(TListTAstar L,int k); //成员函数,将新扩展结点按f从小到大顺序插入待扩展结点队列
int Repeat(TListTAstar L); //成员函数,检查结点是否重复
};
int TAstar::s[Num],TAstar::e[Num*Num];
TAstar::TAstar(char *fname):TEight(fname)
{
for(int i=0;iNum;)
{
r[p[i]]=i; //存储初始状态个个数字的位置
s[q[i]]=i++; //存储目标状态个个数字的位置
}
ifstream fin;
fin.open("eight_dis.txt",ios::in); //打开数据文件
if(!fin)
{
cout"不能打开数据文件!"endl;
return;
}
for(int i=0;iNum*Num;i++) //读入各个数字相对距离值
fine[i];
fin.close();
f=g=h=0; //估价函数初始值
}
void TAstar::Printl(TListTAstar L)
{
TAstar T=*this;
if(T.last==-1) return;
else
{
T=L.GetData(T.last);
T.Printl(L);
T.Printf();
}
}
int TAstar::Expend(int i)
{
if(Extend(i)) //结点可扩展
{
int temp=r[p[r[0]]]; //改变状态后数字位置变化,存储改变后的位置
r[p[r[0]]]=r[0];
r[0]=temp;
return 1;
}
return 0;
}
int TAstar::Calcuf()
{
h=0;
for(int i=0;iNum;i++) //计算估价函数的 h
h+=e[Num*r[i]+s[i]];
return ++g+h;
}
void TAstar::Sort(TListTAstar L,int k)
{
int n=L.Getlen();
int i;
for(i=k+1;in;i++)
{
TAstar T=L.GetData(i);
if(this-f=T.f)
break;
}
L.Insert(*this,i);
}
int TAstar::Repeat(TListTAstar L)
{
int n=L.Getlen();
int i;
for(i=0;in;i++)
if(L.GetData(i)==*this)
break;
return i;
}
void TAstar::Search()
{
TAstar T=*this; //初始结点
T.f=T.Calcuf(); //初始结点的估价函数
TListTAstar L; //建立队列
L.Append(T); //初始结点入队
int head=0,tail=0; //队列头和尾指针
while(head=tail) //队列不空则循环
{
for(int i=0;i4;i++) //空格可能移动方向
{
T=L.GetData(head); //去队列头结点
if(T.h==0) //是目标结点
{
T.Printl(L);//输出搜索路径
T.Printf(); //输出目标状态
return; //结束
}
if(T.Expend(i)) //若结点可扩展
{
int k=T.Repeat(L); //返回与已扩展结点重复的序号
if(khead) //如果是不能扩展的结点
continue; //丢弃
T.last=head; //不是不能扩展的结点,记录父结点
T.f=T.Calcuf(); //计算f
if(k=tail) //新结点与可扩展结点重复
{
TAstar Temp=L.GetData(k);
if(Temp.gT.g) //比较两结点g值
L.SetData(T,k); //保留g值小的
continue;
}
T.Sort(L,head) ; //新结点插入可扩展结点队列
tail++; //队列尾指针后移
}
}
head++; //一个结点不能再扩展,队列头指针指向下一结点
}
}
六、测试程序
A*算法的测试:
int main()
{
TAstar aStar("eight.txt");
aStar.Search();
system("pauze");
return 0;
}
eight.txt文件中的数据(初始态和目标态):
一共三行,第一行是用空格隔开的九个数字0~8,这是初始状态。第二行是一个数字,空格(数字0)的位置,第三行也是用空格隔开的九个数字0~8,这是目标状态。
8 3 5 1 2 7 4 6 0
8
1 2 3 4 5 6 7 8 0
eight_dis.txt中的数据(估计函数使用)
0 1 2 1 2 3 2 3 4
1 0 1 2 1 2 3 2 3
2 1 0 3 2 1 4 3 2
1 2 3 0 1 2 1 2 3
2 1 2 1 0 1 2 1 2
3 2 1 2 1 0 3 2 1
2 3 4 1 2 3 0 1 2
3 2 3 2 1 2 1 0 1
4 3 2 3 2 1 2 1 0
eight_Result.txt中的结果(运行后得到的结果)
七、算法运行结果
1.BFS算法只能适用于到达目标结点步数较少的情况,如果步数超过15步,运行时间太长,实际上不再起作用。
2.对于随机生成的同一个可解状态,BFS算法最慢,DBFS算法较慢,A*算法较快。但在15步以内,DBFS算法与A*算法相差时间不大,超过15步后,随步数增加,A*算法的优势就逐渐明显,A*算法要比DBFS算法快5倍以上,并随步数增大而增大。到25步以上,DBFS同样因运行时间过长而失去价值。-8数码问题搜索树
3.一般来说,解答的移动步数每增加1,程序运行时间就要增加5倍以上。由于八数码问题本身的特点,需要检查的节点随步数增大呈指数形式增加,即使用A*算法,也难解决移动步数更多的问题。
八、问题可解性
八数码问题的一个状态实际上是0~9的一个排列,对于任意给定的初始状态和目标,不一定有解,也就是说从初始状态不一定能到达目标状态。因为排列有奇排列和偶排列两类,从奇排列不能转化成偶排列或相反。
如果一个数字0~8的随机排列871526340,用F(X)表示数字X前面比它小的数的个数,全部数字的F(X)之和为Y=∑(F(X)),如果Y为奇数则称原数字的排列是奇排列,如果Y为偶数则称原数字的排列是偶排列。-8数码问题搜索树
例如871526340这个排列的
Y=0+0+0+1+1+3+2+3+0=10
10是偶数,所以他偶排列。871625340
Y=0+0+0+1+1+2+2+3+0=9
9是奇数,所以他奇排列。
因此,可以在运行程序前检查初始状态和目标状态的窘是否相同,相同则问题可解,应当能搜索到路径。否则无解。
PS:整理自网络
哪位高手帮我做做这道题吧,人工智能的,O(∩_∩)O谢谢啦,题在下面!
实际上就是拼图
s0中2右移,空出一排第一个位置;
再 1上移,空出2排第一个位置;
最后,8左移,空出2排第二个位置。即成为sg。
求8数码A或A*算法(用C语言)
题目地址:
BFS:
#include iostream
using namespace std;
int fac[10]={1,1};
bool tflag[9];
struct bbit{
unsigned int val:4;
};
struct bbbit
{
unsigned int val:2;
};
struct Node
{
bbit s[9],pos;
int step;
bbbit path[21],tag;
int hashval()
{
int ret=0,i,j,tmp;
memset(tflag,false,sizeof(tflag));
for(i=0;i8;i++)
{
tmp=0;
for(j=0;js[i].val;j++)
if(!tflag[j])
tmp++;
ret+=tmp*fac[8-i];
tflag[s[i].val]=true;
}
return ret;
}
bool up()
{
if(pos.val=2)return false;
s[pos.val].val^=s[pos.val-3].val;
s[pos.val-3].val^=s[pos.val].val;
s[pos.val].val^=s[pos.val-3].val;
path[step].val=0;
pos.val-=3;
return true;
}
bool down()
{
if(pos.val=6)return false;
s[pos.val].val^=s[pos.val+3].val;
s[pos.val+3].val^=s[pos.val].val;
s[pos.val].val^=s[pos.val+3].val;
path[step].val=1;
pos.val+=3;
return true;
}
bool left()
{
if(pos.val==0||pos.val==3||pos.val==6)return false;
s[pos.val].val^=s[pos.val-1].val;
s[pos.val-1].val^=s[pos.val].val;
s[pos.val].val^=s[pos.val-1].val;
path[step].val=2;
pos.val--;
return true;
}
bool right()
{
if(pos.val==2||pos.val==5||pos.val==8)return false;
s[pos.val].val^=s[pos.val+1].val;
s[pos.val+1].val^=s[pos.val].val;
s[pos.val].val^=s[pos.val+1].val;
path[step].val=3;
pos.val++;
return true;
}
bool operator==(const Nodex)const
{
int i;
for(i=0;i9;i++)if(s[i].val!=x.s[i].val)return false;
return true;
}
}Q[362880],S,A,tmp,top;
struct Hash
{
bool d1,d2;
Node D;
}hash[362880];
inline void mkfac(){int i;for(i=2;i=9;i++)fac[i]=fac[i-1]*i;}
inline int eval(char c){return c=='x'?0:c-'0';}
void o(Node x,Node y)
{
int i;
for(i=1;i=x.step;i++)
{
switch(x.path[i].val)
{
case 0:putchar('u');break;
case 1:putchar('d');break;
case 2:putchar('l');break;
case 3:putchar('r');break;
}
}
for(i=y.step;i=1;i--)
switch(y.path[i].val){
case 0:putchar('d');break;
case 1:putchar('u');break;
case 2:putchar('r');break;
case 3:putchar('l');break;
}
puts("");
}
int main()
{
char buf[11];
int i,t,l,r;
bool flag;
mkfac();
while(NULL!=gets(buf))
{
t=0;
for(i=0;i=7;i++)A.s[i].val=i+1;A.s[8].val=0;A.pos.val=8;
for(i=0;buf[i];i++)
{
if(buf[i]==' ')continue;
S.s[t].val=eval(buf[i]);
if(S.s[t].val==0)
S.pos.val=t;
t++;
}
l=r=0;
flag=false;
for(i=0;i362880;i++)hash[i].d1=hash[i].d2=false;
S.step=0;S.tag.val=1;
A.step=0;A.tag.val=2;
Q[r++]=S;//tag.val:1
Q[r++]=A;//tag.val:2
while(l=r)
{
top=Q[l++];
top.step++;
tmp=top;
if(tmp.up())
{
if(tmp.tag.val==1)
{
if(!hash[t=tmp.hashval()].d1)
{
hash[t].d1=true;
Q[r++]=tmp;
if(hash[t].d2hash[t].D==tmp)
{
//find ans...
o(tmp,hash[t].D);
goto AA;
}
if(!hash[t].d2)hash[t].D=tmp;
}
}
else
{
if(!hash[t=tmp.hashval()].d2)
{
hash[t].d2=true;
Q[r++]=tmp;
if(hash[t].d1hash[t].D==tmp)
{
//find ans...
o(hash[t].D,tmp);
goto AA;
}
if(!hash[t].d1)hash[t].D=tmp;
}
}
}
tmp=top;
if(tmp.down())
{
if(tmp.tag.val==1)
{
if(!hash[t=tmp.hashval()].d1)
{
hash[t].d1=true;
Q[r++]=tmp;
if(hash[t].d2hash[t].D==tmp)
{
//find ans...
o(tmp,hash[t].D);
goto AA;
}
if(!hash[t].d2)hash[t].D=tmp;
}
}
else
{
if(!hash[t=tmp.hashval()].d2)
{
hash[t].d2=true;
Q[r++]=tmp;
if(hash[t].d1hash[t].D==tmp)
{
//find ans...
o(hash[t].D,tmp);
goto AA;
}
if(!hash[t].d1)hash[t].D=tmp;
}
}
}
tmp=top;
if(tmp.left())
{
if(tmp.tag.val==1)
{
if(!hash[t=tmp.hashval()].d1)
{
hash[t].d1=true;
Q[r++]=tmp;
if(hash[t].d2hash[t].D==tmp)
{
//find ans...
o(tmp,hash[t].D);
goto AA;
}
if(!hash[t].d2)hash[t].D=tmp;
}
}
else
{
if(!hash[t=tmp.hashval()].d2)
{
hash[t].d2=true;
Q[r++]=tmp;
if(hash[t].d1hash[t].D==tmp)
{
//find ans...
o(hash[t].D,tmp);
goto AA;
}
if(!hash[t].d1)hash[t].D=tmp;
}
}
}
tmp=top;
if(tmp.right())
{
if(tmp.tag.val==1)
{
if(!hash[t=tmp.hashval()].d1)
{
hash[t].d1=true;
Q[r++]=tmp;
if(hash[t].d2hash[t].D==tmp)
{
//find ans...
o(tmp,hash[t].D);
goto AA;
}
if(!hash[t].d2)hash[t].D=tmp;
}
}
else
{
if(!hash[t=tmp.hashval()].d2)
{
hash[t].d2=true;
Q[r++]=tmp;
if(hash[t].d1hash[t].D==tmp)
{
//find ans...
o(hash[t].D,tmp);
goto AA;
}
if(!hash[t].d1)hash[t].D=tmp;
}
}
}
}
AA:flag=true;
if(!flag)
puts("unsolvable");
}
return 0;
}
A*:
#include iostream
#include queue
using namespace std;
int fac[10]={1,1};
struct Node
{
int s[9],step,pos;
char path[501];
int hashval()
{
int ret=0,i,j,tmp;
bool flag[9];
memset(flag,false,sizeof(flag));
for(i=0;i8;i++)
{
tmp=0;
for(j=0;js[i];j++)
if(!flag[j])
tmp++;
ret+=tmp*fac[8-i];
flag[s[i]]=true;
}
return ret;
}
bool up()
{
if(pos=2)return false;
s[pos]^=s[pos-3];
s[pos-3]^=s[pos];
s[pos]^=s[pos-3];
path[step]='u';
pos-=3;
return true;
}
bool down()
{
if(pos=6)return false;
s[pos]^=s[pos+3];
s[pos+3]^=s[pos];
s[pos]^=s[pos+3];
path[step]='d';
pos+=3;
return true;
}
bool left()
{
if(pos==0||pos==3||pos==6)return false;
s[pos]^=s[pos-1];
s[pos-1]^=s[pos];
s[pos]^=s[pos-1];
path[step]='l';
pos--;
return true;
}
bool right()
{
if(pos==2||pos==5||pos==8)return false;
s[pos]^=s[pos+1];
s[pos+1]^=s[pos];
s[pos]^=s[pos+1];
path[step]='r';
pos++;
return true;
}
bool operator==(const Nodex)const
{
int i;
for(i=0;i9;i++)if(s[i]!=x.s[i])return false;
return true;
}
void show()
{
int i,j;
for(i=0;i=6;i+=3,coutendl)
for(j=i;j=i+2;j++)
couts[j];
}
bool operator(const Nodex)const
{
int la=0,lb=0,i;
for(i=0;i8;i++)if(s[i]!=i+1)la++;la+=(s[8]!=0);
for(i=0;i8;i++)if(x.s[i]!=i+1)lb++;lb+=(x.s[8]!=0);
return lalb;
}
}S,A,tmp,top;
priority_queueNode Q;
bool hash[362880];
void mkfac(){int i;for(i=2;i=9;i++)fac[i]=fac[i-1]*i;}
int eval(char c){return c=='x'?0:c-'0';}
void output(Node x)
{
int i;
for(i=1;i=x.step;i++)
putchar(x.path[i]);
puts("");
}
int main()
{
char buf[11];
int i,t,l,r;
bool flag;
mkfac();
while(NULL!=gets(buf))
{
t=0;
for(i=0;i=7;i++)A.s[i]=i+1;A.s[8]=0;A.pos=8;
for(i=0;buf[i];i++)
{
if(buf[i]==' ')continue;
S.s[t]=eval(buf[i]);
if(S.s[t]==0)
S.pos=t;
t++;
}
l=r=0;
flag=false;
memset(hash,false,sizeof(hash));
S.step=0;
while(!Q.empty())Q.pop();
Q.push(S);
while(!Q.empty())
{
top=Q.top();
Q.pop();
top.step++;
tmp=top;
if(tmp.up())
{
if(!hash[t=tmp.hashval()])
{
hash[t]=true;
Q.push(tmp);
if(tmp==A)
{
//find ans...
output(tmp);
goto AA;
}
}
}
tmp=top;
if(tmp.down())
{
if(!hash[t=tmp.hashval()])
{
hash[t]=true;
Q.push(tmp);
if(tmp==A)
{
//find ans...
output(tmp);
goto AA;
}
}
}
tmp=top;
if(tmp.left())
{
if(!hash[t=tmp.hashval()])
{
hash[t]=true;
Q.push(tmp);
if(tmp==A)
{
//find ans...
output(tmp);
goto AA;
}
}
}
tmp=top;
if(tmp.right())
{
if(!hash[t=tmp.hashval()])
{
hash[t]=true;
Q.push(tmp);
if(tmp==A)
{
//find ans...
output(tmp);
goto AA;
}
}
}
}
AA:flag=true;
if(!flag)
puts("unsolvable");
}
return 0;
}
问: 40 人工智能及其应用期末作业 用A*算法解决下面的八数码难题。试定义估价函数,启发函数,
#pragma warning(disable:4786)
#include algorithm
#include cstdio
#include set
#include utility
#include ctime
#include cassert
#include cstring
#include iostream
using namespace std;
/*item记录搜索空间中一个结点
state 记录用整数形式表示的8数码格局
blank 记录当前空格位置,主要用于程序优化,
扩展时可不必在寻找空格位置
g, h 对应g(n), h(n)
pre 记录当前结点由哪个结点扩展而来 */
struct item
{
int state;
int blank;
int g;
int h;
int pre;
};
const int MAXSTEPS = 100000;
const int MAXCHAR = 100;
char buf[MAXCHAR][MAXCHAR]; //open表
item open[MAXSTEPS];
//vectoritem open;
int steps = 0;
//closed表,已查询状态只要知道该状态以及它由哪个结点扩展而来即可,用于输出路径
//每次只需得到对应f值最小的待扩展结点,用堆实现提高效率
pairint, int closed[MAXSTEPS];
//读入,将8数码矩阵格局转换为整数表示
bool read(pairint,int state)
{
if (!gets(buf[0]))
return false;
if (!gets(buf[1]))
return false;
if (!gets(buf[2]))
return false;
//cout strlen(buf[0]) ' ' strlen(buf[1]) ' ' strlen(buf[2]) endl;
assert(strlen(buf[0]) == 5 strlen(buf[1]) == 5 strlen(buf[2]) == 5);
// astar.in中的每行数据长度必须为5
state.first = 0;
for (int i = 0, p = 1; i 3; ++i)
{
for (int j = 0; j 6; j += 2)
{
if (buf[i][j] == '0')
state.second = i * 3 + j / 2; // state.second为0(空格)在节点中的位置
else
state.first += p * (buf[i][j] - '0');
p *= 10;
}
}
/* 若初试节点为:
1 2 3
8 0 4
7 6 5
则state.first为567408321,state.second为4
*/
return true;
}
//计算当前结点距目标的距离
int calculate(int current, int target) // return h=the sum of distances each block have to move to the right position,这里的each block不包括空格-8数码问题搜索树
{
int c[9], t[9];
int i, cnt = 0;
for (i = 0; i 9; ++i)
{
c[current % 10] = t[target % 10] = i;
current /= 10;
target /= 10;
}
for (i = 1; i 9; ++i)
cnt += abs(c[i] / 3 - t[i] / 3) + abs(c[i] % 3 - t[i] % 3);
return cnt;
}
//open表中结点间选择时的规则 f(n) = g(n) + h(n)
class cmp
{
public: inline bool operator()(item a, item b)
{
return a.g + a.h b.g + b.h;
}
};
//将整数形式表示转换为矩阵表示输出
void pr(int state)
{
memset(buf, ' ', sizeof(buf));
for (int i = 0; i 3; ++i)
{
for (int j = 0; j 6; j += 2)
{
if (state % 10)
buf[i][j] = state % 10 + '0';
state /= 10;
}
buf[i][5] = '\0';
puts(buf[i]);
}
}
//用于判断当前空格是否可以向对应方向移动
inline bool suit(int a, int b) //空格移动后的坐标为(a,b)
{
return (a = 0 a 3 b = 0 b 3);
}
//递归输出搜索路径
void path(int index)
{
if (index == 0)
{
pr(closed[index].first);
puts("");
return;
}
path(closed[index].second);
pr(closed[index].first); //将整数形式表示转换为矩阵表示输出
puts("");
++steps;
}
int getNixuNum( int state ) //求节点的逆序对数
{
int sum = 0;
int result[9];
memset( result, 0, sizeof(result) );
//cout result[8] result[7] endl;
char buf[10];
itoa( state, buf, 10 );
//cout buf endl;
int k = 0;
while( buf[k] != '\0' )
{
result[9-k-1] = buf[k] - '0';
k++;
}
for( int i = 0; i 9; i++ )
{
for( int j = i + 1; j 9; j++ )
{
if( result[i] result[j] result[i] result[j] )
{
sum++;
}
}
}
return sum; //返回3*3方格数组的逆序对数
}
int main()
{
//cout getNixuNum(87654321);
//open.resize(MAXSTEPS);
unsigned int t1 = clock();
//cout open.size() endl;
if( freopen("astar.in", "r", stdin) == NULL )
{
cout "file not find\n";
exit(0);
};
freopen("astar2.out", "w", stdout);
setintstates;
char tmp[100];
int i, x, y, a, b, nx, ny, end, next, index, kase = 0;
pairint,int start, target;
item head; //4个方向移动时的偏移量
const int xtran[4] = {-1, 0, 1, 0};
const int ytran[4] = {0, 1, 0, -1};
const int p[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
while (read(start)) // 读取初试状态节点
{
unsigned int t2 = clock();
printf("Case %d:\n\n", ++kase);
gets(tmp);
read(target); // 读取目标状态节点
gets(tmp);
int targetNixuNum = getNixuNum(target.first);
//若两者的逆序对数不是同为奇数或同为偶数,则无解
if( !(getNixuNum(start.first)1 targetNixuNum1 || !(getNixuNum(start.first)1) !(targetNixuNum1)) )-8数码问题搜索树
{
cout "无法从初始节点到终态节点\n";
exit(0);
}
//初始化open表,将初始状态加入
open[0].state = start.first;
open[0].h = calculate(start.first, target.first); // 计算当前节点到目标节点的估计距离
open[0].blank = start.second;
open[0].pre = -1; // 初始节点无父节点
open[0].g = 0; // 初始节点的g为0
index = 0;
states.insert(start.first); // 扩展过节点保存在states中,即出现过的状态保存在states中,states为setint类型,其中的states中的元素唯一
//提取open表中f值最小元素放入closed表,并对该结点进行扩展
for (end = 1; end 0; ++index) // end为open表中的元素个数,一直循环到open表为空
{
assert(index MAXSTEPS);
//临时存储
head = open[0]; // 由于使用pop_heap函数和push_heap函数,所以open[0]为g+h最小的元素
//放入closed表记录当前格局和由哪个结点扩展而来(该结点肯定已在closed表中)
closed[index].first = open[0].state; //放入close表中,表示已经扩展完的节点,下面的for循环会扩展其节点 -8数码问题搜索树
closed[index].second = open[0].pre; // index表示当前close表中当前扩展节点的下标
//从open表中删除该结点
pop_heap(open, open + end, cmp());//为algorithm文件中的函数,第一个参数指定开始位置,第二个指定结束,第三个指定比较函数 -8数码问题搜索树
--end;
//得到结果,递归输出路径
if (head.state == target.first)
{
path(index);
break;
}
x = head.blank / 3;
y = head.blank % 3; //空格在3*3方格中的x,y坐标
/*
|2 0 3|
A = |1 8 4|
|7 6 5| // 看成3*3的数组
则head.blank=1
x=0,y=1,即空格的在3*3的数组中下标为(0,1)
*/
for (i = 0; i 4; ++i)
{
nx = x + xtran[i];
ny = y + ytran[i];
/*
i=0时:(nx,ny)为当前空格向上移动一格后的坐标
i=1时:(nx,ny)为当前空格向右移动一格后的坐标
i=2时:(nx,ny)为当前空格向下移动一格后的坐标
i=3时:(nx,ny)为当前空格向左移动一格后的坐标
*/
if (suit(nx, ny)) // 判断是否能够移动
{
a = head.blank; // 空格当前位置,以上面矩阵A为例,a=1
b = nx * 3 + ny; // 空格移动后的新位置,开始是能够向右边移动,故b=0*3+2=2
//调换十进制表示整数对应两个数字位
next = head.state + ((head.state % p[a + 1]) / p[a] - (head.state % p[b + 1]) / p[b]) * p[b] + ((head.state % p[b + 1]) / p[b] - (head.state % p[a + 1]) / p[a]) * p[a]; -8数码问题搜索树
// 如head.state=567481302,空格向右移动一次后,next=567481032,即为移动后的节点
// 判断能否由当前节点到达目标节点
if( ( getNixuNum(next)1 targetNixuNum1 ) || ( !(getNixuNum(next)1) !(targetNixuNum1) ) )
{
//判断是否已经扩展过,即已经出现过
if (states.find(next) == states.end()) //没出现就保存一下,也存入open表 -8数码问题搜索树
{
states.insert(next);
open.pre = index; //扩展后的子节点,其父节点为当前扩展节点
open.blank = b;
open.state = next;
open.h = calculate(next,target.first);
open.g = head.g + 1;
++end; //open表中元素加1
push_heap(open, open + end, cmp()); //压入堆中
}
}
}
}
}
if (end = 0)
puts("No solution");
else
{
printf("Num of steps: %d\n", steps);
printf("Num of expanded: %d\n", index);
printf("Num of generated: %d\n", index + end);
printf("Time consumed: %d\n\n", clock() - t2);
}
states.clear();
steps = 0;
}
printf("Total time consumed: %d\n", clock() - t1);
return 0;
}
八数码问题的问题,有解条件以及求解算法(宽度优先搜索)
八数码问题:
取一个 3*3 的矩阵,将1-8(或者任意从小到大的八个数字),随机分布在矩阵中,留下一个空格(可以用0代替),作为初始状态;再去一个3*3的矩阵,将1-8(或者任意从小到大的八个数字,其取值必须与初始状态相同),随机分布在矩阵中,留下一个空格(可以用0代替),作为目标状态;对初始状态进行操作,其允许的操作是:将空格向上,下,左,右移动(即将空格与周边的数字进行交换),操作初始状态的矩阵,在若干步后达目标状态。求解其过程为八数码问题。如图:-8数码问题搜索树
八数码问题的有解条件:
将矩阵从上到下从左到右的顺序分布成一个数列,并去掉空格,例如:
2 8 3 (0为空格) 分布成数列后:
1 0 4 2 8 3 1 4 7 6 5
7 6 5
如果此 初始状态的数列(矩阵) 的 逆序数 与 目标状态的数列(矩阵) 的 逆序数 的 奇偶性一样 ,则此问题有解。
逆序数 的定义:
有一个数列,在这个数列中任意取一对数字(两个数字),其两个数字在数列中的(从前到后)顺序与数字数值的大小相反,称其为一个逆序。这个数列中所有的逆序的和为逆序数。
证明 :
空格向左右移动时,不改变逆序数的大小;空格向上下移动时,会改变逆序数,改变的幅度为±2或者0 (1)。所以±2或者0的改变幅度不会对逆序数造成奇偶性的改变。所以如果两个矩阵状态如果能互相到达,则必须有其逆序数的奇偶性一样。-8数码问题搜索树
(1) 在矩阵中操作空格上下移动时,在数列中的表现是将被交换的数字提前到他前面两个数字之前或者推后到他后面两个数字之后;例如,被交换的数字的下标是 Z 的话,空格向下移动(即被交换数向上移动)后,被交换数在数列中的位置是 Z-2 ;空格向上移动(即被交换数向下移动)后,则被交换数在数列中的位置是 Z+2。这种交换不会影响到被交换数与被它跨过的两个数以外的数字的顺序。比如说:被交换数的位置 Z ,如果空格向上移动,被交换数位置变成 Z+2,但是Z-1处的数字 与 Z 的顺序没有因为 Z 变成 Z+2 而失去了Z-1 在 Z 的前面的顺序,Z-1在Z前面,也在Z+2前面,同样的,Z变成Z+2也不会改变Z与Z+3的顺序。并且,如果有顺序 2 3 4 ,这个顺序的逆序数为0,如果我把4放到2 3前面,变成4 2 3,其逆序数就变成了+2,逆序数就增长了2;如果有顺序 4 2 3,其逆序数为+2,如果我把4放到2 3后面,变成2 3 4,其逆序数就变成了0,逆序数减少了2;如果有6 4 5,其逆序数为+2,如果我把5放在6 4 的前面,变成5 6 4,其逆序数为2,逆序数改变为0。所以改变的幅度为±2,或者0。-8数码问题搜索树
八数码问题的解法以及算法(宽度优先):
解法:
空格可以上下左右移动,则父状态可以衍生出若干个子状态(考虑其空格不能越3*3的界以及其不返回到父状态或者父父状态等等之类的情况的话,最大的子状态数量为4 最小为0),这种思路的话实际上这个问题就是一个树的搜索问题,所以用搜索的算法可以解决。-8数码问题搜索树
算法(宽度优先):
(1)判断初始状态与目标状态的逆序数的奇偶性来判断是否有解
(2)建立一个OPEN表(队列),用于存放待展开子节点(状态)的父节点
(3)建立一个CLOSED表(队列),用于存放已经展开了子节点的父节点
(4)将初始状态放到OPEN表中,作为第一个
(5)将OPEN表中第一个节点取出(并在OPEN表中删除这个节点),放到CLOSED表中,排在CLOSED表中最后一个,整理好OPEN表
(6)把CLOSED表最后一个节点按照条件(不越界,不返回到父节点)展开其子节点,并且将可能的子节点按照顺序存入OPEN表中
(7)判断此父节点是否为目标状态:
①是,退出,打印答案
②不是,回到步骤(4)
问题:
(1)如果不用数字,而是用毫无关系的符号来填充矩阵,怎么判断是否有解呢?
想法:将初始状态作为参考,将其不同的符号的顺序作为其符号的值的大小,计算出初始状态的逆序数为0,按照初始状态的值大小来判断目标状态的逆序数,然后判断奇偶性进而判断是否有解。
八数码问题的扩展节点数,生成节点数怎么求
二叉树中双分支结点就是度为2的结点,叶子就是度为0的结点 根据二叉树的性质:n0 = n2 + 1 所以叶子结点个数= 15+1 = 16个