再來一篇深度優先遍歷/搜尋總結?
簡介:深度優先搜尋演算法(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;
}
}
總結 :深度優先遍歷不僅存在樹和圖的資料結構中,還有很多也可以用到它。需要確定的是每一步該怎麼走,有幾個方向可以走。