1. 程式人生 > 其它 >資料結構~基礎2~樹【《二叉樹、二叉搜尋樹、AVL樹、B樹、紅黑樹》的設計】~二叉樹

資料結構~基礎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) 後驅結點:分析同理