1. 程式人生 > >A*演算法解決八數碼問題(C++版本)

A*演算法解決八數碼問題(C++版本)

八數碼問題定義:

八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。

A*演算法的通用虛擬碼 :

AstarwithTree

A*演算法解決八數碼問題的關鍵之處:

key
關鍵之處:
要維護兩個結構:

  • 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步的最優解。