24-二叉樹基礎(chǔ)(下):有了如此高效的散列表,為什么還需要二叉樹?

今天來學(xué)習(xí)一種特殊的的二叉樹二叉查找樹。二叉查找樹最大的特點(diǎn)就是支持動態(tài)數(shù)據(jù)集合的快速插入、刪除、查找操作。

我們之前說過,散列表也是支持這些操作的,并且散列表的這些操作比二叉查找樹更高效,時(shí)間復(fù)雜度是 O(1)。既然有了這么高效的散列表,使用二叉樹的地方是不是都可以替換成散列表呢?有沒有哪些地方是散列表做不了,必須要用二叉樹來做的呢?

二叉查找樹

二叉查找樹是二叉樹中最常用的一種類型,也叫二叉搜索樹。顧名思義,二叉查找樹是為了實(shí)現(xiàn)快速查找而生的。不過,它不僅僅支持快速查找一個(gè)數(shù)據(jù),還支持快速插入、刪除一個(gè)數(shù)據(jù)。它是怎么做到這些的呢?

這些都依賴于二叉查找樹的特殊結(jié)構(gòu)。二叉查找樹要求,在樹中的任意一個(gè)節(jié)點(diǎn),其左子樹中的每個(gè)節(jié)點(diǎn)的值,都要小于這個(gè)節(jié)點(diǎn)的值,而右子樹節(jié)點(diǎn)的值都大于這個(gè)節(jié)點(diǎn)的值。

1. 二叉查找樹的查找操作

我們先取根節(jié)點(diǎn),如果它等于我們要查找的數(shù)據(jù),那就返回。如果要查找的數(shù)據(jù)比根節(jié)點(diǎn)的值小,那就在左子樹中遞歸查找;如果要查找的數(shù)據(jù)比根節(jié)點(diǎn)的值大,那就在右子樹中遞歸查找。

2. 二叉查找樹的插入操作

二叉查找樹的插入過程有點(diǎn)類似查找操作。新插入的數(shù)據(jù)一般都是在葉子節(jié)點(diǎn)上,所以我們只需要從根節(jié)點(diǎn)開始,依次比較要插入的數(shù)據(jù)和節(jié)點(diǎn)的大小關(guān)系。

如果要插入的數(shù)據(jù)比節(jié)點(diǎn)的數(shù)據(jù)大,并且節(jié)點(diǎn)的右子樹為空,就將新數(shù)據(jù)直接插到右子節(jié)點(diǎn)的位置;如果不為空,就再遞歸遍歷右子樹,查找插入位置。同理,如果要插入的數(shù)據(jù)比節(jié)點(diǎn)數(shù)值小,并且節(jié)點(diǎn)的左子樹為空,就將新數(shù)據(jù)插入到左子節(jié)點(diǎn)的位置;如果不為空,就再遞歸遍歷左子樹,查找插入位置。

3. 二叉查找樹的刪除操作

針對要刪除節(jié)點(diǎn)的子節(jié)點(diǎn)個(gè)數(shù)的不同,我們需要分三種情況來處理。

  • 第一種情況是,如果要刪除的節(jié)點(diǎn)沒有子節(jié)點(diǎn),我們只需要直接將父節(jié)點(diǎn)中指向要刪除節(jié)點(diǎn)的指針置為 null。

  • 第二種情況是,如果要刪除的節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn)(只有左子節(jié)點(diǎn)或者右子節(jié)點(diǎn)),我們只需要更新父節(jié)點(diǎn)中,指向要刪除節(jié)點(diǎn)的指針,讓它指向要刪除節(jié)點(diǎn)的子節(jié)點(diǎn)就可以了。

  • 第三種情況是,如果要刪除的節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn),這就比較復(fù)雜了。我們需要找到這個(gè)節(jié)點(diǎn)的右子樹中的最小節(jié)點(diǎn),把它替換到要刪除的節(jié)點(diǎn)上。然后再刪除掉這個(gè)最小節(jié)點(diǎn),因?yàn)樽钚」?jié)點(diǎn)肯定沒有左子節(jié)點(diǎn)(如果有左子結(jié)點(diǎn),那就不是最小節(jié)點(diǎn)了),所以,我們可以應(yīng)用上面兩條規(guī)則來刪除這個(gè)最小節(jié)點(diǎn)。

實(shí)際上,關(guān)于二叉查找樹的刪除操作,還有個(gè)非常簡單、取巧的方法,就是單純將要刪除的節(jié)點(diǎn)標(biāo)記為“已刪除”,但是并不真正從樹中將這個(gè)節(jié)點(diǎn)去掉。這樣原本刪除的節(jié)點(diǎn)還需要存儲在內(nèi)存中,比較浪費(fèi)內(nèi)存空間,但是刪除操作就變得簡單了很多。而且,這種處理方法也并沒有增加插入、查找操作代碼實(shí)現(xiàn)的難度。

4. 二叉查找樹的其他操作

除了插入、刪除、查找操作之外,二叉查找樹中還可以支持快速地查找最大節(jié)點(diǎn)和最小節(jié)點(diǎn)、前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)

二叉查找樹還有一個(gè)重要的特性:中序遍歷二叉查找樹,可以輸出有序的數(shù)據(jù)序列,時(shí)間復(fù)雜度是 O(n),非常高效。因此,二叉查找樹也叫作二叉排序樹

支持重復(fù)數(shù)據(jù)的二叉查找樹

前面講二叉查找樹的時(shí)候,我們默認(rèn)樹中節(jié)點(diǎn)存儲的都是數(shù)字。很多時(shí)候,在實(shí)際的軟件開發(fā)中,我們在二叉查找樹中存儲的是一個(gè)包含很多字段的對象。我們利用對象的某個(gè)字段作為鍵值(key)來構(gòu)建二叉查找樹。我們把對象中的其他字段叫作衛(wèi)星數(shù)據(jù)

前面我們講的二叉查找樹的操作,針對的都是不存在鍵值相同的情況。那如果存儲的兩個(gè)對象鍵值相同,這種情況該怎么處理呢?這里有兩種解決方法。

  • 第一種方法比較容易。二叉查找樹中每一個(gè)節(jié)點(diǎn)不僅會存儲一個(gè)數(shù)據(jù),因此我們通過鏈表和支持動態(tài)擴(kuò)容的數(shù)組等數(shù)據(jù)結(jié)構(gòu),把值相同的數(shù)據(jù)存儲在同一個(gè)節(jié)點(diǎn)上。

  • 第二種方法比較不好理解,不過更加優(yōu)雅。每個(gè)節(jié)點(diǎn)仍然只存儲一個(gè)數(shù)據(jù)。在查找插入位置的過程中,如果碰到一個(gè)節(jié)點(diǎn)的值,與要插入數(shù)據(jù)的值相同,我們就將這個(gè)要插入的數(shù)據(jù)放到這個(gè)節(jié)點(diǎn)的右子樹,也就是說,把這個(gè)新插入的數(shù)據(jù)當(dāng)作大于這個(gè)節(jié)點(diǎn)的值來處理。

當(dāng)要查找、刪除數(shù)據(jù)時(shí),遇到值相同的節(jié)點(diǎn),并不能停止查找,而是繼續(xù)在右子樹中查找,直到遇到葉子節(jié)點(diǎn)。

二叉查找樹的時(shí)間復(fù)雜度分析

當(dāng)二叉查找樹根節(jié)點(diǎn)的左右子樹極度不平衡時(shí),就退化成了鏈表,所以查找時(shí)間復(fù)雜度就變成了 O(n)。

剛剛分析了一種最糟糕的情況,我們現(xiàn)在來分析一個(gè)最理想的情況,二叉查找樹是一棵完全二叉樹,這個(gè)時(shí)候插入、刪除、查找的時(shí)間復(fù)雜度是多少?

從前面的介紹可以看出,不管是插入、刪除還是查找,時(shí)間復(fù)雜度都跟樹的高度成正比,即 O(height)?,F(xiàn)在問題轉(zhuǎn)化成了求一棵包含 n 個(gè)節(jié)點(diǎn)的完全二叉樹的高度。

樹的高度就等于最大層數(shù)減一,為了方便計(jì)算,我們轉(zhuǎn)換成層來表示。

包含 n 個(gè)節(jié)點(diǎn)的完全二叉樹中,第一層包含 1 個(gè)節(jié)點(diǎn),第二層包含 2 個(gè)節(jié)點(diǎn),第三層包含 4 個(gè)節(jié)點(diǎn),依次類推,下面一層節(jié)點(diǎn)個(gè)數(shù)是上一層的 2 倍,第 K 層包含的節(jié)點(diǎn)個(gè)數(shù)就是 2 ^ (k - 1)。

不過對于完全二叉樹來說,最后一層的節(jié)點(diǎn)個(gè)數(shù)不遵守上面的規(guī)律,它包含的節(jié)點(diǎn)個(gè)數(shù)在 1 個(gè)到 2 ^ (L - 1) 個(gè)之間,L 是最大層數(shù)。

如果 n 是各層節(jié)點(diǎn)數(shù)之和,那么 n 滿足這樣一個(gè)關(guān)系:

n >= 1 + 2 + 4 + 8 + ... + 2 ^ (L - 2) + 1
n <= 1 + 2 + 4 + 8 + ... + 2 ^ (L - 2) + 2 ^ (L - 1)

運(yùn)用等比數(shù)列求和公式可計(jì)算出 L 的范圍是 [ log_2(n+1), log_2n+1 ]。

完全二叉樹的層數(shù)小于等于 log_2n +1,也就是說,完全二叉樹的高度小于等于 log_2n。所以完全二叉樹查找時(shí)間復(fù)雜度為 O(logn)。

顯然,極度不平衡的二叉查找樹,它的查找性能肯定不能滿足我們的需求。我們需要構(gòu)建一種不管怎么刪除、插入數(shù)據(jù),在任何時(shí)候,都能保持任意節(jié)點(diǎn)左右子樹都比較平衡的二叉查找樹,這就是我們下一節(jié)課要詳細(xì)講的,一種特殊的二叉查找樹,平衡二叉查找樹。平衡二叉查找樹的高度接近 logn,所以插入、刪除、查找操作的時(shí)間復(fù)雜度也比較穩(wěn)定,是 O(logn)。

開篇解答

我們在散列表那節(jié)中講過,散列表的插入、刪除、查找操作的時(shí)間復(fù)雜度可以做到常量級的 O(1),非常高效。而二叉查找樹在比較平衡的情況下,插入、刪除、查找操作時(shí)間復(fù)雜度才是 O(logn),相對散列表,好像并沒有什么優(yōu)勢,那我們?yōu)槭裁催€要用二叉查找樹呢?我認(rèn)為有下面幾個(gè)原因:

第一,散列表中的數(shù)據(jù)是無序存儲的,如果要輸出有序的數(shù)據(jù),需要先進(jìn)行排序。而對于二叉查找樹來說,我們只需要中序遍歷,就可以在 O(n) 的時(shí)間復(fù)雜度內(nèi),輸出有序的數(shù)據(jù)序列。

第二,散列表擴(kuò)容耗時(shí)很多,而且當(dāng)遇到散列沖突時(shí),性能不穩(wěn)定,盡管二叉查找樹的性能不穩(wěn)定,但是在工程中,我們最常用的平衡二叉查找樹的性能非常穩(wěn)定,時(shí)間復(fù)雜度穩(wěn)定在 O(logn)。

第三,籠統(tǒng)地來說,盡管散列表的查找等操作的時(shí)間復(fù)雜度是常量級的,但因?yàn)楣_突的存在,這個(gè)常量不一定比 logn 小,所以實(shí)際的查找速度可能不一定比 O(logn) 快。加上哈希函數(shù)的耗時(shí),也不一定就比平衡二叉查找樹的效率高。

第四,散列表的構(gòu)造比二叉查找樹要復(fù)雜,需要考慮的東西很多。比如散列函數(shù)的設(shè)計(jì)、沖突解決辦法、擴(kuò)容、縮容等。平衡二叉查找樹只需要考慮平衡性這一個(gè)問題,而且這個(gè)問題的解決方案比較成熟、固定。

最后,為了避免過多的散列沖突,散列表裝載因子不能太大,特別是基于開放尋址法解決沖突的散列表,會浪費(fèi)一定的存儲空間。

綜合這幾點(diǎn),平衡二叉查找樹在某些方面還是優(yōu)于散列表的,所以,這兩者的存在并不沖突。我們在實(shí)際的開發(fā)過程中,需要結(jié)合具體的需求來選擇使用哪一個(gè)。

課后思考

今天我講了二叉樹高度的理論分析方法,給出了粗略的數(shù)量級。如何通過編程,求出一棵給定二叉樹的確切高度呢?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

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

  • 一些概念 數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系,并對這種結(jié)構(gòu)定義相應(yīng)的運(yùn)算,而且確保經(jīng)過這...
    Winterfell_Z閱讀 5,882評論 0 13
  • 二叉樹中有種特殊的樹是二叉查找樹,其最大的特點(diǎn)就是,支持動態(tài)的快速插入、刪除、查找操作。 其實(shí)除了二叉查找樹外,散...
    GallenZhang閱讀 443評論 0 0
  • 一直以來,我都很少使用也避免使用到樹和圖,總覺得它們神秘而又復(fù)雜,但是樹在一些運(yùn)算和查找中也不可避免的要使用到,那...
    24K男閱讀 6,778評論 5 14
  • 轉(zhuǎn)載請注明出處!http://www.lxweimin.com/p/d9d9f223f0ad Github源碼地址...
    yifyif閱讀 4,325評論 0 7
  • 今天很榮幸能夠參加這次面試,希望能通過這次面試把自己展示給您,并且通過交流互相學(xué)習(xí)。我叫黃允婷,今年26歲,碩士畢...
    莒箬小姐閱讀 339評論 0 0