使用深度优先搜索(DFS)、广度优先搜索(BFS)、A* 搜索算法求解 (n^2 -1) 数码难题,耗时与内存占用(时空复杂度)对比(附:(n^2 - 1) 数码问题控制台简易演示程序)

一、实验目的

通过编程实验,体会并理解人工智能领域常用的新型搜索算法的测试用例 —— (n^2 -1) 数码问题。

二、实验内容和要求

通过深度优先搜索(DFS)、宽度优先搜索(BFS)、A*搜索算法来求解 (n^2 -1) 数码难题,要求如下:

  1. 初始状态以及目标状态形如下图。
  2. 输出完整的从初始状态到目标状态的动作序列。
  3. 对比3种算法的时间、空间消耗。
    在这里插入图片描述

三、参考实验环境

CPU:				Intel Core i5-8400 @ 2.80 GHz / 3.80 GHz, 6C6T
	指令集:			MMX, SSE, SSE2, SSE3, SSE4.1, SSE4.2, EM64T, VT-x, AES, AVX, AVX2, FMA3
	功耗限制:		PL1 = 75 W, PL2 = 130 W
	步进:			Stepping A, Revision U0
操作系统:			Windows 10 Professional 64-bit
IDE:				Visual Studio 2019
编译器:				Microsoft Visual C++(MSVC) Compiler
	参数:			/std:c17 /std:c++latest /O2 /Oi /GL
	参数解释:		ISO C17 (2018) Standard (/std:c17)
					Preview - Features from the Latest C++ Working Draft (/std:c++latest) (启用 C++20 最新特性)
					Maximum Optimization (Favor Speed) (/O2)
					Enable Intrinsic Functions: Yes (/Oi)
					Whole Program Optimization: Yes (/GL)

四、实验原理

深度优先搜索(DFS)

DFS 是一种搜索树或图的算法。在搜索过程中,该算法总是先尽量向深处(离出发点更远的位置,如果是树,则是从树根开始,往远离根的方向)搜索,再回溯并搜索其它分支。
虽然一些问题不直接具有树或图的结构,但可将其所有的状态视为一棵解答树或图。在本例中,棋盘的不同状态都能看成树的一个节点,每走一步相当于访问一个邻接节点。树根代表棋盘的初始状态。

广度优先搜索(BFS)

BFS 也是一种搜索树或图的算法。在搜索过程中,该算法总是先尽量搜索兄弟节点,再进入更深层的节点继续搜索。

A*搜索算法

A* 搜索算法属于启发式搜索算法,常用于求解最短路(最低开销路径)等问题。通过引入适当的启发式算法,在给出近似最优解的情形下,往往能比保证给出最优解的算法(如:Dijkstra 算法)具有更快的执行速率和更优秀的空间消耗。不同的启发式算法可以令 A* 算法具有各种不同的表现。
理想情况下,A* 亦可以保证给出最优解。

8 数码问题可能有解的条件

首先,我们把去掉空格后的 8 个数看成排列。不难发现:
· 空格左右移动,8 个数构成的排列中,没有任何两个数发生了位置上的交换,逆序数不变。
· 空格上下移动,8 个数构成的排列中,发生 2 次对换,它能通过2次相邻对换等效实现,逆序数不变。
即:初态棋盘对应的排列为奇(偶)排列时,不论空格怎样移动,得到的新棋盘对应的排列依然为奇(偶)排列。
因此,当初态与终态对应的排列的奇偶性不同时,无解。
事实上,有如下充分必要条件:
8 数码问题无解 ⟺ 初态与终态对应的排列的奇偶性不同。
8 数码问题有解 ⟺ 初态与终态对应的排列的奇偶性相同。
篇幅所限,这里不予证明(其实是我不会)。
可以通过改动归并排序算法,在 O(n log ⁡n) 的时间复杂度内求得一个排列的逆序数,n 为排列的长度。算法的正确性证明详见:https://blog.csdn.net/COFACTOR/article/details/109005737
本命题可以推广到更大规模的 15 数码问题、24 数码问题等。

五、源代码

代码解析

输入棋盘的初始状态和目标状态,在验证输入的样例有解后,本代码将依次使用 DFS、BFS 和 A* 算法求解。
本代码使用的 A* 算法是在 BFS 的基础上修改得到的。BFS 总是先扩展深度最浅的状态节点,但 A* 维护一个优先队列,每次选出 f(x) = g(x) + h(x) 最小的节点先行扩展。其中,g(x) 代表从当前状态 x 对应的节点在解答树中的深度;本代码选用当前状态 x与目标状态的 Hamming 距离作为启发式函数 h(x)。
为了避免陷入死循环,本代码还会在搜索过程中通过哈希(使用自定义哈希仿函数的 std::unordered_set 来实现)来判定从根节点到准备扩展的节点的路径上是否有重复的节点。若是,则不再继续扩展此节点。

在 DFS 的过程中,没有显式构造解答树。在 BFS 的过程中,一边构造解答树(树节点为结构体 bfs_node),一边使用队列(std::queue)进行搜索。在 A* 搜索的过程中,同样一边构造解答树(树节点为结构体 astar_node,节点要记录启发式搜索需要的额外信息),一边使用优先队列(std::priority_queue)进行搜索。

代码特色

·支持更大规模的此类问题,例如 15 数码问题和 24 数码问题。可以通过修改代码中预留的常量来求解这些问题。
·支持为棋盘填入不同类型、不同大小的数;可以使用不同的数表示空格,默认为数 0。
  相比之下,许多 8 数码问题的题目或实现代码只能为空格以外的 8 格分别填入 1 ~ 8 这八个整数。
·采用 C++ 风格与 C++11 开始的新特性编写代码。包括但不限于:
	·函数模板(template<class _Ty>)。
	· nullptr 关键字;
	· STL 容器新增的成员函数 emplace、emplace_back;
	·新标准的数据结构,如 std::array、std::unordered_set;
	· C++ 风格的输入输出;
	· C++ 风格的类型别名,如 using board_data_type = unsigned;;
	· C++ 风格的类型转换,如 static_cast<>();
	·高精度计时库 <chrono> 用于进行三种算法的运行耗时的测量。
  增强代码运行性能的同时,也增强可读性。

源代码

#include <algorithm>
#include <array>
#include <chrono>
#include <iostream>
#include <queue>
#include <set>
#include <unordered_set>
#include <vector>

using namespace std;
using namespace std::chrono;

using board_data_type = unsigned;
using board_coord_type = unsigned;
using board_depth_type = size_t;
using board_heuristic_value_type = size_t;

const board_coord_type BOARD_ROW_COUNT = 3, BOARD_COLUMN_COUNT = 3;

using board_column_type = array<board_data_type, BOARD_COLUMN_COUNT>;
using board_type = array<board_column_type, BOARD_ROW_COUNT>;

const board_data_type blank_token = 0;

board_type start, goal;
board_coord_type xs, ys;

struct step {
    
    
	board_type board;
	board_coord_type x, y;
	step() {
    
    }
	step(const board_type& B, const board_coord_type X, const board_coord_type Y) : board(B), x(X), y(Y) {
    
    }
};

inline bool can_up(const board_coord_type x) {
    
    
	if (x > 0) return true;
	return false;
}

inline bool can_down(const board_coord_type x) {
    
    
	if (x < BOARD_ROW_COUNT - 1) return true;
	return false;
}

inline bool can_left(const board_coord_type y) {
    
    
	if (y > 0) return true;
	return false;
}

inline bool can_right(const board_coord_type y) {
    
    
	if (y < BOARD_COLUMN_COUNT - 1) return true;
	return false;
}

inline void up_b(board_type& board, board_coord_type& x, board_coord_type& y) {
    
    
	swap(board[x][y], board[x - 1][y]);
	--x;
}

inline void down_b(board_type& board, board_coord_type& x, board_coord_type& y) {
    
    
	swap(board[x][y], board[x + 1][y]);
	++x;
}

inline void left_b(board_type& board, board_coord_type& x, board_coord_type& y) {
    
    
	swap(board[x][y], board[x][y - 1]);
	--y;
}

inline void right_b(board_type& board, board_coord_type& x, board_coord_type& y) {
    
    
	swap(board[x][y], board[x][y + 1]);
	++y;
}

inline void up_b(step& s) {
    
    
	swap(s.board[s.x][s.y], s.board[s.x - 1][s.y]);
	--s.x;
}

inline void down_b(step& s) {
    
    
	swap(s.board[s.x][s.y], s.board[s.x + 1][s.y]);
	++s.x;
}

inline void left_b(step& s) {
    
    
	swap(s.board[s.x][s.y], s.board[s.x][s.y - 1]);
	--s.y;
}

inline void right_b(step& s) {
    
    
	swap(s.board[s.x][s.y], s.board[s.x][s.y + 1]);
	++s.y;
}

void print_solution(const vector<step>& solution) {
    
    
	cout << "----------------------------------------------------------------" << endl;
	for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i) {
    
    
		for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) cout << solution.cbegin()->board[i][j];
		//cout << '\n';
		cout << ' ';
	}
	for (vector<step>::const_iterator h = solution.cbegin() + 1; h != solution.cend(); ++h) {
    
    
		if (h->x - (h - 1)->x == -1 && h->y - (h - 1)->y == 0) cout << "Up" << endl;
		else if (h->x - (h - 1)->x == 1 && h->y - (h - 1)->y == 0) cout << "Down" << endl;
		else if (h->x - (h - 1)->x == 0 && h->y - (h - 1)->y == -1) cout << "Left" << endl;
		else if (h->x - (h - 1)->x == 0 && h->y - (h - 1)->y == 1) cout << "Right" << endl;
		for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i) {
    
    
			for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) cout << h->board[i][j];
			//cout << '\n';
			cout << ' ';
		}
		//cout << '\n';
		//cout << '\t';
	}
	cout << "\n----------------------------------------------------------------" << endl;
}

struct board_hash {
    
    
	size_t operator()(const board_type& board) const {
    
    
		size_t ret = 0, temp = 0;
		for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i) {
    
    
			for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) {
    
    
				temp += hash<board_data_type>{
    
    }(board[i][j]);
				ret += temp * temp;
			}
		}
		return ret;
	}
};

inline bool equal_by_board_hash(const board_type& board) {
    
    
	static const size_t Hgoal = board_hash{
    
    }(goal);
	if (board_hash{
    
    }(board) != Hgoal) return false;
	return board == goal;
}

bool result;
vector<step> solution;
//set<board_type> container_for_qsearch;
unordered_set<board_type, board_hash> container_for_qsearch;

bool dfs_solve() {
    
    
	if (solution.back().board == goal) return true;
	//if (equal_by_board_hash(solution.back().board) == true) return true;
	if (can_up(solution.back().x)) {
    
    
		step t = solution.back();
		up_b(t);
		if (container_for_qsearch.emplace(t.board).second == true) {
    
    
			solution.emplace_back(t);
			if (dfs_solve() == true) return true;
		}
	}
	if (can_down(solution.back().x)) {
    
    
		step t = solution.back();
		down_b(t);
		if (container_for_qsearch.emplace(t.board).second == true) {
    
    
			solution.emplace_back(t);
			if (dfs_solve() == true) return true;
		}
	}
	if (can_left(solution.back().y)) {
    
    
		step t = solution.back();
		left_b(t);
		if (container_for_qsearch.emplace(t.board).second == true) {
    
    
			solution.emplace_back(t);
			if (dfs_solve() == true) return true;
		}
	}
	if (can_right(solution.back().y)) {
    
    
		step t = solution.back();
		right_b(t);
		if (container_for_qsearch.emplace(t.board).second == true) {
    
    
			solution.emplace_back(t);
			if (dfs_solve() == true) return true;
		}
	}
	container_for_qsearch.erase(solution.back().board);
	solution.pop_back();
	return false;
}

struct bfs_node : public step {
    
    
	bfs_node* parent;
	array<bfs_node*, 4> child;
	bfs_node() {
    
    }
	bfs_node(const board_type& B, const board_coord_type X, const board_coord_type Y) : step(B, X, Y), parent(nullptr), child({
    
     nullptr,nullptr,nullptr,nullptr }) {
    
    }
	bfs_node(const board_type& B, const board_coord_type X, const board_coord_type Y, bfs_node* const ParentPtr) : step(B, X, Y), parent(ParentPtr), child({
    
     nullptr,nullptr,nullptr,nullptr }) {
    
    }
	bfs_node(const step& s) : step(s.board, s.x, s.y), parent(nullptr), child({
    
     nullptr,nullptr,nullptr,nullptr }) {
    
    }
	bfs_node(const step& s, bfs_node* const ParentPtr) : step(s.board, s.x, s.y), parent(ParentPtr), child({
    
     nullptr,nullptr,nullptr,nullptr }) {
    
    }
};

bool bfs_solve() {
    
    
	queue<bfs_node*> q;
	bfs_node* t = new bfs_node(start, xs, ys);
	q.emplace(t);
	container_for_qsearch.emplace(t->board);
	while (q.empty() == false) {
    
    
		t = q.front();
		q.pop();
		if (t->board == goal/*equal_by_board_hash(solution.back().board) == true*/) {
    
    
			bfs_node* p = t;
			while (p != nullptr) {
    
    
				solution.emplace_back(*p);
				p = p->parent;
			}
			reverse(solution.begin(), solution.end());
			return true;
		}
		if (can_up(t->x)) {
    
    
			step u = *t;
			up_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[0] = new bfs_node(u, t);
				q.emplace(t->child[0]);
			}
		}
		if (can_down(t->x)) {
    
    
			step u = *t;
			down_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[1] = new bfs_node(u, t);
				q.emplace(t->child[1]);
			}
		}
		if (can_left(t->y)) {
    
    
			step u = *t;
			left_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[2] = new bfs_node(u, t);
				q.emplace(t->child[2]);
			}
		}
		if (can_right(t->y)) {
    
    
			step u = *t;
			right_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[3] = new bfs_node(u, t);
				q.emplace(t->child[3]);
			}
		}
	}
	return false;
}

struct astar_node : public bfs_node {
    
    
	board_depth_type depth;
	astar_node() {
    
    }
	astar_node(const board_type& B, const board_coord_type X, const board_coord_type Y) : bfs_node(B,X,Y), depth(0) {
    
    }
	astar_node(const board_type& B, const board_coord_type X, const board_coord_type Y, bfs_node* const ParentPtr) : bfs_node(B, X, Y, ParentPtr), depth(0) {
    
    }
	astar_node(const board_type& B, const board_coord_type X, const board_coord_type Y, bfs_node* const ParentPtr, const size_t Depth) : bfs_node(B, X, Y, ParentPtr), depth(Depth) {
    
    }
	astar_node(const step& s) : bfs_node(s), depth(0) {
    
    }
	astar_node(const step& s, bfs_node* const ParentPtr) : bfs_node(s, ParentPtr), depth(0) {
    
    }
	astar_node(const step& s, bfs_node* const ParentPtr, const size_t Depth) : bfs_node(s, ParentPtr), depth(Depth) {
    
    }
};

board_heuristic_value_type heuristic(const board_type& current) {
    
    
	size_t ret = 0;
	for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i)
		for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j)
			if (current[i][j] != goal[i][j]) ++ret;
	return ret;
}

struct astar_node_cmp {
    
    
	bool operator()(const astar_node* const a, const astar_node* const b) const {
    
     return a->depth + heuristic(a->board) > b->depth + heuristic(b->board); }
};

bool astar_solve() {
    
    
	priority_queue<astar_node*, vector<astar_node*>, astar_node_cmp> q;
	astar_node* t = new astar_node(start, xs, ys, 0);
	q.emplace(t);
	container_for_qsearch.emplace(t->board);
	while (q.empty() == false) {
    
    
		t = q.top();
		q.pop();
		if (t->board == goal/*equal_by_board_hash(solution.back().board) == true*/) {
    
    
			bfs_node* p = t;
			while (p != nullptr) {
    
    
				solution.emplace_back(*p);
				p = p->parent;
			}
			reverse(solution.begin(), solution.end());
			return true;
		}
		if (can_up(t->x)) {
    
    
			astar_node u = *t;
			up_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[0] = new astar_node(u, t, u.depth + 1);
				q.emplace(static_cast<astar_node*>(t->child[0]));
			}
		}
		if (can_down(t->x)) {
    
    
			astar_node u = *t;
			down_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[1] = new astar_node(u, t, u.depth + 1);
				q.emplace(static_cast<astar_node*>(t->child[1]));
			}
		}
		if (can_left(t->y)) {
    
    
			astar_node u = *t;
			left_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[2] = new astar_node(u, t, u.depth + 1);
				q.emplace(static_cast<astar_node*>(t->child[2]));
			}
		}
		if (can_right(t->y)) {
    
    
			astar_node u = *t;
			right_b(u);
			if (container_for_qsearch.emplace(u.board).second == true) {
    
    
				t->child[3] = new astar_node(u, t, u.depth + 1);
				q.emplace(static_cast<astar_node*>(t->child[3]));
			}
		}
	}
	return false;
}

array<board_data_type, BOARD_ROW_COUNT* BOARD_COLUMN_COUNT> row_as_start_board, row_as_goal_board;

template<class _Ty> size_t merge(_Ty* const _begin, _Ty* const _mid, _Ty* const _end) {
    
    // This function will modify the involved array.
	const auto l = _end - _begin + 1; size_t tau = 0;
	_Ty* const t = new _Ty[l], * i = _begin, * j = _mid + 1, * k = t;
	while (i <= _mid && j <= _end) {
    
    
		if (*i <= *j) {
    
     *k = *i; ++i; ++k; }
		else {
    
     *k = *j; ++j; ++k; tau += _mid - i + 1; }
	}
	while (i <= _mid) {
    
     *k = *i; ++i; ++k; }
	while (j <= _end) {
    
     *k = *j; ++j; ++k; }
	std::copy(t, t + l, _begin);
	delete[] t;
	return tau;
}

template<class _Ty> size_t inversion_count(_Ty* const _begin, _Ty* const _end) {
    
    // This function will modify the involved array.
	size_t tau = 0;
	if (_begin < _end) {
    
    
		_Ty* const _mid = _begin + (_end - _begin) / 2;
		tau += inversion_count(_begin, _mid);
		tau += inversion_count(_mid + 1, _end);
		tau += merge(_begin, _mid, _end);
	}
	return tau;
}

time_point<high_resolution_clock, nanoseconds> t[2];

int main() {
    
    
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);

	for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i)
		for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) {
    
    
			cin >> start[i][j];
			row_as_start_board[i * BOARD_COLUMN_COUNT + j] = start[i][j];
			if (start[i][j] == blank_token) {
    
     xs = i; ys = j; }
		}

	for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i)
		for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) {
    
    
			cin >> goal[i][j];
			row_as_goal_board[i * BOARD_COLUMN_COUNT + j] = goal[i][j];
		}

	if (inversion_count(&row_as_start_board[0], &row_as_start_board[BOARD_ROW_COUNT * BOARD_COLUMN_COUNT - 1]) % 2 
		!= inversion_count(&row_as_goal_board[0], &row_as_goal_board[BOARD_ROW_COUNT * BOARD_COLUMN_COUNT - 1]) % 2) {
    
    
		cout << "No solution." << endl;
		return 0;
	}

	t[0] = high_resolution_clock::now();
	solution.emplace_back(start, xs, ys);
	container_for_qsearch.emplace(start);
	result = dfs_solve();
	t[1] = high_resolution_clock::now();
	
	cout << "<DFS> Time elapsed: " << duration_cast<milliseconds>(t[1] - t[0]).count() << " ms." << endl;
	cout << "<DFS> Has a solution: "<< boolalpha << result << endl;
	cout << "<DFS> The number of steps: " << solution.size() - 1 << endl;
	//print_solution(solution);
	cout << endl;

	solution.clear();
	container_for_qsearch.clear();

	t[0] = high_resolution_clock::now();
	result = bfs_solve();
	t[1] = high_resolution_clock::now();
	
	cout << "<BFS> Time elapsed: " << duration_cast<milliseconds>(t[1] - t[0]).count() << " ms." << endl;
	cout << "<BFS> Has a solution: " << boolalpha << result << endl;
	cout << "<BFS> The number of steps: " << solution.size() - 1 << endl;
	print_solution(solution);
	cout << endl;

	solution.clear();
	container_for_qsearch.clear();

	t[0] = high_resolution_clock::now();
	result = astar_solve();
	t[1] = high_resolution_clock::now();

	cout << "<A*> Time elapsed: " << duration_cast<milliseconds>(t[1] - t[0]).count() << " ms." << endl;
	cout << "<A*> Has a solution: " << boolalpha << result << endl;
	cout << "<A*> The number of steps: " << solution.size() - 1 << endl;
	print_solution(solution);
	cout << endl;

	return 0;
}

六、样例与实验结果

样例

样例 00 1 2 3 4 5 6 8 7
0 1 2 3 4 5 6 7 8

样例 17 2 4 5 0 6 8 3 1
0 1 2 3 4 5 6 7 8

样例 2(洛谷 P1379):
2 8 3 1 0 4 7 6 5
1 2 3 8 0 4 7 6 5

样例 0 无解,1、2 有解。

结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于 DFS 给出的解太长,因此这里不进行展示。
BFS 给出了步数最少的解(最优解可以不唯一)。A* 在执行 2 个测试用例时,也给出了最优解。

时空复杂度

测试方法:
在 Release 模式(-O2)下,依次注释掉 main 函数里的其它算法的代码,且不打印解,只运行单个算法,在 main 函数的返回语句(return 0;)处设置断点。在断点中断后,于任务管理器(taskmgr)中查看由本代码编译而来的进程的内存占用大小。
测试使用样例 1。
在这里插入图片描述

结论

对于样例,当找到第一个解就返回时,DFS 不能保证给出最优解;只有遍历完整棵解答树以后,才能确定获得的所有解中,哪个是最优解。
由于 BFS 本身的特性,BFS 最先给出的深度相同的若干个解都是最优解。本实验仅展示第一个解。
DFS 的运行耗时可以远远小于 BFS(样例 1),也可以反过来(样例 2),但这都只是特例。理论上,在最坏情况下,两者都有可能需要完全遍历解答树以后,才能给出一个可行解或一个最优解。
A* 虽然也无法保证给出最优解,但其在时空复杂度方面的表现比无信息搜索的BFS和DFS要更优秀。这也符合预期。
综上:实验结果均符合预期,实验成功。

七、算法的进一步改进方案(简述)

双向搜索

从起点和终点同时开始进行 DFS 或 BFS。如果相遇,那么获得了可行解。
作为无信息的暴力搜索,DFS 的时间复杂度达到搜索树的最大深度的指数级,BFS 的时空复杂度均达到最优解所在深度的指数级。双向搜索能让指数减半,极大降低时空复杂度。

迭代加深搜索

主要是 IDDFS 和 IDA*。
迭代加深搜索的本质还是深度优先搜索,只不过在搜索的同时带上了深度 d 作为参数,当达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度 + 1,重新从根开始。
使用 IDDFS 可以极大降低空间复杂度。虽然它会重复搜索浅层的状态,但在已知浅层存在解时,一般地,其时间复杂度和空间复杂度分别将显著优于 DFS 和 BFS,且空间复杂度与 DFS 相当、时间复杂度不比 BFS 差太多。如使用 IDA*,时空复杂度的表现往往更优。

更优秀的启发式函数

在 A* 算法中,启发式函数决定 A* 算法的行为。
优秀的启发式函数,可以使得付出相对较少的代价就能找到尽量接近最优的解。一个不对不同的输入产生太多相同的值的启发式函数,有利于让 A* 算法扩展的总节点数更少。

但在实验过程中,将使用的启发式函数由 Hamming 距离改为 Manhattan 距离后,A* 算法的耗时反而大大增加,从 30 多 ms 陡增至 200 到 300 ms,原因目前还不明确。因此暂时继续使用当前棋盘与目标棋盘的 Hamming 距离作为启发式函数。

哈希判重

最初,使用的是有序集合 std::set 来判定当前正在搜索的路径中是否有重复的节点。将当前路径中的节点改为存入无序集合 std::unordered_set 并自定义哈希仿函数后,三种算法的性能提升均达到大约 50%。
但若在搜索过程中进一步使用哈希来判定棋盘与目标棋盘相符(即已经搜索到目标状态)而不是使用 std::array 已经重载的 == 运算符来逐个比较棋盘的每个位置,耗时确又数倍增长,原因同样不明。因为理论上棋盘的每个位置的值可以看成字符串的字符,而棋盘的状态就可以看成字符串,用哈希比较字符串是否相等应当比逐个比较快得多。初步猜测,可能是因为作为 C++ STL 的 std::unordered_set 额外使用了大量的优化技巧。

八、附录:(n^2 - 1) 数码问题控制台简易演示程序

在控制台中显示当前的棋盘状况和累计进行的操作(上下左右),按 Backspace 退格。
支持更大规模的此类问题,如 15 数码问题和 24 数码问题。
支持为棋盘填入不同类型、不同大小的数(或字符);可以使用不同的数表示空格。
本演示程序仅支持 Windows 平台。

#include <algorithm>
#include <conio.h>
#include <iostream>
#include <string>
#include <tuple>

using namespace std;

const size_t board_row_count = 3;
const size_t board_column_count = 3;
const size_t board_size = board_row_count * board_column_count + 1;

using board_data_type = char;

//board_data_type board[] = {"wxyz00ijkt12 34pqruv*****"};
board_data_type board[] = {
    
    " xy000abc"};
//tuple<size_t, size_t> coord = { 2,2 };
tuple<size_t, size_t> coord = {
    
     1,1 };
string opseq;

int c;

inline bool up(char* const board, const size_t column, tuple<size_t, size_t>& pos) {
    
    
	if (get<0>(pos) > 0) {
    
    
		swap(board[(get<0>(pos) - 1) * column + get<1>(pos)], board[get<0>(pos) * column + get<1>(pos)]);
		--get<0>(pos);
		return true;
	}
	else return false;
}

inline bool down(char* const board, const size_t row, const size_t column, tuple<size_t, size_t>& pos) {
    
    
	if (get<0>(pos) < row - 1) {
    
    
		swap(board[(get<0>(pos) + 1) * column + get<1>(pos)], board[get<0>(pos) * column + get<1>(pos)]);
		++get<0>(pos);
		return true;
	}
	else return false;
}

inline bool left(char* const board, const size_t column, tuple<size_t, size_t>& pos) {
    
    
	if (get<1>(pos) > 0) {
    
    
		swap(board[get<0>(pos) * column + get<1>(pos) - 1], board[get<0>(pos) * column + get<1>(pos)]);
		--get<1>(pos);
		return true;
	}
	else return false;
}

inline bool right(char* const board, const size_t column, tuple<size_t, size_t>& pos) {
    
    
	if (get<1>(pos) < column - 1) {
    
    
		swap(board[get<0>(pos) * column + get<1>(pos) + 1], board[get<0>(pos) * column + get<1>(pos)]);
		++get<1>(pos);
		return true;
	}
	else return false;
}

inline void output_board(const char* const board, const size_t row, const size_t column) {
    
    
	for (size_t i = 0; i < row; ++i) {
    
    
		for (size_t j = 0; j < column; ++j) {
    
    
			cout << board[i * column + j] << ' ';
		}
		cout << endl;
	}
}

inline void clear_and_output_board(const char* const board, const size_t row, const size_t column) {
    
    
	system("cls");
	output_board(board, row, column);
}

inline void add_and_output_opseq(const char* const op) {
    
    
	opseq += op;
	cout << opseq << endl;
}

int main() {
    
    
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);

	output_board(board, board_row_count, board_column_count);

	for (;;) {
    
    
		c = _getch();
		if (c == 224) {
    
    
			switch (_getch()) {
    
    
			case 72:
				if (up(board, board_column_count, coord) == true) {
    
    
					clear_and_output_board(board, board_row_count, board_column_count);
					add_and_output_opseq("U ");
				}
				continue;
			case 80:
				if (down(board, board_row_count, board_column_count, coord) == true) {
    
    
					clear_and_output_board(board, board_row_count, board_column_count);
					add_and_output_opseq("D ");
				}
				continue;
			case 75:
				if (left(board, board_column_count, coord) == true) {
    
    
					clear_and_output_board(board, board_row_count, board_column_count);
					add_and_output_opseq("L ");
				}
				continue;
			case 77:
				if (right(board, board_column_count, coord) == true) {
    
    
					clear_and_output_board(board, board_row_count, board_column_count);
					add_and_output_opseq("R ");
				}
				continue;
			default:
				continue;
			}
		}
		else if (c == '\b') {
    
    
			if (opseq.empty() == false) {
    
    
				opseq.pop_back();
				c = opseq.back();
				switch (c) {
    
    
				case 'U':
					down(board, board_row_count, board_column_count, coord);
					break;
				case 'D':
					up(board, board_column_count, coord);
					break;
				case 'L':
					right(board, board_column_count, coord);
					break;
				case 'R':
					left(board, board_column_count, coord);
					break;
				}
				clear_and_output_board(board, board_row_count, board_column_count);
				opseq.pop_back();
				cout << opseq << endl;
			}
		}
		//cout << c << endl;
	}

	return 0;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/COFACTOR/article/details/116917974