POJ1077:八数码问题(Eight)
解题思路
求步数最少的解时候用广搜
- 状态的表示问题
- 若以字符串的形式表示,需要10字节,且检查是否达到目标状态时需要比较10个字节,占用空间、时间较多。
- 因此可以用int型的变量储存一个状态,如823146570、123456780等,且有相关字符串与整数的转换函数。
- 但在进行移动时,在int上无法操作,需是字符串才好操作(改变字符‘0’的位置),得到新的状态,再转换成int型放入队列。
- 状态的判重问题
-
n最大为876543210,此时就算用1bit的判重标记位,依旧需要过多空间
-
注意到实际有效状态数共为9!=362880,只需要将标志位与状态对应起来即可(由一个状态找到其标志位)
-
康托展开:可给定排列求序号,给定序号求排列
康托展开百科 复杂度:O(n2)
-
C++STL set容器:将所有扩展过的int状态都放到set中,判重时二分查找
C++STL容器set 复杂度:O(logn)
-
3.需要输出最优路径
-
在队列元素中储存指向父节点的指针(就是父节点在数组中的下标),以及父节点到当前节点的移动方式。
-
因此不便使用queue,应该自定义结构体数组实现队列
-
需要队头指针和队尾指针:
元素加入队列,就是在队尾指针的位置写入元素,然后队尾指针后移;
元素弹出队列,实际上只是向后移动队头指针
基础知识铺垫
-
字符串格式化命令:
#include int sprintf(char*str,const char *format,…)
-
字符串转化为整型
#include atoi()
-
string反转
#include reverse( s.begin(),s.end() )
-
set常用命令
插入insert()
二分搜索find() : 若找不到则返回end()
广度优先搜索BFS+康托展开
广度优先搜索(bfs)
优先扩展浅层节点,逐渐深入
- 用队列保存待扩展的结点
- 队首取出节点,拓展出的新节点放入队尾,直到队首出现目标节点
BFS()
{
初始化队列
while(队列不为空且未找到目标节点)
{
取队首节点扩展,并将扩展出的非重复节点放入队尾
必要时记住每个节点的父节点;(具体走法)
}
}
康托展开公式:
其中,ai表示后面元素比第i位小的个数,即第i位在未出现的元素中是排在第几个
eg. 34152 cantor=2x4!+2x3!+0x2!+1x1!+0x0!=61
//单向广搜,最简单做法--康托展开
//内存:2304kB 时间:481ms
#include <iostream>
#include <bitset>
#include <cstring>
using namespace std;
int goalSatus;//目标状态
bitset<362880>Flags;//节点是否被扩展的标记
const int MAXS=400000;
char result[MAXS];//结果
struct Node
{
int status;//状态,即排列的编号
int father;//父节点指针
char move;//父节点到本节点的移动方式u/r/d/l
Node(int s,int f,char m):status(s),father(f),move(m){}
Node(){}
};
Node myQueue[MAXS];//状态队列,状态总数362880
int qHead,qTail;//队头队尾指针
char sz4Moves[]="udrl";//四种动作
unsigned int factoria[21];//存放0-20的阶乘
//perInt里放着整数0到len-1的一个排列,求排列序号,且len<=21
//康托展开
unsigned int GetPermutationNumForInt(int*perInt,int len)
{
unsigned int num=0;
bool used[21]={0};
//或memset(used,0,sizeof(bool)*len);
for(int i=0;i<len;++i)
{
unsigned int n=0;
for(int j=0;j<perInt[i];++j)
{
if(!used[j])++n;//判断比当前元素数值小的中哪些位置在当前元素的后面
}
num+=n*factoria[len-i-1];
used[perInt[i]]=true;//将一个序列的元素从前往后逐一标注
}
return num;
}
//s1,s2是迭代器
//[s1,s1+len)中是第零号排列,[s2,s2+len)中是要求序号的排列
template<class T>
unsigned int GetPermutationNum(T s1,T s2,int len)
{
//转换成[0,len-1]的整数的排列
int *perInt=new int[len];
for(int i=0;i<len;++i)
for(int j=0;j<len;++j){
if(*(s2+i)==*(s1+j)){
perInt[i]=j;
break;
}
}
unsigned int num=GetPermutationNumForInt(perInt,len);
delete []perInt;
return num;
}
//根据编号生成排列
template<class T>
void GenPermutationByNum(T s1,T s2,int len,unsigned int No)
{
//先进行转换[0,len-1]
int perInt[21];
bool used[21]={0};
for(int i=0;i<len;++i){
int j;
for(j=0;j<len;++j){
if(!used[j]){
if(factoria[len-i-1]>=No+1)break;
else No-=factoria[len-i-1];
}
}
perInt[i]=j;
used[j]=true;
}
for(int i=0;i<len;++i)*(s2+i)=*(s1+perInt[i]);
}
int StrStatusToIntStatus(const char*strStatus)
{//字符串形式转换成序号
return GetPermutationNum("012345678",strStatus,9);
}
void IntStatusToStrStatus(int n,char*strStatus)
{//排列序号n转换成字符串形式
GenPermutationByNum((char*)"012345678",strStatus,9,n);
}
int NewStatus(int nStatus,char cMove)
{//从状态nStatus经过cMove(四种情况)移动。返回值为新状态;不可行则为-1
char szTmp[20];int nZeroPos;
IntStatusToStrStatus(nStatus,szTmp);//转化成字符串形式方便处理
for(int i=0;i<9;++i)
if(szTmp[i]=='0'){
nZeroPos=i;break;
}//找到0的位置
switch(cMove)
{
case'u':if(nZeroPos-3<0)return -1;
else{szTmp[nZeroPos]=szTmp[nZeroPos-3];
szTmp[nZeroPos-3]='0';}
break;
case'd':if(nZeroPos+3>8)return -1;
else{szTmp[nZeroPos]=szTmp[nZeroPos+3];
szTmp[nZeroPos+3]='0';}
break;
case'l':if(nZeroPos%3==0)return -1;
else{szTmp[nZeroPos]=szTmp[nZeroPos-1];
szTmp[nZeroPos-1]='0';}
break;
case'r':if(nZeroPos%3==2)return -1;
else{szTmp[nZeroPos]=szTmp[nZeroPos+1];
szTmp[nZeroPos+1]='0';}
break;
}
return StrStatusToIntStatus(szTmp);//最后转换成排列编号
}
//核心广搜函数:从初始状态nStatus开始寻找路径
bool Bfs(int nStatus)
{
int goalStatus=StrStatusToIntStatus("123456780");//将目标状态转为编号
int nNewStatus; Flags.reset();//bitset标记法中清除所有的扩展标记
qHead=0; qTail=1; //队列一头一尾,序号0、1
myQueue[qHead]=Node(nStatus,-1,0);//状态、父节点、及到本节点的移动方式
while(qHead!=qTail)//队列不为空
{
nStatus=myQueue[qHead].status;//状态队列,完成头节点赋值
if(nStatus==goalStatus)return true;//找放到目标状态
for(int i=0;i<4;i++)//尝试四种移动
{
nNewStatus=NewStatus(nStatus,sz4Moves[i]);
if(nNewStatus==-1)continue;
if(Flags[nNewStatus])continue;//bitset用法
Flags.set(nNewStatus,true);
//新节点插入队列(后缀++返回修改前)
myQueue[qTail++]=Node(nNewStatus,qHead,sz4Moves[i]);
}
qHead++;//每次检索出一个新节点,就指向该新节点,从而改变出发节点nStasus
}
return false;//问题无解
}
int main()
{
factoria[0]=factoria[1]=1;
for(int i=2;i<21;++i)factoria[i]=i*factoria[i-1];
char szLine[50]; char szLine2[20];
while(cin.getline(szLine,48))//将输入的原始字符串变为数组字符串
{
int i,j;
for(i=0,j=0;szLine[i];i++)
{
if(szLine[i]!=' ')
{
if(szLine[i]=='x')szLine2[j++]='0';
else szLine2[j++]=szLine[i];
}
}
//szLine2[j]=0;
int sumGoal=0;
for(int i=1;i<9;++i)sumGoal+=i-1;
int sumOri=0;
for(int i=0;i<9;++i)
{
if(szLine2[i]=='0')continue;
for(int j=0;j<i;++j)
{
if(szLine2[j]<szLine2[i]&&szLine2[j]!='0')
sumOri++;
}
}
if(sumOri%2!=sumGoal%2)
{
cout<<"unsolvable"<<endl;
continue;
}
if(Bfs(StrStatusToIntStatus(szLine2)))
{
int nMoves=0;
int nPos=qHead;
do
{
result[nMoves++]=myQueue[nPos].move;
nPos=myQueue[nPos].father;
}while(nPos);//nPos=0说明已经退回到初始状态
for(int i=nMoves-1;i>=0;i--)cout<<result[i];
}
else cout<<"unsolvable"<<endl;
}
}
广度优先搜索BFS+set二分查找
//广度优先搜索--标志位采用set二分查找
//内存:10752kB 时间:533ms
#include<iostream>
#include<cstring>
#include<cstdio>//sprintf()头文件
#include<cstdlib>//atoi()头文件
#include<set>//STL容器
using namespace std;
int goalStatus;
const int MAXS=400000;
char result[MAXS];
struct Node
{
int status;
int father;
char move;
Node(int s,int f,char m):status(s),father(f),move(m){}
Node(){}
};
Node myQueue[MAXS];
int qhead=0;
int qtail=1;
char moves[]="udrl";
void IntStatusToStrStatus(int n,char*strStatus)
{//字符串格式化命令,按十进制转换成9位的字符串,可在前面添加0凑足
sprintf(strStatus,"%09d",n);//需要保留前导0
}
int NewStatus(int status,char cMove)
{//求从状态status经过cMove移动后的新状态;若不可行则返回-1
char tmp[20];
int zeroPos;
IntStatusToStrStatus(status,tmp);//转换成字符串形式进行移动
for(int i=0;i<9;++i)
if(tmp[i]=='0'){zeroPos=i;break;}
switch(cMove)
{
case'u':
if(zeroPos-3<0)return -1;
else {tmp[zeroPos]=tmp[zeroPos-3];
tmp[zeroPos-3]='0';}
break;
case'd':
if(zeroPos+3>8)return -1;
else {tmp[zeroPos]=tmp[zeroPos+3];
tmp[zeroPos+3]='0';}
break;
case'l':
if(zeroPos%3==0)return -1;
else {tmp[zeroPos]=tmp[zeroPos-1];
tmp[zeroPos-1]='0';}
break;
case'r':
if(zeroPos%3==2)return -1;
else {tmp[zeroPos]=tmp[zeroPos+1];
tmp[zeroPos+1]='0';}
break;
}
return atoi(tmp);//再将字符串还原为整数状态返回
}
bool Bfs(int status)
{//从初始状态status到目标的路径,找不到则返回false
int newStatus;
set<int>expanded;//存放标记位
expanded.insert(status);
myQueue[qhead]=Node(status,-1,0);//初始化队列头节点(起始节点)
while(qhead!=qtail)//队列不为空
{
status=myQueue[qhead].status;
if(status==goalStatus)return true;
for(int i=0;i<4;i++)
{
newStatus=NewStatus(status,moves[i]);
if(newStatus==-1)continue;
if(expanded.find(newStatus)!=expanded.end())continue;//set中若没找到则返回end
expanded.insert(newStatus);
myQueue[qtail++]=Node(newStatus,qhead,moves[i]);
}
qhead++;
}
return false;
}
int main()
{
goalStatus=atoi("123456780");
char line1[50]; char line2[20];
while(cin.getline(line1,48))
{
int i,j;
//将原始输入转变成数字字符串
for(i=0,j=0;line1[i];i++)
{
if(line1[i]!=' '){
if(line1[i]=='x')line2[j++]='0';
else line2[j++]=line1[i];
}
}
line2[j]=0;
if(Bfs(atoi(line2)))
{
int moves=0;
int pos=qhead;
do{
result[moves++]=myQueue[pos].move;
pos=myQueue[pos].father;
}while(pos);//pos=0说明已经退回初始状态
for(int i=moves-1;i>=0;i--)cout<<result[i];
}
else cout<<"unsolvable"<<endl;
}
return 0;
}
如何加快速度:DBFS、A*、预处理彻底广搜
双向广搜DBFS+set二分查找
单向:(1-nm)/(1-n)
双向:2*(1-nm/2)/(1-n)
DBFS从两个方向开始扩展,搜索树的宽度明显减少
起始节点、目标节点(总是选择节点较少的一边进行扩展)
两个扩展出现交点–>找到路径
void dbfs()
{
1.将起始节点放入q0,目标节点放入队列q1;
2.当两个队列都未空时,循环:
扩展节点少的队列
3.将未空的队列扩展直到空
}
int expand(i)
{
取队列qi的头节点H;
对H的每一个相邻节点adj:
1若在q1中已经出现过,则抛弃
2若未在q1中出现过,则
1)将adj放入队列qi;
2)若adj曾在队列q1-i中出现过,则输出找到的路径
}
需要两个标志序列,分别记录节点是否出现在两个队列中
//双向广度优先搜索--set二分查找
//内存:1280kB 时间:25ms
#include <iostream>
#include <bitset>
#include <cstring>//reverse字符串顺序反转
#include <cstdio>//sprintf()
#include <cstdlib>//atoi()
#include <set>
#include <algorithm>
using namespace std;
int goalStatus;
const int MAXS=400000;
char result[MAXS];
struct Node
{
int status;
int father;
char move;
Node(int s,int f,char m):status(s),father(f),move(m){}
Node(){}
};
Node myQueue[2][MAXS];//两个方向的状态队列,状态总数为362880
int matchingStatus;//双向相遇的状态
int matchingQ;//找到相遇节点的队列编号
int qHead[2];
int qTail[2];
char moves[]="udrl";
void IntStatusToStrStatus(int n,char*strStatus)
{//字符串格式化命令,按十进制转换成9位的字符串,可在前面添加0凑足
sprintf(strStatus,"%09d",n);//需要保留前导0
}
int NewStatus(int status,char cMove)
{//求从状态status经过cMove移动后的新状态;若不可行则返回-1
char tmp[20];
int zeroPos;
IntStatusToStrStatus(status,tmp);//转换成字符串形式进行移动
for(int i=0;i<9;++i)
if(tmp[i]=='0'){zeroPos=i;break;}
switch(cMove)
{
case'u':
if(zeroPos-3<0)return -1;
else {tmp[zeroPos]=tmp[zeroPos-3];
tmp[zeroPos-3]='0';}
break;
case'd':
if(zeroPos+3>8)return -1;
else {tmp[zeroPos]=tmp[zeroPos+3];
tmp[zeroPos+3]='0';}
break;
case'l':
if(zeroPos%3==0)return -1;
else {tmp[zeroPos]=tmp[zeroPos-1];
tmp[zeroPos-1]='0';}
break;
case'r':
if(zeroPos%3==2)return -1;
else {tmp[zeroPos]=tmp[zeroPos+1];
tmp[zeroPos+1]='0';}
break;
}
return atoi(tmp);//再将字符串还原为整数状态返回
}
inline char ReverseMove(char c)
{
switch(c)
{
case'u':return 'd';
case'l':return 'r';
case'r':return 'l';
case'd':return 'u';
}
return 0;
}
bool DBfs(int status)
{
int newStatus;
set<int>expanded[2];
for(int i=0;i<2;++i){qHead[i]=0;qTail[i]=1;}
myQueue[0][0]=Node(status,-1,0);
expanded[0].insert(status);
myQueue[1][0]=Node(goalStatus,-1,0);
expanded[1].insert(goalStatus);
while(qHead[0]!=qTail[0]||qHead[1]!=qTail[1])
{
int qNo;//本次要扩展的队列
//判断该扩展哪一堆列
if(qHead[0]==qTail[0])qNo=1;
else if(qHead[1]==qTail[1])qNo=0;
else {//比较两个队列中的元素数目
if(qTail[0]-qHead[0]<qTail[1]-qHead[1])qNo=0;
else qNo=1;
}
int vqNo=1-qNo;//另一队列
status=myQueue[qNo][qHead[qNo]].status;
if(expanded[vqNo].find(status)!=expanded[vqNo].end())
{//status在另一队列中扩展过
matchingStatus=status;
matchingQ=qNo;
return true;
}
else
{
for(int i=0;i<4;++i)
{
newStatus=NewStatus(status,moves[i]);
if(newStatus==-1)continue;
if(expanded[qNo].find(newStatus)!=expanded[qNo].end())continue;
expanded[qNo].insert(newStatus);
myQueue[qNo][qTail[qNo]]=Node(newStatus,qHead[qNo],moves[i]);
qTail[qNo]++;
}
qHead[qNo]++;
}
}
return false;
}
int main()
{
goalStatus=atoi("123456780");
char line1[50]; char line2[20];
while(cin.getline(line1,48))
{
int i,j;
//将原始输入转变成数字字符串
for(i=0,j=0;line1[i];i++)
{
if(line1[i]!=' '){
if(line1[i]=='x')line2[j++]='0';
else line2[j++]=line1[i];
}
}
line2[j]=0;
//在此可用奇偶性进行剪枝(省略)
if(DBfs(atoi(line2)))
{
int moves=0;
int pos;
//找到交点正向的位置
if(matchingQ==0) pos=qHead[0];
else{
for(int i=0;i<qTail[0];++i)
if(myQueue[0][i].status==matchingStatus){pos=i;break;}
}
//从交点开始回溯走法(前半段)
do{
if(pos){
result[moves++]=myQueue[0][pos].move;
pos=myQueue[0][pos].father;
}
}while(pos);
reverse(result,result+moves);//将顺序正过来(反转)
if(matchingQ==0)
{
for(int i=0;i<qTail[1];++i)
if(myQueue[1][i].status==matchingStatus){pos=i;break;}
}
else pos=qHead[1];
do{
if(pos){
result[moves++]=ReverseMove(myQueue[1][pos].move);
pos=myQueue[1][pos].father;
}
}while(pos);
for(int i=0;i<moves;++i)cout<<result[i];//顺序输出
cout<<endl;
}
else
cout<<"unsolvable"<<endl;
}
return 0;
}
启发式算法+堆+普通标记数组
理论借鉴⭐️A*算法与启发函数(python代码简明易懂)
BFS–迪杰斯拉特Dijkstra算法–A*算法
A算法集合了Dijkstra算法最优解和贪心算法速度快的优点,代价函数:
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
其中g*(n)表示起点到当前点n实际走的代价,h(n)表示当前点n到终点的估算代价。 两个合起来就是其走当前点到终点的总代价函数f(n)
- 先考虑极端情况, 如果h(n)=0的情况下,只有g(n)起作用,那么A*算法就是Dijkstra算法。
- 如果h(n)始终小于等于实际n点到终点的距离,那么必然能够保证A*算法找的解就是最优解。而且h(n)越小,相应的g(n)就会变大,则A* 扩展的节点也就越多,A*算法运行的也就越慢。
- 另外一种极端情况,就是如果只有h(n)发挥作用,则A*算法就相当于贪心算法。在此思想上,若只要求可行解,并不要求最优,可以适当把 h ( n ) h(n) h(n)的权重加大,乘以一个常数,处理更少的状态,加快效率。
- 启发式搜索的效率很大程度上取决于估价函数,而八数码问题的估价可以用每个数回到正确位置的步数之和来进行估值
故可先研究Dijkstra算法
相应的引出最短路径问题的弗洛伊德Floyd算法
弗洛伊德Floyd算法:(计算任意两点之间的最短路程)
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
//e[i][j]表示从i号顶点到j号顶点之间的路程
//e[i][1]+e[1][j]表示从i号顶点先到1号顶点,再到j号顶点的路程和
//允许1作为中转,允许2作为中转……可推至允许各个点中转
//不断更新任意两点最短路程(类似动规碎片化?),复杂度O(n^3)
//无法解决“负权回路”(负权环)
Dijkstra算法:(计算一个点到图上其他点的最小距离)
void dijkstra(源点)
{
//从源点开始搜索,并标记
start=源点; visit[start]=1; dis[start]=0;
for(图中的点)dis[i]=min(dis[i],map[源点][i])//先更新一遍
for(图中的点)
找到dis[i]的最小点,即离源点最近的、未搜索过的点,并记录编号用于搜索
记录编号为j,令start=j更新搜索
visit[start]=1;
//以新的搜索点来更新dis
for(图中的点)dis[j]=min(dis[j],dis[start]+map[start][j])
}
参考文章:
相关知识:
相关知识:堆的用法(调整堆)
//启发式算法+堆
//内存:4480kB 时间:16ms
#include<iostream>
#include<cstdio>//scanf()
#include<cstdlib>//abs()
#include<cstring>//memset(a,0,sizeof(a))
using namespace std;
struct node
{
int id,f,g,h;
};
char c[10];//输入状态
int a[10],b[10],jie[10];//a[]是过程状态,b[]用来储存a(在尝试走法后要恢复原位)
int tot,start_id;
int v[400000];//标记数组
int pre[400000],w[400000];//分别记录上一操作节点编号和移动方法
int row[9]={3,1,1,1,2,2,2,3,3},
col[9]={3,1,2,3,1,2,3,1,2};
node heap[400000];
//奇偶性判断是否有解
bool have_solution()
{
int cnt=0;
for(int i=1;i<=9;i++)
for(int j=1;j<i;j++)
if(a[j]<a[i]&&a[j])cnt++;
if(cnt&1)return false;//位运算&相同位相加/2
return true;//原排列为偶排列
}
//康托展开
//将a[]状态展开成编号id=ans
inline int cantor()
{
int ans=0;
bool v[10]={0};//新定义的标记数组false
for(int i=1;i<=9;i++)
{
int _rank=0;
for(int j=0;j<a[i];j++)
if(!v[j])_rank++;
ans+=_rank*jie[9-i];
v[a[i]]=true;
}
return ans;
}
//逆康托展开
//将id转到a[]中进行移动
void uncantor(int d)
{
bool v[10]={0};
for(int i=1;i<=9;i++)
{
int t=d/jie[9-i];
d%=jie[9-i];
int _rank=0;
for(int j=0;j<=8;j++)
if(!v[j]){
if(_rank==t){
b[i]=j;
v[j]=true;
break;
}
_rank++;
}
}
}
void init()
{
memset(v,0,sizeof(v));
//补全字符数组c的2~9位
for(int i=2;i<=9;i++)scanf(" %c",&c[i]);
scanf("\n");
//处理数据
for(int i=1;i<=9;i++){
if(c[i]=='x')a[i]=0;
else a[i]=c[i]-'0';
b[i]=i;
}
b[9]=0;
//计算阶乘
jie[0]=1;
for(int i=1;i<=9;++i)jie[i]=jie[i-1]*i;
//建堆
tot=1;//初始化总状态数
//初始化起始状态
heap[1].id=cantor();
heap[1].f=0;
heap[1].g=0;
heap[1].h=0;
start_id=heap[1].id;//储存起始状态的标号,用于输出时的终止条件
v[heap[1].id]=true;//对状态进行标记
}
//向堆中插入新状态元素t
void insert_heap(node t)
{
tot++;
heap[tot]=t;
int i=tot;
while(i>1)//对堆进行调整(自下而上)
{
if(heap[i].f<heap[i/2].f)
{
swap(heap[i],heap[i/2]);
i=i/2;
}
else break;
}
}
//调整堆(自上而下)
void update_heap()
{
int i=1;
while(i<tot)
{
int cnt_i=i;
//在左右两个子结点中判断与根节点的大小并调整
if(i*2<=tot&&heap[i*2].f<heap[cnt_i].f)cnt_i=i*2;
if(i*2+1<=tot&&heap[i*2+1].f<heap[cnt_i].f)cnt_i=i*2+1;
if(i==cnt_i)break;
swap(heap[i],heap[cnt_i]);
i=cnt_i;
}
}
//输出答案
void output(int id)
{
if(id==start_id)return;//递归终止条件
output(pre[id]);
if(w[id]==1)cout<<"u";
if(w[id]==2)cout<<"d";
if(w[id]==3)cout<<"l";
if(w[id]==4)cout<<"r";
}
//操作移动
bool work(node cnt,int ww)
{
int id=cantor();
if(v[id])return id==46233;//若已经出现过,检查是否是目标状态
v[id]=true;
pre[id]=cnt.id;
w[id]=ww;
node t;
t.id=id;
t.g=cnt.g+1;
t.h=0;
for(int i=1;i<=3;i++)
if(a[i]!=0)t.h+=abs(row[a[i]]-1)+abs(col[a[i]]-((i-1)%3+1));//曼哈顿距离
for(int i=4;i<=6;i++)
if(a[i]!=0)t.h+=abs(row[a[i]]-2)+abs(col[a[i]]-((i-1)%3+1));
for(int i=7;i<=9;i++)
if(a[i]!=0)t.h+=abs(row[a[i]]-3)+abs(col[a[i]]-((i-1)%3+1));
t.f=t.g+t.h;
insert_heap(t);
return t.id==46233;
}
//启发式搜索
void bfs()
{
int sum=0;
bool _find=false;
while(tot!=0)
{
sum++;
node cnt=heap[1];
swap(heap[1],heap[tot]);
tot--;//heap[1]出来挨打
update_heap();
uncantor(cnt.id);
int cnt_zero;
for(int i=1;i<=9;i++)
if(b[i]==0){cnt_zero=i;break;}
//向上移动
if(cnt_zero>3)
{
for(int i=1;i<=9;i++)a[i]=b[i];
swap(a[cnt_zero],a[cnt_zero-3]);
if(work(cnt,1))_find=true;
}
//向下移动
if(cnt_zero<7)
{
for(int i=1;i<=9;i++)a[i]=b[i];
swap(a[cnt_zero],a[cnt_zero+3]);
if(work(cnt,2))_find=true;
}
//向左移动
if(cnt_zero%3!=1)
{
for(int i=1;i<=9;i++)a[i]=b[i];
swap(a[cnt_zero],a[cnt_zero-1]);
if(work(cnt,3))_find=true;
}
if(cnt_zero%3!=0)
{
for(int i=1;i<=9;i++)a[i]=b[i];
swap(a[cnt_zero],a[cnt_zero+1]);
if(work(cnt,4))_find=true;
}
//找到答案
if(_find)break;
}
output(46233);
cout<<endl;
}
int main()
{
//freopen("h.in","r",stdin);
//freopen("h.out","w",stdout);
while(scanf("%c",&c[1])!=EOF)
{
init();
if(have_solution())bfs();//先根据奇偶排列性质剪枝
else cout<<"unsolvable"<<endl;
}
return 0;
}
更多策略
-
判重时采用STL容器map<key,value>
可以将状态数字串作为key,是否出现作为value直接建立起状态–是否出现的联系
-
使用hash判重
(康拓展开可以看作是其思想特例:映射而不冲突)
······
参考文章:7种方法求解八数码问题