八数码难题 (IDA*解法)

翻题解的时候看到大佬们都打的双向广搜。(蒟蒻在墙角瑟瑟发抖

在这里提供一种IDA*解法,
还是一层层地搜索,如果超出预期层数就返回
然后用评估函数进行剪枝(就是可行性剪枝)
感觉写起来比双向广搜好多了

评估函数越好,搜索效率越高;
如果评估函数太差,那IDA*和普通爆搜复杂度差不多。
所有一个好的评估函数很关键;
主要是看这道题评估函数(h)怎么写,

对于评估函数想到的有两个方案:
方案一:不在自己应在位置上数的个数;
方案二:所有数距自己应在位置的曼哈顿距离之和;

很显然选第二个方案更优,(虽然方案一也可以AC)
因为一个数移动到自己目标位置,至少要移动它现在位置到它目标位置的曼哈顿距离

例如下面这一个
在这里插入图片描述
它的距原状态的曼哈顿距离和就是4(1+1+2)
(不用计算0,因为其它数字归位后,0也会回到原位)

至于如何实现,可以先开一个数组记录任意两位置之间的曼哈顿距离
然后再开一个数组记录每个数字目标状态的位置

大概是这样的

int dis[9][9]={
	{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}
}; //两个位置间的曼哈顿距离
int yuan[9]={4,0,1,2,5,8,7,6,3};//每个数字的目标位置
int chu[3][3];//存储现在状态
int h()
{
    int ans=0;
    for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    {
    	if(chu[i][j]==0) continue;
        ans+=dis[yuan[chu[i][j]]][i*3+j];//i*3+j表示数字现在位置
    }
    return ans;
}

这样我们的评估函数就写好啦!

然后搜索。
具体细节在代码里说明。(码风有点丑,见谅QAQ)

#include<bits/stdc++.h>
using namespace std;
int dis[9][9]={
	{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}
}; //两个位置间的曼哈顿距离
int yuan[9]={4,0,1,2,5,8,7,6,3};//每个数字的目标位置
int chu[3][3];//存储现在状态
inline int h()
{
    int ans=0;
    for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    {
    	if(chu[i][j]==0) continue;//不考虑0
        ans+=dis[yuan[chu[i][j]]][i*3+j];//i*3+j表示数字现在位置
    }
    return ans;
}
int ax[4]={0,-1,1,0};
int ay[4]={1,0,0,-1};//这两个数组一定要对称写,使搜索时不会返回上一状态
bool flag;
void dfs(int ceng,int dep,int f,int x,int y)//f表示上一次转移的方向
{
    int nx,ny;
    if(ceng==dep) //如果搜到了预期层数
	{
    if(!h()) flag=1;//如果达到了目标状态
    return;
    }
    for(int i=0;i<4;i++)
    {
        nx=x+ax[i];
        ny=y+ay[i];
		
        if(nx<0||ny<0||nx>=3||ny>=3) continue;//出界
        if(i+f==3) continue;//如果会返回上一个状态
        swap(chu[nx][ny],chu[x][y]);
        if(ceng+h()<=dep)//如果最优情况都会超出预期,就直接返回
        dfs(ceng+1,dep,i,nx,ny);
        if(flag) return;//找到解就直接返回
        swap(chu[nx][ny],chu[x][y]);//回溯
    }
    return;
}
int main()
{
    char k;
    int sx,sy;
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            cin>>k;
            chu[i][j]=k-'0';
            if(k=='0') 
            {
                sx=i;
                sy=j;//初始0的位置
            }
        }
    }
	if(!h())//特判,如果一开始就达到目标,直接结束
    {
    	cout<<0;
    	return 0;
	}
    flag=0;
    int maxdep;//预期深度
    for(maxdep=1;;maxdep++)//一层一层地搜
    {
        dfs(0,maxdep,-1,sx,sy);//一开始没有方向,所以传入-1
        if(flag)//找到解
        {
            cout<<maxdep<<endl;
            break;
         } 
    }
    return 0;
    //愉快结束
}

好像跑的挺快的

双向广搜是不可能打的,这辈子都不可能打的