在一个 N * M 的网格中,每个格子的坐标为(x, y),从一个entry(迷宫入口) 开始到所有exit(迷宫出口可能有多个)的路径
创建一个二维数组 int[][] grid; 作为迷宫数据
grid[x][y] == 0 或者 出界的节点 视为路(可走)
grid[x][y] == 1 视为障碍物(不可走)
思路:利用回溯算法思想和剪枝函数来完成路径搜索
1.初始化路径:路径中节点个数为0,从入口格子节点开始,加入到路径中,并记录入口节点已经加入过路径中了。
2.获取路径中最后一个加入的节点X(该节点继续保存在路径中),
--------如果节点X是出口则成功搜索到一条路径了。将路径保存。
----------------如果只要求找到一条路径就算成功,则退出。
----------------如果要求找到所有能走出的路径,则继续执行下面逻辑
--------遍历节点X的上A、下B、左C、右D四个节点,不可走的节点或者之前加入过路径中的节点跳过(此为剪枝函数)
----------------如果节点可走,则将节点加入到路径中,并记录该节点已经加入过路径,GoTo 到 2 继续深度优先遍历
--------如果节点X的上、下、左、右 四个节点都没有可走的节点或者之前都添加到路径中,则从节点X经过无法到达出口,则应该回溯到节点X的上一个节点继续查找(此为回溯),如何回溯?回溯方法:将节点X从路径中移除,路径中最后一个就是节点X的上一个节点了。 GoTo到 2 继续深度优先遍历
3.到此从入口开始的所有能走通的路径都已经获取到了
上面 3 条逻辑用白话说就是:从入口进去,前后左右只要是能往前走就一直走(已经走过的路不能重复走),如果没有路可走了就往回退,直到退到一个能向其他方向(没做过的方向)走的点,继续向前走,最终一定能走到出口
下面是使用递归和迭代两种方式实现的迷宫寻路算法
using System.Collections.Generic;
public class Node
{
public Node parent;
public int index;
public Node(int index)
{
this.index = index;
}
public Node(int index, Node parent)
{
this.index = index;
this.parent = parent;
}
}
// 寻路算法逻辑实现方式
public enum SearchType
{
///
/// 递归实现
///
RECURSION = 1,
///
/// 迭代实现
///
ITERATION = 2,
}
public class LabyrinthLogic
{
private int[][] _grid;
private int _entryRow = 0;
private int _entryCol = 0;
private HashSet<int> hash = new HashSet<int>();
private List<List<int>> resultList = new List<List<int>>();
int[][] dir = new int[][] {
new int[]{-1, 0}, // 上
new int[]{ 1, 0}, // 下
new int[]{ 0,-1}, // 左
new int[]{ 0, 1} // 右
};
public List<List<int>> ResultList
{
get { return resultList; }
}
public bool FindPath(int[][] grid, int entryRow, int entryCol, SearchType type)
{
_grid = grid;
_entryRow = entryRow;
_entryCol = entryCol;
hash.Clear();
resultList.Clear();
if (type == SearchType.RECURSION)
{
dfs(_grid.Length, _entryRow, _entryCol, new List<int>());
}
else if (type == SearchType.ITERATION)
{
dfs(_grid.Length, _entryRow, _entryCol);
}
return resultList.Count > 0;
}
#region 递归实现
// 递归实现
private void dfs(int n, int row, int col, List<int> list)
{
if (IsExit(row, col, n))
{
// 找到出口了,保存路径
resultList.Add(new List<int>(list));
return;
}
// 节点的上下左右节点中搜索
for (int i = 0; i < dir.Length; ++i)
{
int newRow = row + dir[i][0];
int newCol = col + dir[i][1];
int index = newRow * n + newCol;
// 剪枝函数
// 已经搜索过的节点不重复添加(避免死循环)
// 障碍物节点不能通过
if (hash.Contains(index) || !Valid(newRow, newCol, n))
{
continue;
}
// 记录已经搜索过的节点
hash.Add(index);
// 将可以通过的节点保存
list.Add(index);
dfs(n, newRow, newCol, list);
// 回溯
list.RemoveAt(list.Count - 1);
}
}
#endregion
#region 迭代实现
private void dfs(int n, int row, int col)
{
int index = row * n + col;
Node node = new Node(index);
Stack<Node> stack = new Stack<Node>();
stack.Push(node);
while (stack.Count > 0)
{
node = stack.Pop();
index = node.index;
row = index / n;
col = index % n;
if (IsExit(row, col, n))
{
List<int> list = new List<int>();
while (null != node)
{
list.Insert(0, node.index);
node = node.parent;
}
resultList.Add(list);
continue;
}
for (int i = dir.Length - 1; i >= 0; --i)
{
int newRow = row + dir[i][0];
int newCol = col + dir[i][1];
index = newRow * n + newCol;
if (!Valid(newRow, newCol, n) || hash.Contains(index))
{
continue;
}
// 子节点设置父节点
Node childNode = new Node(index, node);
hash.Add(index);
stack.Push(childNode);
}
}
}
#endregion
// 是否有效节点,在地图范围内且节点值为0 的为有效节点
private bool Valid(int row, int col, int n)
{
return row >= 0 && row < n && col >= 0 && col < n && _grid[row][col] == 0;
}
// 是否出口
private bool IsExit(int row, int col, int n)
{
return !IsEntry(row, col) && (row <= 0 || row >= n - 1 || col <= 0 || col >= n - 1);
}
// 是否入口
private bool IsEntry(int row, int col)
{
return (row == _entryRow && col == _entryCol);
}
}
寻路结果动画展示
Unity项目工程下载地址迷宫项目所在路径 PathfindingAlgorithm\Labyrinth如果没有使用过Unity的看上边 C# 代码逻辑即可,Unity项目只是辅助展示。
该代码是一个利用回溯算法是实现的没有做优化处理的Demo,对于小地图是没有问题的,当地图过大如 10000 X 10000 上面代码效率会极其低下,甚至会堆栈溢出崩溃