c++八数码难题全家桶(A*算法、双向BFS、BFS、DFS)


前言

本文用C++实现了八数码问题,详尽四种经典算法:A*算法、双向BFS、BFS、DFS,有被八数码问题困扰的uu们,抓紧时间,进入正文,一定会对你有所帮助!


一、八数码难题是什么?

       八数码问题是在一个3×3的棋盘上有1−8位数字随机分布,以及一个空格,与空格相连的棋子可以滑动到空格中,问题的解是通过空格滑动,使得棋盘转化为目标状态,如下图所示。

7  2 4                     0   1   2

5 0 6     ----------->  3  4    5

8 3 1                      6   7   8

init state                target state

        为了简化问题的输入,首先将空格用数字0表示,然后将3×3的棋盘用9位长的字符串表示,则上图的初始状态为724506831,目标状态为012345678.

        对于上图的初始状态,将数字2移动到空格,称之为u操作(空格上移),将数字3移动到空格,称之为d操作(空格下移),将数字5移动到空格,称之为l操作(空格左移),将数字6移动到空格,称之为r操作(空格右移)

二、算法详解

1.首先利用逆序数来判断是否有解(奇偶性相同即可达)

bool EightDigital::HasSolution(string state) {
    int num = 0;/*计算逆序对数*/
    for (int i = 1; i < state.size(); ++i) {
        if (state[i] != '0') {
            for (int j = 0; j < i; ++j) {
                if (state.at(i) < state.at(j) && state[j != '0']) {
                    ++num;
                }
            }
        }
    }
    return num % 2 == 0;/*判断奇偶性*/
}
初始状态和目标状态奇偶性相同即可达

2.A*算法

1)open表采用最小优先队列实现(但需要实现运算符重载,见头文件)

void EightDigital::Astar(string state) {
	pair<string, int>top_pair;/*open表元素类型是pair,string表示状态,int表示f值*/
	open_.push(make_pair(state, Inspire(state)));/*初始状态深度为0*/
	map_depth_.insert(make_pair(state, 0));
	initial_state_ = state;
	while (!open_.empty()) {
		while (closed_.find(open_.top().first) != closed_.end()) {
			open_.pop();
		}/*判断当前队头是否在closed表,若在表中就出队*/
		if (open_.top().first == target_state_) return;/*当前队头是目标状态,直接退出函数*/
		top_pair = open_.top();/*临时变量赋值为队头*/
		open_.pop();/*队头出队*/
		closed_.insert(top_pair);/*队头入closed表*/
		for (int i = 0; i < 4; ++i) {/*对左右上下四个方向分别尝试移动*/
			TryToMove(top_pair.first, direction[i], map_depth_[top_pair.first] + 1);
		}
		/*由于open表是优先队列,所以只需将状态压入open表,队头自然就是值最小的节点*/
	}
}

 2)启发函数采用曼哈顿距离实现

int EightDigital::Inspire(string state) {
	int cost = 0;/*记录代价,曼哈顿距离*/
	for (int i = 0; i < 9; ++i) {
		if (state[i] != '0') {
			cost += abs((state[i] - '0') / 3 - i / 3) + abs((state[i] - '0') % 3 - i % 3);
		}
	}
	return cost;
}

3)从当前节点依次向四个方向尝试移动 

void EightDigital::TryToMove(string state, char direction, int depth) {
	string s = state;
	int curx, cury, nextx, nexty;
	curx = state.find('0') % 3;
	cury = state.find('0') / 3;
	/*这里没有采用move_deirection[4][2]来表示移动方向对应二维坐标的变化*/
	nextx = curx;
	nexty = cury;
	if (direction == 'l') nextx = curx - 1;
	else if (direction == 'r') nextx = curx + 1;
	else if (direction == 'u') nexty = cury - 1;
	else nexty = cury + 1;
	if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
		s[cury * 3 + curx] = s[nexty * 3 + nextx];
		s[nexty * 3 + nextx] = '0';
		if (map_depth_.find(s) == map_depth_.end()) {/*当前节点不在深度表中,以前从未被访问过*/
			map_depth_[s] = depth;/*将节点加入深度表*/
			map_path_[s] = state;/*建立父子关系*/
			open_.push(make_pair(s, depth + Inspire(s)));/*将深度+曼哈顿值得到f值,将状态压入open表*/
		}
		else {/*当前节点在深度表中,说明以前已被访问过*/
			if (depth < map_depth_[s]) {/*当前深度小于深度表中对应深度,则说明深度表需要更新*/
				map_depth_[s] = depth;
				map_path_[s] = state;
				if (closed_.find(s) != closed_.end()) {
					/*判断节点是否在closed表,若在将其移出closed表,将更新后的状态加入open表*/
					closed_.erase(s);
					open_.push(make_pair(s, depth + Inspire(s)));
				}
			}
		}
	}
}

4)打印A*算法路径

void EightDigital::PrintAstarPath() {
	string s = target_state_;
	int move;/*用来表示前后状态的差值*/
	while (s != initial_state_) {
		move = s.find('0') - map_path_[s].find('0');
		if (move == -1)move_path_.push_back('l');
		else if (move == 1)move_path_.push_back('r');
		else if (move == -3)move_path_.push_back('u');
		else move_path_.push_back('d');
		path_.push_back(s);
		s = map_path_[s];
	}
	path_.push_back(initial_state_);
	/*由于此时path里面的路径是倒置的,故将其反向输出,也可以用栈实现,但输出结束栈空,路径无法保留*/
	for (int i = 0; i < path_.size(); ++i) {
		for (int j = 0; j < 9; ++j) {
			cout << path_[path_.size() - 1 - i][j] << '\t';
			if ((j + 1) % 3 == 0 && j > 0)cout << endl;
		}
		cout << "******************" << endl;
	}
	cout << "MovePath->";
	for (int i = move_path_.size() - 1; i >= 0; --i)cout << move_path_[i];
	cout << endl;
}

 3.双向BFS

1)需要从初始状态和目标状态两个方向同时搜索,故需要设置两个队列,搜索重合即搜索完毕

void EightDigital::TwoWayBFS(string state) {
	queue<string>queue[2];/*设置两个队列,分别对应从初始状态(记为d方向),从目标状态(记为u方向)*/
	queue[0].push(initial_state_);
	queue[1].push(target_state_);
	visited_[initial_state_] = 0;/*d方向访问标记设为0*/
	visited_[target_state_] = 1;/*u方向访问标记设为1*/
	/*设置标记不同用来区分是哪个方向访问的*/
	while (!queue[0].empty() && !queue[1].empty()) {
		string str, temp_str;
		int curx, cury, nextx, nexty, cur_index, next_index;
		for (int i = 0; i < 2; ++i) {/*对应两个方向的队列*/
			str = queue[i].front();
			cur_index = str.find('0');
			curx = cur_index % 3;
			cury = cur_index / 3;
			for (int j = 0; j < 4; ++j) {/*对应四个方向*/
				temp_str = str;
				nextx = curx + move_direction[j][0];
				nexty = cury + move_direction[j][1];
				if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
					next_index = nextx + nexty * 3;
					temp_str[cur_index] = temp_str[next_index];
					temp_str[next_index] = '0';
					if (visited_.find(temp_str) == visited_.end()) {/*该状态从未被访问过(包括两个方向)*/
						queue[i].push(temp_str);
						visited_[temp_str] = i;/*做对应的标记*/
						link_[temp_str] = str;/*建立父子关系*/
					}
					else if (visited_[temp_str] ^ i) {/*该状态被其中一个方向访问过,此时的节点就是公共访问节点*/
						key_str_ = temp_str;/*记录关键节点*/
						/*此时temp_str已在关系表中,再次压入需要做方向标记防止覆盖*/
						if (i == 0)link_[temp_str + 'd'] = str;
						else link_[temp_str + 'u'] = str;
						return;/*前后搜索重合,关键节点已记录,退出函数*/
					}
				}
			}
			queue[i].pop();/*与当前队列有关节点已处理完,将当前队头出队列*/
		}
	}
}

2)打印双向BFS路径

void EightDigital::PrintTwoWayPath() {
	string str;
	/*由于双向BFS,所以利用关键节点key_str,从两个方向反向搜索*/
	if (link_.find(key_str_ + 'd') != link_.end()) {/*关键节点是d方向后找到的*/
		str = link_[key_str_ + 'd'];
		stack<string> prec_stack;
		while (str != initial_state_) {
			prec_stack.push(str);
			str = link_[str];
		}
		prec_stack.push(initial_state_);
		while (!prec_stack.empty()) {
			PrintStringAsMatrix(prec_stack.top());
			prec_stack.pop();
		}
		str = link_[key_str_];
		while (str != target_state_) {
			PrintStringAsMatrix(str);
			str = link_[str];
		}
		PrintStringAsMatrix(target_state_);
	}
	else {/*关键节点是u方向后找到的*/
		str = key_str_;
		stack<string> prec_stack;
		while (str != initial_state_) {
			prec_stack.push(str);
			str = link_[str];
		}
		prec_stack.push(initial_state_);
		while (!prec_stack.empty()) {
			PrintStringAsMatrix(prec_stack.top());
			prec_stack.pop();
		}
		str = link_[key_str_ + 'u'];
		while (str != target_state_) {
			PrintStringAsMatrix(str);
			str = link_[str];
		}
		PrintStringAsMatrix(target_state_);
	}
}

4.BFS 

1)经典BFS

void EightDigital::BFS(string state) {
	queue<string>queue;
	queue.push(state);
	visited_[state] = 1;
	while (!queue.empty()) {
		string str = queue.front();/*取对头*/
		if (str == target_state_)return;/*当前队头为目标状态,直接退出函数*/
		int curx, cury, nextx, nexty, cur_index, next_index;
		cur_index = str.find('0');
		curx = cur_index % 3;
		cury = cur_index / 3;
		for (int i = 0; i < 4; ++i) {
			str = queue.front();/*每次循环str都要重新被队头赋值,否则str会发生改变*/
			nextx = curx + move_direction[i][0];
			nexty = cury + move_direction[i][1];
			if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
				next_index = nextx + nexty * 3;
				str[cur_index] = str[next_index];
				str[next_index] = '0';
				if (visited_.find(str) == visited_.end()) {/*str未被访问过,才会给它入队列*/
					link_[str] = queue.front();/*建立父子关系*/
					queue.push(str);
					visited_[str] = 1;/*标记为已访问过*/
				}
			}
		}
		queue.pop();/*与队头相关的节点都已处理完,将队头出队*/
	}
}

2)打印BFS路径(BFS、DFS打印输出过程完全相同)

void EightDigital::PrintDFSAndBFSPath() {
	stack<string> stack;
	string str = target_state_;
	while (str != initial_state_) {
		stack.push(str);
		str = link_[str];/*自下而上查找,直到找到初始状态*/
	}
	stack.push(initial_state_);
	int num = stack.size();/*记录步数*/
	while (!stack.empty()) {
		PrintStringAsMatrix(stack.top());
		stack.pop();
	}
	cout << "共" << num << "步." << endl;
}

5.DFS(其实DFS不太适用八数码问题,大概率搜不出来,由于空间得不到释放,所以会搜索爆炸) 

1)DFS

void EightDigital::DFS(string state) {
	visited_[state] = 1;/*进入DFS()的节点肯定没访问过,故现在标记为访问过*/
	if (visited_.find(target_state_) != visited_.end()) return;/*判断目标状态有没有访问过,访问过直接退出*/
	int curx, cury, nextx, nexty, cur_index, next_index;
	cur_index = state.find('0');
	curx = cur_index % 3;
	cury = cur_index / 3;
	for (int i = 0; i < 4; ++i) {
		string str = state;
		nextx = curx + move_direction[i][0];
		nexty = cury + move_direction[i][1];
		if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
			next_index = nextx + nexty * 3;
			str[cur_index] = str[next_index];
			str[next_index] = '0';
			if (visited_.find(str) != visited_.end())continue;/*如果节点已经访问过,直接跳出当前循环*/
			link_[str] = state;/*建立父子关系*/
			DFS(str);/*递归调用DFS*/
		}
	}
}

2)打印DFS路径(与BFS相同) 

void EightDigital::PrintDFSAndBFSPath() {
	stack<string> stack;
	string str = target_state_;
	while (str != initial_state_) {
		stack.push(str);
		str = link_[str];/*自下而上查找,直到找到初始状态*/
	}
	stack.push(initial_state_);
	int num = stack.size();/*记录步数*/
	while (!stack.empty()) {
		PrintStringAsMatrix(stack.top());
		stack.pop();
	}
	cout << "共" << num << "步." << endl;
}

 6.Clear函数(清空相关属性,建议每次调用搜索算法前调用此函数)

void EightDigital::Clear() {
	/*清空相关属性,防止前一次调用相关属性会发生变化,建议每次调用搜索算法前调用此函数*/
	steps_ = 0;
	while (!open_.empty())open_.pop();
	closed_.clear();
	map_depth_.clear();
	map_path_.clear();
	path_.clear();
	move_path_.clear();
	visited_.clear();
	link_.clear();
	key_str_ = "";
}

三、完整代码(环境:VS/VSCode)---->注释非常详尽

1.EightDigital.h

#pragma once
#include<iostream>
#include<stack>
#include<queue>
#include<string>
#include<functional>
#include<vector>
#include<map>
using namespace std;

/*以下结构用于实现运算符重载,用以实现最小优先队列*/
struct Cmp {
	bool operator()(pair<string, int>& a, pair<string, int>& b) {
		return a.second > b.second;
	}
};

/*EightDigital类包含A*算法,DFS,BFS,双向BFS,实现八数码难题*/
class EightDigital
{
public:
	EightDigital(string state);/*构造函数,传入状态即为初始状态*/
	void setInitialState(string state);/*设置初始状态*/
	void setTargetState(string state);/*注意DFS设置为"176240583",不然搜索会爆炸*/
	string getInitialState();/*获取初始状态*/
	string getTargetState();/*获取目标状态*/
	void PrintStringAsMatrix(string state);/*将状态由字符串转换成3x3矩阵输出*/
	bool HasSolution(string state);/*判断初始状态是否可达*/
	void DFS(string state);/*DFS求解八数码*/
	void BFS(string state);/*BFS求解八数码*/
	void TwoWayBFS(string state);/*双向BFS求解八数码*/
	void Astar(string state);/*A*算法*/
	int Inspire(string state);/*A*启发函数,曼哈顿距离*/
	void TryToMove(string state, char direction, int depth);/*A*状态移动处理*/
	void PrintAstarPath();/*打印A*算法路径*/
	void PrintDFSAndBFSPath();/*打印DFS、BFS算法路径*/
	void PrintTwoWayPath();/*打印双向BFS算法路径*/
	void Clear();/*清空相关属性,建议每次调用搜索算法前调用此函数*/
private:
	string initial_state_;/*八数码初始状态*/
	string target_state_ = "012345678";/*八数码目标状态*/
	char direction[4] = { 'l','r','u','d' };/*移动方向*/
	int move_direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };/*移动方向对应二维坐标的变化*/
	int steps_ = 0;/*可用于记录搜索的状态数(注意不是移动路径)*/
	priority_queue<pair<string, int>, vector<pair<string, int>>, Cmp>open_;/*open表--最小优先队列*/
	map<string, int>closed_;/*closed表---哈希表*/
	map<string, int> map_depth_;/*深度表(全局)*/
	map<string, string>map_path_;/*A*算法父子关系表,孩子-->父亲*/
	vector<string> path_;/*A*算法路径上的各个状态(倒置)*/
	vector<char> move_path_;/*A*算法路径上的移动方向(倒置)*/
	map<string, int>visited_;/*用于除A*算法外的全局访问记录*/
	map<string, string>link_;/*用于除A*算法外的父子关系表,孩子--->父亲*/
	string key_str_;/*用于记录双向BFS的公共访问节点,命名为关键节点*/
};

2.EightDigital.cpp

#include "EightDigital.h"
EightDigital::EightDigital(string initial_state) {
	initial_state_ = initial_state;
}

void EightDigital::setInitialState(string state) {
	initial_state_ = state;
}

void EightDigital::setTargetState(string state) {
	target_state_ = state;
}

string EightDigital::getInitialState() {
	return initial_state_;
}

string EightDigital::getTargetState() {
	return target_state_;
}

void EightDigital::PrintStringAsMatrix(string state) {
	for (int i = 0; i < 9; ++i) {
		cout << state[i] << '\t';
		if ((i + 1) % 3 == 0)cout << endl;
	}
	cout << "******************" << endl;
}

bool EightDigital::HasSolution(string state) {
	int num = 0;/*计算逆序对数*/
	for (int i = 1; i < state.size(); ++i) {
		if (state[i] != '0') {
			for (int j = 0; j < i; ++j) {
				if (state.at(i) < state.at(j) && state[j != '0']) {
					++num;
				}
			}
		}
	}
	return num % 2 == 0;/*判断奇偶性*/
}

void EightDigital::DFS(string state) {
	visited_[state] = 1;/*进入DFS()的节点肯定没访问过,故现在标记为访问过*/
	if (visited_.find(target_state_) != visited_.end()) return;/*判断目标状态有没有访问过,访问过直接退出*/
	int curx, cury, nextx, nexty, cur_index, next_index;
	cur_index = state.find('0');
	curx = cur_index % 3;
	cury = cur_index / 3;
	for (int i = 0; i < 4; ++i) {
		string str = state;
		nextx = curx + move_direction[i][0];
		nexty = cury + move_direction[i][1];
		if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
			next_index = nextx + nexty * 3;
			str[cur_index] = str[next_index];
			str[next_index] = '0';
			if (visited_.find(str) != visited_.end())continue;/*如果节点已经访问过,直接跳出当前循环*/
			link_[str] = state;/*建立父子关系*/
			DFS(str);/*递归调用DFS*/
		}
	}
}

void EightDigital::BFS(string state) {
	queue<string>queue;
	queue.push(state);
	visited_[state] = 1;
	while (!queue.empty()) {
		string str = queue.front();/*取对头*/
		if (str == target_state_)return;/*当前队头为目标状态,直接退出函数*/
		int curx, cury, nextx, nexty, cur_index, next_index;
		cur_index = str.find('0');
		curx = cur_index % 3;
		cury = cur_index / 3;
		for (int i = 0; i < 4; ++i) {
			str = queue.front();/*每次循环str都要重新被队头赋值,否则str会发生改变*/
			nextx = curx + move_direction[i][0];
			nexty = cury + move_direction[i][1];
			if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
				next_index = nextx + nexty * 3;
				str[cur_index] = str[next_index];
				str[next_index] = '0';
				if (visited_.find(str) == visited_.end()) {/*str未被访问过,才会给它入队列*/
					link_[str] = queue.front();/*建立父子关系*/
					queue.push(str);
					visited_[str] = 1;/*标记为已访问过*/
				}
			}
		}
		queue.pop();/*与队头相关的节点都已处理完,将队头出队*/
	}
}

void EightDigital::TwoWayBFS(string state) {
	queue<string>queue[2];/*设置两个队列,分别对应从初始状态(记为d方向),从目标状态(记为u方向)*/
	queue[0].push(initial_state_);
	queue[1].push(target_state_);
	visited_[initial_state_] = 0;/*d方向访问标记设为0*/
	visited_[target_state_] = 1;/*u方向访问标记设为1*/
	/*设置标记不同用来区分是哪个方向访问的*/
	while (!queue[0].empty() && !queue[1].empty()) {
		string str, temp_str;
		int curx, cury, nextx, nexty, cur_index, next_index;
		for (int i = 0; i < 2; ++i) {/*对应两个方向的队列*/
			str = queue[i].front();
			cur_index = str.find('0');
			curx = cur_index % 3;
			cury = cur_index / 3;
			for (int j = 0; j < 4; ++j) {/*对应四个方向*/
				temp_str = str;
				nextx = curx + move_direction[j][0];
				nexty = cury + move_direction[j][1];
				if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
					next_index = nextx + nexty * 3;
					temp_str[cur_index] = temp_str[next_index];
					temp_str[next_index] = '0';
					if (visited_.find(temp_str) == visited_.end()) {/*该状态从未被访问过(包括两个方向)*/
						queue[i].push(temp_str);
						visited_[temp_str] = i;/*做对应的标记*/
						link_[temp_str] = str;/*建立父子关系*/
					}
					else if (visited_[temp_str] ^ i) {/*该状态被其中一个方向访问过,此时的节点就是公共访问节点*/
						key_str_ = temp_str;/*记录关键节点*/
						/*此时temp_str已在关系表中,再次压入需要做方向标记防止覆盖*/
						if (i == 0)link_[temp_str + 'd'] = str;
						else link_[temp_str + 'u'] = str;
						return;/*前后搜索重合,关键节点已记录,退出函数*/
					}
				}
			}
			queue[i].pop();/*与当前队列有关节点已处理完,将当前队头出队列*/
		}
	}
}

void EightDigital::Astar(string state) {
	pair<string, int>top_pair;/*open表元素类型是pair,string表示状态,int表示f值*/
	open_.push(make_pair(state, Inspire(state)));/*初始状态深度为0*/
	map_depth_.insert(make_pair(state, 0));
	initial_state_ = state;
	while (!open_.empty()) {
		while (closed_.find(open_.top().first) != closed_.end()) {
			open_.pop();
		}/*判断当前队头是否在closed表,若在表中就出队*/
		if (open_.top().first == target_state_) return;/*当前队头是目标状态,直接退出函数*/
		top_pair = open_.top();/*临时变量赋值为队头*/
		open_.pop();/*队头出队*/
		closed_.insert(top_pair);/*队头入closed表*/
		for (int i = 0; i < 4; ++i) {/*对左右上下四个方向分别尝试移动*/
			TryToMove(top_pair.first, direction[i], map_depth_[top_pair.first] + 1);
		}
		/*由于open表是优先队列,所以只需将状态压入open表,队头自然就是值最小的节点*/
	}
}

int EightDigital::Inspire(string state) {
	int cost = 0;/*记录代价,曼哈顿距离*/
	for (int i = 0; i < 9; ++i) {
		if (state[i] != '0') {
			cost += abs((state[i] - '0') / 3 - i / 3) + abs((state[i] - '0') % 3 - i % 3);
		}
	}
	return cost;
}

void EightDigital::TryToMove(string state, char direction, int depth) {
	string s = state;
	int curx, cury, nextx, nexty;
	curx = state.find('0') % 3;
	cury = state.find('0') / 3;
	/*这里没有采用move_deirection[4][2]来表示移动方向对应二维坐标的变化*/
	nextx = curx;
	nexty = cury;
	if (direction == 'l') nextx = curx - 1;
	else if (direction == 'r') nextx = curx + 1;
	else if (direction == 'u') nexty = cury - 1;
	else nexty = cury + 1;
	if (nextx >= 0 && nextx < 3 && nexty >= 0 && nexty < 3) {
		s[cury * 3 + curx] = s[nexty * 3 + nextx];
		s[nexty * 3 + nextx] = '0';
		if (map_depth_.find(s) == map_depth_.end()) {/*当前节点不在深度表中,以前从未被访问过*/
			map_depth_[s] = depth;/*将节点加入深度表*/
			map_path_[s] = state;/*建立父子关系*/
			open_.push(make_pair(s, depth + Inspire(s)));/*将深度+曼哈顿值得到f值,将状态压入open表*/
		}
		else {/*当前节点在深度表中,说明以前已被访问过*/
			if (depth < map_depth_[s]) {/*当前深度小于深度表中对应深度,则说明深度表需要更新*/
				map_depth_[s] = depth;
				map_path_[s] = state;
				if (closed_.find(s) != closed_.end()) {
					/*判断节点是否在closed表,若在将其移出closed表,将更新后的状态加入open表*/
					closed_.erase(s);
					open_.push(make_pair(s, depth + Inspire(s)));
				}
			}
		}
	}
}

void EightDigital::PrintAstarPath() {
	string s = target_state_;
	int move;/*用来表示前后状态的差值*/
	while (s != initial_state_) {
		move = s.find('0') - map_path_[s].find('0');
		if (move == -1)move_path_.push_back('l');
		else if (move == 1)move_path_.push_back('r');
		else if (move == -3)move_path_.push_back('u');
		else move_path_.push_back('d');
		path_.push_back(s);
		s = map_path_[s];
	}
	path_.push_back(initial_state_);
	/*由于此时path里面的路径是倒置的,故将其反向输出,也可以用栈实现,但输出结束栈空,路径无法保留*/
	for (int i = 0; i < path_.size(); ++i) {
		for (int j = 0; j < 9; ++j) {
			cout << path_[path_.size() - 1 - i][j] << '\t';
			if ((j + 1) % 3 == 0 && j > 0)cout << endl;
		}
		cout << "******************" << endl;
	}
	cout << "MovePath->";
	for (int i = move_path_.size() - 1; i >= 0; --i)cout << move_path_[i];
	cout << endl;
}

void EightDigital::PrintDFSAndBFSPath() {
	stack<string> stack;
	string str = target_state_;
	while (str != initial_state_) {
		stack.push(str);
		str = link_[str];/*自下而上查找,直到找到初始状态*/
	}
	stack.push(initial_state_);
	int num = stack.size();/*记录步数*/
	while (!stack.empty()) {
		PrintStringAsMatrix(stack.top());
		stack.pop();
	}
	cout << "共" << num << "步." << endl;
}

void EightDigital::PrintTwoWayPath() {
	string str;
	/*由于双向BFS,所以利用关键节点key_str,从两个方向反向搜索*/
	if (link_.find(key_str_ + 'd') != link_.end()) {/*关键节点是d方向后找到的*/
		str = link_[key_str_ + 'd'];
		stack<string> prec_stack;
		while (str != initial_state_) {
			prec_stack.push(str);
			str = link_[str];
		}
		prec_stack.push(initial_state_);
		while (!prec_stack.empty()) {
			PrintStringAsMatrix(prec_stack.top());
			prec_stack.pop();
		}
		str = link_[key_str_];
		while (str != target_state_) {
			PrintStringAsMatrix(str);
			str = link_[str];
		}
		PrintStringAsMatrix(target_state_);
	}
	else {/*关键节点是u方向后找到的*/
		str = key_str_;
		stack<string> prec_stack;
		while (str != initial_state_) {
			prec_stack.push(str);
			str = link_[str];
		}
		prec_stack.push(initial_state_);
		while (!prec_stack.empty()) {
			PrintStringAsMatrix(prec_stack.top());
			prec_stack.pop();
		}
		str = link_[key_str_ + 'u'];
		while (str != target_state_) {
			PrintStringAsMatrix(str);
			str = link_[str];
		}
		PrintStringAsMatrix(target_state_);
	}
}

void EightDigital::Clear() {
	/*清空相关属性,防止前一次调用相关属性会发生变化,建议每次调用搜索算法前调用此函数*/
	steps_ = 0;
	while (!open_.empty())open_.pop();
	closed_.clear();
	map_depth_.clear();
	map_path_.clear();
	path_.clear();
	move_path_.clear();
	visited_.clear();
	link_.clear();
	key_str_ = "";
}

3.main.cpp 

#include"EightDigital.h"
int main() {
	string str = "724506831";/*初始化初始状态*/
	EightDigital e1(str);/*A*算法*/
	EightDigital e2(str);/*双向BFS*/
	EightDigital e3(str);/*BFS*/
	EightDigital e4(str);/*DFS*/
	EightDigital e5(str);/*DFS*/
	if (e1.HasSolution(e1.getInitialState())) {
		e1.Astar(e1.getInitialState());
		cout << "****** Astar *********" << endl;
		e1.PrintAstarPath();
	}

	if (e2.HasSolution(e2.getInitialState())) {
		e2.TwoWayBFS(e2.getInitialState());
		cout << "****** TwoWayBFS *********" << endl;
		e2.PrintTwoWayPath();
	}

	if (e3.HasSolution(e3.getInitialState())) {
		e3.BFS(e3.getInitialState());
		cout << "****** BFS *********" << endl;
		e3.PrintDFSAndBFSPath();
	}

	e4.setTargetState("602741583");
	e4.DFS(e4.getInitialState());
	cout << "****** DFS *********" << endl;
	e4.PrintDFSAndBFSPath();
	/*因为DFS采用递归或栈实现,由于移动方向比较多。所以如果步数较多,那么空间爆炸将会停止搜索,*/
	/*经测试,VS最多可搜索1000左右,VSCode可搜索10000左右,所以e4修改了目标状态,*/
	/*而e5则是将初始状态变得很简单(离目标状态很近),所以八数码问题不建议采用单纯DFS*/
	if (e5.HasSolution(e5.getInitialState())) {
		e5.setInitialState("312405678");
		e5.DFS(e5.getInitialState());
		cout << "****** DFS *********" << endl;
		e5.PrintDFSAndBFSPath();
	}
	/*总之,算法快慢比较Astar>TwoWayBFS>BFS>DFS,前两种比较实用,运行时后两者可能会很慢*/
	return 0;
}

总结

        以上就是今天要讲的内容,本文详细介绍了八数码难题,以及解决该问题的四种经典方法:A*算法、双向BFS、BFS、DFS,希望能够对uu们有所帮助,如果有uu们有不懂的问题或者有更好的创意和建议,可以在评论区留下宝贵意见,随时欢迎私信博主,给博主点个关注,留个赞,博主不胜感激

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多思善思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值