圖解:什麼是AVL樹?

雷子的程式設計江湖發表於2020-10-21

 

本文絕對乾貨,食用時間約8分鐘,建議細品!

 

 

引子

上一次我給大家介紹了什麼是二叉搜尋樹,但是由於二叉搜尋樹查詢效率的不穩定性,所以很少運用在實際的場景中,所以我們偉大的前人就對二叉搜尋樹進行了改良,發明了AVL樹。

 

AVL樹是一種自平衡二叉搜尋樹,因為AVL樹任意節點的左右子樹高度差的絕對值不超過1,所以AVL樹又被稱為高度平衡樹。

 

AVL樹本質上是一棵帶有平衡條件的二叉搜尋樹,它滿足二叉搜尋樹的基本特性,所以本次主要介紹AVL樹怎麼自平衡,也就是理解它的旋轉過程。

 

二叉搜尋樹特性忘了的小夥伴可以看之前的文章:搞定二叉搜尋樹,9圖足矣!同時我也將基本性質給大家再回顧一遍:

  1. 若它的左子樹不為空,則左子樹上所有節點的值均小於根節點的值。

  2. 若它的右子樹不為空,則右子樹上所有節點的值均大於根節點的值。

  3. 它的左、右子樹也分別為二叉搜尋樹。

 

平衡條件:每個節點的左右子樹的高度差的絕對值不超過1。

 

我們將每個節點的左右子樹的高度差的絕對值又叫做平衡因子。

 

AVL樹的旋轉行為一般是在插入和刪除過程中才發生的,因為插入過程中的旋轉相比於刪除過程的旋轉而言更加簡單和直觀,所以我給大家圖解一下AVL樹的插入過程。

 

插入過程

最開始的時候為空樹,沒有任何節點,所以我們直接用資料構造一個節點插入就好了,比如第一個要插入的資料為18。

 

第一個節點插入完成,開始插入第二個節點,假如資料為20。

 

插入第三個節點資料為14。

 

第四個節點資料為16。從根節點位置開始比較並尋找16的對應插入位置。

 

第五個要插入的資料為12。還是一樣,從樹的根節點出發,根據二叉搜尋樹的特性向下尋找到對應的位置。

 

此時插入一個資料11,根據搜尋樹的性質,我們不難找到它的對應插入位置,但是當我們插入11這個節點之後就不滿足AVL樹的平衡條件了。

 

此時相當於18的左子樹高了,右子樹矮了,所以我們應該進行一次右單旋,右單旋使左子樹被提起來,右子樹被拉下去,相當於左子樹變矮了,右子樹變高了,所以一次旋轉之後,又滿足平衡條件了。

 

簡單分析上圖的旋轉過程因為左子樹被提上去了,所以14成為了新的根節點,而18被拉到了14右子樹的位置,又因為14這個節點原來有右子節點為16,所以18與16旋轉之後的位置就衝突了,但是因為16小於18,所以這個時候根據二叉搜尋樹的特性,將16調整到18的左子樹中去,因為旋轉之後的18這個節點的左子樹是沒有節點的,所以16可以直接掛到18的左邊,如果18的左子樹有節點,那麼還需要根據二叉搜尋樹的性質去將16與18左子樹中的節點比較大小,直到確定新的位置。

 

經過上面的分析我們可以知道:如果新插入的節點插入到根節點較高左子樹的左側,則需要進行一次右單旋,我們一般將這種情況簡單記為左左情況,第一個左說的是較高左子樹的左,第二個左說的是新節點插入到較高左子樹的左側。

 

分析完了左左的情況,我想小夥伴們不難推出右右的情況(第一個右說的是較高右子樹的右,第二個右說的是新節點插入到較高右子樹的右側),就是一次左單旋,這裡就不一步一步地分析右右的情況了,因為它和左左是對稱的。給大家畫個圖,聰明的你一眼就可以學會!

 

現在兩種單旋的情況已經講完了,分別是左左和右右,還剩下兩種單旋的情況,不過別慌,因為雙旋比你想象中的簡單,而且同樣,雙旋也是兩種對稱的情況,實際上我們只剩下一種情況需要分析了,所以,加油,弄懂了的話,面試的時候就完全不用慌了!

 

雙旋

我們假設當前的AVL樹為下圖。

 

這個時候我們新插入一個節點,資料為15,根據搜尋樹的性質,我們找到15對應的位置並插入,如圖

 

我們此時再次計算每個節點的平衡因子,發現根節點18的平衡因子為2,超過了1,不滿足平衡條件,所以需要對他進行旋轉。

 

我們將剛才需要進行右單旋的左左情況和現在的這種情況放在一起對比一下,聰明的你一定發現,當前的情況相比於左左的情況只是插入的位置不同而已,左左情況插入的節點在根節點18較高左子樹的左側,而當前這種情況插入節點是在根節點18較高左子樹的右側,我們將它稱為左右情況。

 

那麼可能正看到這裡的你可能不禁會想:這不跟剛才左左差不多嘛,直接右單旋不就完事了。真的是這樣嗎?讓我們來一次右單旋看看再說。

 

簡單分析該右單旋節點14上提變成新的根節點,18下拉變成根節點的右子樹,又因為當前根節點14原來有右子樹16,所以18與16位置衝突,比較18與16大小之後,發現18大於16,根據搜尋樹的性質,將以16為根節點的子樹調整到18的左子樹,因為18的左子樹目前為空,所以以16為根的子樹直接掛在18的左側,若18的左子樹不為空,則需要根據搜尋樹的性質繼續進行比較,直到找到合適的掛載位置。

 

既然一次右單旋不行,那麼我們應該怎麼辦呢?答案就是進行一次雙旋,一次雙旋可以拆分成兩次單旋,對於當前這種不平衡條件,我們可以先進行一次左單旋,再進行一次右單旋,之後就可以將樹調整成滿足平衡條件的AVL樹了,話不多說,圖解一下。

 

簡單分析左右雙旋先對虛線框內的子樹進行左單旋,則16上提變成子樹的新根,以14為根節點的子樹下拉,調整到16的左子樹,此時發現16的左子樹為15,與14這棵子樹衝突,所以根據搜尋樹規則進行調整,將15掛載到以14為根節點子樹的右子樹,從而完成一次左單旋,之後再對整棵樹進行一次右單旋,節點16上提成為新的根節點,18下拉變成根節點的右子樹,因為之前16沒有右子樹,所以以18為根節點的子樹直接掛載到16的右子樹,從而完成右旋。

 

同樣,對於左右情況的對稱情況右左情況我就不給大家分析了,還是將圖解送給大家,相信聰明的你一看就會!

 

到此為止,我將AVL樹的四種旋轉情況都給大家介紹了一遍,仔細想想,其實不止這四種情況需要旋轉,嚴格意義上來說有八種情況需要旋轉,比如之前介紹的左左情況吧,我們說左左就是將新的節點插入到了根節點較高左子樹的左側,這個左側其實細分一下又有兩種情況,只不過這兩種情況實際可以合成一種情況來看,也就是新的節點插入到左側的時候可以成為它父親節點的左孩子,也可以成為它父親節點的右孩子,那麼這樣的話就是相當於兩種情況了,簡單畫個圖看一下吧。

 

就是這樣上圖這樣,每個新插入的節點都可以是它父親節點的左孩子或者右孩子,這取決於新插入資料的大小,比如11就是12的左孩子,13就是12的右孩子,這兩種情況都屬於左左情況,也就是說他們本質上是一樣的,都插在了節點18較高左子樹的左側。

 

那麼這樣看來這四種旋轉情況嚴格上看都可以多分出一種情況,變成八種情況。

 

後話

emmm…這樣看來AVL樹確實解決了二叉搜尋樹可能不平衡的缺陷,補足了效能上不穩定的缺陷,但是細細想來AVL樹的效率其實不是很好,這裡說的不是查詢效率,而是插入與刪除效率,上面所說的這四大種八小種情況還是很容易命中的,那麼這樣的話就需要花費大量的時間去進行旋轉調整,我的天,這樣也太難搞了!

 

不過聰明的前人早就為我們想好了更加利於實際用途的搜尋樹,在現實場景中AVL樹和二叉搜尋樹一樣,基本上用不到,我們接下來要講的這種二叉類的搜尋樹才是我們經常應用的,相信見多識廣的你一定猜到了它的名字,對,就是它,大名鼎鼎的紅黑樹!我們下次來盤他!

 

鄙人才疏學淺,若有任何差錯,還望各位海涵,不吝指教!

 

喜歡本文的少俠們,歡迎長按下圖關注公眾號雷子的程式設計江湖,修煉更多武林祕籍。

一鍵三連是中華民族的當代美德!

相關文章