再來一篇深度優先遍歷/搜尋總結?

CodeJames發表於2020-05-22

再來一篇深度優先遍歷/搜尋總結?

簡介:深度優先搜尋演算法(Depth-First-Search, DFS),最初是一種用於遍歷或搜尋樹和圖的演算法,在LeetCode中很常見,雖然感覺不難,但是理解起來還是有點難度的。

簡要概括,深度優先的主要思想就是“不撞南牆不回頭”,“一條路走到黑”,如果遇到“牆”或者“無路可走”時再去走下一條路。

思路

假如對樹進行遍歷,沿著樹的深度遍歷樹的節點,儘可能深的搜尋樹的分支,當達到邊際時回溯上一個節點再進行搜尋。如下圖的一個二叉樹。

首先給出這個二叉樹的深度優先遍歷的結果(假定先走左子樹):1->2->4->5->3->6->7

那是怎樣得到這樣的結果呢?

根據深度優先遍歷的概念:沿著這樹的某一分支向下遍歷到不能再深入為止,之後進行回溯再選定新的分支。

定義節點

class TreeNode{
    int val;
    TreeNode left;
    TreeNode right;
}

遞迴的方式

分別對左右子樹進行遞迴,一直到底才進行回溯。如果不瞭解遞迴可以參考我的部落格你真的懂遞迴嗎?

class Solution{
    public void depthOrderTraversalWithRecusive(TreeNode root){
        if(root == null){
            return;
        }
        System.out.print(root.val +"->");
        depthOrderTraversalWithRecusive(root.left);
        depthOrderTraversalWithRecusive(root.right);
    }
}

迭代的方式

上面實現了遞迴方式的深度優先遍歷,也可以利用棧把遞迴轉換為迭代的方式。

但是為了保證出棧的順序,需要先壓入右節點,再壓左節點。

class Solution{
    public void depthOrderTraversalWithoutRecusive(TreeNode root){
        if(root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode node = stack.pop();
            System.out.print(node.val + "->");
            if(node.right != null){
                stack.push(node.right);
            }
            if(node.left != null){
                stack.push(node.left);
            }
        }
    }
}

接著再列舉個利用深度優先遍歷的方式的題目

掃雷

給定一個表示遊戲板的二維字元矩陣,'M'表示一個未挖出的地雷,'E'表示一個未挖出的空方塊,'B' 代表沒有相鄰(上,下,左,右,和所有4個對角線)地雷的已挖出的空白方塊,數字('1''8')表示有多少地雷與這塊已挖出的方塊相鄰,'X' 則表示一個已挖出的地雷。

根據以下規則,返回相應位置被點選後對應的皮膚:

  • 如果一個地雷('M')被挖出,遊戲就結束了- 把它改為 'X'
  • 如果一個沒有相鄰地雷的空方塊('E')被挖出,修改它為('B'),並且所有和其相鄰的方塊都應該被遞迴地揭露。
  • 如果一個至少與一個地雷相鄰的空方塊('E')被挖出,修改它為數字('1''8'),表示相鄰地雷的數量。
  • 如果在此次點選中,若無更多方塊可被揭露,則返回皮膚。

示例

輸入: 

[['E', 'E', 'E', 'E', 'E'],
 ['E', 'E', 'M', 'E', 'E'],
 ['E', 'E', 'E', 'E', 'E'],
 ['E', 'E', 'E', 'E', 'E']]

Click : [3,0]

輸出: 

[['B', '1', 'E', '1', 'B'],
 ['B', '1', 'M', '1', 'B'],
 ['B', '1', '1', '1', 'B'],
 ['B', 'B', 'B', 'B', 'B']]

思路:根據給定的規則,當給定一個Click座標,當不為雷的時候以此座標為基點向四周8個方向進行深度遍歷,把空格E填充為B,並且把與地雷M相連的空方塊標記相鄰地雷的數量。

注意

在這個題中可以沿著8個方向遞迴遍歷,所有要注意程式中,採用了兩個for迴圈可以實現向8個方向遞迴。

for(int i=-1;i<=1;i++){
    for(int j=-1;j<=1;j++){

   	}
}

本程式需要進行返回board,在最後需要進行返回。

程式設計步驟

  • Click給出的座標找出的是地雷,直接返回。
  • 否則,進行遞迴,並標出雷的數量。
class Solution{
    public char[][] updateBoard(char[][] board,int[] click){
        if(board[click[0]][click[1]] == 'M'){
            board[click[0]][click[1]] = 'X';
            return board;
        }
        return click(board,click[0],click[1]);
    }
    private char[][] click(char[][] board,int x,int y){
        int num = getNum(board, x,y);
        if(num == 0){
            board[x][y] = 'B';
        }else{
            board[x][y] = Character.forDigit(num,10);
            return board;
        }
        //遞迴
        for(int i=-1;i<=1;i++){
            for(int j=-1;j<=1;j++){
                if(x + i >= 0 && x + i < board.length&&y + j >=0&&y+j<board[0].length&&board[x+i][y+j]=='E'){
                    board = click(board,x+i,y+j);
                }
            }
        }
        return board;
    }
    private int getNum(char[][] board,int x,int y){
        int num = 0;
        for(int i=-1;i<=1;i++){
            for(int j=-1;j<=1;j++){
                if(x + i >= 0&&y + j >=0&&x+i<board.length&&y+j<board[0].length&&board[x+i][y+j]=='M'){
                    num ++;
                }
            }
        }
        return num;
    }
}

總結 :深度優先遍歷不僅存在樹和圖的資料結構中,還有很多也可以用到它。需要確定的是每一步該怎麼走,有幾個方向可以走。

參考

相關文章