二分搜索樹(Binary Search Tree)

二叉樹
跟鏈表一樣诽凌,二叉樹也是一種動態(tài)數(shù)據(jù)結(jié)構(gòu)毡熏,即,不需要在創(chuàng)建時指定大小侣诵。
跟鏈表不同的是痢法,二叉樹中的每個節(jié)點,除了要存放元素e杜顺,它還有兩個指向其它節(jié)點的引用财搁,分別用Node left和Node right來表示。
類似的躬络,如果每個節(jié)點中有3個指向其它節(jié)點的引用尖奔,就稱其為"三叉樹"...
二叉樹具有唯一的根節(jié)點。
二叉樹中每個節(jié)點最多指向其它的兩個節(jié)點穷当,我們稱這兩個節(jié)點為"左孩子"和"右孩子"提茁,即每個節(jié)點最多有兩個孩子。
一個孩子都沒有的節(jié)點馁菜,稱之為"葉子節(jié)點"茴扁。
二叉樹的每個節(jié)點,最多只能有一個父親節(jié)點汪疮,沒有父親節(jié)點的節(jié)點就是"根節(jié)點"峭火。
二叉樹的形象化描述如下圖:


image.png

二叉樹具有天然的遞歸結(jié)構(gòu)毁习。
1.每個節(jié)點的"左子樹"也是一棵二叉樹,每個節(jié)點的"右子樹"也是一棵二叉樹躲胳。

  1. 二叉樹不一定是"滿的"蜓洪,即,某些節(jié)點可能只有一個子節(jié)點坯苹;更極端一點,整棵二叉樹可以僅有一個節(jié)點摇天;在極端一點粹湃,整棵二叉樹可以一個節(jié)點都沒有;

實現(xiàn)二分搜索樹

二分搜索樹的構(gòu)造函數(shù)泉坐、getSize方法为鳄、isEmpty方法及add方法的實現(xiàn)邏輯如下:

    public class BST<E extends Comparable> {

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

            // 構(gòu)造函數(shù)
            public Node(E e) {
                this.e = e;
                left = null;
                right = null;
            }
        }

        private Node root;
        private int size;  // 記錄二分搜索樹中存儲的元素個數(shù)

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

        // 實現(xiàn)size方法
        public int size() {
            return size;
        }

        // 實現(xiàn)isEmpty方法
        public boolean isEmpty() {
            return size == 0;
        }

        // 實現(xiàn)add方法
        public void add(E e) {
            root = add(root, e);
        }

        // 向以node為根的二分搜索樹中插入元素e,遞歸算法
        // 返回插入新節(jié)點后二分搜索樹的根
        private 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;
        }
    }

二分搜索樹的contains方法實現(xiàn)邏輯如下:

    // 實現(xiàn)contains方法腕让,判斷二分搜索樹中是否包含元素e
    public boolean contains(E e) {
        return contains(root, e);
    }

    // 判斷以node為根的二分搜索樹中是否包含元素e
    private boolean contains(Node node, E e) {
        if (node == null) {
            return false;
        }
        if (e.compareTo(node.e) == 0) {
            return true;
        } else if (e.compareTo(node.e) < 0) {
            return contains(node.left, e);
        } else {
            return contains(node.right, e);
        }
    }

二分搜索樹的遍歷操作孤钦,遍歷操作就是把所有節(jié)點都訪問一遍
前序遍歷的業(yè)務(wù)邏輯如下:

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

    private void preOrder(Node node) {

        if (node == null) {
            return;
        }

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

中序遍歷的業(yè)務(wù)邏輯如下:

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

    // 中序遍歷以node為根的二分搜索樹,遞歸算法
    private void inOrder(Node node) {

        if (node == null) {
            return;
        }

        inOrder(node.left);
        System.out.print(node.e);
        inOrder(node.right);

    }

后序遍歷的業(yè)務(wù)邏輯如下:

    // 二分搜索樹的后序遍歷
    public void postOrder() {
        postOrder(root);
    }

    // 后序遍歷以node為根的二分搜索樹纯丸,遞歸算法
    private void postOrder(Node node) {

        if (node == null) {
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.print(node.e);

    }

簡單測試如下:

    public class Main {

        public static void main(String[] args) {
            BST<Integer> bst = new BST<>();
            int[] nums = {5, 3, 6, 8, 4, 2};
            for (int num : nums) {
                // 測試add方法
                bst.add(num);
            }
            // 測試前序遍歷
            bst.preOrder();
            System.out.println();
            // 測試中序遍歷
            bst.inOrder();
            System.out.println();
            // 測試后序遍歷
            bst.postOrder();

        }
    }

輸出結(jié)果:

532468
234568
243865

 
前序遍歷是最自然的遍歷方式偏形,也是最常用的遍歷方式;中序遍歷的結(jié)果是按從小到大的順序的排列的觉鼻;后序遍歷可以用于為二分搜索樹釋放內(nèi)存俊扭。
利用"棧"實現(xiàn)二分搜索樹的非遞歸前序遍歷
    // 二分搜索樹的非遞歸前序遍歷
    public void preOrderNR() {
        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            System.out.print(cur.e);
            if (cur.right != null) {
                stack.push(cur.right);
            }
            if (cur.left != null) {
                stack.push(cur.left);
            }
        }
    }

二分搜索樹的非遞歸實現(xiàn)比遞歸實現(xiàn)更加復(fù)雜。
二分搜索樹的前序坠陈、中序和后續(xù)遍歷都屬于"深度優(yōu)先"算法萨惑。
二分搜索樹的"層序遍歷"屬于"廣度優(yōu)先"算法。
利用"隊列"實現(xiàn)二分搜索樹的"層序遍歷"

    // 二分搜索樹的層序遍歷
    public void levelOrder() {
        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while (!q.isEmpty()) {
            Node cur = q.remove();
            System.out.print(cur.e);
            if (cur.left != null) {
                q.add(cur.left);
            }
            if (cur.right != null) {
                q.add(cur.right);
            }
        }
    }

獲取二分搜索樹中的最小元素和最大元素

    // 尋找二分搜索樹中的最小元素
    public E minimum() {

        if (size == 0) {
            throw new IllegalArgumentException("BST is empty.");
        }
        return minimum(root).e;

    }

    // 返回以node為根的二分搜索樹的最小元素所在節(jié)點
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    // 尋找二分搜索樹中的最大元素
    public E maximum() {

        if (size == 0) {
            throw new IllegalArgumentException("BST is empty.");
        }
        return maximum(root).e;

    }

    // 返回以node為根的二分搜索樹的最大元素所在節(jié)點
    private Node maximum(Node node) {
        if (node.right == null) {
            return node;
        }
        return maximum(node.right);
    }

刪除二分搜索樹中最小元素和最大元素所在節(jié)點

    // 從二分搜索樹中刪除最小元素所在節(jié)點仇矾,返回最小元素
    public E removeMin() {
        E ret = minimum();
        root = removeMin(root);
        return ret;
    }

    // 刪除掉以node為根的二分搜索樹中的最小元素所在節(jié)點
    // 返回刪除節(jié)點后新的二分搜索樹的根
    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;
    }

    // 從二分搜索樹中刪除最大元素所在節(jié)點庸蔼,返回最小元素
    public E removeMax() {
        E ret = maximum();
        root = removeMax(root);
        return ret;
    }

    // 刪除掉以node為根的二分搜索樹中的最小元素所在節(jié)點
    // 返回刪除節(jié)點后新的二分搜索樹的根
    private Node removeMax(Node node) {
        if (node.right == null) {
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }
        node.right = removeMax(node.right);
        return node;
    }

刪除二分搜索樹中指定元素所對應(yīng)的節(jié)點


    // 從二分搜索樹中刪除元素為e的節(jié)點
    public void remove(E e) {
        remove(root, e);
    }

    // 刪除以node為根節(jié)點的二分搜索樹中元素為e的節(jié)點,遞歸算法
    // 返回刪除節(jié)點后新的二分搜索樹的根
    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 {
            // 待刪除節(jié)點左子樹為空的情況
            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;
                // 待刪除節(jié)點右子樹為空的情況
            } else if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                size--;
                return leftNode;
                // 待刪除節(jié)點左右子樹均不為空
                // 找到比待刪除節(jié)點大的最小節(jié)點贮匕,即待刪除節(jié)點右子樹的最小節(jié)點
                // 用這個節(jié)點頂替待刪除節(jié)點
            } else {
                Node successor = minimum(node.right);
                successor.right = removeMin(node.right);  //這里進行了size--操作
                successor.left = node.left;
                node.left = null;
                node.right = null;
                return successor;
            }
        }
    }

    ```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姐仅,一起剝皮案震驚了整個濱河市爷光,隨后出現(xiàn)的幾起案子驳棱,更是在濱河造成了極大的恐慌谢床,老刑警劉巖齐莲,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肥荔,死亡現(xiàn)場離奇詭異楔脯,居然都是意外死亡污尉,警方通過查閱死者的電腦和手機示弓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門供屉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來行冰,“玉大人溺蕉,你說我怎么就攤上這事〉孔觯” “怎么了疯特?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肛走。 經(jīng)常有香客問我漓雅,道長,這世上最難降的妖魔是什么朽色? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任邻吞,我火速辦了婚禮,結(jié)果婚禮上葫男,老公的妹妹穿的比我還像新娘抱冷。我一直安慰自己,他們只是感情好梢褐,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布旺遮。 她就那樣靜靜地躺著,像睡著了一般盈咳。 火紅的嫁衣襯著肌膚如雪耿眉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天猪贪,我揣著相機與錄音跷敬,去河邊找鬼。 笑死热押,一個胖子當(dāng)著我的面吹牛西傀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桶癣,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拥褂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了牙寞?” 一聲冷哼從身側(cè)響起饺鹃,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎间雀,沒想到半個月后悔详,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡惹挟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年茄螃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片连锯。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡归苍,死狀恐怖用狱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拼弃,我是刑警寧澤夏伊,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站吻氧,受9級特大地震影響溺忧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盯孙,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一砸狞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镀梭,春花似錦、人聲如沸踱启。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埠偿。三九已至透罢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冠蒋,已是汗流浹背羽圃。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抖剿,地道東北人朽寞。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像斩郎,于是被迫代替她去往敵國和親脑融。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容