POJ1077八數碼問題~~bfs

八數碼問題 最簡單的解法:

康託展開+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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章