迷宫问题求解之“穷举+回溯”(一)(转载)

求迷宫从入口到出口的所有路径是一个经典的程序设计问题,求解迷宫,通常采用的是“穷举+回溯”的思想,即从入口开始,顺着某一个方向出发,若能够走通,就继续往前走;若不能走通,则退回原路,换一个方向继续向前探索,直到所有的通路都探寻为止。因此本文依据这种“穷举+回溯”的思想,设计一个求解迷宫的程序。

1 问题分析

为了保证在任何位置上都能够退回原路,显然需要使用一个先进后出的数据结构来保存已经探寻过的位置,因此在程序求解迷宫路径的过程中采用这种数据结构。

迷宫是一个二维地图,其中含有出口和入口,障碍点和通道,因此程序采用一个二位数组map来表示,其中,二维数组值所代表的含义如下:

0:通道    1:起点    2:障碍    3:终点    4:路径

需要注意的是,最后求解出来的通路,必须要是一条简单路径,即在求得的路径上不能重复出现同一通道快。下面简述一下程序的求解过程。

将“在搜索过程中某一个时刻所在迷宫地图中的位置“记为“当前位置”,那么求解迷宫路径算法的基本思想是:若“当前位置”可通行,那么将“当前位置”纳入求解路径中(压到栈中),并朝“下一个位置”继续探索,即把“下一位置”切换到“当前位置”,如此重复下去,直到找到出口;如果当前位置不可以通行,则顺着来的方向退回到“前一个通道位置”,然后再朝着其他方向探索下去;如果该通道块的4个方向(东西南北)都不可通,那么就在该路径(栈)中删除该通道位置(删除栈顶元素),再继续退回到“前一个通道位置”继续探索,即再获取栈顶元素作为“当前位置继续上述的重复探索。

2 算法描述

根据对迷宫问题的分析,求迷宫中一条从入口到出口的路径算法描述如下:

  1. 设定入口位置为当前位置。
  2. if(当前位置是可通行且是没有被走过的通道块 )(分支1)

    2.1 将当前位置压到栈中。

    2.2 若当前位置是出口,则程序结束,返回路径栈

    2.3 若当前不是出口,则切换当前位置为当前位置东边的通道块,返回2继续执行

  3. else(若当前位置不可通行)(分支2)

    3.1 获取栈顶元素所在的位置,若该位置所在的相邻4个方向都被探索过了,则删除当前栈顶元素,获取新的栈顶元素,直到找到一个可通的相邻块位置或出栈至栈空再执行3.2步骤

    3.2 若栈顶元素所在的位置还有其他方向没有被探索过,则设定新的当前位置为栈顶元素的未被探索方向的相邻通道块。(方向访问顺序为顺时针即东南西北)

  4. 重复步骤2.

需要注意步骤2中的没有被走过的通道块,是指该通道块从来未压入栈中,否则求解的路径就不是一条简单路径,很可能导致一个死循环。

3 程序实现

程序采用c#语言对上述的算法描述进行了实现,代码如下。

3.1 通到块实体

class PathElement
{
    public PathElement(int row, int col, int direcation)
    {
        this.Row = row;
        this.Col = col;
        this.Direction = direcation;
    }
    public int Row;//位置所在的行
    public int Col;//位置所在的列
    public int Direction;//从此位置走向下一位置的方向,方向分为东0南1西2北3,默认东方向为初始方向
}

3.2 寻找简单路径

public static Stack<PathElement> FindPath(int[,] map, int startX, int startY, int endX, int endY)
{
    Stack<PathElement> path = new Stack<PathElement>();
    List<PathElement> visitedList = new List<PathElement>();//保存以通过的元素
    PathElement curPosition = new PathElement(startX, startY, 0);
    int tryCount = 0;
    do
    {
        tryCount++;
        if (Pass(curPosition, map) && (!Visited(curPosition, visitedList)))//当前位置能通过,且没被访问过
        {
            path.Push(curPosition);//加入路径
            if (curPosition.Row == endX && curPosition.Col == endY)
                return path;
            visitedList.Add(curPosition);//该位置以被访问
            curPosition = GetNextPosition(curPosition);//获取下一个当前位置,并更新该位置的下一个位置的方向

        }
        else//当前不能通过
        {
            if (path.Count != 0) //如何栈不为空
            {
                PathElement topElement = path.Peek();//获取栈顶元素
                while (topElement.Direction == 4 && (path.Count > 1))//找寻一个可用的位置
                {
                    path.Pop();
                    topElement = path.Peek();
                }
                if (topElement.Direction < 4)
                {
                    curPosition = GetNextPosition(topElement);//获取下一个当前位置,并更新该位置的下一个位置的方向
                }
            }
        }
    } while (path.Count != 0);
    return null;
}

3.3 辅助方法

/*获取下一个位置*/
private static PathElement GetNextPosition(PathElement curPostion)
{
    PathElement nextPosition=null;
    switch (curPostion.Direction)
    {
        case 0:
            nextPosition = new PathElement(curPostion.Row, curPostion.Col+1, 0);
            break;
        case 1:
            nextPosition = new PathElement(curPostion.Row+1, curPostion.Col, 0);
            break;
        case 2:
            nextPosition = new PathElement(curPostion.Row, curPostion.Col-1, 0);
            break;
        case 3:
            nextPosition = new PathElement(curPostion.Row-1, curPostion.Col, 0);
            break;
    }
    curPostion.Direction++;
    return nextPosition;
}

/*是否为通道*/
private static bool Pass(PathElement curPosition, int[,] map)
{
    int rowCount = map.GetLength(0);
    int colCount = map.GetLength(1);
    //边界判断
    if (curPosition.Row >= 0 && curPosition.Row < rowCount && curPosition.Col >= 0 && curPosition.Col < colCount)
    {
        //障碍判断
        if (map[curPosition.Row, curPosition.Col] == 2)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    else
    {
        return false;
    }
}

/*是否被访问*/
private static bool Visited(PathElement curPosition, List<PathElement> visitedList)
{
    foreach (PathElement element in visitedList)
    {
        if (element.Row == curPosition.Row && element.Col == curPosition.Col)
        {
            return true;
        }
    }
    return false;
}

3.4 界面

该部分代码只是提供算法验证的可视化界面,与本文所探讨的算法实现没有多大的关系,程序的源代码可以在文章后面的链接进行下载。

界面所包含的功能:

  1. 能够编辑迷宫地图,单击实现障碍物的设计,双击去掉障碍物。
  2. 能够保存和载入地图。
  3. 能够通过该算法实现从迷宫入口到出口路径的生成和显示。

3.5 结果截图

1 地图的载入

2 路径寻找

4 总结

“穷举+回溯”的思路还是很好理解,通俗的讲,就是选择一条道路一直走到黑,如果碰到前方有障碍,就退回来一步再换一个方向继续走,直到把所有可能的路都做完了。

利用“穷举+回溯”搜索路径,虽然简单,但是它属于一种盲目、机械的搜索算法,从3.5中的结果图中可以看出,该算法找出来的路径,显然不是最优的,走了许多的弯路。那么如何从迷宫的起点到终点找到一条最优的路径呢?敬请期待《迷宫问题求解之“A*搜索”(二)》。

5 资源和参考资料

参考资料:严蔚敏《数据结构c语言版》

源代码下载:http://download.csdn.net/detail/mingge38/9653205

时间: 2024-10-30 14:14:07

迷宫问题求解之“穷举+回溯”(一)(转载)的相关文章

穷举递归和回溯算法终结篇

穷举递归和回溯算法 在一般的递归函数中,如二分查找.反转文件等,在每个决策点只需要调用一个递归(比如在二分查找,在每个节点我们只需要选择递归左子树或者右子树),在这样的递归调用中,递归调用形成了一个线性结构,而算法的性能取决于调用函数的栈深度.比如对于反转文件,调用栈的深度等于文件的大小:再比如二分查找,递归深度为O(nlogn),这两类递归调用都非常高效. 现在考虑子集问题或者全排列问题,在每一个决策点我们不在只是选择一个分支进行递归调用,而是要尝试所有的分支进行递归调用.在每一个决策点有多种

C#跳转语句 迭代法 穷举法

一.跳转语句 break & continue break:跳出循环,终止此循环,不管下面还有多少次,全部跳过. string a=" ", for (int i=1;i<=10;I++) { if(i==5) { break; } a += i +",": } Console.WriteLine(a); 输出结果为 1,2,3,4,5 continue:终止此次循环,直接开始下次循环. string a=" ", for (int

2016年10月10日--穷举、迭代、while循环

穷举 将所有可能性全部全部走一遍,使用IF筛选出满足的情况 练习: 1.单位给发了一张150元购物卡, 拿着到超市买三类洗化用品. 洗发水15元,香皂2元,牙刷5元. 求刚好花完150元,有多少种买法, 没种买法都是各买几样? int i = 0; int j = 0; for (int x = 0; x <= 10; x++) { for (int y = 0; y <= 30; y++) { for (int z = 0; z <= 75; z++) { j++; if (x * 1

穷举练习——7月24日

穷举:把所有可能的情况都走一遍,使用if条件筛选出来满足条件的情况 练习一:购物 //单位给发了一张150元购物卡,拿着到超市买三种洗化用品, //洗发水15元(x).香皂2元(z),牙刷5元(y), //求刚好花完150元有多少种买法,每种买法都是各买几样 int sum = 0; for (int x = 0; x * 15 <= 150; x++) { for (int y = 0; y * 5 <= 150; y++) { for (int z = 0; z * 2 <= 150

穷举迭代03/10

穷举迭代03/10 for循环拥有两类: 1.穷举: 把所有可能的情况都走一遍,使用if条件筛选出来满足条件的情况. for循环拥有两类: 2.穷举: 把所有可能的情况都走一遍,使用if条件筛选出来满足条件的情况. for循环  for(int i = 1; i<=5;i++) { 循环体: } while样式      int i= 1: while(表达式(i<=5)) { 循环体: 状态改变(i++): } do while 样式    do { 循环体: 状态改变(i++); }whi

for 穷举 迭代

for循环拥有两类:穷举:把所有可能的情况都走一遍,使用if条件筛选出来满足条件的情况. 迭代:从初始情况按照规律不断求解中间情况,最终推导出结果. 案例: //单位给发了一张150元购物卡, //拿着到超市买三类洗化用品. //洗发水15元,香皂2元,牙刷5元. //求刚好花完150元,有多少种买法, //每种买法都是各买几样? //设洗发水x 150/15==10 //牙刷y 150/5==30 //香皂z 150/2==75 int sum = 0; int biao = 0; for (

什么叫穷举法?

穷举法的基本思想是根据题目的部分条件确定答案的大致范围,并在此范围内对所有可能的情况逐一验证,直到全部情况验证完毕.若某个情况验证符合题目的全部条件,则为本问题的一个解:若全部情况验证后都不符合题目的全部条件,则本题无解.穷举法也称为枚举法. 用穷举法解题时,就是按照某种方式列举问题答案的过程.针对问题的数据类型而言,常用的列举方法一有如下三种: (1)顺序列举 是指答案范围内的各种情况很容易与自然数对应甚至就是自然数,可以按自然数的变化顺序去列举. (2)排列列举 有时答案的数据形式是一组数的

[C++11][算法][穷举]输出背包问题的所有可满足解

关于背包问题的题目,前人之述备矣,这里只讨论实现 输入: n ca w_1 v_1 w_2 v_2 ... w_n v_n 其中,n是物品总数,ca是背包大小,w_n是第n个物品的重量,v_n是第n个物品的价值 输出: v_1 x v_2 x v_3 x ... 其中,v_n是当前情况为x时背包的价值,x是一串序列,由0,1组成,表示是否放入背包 如: 1001就表示第一个和最后一个物品放入背包,中间两个物品不放入 要求编写一个程序,输出所有可满足解. 思路很简单,就是穷举.穷举每一个情况. 伪

HDU 5339 Untitled (递归穷举)

题意:给定一个序列,要求从这个序列中挑出k个数字,使得n%a1%a2%a3....=0(顺序随你意).求k的最小值. 思路:排个序,从大的数开始模起,这是因为小的模完还能模大的么? 每个元素可以选,也可以不选,两种情况.递归穷举每个可能性,O(2n). 1 //#include <bits/stdc++.h> 2 #include <cstdio> 3 #include <cstring> 4 #include <map> 5 #include <al