八數碼問題 最簡單的解法:
康託展開+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;
}