八數碼問題
阿新 • • 發佈:2021-01-28
八數碼問題思路
前言:備戰藍橋杯遇到一個八數碼問題,整理一下思路。第一次寫,不太好,見諒。
BFS是由近到遠的擴散過程,解決最短距離問題。搜尋的可以是數,也可以是狀態,八數碼就是狀態。從初始狀態出發,每次轉移都逐步逼近最終狀態,每轉移一次步數加一,達到目標時,經過的步數就是最短路徑。
舉個例子:
初始狀態
1 | 2 | 3 |
8 | 4 | |
7 | 6 | 5 |
最終狀態
1 | 3 | |
8 | 2 | 4 |
7 | 6 | 5 |
把空格看成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大的數。
公式知道原理了公式就好理解了。
如果用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; }