八数码问题 最简单的解法:
康托展开+bfs
(只会这一种)
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
首先这个题目告诉了我们 9位的数字,这就是状态,我们需要把这个状态存起来,那么怎么应该存呢?
我们使用一个9位的数组,来表示当前的实际状态(注意一维和二维的转换)
那么虚拟状态呢?就是判重的根据,因为bfs本身就很暴力,而且这道题的数据量又是贼**大,你让他炒鸡暴力的话肯定会被T掉,那么怎么设置vis[ ]数组用来标记呢?
开一个大小为987654321的数组吗?显然不可能,内存早就爆掉了
很明显 状态中不可能出现 两个一样的数字,也就是说,所有的状态一定是0-8这九个数字的全排列,其实就只有9!=362880种状态,数据量一下就减小了接近3000倍吧,内存应该是可以存的下的;接下来就是这道题的网红了====康托展开式,
那么什么是康托展开?↓↓↓
https://baike.baidu.com/item/%E5%BA%B7%E6%89%98%E5%B1%95%E5%BC%80/7968428?fr=aladdin
//康拓展开式
const int jie[]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘
int cantor(char a[]){
int n=9;
int res=0;
for(int i=0;i<n-1;i++){
int sum=0;
for(int j=i+1;j<n;j++)
if(a[j]<a[i])
sum++;//找到后面比当前数小的数
res+=sum*jie[a[i]-1];
}
return res;
}
返回的是某一种组合在这些数字的全排列 从小到大 顺序中的位置,就是前面有几个比他小的,我们就使用它的位置来储存这个状态,谁能想的到呢?还是听老师讲的
后面就是bfs的过程了,我自己也是个需要勤加练习的娃啊
再一个就是比较一些数组函数的运用 memcpy和memcmp,以前确实没见过,跟着这道题,长了见识了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
const int mm=362888;//9的阶乘种状态
int vis[mm];
struct node{
int ss[9];
int step;
};
int sta[9],goal[9];//起始和目标状态
int to[4][2]={0,1,1,0,0,-1,-1,0};
//康拓展开式
const int jie[]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘
int cantor(int a[],int n){
int res=0;
for(int i=0;i<n-1;i++){
int sum=0;
for(int j=i+1;j<n;j++)
if(a[j]<a[i])
sum++;//找到后面比当前数小的数
res+=sum*jie[n-i-1];
}
if(vis[res]==0){
vis[res]=1;
return 1;
}
return 0;
}
int bfs(){
node fa;
memcpy(fa.ss,sta,sizeof(fa.ss));//数组复制函数,复制起点状态
fa.step=0;
queue<node>q;
cantor(fa.ss,9);//给vis赋初值
q.push(fa);
while(!q.empty()){
fa=q.front();
q.pop();
if(memcmp(fa.ss,goal,sizeof(goal))==0)
return fa.step;
int loc;//找到 0的位置
for(loc=0;loc<9;loc++)
if(fa.ss[loc]==0)
break;
int x=loc%3;//转换成二维
int y=loc/3;
for(int i=0;i<4;i++){//枚举方向
int nextx=x+to[i][0];
int nexty=y+to[i][1];
if(nextx>=0&&nextx<3&&nexty>=0&&nexty<3){
int nextloc=nextx+nexty*3;//换成一维
//必须生成副本 不然会影响下一次循环
node nextnode=fa;//原状态转移
swap(nextnode.ss[nextloc],nextnode.ss[loc]);//
nextnode.step++;
if(memcmp(nextnode.ss,goal,sizeof(goal))==0)
return nextnode.step;
if(cantor(nextnode.ss,9))
q.push(nextnode);
}
}
}
return -1;
}
int main()
{
for(int i=0;i<9;i++)
cin>>sta[i];
for(int i=0;i<9;i++)
cin>>goal[i];
int res=bfs();
if(res==-1)printf("no!\n");
else cout<<res<<endl;
return 0;
}
POJ-1077
同样是一个八数码问题,题目稍作修改,增加了一个路径输出的过程
这是个很奇妙的问题,模拟一个栈来储存路径,这道题因为输入错误搞了一晚上,,,脑子瓦特了
HDU-1034是个同名同义的题目,只不过数据更加严格,导致下面的做法弄不出来,还有更深奥的做法等着探索啊
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
const int mm=362888;//9的阶乘种状态
char step[mm];
int pre[mm];
int vis[mm];
struct node{
int ss[9];//9位表示 真实状态
// int step;
};
int to[4][2]={0,1,1,0,0,-1,-1,0};//四个方向
//康拓展开式
const int jie[]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘
int cantor(int a[],int n){
int res=0;
for(int i=0;i<n-1;i++){
int sum=0;
for(int j=i+1;j<n;j++)
if(a[j]<a[i])
sum++;//找到后面比当前数小的数
res+=sum*jie[n-i-1];
}
return res;
}
void bfs(node fa){
mem(vis,0);
queue<node>q;
int u=cantor(fa.ss,9);//给vis赋初值
vis[u]=1;
pre[u]=-1;//用来回溯起点
q.push(fa);
while(!q.empty()){
fa=q.front();
int u=cantor(fa.ss,9);
q.pop();
int loc;//找到 0的位置
for(loc=0;loc<9;loc++)
if(fa.ss[loc]==9)
break;
int x=loc/3;//转换成二维
int y=loc%3;
for(int i=0;i<4;i++){//枚举方向
int nextx=x+to[i][0];
int nexty=y+to[i][1];
if(nextx>=0&&nextx<3&&nexty>=0&&nexty<3){
int nextloc=nextx*3+nexty;//换成一维
//必须生成副本 不然会影响下一次循环
node nextnode=fa;//原状态转移
swap(nextnode.ss[nextloc],nextnode.ss[loc]);
int v=cantor(nextnode.ss,9);
if(!vis[v]){
step[v]=i;//存方向
vis[v]=1;
pre[v]=u;//前一个位置
if(v==0)
return;
q.push(nextnode);
}
}
}
}
}
//路径回溯 难!
void show(){
int n,u;
char path[1000];
n=1;
path[0]=step[0];
u=pre[0];
while(pre[u]!=-1){//往前找节点
path[n]=step[u];//模拟栈存路径方向
n++;
u=pre[u];
}
for(int i=n-1;i>=0;i--)//和前面匹配起来,表示四个方向
if(path[i]==0)
cout<<'r';
else if(path[i]==1)
cout<<'d';
else if(path[i]==2)
cout<<'l';
else cout<<'u';
}
int main()
{
node sta;
//不要输入字符串 不然都不知道哪里错的。。。。
char ch;
for(int i=0;i<9;i++){
cin>>ch;
if(ch=='x')
sta.ss[i]=9;
else sta.ss[i]=ch-'0';//
}
bfs(sta);
if(vis[0])
show();
else cout<<"unsolvable";
cout<<endl;
return 0;
}