[英]A* Search Algorithm 8 Puzzle c++
I am trying to write an A* search Algorithm program that solves the classic 8 Puzzle problem.我正在尝试编写一个 A* 搜索算法程序来解决经典的 8 Puzzle 问题。 However after many attempts and research I have found myself stuck implementing the algorithm.
然而,经过多次尝试和研究,我发现自己在实现算法时遇到了困难。
The program is required to return the shortest path as a string that represents the actions required to take to reach the goal state from the starting state, for example "UDLRUDLRRDD" U = up, D = down, L = left and R = right.程序需要以字符串形式返回最短路径,该字符串表示从起始 state 到达目标 state 所需采取的操作,例如“UDLRUDLRRDD”U = 上、D = 下、L = 左和 ZE1E39D603D4057328 右。
I am trying to follow the algorithm given by geeksforgeeks( https://www.geeksforgeeks.org/a-search-algorithm/ ).我正在尝试遵循 geeksforgeeks 给出的算法( https://www.geeksforgeeks.org/a-search-algorithm/ )。 I Have added a reproducible example below.
我在下面添加了一个可重现的示例。
Struct to store board states存储板状态的结构
#include <ctime>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <bits/stdc++.h>
enum heuristicFunction{misplacedTiles, manhattanDistance};
struct State{ // state struct to store states of board
int cost = 0, hCost = -1, gCost = 0; // total cost F(n) = H(n) + G(n)
int state[3][3]; // Board, the blank tile is represented as 0
int x, y; // 0 pos
string action; // stores the action the state took
State *parent = NULL;
// default state constructor
State(const string element){
int n;
n = 0;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
state[i][j] = element[n] - '0';
if(state[i][j] == 0){
x = j;
y = i;
}
n++;
}
}
}
// copy constructor
State(State *s){
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
this->state[i][j] = s->state[i][j];
}
}
this->x = s->x;
this->y = s->y;
this->cost = s->cost;
this->hCost = s->hCost;
this->gCost = s->gCost;
this->action = s->action;
this->parent = s->parent;
}
};
A* search class that implements the struct above A* 搜索实现上述结构的 class
class AStarSearch{
private:
struct Cost_Compare{ // struct containing operator for cost organize Q
bool operator()(State* const& n1, State* const& n2) {
return n1->cost > n2->cost; // return true if n1 cost is greater
}
};
// Containers
priority_queue<State*, vector<State*>, Cost_Compare> Queue; // Q container
vector<State*> visitedList; // visited list
// start and end states
State *initial_state;
State *final_state;
// heuristic enum
heuristicFunction heuristic;
public:
// constructor
AStarSearch(const string init, const string final, heuristicFunction heuristic){
this->initial_state = new State(init); // set init state
this->final_state = new State(final); // set final state
this->Queue.push(this->initial_state); // push init state to queue
this->heuristic = heuristic;
}
State *getInit(){
return this->initial_state;
}
State *getFinal(){
return this->final_state;
}
// Cost Functions
State* Calculate_H(State *n){
int sum = 0;
switch(heuristic){
case misplacedTiles:
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
if(n->state[i][j] != final_state->state[i][j]){
sum++;
}
}
}
n->hCost = sum;
break;
case manhattanDistance:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if(n->state[i][j] != 0){
for (int k = 0; k < 3; k++) {
for (int l = 0; l < 3; l++) {
if (n->state[i][j] == final_state->state[k][l]) {
sum += abs(i-k) + abs(j-l);
}
}
}
}
}
}
n->hCost = sum;
break;
}
return n;
}
int calculate_cost(State *n){
int cost;
cost = n->hCost + n->gCost;
return cost;
}
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
// State Actions
State* move_left(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y][curr->x-1]);
curr->x = curr->x - 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "L";
curr->parent = current; // assign parent node
return curr;
}
State* move_right(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y][curr->x+1]);
curr->x = curr->x + 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "R";
curr->parent = current; // assign parent node
return curr;
}
State* move_down(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y+1][curr->x]);
curr->y = curr->y + 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "D";
curr->parent = current; // assign parent node
return curr;
}
State* move_up(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y-1][curr->x]);
curr->y = curr->y - 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "U";
curr->parent = current; // assign parent node
return curr;
}
bool isEqual(State *s1, State *s2){
for(int i=0; i<3; i++)
for(int j=0; j<3; j++)
if(s1->state[i][j] != s2->state[i][j])
return false;
return true;
}
bool isPresentInVisited(State *curr){
bool flag1 = false;
for(int i=0; i<visitedList.size(); i++){
if(isEqual(curr, visitedList[i])){
return true;
}
}
return false;
}
bool isPresentInQueue(State *s1, priority_queue<State*, vector<State*>, Cost_Compare> q){
while(!q.empty()){
State *curr = q.top();
q.pop();
if(isEqual(s1, curr)){
return true;
}
}
return false;
}
void search(){
State *current = Queue.top();
if(current->hCost == 0){
visitedList.push_back(current);
return;
}
while(!Queue.empty()){
State* next_state; // next state
next_state = Queue.top(); // assign to top of Q
Queue.pop(); // pop element off Q
visitedList.push_back(next_state); // add to visited list
vector<State*> successors;
if(next_state->hCost == 0){ // check goal
return;
} // else expand state
// expansions
else if(next_state->x == 0 && next_state->y == 0){
/*
0 1 2 0->down
3 4 5 0->right
6 7 8
*/
State *down = move_down(next_state);
State *right = move_right(next_state);
successors.push_back(down);
successors.push_back(right);
}
else if(next_state->x == 0 && next_state->y == 1){
/*
1 0 2 0->down
3 4 5 0->right
6 7 8 0->left
*/
State *down = move_down(next_state);
State *right = move_right(next_state);
State *left = move_left(next_state);
successors.push_back(down);
successors.push_back(right);
successors.push_back(left);
}
else if(next_state->x == 0 && next_state->y == 2){
/*
1 2 0 0->down
3 4 5 0->left
6 7 8
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
successors.push_back(down);
successors.push_back(left);
}
else if(next_state->x == 1 && next_state->y == 0){
/*
1 2 3 0->down
0 4 5 0->left
6 7 8 0->up
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(up);
}
else if(next_state->x == 1 && next_state->y == 1){
/*
1 2 3 0->down
4 0 5 0->left
6 7 8 0->up, 0-right
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 1 && next_state->y == 2){
/*
1 2 3 0->down
4 5 0 0->left
6 7 8 0->up
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 0){
/*
1 2 3
4 5 6 0->right
0 7 8 0->up
*/
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 1){
/*
1 2 3 0->right
4 5 6 0->left
7 0 8 0->up
*/
State *left = move_left(next_state);
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(left);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 2){
/*
1 2 3 0->up
4 5 6 0->left
7 8 0
*/
State *up = move_up(next_state);
State *left = move_left(next_state);
successors.push_back(up);
successors.push_back(left);
}
for(int i=0; i<successors.size(); i++){
if(successors[i]->hCost==0){
visitedList.push_back(successors[i]);
return;
}
else if(!isPresentInVisited(successors[i]) && !isPresentInQueue(successors[i], Queue)){
Queue.push(successors[i]);
}
}
}
return;
}
priority_queue<State*, vector<State*>, Cost_Compare> getQueue(){
return this->Queue;
}
vector<State*> getVisited(){
return this->visitedList;
}
};
This is where the class is implemented.这是实现 class 的地方。 The initial state and goal state are strings that follow this format: initialState = "042158367" and goalState = "012345678"
初始 state 和目标 state 是遵循以下格式的字符串:initialState = "042158367" 和 goalState = "012345678"
string aStar_search(string const initialState, string const goalState, heuristicFunction heuristic){
string path; // final path
// a star start
State *state;
vector<string> pathVector;
AStarSearch search = AStarSearch(initialState, goalState, heuristic);
search.search(); // begin search
state = search.getVisited()[search.getVisited().size()-1]; // assign last state this should the goal state
// debug expected goal state of the board
cout << endl << "Goal State: "<< endl;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
cout << search.getFinal()->state[i][j];
}
cout << endl;
}
// debug the initial state of the board
cout << endl << "init State: "<< endl;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
cout << search.getInit()->state[i][j];
}
cout << endl;
}
// debug this should be the same as goal state
cout << endl << "Goal State from visitedList: "<< endl;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
cout << state->state[i][j];
}
cout << endl;
}
while(state->parent){ // iter back through parents of states
pathVector.push_back(state->action); // push actions to vector
state = state->parent; // next parent
}
for(int i = pathVector.size()-1; i >= 0; i--){ // need to iter backwards because we follow the perant states
path += pathVector[i]; // add to path
}
// aStar exit
cout << endl << "Path To Goal: " << path << endl;
return path;
}
I believe my implementation of the search function is flawed as when I print the goal state from the visited list I get a completely different state than the expected goal state and hence this returns the incorrect path. I believe my implementation of the search function is flawed as when I print the goal state from the visited list I get a completely different state than the expected goal state and hence this returns the incorrect path. For example when I set initialState = "042158367" and goalState = "012345678" I get the following output in the console
例如,当我设置 initialState = "042158367" 和 goalState = "012345678" 我在控制台中得到以下 output
Goal State:
012
345
678
init State:
042
158
367
Goal State from visitedList:
514
082
367
Path To Goal: DRULDRRUR
I was wondering if someone could take a look at my code and tell me where I am going wrong in the implementation of the algorithm.我想知道是否有人可以查看我的代码并告诉我在算法的实现中哪里出错了。 Any help would be greatly appreciated.
任何帮助将不胜感激。
Thanks谢谢
The problem was the coordinates in the expanded states if statement, the x and y values were the wrong way around.问题是扩展状态中的坐标 if 语句,x 和 y 值是错误的。 facepalm
掌心
// expansions
else if(next_state->x == 0 && next_state->y == 0){
/*
0 1 2 0->down
3 4 5 0->right
6 7 8
*/
State *down = move_down(next_state);
State *right = move_right(next_state);
successors.push_back(down);
successors.push_back(right);
}
else if(next_state->x == 1 && next_state->y == 0){
/*
1 0 2 0->down
3 4 5 0->right
6 7 8 0->left
*/
State *down = move_down(next_state);
State *right = move_right(next_state);
State *left = move_left(next_state);
successors.push_back(down);
successors.push_back(right);
successors.push_back(left);
}
else if(next_state->x == 2 && next_state->y == 0){
/*
1 2 0 0->down
3 4 5 0->left
6 7 8
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
successors.push_back(down);
successors.push_back(left);
}
else if(next_state->x == 0 && next_state->y == 1){
/*
1 2 3 0->down
0 4 5 0->left
6 7 8 0->up
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(up);
}
else if(next_state->x == 1 && next_state->y == 1){
/*
1 2 3 0->down
4 0 5 0->left
6 7 8 0->up, 0-right
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 1){
/*
1 2 3 0->down
4 5 0 0->left
6 7 8 0->up
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(up);
}
else if(next_state->x == 0 && next_state->y == 2){
/*
1 2 3
4 5 6 0->right
0 7 8 0->up
*/
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 1 && next_state->y == 2){
/*
1 2 3 0->right
4 5 6 0->left
7 0 8 0->up
*/
State *left = move_left(next_state);
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(left);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 2){
/*
1 2 3 0->up
4 5 6 0->left
7 8 0
*/
State *up = move_up(next_state);
State *left = move_left(next_state);
successors.push_back(up);
successors.push_back(left);
}
Thanks for very interesting question!感谢您提出非常有趣的问题!
Although you're asking to fix a bug in your code, on contrary I decided to implement my own solution from scratch.尽管您要求修复代码中的错误,但相反,我决定从头开始实现我自己的解决方案。 So I don't claim for my answer to be accepted, although I hope my solution would be interesting at least for someone from educational point of view.
所以我不要求我的回答被接受,尽管我希望我的解决方案至少对从教育角度来看的人来说是有趣的。
I implemented step by step details from Wikipedia Article about A* Search Algorithm .我从关于A* Search Algorithm的 Wikipedia 文章中逐步实现了详细信息。 Though my solution has its own programming details, mostly due to extensive usage of standard C++ library.
虽然我的解决方案有自己的编程细节,主要是由于标准 C++ 库的广泛使用。
Before implementing algorithm located in function AStarSolve()
I created a helper supplementary class Board
which gives different helper methods to access game Board.在实现位于 function
AStarSolve()
中的算法之前,我创建了一个辅助补充 class Board
,它提供了不同的辅助方法来访问游戏板。
You may see that at the top of code I created two constants n = 3, m = 3
- this is to allow any board size, my board has NxM dimensions, currently only 3x3 as in your original task, but it can be set to any non-square size.您可能会看到在代码的顶部我创建了两个常量
n = 3, m = 3
- 这是为了允许任何板尺寸,我的板具有 NxM 尺寸,目前只有 3x3 与您的原始任务一样,但它可以设置为任何非方形尺寸。
As you know A-Star has so called heuristic function estimating lowest possible cost (lower bound) for the path, this function is signified as H(path).如您所知,A-Star 有所谓的启发式 function 估计路径的最低可能成本(下限),此 function 表示为 H(路径)。
In my code H(x) is taken as sum of distances of every non-zero cell to final position.在我的代码中,H(x) 被视为每个非零单元到最终 position 的距离之和。 It is quite obvious to prove that this is a lower bound, and in fact quite good.
很明显证明这是一个下限,而且实际上相当不错。
In A-Star algorithm this lower bound H(x) only influences speed of convergence of algorithm, so the better its estimate the faster we find the answer.在 A-Star 算法中,这个下界 H(x) 只影响算法的收敛速度,所以它的估计越好,我们找到答案的速度就越快。 But even with H(x) = 0 we will find the answer, in fact with 0 bound A Star algorithm becomes exactly Dijkstra Algorithm of finding shortest path, hence A Star is generalization of Dijkstra.
但即使 H(x) = 0 我们也会找到答案,事实上,在 0 下,A Star 算法正好变成了寻找最短路径的Dijkstra 算法,因此 A Star 是 Dijkstra 的推广。
I also had idea to make my algorithm multi-core, incorporating all CPU threads, but right now it is only single core just for educational simplicity.我也有想法让我的算法多核,合并所有 CPU 线程,但现在它只是单核,只是为了教育简单。 If I have desire later I'll extend my answer with multi-core version.
如果我以后有愿望,我会用多核版本扩展我的答案。
Following code does this console output of solution:以下代码执行此控制台 output 的解决方案:
042 142 142 142 142
158 --> 058 --> 358 --> 358 --> 358 -->
367 367 067 607 670
142 142 102 012
350 --> 305 --> 345 --> 345
678 678 678 678
#include <iostream>
#include <stdexcept>
#include <map>
#include <set>
#include <functional>
#include <string>
#include <vector>
#include <unordered_map>
#define ASSERT_MSG(cond, msg) { if (!(cond)) throw std::runtime_error("Assertion (" #cond ") failed at line " + std::to_string(__LINE__) + "! Msg '" + std::string(msg) + "'."); }
#define ASSERT(cond) ASSERT_MSG(cond, "")
using u8 = uint8_t;
size_t constexpr n = 3, m = 3;
class Board {
public:
Board() : n_(n), m_(m), board_(n_ * m_) {}
Board(std::string const & s) : n_(n), m_(m), board_(n_ * m_) {
for (size_t i = 0; i < m_; ++i)
for (size_t j = 0; j < n_; ++j)
(*this)(i, j) = s.at(i * m_ + j) - '0';
}
u8 & operator () (size_t i, size_t j) { return board_[i * m_ + j]; }
u8 const & operator () (size_t i, size_t j) const { return const_cast<Board&>(*this)(i, j); }
bool operator == (Board const & o) const { return board_ == o.board_; }
std::vector<Board> Neighbours() const {
std::vector<Board> r;
for (ptrdiff_t i = 0; i < n_; ++i)
for (ptrdiff_t j = 0; j < m_; ++j)
if ((*this)(i, j) == 0) {
for (std::pair<int, int> p: std::vector<std::pair<int, int>>{{0, -1}, {0, 1}, {-1, 0}, {1, 0}}) {
ptrdiff_t const ni = i + p.first, nj = j + p.second;
if (ni < 0 || ni >= n_ || nj < 0 || nj >= m_)
continue;
Board o = *this;
std::swap(o(i, j), o(ni, nj));
r.push_back(std::move(o));
}
break;
}
return std::move(r);
}
std::string Str(bool newline = false) const {
std::string r;
for (size_t i = 0; i < n_; ++i) {
for (size_t j = 0; j < m_; ++j)
r.append(1, (*this)(i, j) + '0');
if (newline && i + 1 < n_)
r.append(1, '\n');
}
return r;
}
size_t MinDist(Board const & to) const {
size_t r = 0;
for (ptrdiff_t i = 0; i < n_; ++i)
for (ptrdiff_t j = 0; j < m_; ++j) {
auto const v = (*this)(i, j);
if (v == 0)
continue;
size_t dist = size_t(-1);
for (ptrdiff_t i2 = 0; i2 < n_; ++i2) {
for (ptrdiff_t j2 = 0; j2 < m_; ++j2)
if (to(i2, j2) == v) {
dist = std::abs(i - i2) + std::abs(j - j2);
break;
}
if (dist != size_t(-1))
break;
}
ASSERT(dist != -1);
r += dist;
}
return r;
}
private:
size_t n_ = 0, m_ = 0;
std::vector<u8> board_;
};
std::vector<Board> AStarSolve(Board const & start, Board const & goal) {
// https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode
using IdT = std::string;
struct Entry {
Board board;
size_t gscore = size_t(-1), fscore = size_t(-1);
IdT came_from{};
};
std::unordered_map<IdT, Entry> entries;
std::map<size_t, std::set<IdT>> open_set;
auto H = [&](Entry const & e) {
return e.board.MinDist(goal);
};
{
Entry first{.board = start, .gscore = 0};
first.fscore = H(first);
entries[first.board.Str()] = first;
open_set[first.fscore].insert(first.board.Str());
}
std::function<std::vector<Board>(IdT const &, size_t)> ReconstructPath =
[&](IdT const & id, size_t depth){
thread_local std::vector<Board> path;
if (id == IdT{})
return path;
if (depth == 0)
path.clear();
auto const & e = entries.at(id);
path.insert(path.begin(), e.board);
return ReconstructPath(e.came_from, depth + 1);
};
while (!open_set.empty()) {
auto const min_fscore = open_set.begin()->first;
auto const min_entries = open_set.begin()->second;
for (auto const & id: min_entries)
if (entries.at(id).board == goal)
return ReconstructPath(id, 0);
open_set.erase(min_fscore);
for (auto const & cid: min_entries) {
auto const & cure = entries.at(cid);
for (auto const & nbid: cure.board.Neighbours()) {
size_t const tentative_gscore = cure.gscore + 1;
auto const nid = nbid.Str();
auto it = entries.find(nid);
bool is_new = it == entries.end();
if (is_new || tentative_gscore < it->second.gscore) {
if (is_new)
it = entries.insert({nid, Entry{.board = nbid}}).first;
it->second.came_from = cid;
it->second.gscore = tentative_gscore;
if (!is_new) {
auto it2 = open_set.find(it->second.fscore);
if (it2 != open_set.end() && it2->second.count(nid)) {
it2->second.erase(nid);
if (it2->second.empty())
open_set.erase(it2);
}
}
it->second.fscore = tentative_gscore + H(it->second);
open_set[it->second.fscore].insert(nid);
}
}
}
}
ASSERT_MSG(false, "Unreachable!");
}
void Solve(std::string const & start, std::string const & goal) {
auto const v = AStarSolve(start, goal);
size_t constexpr per_line = 5;
bool last = false;
for (size_t i = 0; !last; ++i) {
for (size_t j = 0; j < n; ++j) {
for (size_t i2 = 0; i2 < per_line; ++i2) {
size_t const k = i * per_line + i2;
if (k >= v.size()) {
last = true;
for (size_t l = 0; l < (m + 5); ++l)
std::cout << " ";
} else {
auto const & e = v.at(k);
auto const s = e.Str(true);
size_t pos = 0;
for (size_t ip = 0; ip < j; ++ip)
pos = s.find('\n', pos) + 1;
size_t pos2 = std::min<size_t>(s.size(), s.find('\n', pos));
std::cout << s.substr(pos, pos2 - pos) << (j == (n / 2) && k + 1 < v.size() ? " --> " : " ");
}
std::cout << (i2 + 1 >= per_line ? "\n" : "");
}
}
std::cout << std::endl;
}
}
int main() {
try {
Solve("042158367", "012345678");
return 0;
} catch (std::exception const & ex) {
std::cout << "Exception: " << ex.what() << std::endl;
return -1;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.