「Golang成長之路」迷宮的廣度優先搜尋

ice_moss發表於2021-10-15

迷宮的廣度優先搜尋

一、 廣度優先演算法

迷宮的廣度優先搜尋是基於廣度優先演算法來實現的,在爬蟲領域也會經常使用廣度優先演算法,首先來了解一下廣度優先演算法。
我在之前對文章中具體介紹了廣度優先演算法,可參見[圖論系列之「廣度優先遍歷及無權圖的最短路徑(ShortPath)」,這裡我們需要使用一個輔助佇列,用來存放訪問過的節點,下圖是我們的迷宮例項(6行5列):

「Golang成長之路」迷宮的廣度優先搜尋
從左上角為入口,右下角為出口,數字1表示牆(走不通),數字0表示通道,這裡我們需要規定節點的遍歷方向,即:上左下右(逆時針),下面來具體介紹廣度優先演算法在迷宮中的邏輯:

  1. 當前節點為0(入口),將該0放入輔助佇列中,然後拿出節點0並標記,再將該0節點相鄰的節點以上左下右(逆時針)的順序放入佇列中,此時的佇列中,佇列:0、1

  2. 接著從佇列中拿出0,在將該0相鄰的節點(0、0)放入佇列中,此時的佇列:1、0、0

  3. 接著再拿出佇列中的第一個節點”1”,但節點1是牆,將其捨棄此時的佇列:0、0

  4. 再拿出佇列中的第一個節點”0”,然後將0節點相鄰的節點1、1放入佇列中,此時的佇列:0、1、1
    5接著再拿出佇列中第一個節點“0”,而該節點相鄰的節點有:1、0、1、0
    此時只有該0節點的右鄰節點未被訪問,即:0,放入佇列,此時的佇列:0、1、1、1
    ……
    ……
    以此類推,就可找到出口

    二、檔案讀入

    我們需要將:

    6 5
    0 1 0 0 0
    0 0 0 1 0
    0 1 0 1 0
    1 1 1 0 0
    0 1 0 0 1
    0 1 0 0 0

    讀入,並建立一個6行5列的二維slice
    程式如下:

    //讀入檔案
    func ReadMaze(filename string) [][]int{
     file, err := os.Open(filename)
     if err != nil{
        panic(err)
     }
     var row, col int
    fmt.Fscanf(file, "%d %d", &row, &col)   //fmt.Fscanf()?
    maze := make([][]int, row)
     for i := range maze{
        maze[i]= make([]int, col)
        for j := range maze[i]{
           fmt.Fscanf(file, "%d", &maze[i][j])
        }
    
     }
     return maze
    }

    三、廣度優先演算法的實現

    我們需要將數字抽象為節點:

    type point struct {
      i, j int
    }

    還需要對每一個節點的相鄰節點進行訪問:

    var dirs = [4]point{
      {-1,0}, {0,-1}, {1,0}, {0,1}}

    這是廣度優先演算法的核心:

    func walk(maze [][]int, start, end point) [][]int{
     //建立迷宮正確路線
    steps := make([][]int, len(maze))
     for i := range steps{
        steps[i] = make([]int, len(maze[i]))
    
        }
          //建立佇列Q
    Q := []point{start}
    
        for len(Q) > 0 {
           cur := Q[0]
           Q = Q[1: ]
    
           if cur == end{
              break
    }
    
           for _, dir := range dirs{
              next := cur.add(dir)
    
              val, ok := next.at(maze)
              if !ok || val == 1{    //1為牆,ok == fals為越界
    continue
    }
              val, ok = next.at(steps) //判斷是否被訪問,steps中的值為零表示為牆
    if !ok || val != 0 {
                 continue
    }
              if next == start{
                 continue
    }
    
              curSteps, _ := cur.at(steps)
              steps[next.i][next.j] = curSteps + 1
    
    Q = append(Q, next)
    
              //maze at next is 0
    //and steps at next is 0 指到過的點 //and next == 0  }
        }
        return steps
     }

    我們還需要對座標進行變更新:

    //座標變換
    func (p point)add(r point) point{
      return point{p.i + r.i, p.j + r.j}
    }

    在遍歷過程中需要檢查是否越界:

    //判斷越界
    func (p point)at(grid [][]int)(int, bool){
     if p.i < 0 || p.i >= len(grid) {
        return 0, false
    }
      if p.j < 0 || p.j >= len(grid[p.i]){
         return 0, false
    }
     return grid[p.i][p.j],true
    }

    四、呼叫

    func main() {
     maze := ReadMaze("maze/maze.in")
    
     steps := walk(maze, point{0,0}, point{len(maze)-1, len(maze[0])-1})
    
     for i := range steps{
        for j := range steps[i]{
           fmt.Printf("%3d " ,steps[i][j])
        }
        fmt.Println()
     }
    }

列印結果:

  0   0   4   5   6 
  1   2   3   0   7 
  2   0   4   0   8 
  0   0   0  10   9 
  0   0  12  11   0 
  0   0  13  12  13 
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章