查找(二叉查找數(shù))

二叉查找數(shù)

定義

一個(gè)二叉查找數(shù)(BST)是一顆二叉樹,其中每個(gè)結(jié)點(diǎn)都含有一個(gè)Comparable的鍵以及相關(guān)聯(lián)的值眉踱,且每個(gè)結(jié)點(diǎn)的鍵都大于其左子樹的任意結(jié)點(diǎn)的鍵田轧,小于其右子樹的任意結(jié)點(diǎn)的鍵

基本實(shí)現(xiàn)

  1. 數(shù)據(jù)表示
    鍵+ 值+ 左鏈接+右鏈接+結(jié)點(diǎn)計(jì)數(shù)器
  2. 查找
    如果樹是空的玩讳,則查找未命中;如果查找的鍵小于當(dāng)前結(jié)點(diǎn)的鍵议泵,則繼續(xù)查找其左子樹;如果查找的鍵大于當(dāng)前結(jié)點(diǎn)的鍵桃熄,則繼續(xù)查找其右子樹先口;如果查找的鍵等于當(dāng)前結(jié)點(diǎn)的鍵,則查找命中瞳收。
  3. 插入
    如果樹是空的碉京,則返回一個(gè)含有該鍵值對(duì)的新結(jié)點(diǎn)。如果被查找的鍵小于根節(jié)點(diǎn)的鍵螟深,則繼續(xù)在左子樹中插入該鍵谐宙,否則在右子樹中插入該鍵。(記得更新節(jié)點(diǎn)計(jì)數(shù)器)
  4. 性能分析
    二叉查找數(shù)的算法的運(yùn)行時(shí)間依賴于樹的形狀界弧,而樹的形狀則取決于鍵的插入順序凡蜻。如果插入一個(gè)有序的鍵值對(duì),則會(huì)出現(xiàn)最壞情況垢箕。
    由N個(gè)隨機(jī)鍵構(gòu)造的二叉查找數(shù)划栓,查找命中和插入命中的平均所需比較次數(shù)為~2lnN(1.39lgN)。
    二叉查找和插入的成本都是對(duì)數(shù)級(jí)別的(相比于二分查找的優(yōu)勢(shì))

有序性相關(guān)的方法實(shí)現(xiàn)

  1. 最大鍵和最小鍵
    最小鍵:如果左鏈接為空条获,最小鍵就是根節(jié)點(diǎn)忠荞;如果左鏈接非空,最小鍵就是左子樹的最小鍵
    最大鍵:如果右鏈接為空帅掘,最大鍵就是根節(jié)點(diǎn)钻洒;如果右鏈接非空,最大鍵就是右子樹的最大鍵
  2. 向上取整和向下取整
    floor(key) : key等于二叉樹的根節(jié)點(diǎn)锄开,返回根節(jié)點(diǎn)的值
    2.1 key小于二叉樹的根節(jié)點(diǎn),floor(key)一定在根節(jié)點(diǎn)的左子樹中
    2.2 key大于二叉樹的根節(jié)點(diǎn)
    2.2.1 右子樹中存在小于等于key的結(jié)點(diǎn)時(shí)称诗,floor(key)在右子樹中
    2.2.2 否則返回根節(jié)點(diǎn)
  3. 選擇操作
    查找排名為k的鍵(樹中有k個(gè)小于它的節(jié)點(diǎn))萍悴,假設(shè)左子樹的結(jié)點(diǎn)數(shù)為t
    3.1 如果t > k 在左子樹中查找排名為k的鍵
    3.2 如果t < k 在右子樹中查找排名為k-t-1的鍵
    3.3 如果t=k 返回根節(jié)點(diǎn)
  4. 排名
    返回給定鍵在樹中的排名
    4.1 如果給定的鍵小于根節(jié)點(diǎn)的鍵,則返回該鍵在左子樹中的排名
    4.2 如果給定的鍵大于根節(jié)點(diǎn)的鍵寓免,則返回該鍵在右子樹中的排名+左子樹的節(jié)點(diǎn)個(gè)數(shù)+1
    4.3 如果給定的鍵等于根節(jié)點(diǎn)的鍵癣诱,則返回左子樹的節(jié)點(diǎn)個(gè)數(shù)
  5. 刪除最大值和最小值
    deleteMin():如果左子樹不為空,則要?jiǎng)h除的節(jié)點(diǎn)在左子樹袜香;如果左子樹為空撕予,則返回該節(jié)點(diǎn)的右子樹
  6. 刪除操作
    如果某個(gè)節(jié)點(diǎn)左右子樹都不為空,刪除節(jié)點(diǎn)以后蜈首,會(huì)產(chǎn)生兩個(gè)子樹而被刪除的節(jié)點(diǎn)的父節(jié)點(diǎn)只空出了一個(gè)鏈接实抡∏纺福可以使用被刪除節(jié)點(diǎn)的后繼節(jié)點(diǎn)(右子樹中的最小節(jié)點(diǎn))補(bǔ)充它的位置。
    6.1 將指向即將被刪除的結(jié)點(diǎn)的鏈接保存為t
    6.2 將x指向它的后繼結(jié)點(diǎn)min(t.right)
    6.3 將x的右鏈接指向deleteMin(t.right)
    6.4 將x的左鏈接設(shè)為t.left

代碼實(shí)現(xiàn)

package edu.princeton.cs.algs4.chapter3;

/**
 * 使用二叉查找樹實(shí)現(xiàn)的符號(hào)表
 * Created by tianxianhu on 2017/3/6.
 */
public class BST<Key extends Comparable<Key>, Value> {
    private Node root;
    private class Node{
        private Key key;
        private Value val;
        private Node left;
        private Node right;
        private int N; //維護(hù)一個(gè)當(dāng)前節(jié)點(diǎn)為根節(jié)點(diǎn)的節(jié)點(diǎn)總數(shù)量

        public Node (Key key, Value val, int N) {
            this.key = key;
            this.val = val;
            this.N = N;
        }
    }

    public int size() {
        return size(root);
    }

    private int size(Node x) {
        if (x == null)
            return 0;
        else return x.N;
    }

    /**
     * 獲取指定鍵的值吆寨,從根節(jié)點(diǎn)開始赏淌,比較鍵值
     * 小于當(dāng)前鍵值則在左子樹中繼續(xù)尋找,大于則在右子樹中繼續(xù)尋找啄清,等于則返回當(dāng)前節(jié)點(diǎn)的值
     * @param key
     * @return
     */
    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            return get(x.left, key);
        } else if (cmp > 0) {
            return get(x.right, key);
        } else {
            return x.val;
        }
    }

    /**
     * 尋找key六水,找到則更新它的值,否則為它創(chuàng)建一個(gè)新節(jié)點(diǎn)辣卒,同時(shí)更新節(jié)點(diǎn)數(shù)量
     * @param key
     * @param val
     * @return
     */
    public Node put(Key key, Value val) {
        return put(root, key, val);
    }

    private Node put(Node x, Key key, Value val) {
        // 如果不存在掷贾,新建節(jié)點(diǎn)插入到字?jǐn)?shù)當(dāng)中
        if (x == null)
            return new Node(key, val, 1);
        int cmp = key.compareTo(x.key);
        // 不存在,則繼續(xù)在左/右子樹上尋找
        if (cmp < 0) {
            x.left = put(x.left, key, val);
        } else if (cmp > 0) {
            x.right = put(x.right, key, val);
        } else {
            // 存在荣茫,則更新值
            x.val = val;
        }
        // 增加了節(jié)點(diǎn)想帅,更新每個(gè)子樹的數(shù)量
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    public Key min() {
        return min(root).key;
    }

    private Node min(Node x) {
        if (x.left == null)
            return x;
        return min(x.left);
    }

    /**
     * 將要查詢的鍵值和根節(jié)點(diǎn)的鍵值進(jìn)行比較
     * 1. 與根節(jié)點(diǎn)鍵值相等,返回當(dāng)前節(jié)點(diǎn)的鍵
     * 1. 小于根節(jié)點(diǎn)鍵值计露,則在左子樹中繼續(xù)尋找
     * 2. 大于根節(jié)點(diǎn)鍵值博脑,則在右子樹中尋找(可能在右子樹中,也可能是根節(jié)點(diǎn))
     * 2.1 如果右子樹中尋找返回null票罐,則返回根節(jié)點(diǎn)
     * 2.2 如果在右子樹中尋找到叉趣,則返回找到的節(jié)點(diǎn)
     * @param key
     * @return
     */
    public Key floor(Key key) {
        Node x = floor(root, key);
        if (x == null)
            return null;
        return x.key;
    }

    private Node floor(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        // 相等,返回當(dāng)前值
        if (cmp == 0)
            return x;
        // 小于根節(jié)點(diǎn)该押,在左子樹當(dāng)中尋找
        if (cmp < 0)
            return floor(x.left, key);
        // 大于根節(jié)點(diǎn)疗杉,在右子樹中尋找
        Node t = floor(x.right, key);
        // 存在則返回該節(jié)點(diǎn),不存在則返回根節(jié)點(diǎn)
        if (t != null)
            return t;
        else
            return x;
    }

    /**
     * 查詢排名為k的鍵(0-based)
     * 計(jì)算做子樹的節(jié)點(diǎn)數(shù)量t蚕礼,與排名k進(jìn)行比較
     * 1. 如果t>k, 就繼續(xù)在左子樹中查找排名為k的鍵
     * 2. 如果k>t, 就在右子樹中查找排名為k-t-1的鍵
     * 3. 如果k=t烟具, 就返回當(dāng)前節(jié)點(diǎn)
     * @param k
     * @return
     */
    public Key select(int k) {
        return select(root, k).key;
    }

    private Node select(Node x, int k) {
        if (x == null)
            return null;
        int t = size(x.left);
        if (k < t) {
            return select(x.left, k);
        } else if (t < k) {
            return select(x.right, k - t - 1);
        } else {
            return x;
        }
    }

    /**
     * 查詢鍵key的排名
     * 將要查詢的鍵與當(dāng)前的鍵值進(jìn)行比較
     * 1.如果相等,則返回根節(jié)點(diǎn)左子樹的節(jié)點(diǎn)數(shù)
     * 2.小于根節(jié)點(diǎn)奠蹬,則返回該鍵在左子樹中的排名
     * 3.大于根節(jié)點(diǎn)朝聋,則返回size(x.left)+1+右子樹中的排名
     * @param key
     * @return
     */
    public int rank(Key key) {
        return rank(root, key);
    }

    private int rank(Node x, Key key) {
        if (x == null)
            return 0;

        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return rank(x.left, key);
        else if (cmp > 0)
            return 1 + size(x.left) + rank(x.right, key);
        else
            return size(x.left);
    }

    /**
     * 1.不斷檢索左子樹,直到遇見空的左子樹
     * 2.返回該節(jié)點(diǎn)的右鏈接
     * 3.遞歸調(diào)用結(jié)束后更新節(jié)點(diǎn)計(jì)數(shù)器
     */
    public void deleteMin() {
        root = deleteMin(root);
    }

    private Node deleteMin(Node x) {
        if (x.left == null)
            return x.right;
        x.left = deleteMin(x.left);
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    public void delete(Key key) {
        root = delete(root, key);
    }

    /**
     * 1.尋找到要?jiǎng)h除節(jié)點(diǎn)t的后繼節(jié)點(diǎn)min(t.right)
     * 2.將后繼節(jié)點(diǎn)x.right指向deleteMin(x.right),即刪除后繼節(jié)點(diǎn)以后的子樹囤躁,該子樹大于后繼節(jié)點(diǎn)
     * 3.將后繼節(jié)點(diǎn)x.left指向t.left
     * @param x
     * @param key
     * @return
     */
    private Node delete(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            x.left = delete(x.left, key);
        else if (cmp > 0)
            x.right = delete(x.right, key);
        else {
            if (x.left == null)
                return x.right;
            if (x.right == null)
                return x.left;
            Node t = x;
            x = min(t.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        }
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冀痕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狸演,更是在濱河造成了極大的恐慌言蛇,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宵距,死亡現(xiàn)場離奇詭異腊尚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)满哪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門婿斥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劝篷,“玉大人,你說我怎么就攤上這事受扳⌒辏” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵勘高,是天一觀的道長峡蟋。 經(jīng)常有香客問我,道長华望,這世上最難降的妖魔是什么蕊蝗? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮赖舟,結(jié)果婚禮上蓬戚,老公的妹妹穿的比我還像新娘。我一直安慰自己宾抓,他們只是感情好子漩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著石洗,像睡著了一般幢泼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讲衫,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天缕棵,我揣著相機(jī)與錄音,去河邊找鬼涉兽。 笑死招驴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枷畏。 我是一名探鬼主播别厘,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼拥诡!你這毒婦竟也來了丹允?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤袋倔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后折柠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宾娜,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年扇售,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了前塔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚣艇。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖华弓,靈堂內(nèi)的尸體忽然破棺而出食零,到底是詐尸還是另有隱情,我是刑警寧澤寂屏,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布贰谣,位于F島的核電站,受9級(jí)特大地震影響迁霎,放射性物質(zhì)發(fā)生泄漏吱抚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一考廉、第九天 我趴在偏房一處隱蔽的房頂上張望秘豹。 院中可真熱鬧,春花似錦昌粤、人聲如沸既绕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凄贩。三九已至,卻和暖如春膊升,著一層夾襖步出監(jiān)牢的瞬間怎炊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國打工廓译, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留评肆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓非区,卻偏偏與公主長得像瓜挽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子征绸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

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