p1379 八數碼難題 洛谷
八數碼難題
題目描述
在3×3的棋盤上,擺有八個棋子,每個棋子上標有1至8的某一數字。棋盤中留有一個空格,空格用0來表示。空格周圍的棋子可以移到空格中。要求解的問題是:給出一種初始佈局(初始狀態)和目標佈局(為了使題目簡單,設目標狀態為123804765),找到一種最少步驟的移動方法,實現從初始佈局到目標佈局的轉變。
輸入格式
輸入初始狀態,一行九個數字,空格用0表示輸出格式
只有一行,該行只有一個數字,表示從初始狀態到目標狀態需要的最少移動次數(測試資料中無特殊無法到達目標狀態資料)輸入輸出樣例
輸入 #1283104765輸出 #1
4
-------------------------------快樂的分割線----------------------------------
題目分析:
首先由題意可以知道我們的目的是尋找最少移動次數,注意/*最少*/,這表示我們可以藉助A*或者IDA*來解決這道題(其實這道題和騎士那個挺像的,都是以空格為初始節點求到達目標節點的值,這個題做完之後有興趣的同學可以看一下洛谷p2324)
回到這個題,我們從這個題中可以找出應用A*和IDA*的一般規律:
1.題目中給出限制條件:類似本題,題目中已經明確給出最終狀態
2.題目中要求最優解:本題就是尋找最少步驟的移動方法
這樣,當我們再次遇到類似題目時,優先考慮A*和IDA*演算法
題目詳解:
通過上面的分析,我們已經知道了基本演算法,那如何進行構造呢?
首先,我們發現題目中給出的是一維字串,那麼我們就應當把它構造成二維,讓它符合IDA*演算法的二維背景,所以:
cin>>s; for(int i=0;i<9;i++) { a[i/3+1][i%3+1]=s[i]-'0'; if(s[i]-'0'==0) x=i/3+1,y=i%3+1; }
這樣一來,我們就把一維陣列建成了二維結構
其次,我們要生成一個目標陣列goal來進行如下操作:
1.判斷當前陣列是否到達最終目標
bool check() { for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) { if(goal[i][j]!=a[i][j]) return0;//表示不到達 } return 1;//表示到達 }
2.對最優解進行估計(構造估計函式)
bool test(int step) { int cnt=0; for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) if(a[i][j]!=goal[i][j]) { if(++cnt+step>k) return 0;//相當於一個估計函式 } return 1; }
對於估計函式,這裡詳細解釋一下。看上面程式碼,我們知道如果當前元素值和目標函式不相同,那麼最好的情況就是,對於每個元素,我們只交換一次就可以到達最終目標,但是這種情況只可能出現在最理想狀態中,實際問題裡面幾乎不會出現這種情況,所以我們稱這種情況為“最優情況”,對於IDA*演算法而言,我們規定了最大搜索範圍,如果我們對於當前情況進行預先估計,發現及時是在最理想的狀態下,最後結果也大於當前搜尋的最大層數,那麼我麼就可以直接放棄這種情況,減小時間複雜度
(不理解可以先去看一下IDA*的演算法基礎)
這樣,我們就完成了對於前置條件的設定,接下來就是IDA*演算法的實現了
void a_(int step,int x,int y,int fa) { if(step==k) { if(check()) flat=1;return ; } if(flat) return ; for(int i=0;i<4;i++) { int nx=x+dx[i],ny=y+dy[i]; if(nx<1||nx>3||ny<1||ny>3||fa+i==3) continue; swap(a[x][y],a[nx][ny]); if(test(step)&&!flat) a_(step+1,nx,ny,i); swap(a[x][y],a[nx][ny]); } }
通過這些函式的執行,我們最終就可以求出最小步驟
----------------------------------快樂的分割線----------------------------------------
完整程式碼
#include<bits/stdc++.h> using namespace std; string s; int goal[4][4]={ {0,0,0,0}, {0,1,2,3}, {0,8,0,4}, {0,7,6,5} }; int a[4][4]; int x,y; bool flat; int k; int dx[4]={0,1,-1,0}; int dy[4]={1,0,0,-1}; bool check() { for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) { if(goal[i][j]!=a[i][j]) return 0; } return 1; } bool test(int step) { int cnt=0; for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) if(a[i][j]!=goal[i][j]) { if(++cnt+step>k) return 0;//相當於一個估計函式 } return 1; } void a_(int step,int x,int y,int fa) { if(step==k) { if(check()) flat=1;return ; } if(flat) return ; for(int i=0;i<4;i++) { int nx=x+dx[i],ny=y+dy[i]; if(nx<1||nx>3||ny<1||ny>3||fa+i==3) continue; swap(a[x][y],a[nx][ny]); if(test(step)&&!flat) a_(step+1,nx,ny,i); swap(a[x][y],a[nx][ny]); } } int main() { cin>>s; for(int i=0;i<9;i++) { a[i/3+1][i%3+1]=s[i]-'0'; if(s[i]-'0'==0) x=i/3+1,y=i%3+1; } if(check()) { cout<<0<<endl; return 0; } while(++k) { a_(0,x,y,-1); if(flat) { cout<<k<<endl; break; } } return 0; }
----------------END-------------------