數(shù)據(jù)結(jié)構(gòu)(六)二分搜索樹(Binary Search Tree)(上)

數(shù)據(jù)結(jié)構(gòu)(一)數(shù)組實(shí)現(xiàn)一個(gè)簡單的ArrayList
數(shù)據(jù)結(jié)構(gòu)(二)鏈表實(shí)現(xiàn)LinkedList
數(shù)據(jù)結(jié)構(gòu)(三)用兩種方式簡單實(shí)現(xiàn)棧
數(shù)據(jù)結(jié)構(gòu)(四)棧和隊(duì)列的簡單應(yīng)用
數(shù)據(jù)結(jié)構(gòu)(五)用兩種方式簡單實(shí)現(xiàn)隊(duì)列
數(shù)據(jù)結(jié)構(gòu)(六)二分搜索樹(Binary Search Tree)(上)
數(shù)據(jù)結(jié)構(gòu)(六)二分搜索樹(Binary Search Tree)(下)
數(shù)據(jù)結(jié)構(gòu)(七)兩種方式實(shí)現(xiàn)set
數(shù)據(jù)結(jié)構(gòu)(八)兩種方式實(shí)現(xiàn)map
數(shù)據(jù)結(jié)構(gòu)(九)set解決LeetCode349號問題

定義

簡單來說二分搜索樹是具有以下行的二叉樹

  • 1.若任意節(jié)點(diǎn)的左子樹不空东羹,則左子樹上所有節(jié)點(diǎn)的值均小于它的根節(jié)點(diǎn)的值;
  • 2.若任意節(jié)點(diǎn)的右子樹不空案疲,則右子樹上所有節(jié)點(diǎn)的值均大于它的根節(jié)點(diǎn)的值祝沸;
  • 3.任意節(jié)點(diǎn)的左矮烹、右子樹也分別為二叉查找樹;
  • 4.沒有鍵值相等的節(jié)點(diǎn)罩锐。


    二分搜索樹

二分搜索樹是比較重要的一種數(shù)據(jù)結(jié)構(gòu)奉狈,應(yīng)用也比較廣泛,希望大家多學(xué)習(xí)涩惑,多理解仁期,多應(yīng)用。

實(shí)現(xiàn)

下邊我簡單實(shí)現(xiàn)了一下二分搜索樹竭恬,話不多說跛蛋,直接上源碼。

import java.util.Stack;

public class BST<E extends Comparable<E>> {

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

        public Node(E e) {
            this.e = e;
            left = null;
            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
    public void add(E e) {
        root = add(root, e);
    }

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

    // 看二分搜索樹中是否包含元素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 // e.compareTo(node.e) > 0
            return contains(node.right, e);
    }

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

    // 前序遍歷以node為根的二分搜索樹, 遞歸算法
    private void preOrder(Node node) {
        if (node == null)
            return;

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

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

    // 中序遍歷以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);
    }

    // 后序遍歷以node為根的二分搜索樹, 遞歸算法
    private void postOrder(Node node) {
        if (node == null)
            return;

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

    private void preOrderNR() {
        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            if (root.left != null)
                stack.push(root.left);
            if (root.right != null)
                stack.push(root.right);
        }
    }

    //效果和minimum是一樣的這個(gè)是非遞歸的實(shí)現(xiàn)赊级,那個(gè)是遞歸的實(shí)現(xiàn)。
    public E minNum() {
        if (size == 0) {
            throw new IllegalArgumentException("bst is empty");
        }
        Node node = root;
        while (node.left != null) {
            node = node.left;
        }

        return node.e;
    }

     /**
     * 遞歸的方式實(shí)現(xiàn)找到二分搜索樹的最小值寿桨。
     *
     * @return 返回二分搜索樹最小節(jié)點(diǎn)的值
     */
    public E mininum() {
        if (size == 0) {
            throw new IllegalArgumentException("bst is empty");
        }
        return mininum(root).e;
    }

    /**
     * 以node為節(jié)點(diǎn)的二分搜索樹的最小節(jié)點(diǎn)此衅。
     *
     * @param node
     * @return
     */
    private Node mininum(Node node) {
        if (node.left == null)
            return node;
        return mininum(node.left);
    }

    //從二分搜索樹種刪除最小值所在的接點(diǎn)强戴,并返回最小接點(diǎn)的值。
    public E removeMin() {
        E ret = mininum();
        root = removeMin(root);

        return ret;
    }

    //刪除以node為根的二分搜索樹的最小節(jié)點(diǎn)
    //返回刪除節(jié)點(diǎn)后新的二分搜索樹的根
    private Node removeMin(Node node) {
        if (node.left == null) {
            Node right = node.right;
            node.right = null;
            size--;
            return right;
        }

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

 public E maxNum(){
        if (size == 0) {
            throw new IllegalArgumentException("bst is empty");
        }
        Node node = root;
        while (node.right != null) {
            node = node.right;
        }

        return node.e;  
    }

    public E maxmum() {
        if (size == 0) {
            throw new IllegalArgumentException("bst is empty");
        }
        return maxmum(root).e;
    }

    private Node maxmum(Node node) {
        if (node.right == null)
            return node;
        return maxmum(node.right);
    }

    /**
     * 刪除二分搜索樹最大值的節(jié)點(diǎn)挡鞍。
     *
     * @return
     */
    public E removeMax() {
        E ret = maxmum();
        removeMax(root);
        return ret;
    }

    private Node removeMax(Node node) {
        if (node.left == null) {
            Node right = node.right;
            node.right = null;
            size--;
            return right;
        }
        node.left = removeMax(node);
        return node;
    }

    /**
     * 從二分搜索樹中刪除元素為e的節(jié)點(diǎn)骑歹。
     *
     * @param e
     */
    public void remove(E e) {
        root = remove(root, e);
    }

    //刪除以node為根的二分搜索樹中元素為e的節(jié)點(diǎn),遞歸算法
    //返回刪除節(jié)點(diǎn)后二分搜索樹的根
    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 == node.e
            if (node.left == null) {
                Node right = node.right;
                node. right = null;
                size--;
                return right;
            }

            if (node.right == null) {
                Node left = node.left;
                node.left = null;
                size--;
                return left;
            }

            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = node.right = null;
            return successor;

        }
    }


    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        generateString(root, 0, res);
        return res.toString();
    }

    // 生成以node為根節(jié)點(diǎn)墨微,深度為depth的描述二叉樹的字符串
    private void generateString(Node node, int depth, StringBuilder res) {

        if (node == null) {
            res.append(generateDepthString(depth) + "null\n");
            return;
        }

        res.append(generateDepthString(depth) + node.e + "\n");
        generateString(node.left, depth + 1, res);
        generateString(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();
    }
}

解析

首先大家可以看到我們的泛型與之前的不太一樣道媚,這里必須是可以比較的泛型,因?yàn)槎炙阉鳂涫前凑沾笮砼帕械那滔兀瑑?nèi)部有序最域。下邊我們一一來介紹他的方法。


Binary Search Tree
  • add
    這里我們用的是遞歸的實(shí)現(xiàn)锈麸,所以我們先來看一下結(jié)束的條件镀脂,就是當(dāng)前的節(jié)點(diǎn)是空的時(shí)候,極限情況就是這個(gè)二分搜索樹是空的忘伞,那么插入一個(gè)元素薄翅,只需要把這個(gè)節(jié)點(diǎn)插入到當(dāng)前節(jié)點(diǎn)上,并且給size加一就可以氓奈,下邊就是就是遞歸的具體條件了翘魄,如果他比當(dāng)前節(jié)點(diǎn)小的話,那么那么就去他的左子樹去處理舀奶,如果比當(dāng)前節(jié)點(diǎn)大的話就去他的右子樹去處理暑竟,因?yàn)楫?dāng)前出是不存在鍵值相同的節(jié)點(diǎn),所以就這樣去遍歷直到找到合適的位置育勺,這樣就完成了二分搜索樹的插入工作但荤。如上邊的圖,我們插入11怀大,比根節(jié)點(diǎn)小雏逾,去左子樹比較防泵,比18這個(gè)節(jié)點(diǎn)小树叽,再去他的左子樹比較载矿,比10大馏鹤,去右子樹蛮穿,10這個(gè)節(jié)點(diǎn)右葉子節(jié)點(diǎn)是空的蓉坎,所以直接插入到那個(gè)位置就可以了黎烈。
  • contains
    這里我們也是用的遞歸的方法實(shí)現(xiàn)的垒手,同樣的道理我們也是找一下結(jié)束的條件蒜焊,當(dāng)前的節(jié)點(diǎn)為空的時(shí)候說明走到結(jié)束了也沒有找到相同的值的節(jié)點(diǎn),所以返回false科贬,下邊就是判斷如果當(dāng)前節(jié)點(diǎn)與傳入的元素相同則返回true泳梆;如果比當(dāng)前節(jié)點(diǎn)的值小則去左子樹去遍歷鳖悠;如果比當(dāng)前節(jié)點(diǎn)值大則去右子樹遍歷。這樣查找的方法也結(jié)束了优妙。
  • miniNum
    查找最小數(shù)乘综,因?yàn)槎炙阉鳂涞奶攸c(diǎn)就是左子樹肯定比當(dāng)前節(jié)點(diǎn)小,所以找最小值只需要找左下角的葉子節(jié)點(diǎn)就可以了套硼,所以我們只需要遍歷左子樹直到左子樹的左葉子節(jié)點(diǎn)是空的卡辰,就放回這個(gè)節(jié)點(diǎn)就可以了。上邊的圖可以看到邪意,最左邊的是最小值九妈,就是10。
  • maxmum
    這個(gè)方法跟上邊的方法思想是一樣的所以這里不做過多的解釋了雾鬼。
  • minNum
    這里是用循環(huán)的方式實(shí)現(xiàn)的刪除最小節(jié)點(diǎn)的元素萌朱,就是一直看左子樹,知道左子樹為空的時(shí)候就是他的最小值策菜。
  • maxNum
    這個(gè)方法和上邊的方法是一樣的嚷兔,我們這里也不做過多的解釋了。


    remove.png
  • removeMin
    這里用遞歸的方式去尋找的做入,所以要找結(jié)束條件冒晰,就是到最小值了,所以這個(gè)節(jié)點(diǎn)左葉子節(jié)點(diǎn)為空竟块,然后我們獲取右葉子節(jié)點(diǎn)壶运,保存的臨時(shí)變量里,然后把這個(gè)節(jié)點(diǎn)的右葉子節(jié)點(diǎn)置為null浪秘,size減一蒋情,返回右葉子節(jié)點(diǎn),如果不是空那么就繼續(xù)找這個(gè)節(jié)點(diǎn)的左子樹耸携。
  • removeMax
    這個(gè)方法的思想和上邊的是相同的棵癣。

今天我們先分析這些方法,下次我們分析剩下的那幾個(gè)方法夺衍。希望大家多多關(guān)注與支持狈谊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沟沙,隨后出現(xiàn)的幾起案子河劝,更是在濱河造成了極大的恐慌,老刑警劉巖矛紫,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赎瞎,死亡現(xiàn)場離奇詭異,居然都是意外死亡颊咬,警方通過查閱死者的電腦和手機(jī)务甥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門牡辽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人敞临,你說我怎么就攤上這事催享。” “怎么了哟绊?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵因妙,是天一觀的道長。 經(jīng)常有香客問我票髓,道長攀涵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任洽沟,我火速辦了婚禮以故,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘裆操。我一直安慰自己怒详,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布踪区。 她就那樣靜靜地躺著昆烁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缎岗。 梳的紋絲不亂的頭發(fā)上静尼,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音传泊,去河邊找鬼鼠渺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眷细,可吹牛的內(nèi)容都是我干的拦盹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼溪椎,長吁一口氣:“原來是場噩夢啊……” “哼普舆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起池磁,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤奔害,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后地熄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芯杀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年雅潭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扶供。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笋敞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趁餐,到底是詐尸還是另有隱情澎怒,我是刑警寧澤走孽,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布困食,位于F島的核電站符匾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一瘟仿、第九天 我趴在偏房一處隱蔽的房頂上張望箱锐。 院中可真熱鬧,春花似錦劳较、人聲如沸驹止。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽臊恋。三九已至,卻和暖如春墓捻,著一層夾襖步出監(jiān)牢的瞬間抖仅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工砖第, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撤卢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓梧兼,卻偏偏與公主長得像放吩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子羽杰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

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