算法系列筆記(九)二叉查找樹(shù)

我們?cè)谥霸诘谄哒聦W(xué)習(xí)優(yōu)先隊(duì)列中學(xué)習(xí)堆有序中學(xué)習(xí)到了完全二叉樹(shù)堂油,而這里我們將范圍擴(kuò)大變成二叉樹(shù),而且將每個(gè)結(jié)點(diǎn)變成存儲(chǔ)鍵值對(duì)的數(shù)據(jù)碧绞,這就成為二叉查找樹(shù)府框。

強(qiáng)調(diào)一下,其實(shí)一個(gè)結(jié)點(diǎn)的子結(jié)點(diǎn)往下的所有部分也可以看成一個(gè)二叉樹(shù)讥邻,按子結(jié)點(diǎn)是左還是右分為左子樹(shù)和右子樹(shù)迫靖。
實(shí)際上每個(gè)結(jié)點(diǎn)的鍵是有比較值的,每個(gè)結(jié)點(diǎn)的鍵都大于左子樹(shù)中任意結(jié)點(diǎn)的鍵而小于右子樹(shù)任意結(jié)點(diǎn)的鍵兴使。

所以我們搜尋一個(gè)結(jié)點(diǎn)系宜,其實(shí)就是遍歷二叉樹(shù)的一個(gè)過(guò)程,由于是二叉发魄,所以就很有規(guī)律蜈首。

我們從根結(jié)點(diǎn)出發(fā),每到一個(gè)結(jié)點(diǎn)欠母,就和當(dāng)前結(jié)點(diǎn)比較,如果比當(dāng)前結(jié)點(diǎn)大就遍歷結(jié)點(diǎn)的右子樹(shù)吆寨,如果比它下則遍歷左子樹(shù)赏淌,重復(fù)以往,直到找到所要的點(diǎn)或者找到子結(jié)點(diǎn)了(說(shuō)明樹(shù)中并無(wú)該值)

這樣我們其實(shí)每次比較啄清,選擇子樹(shù)其實(shí)可以說(shuō)是有一種二分的思想了六水。

public class BinarySearchTree<Key extends Comparable<Key>,Value> {
        private Node root;
        private class Node{
            private Key key;
            private Value val;
            private Node left,right;
            private int N;
            public Node(Key k,Value v,int a) {
                key=k;
                val=v;
                N=a;
            }
        }
        
        public int size() {
            return size(root);
        }
        private int size(Node x) {
            if(x==null) return 0;
            else return x.N;
        }
        
        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;
        }
        
        public void put(Key key,Value val) {
            root=put(root, key,val);
        }
        private Node put(Node x,Key key,Value val) {
            if(x==null) return new Node(key, val, 1);
            int cmp=key.compareTo(x.key);
            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;
            x.N=size(x.left)+size(x.right)+1;
            return x;
        }
}
找大小

下面是min max floor(找到最大的比key小于等于的結(jié)點(diǎn)) ceiling(找到最小的比key大于等于的結(jié)點(diǎn)),后面兩個(gè)實(shí)現(xiàn)的思路有點(diǎn)意思辣卒。

        public Key min() {
            return min(root).key;
        }
        private Node min(Node x) {
            if(x.left==null) return x;
            return min(x.left);
        }
        public Key max() {
            return max(root).key;
        }
        private Node max(Node x) {
            if(x.right==null) return x;
            return max(x.right);
        }
        
        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;
            if(x.key.compareTo(key) == 0){
                return x;
            }
     
            //如果該結(jié)點(diǎn)比key要大的話(huà)掷贾,就要找左子樹(shù),如果沒(méi)有左子樹(shù)則返回null荣茫,因?yàn)闆](méi)有比key小的數(shù)
            if(x.key.compareTo(key) > 0){
                return floor(x.left, key);
            }
            //如果該結(jié)點(diǎn)比key小想帅,則這個(gè)結(jié)點(diǎn)有可能就是所求答案,但可能會(huì)有比這個(gè)數(shù)更大但符合小于key的數(shù)啡莉。
            //如果在右子樹(shù)找不到合適的數(shù)港准,則該結(jié)點(diǎn)就是答案
            Node t=floor(x.right, key);
            if(t!=null) return t; //經(jīng)過(guò)層層迭代,倘若不為空咧欣,這個(gè)就是迭代后最佳的答案浅缸,反正比自己好,也不敢多bb
            else return x;           //發(fā)現(xiàn)自己的右子樹(shù)沒(méi)有一個(gè)合適的人選魄咕,沒(méi)辦法就只能自己上衩椒。
        }
        
        public Key ceiling(Key key) {
            Node x=ceiling(root,key);
            if(x==null) return null;
            return x.key;
        }
        private Node ceiling(Node x,Key key) {
            if(x==null) return null;
            if(x.key.compareTo(key)==0) return x;
            
            //結(jié)點(diǎn)比key小,不合適,要嘗試找更加大的
            if(x.key.compareTo(key)<0) return ceiling(x.right, key);
            
            //結(jié)點(diǎn)比key大了,看看有沒(méi)有稍微小一點(diǎn)的
            Node t=ceiling(x.left, key);
            if(t!=null) return t;
            else return x;
        }
select和rank

很有意思

左子樹(shù)的結(jié)點(diǎn)都是小于當(dāng)前結(jié)點(diǎn)的毛萌,而對(duì)于根結(jié)點(diǎn)和從根節(jié)點(diǎn)開(kāi)始一直走左邊所經(jīng)過(guò)的結(jié)點(diǎn)來(lái)說(shuō)苟弛,它的左子樹(shù)大小+1就是它當(dāng)前的序號(hào)。而對(duì)于右結(jié)點(diǎn)來(lái)說(shuō)朝聋,第k結(jié)點(diǎn)就是右子樹(shù)的第k-size(左子樹(shù))-1結(jié)點(diǎn)嗡午。

而rank是select的逆操作,其實(shí)就是不斷遍歷搜集所有比自己小的數(shù)冀痕,比如當(dāng)你發(fā)現(xiàn)比當(dāng)前結(jié)點(diǎn)大了荔睹,要往右子樹(shù)遍歷,此時(shí)候你必須記錄左子樹(shù)的所有結(jié)點(diǎn)的數(shù)目言蛇,然后再跑到右子樹(shù)進(jìn)行新的迭代僻他,倘若你需要往左子樹(shù)遍歷,則右子樹(shù)的結(jié)點(diǎn)和自己完全無(wú)關(guān)腊尚。

        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);
            
            //k比t小吨拗,說(shuō)明所要結(jié)點(diǎn)在左子樹(shù)
            if(k<t) return select(x.left,k);
            //k比t大,則需要從右子樹(shù)找了婿斥。注意此時(shí)要找的結(jié)點(diǎn)是“右子樹(shù)”中的第t-k-1個(gè)
            else if(k>t) return select(x.right, t-k-1);
            else return x;
        }
        
        public int rank(Key key) {
            return rank(key,root);
        }
        private int rank(Key key,Node x) {
            if(x==null) return 0;
            int cmp=key.compareTo(x.key);
            if(cmp<0) return rank(key,x.left);
            else if(cmp>0) return 1+size(x.left)+rank(key, x.right);
            else return size(x.left);
        }
刪除操作

刪除有兩個(gè)操作劝篷。一個(gè)是deleteMin(刪除最小結(jié)點(diǎn)),一個(gè)是delete(一個(gè)是刪除指定鍵的結(jié)點(diǎn))

我們知道對(duì)于一個(gè)結(jié)點(diǎn)來(lái)說(shuō)民宿,倘若它有左結(jié)點(diǎn)娇妓,那最小值肯定在左子樹(shù)當(dāng)中,否則自己就是最小的活鹰。

        public void deleteMin() {
            root=deleteMin(root);
        }
        private Node deleteMin(Node x) {
            //x.left=null說(shuō)明此時(shí)x就是最小點(diǎn)哈恰,要?jiǎng)h除該點(diǎn),要將右子樹(shù)接到其父結(jié)點(diǎn)中
            if(x.left==null) return x.right;   //如果沒(méi)有右結(jié)點(diǎn)返回null也合適
            x.left=deleteMin(x.left);        //更新父結(jié)點(diǎn)
            x.N=size(x.left)+size(x.right)+1;  //更新每一個(gè)結(jié)點(diǎn)的x.N
            return x;
        }

對(duì)于delete來(lái)說(shuō)志群,找到所要?jiǎng)h除的結(jié)點(diǎn)并不難着绷,重點(diǎn)是刪除結(jié)點(diǎn)后它的子結(jié)點(diǎn)(子樹(shù))該怎么接到父結(jié)點(diǎn)上。有三種情況锌云,沒(méi)有子樹(shù)荠医,只有左子樹(shù)或右子樹(shù),同時(shí)有左右子樹(shù)宾抓。第一種很簡(jiǎn)單子漩,第二種直接將剩下的子樹(shù)接到父結(jié)點(diǎn)即可。第三種比較麻煩石洗,我們知道父結(jié)點(diǎn)只有一個(gè)子結(jié)點(diǎn)的接口幢泼。不可能直接將兩個(gè)子樹(shù)接上去。

所以我們需要從一個(gè)子結(jié)點(diǎn)中找到一個(gè)合適的結(jié)點(diǎn)來(lái)代替將要被刪除的結(jié)點(diǎn)的位置讲衫。唯一合適的子結(jié)點(diǎn)就一定存在在右子樹(shù)的最小的結(jié)點(diǎn)A上缕棵。(仔細(xì)體會(huì)一下)孵班。
然后左子樹(shù)接在A的左結(jié)點(diǎn)上,而原來(lái)右子樹(shù)需要處理招驴,因?yàn)?strong>A可能本來(lái)就有右子樹(shù)篙程,需要將A的右子樹(shù)接在它的父結(jié)點(diǎn)上(執(zhí)行deleteMin操作),然后再將整個(gè)右子樹(shù)接到A的右邊别厘。
再更新受影響各個(gè)結(jié)點(diǎn)的N

        public void delete(Key key) {
            root=delete(root,key);
        }
        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 {
              //被刪除的結(jié)點(diǎn)B只有一個(gè)子結(jié)點(diǎn)
                if(x.right==null) return x.left;
                if(x.left==null) return x.right;
                Node t=x;
            //找到代替刪除結(jié)點(diǎn)的結(jié)點(diǎn)A 
                x=min(t.right);
           //deleteMin處理代替結(jié)點(diǎn)可能存在的右子樹(shù)的情況,然后將B經(jīng)處理右子樹(shù)接在A上
                x.right=deleteMin(t.right);
                x.left=t.left;
            }
            //更新N值
            x.N=size(x.left)+size(x.right)+1;
            return x;
        }
鍵查找
        //遍歷所有的鍵虱饿,應(yīng)該是從小到大順序的
        public Iterable<Key>keys(){
            return keys(min(),max());
        }
        public Iterable<Key>keys(Key lo,Key hi){
            LinkedList<Key> queue=new LinkedList<Key>();
            keys(root,queue,lo,hi);
            return queue;
        }
        private void keys(Node x,LinkedList<Key> queue,Key lo,Key hi) {
            if(x==null) return;
            int cmplo=lo.compareTo(x.key);
            int cmphi=hi.compareTo(x.key);
            
            //從左往右找,按從小到大順序
            //說(shuō)明當(dāng)前的結(jié)點(diǎn)比較大触趴,可以看看有沒(méi)有更小的但符合范圍的結(jié)點(diǎn)
            if(cmplo<0) keys(x.left,queue,lo,hi);
            //合適的鍵氮发,加入隊(duì)列當(dāng)中
            if(cmplo<=0&&cmphi>=0) queue.addFirst(x.key);
            if(cmphi>0) keys(x.right,queue,lo,hi);
        }

二叉查找樹(shù)平均情況:查找1.39logN 插入1.39logN

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市冗懦,隨后出現(xiàn)的幾起案子爽冕,更是在濱河造成了極大的恐慌,老刑警劉巖披蕉,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颈畸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡没讲,警方通過(guò)查閱死者的電腦和手機(jī)眯娱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)爬凑,“玉大人困乒,你說(shuō)我怎么就攤上這事》∫ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵迁霎,是天一觀的道長(zhǎng)吱抚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)考廉,這世上最難降的妖魔是什么秘豹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮昌粤,結(jié)果婚禮上既绕,老公的妹妹穿的比我還像新娘。我一直安慰自己涮坐,他們只是感情好凄贩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著袱讹,像睡著了一般疲扎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天椒丧,我揣著相機(jī)與錄音壹甥,去河邊找鬼。 笑死壶熏,一個(gè)胖子當(dāng)著我的面吹牛句柠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棒假,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼溯职,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了淆衷?” 一聲冷哼從身側(cè)響起缸榄,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎祝拯,沒(méi)想到半個(gè)月后甚带,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佳头,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年鹰贵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片康嘉。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碉输,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亭珍,到底是詐尸還是另有隱情敷钾,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布肄梨,位于F島的核電站阻荒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏众羡。R本人自食惡果不足惜侨赡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粱侣。 院中可真熱鬧羊壹,春花似錦、人聲如沸齐婴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柠偶。三九已至眨攘,卻和暖如春主慰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鲫售。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工共螺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人情竹。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓藐不,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秦效。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雏蛮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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