1. 程式人生 > 實用技巧 >八數碼問題(DFS,BFS,A*)

八數碼問題(DFS,BFS,A*)

DFS,BFS的open表分別使用棧、佇列

A*的open表使用優先佇列

close表都使用集合

#include <queue>
#include <stack>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

struct borad {
    int
status[9];//status[0]到status[8]表示3X3的矩陣,0表示空格 int depth;//深度 int Fn;//啟發函式值,Fn = depth + hn即深度加曼哈頓距離 borad* pre;//父指標,指向移動前的棋盤狀態 borad() : pre(0), status(), depth(0), Fn(INT_MAX - 1) { for (int j = 0; j < 9; j++) { status[j] = j; } } borad(borad* x, int i[9
], int y, int z) : pre(x), depth(y), Fn(z) { for (int j = 0; j < 9; j++) { status[j] = i[j]; } } }; //優先佇列自定義排序規則,升序 struct cmp { bool operator() (const borad* a, const borad* b) { return a->Fn > b->Fn; } }; bool swapnum(int a, int b, int* status);//
交換元素 int getindex(int* status, int num);//獲得元素在棋盤上的一維座標 void print(int* status);//列印棋盤 int hn(int* status, int* target);//當前狀態與目標狀態的曼哈頓距離 void printans(borad* cur);//列印解法,回溯 int status2int(int* status);//棋盤狀態轉為int格式 int reversesum(int* status);//計算逆序數之和 int* randstatus(int* target);//獲得隨機初始狀態 int main() { int go[4] = { -1,1,-3,3 };//四個移動方向 int* start;//隨機初始狀態 //int start[9] = { 2,8,3,1,6,4,7,0,5 };//初始狀態 //2,3,7 ,4,5,8 ,0,6,1 int target[9] = { 1,2,3,8,0,4,7,6,5 };//目標狀態 stack<borad*> D_open;//DFS的open表,使用棧,深度大的在表頭 queue<borad*> B_open;//BFS的open表,使用佇列,深度小的在表頭 priority_queue<borad*, vector<borad*>, cmp> A_open;//A*的open表,使用優先佇列,啟發函式值小的元素在表頭 unordered_set<int> close;//close表,存放已訪問過的狀態,元素為狀態的int格式 //例:{ 1,2,3,8,0,4,7,6,5 }==》123804765(int) //{ 0,1,3,8,2,4,7,6,5 }==》13824765(int) //生成隨機初始狀態 start = randstatus(target); //--------------------------------------------start-A*-------- Fn=Gn+Hn -----------------------------// //初始狀態壓入佇列 A_open.push(new borad(NULL, start, 0, INT_MAX - 1)); borad* temp = A_open.top(); printf("初始狀態:"); print(temp->status); printf("目標狀態:"); print(target); printf("A* Fn=Gn+Hn:\n"); while (!A_open.empty()) { //彈出一個狀態 borad* cur = A_open.top(); A_open.pop(); //hn=Fn-depth為與目標狀態的曼哈頓距離,為0即到達目標狀態 if (cur->Fn - cur->depth == 0) { printf("到達目標狀態\nclose表大小為%d\n\n", close.size()); //printans(cur); break; } //存放int格式的狀態 int intstatus = status2int(cur->status); //出現重複狀態 if (close.count(intstatus)) { continue; } //加入close表,表示已訪問過 close.insert(intstatus); //獲得0的座標 int zeroindex = getindex(cur->status, 0); for (int i = 0; i < 4; i++) { //新建節點,複製當前棋盤狀態,深度+1 borad* temp = new borad(cur, cur->status, cur->depth + 1, 0); //0向四個方向移動 if (swapnum(zeroindex, zeroindex + go[i], temp->status)) { //移動成功 //計算啟發函式值,並更新節點 temp->Fn = temp->depth + hn(temp->status, target); //加入A_open表 A_open.push(temp); } else { //移動失敗 delete(temp); } } } //清空close表 close.clear(); //--------------------------------------------end-A*--------- Fn=Gn+Hn -------------------------// //清空A_open while (!A_open.empty()) { A_open.pop(); } //--------------------------------------------start-A*改----- Fn=Hn ------------------------// //初始狀態壓入佇列 A_open.push(new borad(NULL, start, 0, INT_MAX - 1)); printf("A* Fn=hn:\n"); while (!A_open.empty()) { //彈出一個狀態 borad* cur = A_open.top(); A_open.pop(); if (cur->Fn == 0) { printf("到達目標狀態\nclose表大小為%d\n\n", close.size()); //printans(cur); break; } //存放int格式的狀態 int intstatus = status2int(cur->status); //出現重複狀態 if (close.count(intstatus)) { continue; } //加入close表,表示已訪問過 close.insert(intstatus); //獲得0的座標 int zeroindex = getindex(cur->status, 0); for (int i = 0; i < 4; i++) { //新建節點,複製當前棋盤狀態,深度+1 borad* temp = new borad(cur, cur->status, cur->depth + 1, 0); //0向四個方向移動 if (swapnum(zeroindex, zeroindex + go[i], temp->status)) { //移動成功 //計算啟發函式值,並更新節點 temp->Fn = hn(temp->status, target); //加入A_open表 A_open.push(temp); } else { //移動失敗 delete(temp); } } } //清空close表 close.clear(); //--------------------------------------------end-A*改----------- Fn=Hn -------------------------// //--------------------------------------------start-BFS------------------------------------------// //初始狀態壓入佇列 B_open.push(new borad(NULL, start, 0, INT_MAX - 1)); printf("BFS:\n"); while (!B_open.empty()) { //彈出一個狀態 borad *cur = B_open.front(); B_open.pop(); //與目標狀態的距離,為0即到達目標狀態 if (hn(cur->status, target) == 0) { printf("到達目標狀態\nclose表大小為%d\n\n", close.size()); //printans(cur); break; } //存放int格式的狀態 int intstatus = status2int(cur->status); //出現重複狀態 if (close.count(intstatus)) { continue; } //加入close表,表示已訪問過 close.insert(intstatus); //獲得0的座標 int zeroindex = getindex(cur->status, 0); for (int i = 0; i < 4; i++) { //新建節點,複製當前棋盤狀態,深度+1 borad *temp = new borad(cur, cur->status, cur->depth + 1, INT_MAX - 1); //0向四個方向移動 if (swapnum(zeroindex, zeroindex + go[i], temp->status)) { //移動成功 B_open.push(temp); } else { //移動失敗 delete(temp); } } } //清空close表 close.clear(); //--------------------------------------------end-BFS------------------------------------------// //--------------------------------------------start-DFS------------------------------------------// //初始狀態壓入佇列 D_open.push(new borad(NULL, start, 0, INT_MAX - 1)); printf("DFS:\n"); while (!D_open.empty()) { //彈出一個狀態 borad *cur = D_open.top(); D_open.pop(); //if (cur->depth == 5) { // break; //} //與目標狀態的距離,為0即到達目標狀態 if (hn(cur->status, target) == 0) { printf("到達目標狀態\nclose表大小為%d\n\n", close.size()); //printans(cur); break; } //存放int格式的狀態 int intstatus = status2int(cur->status); //出現重複狀態 if (close.count(intstatus)) { continue; } //加入close表,表示已訪問過 close.insert(intstatus); //獲得0的座標 int zeroindex = getindex(cur->status, 0); for (int i = 0; i < 4; i++) { //新建節點,複製當前棋盤狀態,深度+1 borad *temp = new borad(cur, cur->status, cur->depth + 1, INT_MAX - 1); //0向四個方向移動 if (swapnum(zeroindex, zeroindex + go[i], temp->status)) { //移動成功 D_open.push(temp); } else { //移動失敗 delete(temp); } } } //--------------------------------------------end-DFS------------------------------------------// delete(start); return 1; } //列印棋盤 void print(int* status) { for (int i = 0; i < 9; i++) { if (i % 3 == 0) { printf("\n"); } printf("%d", status[i]); } printf("\n\n"); } //獲得元素在棋盤上的一維座標 int getindex(int* status, int num) { for (int i = 0; i < 9; i++) { if (status[i] == num) { return i; } } return -1; } //交換元素 bool swapnum(int a, int b, int* status) { if (b >= 0 && b <= 8 && (a / 3 == b / 3 || a % 3 == b % 3)) { swap(status[a], status[b]); return true; } else { return false; } } //當前狀態與目標狀態的曼哈頓距離 int hn(int* status, int* target) { //獲得當前狀態與目標狀態的二維x,y座標 int x, y, xt, yt, it, h = 0; for (int i = 0; i < 9; i++) { x = i % 3; y = i / 3; it = getindex(target, status[i]); xt = it % 3; yt = it / 3; h += abs(x - xt) + abs(y - yt); } return h; } //列印解法,回溯 void printans(borad* cur) { vector<string> ans; while (cur) { ans.push_back(to_string(cur->status[0]) + to_string(cur->status[1]) + to_string(cur->status[2]) + "\n" + to_string(cur->status[3]) + to_string(cur->status[4]) + to_string(cur->status[5]) + "\n" + to_string(cur->status[6]) + to_string(cur->status[7]) + to_string(cur->status[8])); cur = cur->pre; } for (int i = ans.size() - 1; i >= 0; i--) { printf("%s\n ↓\n", ans[i].c_str()); } printf("END\n\n"); } //棋盤狀態轉為int格式 int status2int(int* status) { int res = 0; for (int i = 0, j = 8; i < 9; i++, j--) { res += status[i] * pow(10, j); } return res; } //計算逆序數之和 int reversesum(int* status) { int sum = 0; for (int i = 0; i < 9; i++) { if (status[i] != 0) { for (int j = 0; j < i; j++) { if (status[j] > status[i]) { sum++; } } } } return sum; } //獲得隨機初始狀態 int* randstatus(int* target) { int* start=new int[9](); unordered_map<int, int> nums;//記錄已新增的數 srand((int)time(0)); int element, sum1, sum2; sum2 = reversesum(target); //根據初始狀態與目標狀態的逆序數之和(sum1、sum2)是否相等,判斷初始狀態是否有解,不相等(即無解)則重新生成初始狀態 do { for (int i = 0; i < 9; i++) { element = rand() % 9; while (nums[element]) { element = rand() % 9; } nums[element]++; start[i] = element; } //清空記錄 nums.clear(); //計算逆序數之和 sum1 = reversesum(start); } while (sum1 % 2 != sum2 % 2); return start; }