八數碼,在3×3的方格棋盤上,擺放着1到8這八個數碼,有1個方格是空的,其初始狀態如圖1所示,
要求對空格執行空格左移、空格右移、空格上移和空格下移這四個操作使得棋盤從初始狀態到目標狀態。
內容提要:
分別用廣度優先搜索策略、深度優先搜索策略和啟發式搜索算法(至少兩種)求解八數碼問題;
分析估價函數對啟發式搜索算法的影響;探究討論各個搜索算法的特點。
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
const int INF=0x3f3f3f3f;
struct Status{
int gx; //實際已走代價
int fx; //估值
string sta; //長度為9的字符串表示狀態
bool operator < (const Status& t) const{
return fx > t.fx;
}
}initial,target;
unordered_map<string,int>close; //記錄已遍歷的狀態
const int LAYER_MAX=20; //dfs最大深度
int MAX_COST=0; //遍歷到目標狀態的代價
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; //下一狀態可能的改變
//輸出顯示當前狀態
void show(const Status& t){
cout<<"當前狀態gx="<<t.gx<<" fx="<<t.fx<<endl;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cout<<t.sta[i*3+j];
}
cout<<endl;
}
}
//計算估值函數
int get_hx(const Status& t){
int hx=0;
for(int i=0;i<9;i++)
if(target.sta[i]!=t.sta[i]) //計算位置不同的個數
hx++;
return hx;
}
void init(){
// 初始狀態
cout<<"請輸入初始狀態:"<<endl;
initial.sta="";
initial.gx=0;
initial.fx=get_hx(initial)+initial.gx;
vector<int>used(10,0);
for(int i=0;i<9;i++){
int index=0;
while(1){
index=rand()%9;
if(!used[index])break;
}
if(index==0)initial.sta+=' ';
else initial.sta+=char('0'+index);
used[index]=1;
}
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cout<<initial.sta[i*3+j];
}
cout<<endl;
}
//cout<<"請輸入目標狀態:"<<endl;
target.sta="1238 4765";
target.gx=INF;
target.fx=INF;
}
int bfs(){
close.clear();
queue<string>open;
open.push(initial.sta);
close[initial.sta]=0;
while(!open.empty()){
string now=open.front();
open.pop();
//show(now);
if(now==target.sta){
return close[target.sta]; //到達目標狀態
}
int dis=close[now];
int index;
for(int i=0;i<9;i++){
if(now[i]==' '){
index=i;
break;
}
}
int row=index/3,colum=index%3; //一維坐標轉化成二維
for(int i=0;i<4;i++){
int trow=row+dir[i][0]; //假設空白處移動
int tcolum=colum+dir[i][1];
if(trow>=0&&trow<=2&&tcolum>=0&&tcolum<=2){ //判斷移動后的坐標是否合理
string next=now;
swap(next[trow*3+tcolum],next[index]); //真實移動空白處
if(!close.count(next)){ //判斷當前狀態是否遍歷過
close[next]=dis+1;
open.push(next);
MAX_COST++;
}
}
}
}
return -1;
}
void dfs(string now,int row,int colum,int layer_num){
if(!close.count(now)) close[now]=layer_num; //貪心獲取較小的可行解
else {
if(close[now]<layer_num) return ;
else close[now]=layer_num;
}
if(layer_num>=LAYER_MAX){ //超過搜索層數
return ;
}
if(now==target.sta){ //達到目標狀態
return ;
}
//往上下左右四個方向進行擴展
for(int i=0;i<4;i++){
int trow=row+dir[i][0];
int tcolum=colum+dir[i][1];
if(trow>=0&&trow<=2&&tcolum>=0&&tcolum<=2){ //選擇合理狀態
swap(now[trow*3+tcolum],now[row*3+colum]);
dfs(now,trow,tcolum,layer_num+1);
MAX_COST++;
swap(now[trow*3+tcolum],now[row*3+colum]);
}
}
}
void astar(){
close.clear();
close[target.sta]=INF;
priority_queue<Status>open;
open.push(initial);
while(!open.empty()){
Status now=open.top();
open.pop();
close[now.sta]=now.fx;
//show(now); //輸出當前狀態
if(!get_hx(now))break; //到達目標狀態
//找到空白處坐標
int row,colum;
for(int i=0;i<9;i++){
if(now.sta[i]==' '){
row=i/3; colum=i%3; //一維坐標轉化成二維
break;
}
}
for(int i=0;i<4;i++){
//假設空白處移動
int trow=row+dir[i][0];
int tcolum=colum+dir[i][1];
//判斷移動后的坐標是否合理
if(trow>=0&&trow<=2&&tcolum>=0&&tcolum<=2){
Status next=now;
next.gx++;
next.fx=next.gx+get_hx(next);
//真實移動空白處
swap(next.sta[trow*3+tcolum],next.sta[row*3+colum]);
//啟發判斷下一狀態
if(!close.count(next.sta)||close[next.sta]>=next.fx){
open.push(next);
MAX_COST++;
}
}
}
}
}
int main(){
srand(time(NULL)); //時間種子
init();
int x,y; //獲取初始狀態的空格位置
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(initial.sta[i*3+j]==' '){
x=i;
y=j;
break;
}
}
}
cout<<"廣度優先搜索bfs算法:"<<endl;
int ans=bfs();
cout<<"到目標狀態廣搜代價:"<<MAX_COST<<endl;
cout<<"到目標狀態最小代價:";
cout<<(ans==-1?"不可解":to_string(ans))<<endl;
//進到深度搜索
cout<<"深度優先搜索dfs算法:"<<endl;
close.clear(); //初始化close表
close[target.sta]=INF;
MAX_COST=0;
dfs(initial.sta,x,y,0);
cout<<"到目標狀態深搜代價:"<<MAX_COST<<endl;
cout<<"到目標狀態最小代價:";
cout<<(close[target.sta]==INF?"不可解":to_string(close[target.sta]))<<endl;
//進到啟發式搜索
cout<<"啟發搜索A*算法:"<<endl;
MAX_COST=0;
astar();
cout<<"到目標狀態廣搜代價:"<<MAX_COST<<endl;
cout<<"到目標狀態最小代價:";
cout<<(close[target.sta]==INF?"不可解":to_string(close[target.sta]))<<endl;
return 0;
}