資料結構-Tree

weixin_33850890發表於2018-07-01

鄭重說明:儘管網路上有很多的資源可以借鑑,但是筆者還是需要很多的幫助才能寫出這些總結筆記
1️⃣首先也是最重要的慕課網給了我巨大的幫助,自從第一次開啟慕課網以後,從此就在我心裡播下了一顆學習的種子;
2️⃣要特別鳴謝慕課網的liuyubobobo老師,從他那裡我"偷了"許多的想法放到這篇筆記裡邊;
3️⃣本文內容直接出自liuyubobobo老師的課程<玩轉資料結構 從入門到進階>
4️⃣清醒時做事,糊塗時學習,祝大家都能夢想成真;

一 為什麼要研究樹結構。

樹結構是計算機世界中非常高效的一種資料結構,也是應用非常廣泛的資料結構;

1⃣️ 什麼是樹結構?
12420747-629626ec4657e005.png
樹結構圖示.png

2⃣️ 樹結構的應用場景。
12420747-d5ec58d99001fad0.png
計算機中檔案目錄結構圖示.png
12420747-1e74be6da243b8af.png
公司組織架構圖示.png

以上這些應用場景都是樹型結構的使用場景,樹結構的特點就是層次清晰,高效。
3⃣️ 樹結構常用的型別都有什麼?
二分搜尋樹 平衡二叉樹 線段樹 Trie等都是很常用的樹型資料結構。

二 二分搜尋樹基礎

二分搜尋樹是樹型結構中的一種,要了解二分搜尋樹需要先說一下二叉樹結構;

1⃣️ 什麼是二叉樹?
12420747-da67c3df30416ea6.png
二叉樹圖示.png
二叉樹和連結串列是一樣,都是動態資料結構,所以我們在建立二叉樹的資料就不需要指定容量;

2⃣️ 二叉樹的組成圖示。
12420747-ae0c5bbaf63571e3.png
二叉樹的組成圖示.png

根節點:二叉樹具有一個唯一的根節點;
左右節點:二叉樹每個節點最多有兩個節點(左節點和右節點);
父節點:二叉樹每個節點最多有一個父節點;
3⃣️ 二叉樹具有天然遞迴結構。
12420747-8e887256217d5fb5.png
二叉樹具有天然遞迴結構.png

為什麼說二叉樹具有天然的遞迴結構呢?因為對於一個父節點來說,下邊的每個節點(左節點和右節點)又是一個新的二叉樹;
4⃣️ 二叉樹不一定都是滿的。

在介紹非滿二叉樹前,先來介紹一下滿二叉樹
12420747-28dc132836ca8645.png
滿二叉樹圖示.png
所謂的滿二叉樹就是指,每個節點都有左右兩個節點,直到葉子節點為止;
12420747-3eecda6aae17eeed.png
非滿二叉樹圖示1.png
12420747-d4e070d24a788b6d.png
非滿二叉樹圖示2.png
12420747-621ae2d8cef65d65.png
非滿二叉樹圖示3.png
12420747-61f8ce503f7e1c69.png
最特殊的二叉樹.png
有時候一個節點甚至是空也是一個二叉樹;
5⃣️ 二分搜尋樹
在介紹完二叉樹以後我們來看一下二分搜尋樹;
12420747-44e3655519345872.png
二分搜尋樹圖示.png
12420747-9eb70fc6aea3ce9a.png
二分搜尋樹概念圖示.png
二分搜尋樹是二叉樹的一種,二分搜尋樹的特點是每個節點的值都大於左子樹的所有節點,每個節點的值都小於右子樹中所有節點的值,然後每個父節點下邊的子樹也是一個二分搜尋樹,同時二分搜尋樹每個節點的值必須滿足可比較性;

二分搜尋樹既然是二叉樹的一種,也就意味著二分搜尋樹也不一定都是滿的,更多的時候應該是不滿的情況;
12420747-4c0169cfa0c7f391.png
不規則的二分搜尋樹.png

三 程式碼實現一個二分搜尋樹

package com.mufeng.binarySearchTree;

/**
 * Created by wb-yxk397023 on 2018/6/30.
 */
public class BST<E extends Comparable<E>> {

    private class Node{
        public E e;
        public Node left, right;

        public Node(E e){
            this.e = e;
            this.left = null;
            this.right = null;
        }
    }

    private Node root;
    private int size;

    public BST(){
        root = null;
        size = 0;
    }

    public int getSize(){
        return size;
    }
    
    public boolean isEmpty(){
        return size == 0;
    }
}

四 向二分搜尋樹中新增元素

1⃣️ 新增新元素圖示
12420747-95f466a80d7c7c26.png
一個空的樹
12420747-fa4fc2a44fdfdd62.png
新新增的元素將會成為根

當向一個樹中新增元素的時候如果這個樹為空,那麼第一個新增的元素將會變成這個樹的root節點;
12420747-578850286f50e038.png
繼續進行新增操作
12420747-b7696d9d8349594a.png
通過對比找到位置
向一個有元素的樹結構中新增一個新元素的時候,將會通過對比找到新新增元素的位置(比根節點小的元素放在左子樹中,反之放在右子樹中);
12420747-499c695251208971.png
新增了多個元素的樹
12420747-152ba21441c79a75.png
繼續新增新元素
12420747-d4cae3f2f440f34d.png
通過對比找到具體的位置

繼續通過對比找到新新增元素的位置;
12420747-88404d961223a094.png
繼續新增元素
12420747-29fb975a24aff727.png
通過對比找到位置
通過圖示不難發現,向樹中新增元素是一個比較的操作,通過對比將新新增的元素放在合適的位置上邊;
補充:我們的新增操作是不包含重複操作的;但是如果想要包含重複元素的只需要改變一個對比的條件就可以了,比如左子樹可以放置小於或者等於根節點的元素或者右子樹中放置大於或者等於根的元素即可;

2⃣️ 程式碼實現

/**
     * 向二分搜尋樹中新增新的元素e
     * @param e
     */
    public void add(E e){
        if (root == null){
            root = new Node(e);
            size++;
        }else {
            add(root, e);
        }
    }

    /**
     * 向以node為根的二分搜尋樹中插入元素e,遞迴演算法
     * @param node
     * @param e
     */
    public void add(Node node, E e){
        // 遞迴的終止條件
        if (e.equals(node.e)){
            return;
        }else if (e.compareTo(node.e) < 0 && node.left == null){
            node.left = new Node(e);
            size++;
            return;
        }else if (e.compareTo(node.e) > 0 && node.right == null){
            node.right = new Node(e);
            size++;
            return;
        }
        
        // 遞迴呼叫
        if (e.compareTo(node.e) < 0){
            add(node.left, e);
        }
        add(node.right, e);
    }

由於二分搜尋樹的結構非常適合使用遞迴的操作,所以我們的新增操作直接使用遞迴的方式進行實現,但是上邊的程式碼遞迴的終止條件顯得有些臃腫,所以我們需要對新增操作的程式碼邏輯進行優化;

/**
     * 向二分搜尋樹中新增新的元素e
     * @param e
     */
    public void add(E e){
        root = add(root, e);
    }

    /**
     * 向以node為根的二分搜尋樹中插入元素e,遞迴演算法
     * 返回插入新節點後二分搜尋樹的根
     * @param node
     * @param e
     * @return
     */
    public Node add(Node node, E e){
        // 遞迴的終止條件
        if (node == null){
            size++;
            return new Node(e);
        }

        // 遞迴呼叫
        if (e.compareTo(node.e) < 0){
            node.left = add(node.left, e);
        }else if (e.compareTo(node.e) > 0){
            node.right = add(node.right, e);
        }

        return node;
    }

五 二分搜尋樹的查詢操作

如果大家理解了二分搜尋樹的新增操作,那麼就可以很容易的理解二分搜尋樹的查詢操作;
/**
     * 二分搜尋樹中是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e){
        return contains(root, e);
    }

    /**
     * 查詢以node為節點的二分搜尋樹中是否包含元素e
     * @param node
     * @param e
     * @return
     */
    private boolean contains(Node node, E e){
        if (node == null){
            return false;
        }else if (e.compareTo(node.e) == 0){
            return true;
        }else if (e.compareTo(node.e) < 0){
            return contains(node.left, e);
        }
        return contains(node.right, e);
    }

六 二分搜尋樹的遍歷

1⃣️ 前序遍歷

前序遍歷就是在遍歷左右子樹之前先遍歷當前節點,對於二分搜尋樹來說,前序遍歷是最自然的遍歷方式也是最常用的遍歷方式
12420747-c2cbb725481860bb.png
二叉樹圖示
12420747-9e4cf4c0c094c705.png
二分搜尋樹模型圖
遍歷操作的意思就是把所有節點都訪問一遍,遍歷操作的原因可能是基於業務的相關的,線上性結構下,遍歷是極其容易的但是在樹結構下可能有點不太一樣,這是因為在執行遍歷二分搜尋樹的時候左右兩個子樹也需要進行遍歷。
/**
     * 二叉樹的前序遍歷
     */
    public void preOrder(){
        preOrder(root);
    }

    /**
     * 前序遍歷以node為根的二分搜尋樹,遞迴演算法
     * @param node
     */
    private void preOrder(Node node){
        if (node == null){
            return;
        }

        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

重寫toString

/**
     * 以前序遍歷的方式重寫toString
     * @return
     */
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        generateBSTString(root, 0, res);
        return res.toString();
    }

    /**
     * 生成以node為根節點,深度為depth的描述二叉樹的字串
     * @param node
     * @param depth
     * @param res
     */
    private void generateBSTString(Node node, int depth, StringBuilder res){
        if (node == null){
            res.append(generateDepthString(depth) + "null\n");
            return;
        }
        res.append(generateDepthString(depth) + node.e + "\n");
        generateBSTString(node.left, depth + 1, res);
        generateBSTString(node.right, depth + 1, res);
    }

    private String generateDepthString(int depth){
        StringBuilder res = new StringBuilder();

        for (int i = 0; i < depth; i++){
            res.append("--");
        }
        return res.toString();
    }

2⃣️ 中序遍歷

既然有前序遍歷自然就有中序遍歷,只需要改變遍歷的順序即可實現中序遍歷,中序遍歷的順序為先遍歷左子樹,然後遍歷當前節點最後遍歷右子樹即可;
12420747-6e324749854b3ca6.png
中序遍歷圖示
/**
     * 二分搜尋樹的中序遍歷
     */
    public void inOrder(){
        inOrder(root);
    }

    /**
     * 中序遍歷以node為根的二分搜尋樹,遞迴演算法
     * @param node
     */
    private void inOrder(Node node){
        if (node == null){
            return;
        }
        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }

3⃣️ 後序遍歷
後序遍歷的原理在遍歷當前節點之前先遍歷左右子樹,然後在遍歷當前節,得到的結果就是後序遍歷的結果;
後續遍歷的適用場景:比如釋放二分搜尋樹的記憶體這樣的操作需要用到後序遍歷,因為要釋放記憶體肯定需要先釋放左右子樹在釋放當前節點;

/**
     * 二分搜尋樹的後序遍歷
     */
    public void postOrder(){
        postOrder(root);
    }

    /**
     * 後續遍歷以node為根的二分搜尋樹,遞迴演算法
     * @param node
     */
    private void postOrder(Node node){
        if (node == null){
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

4⃣️ 深度優先遍歷
非遞迴實現二分搜尋樹的前序遍歷

// 二分搜尋樹的非遞迴前序遍歷
    public void preOrderNR(){

        if(root == null)
            return;

        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            Node cur = stack.pop();
            System.out.println(cur.e);

            if(cur.right != null)
                stack.push(cur.right);
            if(cur.left != null)
                stack.push(cur.left);
        }
    }

在二分搜尋樹這樣的結構中,使用遞迴可以很簡單的實現功能,但是非遞迴的話實現起來就會有一點困難,當使用非遞迴的方式實現前序遍歷的話,需要藉助棧來實現,這樣的遍歷方式也被稱為深度優先遍歷;
5⃣️ 廣度優先遍歷

// 二分搜尋樹的層序遍歷
    public void levelOrder(){

        if(root == null)
            return;

        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
            Node cur = q.remove();
            System.out.println(cur.e);

            if(cur.left != null)
                q.add(cur.left);
            if(cur.right != null)
                q.add(cur.right);
        }
    }

二分搜尋樹的層序遍歷也叫做廣度優先遍歷;深度優先遍歷以及廣度優先遍歷更多的是應用與演算法中的,所以這裡不做深入的說明;

七 二分搜尋樹中的刪除操作

1⃣️ 刪除二分搜尋樹中的最大值和最小值

在二分搜尋樹中執行刪除操作是相對於其他操作來說比較麻煩的,我們先來一端圖示分析一下;
12420747-a418c31b707f8690.png
一個完整的二分搜尋樹

以此圖為例,如果要刪除這個二分搜尋樹的最小以及最大節點,我們需要從根節點開始執行查詢操作,如果要刪除最小值的話,就需要一直向左進行查詢,直到找到最小值即可,刪除最大值也是一樣的操作;
12420747-779160cb5dc51491.png
刪除後的二分搜尋樹

此處需要注意的是,如果我們在進行刪除此樹的最小值的話,則16將會被刪除,22將作為此樹根節點28的左子樹的根節點。
需要注意的是,刪除操作的前提是需要先找到待刪除的樹的最小值與最大值,所以我們需要先實現這個邏輯才能進行刪除的操作;

/**
     * 尋找二分搜尋樹的最小元素
     * @return
     */
    public E minimum(){
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        Node miniNode = minimum(root);
        return miniNode.e;
    }

    /**
     * 返回以node為根的二分搜尋樹的最小值所在的節點
     * @param node
     * @return
     */
    private Node minimum(Node node){
        if (node.left == null){
            return node;
        }

        return minimum(node.left);
    }

    /**
     * 尋找二分搜尋樹的最大元素
     * @return
     */
    public E maxmum(){
        if (size == 0){
            throw new IllegalArgumentException("BST is empty");
        }

        Node maxNode = maxmum(root);
        return maxNode.e;
    }

    /**
     * 返回以node為根的二分搜尋樹的最大值所在的節點
     * @param node
     * @return
     */
    private Node maxmum(Node node){
        if (node.right == null){
            return node;
        }

        return maxmum(node.right);
    }

最大和最小的值都找到以後我們就可以進行刪除的操作了,但是刪除的時候我們需要考慮特殊的情況,比如如圖所示:
12420747-1391454bfc621beb.png
待刪除最小節點的二分搜尋樹

比如這棵樹中的最小值是13,我們找到後可以直接刪除,因為13這個節點是一個葉子節點,刪除之後沒有影響
12420747-a380c9330d79e711.png
刪除最小值後的二分搜尋樹
如果此時在刪除15也是一樣的,15這個節點下邊的也沒有了節點,刪除15對於我們來說也沒有影響
12420747-eecd77ba03025109.png
刪除最小值後的二分搜尋樹
如果此時我們要刪除22這個節點,但是22不是葉子節點,這個時候我們就需要一些處理才能刪除,因為22這個節點下邊還有元素,我們可以將22下邊的整個右子樹作為根節點的左子樹即可
12420747-620b605fcf573e89.png
刪除22節點後的二分搜尋樹

對於刪除二分搜尋樹中的最大節點也是一樣的操作,這裡就不再進行圖示了。

/**
     * 從二分搜尋樹中刪除最小值所在節點, 返回最小值
     * @return
     */
    public E removemin(){
        E ret = minimum();
        root = removemin(root);
        return ret;
    }

    /**
     * 刪除掉以node為根的二分搜尋樹中的最小節點
     * 返回刪除節點後新的二分搜尋樹的根
     * @param node
     * @return
     */
    private Node removemin(Node node){
        if (node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }

        node.left = removemin(node.left);
        return node;
    }

    /**
     * 從二分搜尋樹中刪除最大值所在節點,返回最大值
     * @return
     */
    public E removeMax(){
        E ret = maxmum();
        root = removeMax(root);
        return ret;
    }

    /**
     * 刪除掉以node為根的二分搜尋樹中的最大節點
     * 返回刪除節點後新的二分搜尋樹的根
     * @param node
     * @return
     */
    private Node removeMax(Node node){
        if (node.right == null){
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }
        
        node.left = removeMax(node.right);
        return node;
    }

2⃣️ 刪除二分搜尋樹中的任意元素
刪除二分搜尋樹中任意元素分以下幾種情況:

1 刪除只有左子樹的元素;
12420747-206afbeefc599ed4.png
刪除只有左子樹的元素
2 刪除只有右子樹的元素;
12420747-0d471708458a99fb.png
刪除只有右子樹的元素
3 刪除有左右子樹的元素;
12420747-ff95e861b2fe0edc.png
刪除有左右子樹的元素

第一種情況和第二種情況都比較簡單,和我們刪除最小值和最大值的邏輯基本一樣,但是第三種情況則相對複雜一些,這裡我們採取一種後繼的做法,請看圖示
12420747-a055672e0444754e.png
刪除任意元素1
假設我們要刪除58這個元素,但是這個元素有左右兩個子樹,我們採用後繼的方式意思就是在58節點的右子樹中找到一個距離58最近的元素來代替58
12420747-6e8b77011a45c936.png
刪除任意元素2
在這個右子樹中我們找到59這個元素
12420747-5de352f6f22ed0c5.png
刪除任意元素3
將59這個元素取出並作為50和60兩個節點的根節點
12420747-e3b3eae5e6922b48.png
刪除任意元素4
刪除掉58這個元素
12420747-26d976e191681caa.png
刪除任意元素5
將59這個元素掛在在根元素下
12420747-1d4c24c7b1198161.png
刪除任意元素6
這樣就完成了這個刪除操作;
/**
     * 從二分搜尋樹中刪除元素為e的節點
     * @param e
     */
    public void remove(E e){
        root = remove(root, e);
    }

    /**
     * 刪除掉以node為根的二分搜尋樹中值為e的節點, 遞迴演算法
     * 返回刪除節點後新的二分搜尋樹的根
     * @param node
     * @param e
     * @return
     */
    private Node remove(Node node, E e){

        if( node == null )
            return null;

        if( e.compareTo(node.e) < 0 ){
            node.left = remove(node.left , e);
            return node;
        }
        else if(e.compareTo(node.e) > 0 ){
            node.right = remove(node.right, e);
            return node;
        }
        else{   // e.compareTo(node.e) == 0

            // 待刪除節點左子樹為空的情況
            if(node.left == null){
                removeMin(node);
            }

            // 待刪除節點右子樹為空的情況
            if(node.right == null){
                removeMax(node);
            }

            // 待刪除節點左右子樹均不為空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

八 二分搜尋樹相關程式碼

package com.mufeng.binarySearchTree;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

/**
 * Created by wb-yxk397023 on 2018/6/30.
 */
public class BST<E extends Comparable<E>> {

    private class Node{
        public E e;
        public Node left, right;

        public Node(E e){
            this.e = e;
            this.left = null;
            this.right = null;
        }
    }

    private Node root;
    private int size;

    public BST(){
        root = null;
        size = 0;
    }

    public int size(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 向二分搜尋樹中新增新的元素e
     * @param e
     */
    public void add(E e){
        root = add(root, e);
    }

    /**
     * 向以node為根的二分搜尋樹中插入元素e,遞迴演算法
     * 返回插入新節點後二分搜尋樹的根
     * @param node
     * @param e
     * @return
     */
    public Node add(Node node, E e){
        // 遞迴的終止條件
        if (node == null){
            size++;
            return new Node(e);
        }

        // 遞迴呼叫
        if (e.compareTo(node.e) < 0){
            node.left = add(node.left, e);
        }else if (e.compareTo(node.e) > 0){
            node.right = add(node.right, e);
        }

        return node;
    }

    /**
     * 二分搜尋樹中是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e){
        return contains(root, e);
    }

    /**
     * 查詢以node為節點的二分搜尋樹中是否包含元素e
     * @param node
     * @param e
     * @return
     */
    private boolean contains(Node node, E e){
        if (node == null){
            return false;
        }else if (e.compareTo(node.e) == 0){
            return true;
        }else if (e.compareTo(node.e) < 0){
            return contains(node.left, e);
        }
        return contains(node.right, e);
    }

    /**
     * 二分搜尋樹的前序遍歷
     */
    public void preOrder(){
        preOrder(root);
    }

    /**
     * 前序遍歷以node為根的二分搜尋樹,遞迴演算法
     * @param node
     */
    private void preOrder(Node node){
        if (node == null){
            return;
        }

        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

    /**
     * 二分搜尋樹的非遞迴前序遍歷
     */
    public void preOrderNR(){

        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            Node cur = stack.pop();
            System.out.println(cur.e);

            if(cur.right != null)
                stack.push(cur.right);
            if(cur.left != null)
                stack.push(cur.left);
        }
    }

    /**
     * 二分搜尋樹的中序遍歷
     */
    public void inOrder(){
        inOrder(root);
    }

    /**
     * 中序遍歷以node為根的二分搜尋樹,遞迴演算法
     * @param node
     */
    private void inOrder(Node node){
        if (node == null){
            return;
        }
        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }

    /**
     * 二分搜尋樹的後序遍歷
     */
    public void postOrder(){
        postOrder(root);
    }

    /**
     * 尋找二分搜尋樹的最小元素
     * @return
     */
    public E minimum(){
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        Node miniNode = minimum(root);
        return miniNode.e;
    }

    /**
     * 返回以node為根的二分搜尋樹的最小值所在的節點
     * @param node
     * @return
     */
    private Node minimum(Node node){
        if (node.left == null){
            return node;
        }

        return minimum(node.left);
    }

    /**
     * 尋找二分搜尋樹的最大元素
     * @return
     */
    public E maxmum(){
        if (size == 0){
            throw new IllegalArgumentException("BST is empty");
        }

        Node maxNode = maxmum(root);
        return maxNode.e;
    }

    /**
     * 返回以node為根的二分搜尋樹的最大值所在的節點
     * @param node
     * @return
     */
    private Node maxmum(Node node){
        if (node.right == null){
            return node;
        }

        return maxmum(node.right);
    }

    /**
     * 從二分搜尋樹中刪除最小值所在節點, 返回最小值
     * @return
     */
    public E removemin(){
        E ret = minimum();
        root = removeMin(root);
        return ret;
    }

    /**
     * 刪除掉以node為根的二分搜尋樹中的最小節點
     * 返回刪除節點後新的二分搜尋樹的根
     * @param node
     * @return
     */
    private Node removeMin(Node node){
        if (node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    /**
     * 從二分搜尋樹中刪除最大值所在節點,返回最大值
     * @return
     */
    public E removeMax(){
        E ret = maxmum();
        root = removeMax(root);
        return ret;
    }

    /**
     * 刪除掉以node為根的二分搜尋樹中的最大節點
     * 返回刪除節點後新的二分搜尋樹的根
     * @param node
     * @return
     */
    private Node removeMax(Node node){
        if (node.right == null){
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }

        node.left = removeMax(node.right);
        return node;
    }

    /**
     * 後續遍歷以node為根的二分搜尋樹,遞迴演算法
     * @param node
     */
    private void postOrder(Node node){
        if (node == null){
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

    /**
     * 二分搜尋樹的層序遍歷
     */
    public void levelOrder(){

        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
            Node cur = q.remove();
            System.out.println(cur.e);

            if(cur.left != null)
                q.add(cur.left);
            if(cur.right != null)
                q.add(cur.right);
        }
    }

    /**
     * 從二分搜尋樹中刪除元素為e的節點
     * @param e
     */
    public void remove(E e){
        root = remove(root, e);
    }

    /**
     * 刪除掉以node為根的二分搜尋樹中值為e的節點, 遞迴演算法
     * 返回刪除節點後新的二分搜尋樹的根
     * @param node
     * @param e
     * @return
     */
    private Node remove(Node node, E e){

        if( node == null )
            return null;

        if( e.compareTo(node.e) < 0 ){
            node.left = remove(node.left , e);
            return node;
        }
        else if(e.compareTo(node.e) > 0 ){
            node.right = remove(node.right, e);
            return node;
        }
        else{   // e.compareTo(node.e) == 0

            // 待刪除節點左子樹為空的情況
            if(node.left == null){
                removeMin(node);
            }

            // 待刪除節點右子樹為空的情況
            if(node.right == null){
                removeMax(node);
            }

            // 待刪除節點左右子樹均不為空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

    /**
     * 以前序遍歷的方式重寫toString
     * @return
     */
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        generateBSTString(root, 0, res);
        return res.toString();
    }

    /**
     * 生成以node為根節點,深度為depth的描述二叉樹的字串
     * @param node
     * @param depth
     * @param res
     */
    private void generateBSTString(Node node, int depth, StringBuilder res){
        if (node == null){
            res.append(generateDepthString(depth) + "null\n");
            return;
        }
        res.append(generateDepthString(depth) + node.e + "\n");
        generateBSTString(node.left, depth + 1, res);
        generateBSTString(node.right, depth + 1, res);
    }

    private String generateDepthString(int depth){
        StringBuilder res = new StringBuilder();

        for (int i = 0; i < depth; i++){
            res.append("--");
        }
        return res.toString();
    }
}

相關文章