栈&队列解决迷宫问题

1,042 阅读5分钟

栈解决迷宫问题

这种解决思路类似DFS,从起点开始向前走,每走一步,就向四周探寻可走方向,如果找到一个可走方向,首先“保存现场”(将走向的是哪一个方向保存下来),再向找到的方向走一步,重复上述过程。同时对走过的路上做一个标记,防止形成死循环。

如果发现走到了死胡同,四周都无路可走,则回退到上一步,“恢复现场”(从保存的方向开始,看下一个方向是否可走),如果还是死胡同,再回退。若回退到起点(栈已经pop到没有元素了),则无解。

如果走着走着发现走到了终点,则找到了迷宫的一个解,此时将栈内元素依次出栈,并存储到一个数组内,再逆序输出数组就得到了我们想要的一条路径。

这个算法找出来的可能并不是最优解。但是可以在找到一条路径以后,再回到上一步,继续上面的步骤就可以输出所有解。

算法实现如下,mg二维数组用来存储迷宫,0:可走,1:不可走,-1:走过了。

注意,在回退到上一步的时候要把该店对应mg数组的值还原为0,不要影响其他路的寻找。 由于回溯到上一步以后会从下一个可能的方向开始走,不会再回到还原为0的这一步,所以不用担心会形成死循环。

可以到我的GitHub查看完整源码:simple-math/mgpath

#pragma once
#include "SqStack4MG.h"                   //为本算法服务的数据结构
#include "MG.h"                           //迷宫数组
#include <iostream>
using namespace std;
bool mgpath(int xi, int yi, int xe, int ye)
{
	Box path[MaxSize], e;
	int i, j, di, i1, j1, k;
	bool find;
	StType* st;
	InitStack(st);
	e.i = xi; e.j = yi; e.direction = -1;
	Push(st, e);
	mg[xi][yi] = -1;
	while (!StackEmpty(st))
	{
		GetTop(st, e);
		i = e.i; j = e.j; di = e.direction;
		if (i == xe && j == ye)
		{
			cout << "迷宫路径如下:" << endl;
			k = 0;
			while (!StackEmpty(st))
			{
				Pop(st, e);
				path[k++] = e;
			}
			int n = 0;
			while (k >= 1)
			{
				k--; n++;
				cout << "\t" << path[k].i << ',' << path[k].j;
				if (n % 5 == 0) cout << endl;
			}
			cout << endl;
			DestroyStack(st);
			return true;
		}
		find = false;
		while (di < 4 && !find)
		{
			di++;
			switch (di)
			{
			case 0:i1 = i - 1; j1 = j; break;
			case 1:i1 = i; j1 = j + 1; break;
			case 2:i1 = i + 1; j1 = j; break;
			case 3:i1 = i; j1 = j - 1; break;
			}
			if (mg[i1][j1] == 0)find = true;
		}
		if (find)
		{
			st->data[st->top].direction = di;
			e.i = i1; e.j = j1; e.direction = -1;
			Push(st, e);
			mg[i1][j1] = -1;
		}
		else
		{
			Pop(st, e);
			mg[e.i][e.j] = 0;
		}
	}
	DestroyStack(st);
	return false;
}

队列解决迷宫问题

这一算法思路很想BFS,同时结果也是最简路径,和BFS如出一辙。

寻找路径的思路和栈的解决方法最大的不同有两处:

  • 不再需要一个方向一个方向地去试探有没有可走方块,而是直接将所有可走的方块enQueue(进队列),同时为队列中的每一个元素增加一个变量:pre用来指向上一步的方块,存储“我是怎么走过来的?”这一信息,再将这些方块在mg数组中对应的值置为-1,避免死循环。这样一来入队速度就会远快于出队(<=3倍)这样一来,就相当于直接将所有路径信息全部存储到队列内,然后慢慢deQueue(出队列),这时,每出一个元素,就看看他是不是终点?如果是,皆大欢喜,游戏结束,顺"藤"(我是怎么走过来的)摸瓜输出这条路径。如果不是,就继续deQueue,如果这个迷宫有解,那么终点一定已经存储进来了,就一定会找到。
  • 用队列存储路径,在找到完整的路径以后如何输出的问题。顺藤摸瓜,“摸的过程中”对摸过的每一个结点都标记一下(比如将pre的值置为-1(正常情况下pre不会变为-1,而是0和一系列正整数)),如果摸着摸着,突然一个结点的pre是0,就说明摸到“根”了,然后再用一个数组从“根”开始慢慢将所有标记的结点存起来,就得到了完整的迷宫路径了,而且由于此时是第一个找到终点的路径,所有它一定是最短的路径! 但是由于顺藤摸瓜的过程中破坏了队列中的pre存储结构,所以难以再输出其他路径了,当然你可以采取其他方法不破化pre的方法输出所有路径,所有路径都存在队列里了,所以能全部输出也是必然的!

部分代码如下,可以到我的GitHub查看完整源码:mgpath

#pragma once
#include "QuType.h"
#include "MG.h"
#include <iostream>
using namespace std;


void print(QuType* qu, int front)
{
	int k = front, j, ns = 0;
	cout << endl;
	do
	{
		j = k;
		k = qu->data[k].pre;
		qu->data[j].pre = -1;
	} while (k != 0);
	cout << "迷宫路径如下" << endl;
	k = 0;
	while (k < MaxSize)
	{
		if (qu->data[k].pre == -1)
		{
			ns++;
			cout << "\t" << qu->data[k].i << ',' << qu->data[k].j;
			if (ns % 5 == 0) cout << endl;
		}
		k++;
	}
	cout << endl;
}

bool mgpath(int xi, int yi, int xe, int ye)
{
	Box e;
	int i, j, di, i1, j1;
	QuType* qu;
	InitQueue(qu);
	e.i = xi, e.j = yi, e.pre = -1;
	enQueue(qu, e);
	mg[xi][yi] = -1;
	while (!QueueEmpty(qu))
	{
		deQueue(qu, e);
		i = e.i; j = e.j;
		if (i == xe && j == ye)
		{
			print(qu, qu->front);
			DestroyQueue(qu);
			return true;
		}
		for (di = 0; di < 4; di++)
		{
			switch (di)
			{
			case 0:i1 = i - 1; j1 = j; break;
			case 1:i1 = i; j1 = j + 1; break;
			case 2:i1 = i + 1; j1 = j; break;
			case 3:i1 = i; j1 = j - 1; break;
			}
			if (mg[i1][j1] == 0)
			{
				e.i = i1; e.j = j1;
				e.pre = qu->front;
				enQueue(qu, e);
				mg[i1][j1] = -1;
			}
		}
	}
	DestroyQueue(qu);
	return false;
}