二叉樹,二叉查找樹,平衡二叉樹以及紅黑樹概述

在這篇博客之前,花了些時間瞭解紅黑樹的內容,但是沒有形成自己的知識圖譜,也沒有一條清晰的邏輯主線將知識串聯起來,這次重新整理了一下。

首先,這裏過濾了樹模型的一些基礎概念上的內容,比如父節點,子節點,葉子節點(葉節點),兄弟節點,樹的深度,樹的高度,樹的層數等,在這篇博客中沒有敘述。

首先以最基本的二叉樹開始,由淺入深,逐漸瞭解各個算法的優缺點適用場景,可以解決什麼樣的問題,優缺點有哪些,實現權衡等。

章節和對應的主要內容:

序號 內容
1 二叉樹
2 二叉查找樹
3 平衡二叉查找樹、紅黑樹

1:二叉樹

二叉樹可以分爲以下幾種:

  • 普通二叉樹——對應編號1
  • 滿二叉樹——對應編號2
  • 完全二叉樹——對應編號3

二叉樹的幾種類別

(1)普通二叉樹

  • 編號1,由一個根節點加上兩棵分別稱爲左子樹和右子樹組成

(2)滿二叉樹

  • 編號 2 的二叉樹中,葉子節點全都在最底層,除了葉子節點之外,每個節點都有左右兩個子節點

(3)完全二叉樹

  • 編號 3 的二叉樹中,葉子節點都在最底下兩層,最後一層的葉子節點都靠左排列,並且除了最後一層,其他層的節點個數都要達到最大

在理解二叉樹的定義和類別後,進一步思考,如何存儲一棵二叉樹

想要存儲一棵二叉樹,一種方法是基於指針或者引用的二叉鏈式存儲法,一種是基於數組的順序存儲法。

下圖表示的就是二叉樹的鏈式存儲法,鏈式存儲更爲直觀,也比較簡單。圖中,每個節點有三個字段,其中一個存儲數據,另外兩個指向左右子節點的指針。只要找到根節點,就可以通過左右子節點的指針,把整棵樹都串聯起來,大部分二叉樹都是通過這種結構實現的。

在這裏插入圖片描述

另一種方法,如下圖所示,基於數組的順序存儲法,圖中,我們把根節點A存儲在下標i=1的位置,那麼左子節點B存儲在下標2*i=2的位置,右子節點C存儲在2 * i +1=3的位置,以此類推。總結:如果節點X存儲在數組中下標爲i的位置,下標爲2i的位置存儲的就是其左子節點,下標爲2 * i +1的位置存儲的就是右子節點,反過來也可以倒推。因此,我們只要知道根節點的存儲位置(一般情況下,爲了方便計算,根節點都會存儲在下標爲1的位置),這樣就可以通過下標計算,把整棵樹的節點串聯起來。

由於圖中顯示的是一顆完全二叉樹,所以僅僅浪費了一個下標爲0的存儲位置。

[外鏈圖片轉存失敗(img-1QwyyrCV-1562332114148)(C:\Users\weidai\AppData\Local\Temp\1561981522287.png)]

下圖中,由於是一個非完全二叉樹,所以在用數組存儲的時候,會浪費比較多的存儲空間。

[外鏈圖片轉存失敗(img-kbvb0JEh-1562332114148)(C:\Users\weidai\AppData\Local\Temp\1561982185267.png)]

二叉樹的遍歷

遍歷有三種方式:前序遍歷、中序遍歷和後序遍歷。其中,前、中、後序,表示的是節點與它的左右子樹接地那遍歷的先後順序

  • 前序遍歷是指,對於樹中的任意節點來說,先打印這個節點,然後再打印它的左子樹,最後打印它的右子樹——根左右

  • 中序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它本身,最後打印它的右子樹——左根右

  • 後序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它的右子樹,最後打印這個節點本身——左右根

2:二叉查找樹

二叉查找樹是二叉樹中最常用的一種類型,是爲了實現快速查找的,不僅僅支持快速查找一個數,還支持快速插入和刪除數據。二叉查找樹的這些性能都依賴於二叉查找樹的特殊結構,二叉查找樹的要求,在樹中的任意一個接地那,其左子樹中的每個節點的值,都要小於這個節點的值,而右子樹節點的值都要大於這個節點的值。

[外鏈圖片轉存失敗(img-NqSiCOPy-1562332114150)(C:\Users\weidai\AppData\Local\Temp\1561982871660.png)]

2.1:二叉查找樹的查找

如果需要查找一個數,首先取根節點,如果它等於要查找的數據,則直接返回,如果小於要查找的數據,則在右子樹中繼續查找,如果大於要查找的數據,則在左子樹中繼續查找,也就是二分查找的思想,這樣一直遞歸。

2.2:二叉查找樹的插入

二叉樹的插入類似於查找過程,首先還是從根節點開始,然後依次比較要插入的數據與接地那的關係。如果要插入的數據比節點的數據大,並且節點的右子樹爲空,就將新數據直接插到右子節點的位置;如果不爲空,就再遞歸遍歷右子樹,查找插入位置。同理,如果要插入的數據比節點的數據小,也是類似的操作。

2.3:二叉查找樹的刪除

二叉查找樹的刪除相對於查找和插入要稍微複雜一點。主要有3種情況需要考慮:

  1. 如果要刪除的節點沒有子節點,只需要將父節點中,指向闡述接地那的指針置爲NULL,比如刪除圖中的節點55
  2. 如果要刪除的節點只有一個子節點(只有左子節點或者右子節點),只需要刪除父節點中,指向要刪除的指針,讓它指向要刪除的節點的子節點就可以了。比如要刪除圖中節點13
  3. 如果要刪除的節點上有兩個子節點,要稍微複雜一點。首先找到這個節點的右子樹中最小的節點,把它替換到要刪除的節點,然後再刪除這個最小節點。因爲最小節點肯定沒有左子節點。比如刪除節點18

[外鏈圖片轉存失敗(img-AJbidSQM-1562332114153)(C:\Users\weidai\AppData\Local\Temp\1561983513007.png)]

2.4:二叉查找樹的時間複雜度分析

二叉查找樹的形態各式各樣。比如這個圖中,對於同一組數據,我們構造了三種二叉查找樹,它們的查找、插入、刪除操作的執行效率都是不一樣的。

極端情況下,圖中第一種二叉查找樹,根節點的左右子樹極度不平衡,已經退化成了鏈表,所以查找的時間複雜度就變成了 O(n)。

[外鏈圖片轉存失敗(img-judJkuqx-1562332114154)(C:\Users\weidai\AppData\Local\Temp\1561983897871.png)]

而從圖中可以看到,二叉查找樹的插入,刪除,查找,都是與樹的高度有關。

那麼我們考慮在最理想的情況下,問題就變成:如何求得一棵包含n個節點的完全二叉樹的高度?

根據完全二叉樹的定義,很容易求得:完全二叉樹的高度是小於扥等於log2n。

2.5:二叉查找樹的總結

二叉查找樹,除了插入、刪除、查找之外,還可以支持快速查找最大節點、最小節點、前繼節點、後繼節點。同事二叉查找樹還有一個重要特性,就是 中序遍歷二叉樹,可以輸出有序的數據序列,時間複雜度爲O(n),非常高效。

進一步思考: 二叉查找樹可以支持快速插入、刪除、查找操作,各個操作的時間複雜度與樹的高度成正比,最理想情況下,時間複雜度是O(logn)。

但在極端情況下,比如頻繁的刪除等操作,二叉樹會退化成一個鏈表,那麼時間複雜度就變成了O(n)。

因此,爲了避免這種極端情況,後來又設計一種平衡二叉樹,平衡二叉查找樹的高度接近 logn,所以插入、刪除、查找操作的時間複雜度也比較穩定,是 O(logn)。

3:平衡二叉樹

3.1:平衡二叉樹的概念

平衡二叉樹定義二叉樹中任意一個節點的左右子樹的高度相差不能大於 1,所以上圖中的滿二叉樹和完全二叉樹都是平衡二叉樹,但是非完全二叉樹也可以是平衡二叉樹。

[外鏈圖片轉存失敗(img-eQDQOCCY-1562332114155)(C:\Users\weidai\AppData\Local\Temp\1562326496635.png)]

最先被髮明的平衡二叉查找樹是AVL樹,它是一種非常嚴格的平衡二叉查找樹,即任何節點的左右子樹高度相差不超過 1。

實際上,平衡二叉查找樹的出現就是爲了避免二叉查找樹在頻繁的插入、刪除等操作的情況下,出現極端情況(比如退化成鏈表),導致時間複雜度退化。

因此,平衡二叉查找樹中的“平衡”,意思就是讓整個樹模型看起來更加對稱,不會出現左邊特別高,右邊特別低,或者左邊特別低,右邊特別高的情況。這樣,整棵樹的高度相對來說更低一點,所以插入,刪除,查找等操作的效率更高。

3.2:平衡二叉樹帶來的問題:

雖然平衡二叉樹解決了二叉查找樹可能退化了鏈表的極端情況,並且能夠把查找時間控制在O(logn),所以”平衡“的意思就是爲了使性能不退化。但是由於平衡二叉樹嚴格的定義:每個節點的左子樹和右子樹的高度差至多等於1,導致每次在插入或者刪除的時候,爲了符合平衡二叉樹嚴格的條件,需要進行一些操作來進行調整,比如左旋、右旋等操作,使其再次符合平衡二叉樹的條件。

很明顯,如果要是再插入和刪除非常頻繁的場景下,平衡二叉樹每一次更新都必須要進行調整,這樣太耗時了,性能也會受到很大影響。

因此,爲了避免平衡二叉樹在頻繁更新過程中,所帶來的維持樹結構的時間消耗,從而引入了紅黑樹。

4:紅黑樹

上面說到,平衡二叉樹的定義非常嚴格,因此導致在一些特定的場合中, 維持樹結構的時間消耗較大。

因此,引入紅黑樹解決上述問題,紅黑樹是一個近似平衡的二叉樹,它的定義如下:

  1. 具有二叉查找樹的特點
  2. 根節點是黑色的
  3. 每個葉子節點都是黑色的空節點(NIL),也就是說,葉子節點不存數據
  4. 任何相鄰的節點都不能同時爲紅色,也就是說,紅色節點是被黑色節點隔開的
  5. 每個節點,從該節點到達其可達的葉子節點是所有路徑,都包含相同數目的黑色節點。

[外鏈圖片轉存失敗(img-OKjBYYG1-1562332114155)(C:\Users\weidai\AppData\Local\Temp\1562328184794.png)]

既然紅黑樹是一個近似平衡的二叉樹,那麼它的性能與平衡二叉樹相比如何?

由上面第二節講到,二叉查找樹的很多操作性能都是與樹的高度成正比,而一棵滿二叉樹、完全二叉樹的高度大約是log2n。所以要證明紅黑樹是近似平衡的,只需要分析,紅黑樹的高度與二叉查找樹最好情況下的log2n相比就可以。

紅黑樹近似平衡的簡單證明:

下圖是兩顆紅黑樹,首先,如果將紅色節點從紅黑樹中刪除,那隻包含黑色節點的紅黑樹的高度是多少呢?

紅色節點刪除之後,有些節點就沒有父節點了,它們會直接拿這些節點的祖父節點(父節點的父節點)作爲父節點,所以,之前的二叉樹就變成了四叉樹。而完全二叉樹的高度小於等於log2n,所以,這裏的四叉黑樹的高度要低於完全二叉樹,所以去掉紅色節點“黑樹”的高度也不會超過log2n

同時,由於紅黑樹中的定義:紅色節點不能相鄰,因此,有一個紅色節點,其上父節點和其下子節點(如果有子節點的話)一定是黑色節點,也就是說黑色節點將紅色節點與不在同一層的其他紅色幾點隔離開了,而去掉紅色紅色節點“黑樹”的高度不會超過log2n,那麼假如紅色節點後,最長路徑也不會超過2log2n(假設隔一層就有個紅色節點將原始的黑色節點分開),也就是說,紅黑樹的高度近似 2log2n。

[外鏈圖片轉存失敗(img-WN7AIVmf-1562332114157)(C:\Users\weidai\AppData\Local\Temp\1562329325509.png)]

所以,紅黑樹的高度只比高度平衡的 AVL 樹的高度(log2n)僅僅大了一倍,在性能上,下降得並不多,但卻保留了平衡二叉樹優點,比如插入,刪除,查找等特性。

5:總結

線性表,插入時間複雜度爲O(1),但是因爲內部無法保證有序,所以查找需要O(n)的時間複雜度,而刪除則取決於所用實現是鏈表還是數組,鏈表爲O(1),數組爲O(n)。比較社會數據量較小,查詢量也極小的情況(<= 100)。

二叉查找樹,插入,查找,刪除等操作,均攤時間複雜度爲O(logn),時間表現效率較高,但極端情況下,很可能出現退化成鏈表的情況,導致時間複雜度變爲O(n)。

爲了避免二叉查找樹退化成鏈表,引入平衡二叉樹,平衡二叉樹定義:二叉樹中任意一個節點的左右子樹的高度相差不能大於 1,所以不會出現左邊特別高,右邊特別低,或者左邊特別低,右邊特別高的情況(也就是整棵樹看起來更加平衡)。

雖然平衡二叉樹避免退化成鏈表的情況,並且能夠把查找時間控制在O(logn),但是爲了符合平衡二叉樹嚴格的條件,如果有頻繁的插入,刪除的操作,每次在更新的時候,需要進行一些操作來進行調整,比如左旋、右旋等操作,使其再次滿足平衡二叉樹的條件,導致維持樹結構的時間消耗較多,複雜度退化。

爲了解決平衡二叉樹在維持樹結構上時間消耗的問題,引入了紅黑樹,由於紅黑樹的條件限制,與平衡樹不同的是,紅黑樹在插入、刪除等操作,不會像平衡樹那樣,頻繁着破壞紅黑樹的規則,所以不需要頻繁着調整。紅黑樹的高度近似 log2n,所以它是近似平衡,插入、刪除、找操作的時間複雜度都是 O(logn)。

備註:圖片來自極客時間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章