[算法与数据结构] 走迷宫问题(广度与深度优先搜索)

Posted 飞鹰技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[算法与数据结构] 走迷宫问题(广度与深度优先搜索)相关的知识,希望对你有一定的参考价值。

迷宫问题与图遍历

算法简介辅助结构辅助函数深度优先搜索广度优先搜索两种搜索与最短路径

算法简介

我们常遇到的走迷宫问题,其实就是利用图的遍历(Traversing Graph)来求解图的连通性,常见的两种遍历方法:

  • 深度优先搜索(Depth-First Search):类似于树的先根遍历;从某点出发,遍历‘未被访问的’临节点,然后重复此过程(找到一个未访问的临节点N后,然后从此N节点继续查找;即一条路走到黑,然后再去遍历其他的);

  • 广度优先搜索(Breadth-First Search):类似于树的按层遍历;从某点出发,递归遍历所有‘未被访问的’临节点(先遍历所有未被访问的临节点);

辅助结构

以二维数组的形式表示迷宫,1标识可走,0标识禁止,-1标识出口;为方便处理,在外围围上一圈禁止走的0:

 1int[,] map = {
2    {0,0,0,0,0,0,0,0,0,0},
3    {0,1,1,0,1,0,1,1,1,0},
4    {0,1,0,0,1,1,1,0,1,0},
5    {0,1,0,1,1,1,0,1,1,0},
6    {0,1,0,1,0,1,1,1,0,0},
7    {0,1,1,1,0,1,0,1,1,0},
8    {0,1,0,1,0,1,0,1,1,0},
9    {0,1,0,1,0,1,0,0,1,0},
10    {0,0,1,1,1,1,1,1,-1,0},
11    {0,0,0,0,0,0,0,0,0,0}
12};

定义WalkNode表示走过的节点。prev标识上一步经过的节点,以便在找到出口时可以方便的输出经过的路径;dir表示移动方向,当每个方向都遍历后,即搜索完了此节点。

 1public enum Direction
2{
3    Left = 1,
4    Right,
5    Up,
6    Down,
7    None,
8}
9public class WalkNode
10{
11    public int row;
12    public int col;
13    public Direction dir;
14    public WalkNode prev;
15
16    public WalkNode(int row_, int col_, WalkNode pre_=null)
17    
{
18        row = row_;
19        col = col_;
20        dir = Direction.Left;
21        prev = pre_;
22    }
23
24    public void SetPrev(WalkNode pre_)
25    
{
26        this.prev = pre_;
27    }
28
29    public void RestDir()
30    
{
31        dir = Direction.Left;
32    }
33
34    public WalkNode Renew()
35    
{
36        return new WalkNode(row, col);
37    }
38
39    public WalkNode Next()
40    
{
41        if (dir == Direction.None) return null;
42        int nRow = row, nCol = col;
43        switch (dir)
44        {
45            case Direction.Left:
46                --nCol;
47                break;
48            case Direction.Right:
49                ++nCol;
50                break;
51            case Direction.Up:
52                --nRow;
53                break;
54            case Direction.Down:
55                ++nRow;
56                break;
57        }
58        ++dir;
59
60        return new WalkNode(nRow, nCol, this);
61    }
62
63    public override string ToString()
64    
{
65        return string.Format("[{0},{1}]", row, col);
66    }
67}

辅助函数

辅助函数简化遍历过程中的代码处理,便于集中注意力与算法本身。

 1bool canWalk(int[,] mzMap, WalkNode node)
2
{
3    return mzMap[node.row, node.col] != 0;
4}
5
6bool isExit(int[,] mzMap, WalkNode node)
7
{
8    return mzMap[node.row, node.col] == -1;
9}
10
11void PrintRoad(WalkNode ndLast_)
12
{
13    string strRoad = string.Empty;
14    int nCount = 0;
15    while (ndLast_ != null)
16    {
17        ++nCount;
18        if (strRoad.Length > 0)
19        {
20            strRoad = string.Format("{0}", ndLast_) + "->" + strRoad;
21        }
22        else
23        {
24            strRoad = string.Format("{0}", ndLast_);
25        }
26
27        ndLast_ = ndLast_.prev;
28    }
29
30    Console.WriteLine("Road (Len: {0}): {1}", nCount, strRoad);
31    Console.WriteLine();
32}

深度优先搜索

先把入口节点入栈,递归直到栈空:

  1. 获取栈顶节点;

  2. 遍历临节点,若邻居点可通过:
    1). 若是出口,则打印路径;
    2). 否则把临节点入栈,并返回‘1’继续遍历;

  3. 所有临节点都不可通过:弹出栈顶节点,返回‘1’继续遍历;

经过的节点设为不通:

  • 走过的节点在入栈时,设为0(禁止通过),避免循环遍历;

  • 当节点出栈时,设为1(允许通过),以便其他路径再次遍历;

 1void SetWalked(int[,] mzMap, WalkNode node)
2
{
3    mzMap[node.row, node.col] = 0;
4}
5
6void ClearWalked(int[,] mzMap, WalkNode node)
7
{
8    mzMap[node.row, node.col] = 1;
9}
10
11void Walked(int[,] mzMap, WalkNode node, Stack<WalkNode> stackWalk)
12
{
13    stackWalk.Push(node);
14    SetWalked(mzMap, node);
15}
16
17void Goback(int[,] mzMap, Stack<WalkNode> stackWalk)
18
{
19    var top = stackWalk.Pop();
20    ClearWalked(mzMap, top);
21}
22
23public void WalkMazeDFS(int[,] mzMap, WalkNode ndStart)
24
{
25    Console.WriteLine("WalkMazeDFS: ");
26    if (!canWalk(mzMap, ndStart))
27    {
28        Console.WriteLine("Error, can not walk {0}", ndStart);
29        return;
30    }
31    if (isExit(mzMap, ndStart))
32    {
33        Console.WriteLine("OH, it is the exit {0}", ndStart);
34        return;
35    }
36
37    Stack<WalkNode> stackWalk = new Stack<WalkNode>();
38    Walked(mzMap, ndStart.Renew(), stackWalk);
39    while (stackWalk.Count > 0)
40    {
41        var cur = stackWalk.Peek();
42
43        // walk next
44        bool hasRoad = false;
45        WalkNode next = null;
46        do
47        {
48            next = cur.Next();
49            if (next != null)
50            {
51                if (canWalk(mzMap, next))
52                {
53                    hasRoad = true;
54                    if (isExit(mzMap, next))
55                    { // found the road
56                        PrintRoad(next);
57                    }
58                    else
59                    {
60                        Walked(mzMap, next, stackWalk);
61                    }
62                    break;
63                }
64            }
65        } while (next != null);
66
67        if (!hasRoad)
68        { // all try, goback
69            Goback(mzMap, stackWalk);
70        }
71    }
72}

广度优先搜索

先把入口节点入队列,递归直到队列空:

  1. 获取栈顶节点;

  2. 遍历所有临节点,若邻居点可通过:
    1). 若是出口,则打印路径;
    2). 否则若节点未在当前路径上,把临节点入队;

 1bool HasWalked(WalkNode ndNext, WalkNode ndHistroy)
2
{
3    while (ndHistroy != null)
4    {
5        if (ndHistroy.IsSame(ndNext))
6            return true;
7
8        ndHistroy = ndHistroy.prev;
9    }
10    return false;
11}
12
13public void WalkMazeBFS(int[,] mzMap, WalkNode ndStart)
14
{
15    Console.WriteLine("WalkMazeBFS: ");
16    if(!canWalk(mzMap, ndStart))
17    {
18        Console.WriteLine("Error, can not walk {0}", ndStart);
19        return;
20    }
21    if(isExit(mzMap, ndStart))
22    {
23        Console.WriteLine("OH, it is the exit {0}", ndStart);
24        return;
25    }
26
27    Queue<WalkNode> quWalk = new Queue<WalkNode>();
28    quWalk.Enqueue(ndStart.Renew());
29    while (quWalk.Count > 0)
30    {
31        var cur = quWalk.Dequeue();
32
33        // walk next
34        WalkNode next = null;
35        do
36        {
37            next = cur.Next();
38            if(next != null)
39            {
40                if(canWalk(mzMap, next))
41                {
42                    if(isExit(mzMap, next))
43                    {
44                        // found the road
45                        PrintRoad(next);
46                    }
47                    else
48                    {
49                        if (!HasWalked(next, cur))
50                            quWalk.Enqueue(next);
51                    }
52                }
53            }
54        } while (next != null);
55    }
56}

两种搜索与最短路径

两者都是递归处理所有节点。
广度优先时,遍历当前节点的所有可通临节点,然后加入到队列中;而深度优先时,遍历当前节点的任一可通节点,然后加入栈中。
广度优先搜索找到的第一条路径即为最短路径。


以上是关于[算法与数据结构] 走迷宫问题(广度与深度优先搜索)的主要内容,如果未能解决你的问题,请参考以下文章

异步广度优先搜索算法

走迷宫之广度优先搜索

数据结构与算法-深度优先搜索

广度优先深度优先搜索算法——面试题

数据结构与算法—— * 广度优先搜索 *

Golang 广度优先搜索算法走迷宫