A*演算法解決八數碼問題(C++版本)
阿新 • • 發佈:2019-01-25
八數碼問題定義:
八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。
A*演算法的通用虛擬碼 :
A*演算法解決八數碼問題的關鍵之處:
關鍵之處:
要維護兩個結構:
- open表,存放將要拓展的節點。
- close表,存放已經拓展過的節點。
每次從open表中選擇F值(f = g + h)最小的點進行拓展,對於拓展出的新節點要,如果已經訪問過且此時F值比以前訪問時更優時,要更新close表,並將此節點重新插入到open表中。
實現原始碼:
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <list>
//#include "process.cpp";
#define SIZE 3//棋盤的大小 size*size
using namespace std;
/**
*定義方案節點
*/
typedef struct Node
{
vector<int> board;
int rc;
int h;
int g;
int parent;//在close中指示父親節點的下表
}pNode;
int x_axis[] = {-1, 0, 0, 1};
int y_axis[] = { 0, -1, 1, 0};
/**
*輸出方案
*/
void print(vector<int> board, int rc)
{
cout<<"當前方案為:"<<endl;
for(int i = 0; i < rc; ++i)
{
for(int j = 0; j < rc; ++j)
{
cout<<board[i*rc+j]<<' ' ;
}
cout<<endl;
}
//cout<<"當前方案的F值為:"<<endl;
//cout<<board[board.size()-1]<<endl;
return ;
}
/**
*將遍歷到的當前節點輸出到文字檔案中
*/
void out_data(pNode &node)
{
int s = node.rc;
ofstream outdata("rt.txt",ios::app);
outdata<<"第"<<node.parent<<"層資料"<<endl;
for(int i = 0; i < s; i++) {
for(int j = 0; j < s; j++) {
outdata<<node.board[i*s+j]<<" ";
}
outdata<<endl;
}
outdata<<"h值:"<<node.h<<endl;
outdata.flush();
outdata.close();
return ;
}
/*
*判斷棋盤是否有序
*
*@param vector<int> board
*@return bool是否有序
*/
bool is_ordered(vector<int> board)
{
for(int i = 0; i < board.size(); ++i)
{
if(board[i] != i)
{
return false;
}
}
return true;
}
/**
*定義heuristic函式 探索函式
*
*@param vector<int> board , int rc 表示解決方案是rc*rc的 r&c
*@return int value of heuristic
*/
int heuristics(vector<int> board,int rc)
{
//表示元素的正確位置
int gx = 0;
int gy = 0;
//表示Manhattan block distance
int distance = 0;
for(int i = 0; i < rc*rc; ++i)
{
int nx = i / rc;
int ny = i % rc;
gx = board[i] / rc;
gy = board[i] % rc;
distance += abs(nx - gx) + abs(ny-gy);
}
return distance;
}
/**
*same_plan()判斷兩個vector<int> 是否相等
*即判斷兩個方案是否相等
*
*@param vector<int> board1, vector<int> board2
*@return bool
*/
bool same_plan(vector<int> board1, vector<int> board2)
{
//首先判斷陣列的長度是否相等
if(board1.size() != board2.size())
{
return false;
}
//判斷每一個元素是否對應相等
for(int i = 0; i < board1.size(); ++i)
{
if(board1[i] != board2[i])
{
return false;
}
}
//所有元素都比較完畢以後
return true;
}
/**
*In_open()函式,判斷一個節點是否在open表中
*
*@param pNode, list<pNode> open
*@return bool true--在ope表中, false--不再open表中
*/
bool in_open(pNode plan, list<pNode> &open)
{
//遍歷整個open表,使用迭代器
for(list<pNode>::iterator it = open.begin(); it != open.end(); it++)
{
if(same_plan(plan.board, (*it).board))
{
return true;
}
}
return false;
}
/**
*in_close()函式,判斷一個節點是否在close表中
*
*@param pNode, vector<pNode> close
*@return bool true 在close表中 false不再表中
*/
bool in_close(pNode plan, vector<pNode> &close)
{
//遍歷每一個節點
for(int i = 0; i < close.size(); i++)
{
if(same_plan(plan.board, close[i].board))
{
return true;
}
}
//都遍歷完畢以後依然沒有找到
return false;
}
/**
*insert_close()函式 將正在訪問的節點新增到close表中
*
*@param pNode, vector<pNode &close
*@return int position ,節點在close表中的索引
*/
int insert_close(pNode plan, vector<pNode>& close)
{
close.push_back(plan);
return close.size()-1;
}
/**
*insert_open()函式 將新生成的plan新增到fringe上去
*
*@param pNode plan, list<pNode> fringe
*@return fringe
*/
bool insert_open(pNode plan, list<pNode>& open)
{
//先取得當前方案的f-value值 = h + g;
int f = plan.h + plan.g;
//int h = plan.h;
//遍歷fringe,檢視此方案是否已在佇列中
for(list<pNode>::iterator it = open.begin(); it != open.end(); it++)
{
//if(h < ((*it).f + (*it).g))
if(f < ((*it).h+(*it).g))
{
//cout<<"進行插入"<<endl;
open.insert(it, plan);
return true;
}
}
//當走到這時,說明這個plan的fvalue是最大的
open.push_back(plan);
return true;
}
/**
*generate函式:生成新的解決方案
*
*@param vector<int> board, list<board> l, int rc邊的長度;
*/
void generate(list<pNode>& open, vector<pNode> &close)
{
//取得h值最小的元素
pNode plan = open.front();
//將取得的節點從open包中刪除
open.pop_front();
//將當前方案新增到close表中
int pos = insert_close(plan, close);
//資料是3*3的
int rc = plan.rc;
//尋找0的位置
int px;
int py;
for(int i = 0; i < rc*rc; ++i)
{
if(plan.board[i] == 0)
{
px = i / rc;
py = i % rc;
}
}
//想四個方向拓展,首先得判斷能否拓展
for(int i = 0; i < 4; i++)
{
if(px+x_axis[i] >= 0 && px+x_axis[i] < rc &&
py+y_axis[i] >= 0 && py+y_axis[i] < rc)
{
//生成一個新的節點
pNode new_plan = plan;
new_plan.board[px*rc+py] =
new_plan.board[(px+x_axis[i])*rc+(py+y_axis[i])];
new_plan.board[(px+x_axis[i])*rc+(py+y_axis[i])] = 0;
new_plan.h = heuristics(new_plan.board, new_plan.rc);
new_plan.g = plan.g+1;//g表示層數
new_plan.parent = pos;//記錄新生成節點的父節點
if(in_open(new_plan, open))//如果新生成的節點在open表中
{
for(list<pNode>::iterator it = open.begin(); it != open.end(); it++)
{
if(same_plan(new_plan.board, (*it).board))
{
if((new_plan.h+new_plan.g) > ((*it).h+(*it).g))
{
//open.erase(it);
break;
}
else
{
//刪除
open.erase(it);
break;
}
}
//找該節點應該插入的位置
bool inserted = false;
if(!inserted && (new_plan.h+new_plan.g) < ((*it).h+(*it).g))
{
inserted = true;
open.insert(it, new_plan);
}
}
}
else if(in_close(new_plan, close))
{
for(int i = 0; i < close.size(); i++)
{
if(same_plan(new_plan.board, close[i].board) &&
((new_plan.h+new_plan.g)<(close[i].h+close[i].g)))
{
close[i].h = new_plan.h;
close[i].g = new_plan.g;
//將這個重新賦值的節點插入到open表中
insert_open(new_plan, open);
break;
}
}
}
else
{
insert_open(new_plan, open);
}
}
}
return ;
}
/**
*void get_path()函式,根據當前符合目標狀態的節點在close表中回溯,
* 從而獲得從起點到達目標終點的路徑
*
*@param pNode, vector<pNode> close
*@return null
*/
void get_path(pNode plan, vector<pNode> &close, int plen)
{
if(plan.parent == -1)
{
print(plan.board, plan.rc);
cout<<"路徑長度為:"<<plen++<<endl;
return;//遞迴出口,表示回溯到起點
}
//輸出當前節點
print(plan.board, plan.rc);
return get_path(close[plan.parent], close, ++plen);
}
int main(int argc, char *argv[])
{
//構造輸入
pNode plan;
//生成初始狀態
ifstream data_in("input.txt");
char digit;
while(data_in.get(digit))
{
if(digit >= '0' && digit <= '9')
{
int num = digit - '0';
//cout<<digit;
plan.board.push_back(num);
}
}
plan.rc = 3;
plan.g = 1;
plan.parent = -1; //標識起點
plan.h = heuristics(plan.board, plan.rc);
//生成open表,並將起點新增到表中
list<pNode> open;
open.push_back(plan);
//生成close表
vector<pNode> close;
while(!open.empty())
{
//print(open.front().board, 3);
out_data(open.front());
//判斷當前節點是否滿足目標狀態
if(is_ordered(open.front().board))
{
cout<<"************************"<<endl;
cout<<"*******查詢完成*********"<<endl;
cout<<"************************"<<endl;
get_path(open.front(), close, 0);
break;
}
//產生新的方案
generate(open, close);
}
//cout<<close.size()<<endl;
return 0;
}
測試用例
[1 6 4 8 7 0 3 2 5]的輸入可以得到21步的最優解。