1. 程式人生 > 其它 >八數碼問題

八數碼問題

技術標籤:BFSACM演算法練習演算法佇列c++

八數碼問題思路


前言:備戰藍橋杯遇到一個八數碼問題,整理一下思路。第一次寫,不太好,見諒。

BFS是由近到遠的擴散過程,解決最短距離問題。搜尋的可以是數,也可以是狀態,八數碼就是狀態。從初始狀態出發,每次轉移都逐步逼近最終狀態,每轉移一次步數加一,達到目標時,經過的步數就是最短路徑。

舉個例子:

初始狀態

123
84
765

最終狀態

13
824
765

把空格看成0,計算最少的移動步數。

共9!種狀態。用佇列描述BFS,畫圖模擬一下,這個很簡單就不多寫了。會發現有重複的情況,那麼我們需要判斷重複。

八數碼問題最重要的就是判斷重複。可以用康託展開Cantor()

康託展開是幹什麼的?

就是計算這個數在全排列中是第幾大的數。(按從小到大排列)

知道是第幾個數後,用陣列記錄各個排列的位置,第一次訪問這個排列時,設visited[N] = 1,再次訪問時發現等於1,就說明重複了。


詳細說一下康託展開的原理:

eg.判斷2143是{1,2,3,4}的全排列中第幾個數?

就是計算2143前面的排列的數目。

1、首位小於2的所有排列。

只有1,後面的三個數的排列用3*2*1=3!種狀態,寫成1*3!=6

2、首位是2,第二位小於1的所有排列。

無,寫成0*2!

3、前兩位是21,第三位小於4的所有排列。

只有2134,寫成1*1!=1

4、前三位是214,第四位小於3的所有排列。

無,寫成0*0!

求和,等於7,那麼2143是其全排列中的第8大的數。

公式X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!知道原理了公式就好理解了。

如果用int visited[24]陣列記錄個排列的位置,那麼{2143}就是visited[7],設visited[7] = 1,判斷重複。


上述過程的反過程是康託逆展開:某個集合的全排列,輸入一個數字k,返回第k大的排列。

大體就是這麼一個思路,下面是程式碼,用BFS+Cantor解決。

/*康託展開*/
#include<bits/stdc++.h>
using namespace std;
const int LEN = 362880;//狀態共9!=362880種 
struct node{
	int state[9];//記錄一個八數碼的排列
	int dis;//記錄到起點的距離 
};

int dir[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};//左,上,右,下順時針方向,左上角的座標是(0,0) 

int visited[LEN]={0};//與每個狀態對應的記錄,cantor()對它置數,並判斷重複

int start[9];//開始狀態

int goal[9];//目標狀態

long int factory[] = {1,1,2,6,34,120,720,5040,40320,362880};//cantor常用到的數

bool Cantor(int str[],int n){//判斷重複
	 long result = 0;
	 for(int i = 0;i<n;i++){
	 	int counted = 0;
	 	for(int j = i+1;j < n;j++){
	 		if(str[i]>str[i])//當前未出現的元素排第幾個
			 ++counted; 
		 }
		 result =+ counted*factory[n - i - 1];
	 }
	 if(!visited[result]){//沒有被訪問過的 
	 	visited[result] = 1;
	 	return 1;
	 }
	else
		return 0;
} 
int bfs(){
	node head;
	memcpy(head.state,start,sizeof(head.state));//複製起點的狀態
	head.dis = 0;
	queue<node>q;//佇列中的內容是記錄狀態
	Cantor(head.state,9);//對visited賦初始值
	q.push(head);//第一個進佇列的是起點狀態
	
	while(!q.empty()){//處理佇列
		head = q.front();
		if(memcmp(head.state,goal,sizeof(goal)) == 0)//與目標狀態對比
		return head.dis;//達到目標狀態,返回距離,結束
		q.pop();//可在此處列印head.state,看彈出佇列的情況
		int z;
		for(z = 0;z < 9;z++)//找這個狀態中元素0的位置
			  if(head.state[z] == 0)//找到了
			  				   break;
			//轉化為二維,左上角是原點(0,0)
			int x = z%3;//橫座標
			int y = z/3;//縱座標
			for(int i = 0;i < 4;i++){//最多可能有4個新狀態
					int newx = x+dir[i][0];//元素0轉移後的新座標
					int newy = y+dir[i][1];
					int nz = newx + 3*newy;//轉化為一維
					if(newx >= 0&&newx < 3&&newy >= 0&&newy < 3){//未越界 
						node newnode;
						memcpy(&newnode,&head,sizeof(struct node));//複製新的狀態
						swap(newnode.state[z],newnode.state[nz]);//把0移動到新的位置
						newnode.dis++;
						if(Cantor(newnode.state,9))//判斷重複
								q.push(newnode);//把新的狀態放入佇列
								 
					} 
			} 
	} 
	return -1;//沒找到
}

 
 

int main()
{
 	for(int i = 0;i < 9;i++) cin>>start[i];//初始狀態
	for(int i = 0;i < 9;i++) cin>>goal[i];//目標狀態
	int num = bfs();
	if(num != -1) cout<<num<<endl;
	else
		cout<<"Impossible"<<endl;
		return 0; 
 }