數(shù)據(jù)結構之美-深入理解樹形結構

一 認識樹形結構

樹形結構是一種廣泛應用的非線性數(shù)據(jù)結構,它在計算機科學和日常生活中都有廣泛的應用。比如文件系統(tǒng),郵件系統(tǒng),編譯器語法樹,決策樹,網(wǎng)絡通信,甚至機器學習當中,都有樹形數(shù)據(jù)結構的影子。本文旨在梳理日常用到的各類樹形結構以及其優(yōu)點和劣勢,讓瀆者對樹形結構有一個深入的認知和了解。下面列舉幾類常見的樹形結構的應用場景。

1.1 文件系統(tǒng)

計算機中用于存儲和管理文件的一種系統(tǒng),它使用樹形結構來組織文件和目錄。每個文件和目錄都存儲在樹的一個節(jié)點上,父節(jié)點表示上級目錄,子節(jié)點表示下級目錄或文件。


1.2 域名解析

互聯(lián)網(wǎng)的域名解析系統(tǒng)(DNS)采用了層次式樹形結構,這種結構由多個層次組成,最上層為全球根服務器。每個國家、地區(qū)以及較大的單位都有自己的域名服務器。這些服務器通過復雜的樹形結構相互連接,形成一個分布式的數(shù)據(jù)庫網(wǎng)絡。

我們觀察一個域名,以在線打字練習網(wǎng)站,《巧手打字通》為例,其域名為:www.laidazi.com,就會發(fā)現(xiàn)它被兩個點號分割成了三個部分。其中com為頂級域名,laidazi為二級域名,www為三級域名。


這種層次式樹形結構的采用,不僅提高了域名解析的效率,還保證了域名解析的穩(wěn)定性。即使某個域名服務器出現(xiàn)故障,整個DNS系統(tǒng)的正常運行也不會受到太大影響,因為數(shù)據(jù)分布在多個服務器上,可以通過其他可用的服務器完成解析過程。

1.3 郵件系統(tǒng)

郵件通常是通過樹形結構進行組織和管理的。每個郵件可以看作是樹的一個節(jié)點,回復或轉發(fā)的郵件可以作為該郵件節(jié)點的子節(jié)點,形成一個樹形結構。


1.4 語法樹

程序代碼在編譯過程中,編譯器使用樹形結構(稱為語法樹)來表示源代碼的結構和語義。每個語法樹節(jié)點表示特定的語法結構,如一個函數(shù)、一個循環(huán)或一個條件語句。


1.5 決策樹

在決策樹中,每個節(jié)點表示一個決策點,每個分支表示一個可能的決策結果,葉子節(jié)點表示決策的最終結果。決策樹常用于分類、預測和優(yōu)化等領域。


二 樹形結構的演進

2.1 線性結構

在工程實踐過程中,數(shù)組,鏈表,棧,隊列等數(shù)據(jù)結構應該都是最常用的,它們都屬于有序的數(shù)據(jù)元素組合,也被稱為線性結構。它們在數(shù)據(jù)的存儲,遍歷,排序,分析計算等場景中發(fā)揮著重要作用。


但是這種線性結構在處理具有層次,分類,關系復雜的數(shù)據(jù)時,就會顯得力不從心。而樹形結構在這方面卻有著顯著優(yōu)勢。


我們也可以形象的將某些線性結構比類比成由單個子節(jié)點串聯(lián)起來的特殊樹形結構。

2.2 二叉樹

二叉樹就是每個節(jié)點最多存在兩個子節(jié)點。其特點是具有有序性,即左子節(jié)點總是在右子節(jié)點之前。這種特性可以用來解決排序、搜索、數(shù)據(jù)壓縮等各種問題。二叉樹根據(jù)節(jié)點的分布情況,又細分為完全二叉樹和平衡二叉樹。

2.2.1 完全二叉樹

完全二叉樹,由于它的節(jié)點是按照層次順序進行排列的,任意節(jié)點的左子節(jié)點的鍵值小于該節(jié)點的鍵值,右子節(jié)點的鍵值大于該節(jié)點的鍵值,這種基于二分查找的思想,使得查找操作變得相對簡單高效。這種查找方式的時間復雜度為O(log n),在數(shù)據(jù)量較大時具有較高的效率,因此主要用于優(yōu)化某些特定的查找操作,日常經(jīng)常用到的堆排序就是使用了這種完全二叉樹結構。


完全二叉樹存在各個鏈路節(jié)點的深度參差不齊的問題,極端的情況下,就退變成類似于鏈表數(shù)據(jù)結構了。這就會使得數(shù)據(jù)查找的時間復雜度變?yōu)镺(n)了。

2.2.2 平衡二叉樹

平衡二叉樹是一種自平衡的二叉查找樹,主要彌補了完全二叉樹存在的節(jié)點深度差異問題。這在需要頻繁進行查找和插入操作的場景中,能夠在最壞情況下保持O(log n)的查找性能,紅黑樹就是一種自平衡的二叉查找樹,其中每個節(jié)點都有一個顏色屬性(紅或黑)。通過顏色和旋轉規(guī)則來維護樹的平衡,確保從根到葉子的最長路徑不會超過最短路徑的兩倍。


平衡二叉樹為了保證任何時刻樹的高度都保持平衡,需要進行大量的左旋、右旋、高度計算等操作,這會導致插入和刪除節(jié)點的速度相對較慢,因此不太適合用于大數(shù)據(jù)量的場景。

2.3 多路搜索樹

在現(xiàn)實應用場景中,由于二叉樹的子節(jié)點個數(shù)受到限制,這會導致樹的高度相對較高, 而且附帶的業(yè)務數(shù)據(jù)本身也是存儲到樹的中間節(jié)點上的,導致對數(shù)據(jù)的讀寫要經(jīng)歷較長的節(jié)點路徑。大數(shù)據(jù)場景下,不可能將所有數(shù)據(jù)都加載到內(nèi)存中,因此每個樹節(jié)點的訪問都需要磁盤IO, 導致磁盤IO讀寫次數(shù)也較高,而磁盤IO的讀寫速度相對內(nèi)存訪問速度會慢很多,因此二叉樹不適宜處理海量大規(guī)模數(shù)據(jù)。

2.3.1 B-Tree

B-Tree通過增加子節(jié)點的個數(shù),減少了樹的層級,可以極大的減少磁盤的IO次數(shù),解決了平衡二叉樹磁盤訪問效率低的問題,B樹本身也是一種自平衡樹,可以自適應地動態(tài)更新數(shù)據(jù),通過節(jié)點裂變的方式保持自平衡。


由于B-Tree中間節(jié)點(即非葉子節(jié)點)上同時存儲了關鍵字,數(shù)據(jù)記錄和子節(jié)點的索引指針數(shù)據(jù),體積相對較大,如果都調(diào)入到內(nèi)存中,這會使得內(nèi)存利用率不高。在查詢效率層面,由于采用中序遍歷策略,每次查詢會隨著靠近根節(jié)點的數(shù)據(jù)而變化,不是很穩(wěn)定,對于范圍查詢效率也不高。

2.3.2 B+Tree

B+樹(B+Tree)是對B樹的一種改進,它與B樹的不同點主要包括:

?B+樹的所有數(shù)據(jù)記錄都存儲在葉子節(jié)點上,中間節(jié)點只存儲關鍵字和子節(jié)點索引指針數(shù)據(jù);

?B+樹的所有葉子節(jié)點被一個鏈表按照數(shù)據(jù)的從小到大的順序串聯(lián)起來。因此B+樹上對數(shù)據(jù)的結果集的訪問可以不用回溯到非葉子節(jié)點,直接通過葉子節(jié)點鏈表就可以直接收集,效率比較高。

由于數(shù)據(jù)結構調(diào)整,B+Tree在磁盤讀寫效率、范圍查詢效率和內(nèi)存利用率等各個方面要優(yōu)于B-Tree。


多路搜索樹對插入、刪除和修改操作都有比較高效的節(jié)點調(diào)整或裂變等方式來保證樹的平衡性,從而保證O(lgN)的高效查詢時間復雜度。但當應用場景擴展到多維度的空間索引查詢時,由于父子節(jié)點兄弟節(jié)點之間的大小有序關系表現(xiàn)在多個維度上,節(jié)點調(diào)整或裂變操作還需要修改數(shù)量較多的離得較遠的葉子節(jié)點或祖先節(jié)點(極限情況可能波及整棵樹),就會導致數(shù)據(jù)動態(tài)更新效率變低。

2.4 多維樹

2.4.1 KD-Tree

KD-Tree(K-Dimensional Tree)是一種高維索引樹形數(shù)據(jù)結構,用于在k維空間中存儲和檢索實例點。


上圖是對6個二維平面坐標軸的數(shù)據(jù)點集合:(2,3), (5,4),(9,6),(4,7),(8,1),(7,2)的存儲。

1.先按照X維度值7把空間一分為二得到x<=7 和 x>7兩部分左子樹和右子樹;

2.再對左子樹按Y維度4再一分為二;

3.按照上面兩個步驟依次遞歸類推,即:第一層先按X維劃分,第二層按Y維度劃分, 第三層按X維度劃分,第4層再按Y維度劃分,循環(huán)往復。

KD-Tree適宜處理多維數(shù)據(jù),查詢效率較高。一個靜態(tài)多維數(shù)據(jù)集合通過KD-Tree后,查詢時間復雜度是O(lgN)。

2.4.2 KD-B-Tree

KD-B-Tree(K-Dimension-Balanced-Tree)顧名思義,結合了KD-Tree和B+Tree。它主要解決了KD-Tree的二叉樹形式樹高較高,對磁盤IO不夠友好的缺點,引入了B+樹的多叉樹形式,不僅降低了樹高,而且全部數(shù)據(jù)都存儲在葉子節(jié)點,增加了磁盤空間存儲利用率。


因為KD-Tree和KD-B-Tree的分層劃分是依據(jù)維度依次輪替進行的,動態(tài)更新后調(diào)整某個中間節(jié)點時,變更的當前維度的同時,也需要調(diào)整其全部子孫節(jié)點中的當前維度值,導致對樹節(jié)點的訪問和操作增多,操作耗時增大。因此,KD-Tree更適宜處理的是靜態(tài)場景的多維海量數(shù)據(jù)的查詢操作。

2.4.3 BKD-Tree

BKD-Tree(Block-K-Dimension-Tree ),它是一族(Block)KD-Trees。即它其實是一個森林,不再是一顆樹。


該數(shù)據(jù)結構是由杜克大學的幾位教授基于KD-B-Tree設計的,同時也結合了一種被稱為 “l(fā)ogarithmic method" 的方法,使得靜態(tài)數(shù)據(jù)動態(tài)化。

1.不同于KD-B-Tree的多叉樹,BKD-Tree是完全二叉樹。雖然二叉樹不如多叉樹的磁盤IO更友好,但是BKD-Tree仍然采用二叉樹的形式主要原因可能在于:

?采用了完全二叉樹的形式,類似于堆或優(yōu)先級隊列,能直接通過下標訪問父節(jié)點或子節(jié)點;

?減少了存儲在中間節(jié)點中的索引的存儲空間,極簡緊湊的中間索引節(jié)點占用空間更小,甚至中間節(jié)點可以一次性全部調(diào)入內(nèi)存存儲,調(diào)用內(nèi)存訪問索引節(jié)點效率更高。

1.采用一種被稱為 “l(fā)ogarithmic method" 的方法使得靜態(tài)數(shù)據(jù)動態(tài)化, 極大提高了動態(tài)數(shù)據(jù)更小的效率。

?采用一個二叉的KD-B-Tree的森林。新增加的數(shù)據(jù)存儲在一個初始支持高效查詢和動態(tài)更新的小規(guī)模數(shù)據(jù)結構上M,再通過M和多個小的二叉KD-B-Tree,以一定的策略和時機合并成大的二叉KD-B-Tree,以替換原結構的方式更新數(shù)據(jù);

?數(shù)據(jù)刪除可以采取標記刪除的方式實現(xiàn),從而實現(xiàn)了多維數(shù)值數(shù)據(jù)的高效率動態(tài)更新操作;

在Lucene中,BKD-Tree被用來解決大規(guī)模數(shù)據(jù)集的搜索和過濾操作的效率問題。為了支持高效的數(shù)值類或者多維度查詢,Lucene引入了Bkd-Tree。

三 樹形結構的應用

3.1 字典樹

Trie樹通過將字符串的字符作為節(jié)點進行存儲,其底層使用的就是多叉樹數(shù)據(jù)結構。經(jīng)常用于統(tǒng)計和排序大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計。其主要優(yōu)點是就是最大限度地減少無謂的字符串比較,查詢效率比哈希表高。


Trie樹前綴匹配常用于搜索提示。如當輸入一個關鍵字,可以自動搜索出可能的選擇。當沒有完全匹配的搜索結果,可以返回前綴最相似的可能。

3.2 隨機森林

隨機森林是一種監(jiān)督式算法,由眾多決策樹組成,通過自助采樣的方法生成眾多并行式的分類器(決策樹),通過“少數(shù)服從多數(shù)”的原則來確定最終結果 。隨機森林使用多棵樹來避免和防止單純決策樹過擬合的問題。其主要特點有以下兩點:

1.隨機:隨機選擇樣本,隨機選擇特征;

2.集成學習:投票選舉策略;

下面舉個實際的例子,比如我們想根據(jù)某個人的年齡(age),性別(gender),學歷(edu),行業(yè)(industry),居住地(city)共5個特征屬性來預測其收入層次(高,中,低)。

隨機森林中,每顆樹都可以看做是一顆分類回歸樹(CART),假設森林中有5可CART樹,總特征樹N=5,取M=1(表示每個CART樹對應一個不同的特征)。


基于不同的特征,每一個決策樹都會給出一個收入層次的概率(特征概率值就是我們模型訓練后的結果),拿圖中的示例來看,預測結果如下:

1.age(20-40):7%,23%,70%;

2.gender(male):3%,27%,70%;

3.edu(大學):6%,14%,80%;

4.city(北京):15,%,20%,65%;

5.industry(互聯(lián)網(wǎng)):5%,30%,65%;

綜合取平均結果為:高收入概率-7.2%,中等收入概率-22.8%,低收入概率-70%。

隨機森林采用集成學習方法,具有模型預測準確性高,抗干擾能力,可解釋性強等特點,在風控,語音識別,圖像分類等各種分類場景發(fā)揮著很大作用。

3.3 數(shù)據(jù)庫索引

數(shù)據(jù)庫索引通常用于加速對數(shù)據(jù)的訪問,按照數(shù)據(jù)分布特點會有不同的數(shù)據(jù)結構,常見的有B-Tree,B+Tree,R-Tree,Hash Index,Bitmap Index,Bloom Filter等。下面就以mysql的Innodb引擎中使用的B+Tree索引結構為例,探究數(shù)據(jù)結構如何指導著我們上層應用的。

先看一張典型的B+Tree索引數(shù)據(jù)結構圖:


3.3.1 索引樹深度

索引樹的深度應該越少越好,它會影響磁盤空間的占用以及索引的查詢效率。影響索引深度的因素主要有以下幾點:

1.索引數(shù)據(jù)的重復度,重復度越高,索引樹深度相對就會增加;

2.索引字段的數(shù)據(jù)越小越好,這也會影響索引深度;

3.索引數(shù)據(jù)的量級,數(shù)據(jù)量越大,索引樹深度也會增加;

InnoDB數(shù)據(jù)的最小的存儲單位是Page(默認大小為16k),假設主鍵占8字節(jié),指針占6個字節(jié),一行數(shù)據(jù)記錄大小為1k,那么,單個非葉子節(jié)點可以存儲數(shù)據(jù)個數(shù):16384/14=1170,單個葉子節(jié)點可容納記錄數(shù)16K/1K=16條。

數(shù)據(jù)總容量 = 根節(jié)點節(jié)點指針數(shù)量 * 每頁保存的數(shù)據(jù)量。

假設一個高度為3的B+樹,它可以存放的記錄數(shù):1170*1170*16=21902400條。

由此得出結論建議,如下:

1.重復度較高的字段(比如性別,狀態(tài)等字段)上不適合建立獨立索引,這往往會導致樹的深度增加。如果確實有需要,可以考慮聯(lián)合其它重復度低的屬性字段,一起組合建立復合索引;

2.對于索引字段數(shù)據(jù)過大的問題,可以考慮使用前綴索引來減少索引的大??;

3.確保主鍵索引是唯一的,并且不包含NULL值。這有助于提高查詢速度和數(shù)據(jù)的完整性;

4.數(shù)據(jù)記錄長度越大,同等樹深的數(shù)據(jù)容量就會越少;

5.數(shù)據(jù)量大了,可以考慮通過分表來緩解樹深度過大的問題;

3.3.2 數(shù)據(jù)存儲格式

在B+Tree中,主要的存儲結構信息,如下:

1.中間節(jié)點存儲鍵值信息、子節(jié)點指針、引用計數(shù);

2.葉子節(jié)點會存儲鍵值信息、數(shù)據(jù)記錄指針、刪除標記;

3.葉子節(jié)點之間還通過指針相互連接,形成一個鏈表結構,便于順序訪問;

中間節(jié)點和葉子節(jié)點在InnoDB索引中扮演著不同的角色。中間節(jié)點用于組織和維護索引的結構,而葉子節(jié)點則存儲了所有的數(shù)據(jù)記錄,并提供對數(shù)據(jù)的直接訪問。通過合理地設計和使用索引,可以提高數(shù)據(jù)庫查詢的性能和效率。

由此得出結論建議,如下:

1.使用覆蓋索引:如果一個索引包含了查詢所需要的所有數(shù)據(jù),那么查詢可以直接通過索引來獲取數(shù)據(jù),而不需要回表。這稱為覆蓋索引,可以顯著提高查詢效率;

2.避免使用函數(shù)或表達式:數(shù)據(jù)庫查詢優(yōu)化器通?;诹械闹祦磉x擇和使用索引。當列上的函數(shù)或表達式被應用時,優(yōu)化器可能無法識別或利用這些索引,從而導致索引失效;

3.范圍查找很高效:由于B+Tree葉子節(jié)點之間順序鏈接的特性,使得范圍查找非常高效。它能夠快速定位到指定范圍的起始和結束位置,然后通過線性遍歷來獲取所有范圍內(nèi)的值,從而避免了全樹遍歷。

4.最左前綴原則:當我們創(chuàng)建一個聯(lián)合索引時,例如(a,b,c),實際上是創(chuàng)建了一個由列a、b和c組成的B+樹。在這個樹中,每個節(jié)點都包含了a、b和c的值,并且按照a、b和c的順序進行排序。如果我們要查找列a和b的值,而跳過了列a直接查找列b的值,那么B+樹的范圍查找就無法使用了。因為B+樹的范圍查找是基于列的順序進行的,如果跳過了最左邊的列,那么就無法確定要查找的范圍了。

5.控表的索引個數(shù):過多的索引無論是在存儲空間的占用方面,還是在索引維護的性能開銷方面(鎖競爭),都有不小的負面影響。索引的設計盡可能的考慮復用;

四 總結

從線性結構到二叉樹,是樹形結構的一次飛躍。二叉樹使得數(shù)據(jù)結構可以呈現(xiàn)層級關系,大大簡化了數(shù)據(jù)組織的復雜性。平衡樹在此基礎上,通過平衡操作保持樹的深度在可接受范圍內(nèi),從而在插入、刪除等操作時能保持較好的性能。

多路搜索樹如B樹、B+樹等,進一步擴展了樹形結構的應用范圍。它們允許節(jié)點有多個子節(jié)點,從而在保持樹的平衡性方面更加靈活。多維樹則將樹形結構從一維擴展到多維,使得數(shù)據(jù)結構能夠更好地適應復雜數(shù)據(jù)類型和多維數(shù)據(jù)關系。

在應用方面,樹形結構被廣泛應用人工智能、圖形學、網(wǎng)絡協(xié)議等眾多領域。掌握其底層實現(xiàn)原理后,能夠更好的解決工程測的痛點問題。比如:在數(shù)據(jù)庫系統(tǒng)中,索引常常采用樹形結構來提高查詢效率,了解其內(nèi)部實現(xiàn)后,就能夠在資源使用率和性能方面擁有更好的表現(xiàn)。

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

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