數據映射–平衡二叉有序樹

上次咱們提到了使用有序的數組來進行二分查找,從而提升映射查詢的效率,使時間複雜度從O(n)下降到O(log2N).html

本週讓我來介紹一下二叉樹。算法

一談到二叉樹,相信不少人必定會有一個疑問:這玩意兒有什麼用?(固然這麼多人裏面確定包括大學時候的我--)數組

其實,我我的以爲這並不怪咱們,是教科書寫的有點問題,開始的時候沒有給到你們明確的學習意義,開始就去講如何遍歷,如何從樹變森林,如何作樹的前序中序後序遍歷。但這樣的學習會讓整個過程很無聊,太容易讓人放棄了。因此在今天,請容許我用另外的方式來從新講解一下吧~數據結構

首先,明確一下意義,二叉樹主要用來表示樹形結構的數據,主要的應用場景是,實現數據映射,或者實現壓縮算法(哈夫曼樹)。併發

下面,讓咱們從二叉樹的特性,來看看二叉樹能作些什麼。ide

1.有一個ROOT節點性能

意味着每一次查詢都須要從一個節點開始進行,與之相對的,若是是圖類的數據結構,則起點是不固定的。學習

2.一個節點有兩個子節點。指針

與擁有多個子節點的樹相比,兩個子節點會讓樹變得更深,但好處咱們在後面的二叉排序樹時會看到。htm

3.父節點都有一個到子節點的引用(指針)。有些時候,爲了遍歷方便,還須要一個從子節點到父節點的引用(指針)

有些時候,咱們須要順序的遍歷一棵樹,這時候若是有了雙向指針,那麼遍歷就會變得更爲方便。

一個純粹的二叉樹,除了能支持遍歷之外,沒有什麼油水,下面讓咱們來看看二叉樹的最主要用途—-映射,是如何利用平衡二叉排序樹來實現的吧。

先從二叉排序樹開始提及,所謂的二叉排序樹,其實只是在二叉樹上額外增長了一個條件,左邊的子節點上的數據必定比父節點的小,而右邊子節點必定比父節點的數據大。

咱們仍是用上週咱們使用過的例子來作一下講解:

給定有序結果集S={1對應a,2對應b,3對應c,4對應e,6對應f},讓咱們以這個映射關係來作例子,以便你們可以更快速的理解。

這樣作的最大好處麼~就是能夠進行順序遍歷了。其餘好處彷佛是沒有。好比如下這樣的二叉樹:204140366.jpg

這就是一個很是極端的狀況了。也就是沒有左面的孩子,只有右面的孩子,這樣的一棵樹其實就退化成了鏈表,由於訪問只能從root開始,因此除了可以提供順序遍歷以外,沒法提供更多的功能了。

然而,若是咱們再加上一個條件,那麼二叉排序樹就馬上可以麻雀變鳳凰,成爲咱們的一種重要的實現映射的利器了。

這個條件,就是平衡,因而全稱就變成了:平衡二叉排序樹。咱們來看看平衡的定義:

一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。

204228292.jpg

也就是說,除了最底層的節點,樹的左子節點和右子節點應該保證都有值。則樹就會平衡,而且樹的高度是log2N。

嘿嘿,看到log2N,是否是有似曾相識的感受?沒錯,就是二分查找的時間複雜度,或者用更容易理解的一個詞的話,爲了查找到指定的數據所須要遍歷節點的次數。

爲何二分查找和樹的高度在數值上如此的一致呢?這就是咱們下面要重點分析的東西了。

首先來回憶一下咱們在有序數組章節

(http://blog.sina.com.cn/s/blog_693f08470101mi2o.html)裏面提到的二分查找算法的必要條件吧:

1.數據可以按照某種條件進行排序,好比S={0,1,2,3,4,5,6,7,100,101,102}就是排好序的數據,左面的數據必定小於右面的。

2.能夠經過某種方式,取出該數據集中任意子集的中間值。好比對於S={0,1,2,3,4,5,6,7,100,101,102},取中值意味着應該取出下標爲整數除法(011)/2=5的數字。若是咱們可以快速而直接的取出這個中間值,也就是可以快速的取出下標爲5的位置所對應的數據,那麼咱們就可以進行二分查找。

在平衡二叉排序樹中,上面的兩個要求是可以被知足的。

首先,數據自己是有序的,左邊的子節點內的數據,必定小於父節點內的數據,而右邊節點內的數據,必定大於父節點內的數據。

其次,在平衡二叉樹中,你會發現父節點永遠是兩個子節點的「中值」,所以能夠利用這個中值很是快速的排除掉一半的數據。

所以,幾乎能夠認爲,一顆平衡二叉樹,也同樣能夠利用二分查找的方式快速的從整個數據集合上面快速的取到符合要求的結果。

那麼,既然排序後數組和平衡二叉排序樹均可以以O(log2N)的代價來快速的根據一個key定位到value.那麼我爲何不用數組來作這件事,而要選擇使用平衡二叉排序樹呢?

這裏就涉及到一個問題,排序數組有什麼短板沒有呢?固然是有的,就是不支持更新。而平衡二叉樹更新的代價則要小不少,緣由也很簡單,由於父節點和子節點之間使用了引用(指針)來進行的數據組織的,因此,須要插入新數據的時候,只須要調整指針就可讓樹重新平衡並有序了。

固然,這種調整的方式有不少種,他們各有優點和劣勢。不過目前由於不須要咱們花功夫去實現這些數據結構了,因此只須要簡單瞭解一下我以爲就能夠了。

首先被提出來的平衡樹的方式是AVL樹,而後提出來的是TreeHeap樹,最後目前在實踐中最爲高效的紅黑樹。

在Java中,目前的TreeMap就是使用了紅黑樹來實現的,各位感興趣也能夠去看一下他的代碼,對這類二叉樹的平衡算法,教科書上面教的已經很完美了,帶僞碼帶原理,這裏我就不展開了。

在文章的末尾,咱們仍是以咱們定義的幾個集合的評價標準來看看這平衡二叉排序樹的技術特性

1.是否支持範圍查找

由於數據是有序的,因此理論上來講是可以支持範圍查找的,但從細節來講,支持的方法卻不是徹底相同。

這裏會用到你們在教科書上面學的,二叉樹的遍歷方法中的中序遍歷方法,也即若是要順序的訪問數據,須要不斷地重複左子樹->根節點->右子樹的遍歷方式,直到查詢結束。

若是咱們只存了從父節點到子節點的指針,那麼在遍歷過程當中,咱們就必須使用一個額外的棧來存放某個子節點的父節點的引用,不然咱們是沒法從子節點回到父節點的。

而若是咱們在每一個節點都存放父節點到子節點的指針後,額外的再存一個從子節點到父節點的指針,那麼咱們就不須要用額外的棧來幫助咱們進行遍歷了,能夠直接按照中序遍歷的方式便可。

2.集合是否可以隨着數據的增加而自動擴展

費了那麼半天勁,也就是爲了能讓數據增加變得更簡單。因此咱們能夠很高興的告知你們,使用平衡排序二叉樹,是能夠支持數據自動擴展的,鼓掌~

讓樹可以在保持有序的前提下儘量平衡的主要方式就是咱們上面提到過的AVL,Treeheap,以及紅黑樹。

3.讀寫性能如何

在內存中指針的跳轉速度雖然不如使用數組快,不過也是很快的,基本上咱們能夠認爲查詢效率就是O(log2N)。

對於寫來講,效率也是O(log2N)

4.是否面向磁盤結構

回憶咱們提到過的磁盤的特性,一次取出一塊數據,能比較好的處理順序讀寫,而對隨機讀寫則不擅長。

對於內存來講,根據指針的要求在內存中進行跳躍,代價並不高,但若是這個操做在磁盤中進行,那麼根據指針的要求的每一次跳躍,都是一次磁盤的隨機讀寫,由於在取出節點來實際看看以前,咱們沒法預測這個父節點的子節點被放在磁盤的哪一個位置上的。那麼查詢一次數據須要跳躍多少次呢?O(log2N-1)次。。跳躍的次數仍是很是誇張的。

所以,平衡二叉查找樹,不是個面向磁盤的結構。

5.並行指標

不大適合並行操做,在進行結構調整的時候讀取確定是錯誤的,可以使用的並行讀寫的思路,主要是兩類:

一個是鎖分離

一個是copyonwrite

不過由於在目前的平衡二叉樹實現中基本都須要作旋轉操做,沒法保證這個旋轉的原子性,因此在主流的平衡二叉排序樹中沒見過可以很好地處理併發的。

6.內存佔用

相比較數組而言,鏈表的內存佔用是固定的,每一個節點上固定的兩個到下層子節點的指針,以及到父節點的一個指針。其餘空間所有能夠存放數據,空間消耗上,若是數組上的數據是全滿的,那麼鏈表沒優點。不過若是要是自增的數組,也即每次都以2倍空間大小作自動擴展,那麼鏈表的內存佔用通常是優於自動擴展數組的各種實現的,由於自動擴展數組最壞狀況下有一半的空間都是空着的。

相關文章
相關標籤/搜索