广度优先算法解决8数码问题【c++】

8数码问题 (广度优先算法解决----c++)

8数码问题问题描述

八数码问题也称为九宫问题,在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格(空格用0来表示),空格周围的棋子可以移到空格中。
在这里插入图片描述
要求解的问题是:
给出一种初始布局(初始状态)和目标布局(目标状态),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
在这里插入图片描述

问题分析

八数码问题的核心在于移动棋子使其得到需要的状态分布(即目标状态),为便于控制操作,我们将每次移动棋子的过程都视为标号为0的棋子与其相邻棋子的位置交换操作。(如下图,2移动到0,和0移动到2,进行了一样的操作,交换0和2的位置)
在这里插入图片描述

我们以空格(0号棋子)为操作对象,将其与周围棋子交换位置。求解棋子移动过程的问题可以转化为求空白格(0号棋)移动过程。(如下图1,0可以和他周围的2,8,6,4交换位置,图2,0可以和他周围的1,2,3交换位置,图3,0可以和他周围的1,8交换位置,)

在这里插入图片描述
0号棋子移动过程只涉及到上移,下移,左移,右移四条法则。根据移动法则,从初始状态出发可以建立搜索树,求解问题其实就是求解最短路径问题。
在这里插入图片描述

广度优先算法

求解最短路径的问题考虑使用广度优先搜索算法(BFS,其英文全称是Breadth First Search。),所有因为展开节点而得到的子节点都会被加进一个先进先出的队列中。一般的实验里,其邻居节点尚未被检验过的节点会被放置在一个被称为 open 的容器中(例如队列或是链表),而被检验过的节点则被放置在被称为 closed 的容器中。(open-closed表)

1.算法执行过程
在这里插入图片描述
2.为算法实现的概念图:(黑色加粗数字为节点拓展顺序)
在这里插入图片描述
3.算法的优点:
广度优先搜索算法对于解决最短路径问题非常有效,搜索深度较小,每个节点只访问一次,节点总是以最短的结点总是以最短路径被访问,所以第二次路径确定不会比第一次短。

4.算法的缺点
广度优先搜索算法坚决最短路径问题虽然非常有效,但是解决问题需要非常庞大的内存空间,当路径较长或者节点过多时,会有非常巨大的内存开销,并且运行时间会非常长。
坚决方案:当节点数达到一定值时,默认该问题无解,停止算法。

编程环境及语言

语言:c++
环境:vs2019
在这里插入图片描述

代码实现

定义相关节点结构体:
在这里插入图片描述

定义相关变量和函数:
在这里插入图片描述

核心函数实现:
在这里插入图片描述

核心函数核心算法实现:
在这里插入图片描述

算法设置目标状态:
在这里插入图片描述

代码详情

/*
==============================================================
                   广度优先算法解决8数码问题                
==============================================================

输入示例: 1_3_2_4_5_7_6_8_0[回车]   (下划线表示空格)
对应的8数码九宫格状态    |1|3|2|                           
                        |4|5|7|                           
                        |6|8|0|  
						
		目标状态		    |1|2|3|
						|4|5|6|
						|7|8|0|

                【 0 表示九宫格的空白格 】
==============================================================


*/
#include <iostream>
using namespace std;

#define MAX_OPEN_LEN 50000
#define MAX_CLOSE_LEN 50000

struct Snode//节点结构体
{
	int parent;  //指向该结点父节点的编号
	int map[9];
public:
	void In(const int* d);
	void Out(int* d);
};
void Snode::In(const int* d)
{

	for (int i = 0; i < 9; ++i)
		map[i] = d[i];
}

Snode OPEN[MAX_OPEN_LEN];//OPEN表
int op = 0;
Snode CLOSE[MAX_CLOSE_LEN];//close表
int cp = 0;                //扩展的节点数
int result[50000][9];     //result数组用于保存路径


int YONNEW(Snode& , Snode& );     //判断是否为新节点        返回值 1: 是  0: 不是
int FIND(const int* );         //广度优先运行函数   
inline void  CHANGE(int& , int& );//交换俩个数              
int JODER(Snode& );        //判断节点是否为目标节点  返回值 1: 是  0: 不是

int main(void)
{
	int m[9] = {0};//初始化
	cout << "==============================================================" << endl;
	cout << "                    广度优先算法解决8数码问题                 " << endl;
	cout << "==============================================================" << endl;
	cout << "示例: 1_3_2_4_5_7_6_8_0[回车]   (下划线表示空格)          "  << endl;
	cout << endl;
	cout << "对应的8数码九宫格状态   |1|3|2|  目标状态:  |1|2|3|          " << endl;
	cout << "                        |4|5|7|              |4|5|6|          " << endl;
	cout << "                        |6|8|0|              |7|8|0|          " << endl;
	cout << endl;
	cout << "【注意】工程生产节点多于10000个视为无解                       " << endl;
	cout << "==============================================================" << endl;
	cout << "成功案例参考 :[123564780]  [123456780]                           " << endl;

	cout << "失败案例参考 :[756482310]  [156234780] " << endl;
	
	cout << "==============================================================" << endl;
	cout << "请按照示例输入8数码初始状态:";

	for (int i = 0; i < 3; i++)//输入
		for (int j = 0; j < 3; j++)
			cin >> m[3 * i + j];

	FIND(m);//运行
	return 0;
}

int YONNEW(Snode& node1, Snode& node2)  //判断是否为新节点
{
	int f = 1;
	for (int i = 0; i < 9; i++)
	{
		if (node1.map[i] != node2.map[i]) f = 0;
	}
	return f;
}

inline void CHANGE(int& a, int& b)//交换俩个数
{
	int t = a;
	a = b;
	b = t;
}

int JODER(Snode& node)//判断节点是否为目标节点
{
	int f = 1;
	int g[9] = { 1,2,3,4,5,6,7,8,0};//目标节点
	for (int i = 0; i < 9; i++)
	{
		if (node.map[i] != g[i])
			f = 0;
	}
	return f;
}

int FIND(const int* d)//运行函数
{
	int begin = 0;                    //begin含义是每次从OPEN表中去除要扩展的那个节点
	int node_number = 1;              //扩展节点数,初始时已有OPEN[0]节点,故为1
	static int dp[4] = { -3,-1,1,3 }; 
	//-3上,3下,-1左,1右

	op = 1;
	cp = 0;
	OPEN[begin].In(d);
	OPEN[begin].parent = -1;  

	//OPEN表不为空
	while (op > 0)
	{
		int i = 0, KONGE, pos, j = 0, k = 0;
		//找目标节点
		if (JODER (OPEN[begin]) == 1)  
		{
			cout << endl;
			cout << endl;
			cout << "==============================================================" << endl;
			cout <<  endl;
			cout << "                    成功得到正确解,路径如下:                " << endl;
			cout << endl;
			cout << "———————————————————————————————" << endl;
			CLOSE[cp] = OPEN[begin];

			//路径存入数组result中,目标节点--->根节点
			while (begin != -1)         
			{
				for (int i = 0; i < 9; i++)
				{
					result[j][i] = OPEN[begin].map[i];
				}
				j = j + 1;
				begin = OPEN[begin].parent;
			}

			//result数组中路径输出,根节点--->目标节点
			for (i = j - 1; i >= 0; i--)       
			{
				for (k = 0; k < 9; k++)
				{
					cout << result[i][k] << " ";
					if (k % 3 == 2)  cout << endl;
				}
				cout << endl;
			}
			cout << "=============================================================="  << endl;
			cout << "生成的节点总数为           ||           " << node_number << endl;
			cout << "扩展的节点总数为:         ||           " << cp << endl;
			cout << "==============================================================" <<  endl;

			return 1;
		}
		for (KONGE = 0; KONGE < 9; ++KONGE)
		{
			if (OPEN[begin].map[KONGE] == 0)
				break;                        //跳出当前for循环,向下执行
		}
		for (i = 0; i < 4; ++i)
		{   //判断空白位置位置怎样可以移动,与上边的判断相同
			if (KONGE == 0 && (i == 0 || i == 1))  continue;     
			if (KONGE == 1 && i == 0)         continue;
			if (KONGE == 2 && (i == 0 || i == 2))  continue;
			if (KONGE == 3 && i == 1)         continue;
			if (KONGE == 5 && i == 2)         continue;
			if (KONGE == 6 && (i == 1 || i == 3))  continue;
			if (KONGE == 7 && i == 3)         continue;
			if (KONGE == 8 && (i == 2 || i == 3))  continue;
			pos = KONGE + dp[i];
			//交换位置
			CHANGE(OPEN[begin].map[KONGE], OPEN[begin].map[pos]); 
			Snode child;
			child.In(OPEN[begin].map);
			//判断是否为新节点
			for (j = 0; j < cp; ++j)      
			{
				if (YONNEW(CLOSE[j], child) == 1)
					break;
			}
			//得到新节点
			if (j == cp)                         
			{
				OPEN[op] = child;             //先使用op,再op加1
				OPEN[op].parent = begin;
				op++;
				node_number++;
				//无解,这里当op等于10000时退出,并返回0
				if (node_number == 10000)                             //无解,这里当op等于10000时退出,并返回0
				{
					cout << endl;
					cout << endl;
					cout << "==============================================================" << endl;
					cout << endl;
					cout << "           警告:                节点过多视为无解             " << endl;
					cout << endl;
					cout << "==============================================================" << endl;
					
					return 0;
				}
			}
			//前边把OPEN[min]的值进行了交换,现在再换回去,保持OPEN[min]的map数组
			CHANGE (OPEN[begin].map[KONGE], OPEN[begin].map[pos]);    
		}
		CLOSE[cp++] = OPEN[begin];
		begin = begin + 1;
	}

	return 0;
}

算法测试

算法输入初始状态:
在这里插入图片描述

测试结果
在这里插入图片描述
在这里插入图片描述
如上图,理论结果与算法结果一致,算法适应性较好。

算法其他运行实例
设置目标状态为:
在这里插入图片描述
1.成功案例
(1)初始状态为 123456780

在这里插入图片描述(2)初始状态为 123564780
在这里插入图片描述
在这里插入图片描述
2.失败案例
(1)初始状态为 123564780
在这里插入图片描述

(2)初始状态为756482310
在这里插入图片描述

心得体会

设计算法解决问题的过程一定要抓住核心,定好操作对象,才能更好的解决问题。从算法到代码实现的过程也是一个复杂的过程,需要用到很多其他的知识储备比如数据结构等等。

希望这篇文章可以帮助刚接触算法的同学更好的理解广度优先算法。同时也希望自己可以在编程这条路上有所成就。

  • 21
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
好的,我可以为您讲解一下如何用C++编写用深度优先算法广度优先算法解决八数码问题八数码问题是一种经典的搜索问题,旨在找到一种移动拼图块的方式,以使得初始状态变为目标状态。其中,每个拼图块只能移动到空格上,而且只能沿横向或纵向移动。这个问题可以使用深度优先算法广度优先算法进行解决。 首先,我们需要定义一个八数码状态的数据结构。一种常见的表示方式是使用一个一维数组来表示状态,其中0表示空格。例如,初始状态[1,2,3,4,0,5,6,7,8]可以表示为: ``` 1 2 3 4 5 6 7 8 ``` 接下来,我们可以使用一个搜索树来表示所有可能的状态。搜索树的根节点为初始状态,每个节点表示一个状态,其子节点是通过移动一块得到的所有可能状态。我们可以使用一个队列来实现广度优先算法,或使用递归函数来实现深度优先算法。 下面是一种使用广度优先算法解决八数码问题C++代码实现: ```c++ #include <iostream> #include <queue> #include <vector> using namespace std; const int N = 9; const int dx[4] = {-1, 0, 1, 0}; const int dy[4] = {0, 1, 0, -1}; struct State { int a[N]; int pos; int steps; }; bool check(const State& s) { for (int i = 0; i < N; i++) { if (s.a[i] != i) return false; } return true; } void print(const State& s) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { cout << s.a[i * 3 + j] << " "; } cout << endl; } cout << endl; } int bfs(const State& start) { queue<State> q; vector<State> vis; q.push(start); vis.push_back(start); while (!q.empty()) { State s = q.front(); q.pop(); if (check(s)) return s.steps; for (int i = 0; i < 4; i++) { int nx = s.pos / 3 + dx[i], ny = s.pos % 3 + dy[i]; if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3) continue; State t = s; swap(t.a[s.pos], t.a[nx * 3 + ny]); t.pos = nx * 3 + ny; t.steps++; bool ok = true; for (int i = 0; i < vis.size(); i++) { if (vis[i].pos == t.pos && vis[i].a == t.a) { ok = false; break; } } if (ok) { q.push(t); vis.push_back(t); } } } return -1; } int main() { State start; for (int i = 0; i < N; i++) { cin >> start.a[i]; if (start.a[i] == 0) start.pos = i; } start.steps = 0; cout << bfs(start) << endl; return 0; } ``` 这个实现中,我们定义了一个State结构体来表示八数码状态,其中a数组存储状态,pos表示空格位置,steps表示移动步数。check函数用于判断是否到达目标状态,print函数用于输出状态。 在bfs函数中,我们使用一个队列q和一个vector vis来实现广度优先搜索。每次从队列中取出一个状态,尝试通过移动一块来得到所有可能的状态,然后将未访问过的状态加入队列中。如果已经访问过,则不再加入队列。 这个实现中,我们使用了一个check函数来判断是否到达目标状态,但实际上也可以在枚举状态时直接判断。另外,我们可以使用一个pre数组来记录状态的前驱节点,以便输出路径。 接下来,我们看一下如何使用深度优先算法解决八数码问题。这个实现中,我们使用了递归函数来实现深度优先搜索。 ```c++ #include <iostream> #include <stack> #include <vector> using namespace std; const int N = 9; const int dx[4] = {-1, 0, 1, 0}; const int dy[4] = {0, 1, 0, -1}; struct State { int a[N]; int pos; int steps; }; bool check(const State& s) { for (int i = 0; i < N; i++) { if (s.a[i] != i) return false; } return true; } void print(const State& s) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { cout << s.a[i * 3 + j] << " "; } cout << endl; } cout << endl; } bool dfs(const State& s, int depth, int limit, vector<State>& vis, stack<State>& path) { if (check(s)) { while (!path.empty()) { print(path.top()); path.pop(); } print(s); return true; } if (depth + s.steps > limit) return false; for (int i = 0; i < 4; i++) { int nx = s.pos / 3 + dx[i], ny = s.pos % 3 + dy[i]; if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3) continue; State t = s; swap(t.a[s.pos], t.a[nx * 3 + ny]); t.pos = nx * 3 + ny; t.steps++; bool ok = true; for (int i = 0; i < vis.size(); i++) { if (vis[i].pos == t.pos && vis[i].a == t.a) { ok = false; break; } } if (ok) { vis.push_back(t); path.push(t); if (dfs(t, depth + 1, limit, vis, path)) return true; path.pop(); } } return false; } int iddfs(const State& start) { vector<State> vis; stack<State> path; for (int limit = 0; ; limit++) { vis.clear(); path.push(start); vis.push_back(start); if (dfs(start, 0, limit, vis, path)) return limit; path.pop(); } return -1; } int main() { State start; for (int i = 0; i < N; i++) { cin >> start.a[i]; if (start.a[i] == 0) start.pos = i; } start.steps = 0; cout << iddfs(start) << endl; return 0; } ``` 这个实现中,我们定义了一个dfs函数来实现深度优先搜索,并使用一个limit参数来控制搜索深度。在dfs函数中,我们首先判断当前状态是否为目标状态,如果是,则输出路径并返回true。否则,如果当前深度加上移动步数已经超过了限制,则返回false。然后,我们枚举所有可能的状态,并判断是否已经访问过,如果未访问过,则递归调用dfs函数。如果递归调用返回true,则表示已经找到了目标状态,否则需要弹出当前状态。 在iddfs函数中,我们使用一个循环来不断增加限制,直到找到目标状态或搜索到最大深度为止。 这个实现中,我们使用了一个vis数组来记录已经访问过的状态,以便避免重复访问。另外,我们使用了一个path栈来记录路径,以便输出路径。在输出路径时,我们可以从栈顶往栈底依次输出状态。 需要注意的是,八数码问题并不是所有情况下都有解的。如果初始状态与目标状态不可达,则算法会一直搜索下去,直到达到限制深度为止。因此,在实际应用中,需要对问题进行判断,以避免无谓的计算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DreamBoy@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值