image.png

第六章 回溯法


::: hljs-center

目录

第六章 回溯法

●前言

●一、回溯法是什么?

1.简要介绍

●二、回溯法经典案例——兔子走迷宫游戏

1.具体情况

2.代码展示(C++)

3.结果展示

●总结

:::

前言

简单的来说,算法就是用计算机程序代码来实现数学思想的一种方法。学习算法就是为了了解它们在计算机中如何演算,以及在当今的信息时代,它们是如何在各个层面上影响我们的日常生活的,从而提高我们的逻辑思维能力和处理实际问题的能力。善用算法、巧用算法,是培养程序设计逻辑的重中之重,许多实际的问题都可用多个可行的算法来解决, 但是要从中找出最优的解决算法却是一项挑战。


一、回溯法是什么?

1.简要介绍

回溯法是枚举法的一种,它是一种可以找出所有解的一般性算法。同时为了避免枚举出现不正确的数值,发现错误值就停止向下一层运行,而是回溯到上一层。为了去减短时间和提高程序代码的执行效率,回溯法一般是一种走不通就退回另寻蹊径的方法。它的特点是在搜索过程中去寻找问题的解,发现不满足的答案就去回溯,从而去避免无效搜索。

二、回溯法经典案例——兔子走迷宫游戏

1.具体情况

假如有一只兔子,我们去把它放到一个没有盖子的大迷宫盒中的起点,迷宫内有很多墙,从而使得大部分路径都被挡住而不能前行。兔子需要采用一种试错的方式去尝试走出迷宫,并且兔子需要在每次走错路时把走过的路记录下来,避免再次重复,直到最终到达终点。即①一次只能走一格;②遇到墙无法前行时回退一步去重新判断;③走过的路不会再走第二次; 在程序中我们仿真迷宫的地图就用二维数组来抽象表示,用0去表示墙,用1表示通路。从而去创建一个12✖12大小的迷宫地图。如下图所示: image.png 兔子将会从起点maze[1][1]进入,从终点maze[9][10]出来,兔子当前的位置我们用maze[x][y]去表示。兔子在迷宫中可以选择的方向有四个,东、南、西、北。但并非每个位置此刻的兔子都可以去任意移动,需根据兔子所处在迷宫中的具体情况而定。从而我们可去创建一个老鼠移动的方位图。如下图所示: image.png 在该过程中我们将会用链表去对兔子走过的路径进行记录,并且将兔子走过的路径标记为2,再将其放入堆栈中,再去进行下一个方向的判断。由于每次每次新加入堆栈中的位置都会在其顶端,因此堆栈顶端指针指向的编号便是当前迷宫中兔子的位置。我们会将主要的算法写到类模块中,它主要用于去判断在当前位置的四个方位上是否存在前进的表格。若找到可前进的方格,则将该方格的编号记录到堆栈并且移动,反之则回溯进行重新的判断。

2.代码展示(C++)

用程序代码去实现具体情况中的兔子走迷宫游戏。

#include<iostream>
using namespace std;
#define north maze[x-1][y]   //北
#define south maze[x+1][y]   //南
#define west maze[x][y-1]    //西
#define east maze[x][y+1]	 //东
int maze[12][12] = { {0,0,0,0,0,0,0,0,0,0,0,0},
					 {0,1,1,1,0,0,0,0,0,0,0,0},
					 {0,0,0,1,0,0,1,1,1,1,0,0},
					 {0,0,0,1,0,0,1,0,0,1,0,0},
					 {0,0,0,1,1,1,1,0,0,1,0,0},
					 {0,0,0,1,0,0,1,0,0,1,0,0},
					 {0,0,0,1,0,0,1,0,0,1,0,0},
					 {0,0,0,1,0,0,1,0,0,1,0,0},
					 {0,0,0,0,0,0,1,0,0,1,0,0},
					 {0,0,1,1,1,1,1,1,0,1,1,0},
					 {0,0,1,0,0,0,0,0,0,0,0,0},
					 {0,0,0,0,0,0,0,0,0,0,0,0} };  //二维数组迷宫图,0表示墙,1表示通路
const int exitx = 9, exity = 10;   //出口端在数组迷宫中的位置,9行10列
 
//链表的定义及其记录使用
struct list {
	int x, y;
	struct list* next;
};
typedef struct list node;
typedef node* link;
link push(link stack,int x,int y)
{
	link newnode;
	newnode = new node;
	if (!newnode)
	{
		return NULL;
	}
	newnode->x = x;
	newnode->y = y;
	newnode->next = stack;
	stack = newnode;
	return stack;
}
link pop(link stack, int *x, int *y)
{
	link top;
	if (stack != NULL)
	{
		top = stack;
		stack=stack->next;
		*x= top->x;
		*y = top->y;
		delete top;
		return stack;
	}
	else
	{
		*x = -1;
	}
	return stack;
}
//
 
int checkexit(int x,int y)  //检查是否到达终点
{
	if (x == exitx && y == exity)
		return 1;
	else
		return 0;
}
 
class maze_data {
public:
	int x;
	int y;
	void walk_judgement()
	{
		link path = NULL;   //初始化路径
		while (x <= exitx && y <= exity)   //while循环中进行东南西北四个方位移动的判断
		{
			maze[x][y] = 2;
			if (north == 1)  //上一格可走
			{
				x--;  //往上走
				path = push(path, x, y);  //加入方格编号到对堆栈
			}
			else if (south == 1)  //下一个可走
			{
				x++;   //往下走
				path = push(path, x, y); //加入方格编号到对堆栈
			}
			else if (west == 1)   //左一格可走
			{
				y--;   //往左走
				path = push(path, x, y); //加入方格编号到对堆栈
			}
			else if (east == 1)    //右一格可走
			{
				y++;   //往右走
				path = push(path, x, y); //加入方格编号到对堆栈
			}
			else if (checkexit(x, y) == 1)  //如果判断到已经到达终点,从而跳出循环
				break; 
			else    //记录走过的位置
			{
				maze[x][y] = 2;
				path = pop(path, &x, &y);
			}
		}
	}
};
void text1()
{
	cout << "-----------------------" << endl;
	for (int i = 0; i < 12; i++)
	{
		for (int j = 0; j < 12; j++)
		{
			cout << maze[i][j] << " ";
		}
		cout << endl;
	}
	cout << "-----------------------" << endl;
}
void text2()
{
	maze_data md;
	md.x = 1;
	md.y = 1;
	md.walk_judgement();
}
int main()
{
	text1();  //输出初始的迷宫矩阵
	text2();  
	text1();  //输出最终老鼠走过路径的迷宫矩阵
}

3.结果展示

image.png (图一) image.png (图二)


总结

在以上我们通过一个兔子走迷宫游戏案例,对回溯法进行了实现。其实回溯法应该很广泛的运用到我们的程序代码中,它是一种很好的算法编程思想。大家也可以对我上面的代码进行二次修改,从而去实现一个更大的迷宫地图、更复杂路径和过程的迷宫小游戏。今天刚好是大年除夕,博主在这里给大家拜年了!祝福大家阖家欢乐,兔年大吉!事业“兔”飞猛进,财富“兔”然斗增!身体蹦蹦跳跳健康平安。

<您的三连和关注是我最大的动力> 🚀 文章作者:Keanu Zhang 分类专栏:算法之美(C++系列文章)