1. 程式人生 > >基於Java的二叉樹的三種遍歷方式的遞迴與非遞迴實現

基於Java的二叉樹的三種遍歷方式的遞迴與非遞迴實現

二叉樹的遍歷方式包括**前序遍歷**、**中序遍歷**和**後序遍歷**,其實現方式包括**遞迴實現**和**非遞迴實現**。 前序遍歷:根節點 | 左子樹 | 右子樹 中序遍歷:左子樹 | 根節點 | 右子樹 後序遍歷:左子樹 | 右子樹 | 根節點 ### 1. 遞迴實現 遞迴方式實現程式碼十分簡潔,三種遍歷方式的遞迴實現程式碼結構相同,只是執行順序有所區別。 前序遍歷: ```java public class preOrderRecur { List res = new ArrayList<>(); public List preOrderTraversal(TreeNode root) { if (root != null) { res.add(root.val); // 根節點 preOrderTraversal(root.left); // 左子樹 preOrderTraversal(root.right); // 右子樹 } return res; } } ``` 中序遍歷: ```java public class inOrderRecur { List res = new ArrayList<>(); public List inOrderTraversal(TreeNode root) { if (root != null) { inOrderTraversal(root.left); // 左子樹 res.add(root.val); // 根節點 inOrderTraversal(root.right); // 右子樹 } } return res; } ``` 後序遍歷: ```java public class inOrderRecur { List res = new ArrayList<>(); public List inOrderTraversal(TreeNode root) { if (root != null) { inOrderTraversal(root.left); // 左子樹 inOrderTraversal(root.right); // 右子樹 res.add(root.val); // 根節點 } } return res; } ``` ### 2. 迭代實現 #### 2.1 使用輔助棧——空間複雜度O(N) ##### 2.1.1 中序遍歷 - 從當前結點一直向其最左孩子搜尋,直到沒有左孩子了停止,這個過程中將路程中的所有結點入棧; - 彈出棧頂元素,將其記錄在答案中,並把當前結點置為彈出元素的右孩子並重復第一步過程。 ```java public class inOrderIterator { List res = new ArrayList<>(); public List inOrderTraversal(TreeNode root) { Stack stack = new Stack<>(); while (root != null || !stack.isEmpty()) { if (root != null) { stack.push(root); root = root.left; } else { TreeNode node = stack.pop(); res.add(node.val); root = node.right; } } return res; } } ``` ##### 2.1.2 前序遍歷 方法1:因為前序遍歷訪問順序是“中-左-右”,所以可以先將根結點壓棧,然後按照下列步驟執行。 - 如果棧不為空,則彈出棧頂元素存入結果中; - 如果彈出元素的右孩子不為空則將右孩子壓棧,然後如果其左孩子也不為空將其左孩子壓棧(因為棧是後入先出的,所以為了達到“中-左-右”的順序,需要先壓入右孩子,再壓入左孩子)。 ```java public class preOrderIterator { List res = new ArrayList<>(); public List inOrderTraversal(TreeNode root) { if (root == null) return res; Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { root = stack.pop(); res.add(root.val); // 右孩子壓棧 if (root.right != null) stack.push(root.right); // 左孩子壓棧 if (root.left != null) stack.push(root.left); } return res; } } ``` 方法2:根據中序遍歷進行微調: ```java public class preOrderIterator { List res = new ArrayList<>(); public List inOrderTraversal(TreeNode root) { Stack stack = new Stack<>(); while (root != null || !stack.isEmpty()) { if (root != null) { res.add(root.val); stack.push(root); root = root.left; } else { TreeNode node = stack.pop(); root = node.right; } } return res; } } ``` ##### 2.1.3 後序遍歷 因為前序遍歷的順序是“左-中-右”,而後序遍歷順序是“左-右-中”,不考慮左結點,區別只是在於中結點和右結點的順序進行了反向而已,因此可以使用前序遍歷的程式碼進行調整,只需要將前序遍歷對左右孩子壓棧的順序反向即可,即先壓入左孩子,再壓入右孩子。除此之外,因為按照這種方法調整得到的遍歷順序為“中-右-左”,正好是後序遍歷的反向順序,因此在獲得遍歷序列後還需進行逆序操作。 ```java public class postOrderIterator { List res = new LinkedList<>(); public List postOrderTraversal(TreeNode root) { if (root == null) return res; Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { root = stack.pop(); // 頭插法 res.add(0, root.val); // 左孩子壓棧 if (root.left != null) stack.push(root.left); // 右孩子壓棧 if (root.right != null) stack.push(root.right); } return res; } } ``` #### 2.2 Morris遍歷——空間複雜度O(1) 該方法的思路簡單說就是,對於每一個結點,找到它左孩子的最右子結點,因為按照正常訪問順序,其左孩子的最有子節點訪問完後就應該訪問其本身了,因此將其左孩子最右子節點的右指標指向它。基本步驟如下: - 如果當前結點左孩子為空,說明最左邊訪問完畢,將其置為其右孩子 - 如果當前結點左孩子不為空,那麼開始嘗試找到該結點左孩子的最右子節點,建立連線關係 - 如果找到的當前結點的左孩子的最右子節點右指標為空,說明還未建立連線關係,是首次訪問當前結點,那麼將該最右結點的右指標指向當前結點,然後當前結點向左孩子走一步繼續重複所有步驟。 - 如果找到的當前結點的左孩子的最右子節點右指標不為空,說明已建立過連線關係,是第二次訪問當前結點,這意味著當前結點的左子樹應該已經全部遍歷完了,此時應恢復連線關係重新置為空,然後當前結點向右孩子走一步繼續重複所有步驟。 該方法雖然保證了O(1)的空間複雜度,但在遍歷過程中改變了部分結點的指向,破壞了樹的結構。 ##### 2.2.1 中序遍歷 ```java public class inOrderMorris { List res = new ArrayList<>(); public List inOrderTraversal(TreeNode root) { TreeNode pre = null; TreeNode cur = root; while (cur != null) { if (cur.left == null) { res.add(cur.val); cur = cur.right; } else { pre = cur.left; while (pre.right != null && pre.right != cur) pre = pre.right; if (pre.right == null) { pre.right = cur; cur = cur.left; } else { res.add(cur.val); pre.right = null; cur = cur.right; } } } return res; } } ``` ##### 2.2.2 前序遍歷 ```java public class preOrderMorris { List res = new ArrayList<>(); public List preOrderTraversal(TreeNode root) { TreeNode pre = null; TreeNode cur = root; while (cur != null) { if (cur.left == null) { res.add(cur.val); cur = cur.right; } else { pre = cur.left; while (pre.right != null && pre.right != cur) pre = pre.right; if (pre.right == null) { res.add(cur.val); pre.right = cur; cur = cur.left; } else { pre.right = null; cur = cur.right; } } } return res; } } ``` ##### 2.2.3 後序遍歷 前序遍歷反向的思想 ```java public class postOrderMorris { List res = new LinkedList<>(); public List postOrderTraversal(TreeNode root) { TreeNode pre = null; TreeNode cur = root; while (cur != null) { if (cur.right == null) { res.add(0, cur.val); cur = cur.left; } else { pre = cur.right; while (pre.left != null && pre.left != cur) pre = pre.left; if (pre.left == null) { res.add(0, cur.val); pre.left = cur; cur = cur.right; } else { pre.left = null; cur = cur.left; } } } return res; } } ```