檸檬哥整理了50本計算機相關的電子書,關注公眾號「後端技術學堂」,回覆「1024」我發給你,回覆「進群」拉你進百人讀者技術交流群。
本文首發個人技術微信公眾號,點選閱讀全文
資料結構這門課程是計算機相關專業的基礎課,資料結構指的是資料在計算機中的儲存、組織方式。
我們在學習資料結構時候,會遇到各種各樣的基礎資料結構,比如堆疊、佇列、陣列、連結串列、樹...這些基本的資料結構型別有各自的特點,不同資料結構適用於解決不同場景下的問題。
樹形結構相比陣列、連結串列、堆疊這些資料結構來說,稍微複雜一點點,但樹形結構可以用於解決很多實際問題,因為現實世界事物之間的關係往往不是線性關聯的,而「樹」恰好適合描述這種非線性關係。
今天就帶大家一起學習下,資料結構中的各種「樹」,這也是面試中經常考察的內容,手撕二叉樹是常規套路,對候選人也很有區分度,學完這篇文章,相信大家都會心中有「樹」了。
從樹說起
什麼是樹?現實中的樹大家都見過,在資料結構中也有樹,此樹非彼樹,不過資料結構的樹和現實中的樹在形態上確實有點相像。
樹是非線性的資料結構,用來模擬具有樹狀結構性質的資料集合,它是由n個有限節點組成的具有層次關係的集合。在資料結構中樹是非線性資料結構,那我們先來了解下,什麼是線性與非線性資料結構?
線性結構
線性結構是一個有序資料元素的集合。 其中資料元素之間的關係是一對一的關係,即除了第一個和最後一個資料元素之外,其它資料元素都是首尾相接的,常用的線性結構有:線性表,棧,佇列,雙端佇列,陣列,串。
可以想象,所謂的線性結構資料組織形式,就像一條線段一樣筆直,元素之間首尾相接。比如現實中的火車進站、食堂打飯排隊的佇列。
非線性結構
簡而言之,線性結構的對立面就是非線性結構。
樹
線性結構中節點是首位相接一對一關係,在樹結構中節點之間不再是簡單的一對一關係,而是較為複雜的一對多的關係,一個節點可以與多個節點發生關聯,樹是一種層次化的資料組織形式,樹在現實中是可以找到例子的,比如現實中的族譜,親戚之間的關係是層次關聯的樹形關係。
資料結構中的「樹」的名字由來,是因為如果把節點之間的關係直觀展示出來,由於長得和現實世界中的樹很像,由此得名。如圖:
樹的關鍵概念
人們對樹形結構的研究比較深入,為了方便研究樹的各種性質,抽象出了一些樹相關的概念,以便清晰簡介的描述一顆樹。下面幾個基礎概念必須瞭解,否則你當你刷LeetCode樹相關題目時候,或者面試官向你描述問題時,你會連題目都看不懂事什麼意思。
先來上一個圖解,下面的術語和概念對照著看,更容易理解。
什麼是節點的度?
度很好理解,直觀來說,數一下節點有幾個分叉就說這個節點的度是多少。
什麼是根節點?
在一顆樹形結構中,最頂層的那個節點就是根節點了,所有的子節點都源自它發散開來。
什麼是父節點?
樹的父子關係和現實中很相似,若一個節點含有子節點,則這個節點稱為其子節點的父節點。
什麼是葉子節點?
直觀來看葉子節點都位於樹的最底層,就是沒有分叉的節點,嚴格的定義是度為 0 的節點叫葉子節點。
什麼是節點的高度?
高度是從葉子節點開始「自底向上」逐層累加的路徑長度,樹葉的高度為 0(有些書上也說是 0,不用糾結)
什麼是節點的深度?
深度是從根節點開始「自頂向下」逐層累加的路徑長度,根的深度為1(有些書上也說是 0,問題不大)
小技巧:高度和深度,一個從下往上數,一個從上往下數。
樹的特點
樹形資料結構,具有以下的結構特點:
- 每個節點都只有有限個子節點或無子節點;
- 沒有父節點的節點稱為根節點;
- 每一個非根節點有且只有一個父節點;
- 除了根節點外,每個子節點可以分為多個不相交的子樹;
- 樹裡面沒有環路,意思就是從一個節點出發,除非往返,否則不能回到起點。
二叉樹
有了前面「樹」的基礎鋪墊,二叉樹是一種特殊的樹,還記的上面我們學過「節點的度」嗎?二叉樹中每個節點的度不大於 2 ,即它的每個節點最多隻有兩個分支,通常稱二叉樹節點的左右兩個分支為左右子樹。
二叉樹是很多其他樹型結構的基礎結構,比如下面要講的 AVL 樹、二叉查詢樹,他們都是由二叉樹增加一些約束條件進化而來。
三種遍歷方式
二叉樹的遍歷就是逐個訪問二叉樹節點的資料,常見的二叉樹遍歷方式有三種,分別是前中後序遍歷,初學者分不清這幾個順序的差別。
有個簡單的記憶方式,這裡的「前中後」都是對於根節點而言。
先訪問根節點後訪問左右子樹的遍歷方式是前序遍歷,先訪問左右子樹最後訪問根節點的遍歷方式是後序遍歷,先訪問左子樹再訪問根節點最後訪問右子樹的遍歷方式是中序遍歷,下面詳細說明:
前序遍歷
遍歷順序是根節點->左子樹->右子樹
遍歷的得到的序列是:1 2 4 5 3 6 7
中序遍歷
遍歷順序是左子樹->根節點->右子樹
遍歷的得到的序列是:4 2 5 1 6 3 7
後序遍歷
遍歷順序是左子樹->右子樹->根節點
遍歷的得到的序列是:4 5 2 6 7 3 1
二叉查詢樹
由於最基礎的二叉樹節點是無序的,想象一下如果在二叉樹中查詢一個資料,最壞情況可能要要遍歷整個二叉樹,這樣的查詢效率是非常低下的。
由於基礎二叉樹不利於資料的查詢和插入,因此我們有必要對二叉樹中的資料進行排序,所以就有了「二叉查詢樹」,可以說這種樹是為了查詢而生的二叉樹,有時也稱它為「二叉排序樹」,都是同一種結構,只是換了個叫法。
查詢二叉樹理解了也不難,簡單來說就是二叉樹上所有節點的,左子樹上的節點都小於根節點,右子樹上所有節點的值都大於根節點。
這樣的結構設計,使得查詢目標節點非常方便,可以通過關鍵字和當前節點的對比,很快就能知道目標節點在該節點的左子樹還是右子樹上,方便在樹中搜尋目標節點。
如果對排序二叉樹執行中序遍歷,因為中序遍歷的順序是:左子樹->根節點->右子樹,最終可以得到一個節點值的有序列表。
舉個例子:對上圖的排序二叉樹執行中序遍歷,我們可以得到一個有序序列:1 2 3 4 5 6 7
查詢效率
二叉查詢樹的查詢複雜度取決於目標節點的深度,因此當節點的深度比較大時,最壞的查詢效率是O(n),其中n是樹中的節點個數。
實際應用中有很多改進版的二叉查詢樹,目的是儘可能使得每個節點的深度不要過深,從而提高查詢效率。比如AVL樹和紅黑樹,可以將最壞效率降低至O(log n),下面我們就來看下這兩種改進的二叉樹。
AVL樹
AVL 也叫平衡二叉查詢樹。AVL 這個名字的由來,是它的兩個發明者G. M. Adelson-Velsky 和 Evgenii Landis 的縮寫,AVL最初是他們兩人在1962 年的論文「An algorithm for the organization of information」中提出來一種資料結構。
定義
AVL 樹是一種平衡二叉查詢樹,二叉查詢樹我們已經知道,那平衡是什麼意思呢?
我們舉個天平的例子,天平兩端的重量要差不多才能平衡,否則就會出現向一邊傾斜的情況。把這個概念遷移到二叉樹中來,根節點看作是天平的中點,左子樹的高度放在天平左邊,右子樹的高度放在天平右邊,當左右子樹的高度相差「不是特別多」,稱為是平衡的二叉樹。
AVL樹有更嚴格的定義:在二叉查詢樹中,任一節點對應的兩棵子樹的最大高度差為 1,這樣的二叉查詢樹稱為平衡二叉樹。其中左右子樹的高度差也有個專業的叫法:平衡因子。
AVL樹的旋轉
一旦由於插入或刪除導致左右子樹的高度差大於1,此時就需要旋轉某些節點調整樹高度,使其再次達到平衡狀態,這個過程稱為旋轉再平衡。
保持樹平衡的目的是可以控制查詢、插入和刪除在平均和最壞情況下的時間複雜度都是O(log n),相比普通二叉樹最壞情況的時間複雜度是 O(n) ,AVL樹把最壞情況的複雜度控制在可接受範圍,非常合適對演算法執行時間敏感類的應用。
B樹
B樹是魯道夫·拜爾(Rudolf Bayer)1972年在波音研究實驗室(Boeing Research Labs)工作時發明的,關於B樹名字的由來至今是個未解之謎,有人猜是Bayer的首字母,也有人說是波音實驗室(Boeing Research Labs)的Boeing首字母縮寫,雖然B樹這個名字來源撲朔迷離,我們心裡也沒點 B 樹,但不影響今天我們來學習它。
定義
一個 m 階的B樹是一個有以下屬性的樹
- 每一個節點最多有 m 個子節點
- 每一個非葉子節點(除根節點)最少有 ⌈m/2⌉ 個子節點,⌈m/2⌉表示向上取整。
- 如果根節點不是葉子節點,那麼它至少有兩個子節點
- 有 k 個子節點的非葉子節點擁有 k − 1 個鍵
- 所有的葉子節點都在同一層
如果之前不瞭解,相信第一眼看完定義肯定是蒙圈,不過多看幾遍好好理解一下就好了,畫個圖例,對照著看看:
特點
-
B樹是所有節點的平衡因子均等於0的多路查詢樹(AVL樹是平衡因子不大於 1 的二路查詢樹)。
-
B 樹節點可以儲存多個資料,使得 B 樹可以不用像 AVL 樹那樣為了保持平衡頻繁的旋轉節點。
-
B樹的多路的特性,降低了樹的高度,所以B樹相比於平衡二叉樹顯得矮胖很多。
-
B樹非常適合儲存在磁碟中的資料讀取,因為每次讀取都會有一次磁碟IO,高度降低減少了磁碟IO的次數。
B樹常用於實現資料庫索引,典型的實現,MongoDB索引用B樹實現,MySQL的Innodb 儲存引擎用B+樹存放索引。
說到B樹不得不提起它的好兄弟B+樹,不過這裡不展開細說,只需知道,B+樹是對B樹的改進,資料都放在葉子節點,非葉子節點只存資料索引。
紅黑樹
紅黑樹也是一種特殊的「二叉查詢樹」。
到目前為止我們學習的 AVL 樹和即將學習的紅黑樹都是二叉查詢樹的變體,可見二叉查詢樹真的是非常重要基礎二叉樹,如果忘了它的定義可以先回頭看看。
特點
紅黑樹中每個結點都被標記了紅黑屬性,紅黑樹除了有普通的「二叉查詢樹」特性之外,還有以下的特徵:
- 節點是紅色或黑色。
- 根是黑色。
- 所有葉子都是黑色(葉子是NIL節點)。
- 每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
- 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。
這些性質有興趣可以自行研究,不過,現在你只需要知道,這些約束確保了紅黑樹的關鍵特性:從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。
而節點的路徑長度決定著對節點的查詢效率,這樣我們確保了,最壞情況下的查詢、插入、刪除操作的時間複雜度不超過O(log n),並且有較高的插入和刪除效率。
應用場景
紅黑樹在實際應用中比較廣泛,有很多已經落地的實踐,比如學習C++的同學都知道會接觸到 STL 標準庫,而STL容器中的map、set、multiset、multimap 底層實現都是基於紅黑樹。
再比如,Linux核心中也有紅黑樹的實現,Linux系統在實現EXT3檔案系統、虛擬記憶體管理系統,都有使用到紅黑樹這種資料結構。
Linux核心中的紅黑樹定義在核心檔案如下的位置:
如果找不到,可以 find / -name rbtree.h
搜尋一下即可,有興趣可以開啟檔案看下具體實現。
紅黑樹 VS 平衡二叉樹(AVL樹)
-
插入和刪除操作,一般認為紅黑樹的刪除和插入會比 AVL 樹更快。因為,紅黑樹不像 AVL 樹那樣嚴格的要求平衡因子小於等於1,這就減少了為了達到平衡而進行的旋轉操作次數,可以說是犧牲嚴格平衡性來換取更快的插入和刪除時間。
-
紅黑樹不要求有不嚴格的平衡性控制,但是紅黑樹的特點,使得任何不平衡都會在三次旋轉之內解決。而 AVL 樹如果不平衡,並不會控制旋轉操作次數,旋轉直到平衡為止。
-
查詢操作,AVL樹的效率更高。因為 AVL 樹設計比紅黑樹更加平衡,不會出現平衡因子超過 1 的情況,減少了樹的平均搜尋長度。
Trie樹(字首樹或字典樹)
Trie來源於單詞 retrieve(檢索),Trie樹也稱為字首樹或字典樹。利用字串字首來查詢指定的字串,縮短查詢時間提高查詢效率,主要用於字串的快速查詢和匹配。
為什麼要稱其為字典樹呢?因為Trie樹的功能就像字典一樣,想象一下查英文字典,我們會根據首字母找到對應的頁碼,接著根據第二、第三...個單詞,逐步查詢到目標單詞,Trie樹的組織思想和字典組織很像,字典樹由此得名。
定義
Trie的核心思想是空間換時間,有 3 個基本性質:
- 根節點不包含字元,除根節點外每一個節點都只包含一個字元。
- 從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串。
- 每個節點的所有子節點包含的字元都不相同。
比如對單詞序列lru, lua, mem, mcu
建立Trie樹如下:
Trie樹建立和查詢是可以同步進行的,可以在還沒建立出完成的 Trie 樹之前就找到目標資料,而如果用 Hash 表等結構儲存是需要先建立完成表才能開始查詢,這也是 Trie 樹查詢速度快的原因。
應用
Trie樹還用於搜尋引擎的關鍵詞提示功能。比如當你在搜尋框中輸入檢索單詞的開頭幾個字,搜尋引擎就可以自動聯想匹配到可能的片語出來,這正是Trie樹的最直接應用。
這種結構在海量資料查詢上很有優勢,因為不必為了找到目標資料遍歷整個資料集合,只需按字首遍歷匹配的路徑即可找到目標資料。
因此,Trie樹還可用於解決類似以下的面試題:
給你100000個長度不超過10的單詞。對於每一個單詞,我們要判斷他出沒出現過,如果出現了,求第一次出現在第幾個位置。
有一個1G大小的一個檔案,裡面每一行是一個詞,詞的大小不超過16位元組,記憶體限制大小是1M,求頻數最高的100個詞
1000萬字串,其中有些是重複的,需要把重複的全部去掉,保留沒有重複的字串,請問怎麼設計和實現?
一個文字檔案,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間複雜度分析。
絮絮叨叨
樹形資料結構有許多變種,這篇文章我們從樹開始,把幾種常見樹形資料結構學習了一遍,包括二叉樹、二叉查詢樹(二叉搜尋樹)、AVL樹、紅黑樹、B樹、Trie樹。
文章構思的時候想聊聊資料結構中的樹,沒想到步子跨的有點大,大到不知從何說起,因為資料結構中各種樹的變體非常多,一篇文章實難細數,篇幅有限,也只能說是淺嘗輒止,作為樹形資料結構入門,如果要深入的學習,每個知識點還能寫出一篇文章,比如 B+ 樹原理及其在資料庫索引中的應用、紅黑樹的詳細分析等等,這次檸檬只是粗略帶大家走一遍。
在後端開發中,資料結構與演算法是後端程式設計師的基本素養,除了基礎架構部的後端開發同學,雖然我們平常不會經常造輪子,但是掌握基本的資料結構與演算法仍然是非常有必要,面試也對相關能力有要求。回顧往期文章,資料結構的內容寫的比較少,如果大家有興趣,檸檬會再多寫一些相關主題的文章!
感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習。
Hi,我是檸檬,熱愛技術,也愛生活!一枚一線網際網路大廠後端程式設計師,個人技術公眾號主要分享後端開發相關的技術文章,每篇文章都是我精心創作,如果文章對你有幫助,請幫點亮「在看」也可以「分享」給需要的小夥伴,你的分享和在看對檸檬很重要,在此先行謝過各位了!我是lemon,我們下期再見!
如果文章對你有幫助,答應我不要白piao好嗎? 「點贊、評論、轉發」激勵我持續創作。
可以微信搜尋公眾號「 後端技術學堂 」回覆「1024」獲取50本計算機電子書,回覆「進群」拉你進讀者技術交流群,文章每週持續更新,我們下期見!