几种常见的树:排序二叉树、平衡二叉树、红黑树、B+树
一、排序二叉樹(shù)(Binary Sort Tree,BST樹(shù))
二叉排序樹(shù),又叫二叉搜索樹(shù)、有序二叉樹(shù)(ordered binary tree)或排序二叉樹(shù)(sorted binary tree)。
1.BST樹(shù)的特點(diǎn)
排序二叉樹(shù)要么是一棵空二叉樹(shù),要么是具有下列性質(zhì)的二叉樹(shù):
若它的左子樹(shù)不空,則左子樹(shù)上所有節(jié)點(diǎn)的值均小于它的根節(jié)點(diǎn)的值;
若它的右子樹(shù)不空,則右子樹(shù)上所有節(jié)點(diǎn)的值均大于它的根節(jié)點(diǎn)的值;
它的左、右子樹(shù)也分別為排序二叉樹(shù)。
沒(méi)有鍵值相等的節(jié)點(diǎn)。
由排序二叉樹(shù)的特點(diǎn),我們很容易得出這樣的結(jié)論:按中序遍歷排序二叉樹(shù)可以得到由小到大的有序序列。排序二叉樹(shù)要求所有的元素都能夠排序,也就是鍵要實(shí)現(xiàn)comparable接口。
2.BST樹(shù)的缺點(diǎn)
排序二叉樹(shù)雖然可以快速檢索,但出現(xiàn)最壞的情況——如果插入的節(jié)點(diǎn)集本身就是有序的(從小到大排列或從大到小排列),在這種情況下,排序二叉樹(shù)就退化成了普通鏈表,其檢索效率就會(huì)很差。
二、平衡樹(shù)
為了保證不退化成線性查找,就要維持樹(shù)的平衡。這個(gè)平衡條件要容易保持,從而保證樹(shù)的深度為O(logN),很容易想到兩種平衡條件:
最簡(jiǎn)單的是要求左右子樹(shù)具有相同的高度。
另一種是要求每個(gè)節(jié)點(diǎn)都必須有相同高度的左子樹(shù)和右子樹(shù)。
然而,這兩種要求都太嚴(yán)格而難以使用。
1.AVL樹(shù)(Adelson-Velskii 和 Laandis)
AVL樹(shù)得名于它的發(fā)明者 G.M. Adelson-Velsky 和 E.M. Landis,是最早被發(fā)明的一種平衡樹(shù)。平衡二叉樹(shù)本質(zhì)上還是一顆二叉查找樹(shù),只是帶上了平衡條件。
一棵平衡二叉樹(shù)是每個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的高度最多差1的二叉查找樹(shù)。
平衡二叉樹(shù)是帶有平衡條件的二叉查找樹(shù)
平衡二叉樹(shù)的每個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的高度最多差1
1.1AVL樹(shù)的特點(diǎn)
AVL樹(shù)中的每個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的高度差不會(huì)大于1。在插入時(shí),檢查新節(jié)點(diǎn)的插入點(diǎn)所在的最低子樹(shù)的根。如果它的子節(jié)點(diǎn)的高度相差大于1,則會(huì)破壞原有的平衡性,因此需要進(jìn)行旋轉(zhuǎn)操作以達(dá)到再次平衡,此時(shí)會(huì)執(zhí)行一次或兩次旋轉(zhuǎn)使他們的高度相等。然后算法向上移動(dòng),檢查上面的節(jié)點(diǎn),必要時(shí)均衡高度。這個(gè)檢測(cè)檢查所有路徑一直向上,直到根為止。
1.2.AVL樹(shù)的缺點(diǎn)
由于插入(或刪除)一個(gè)節(jié)點(diǎn)時(shí)需要掃描兩趟樹(shù),一次向下查找插入點(diǎn),一次向上平衡樹(shù),所以AVL樹(shù)不如下面介紹的紅黑樹(shù)效率高,也不如紅黑樹(shù)常用。
(也就是說(shuō)為了保持平衡,平衡二叉樹(shù)定義節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的高度差不大于1,這種規(guī)定過(guò)于嚴(yán)格,導(dǎo)致在做插入或刪除操作時(shí)需要自底向上逐層檢查節(jié)點(diǎn)的平衡性(高度差),因此效率較低)
2.紅黑樹(shù)(Red-Black Tree)
紅黑樹(shù)是二叉搜索樹(shù)的一種改進(jìn),是另一種平衡樹(shù)。我們知道二叉搜索樹(shù)在最壞的情況下退化成了一個(gè)鏈表,而紅黑樹(shù)在每一次插入或刪除節(jié)點(diǎn)之后都會(huì)花O(log N)的時(shí)間來(lái)對(duì)樹(shù)的結(jié)構(gòu)作修改,以保持樹(shù)的平衡。也就是說(shuō),紅黑樹(shù)的查找方法與二叉搜索樹(shù)完全一樣;插入和刪除節(jié)點(diǎn)的方法前半部分與二叉搜索樹(shù)完全一樣,而后半部分添加了一些修改樹(shù)的結(jié)構(gòu)的操作。
紅黑樹(shù)的定義:
每個(gè)節(jié)點(diǎn)是紅色或者黑色
根節(jié)點(diǎn)是黑色
每個(gè)葉子節(jié)點(diǎn)都是黑色的(這里的葉子節(jié)點(diǎn)指的并非指6,11,15,22,27這樣的節(jié)點(diǎn),而是圖中的NIL節(jié)點(diǎn),表示這是樹(shù)的尾端。)
對(duì)于任意的一個(gè)節(jié)點(diǎn),其到尾端節(jié)點(diǎn)(NIL)的路徑都包含了相同數(shù)目的黑節(jié)點(diǎn)。
參考:一步一圖一代碼,一定要讓你真正徹底明白紅黑樹(shù)
三、平衡多路查找樹(shù)
前面講的BST、AVL、紅黑樹(shù)都是典型的二叉查找樹(shù)結(jié)構(gòu),其查找的時(shí)間復(fù)雜度與樹(shù)高相關(guān)。那么降低樹(shù)高自然對(duì)查找效率是有所幫助的。另外還有一個(gè)比較實(shí)際的問(wèn)題:就是在大量數(shù)據(jù)存儲(chǔ)中實(shí)現(xiàn)查詢的場(chǎng)景下,平衡二叉樹(shù)由于樹(shù)深度過(guò)大而造成磁盤IO讀寫過(guò)于頻繁,進(jìn)而導(dǎo)致效率低下。那么如何減少樹(shù)的深度(當(dāng)然不能減少查詢數(shù)據(jù)量),一個(gè)基本的想法就是:
①.每個(gè)節(jié)點(diǎn)存儲(chǔ)多個(gè)元素(但元素?cái)?shù)量不能無(wú)限多,否則查找就退化成了節(jié)點(diǎn)內(nèi)部的線性查找了)。
②.摒棄二叉樹(shù)結(jié)構(gòu)而采用多分支(多叉)樹(shù)(由于節(jié)點(diǎn)內(nèi)元素?cái)?shù)量不能無(wú)限多,自然子樹(shù)的數(shù)量也就不會(huì)無(wú)限多了)。
這樣我們就提出了一個(gè)新的查找樹(shù)結(jié)構(gòu) ——多路查找樹(shù)。 根據(jù)AVL給我們的啟發(fā),一顆平衡多路查找樹(shù)(B樹(shù))自然可以使得數(shù)據(jù)的查找效率保證在O(logN)這樣的對(duì)數(shù)級(jí)別上。
1.B-樹(shù) (B樹(shù),平衡多路查找樹(shù))
可參考:《算法導(dǎo)論》第18章B樹(shù)
注意,這里的B-Tree中的減號(hào)只是分隔符,我們通常在書或博客中見(jiàn)到的B-Tree或者B~Tree實(shí)際上指的都是B樹(shù)。
B樹(shù)是為磁盤或其他直接存儲(chǔ)的輔助設(shè)備而設(shè)計(jì)的一種平衡搜索樹(shù)。B樹(shù)類似于紅黑樹(shù),但它們?cè)诮档痛疟PI/O操作次數(shù)方面要更好一些。許多數(shù)據(jù)庫(kù)使用B樹(shù)或B樹(shù)的變種來(lái)存儲(chǔ)信息。B樹(shù)與紅黑樹(shù)不同之處在于B樹(shù)的節(jié)點(diǎn)可以有多個(gè)孩子。B樹(shù)類似于紅黑樹(shù),就是含有n個(gè)節(jié)點(diǎn)的B樹(shù)的高度為O(lgn)。
1.1B樹(shù)的定義
一棵m階的B-Tree有如下特性:
1. 每個(gè)節(jié)點(diǎn)最多有m個(gè)孩子。
2. 除了根節(jié)點(diǎn)和葉子節(jié)點(diǎn)外,其它每個(gè)節(jié)點(diǎn)至少有Ceil(m/2)個(gè)孩子。
3. 若根節(jié)點(diǎn)不是葉子節(jié)點(diǎn),則至少有2個(gè)孩子
4. 所有葉子節(jié)點(diǎn)都在同一層,且不包含其它關(guān)鍵字信息
5. 每個(gè)非終端節(jié)點(diǎn)包含n個(gè)關(guān)鍵字信息(P0,P1,…Pn, k1,…kn)
6. 關(guān)鍵字的個(gè)數(shù)n滿足:ceil(m/2)-1 <= n <= m-1
7. ki(i=1,…n)為關(guān)鍵字,且關(guān)鍵字升序排序。
8. Pi(i=1,…n)為指向子樹(shù)根節(jié)點(diǎn)的指針。P(i-1)指向的子樹(shù)的所有節(jié)點(diǎn)關(guān)鍵字均小于ki,但都大于k(i-1)
1.2B樹(shù)的結(jié)構(gòu)
B-Tree中的每個(gè)節(jié)點(diǎn)根據(jù)實(shí)際情況可以包含大量的關(guān)鍵字信息和分支,如下圖所示為一個(gè)3階的B-Tree:
每個(gè)節(jié)點(diǎn)占用一個(gè)盤塊的磁盤空間,一個(gè)節(jié)點(diǎn)上有兩個(gè)升序排序的關(guān)鍵字和三個(gè)指向子樹(shù)根節(jié)點(diǎn)的指針,指針存儲(chǔ)的是子節(jié)點(diǎn)所在磁盤塊的地址。兩個(gè)關(guān)鍵詞劃分成的三個(gè)范圍域?qū)?yīng)三個(gè)指針指向的子樹(shù)的數(shù)據(jù)的范圍域。以根節(jié)點(diǎn)為例,關(guān)鍵字為17和35,P1指針指向的子樹(shù)的數(shù)據(jù)范圍為小于17,P2指針指向的子樹(shù)的數(shù)據(jù)范圍為17~35,P3指針指向的子樹(shù)的數(shù)據(jù)范圍為大于35。
1.3B樹(shù)的查找過(guò)程
查找關(guān)鍵字29的過(guò)程:
根據(jù)根節(jié)點(diǎn)找到磁盤塊1,讀入內(nèi)存。【磁盤I/O操作第1次】
比較關(guān)鍵字29在區(qū)間(17,35),找到磁盤塊1的指針P2。
根據(jù)P2指針找到磁盤塊3,讀入內(nèi)存。【磁盤I/O操作第2次】
比較關(guān)鍵字29在區(qū)間(26,30),找到磁盤塊3的指針P2。
根據(jù)P2指針找到磁盤塊8,讀入內(nèi)存。【磁盤I/O操作第3次】
在磁盤塊8中的關(guān)鍵字列表中找到關(guān)鍵字29。
由于節(jié)點(diǎn)內(nèi)部的關(guān)鍵字是有序的,所以在節(jié)點(diǎn)內(nèi)部的查找可以使用二分法進(jìn)行。
分析上面過(guò)程,發(fā)現(xiàn)需要3次磁盤I/O操作,和3次內(nèi)存查找操作。由于內(nèi)存中的關(guān)鍵字是一個(gè)有序表結(jié)構(gòu),可以利用二分法查找提高效率。而3次磁盤I/O操作是影響整個(gè)B-Tree查找效率的決定因素。B-Tree相對(duì)于AVLTree縮減了節(jié)點(diǎn)個(gè)數(shù),使每次磁盤I/O取到內(nèi)存的數(shù)據(jù)都發(fā)揮了作用,從而提高了查詢效率。
2.B+樹(shù)
B+樹(shù)是B樹(shù)的一種變種和優(yōu)化,使其更適合實(shí)現(xiàn)外存儲(chǔ)索引結(jié)構(gòu),InnoDB存儲(chǔ)引擎就是用B+Tree實(shí)現(xiàn)其索引結(jié)構(gòu)。B+樹(shù)的特點(diǎn)是能夠保持?jǐn)?shù)據(jù)穩(wěn)定有序,其插入與修改擁有較穩(wěn)定的對(duì)數(shù)時(shí)間復(fù)雜度。B+樹(shù)元素自底向上插入,這與二叉樹(shù)恰好相反。
從上面的B-Tree結(jié)構(gòu)圖中可以看到每個(gè)節(jié)點(diǎn)中不僅包含數(shù)據(jù)的key值,還有data值。而每一個(gè)頁(yè)的存儲(chǔ)空間是有限的,如果data數(shù)據(jù)較大時(shí)將會(huì)導(dǎo)致每個(gè)節(jié)點(diǎn)(即一個(gè)頁(yè))能存儲(chǔ)的key的數(shù)量很小,當(dāng)存儲(chǔ)的數(shù)據(jù)量很大時(shí)同樣會(huì)導(dǎo)致B-Tree的深度較大,增大查詢時(shí)的磁盤I/O次數(shù),進(jìn)而影響查詢效率。在B+Tree中,所有數(shù)據(jù)記錄節(jié)點(diǎn)都是按照鍵值大小順序存放在同一層的葉子節(jié)點(diǎn)上,而非葉子節(jié)點(diǎn)上只存儲(chǔ)key值信息,這樣可以大大加大每個(gè)節(jié)點(diǎn)存儲(chǔ)的key值數(shù)量,降低B+Tree的高度。
B+Tree相對(duì)于B-Tree的不同點(diǎn):
①節(jié)點(diǎn)存儲(chǔ)的信息不同:
B+樹(shù)的分支結(jié)點(diǎn)僅僅存儲(chǔ)著關(guān)鍵字信息和兒子的指針(這里的指針指的是磁盤塊的偏移量),也就是說(shuō)內(nèi)部結(jié)點(diǎn)僅僅包含著索引信息。
B樹(shù)的節(jié)點(diǎn)存儲(chǔ)了索引信息和數(shù)據(jù)。
②數(shù)據(jù)存儲(chǔ)的位置不同:
B+樹(shù)中的數(shù)據(jù)都存儲(chǔ)在葉子結(jié)點(diǎn)上,也就是其所有葉子結(jié)點(diǎn)的數(shù)據(jù)組合起來(lái)就是完整的數(shù)據(jù)。但B樹(shù)的數(shù)據(jù)存儲(chǔ)在每一個(gè)結(jié)點(diǎn)中,并不僅僅存儲(chǔ)在葉子結(jié)點(diǎn)上。
③查找路徑不同:
B+樹(shù)的所有葉子節(jié)點(diǎn)都通過(guò)指針相連,每次查找都通過(guò)內(nèi)部節(jié)點(diǎn)找到對(duì)應(yīng)的葉子節(jié)點(diǎn),從而獲取到數(shù)據(jù)。順序查找和區(qū)間查找也是這樣,從內(nèi)部節(jié)點(diǎn)定位到葉子節(jié)點(diǎn),再在葉子節(jié)點(diǎn)中順序查找。
B樹(shù)則是每個(gè)節(jié)點(diǎn)都帶有數(shù)據(jù),找到了即停止查找,可能在內(nèi)部節(jié)點(diǎn)中找到,也可能在葉子節(jié)點(diǎn)中找到。而區(qū)間查找則需要在上下層中不停的穿梭。
④另外:
B樹(shù)在內(nèi)部節(jié)點(diǎn)出現(xiàn)的索引項(xiàng)不會(huì)再出現(xiàn)在葉子節(jié)點(diǎn)中。(???待確定)
將上一節(jié)中的B樹(shù)優(yōu)化,由于B+Tree的非葉子節(jié)點(diǎn)只存儲(chǔ)鍵值信息,假設(shè)每個(gè)磁盤塊能存儲(chǔ)4個(gè)鍵值及指針信息,則變成B+Tree后其結(jié)構(gòu)如下圖所示:
通常在B+Tree上有兩個(gè)頭指針,一個(gè)指向根節(jié)點(diǎn),另一個(gè)指向關(guān)鍵字最小的葉子節(jié)點(diǎn),而且所有葉子節(jié)點(diǎn)(即數(shù)據(jù)節(jié)點(diǎn))之間是一種鏈?zhǔn)江h(huán)結(jié)構(gòu)。因此可以對(duì)B+Tree進(jìn)行兩種查找運(yùn)算:一種是對(duì)于主鍵的范圍查找和分頁(yè)查找,另一種是從根節(jié)點(diǎn)開(kāi)始,進(jìn)行隨機(jī)查找。
Mysql索引中B+樹(shù)通的高度通常為2~4層,那么這是為什么呢?
InnoDB存儲(chǔ)引擎中頁(yè)的大小為16KB,一般表的主鍵類型為INT(占用4個(gè)字節(jié))或BIGINT(占用8個(gè)字節(jié)),指針類型也一般為4或8個(gè)字節(jié),也就是說(shuō)一個(gè)頁(yè)(B+Tree中的一個(gè)節(jié)點(diǎn))中大概存儲(chǔ)16KB/(8B+8B)=1K個(gè)鍵值(因?yàn)槭枪乐担瑸榉奖阌?jì)算,這里的K取值為〖10〗^3)。也就是說(shuō)一個(gè)深度為3的B+Tree索引可以維護(hù)10^3 * 10^3 * 10^3 = 10億 條記錄。實(shí)際情況中每個(gè)節(jié)點(diǎn)可能不會(huì)填充滿,因此在數(shù)據(jù)庫(kù)中,B+Tree的高度一般都在2~4層。
MySQL的InnoDB存儲(chǔ)引擎在設(shè)計(jì)時(shí)是將根節(jié)點(diǎn)常駐內(nèi)存的(少了一次磁盤IO),也就是說(shuō)查找某一鍵值的行記錄時(shí)最多只需要1~3次磁盤I/O操作。所以B+樹(shù)適合用來(lái)實(shí)現(xiàn)文件索引。
3.B*樹(shù)
B*樹(shù)是B+樹(shù)的變體,在B+Tree的非根和非葉子結(jié)點(diǎn)再增加指向兄弟的指針;
參考:
一步步分析為什么B+樹(shù)適合作為索引的結(jié)構(gòu)
Mysql索引分析
總結(jié)
以上是生活随笔為你收集整理的几种常见的树:排序二叉树、平衡二叉树、红黑树、B+树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 归心似箭,IT达人分享抢票攻略
- 下一篇: 【数学建模】—优秀论文(一)