历届试题 九宫重排 bfs八数码问题+康托展开和逆康托展开

问题描述
  如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
  在这里插入图片描述在这里插入图片描述
  我们把第一个图的局面记为:12345678.
  把第二个图的局面记为:123.46758
  显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
  本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式
  输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
  输出最少的步数,如果不存在方案,则输出-1。
样例输入
12345678.
123.46758
样例输出
3
样例输入
13524678.
46758123.
样例输出
22
分析题目:一开始我以为是要建一个二维图,但是建了一个图之后发现不太能够标记走过的路径,其实不需要的,这道题目可以这样去想,用一个一维数组去存放,然后每一种情况就用对应的十进制数来表示,存放在set里面用于判断是否存在这样的情况就好了,这样对应的二位数组的下标就用一位数组的下标来表示了,举个栗子:一位数组为6时,对应是二维数组的下标就是map[6/3][6%3]了,然后依据这个方式去用队列广搜就可以了

#include<iostream>
#include<cstdio>
#include<cmath>
#include<set>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int N=1e5+10;
typedef long long ll;
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int INF=0x3f3f3f3f;
int n,m,ans;
char str[20];
//typedef int E[10];
struct node{
    int step;
    int e[9];
};
queue<node>q;
set<int>se;
node disn;
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
bool check(node p){
    int sum=0;
    rep(i,0,8) sum=sum*10+p.e[i];
    if(se.find(sum)!=se.end())return false;
    se.insert(sum);
    return true;
}
void dfs(node p0){
    p0.step=0;
    q.push(p0);
    while(q.size()){
        node p=q.front();
        q.pop();
        if(memcmp(p.e,disn.e,sizeof disn.e)==0){
            ans=p.step;
            return ;
        }
        int k=0;
        for(k=0;k<9;k++)
            if(p.e[k]==0) break;
        int x=k/3;
        int y=k%3;
        for(int i=0;i<4;i++){
            int xx=x+dx[i];
            int yy=y+dy[i];
            if(xx<0||yy<0||xx>2||yy>2) continue;
            int pp=xx*3+yy;
            node res;
            memcpy(res.e,p.e,sizeof p.e);
            swap(res.e[pp],res.e[k]);
            if(check(res)){
                res.step=p.step+1;
                q.push(res);
            }
        }
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    scanf("%s",str);
    node p;
    rep(i,0,8){
    if(str[i]=='.') p.e[i]=0;
    else p.e[i]=str[i]-'0';
    }
    scanf("%s",str);
    rep(i,0,8){
    if(str[i]=='.') disn.e[i]=0;
    else disn.e[i]=str[i]-'0';
    }
    ans=-1;
    dfs(p);
    printf("%d\n",ans);
    return 0;
}



然后这一道题目还可以用[康托展开]来去处理然后用哈希去判断,其实这样去做和上面的方法差不多(https://blog.csdn.net/wbin233/article/details/72998375)

#include<iostream>    
#include<string.h>  
#include<cstdio>
#define M 1000000  
using namespace std; 
typedef int type[9];
type qs,mb,gc[M];
int sept[M]; //保存步数 
int dir[4][2] = {-1,0,0,-1,0,1,1,0},st=0; 
int step[M]={0};
int fact[10]={1},vis[M];
void init() //初始化 阶乘 
{
	int i;
	for (i=1; i<=9; i++)
	{
		fact[i] = i*fact[i-1];
	}
}
int kangtuo(int *a)
{
	int num=0,t,i,j;
	for (i=0; i<8; i++)
	{
		t = 0;
		for (j=i+1; j<9; j++)
		{
			if (a[i] > a[j])
			t++;
		}
		num += t * fact[8-i];//num为展开的第几个排列 
	}
	if (vis[num] == 1)
	 return 0;
	vis[num] = 1; 
	return 1; 
}
int bfs()
{
	int front=0,rear=1,i,j,k,c,x,y,xx,yy;
	memcpy(gc[st],qs,sizeof(qs)); //把起始状态放入 
	while (front < rear)
	{
		type temp;
		memcpy(temp,gc[front],sizeof(gc[front])); //获取对头 
		if (memcmp(temp,mb,sizeof(mb))==0)//到达终点
		{
			return front;
		}
		for(k=0; k<9; k++)
		{
			if (temp[k]==0)
				break;
		}
		x = k/3;
		y = k%3; //转2维下标
		for(i=0; i<4; i++)
		{
			xx = dir[i][0] + x;
		    yy = dir[i][1] + y;
		    if (xx>=0&&yy>=0&&xx<3&&yy<3)
		    {
		    	c = 3*xx + yy;
		    	type temp2;
		    	memcpy(temp2,temp,sizeof(temp));
		    	temp2[k] = temp2[c];
				temp2[c] = 0; //进行交换
				if (kangtuo(temp2))//康托展开 ,判重 
				{
					memcpy(gc[rear],temp2,sizeof(temp2));
					step[rear++] = step[front] + 1; 
				}
			}
		 }
		 front++;//进入下一个队列元素
	} 
	return -1;
}
int main()
{
	char ch;
	int i,r;
	init();
	for (i=0; i<9; i++)
	{
		cin>>ch;
		ch == '.' ? qs[i]=0 : qs[i]=ch-'0';
	}
	getchar();
	for (i=0 ;i<9; i++)
	{
		cin>>ch;
		ch == '.' ? mb[i]=0 : mb[i]=ch-'0';
	}
	r = bfs();
	r == -1 ? cout<<r<<endl : cout<<step[r]<<endl;
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值