广度优先搜索解决“营救公主”问题

新浪微博 QQ空间

在之前的一篇文章(迷宫营救公主算法)中提供了一个半成品的解决方案,之所以说他是半成品,是因为首先选择的算法就不对,采用的是深度优先搜索,其次也没有真正的用对深度优先算法,走过的点应该标记为已经走过,而不应该重复遍历该节点。下面的文章对于广度优先和深度优先两种算法的解释非常到位。今天准备把这个问题再完整的用正确的算法解答一遍。

文章链接:【图论】广度优先搜索和深度优先搜索

就营救公主的问题而言,这里不再重复描述问题了,请点击这里查看题目分析:链接。这次选用广度优先算法来解决该问题。上面的算法介绍文章中对该算法已经分析得非常清晰了。先给解决本问题的算法伪代码:

这里每个节点有三种状态:

*)该状态表示墙,如果访问到该类型节点,则不用继续遍历。应退回至上层节点。

.)该状态表示该节点通行,访问到该节点应该将节点压入队列。并将该节点标记为已经访问。为区别图中已经有的几种元素,这里定义一个新的标记’#’来表示已经访问过的节点。同时可以使用一个二维数组记录从起始节点到该节点的访问距离step[i][j],该值应该是由其上一个节点的值加1之后得到。这样起始节点的值为1,依次每一个节点的距离都可以得到。

P)该状态表示已经搜索到了公主的位置,搜索结束。这时给出P点的距离,如果该距离值在给定的范围内,则可以营救出公主,否则失败。

解决问题的关键在于用一个step数组记录每个节点的距离起始点的距离,另外用一个’#’字符标记该节点已经被访问过,避免走“回头路”。

BFS(G, s)    
    init V[G]   // 由测试用例实现。
    status[s] = S    // s王子的位置,通过一次遍历可以获取其坐标。
    
    int count = -1;
    queue q
    q.add(s) // 将王子的位置入队。
    while !q.isEmpty()
        t = q.poll()
        for each vertex v in Adj[t] // 与t邻接的点
            if status[v] = 'P'    // 只对未访问的操作
                count = step[v] = step[t] + 1
                q.clear()
                break
            elseif status[v] = '.'    
                status[v] = '#'    // 标记为已经走过该节点
                q.add(v)
                step[v] = step[t] + 1 // 累加得到当前节点的步长
            elseif status[v] = '*'
                step[v] = -1   // 遇到墙,则将该节点的距离置为-1,在遍历完整个图时,
                               // 如果遇到最后一个节点的值为-1,则表明没有找到。
            else
                error
            
    if count != -1
        if count < L
            return true
    return false

 

有了上面的伪代码,翻译成代码也就非常简单了。
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * 公主被魔王抓走了,王子需要拯救出美丽的公主。他进入了魔王的城堡,魔王的城堡是一座很大的迷宫。
 * 为了使问题简单化,我们假设这个迷宫是一个N*M的二维方格。迷宫里有一些墙,王子不能通过。王子只
 * 能移动到相邻(上下左右四个方向)的方格内,并且一秒只能移动一步。地图由'S','P','.','*'
 * 四种符号构成,'.'表示王子可以通过,'*'表示墙,王子不能通过;'S'表示王子的位置;'P'表示公主
 * 的位置;T表示公主存活的剩余时间,王子必须在T秒内到达公主的位置,才能救活公主。如下图所示:
 */
public class SavePrincess
{
    private int m;
    private int n;
    private char[][] visited;
    private Position s = null;
    private Position p = null;

    public static void main(String[] args)
    {
        char maze[][] = {
        /* 0 1 2 3 4 5 6 7 8 9 */
        /* 0 */{ '.', '*', '.', '.', '.', '*', '.', '.', '.', '.' },
        /* 1 */{ '*', 'S', '*', '.', '.', '.', '.', '.', '.', '.' },
        /* 2 */{ '.', '*', '*', '.', '.', '.', '.', '.', '.', '.' },
        /* 3 */{ '.', '.', '*', '*', '*', '.', '.', '.', '.', '.' },
        /* 4 */{ '.', '.', '.', '.', '.', '.', '.', '.', '.', '.' },
        /* 5 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
        /* 6 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
        /* 7 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
        /* 8 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
        /* 9 */{ '.', 'P', '.', '.', '*', '.', '.', '.', '.', '.' } };

        new SavePrincess().saved(maze, 10, 8, 11);
    }

    /**
     * @param vis
     *            M行N列迷宫
     * @param m
     *            M
     * @param n
     *            N
     * @param t
     *            T
     * 
     * @return boolean true表示营救成功,反之表示失败。
     */
    public boolean saved(char[][] vis, int m, int n, int t)
    {
        if (t < 0)
        {
            return false;
        }

        this.m = m;
        this.n = n;
        this.visited = vis;
        int step[][] = new int[m][n];

        init(m, n, step);

        if (s == null || p == null)
        {
            System.out.println("input visited error!");
            return false;
        }

        Queue<Position> queue = new LinkedList<Position>();
        queue.add(s);
        int l = -1;
        while (!queue.isEmpty())
        {
            Position e = queue.poll();
            l = dealOneNode(step, queue, e);
        }

        if (l != -1 && l <= t)
        {
            System.out.println("Saved the princess in " + l + " seconds!");
            return true;
        }

        System.out.println("failed saved the princess! " + l + " > " + "t");
        return false;
    }

    private void init(int m, int n, int[][] step)
    {
        for (int i = 0; i != m; i++)
        {
            for (int j = 0; j != n; j++)
            {
                System.out.print(visited[i][j]);
                System.out.print(' ');
                step[i][j] = 0;
                switch (visited[i][j])
                {
                case '*':
                    break;
                case 'S':
                    this.s = new Position(i, j, 'S');
                    break;
                case '.':
                    break;
                case 'P':
                    this.p = new Position(i, j, 'P');
                    break;
                default:
                    System.out.println("input visited error!");
                    System.exit(-1);
                }
            }
            System.out.println("");
        }
    }

    private int dealOneNode(int[][] step, Queue<Position> queue, Position e)
    {
        /**
         * 在求相邻节点时做了一点优化,遇到墙的节点则不放到相邻节点中。
         * 从设计思想和可读性上看不是太好,从运行效率考虑,可以。
         */
        for (Position c : getNextPostions(e))
        {
            switch (c.getV())
            {
            case 'P':
                queue.clear();
                return step[e.getI()][e.getJ()] + 1;
            case '.':
                visited[c.getI()][c.getJ()] = '#';
                queue.add(c);
                step[c.getI()][c.getJ()] = step[e.getI()][e.getJ()] + 1;
                break;
            default:
                // 不可能进入该分支。
                System.err.println("some error!");
                break;
            }
        }
        return -1;
    }

    /**
     * 取有效节点。
     * 遇到墙则不加入相邻节点中。
     * 
     * @param e
     * @return
     */
    private List<Position> getNextPostions(Position e)
    {
        int i = e.getI();
        int j = e.getJ();
        List<Position> lst1 = new LinkedList<Position>();
        addOneNode(i + 1, j, lst1);
        addOneNode(i, j + 1, lst1);
        addOneNode(i - 1, j, lst1);
        addOneNode(i, j - 1, lst1);
        return lst1;
    }

    private void addOneNode(int i, int j, List<Position> lst)
    {
        if (i >= m || i < 0 || j >= n || j < 0)
        {
            return;
        }

        char v = visited[i][j];
        switch (v)
        {
        case '.':
        case 'P':
            lst.add(new Position(i, j, v));
            break;
        default:
            break;
        }
    }

    private class Position
    {
        private int i;
        private int j;
        private char v;

        public Position(int i, int j, char value)
        {
            this.i = i;
            this.j = j;
            this.v = value;
        }

        public char getV()
        {
            return v;
        }

        public int getI()
        {
            return i;
        }

        public int getJ()
        {
            return j;
        }

        public String toString()
        {
            return "(" + i + ", " + j + ")";
        }
    }

}

新浪微博 QQ空间

| 1 分2 分3 分4 分5 分 (5.00- 3票) Loading ... Loading ... | 这篇文章归档在:Java, 算法数据结构 | 标签: , , , , , . | 永久链接:链接 | 评论(6) |

6 条评论

  1. white_saber
    评论于 三月 31, 2014 at 20:47:58 CST | 评论链接

    char maze[][] = {
    /* 0 1 2 3 4 5 6 7 8 9 */
    /* 0 */{'.', '.', '.', '.', '.', '*', '.', '.', '.', '.'},
    /* 1 */{'.', 'P', '.', '.', '.', '.', '.', '.', '.', '.'},
    /* 2 */{'.', '*', '.', '.', '.', '.', '.', '.', '.', '.'},
    /* 3 */{'.', '.', '*', '*', '*', '.', '.', '.', '.', '.'},
    /* 4 */{'.', '.', '.', '.', '.', '.', '.', '.', '.', '.'},
    /* 5 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
    /* 6 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
    /* 7 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
    /* 8 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
    /* 9 */{'.', 'S', '.', '.', '*', '.', '.', '.', '.', '.'}}; 
    这个矩阵得出的结果不对,不是最优解

    然后解的路径没有。只有是否能在规定步数内救出公主的true?false,需要输出最优解的路径

    • 评论于 三月 31, 2014 at 21:57:34 CST | 评论链接

      根据这个迷宫,已经找到了代码中的bug,并修改了日志中代码。
      这位同学测试得好细心啊。赞并感谢!!

      你提出的记录路劲的需求,应该是要扩充每遍历一个节点时,不仅仅要记录该节点距离起始点“S”的距离,还需要记录起始节点“S”到当前节点的轨迹,这样就可以在找到公主时打印出路径了。因此setup不是一个简单的int型的二维数组,而应该是一个符合类型对象的二维数组,该对象中包含了距离和所经过的节点的链表。

      char maze[][] = {
      /* 0 1 2 3 4 5 6 7 8 9 */
      /* 0 */{'.', '.', '.', '.', '.', '*', '.', '.', '.', '.'},
      /* 1 */{'.', 'P', '.', '.', '.', '.', '.', '.', '.', '.'},
      /* 2 */{'.', '*', '.', '.', '.', '.', '.', '.', '.', '.'},
      /* 3 */{'.', '.', '*', '*', '*', '.', '.', '.', '.', '.'},
      /* 4 */{'.', '.', '.', '.', '.', '.', '.', '.', '.', '.'},
      /* 5 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
      /* 6 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
      /* 7 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
      /* 8 */{'.', '.', '.', '.', '*', '.', '.', '.', '.', '.'},
      /* 9 */{'.', 'S', '.', '.', '*', '.', '.', '.', '.', '.'}}; 

       

    • 评论于 三月 31, 2014 at 22:31:26 CST | 评论链接

      增加路径记录的代码如下:

      
      import java.util.LinkedList;
      import java.util.List;
      import java.util.Queue;
      
      /**
       * 公主被魔王抓走了,王子需要拯救出美丽的公主。他进入了魔王的城堡,魔王的城堡是一座很大的迷宫。
       * 为了使问题简单化,我们假设这个迷宫是一个N*M的二维方格。迷宫里有一些墙,王子不能通过。王子只
       * 能移动到相邻(上下左右四个方向)的方格内,并且一秒只能移动一步。地图由'S','P','.','*'
       * 四种符号构成,'.'表示王子可以通过,'*'表示墙,王子不能通过;'S'表示王子的位置;'P'表示公主
       * 的位置;T表示公主存活的剩余时间,王子必须在T秒内到达公主的位置,才能救活公主。如下图所示:
       */
      public class SavePrincess
      {
          private int m;
          private int n;
          private char[][] visited;
          private Position s = null;
          private Position p = null;
      
          public static void main(String[] args)
          {
              char maze[][] = {
              /* 0 1 2 3 4 5 6 7 8 9 */
              /* 0 */{ '.', '*', '.', '.', '.', '*', '.', '.', '.', '.' },
              /* 1 */{ '.', 'S', '.', '.', '.', '.', '.', '.', '.', '.' },
              /* 2 */{ '*', '*', '*', '.', '.', '.', '.', '.', '.', '.' },
              /* 3 */{ '.', '.', '*', '.', '*', '.', '.', '.', '.', '.' },
              /* 4 */{ '.', '.', '.', '.', '.', '.', '.', '.', '.', '.' },
              /* 5 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
              /* 6 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
              /* 7 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
              /* 8 */{ '.', '.', '.', '.', '*', '.', '.', '.', '.', '.' },
              /* 9 */{ '.', 'P', '.', '.', '*', '.', '.', '.', '.', '.' } };
      
              new SavePrincess().saved(maze, 10, 8, 20);
          }
      
          /**
           * @param vis
           *            M行N列迷宫
           * @param m
           *            M
           * @param n
           *            N
           * @param t
           *            T
           * 
           * @return boolean true表示营救成功,反之表示失败。
           */
          public boolean saved(char[][] vis, int m, int n, int t)
          {
              if (t < 0)
              {
                  return false;
              }
      
              this.m = m;
              this.n = n;
              this.visited = vis;
              Step step[][] = new Step[m][n];
      
              init(m, n, step);
      
              if (s == null || p == null)
              {
                  System.out.println("input visited error!");
                  return false;
              }
      
              Queue<Position> queue = new LinkedList<Position>();
              queue.add(s);
              Step l = null;
              while (!queue.isEmpty())
              {
                  Position e = queue.poll();
                  l = dealOneNode(step, queue, e);
              }
      
              if (l != null && l.getStep() + 1 <= t)
              {
                  System.out.println("Saved the princess in " + l + " seconds!");
                  return true;
              }
      
              System.out.println("failed saved the princess! " + l + " > " + "t");
              return false;
          }
      
          private void init(int m, int n, Step[][] step)
          {
              for (int i = 0; i != m; i++)
              {
                  for (int j = 0; j != n; j++)
                  {
                      System.out.print(visited[i][j]);
                      System.out.print(' ');
                      step[i][j] = new Step(0);
                      switch (visited[i][j])
                      {
                      case '*':
                          break;
                      case 'S':
                          this.s = new Position(i, j, 'S');
                          break;
                      case '.':
                          break;
                      case 'P':
                          this.p = new Position(i, j, 'P');
                          break;
                      default:
                          System.out.println("input visited error!");
                          System.exit(-1);
                      }
                  }
                  System.out.println("");
              }
          }
      
          private Step dealOneNode(Step[][] step, Queue<Position> queue, Position e)
          {
              /**
               * 在求相邻节点时做了一点优化,遇到墙的节点则不放到相邻节点中。
               * 从设计思想和可读性上看不是太好,从运行效率考虑,可以。
               */
              for (Position c : getNextPostions(e))
              {
                  switch (c.getV())
                  {
                  case 'P':
                      queue.clear();
                      return step[e.getI()][e.getJ()];
                  case '.':
                      visited[c.getI()][c.getJ()] = '#';
                      queue.add(c);
                      step[c.getI()][c.getJ()].setStep(step[e.getI()][e.getJ()].step + 1);
                      List<Position> l = step[c.getI()][c.getJ()].getPath();
                      l.addAll(step[e.getI()][e.getJ()].path);
                      l.add(c);
                      break;
                  default:
                      // 不可能进入该分支。
                      System.err.println("some error!");
                      break;
                  }
              }
              return null;
          }
      
          /**
           * 取有效节点。
           * 遇到墙则不加入相邻节点中。
           * 
           * @param e
           * @return
           */
          private List<Position> getNextPostions(Position e)
          {
              int i = e.getI();
              int j = e.getJ();
              List<Position> lst1 = new LinkedList<Position>();
              addOneNode(i + 1, j, lst1);
              addOneNode(i, j + 1, lst1);
              addOneNode(i - 1, j, lst1);
              addOneNode(i, j - 1, lst1);
              return lst1;
          }
      
          private void addOneNode(int i, int j, List<Position> lst)
          {
              if (i >= m || i < 0 || j >= n || j < 0)
              {
                  return;
              }
      
              char v = visited[i][j];
              switch (v)
              {
              case '.':
              case 'P':
                  lst.add(new Position(i, j, v));
                  break;
              default:
                  break;
              }
          }
      
          private class Position
          {
              private int i;
              private int j;
              private char v;
      
              public Position(int i, int j, char value)
              {
                  this.i = i;
                  this.j = j;
                  this.v = value;
              }
      
              public char getV()
              {
                  return v;
              }
      
              public int getI()
              {
                  return i;
              }
      
              public int getJ()
              {
                  return j;
              }
      
              public String toString()
              {
                  return "[" + i + ", " + j + "]";
              }
          }
          
          private class Step
          {
          	private int step;
          	
          	private List<Position> path = new LinkedList<Position>();
      		
          	public Step(int step)
          	{
          		this.step = step;
          	}
          	
          	public int getStep() 
      		{
      			return step;
      		}
          	
      		public void setStep(int step) 
      		{
      			this.step = step;
      		}
      		
      		public List<Position> getPath() 
      		{
      			return path;
      		}
      		
      		@Override
      		public String toString()
      		{
      			StringBuilder sb = new StringBuilder();
      			sb.append("(").append("step: ").append(step).append(", path: ");
      			if (path != null)
      			{
      				for (Position pnode : path)
      				{
      					sb.append(pnode).append(" -> ");
      				}
      			}
      			sb.append(p).append(")");
      			return sb.toString();
      		}
          }
      
      }
      

       

      运行结果:
      . * . . . * . .
      . S . . . . . .
      * * * . . . . .
      . . * . * . . .
      . . . . . . . .
      . . . . * . . .
      . . . . * . . .
      . . . . * . . .
      . . . . * . . .
      . P . . * . . .
      Saved the princess in (step: 11, path: [1, 2] -> [1, 3] -> [2, 3] -> [3, 3] -> [4, 3] -> [5, 3] -> [6, 3] -> [7, 3] -> [8, 3] -> [9, 3] -> [9, 2] -> [9, 1]) seconds!

  2. 评论于 十月 17, 2013 at 13:32:25 CST | 评论链接

                                   

  3. 评论于 十月 15, 2013 at 15:34:07 CST | 评论链接

    对于网站降权方面,我能博主一些问题么。我的一个网站www.caizicheng.com六月份改版之后,被降权了。不过在7月份有一段时间,权重回来之后现在又被降权了。而且,这次网站的首页也找不到了。
    现在,我应该从那些方面着手来恢复网站的权重。 

    • 评论于 十月 15, 2013 at 17:52:53 CST | 评论链接

      在这方面,你比我懂得多多了。我只觉得一个稳固而实用的界面和充实的内容非常重要。

回复 童燕群

邮箱地址不会被泄露, 标记为 * 的项目必填。

8 - 2 = *



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <img alt="" src="" class=""> <pre class=""> <q cite=""> <s> <strike> <strong>

返回顶部