【演算法】二叉樹、N叉樹先序、中序、後序、BFS、DFS遍歷的遞迴和迭代實現記錄(Java版)

宋者為王發表於2020-10-29

        本文總結了刷LeetCode過程中,有關樹的遍歷的相關程式碼實現,包括了二叉樹、N叉樹先序、中序、後序、BFS、DFS遍歷的遞迴和迭代實現。這也是解決樹的遍歷問題的固定套路。


一、二叉樹的先序、中序、後序遍歷
  1、遞迴模板
  (1)先序

1 public void preorder(TreeNode root) {
2    if (root == null) {
3        return;
4    }
5    res.add(root.val);
6    preorder(root.left);
7    preorder(root.right);
8 }

  (2)中序

1 public void inorder(TreeNode root) {
2    if (root == null) {
3       return;
4    }
5    inorder(root.left);
6    res.add(root.val);
7    inorder(root.right);
8 }

  (3)後序

1 public void postorder(TreeNode root) {
2     if (root == null) {
3        return;
4     }
5     postorder(root.left);
6     postorder(root.right);
7     res.add(root.val);
8 }

  2、迭代模板:顯式使用棧
  (1)先序 。也可以使用後文的DFS實現

 1 public void preorder(TreeNode root) {
 2      Deque<TreeNode> stack = new ArrayDeque<>();
 3       while(root !=null || !stack.isEmpty()) {
 4          while(root != null) {
 5             stack.push(root);
 6             res.add(root.val);//儲存結果
 7             root = root.left;
 8          }
 9          root = stack.pop();
10          root = root.right;
11       }
12 }

  (2)中序

 1 public void inorder(TreeNode root) {
 2     Deque<TreeNode> stack = new ArrayDeque<>();
 3     while(root != null || !stack.isEmpty()) {
 4        while(root != null) {
 5           stack.push(root);
 6           root = root.left;
 7        }
 8        root = stack.pop();
 9        res.add(root.val);//儲存結果
10        root = root.right;
11     }
12 }

  (3)後序。先序是根——左——右,而後序是左-右-根,可以將先序改成根-右-左,然後將結果反轉。如下程式碼對比先序的程式碼:

 1 public void postorder(TreeNode root) {
 2     Deque<TreeNode> stack = new ArrayDeque<>();
 3      while(root != null || !stack.isEmpty()) {
 4          while(root != null) {
 5             stack.push(root);
 6             res.add(root.val);//儲存結果
 7             root = root.right;//注意這裡和先序、中序的差別
 8          }
 9          root = stack.pop();
10          root = root.left();
11      }
12      Collections.reverse(res);//將前面的結果反轉
13 }

       當然,上面這種方法比較間接,藉助於先序遍歷的理解。下面採用一種直接的迭代方式:

 1 public void postorder(TreeNode root) {
 2     Deque<TreeNode> stack = new ArrayDeque<>();
 3     TreeNode preNode = new TreeNode();//該節點用於儲存前一個出棧的節點
 4     while (root != null || !stack.isEmpty()) {
 5         //將當前節點的左子樹節點一次入棧
 6         while (root != null) {
 7             stack.push(root);
 8             root = root.left;
 9         }
10         root = stack.peek();
11         //當前節點沒有右孩子了,或者其右孩子已經出棧了,則當前節點也出棧
12         if (root.right == null || root.right == preNode) {
13             root = stack.pop();
14             res.add(root.val);//儲存結果
15             preNode = root; //記錄本次剛輸出的節點
16             root = null;
17         } else {
18             //當前節點還有右孩子,且其右孩子還沒有出棧,則先處理其右孩子
19             root = root.right;
20         }
21     }
22 }

 

二、N叉樹的先序與後序遍歷
  1、遞迴模板
  (1)先序

 1 public void preorder(TreeNode root) {
 2     if (root == null) {
 3         return;
 4     }
 5     res.add(root.val);//儲存結果
 6     if (root.children != null) {
 7         for (int i = 0; i < root.children.size(); i++) {
 8             dfs(root.children.get(i));
 9         }
10     }
11 }

  (2)後序

1 public void postorder(TreeNode root) {
2     if (root == null) {
3         return;
4     }
5     for (int i = 0; i < root.children.size(); i++) {
6         postorder(root.children.get(i));
7     }
8     res.add(root.val);//儲存結果
9 }

  2、迭代模板
  (1)先序。即下文中DFS的實現

 1 public void preorder(Node root) {
 2    Deque<Node> stack = new ArrayDeque<>(); //BFS使用佇列,這裡使用棧
 3    stack.push(root);
 4    while (!stack.isEmpty()) {
 5        root = stack.pop();
 6        res.add(root.val);//儲存結果
 7        int childCount = root.children == null ? 0 : root.children.size();
 8        //這裡需要注意孩子節點入棧的順序
 9         for (int i = childCount - 1; i >= 0; i--) {
10            stack.push(root.children.get(i));
11        }
12    }
13 }

  (2)後序。先序是根——左——右,而後序是左-右-根,可以將先序改成根-右-左,然後將結果反轉。如下程式碼對比先序的程式碼:

 1 public void postorder(Node root) {
 2    List<Integer> result = new ArrayList<>();
 3    Deque<Node> stack = new ArrayDeque<>();
 4    stack.push(root);
 5    while (!stack.isEmpty()) {
 6        root = stack.pop();
 7        result.add(root.val);//儲存結果
 8        int childCount = root.children == null ? 0 : root.children.size();
 9         //還入棧的順序正好和先序遍歷相反
10        for (int i = 0; i < childCount; i++) {
11            stack.push(root.children.get(i));
12        }
13    }
14     //將結果反轉
15    Collections.reverse(result);
16    for (Integer i:result) {
17        System.out.print(i);
18    }
19 }

       目前還沒有找到類似二叉樹直接後序遍歷的方法

 

三、樹的BFS(廣度優先搜尋)和DFS(深度優先搜尋)實現模板
  1、遞迴實現
  (1)BFS
          一般來說不能使用遞迴來實現BFS,因為BFS使用的時佇列實現,而遞迴使用的是棧實現。
  (2)DFS
          普通的N叉樹的DFS包括先序遍歷和後序遍歷,它們的遞迴實現和前文一致。如果是二叉樹,還有中序遍歷,遞迴實現和前文一致。
  2、迭代實現
  (1)BFS。即按層次來遍歷

 1 public void bfs(Node root) {
 2    Queue<Node> queue = new LinkedList<>();
 3    queue.offer(root);
 4    while (!queue.isEmpty()) {
 5        root = queue.poll();
 6        res.add(root.val);//儲存結果
 7        if (root.children != null) {
 8            queue.addAll(root.children);//這裡的addAll後,root的children會從左往右依次入隊。這一點Deque實現的棧正好相反,這一點需要注意
 9        }
10    }
11 }

  (2)DFS
       先序遍歷、中序遍歷(二叉樹)和後序遍歷的迭代方式實現和前文一致。

 

四、圖的BFS和DFS遍歷模板

        樹是圖的一種特殊情況,相比於樹而言圖中可能出現環,所以在遍歷的時候可能重複訪問。所以樹的BFS和DFS實現需要在樹的基礎上維護一個Set來避免訪問重複的節點即可。

  1、BFS

 1 public void graphyBfs(Node root) {
 2     if (root == null) {
 3         return;
 4     }
 5     Set<Node> visitedSet = new HashSet<>(); //維護一個set,儲存已經訪問過的節點
 6     Queue<Node> queue = new LinkedList<>();
 7     queue.offer(root);
 8     while (!queue.isEmpty()) {
 9         root = queue.poll();
10         visitedSet.add(root);//儲存每個已經輸出的節點
11         res.add(root.val);//儲存結果
12         if (root.children == null) {
13             return;
14         }
15         for (int i = 0; i < root.children.size(); i++) {
16             Node tmpNode = root.children.get(i);
17             if (!visitedSet.contains(tmpNode)) {//已經輸出過的節點,則不用再加入
18                 queue.offer(tmpNode);
19             }
20         }
21     }
22 }

  2、DFS
       迭代方式、遞迴方式,都維護一個set來儲存已經訪問過的節點,在輸出節點時儲存到set中,在新增節點時新增去重操作即可。

 

相關文章