基础算法·深搜·题解【八数码】

题目

题目描述

现将1~8这八个自然数填入方格中,给定一个初始状态,例如为:281463750,其中空方格用数字0表示。现允许移动空格,但每次只能移动1格。试编一程序完成对于任意给定的一个目标状态,如:123804765,能够以最少步数实现从初始状态到目标状态的转换。方向优先:(左,上,下,右)

输入

两行,每行9个数,保证里面有0 第一行表示起始状态 第二行表示目标状态

输出

若干个组 3行为一组,每行3个数,表示每一步八数码移动的状态 每个数字占3个场宽
如果无解,则输出:NO solution!

样例

  • 输入样例
    2 8 3 1 0 4 7 6 5
    1 2 3 8 0 4 7 6 5

  • 输出样例
    2 8 3
    1 0 4
    7 6 5

    2 0 3
    1 8 4
    7 6 5

    0 2 3
    1 8 4
    7 6 5

    1 2 3
    0 8 4
    7 6 5

    1 2 3
    8 0 4
    7 6 5
    //域宽为3,故仅供参考

题意

给出一个0~8的九宫格(以一维方式),并给出一个目标九宫格。其中只能通过向四个方向移动0点,把原九宫格变为目标九宫格。要求输出移动0点的步骤。

题解

先讲一下主要思路。题目要求0点移动步数最少。观察发现,每次移动只能把一个数字与0交换,这样最多把一个数字向它对应的目标位置靠近1步。加入每一步移动都是有效的,这样每个状态到目标状态的移动步数,就不可能小于所有数当前位置与目标位置的曼哈顿距离之和。于是我们可以得到一个这样的估价函数: F ( i ) = s u m ( ∣ x i − x g o a l ∣ + ∣ y i − y g o a l ∣ ) F(i)=sum(|x_i-x_{goal}|+|y_i-y_{goal}|) F(i)=sum(xixgoal+yiygoal),其中 x i x_i xi y i y_i yi表示每个数字现在的位置, x m x_m xm y m y_m ym表示每个数字的目标位置。

下面讲讲细节。首先关于存储:由于我们的当前值、目标值都是一维的,也就是 s [ 0 ] s[0] s[0] s [ 8 ] s[8] s[8] g o a l [ 0 ] goal[0] goal[0] g o a l [ 8 ] goal[8] goal[8] [ 0 ] [ 0 ] [0][0] [0][0] [ 2 ] [ 2 ] [2][2] [2][2]有兴趣可以去试一下),所以在走迷宫需要调用行数和列数时,需要取 x / 3 x/3 x/3 y % 3 y\%3 y%3
然后解释一下结构体: f a , p o s , d e p , e v e fa,pos,dep,eve fa,pos,dep,eve分别维护了当前状态的父亲、0点的位置、搜索的深度和当前估值, s [ 10 ] s[10] s[10]便是当前状态。
最后便是大家最关心的康托展开。这种操作博主也是第一次见,所以也没有深究大写的菜 。总之就是用一个无重复键值的哈希,把当前状态存下来,以避免重复。如果觉得难以理解的话,可以暂时想成10进制——比如说此时状态为:1 2 3 4 5 6 7 8 0,则此时的哈希值为123456780,所以如果遍历到这种情况则 v i s [ 123456780 ] = 1 vis[123456780]=1 vis[123456780]=1——大概就是这样啦,虽然不太可能,所以用康托展开,把每个状态映射到 c o d e code code,然后判逆序对和重复。

代码

#include<iostream>
#include<cstdio>
#include<cctype>
#include<queue>
#include<iomanip>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=2e5+10;
int last;
int dx[5]={0,0,-1,0,1};
int dy[5]={0,-1,0,1,0};//下左上右 
int goal[10];
bool vis[maxn<<1];
long long power[10];
struct node
{
	int fa,pos,dep,eva;
	int s[10];
	bool operator <(const node &t)const
	{
		return eva>t.eva;
	}
};
node list[maxn];
priority_queue<node> q;
inline bool cmp(int *a,int *b)
{
	for(int i=0;i<9;++i)
		if(a[i]!=b[i]) return 1;
	return 0;
}//比较当前状态与目标状态是否一致 
inline int contor(int *x)
{
	int code=0;
	for(int i=0;i<9;++i)
	{
		int cnt=0;
		for(int j=i+1;j<9;++j) if(x[j]<x[i]) cnt++;
		code+=1ll*cnt*power[8-i];
	}
	if(vis[code]) return 1;
	vis[code]=1;
	return 0;
}//康托展开 
inline void print(int *x)
{
	for(int i=0;i<9;++i)
	{
		cout<<setw(3)<<x[i];//输出域宽为3 
		if((i+1)%3==0) cout<<"\n";
	}
	cout<<"\n";
}
inline void write(int k)
{
	if(!k) return;
	write(list[k].fa);
	print(list[k].s);
}
inline int evaluation(int *x)
{
	int ret=0;
	int c[10],t[10];
	for(int i=0;i<9;++i)
		c[x[i]]=i,t[goal[i]]=i;
	for(int i=1;i<9;++i)
		ret+=abs(c[i]/3-t[i]/3)+abs(c[i]%3-t[i]%3);
	return ret;
}//计算估价函数 
inline void expand(node &st)
{
	int x=st.pos/3;
	int y=st.pos%3;
	for(int i=1;i<=4;++i)
	{
		int newx=x+dx[i];
		int newy=y+dy[i];
		int newp=newx*3+newy;
		if(newx<3&&newx>=0&&newy<3&&newy>=0)
		{
			node now=st;
			now.s[newp]=st.s[st.pos];
			now.s[st.pos]=st.s[newp];
			if(contor(now.s)) continue;
			now.fa=last;
			now.pos=newp;
			now.dep++;
			now.eva=now.dep+evaluation(now.s);
			q.push(now);
		}
	}
}//深搜展开 
int bfs()
{
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		list[++last]=tmp;
		if(!cmp(goal,tmp.s)) return 1;
		expand(tmp);
		if(last>10000) break;
	}
	return 0;	
}
int main()
{
	power[0]=1;
	for(int i=1;i<9;++i) power[i]=power[i-1]*1ll*i;
	node st;
	for(int i=0;i<9;++i)
	{
		cin>>st.s[i];
		if(!st.s[i]) st.pos=i;
	}
	for(int i=0;i<9;++i) cin>>goal[i];
	st.fa=0;
	st.eva=evaluation(st.s);
	st.dep=0;
	contor(st.s);
	q.push(st);
	if(bfs()) write(last);
	else cout<<"NO solution!";
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值