資料結構~基礎2~樹【《二叉樹、二叉搜尋樹、AVL樹、B樹、紅黑樹》的設計】~二叉樹
資料結構~基礎2~樹【《二叉樹、二叉搜尋樹、AVL樹、B樹、紅黑樹》的設計】~二叉樹
● 樹的形狀:【左子樹(左區間)】 根(父結點)【右子樹(右區間)】
❀ 為啥遍歷是不斷沿著左子樹爬下下一層~~~為了實現拿到當前層的第一個結點。
❀ 對於樹的遍歷,到下一層,在形式上是先到了“根”(父結點)上。
1、二叉樹之通用的介面【遍歷】:
●前序遍歷: 根(父) 左子樹 右子樹 | 根(父) 右子樹 左子樹 【前序只需滿足 根最先訪問】
● 中序遍歷:左子樹 根(父) 右子樹 | 右子樹 根(父) 左子樹 【中序只需滿足 根中間訪問】
●後序遍歷: 左子樹
●層序遍歷: 一層一層的結點從左到右進行訪問。
❀ 二叉樹的通用介面:遍歷[前序、中序、後序、層序、允許外界遍歷二叉樹的元素(設計介面)+增強遍歷] + 前驅、後驅結點
1,前序遍歷:
■ 遞迴:根 左 右
■ 迭代:【使用棧】:不斷地下一層【通過node.left方式】:不斷地拿到下一層的根,將根資料進行新增,
直到到達最後一層【node.left = null】,pop出當前的根結點,通過其切換到右子樹。
□ 前序遍歷的程式碼:
//遞迴實現
// 前序遍歷需要傳入一個結點(根結點),然後才能開始遍歷private void preorderTraversal(Node<E> node) { // 不斷地遍歷,終有一空便結束 if (node == null) return; System.out.println(node.elmement); preorderTraversal(node.left); preorderTraversal(node.right); }
//迭代實現【並將結果儲存到List】
public List<Integer> preorderTraversal1(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); if (root == null) { return res; } //遍歷過的點,不要了可以pop()掉,不斷的pop(),然後才能拿到當前結點的右結點(先是不斷的更新結點為左結點) //然後沒有左結點了,開始pop() 一層(一個結點) Deque<TreeNode> stack = new LinkedList<TreeNode>(); TreeNode node = root; while (!stack.isEmpty() || node != null) { while (node != null) { res.add(node.val); stack.push(node); node = node.left; } node = stack.pop(); node = node.right; } return res; }
2,中序遍歷:
■ 遞迴:左 根 右
■ 迭代:【使用棧】:不斷地下一層【通過node.left方式】:不斷地拿到下一層的根,
直到到達最後一層【node.left = null】,pop出當前的根結點,將當前根的資料進行新增,通過其切換到右子樹。
□ 中序遍歷的程式碼:
// 遞迴:
private void inorderTraversal(Node<E> node) { if (node == null) return; inorderTraversal(node.left); System.out.println(node.elmement); inorderTraversal(node.right); }
// 迭代 public List<Integer> inorderTraversal2(TreeNode root) { List<Integer> list2 = new ArrayList<>(); if (root == null) return list2; Deque<TreeNode> stack = new LinkedList<>(); TreeNode node = root; while(node != null || !stack.isEmpty() ) { while(node != null) { stack.push(node); node = node.left; } node = stack.pop(); list2.add(node.val); //已經拿到當前結點了 node = node.right; } return list2; }
3,後序遍歷:
■ 遞迴:左 右 根
■ 迭代:【使用棧】:不斷地下一層【通過node.left方式】:不斷地拿到下一層的根,
直到到達最後一層【node.left = null】,pop出當前的根結點,考慮右結點:【情況一:右邊結點存在,將根push回去,切換到右子樹】
【情況二:右邊結點不存在或者當前結點的右結點是上一個遍歷過的結點,將當前結點(根)的資料進行新增,並記錄前一個結點。】
□ 後序遍歷的程式碼:
// 遞迴:
private void postorderTraversal(Node<E> node) { if (node == null) return; postorderTraversal(node.left); postorderTraversal(node.right); System.out.println(node.elmement); }
// 迭代 public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>(); if(root == null) return list; Deque<TreeNode> stack = new LinkedList<>(); TreeNode prev = null; while(!stack.isEmpty() || root != null) { while(root != null) { stack.push(root); root = root.left; } root = stack.pop();
//考慮右結點 //右結點不存在,或者右結點是前一個遍歷過的結點prev if (root.right == null || root.right == prev) { list.add(root.val); prev = root; root = null; //不加:超出記憶體 } else { //右結點存在 stack.push(root); root = root.right; } } return list; }
4,層序遍歷:
■ 迭代:【使用佇列】(使用佇列理由:一層一層從左邊到右邊【父節點A 先於 父結點B,則父節點A 的孩子 會先於 父結點B 的孩子】):
首先先根入隊,然後開始不斷地取出當前結點,判斷當前結點是否有左結點,有入隊,是否有右結點,有入隊。
// 層序遍歷【迭代】 public void levelOrderTraversal() { if (root == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); // 只要佇列不為空:就不斷的出隊,然後入隊左右子節點 while (!queue.isEmpty()) { Node<E> node = queue.poll(); System.out.println(node.elmement); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } }
5,允許外界遍歷二叉樹的元素(設計介面)+ 增強遍歷【這裡以層序遍歷 和 後序遍歷(前序、中序跟後序一樣)】
(1)層序遍歷:
① 層序遍歷【允許外界遍歷二叉樹的元素的情況的介面設計】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
② 層序遍歷【允許外界遍歷二叉樹的元素的情況的介面設計】+ 增強遍歷:
增強遍歷:通過介面方法的返回值,控制遍歷的停止(so 介面方法需要寫成可以被控制的,例如布林型別)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(2)後序遍歷:
① 後序遍歷【允許外界遍歷二叉樹的元素的情況的介面設計】跟 層序一致(略)
② 後序遍歷【允許外界遍歷二叉樹的元素的情況的介面設計】+ 增強遍歷:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
6,前驅、後驅結點:
■前提是:中序遍歷才有所謂的前驅和後驅結點。
(1) 前驅結點:中序遍歷時的前一個結點。
即:前驅結點(就是比當前結點小的前一個結點)。
●哪個位置的結點有機會有前驅(根 和 右):
●“前一個結點”:需要離得最近。
①根(看左,找左區間最大的,離得近)
②右(看根,找根)
●方法(介面):
publicNode<E> predecessor(Node<E> node){
//當前傳入結點(可,需要判斷一下:是否有左(有左即可,不需要有右))
node = node.left;
//① 若node 非空:則作為“根”,去左區間找最大的
//② 若node 空:找根(“父結點”),通過判斷是否構成“根-右”的關係。【找的過程是逆思維:因為需要遍歷 while(“根-左”), 跳出迴圈則是找到了“根-右”的關係】
}
● 具體程式碼:
// 前驅結點 public Node<E> predecessor(Node<E> node) { // 有左節點(left.right.right.right...) Node<E> p = node.left; if (p != null) { while (p.right != null) { p = p.right; } return p; } // 沒有左節點(檢視找到第一個父結點的右結點等於當前結點) // 若一直是父節點左,就繼續上一層的父節點 while (node.parent != null && node.parent.left == node) { node = node.parent; } // 來到這裡:要麼父節點是null,要麼是 當前結點是父節點的右結點 return node.parent; }
(2) 後驅結點:分析同理