一 認識樹形結構
樹形結構是一種廣泛應用的非線性數(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)。