提及樹,想必大多數人第一反應都是二叉樹以及二叉樹的各類親戚,包括紅黑樹、平衡二叉樹等。可是其實除了二叉樹外,普通的樹結構在數據結構中也佔據着很是重要的一部分。程序員
不只如此,所謂百川成海,白木成林。既然有了樹結構,天然而然也會有相應的森林結構。所以,本文就將從普通的樹結構出發,探討並介紹一下樹和森林的那些事。web
1 樹的定義
樹實際上就是由許多個節點組成的集合,只不過每一個節點的的組成是根據樹狀結構進行劃分。一顆普通的樹結構能夠經過如下圖來定義。數組

仍是再來羅嗦一遍,樹的結構就像是一顆倒掛的樹,結點的組成是以層級往下。一棵樹由若干子樹構成,而子樹又有更小的子樹構成。微信
樹的血緣關係
對於樹中的某個結點,最多隻和上一層的結點有直接的關係,而與其下一層的多個結點有直接關係。其上一層的結點稱爲雙親結點,下一層的結點稱爲孩子結點。全部位於樹的最底部,沒有孩子結點的結點被稱之爲葉子節點。具備相同雙親的結點互爲兄弟節點。數據結構
樹的家族等級
樹是一個你們族,等級十分森嚴。樹中某個結點的子樹個數稱爲該結點的度。因此葉子結點也就是度爲0的結點。而度不爲0的結點被稱之爲內部結點。每個結點都具備本身的層次,該層次由高往低遞增,根結點爲第一層,根的孩子結點爲第二層,依次類推。一棵樹最大的層數稱之爲樹的高度(或深度)。編輯器
2 樹的存儲結構
因爲普通的樹結構並不像二叉樹那麼規則,多是多叉樹的組合,所以很難用常規的線性結構來存儲。所以樹結構的存儲須要將樹家族中的關係剝離出來進行存儲,保存了每一個結點之間的關係,整個樹結構也就能依次進行恢復。flex
這就比如家族中的族譜同樣,記錄的是咱們和雙親以及兄弟姐妹的關係。對於樹而言,則根據存儲關係的不一樣,可分爲雙親表示、孩子表示以及孩子兄弟表示三種存儲方法。spa
雙親表示法
樹的雙親表示,顯然就是經過記錄每一個結點的雙親結點來存儲整顆樹的層次關係。這裏經常使用的一種存儲結構就是數組。在連續的地址中存儲樹的結點,同時將之與其雙親結點在數組中的序號進行對應,這樣一來就可以保存全部結點的雙親信息。.net

雙親表示法直接存儲的是結點的雙親位置(對應於數組的下標),所以在求某個結點的雙親結點以及祖先結點時很是方便。可是卻沒法直接得到該結點的孩子結點的位置。指針
若須要查找指定結點的孩子以及後代結點,須要遍歷整個數組並進行屢次判斷才行。
孩子表示法
樹的雙親表示法的缺點顯而易見,因此最直接的解決辦法就是乾脆存孩子結點算了。還別說,孩子表示法就是這樣一種表示方法。可是相較於雙親結點的存儲,存儲孩子結點有一個須要考慮的問題,就是某個結點的雙親結點最多隻有一個,可是其孩子結點可能有多個。若是每一個孩子結點都存儲在數組裏,這樣的方式不是一個明智的選擇,而且也沒有必要。

因此在使用孩子表示法來存儲樹的結構時,常使用數組+鏈表的結構。這種結構是否是很常見,跟解決哈希衝突的鏈地址法有殊途同歸之意。在這樣的鏈式結構中,用指針指示出結點的每一個孩子,每一個孩子的位置經過鏈表依次相連,這樣就十分方便與查找每一個結點的子孫。
只不過問題依舊,若要找出尋找某個結點的雙親則一樣須要遍歷全部鏈表。不過,既然雙親表示和孩子表示都有了,簡單粗暴的合併一下不就能夠相互補充,共同進退嗎。

所謂的雙親孩子表示法,直接將雙親表示和孩子表示組合起來便可。這樣便可知足雙親的查找,也能夠知足孩子的查找。
孩子兄弟表示法
原本有了雙親孩子表示法就已經足夠用來存儲樹中的數據信息了,爲何還要來一個孩子兄弟法呢?其實否則,孩子兄弟表示法反而是一種頗有意思且頗有價值的表示方式。
在孩子兄弟表示法中,咱們約定只存儲每一個結點的第一個孩子結點和下一個兄弟結點。不只如此,結點的存儲是經過鏈表進行的。話說不太清,仍是直接看圖吧。

看起來彷佛有些詭異的形狀,每一個結點都做爲鏈表的一個節點,經過兩個指針分別指向第一個孩子結點和下一個兄弟結點。爲了防止你們看不懂,我舉個例子。拿結點B
來講,它的第一個孩子結點是E
,而它的下一個兄弟是與它處於同一層級的C
。所以結點B的兩個指針分別指向了E
和C
。
孩子兄弟表示法這樣看起來彷佛很雞肋,可是假如咱們調整一下右邊這個圖,就能看出其中的蹊蹺了。

看出來了嗎,孩子兄弟表示法實際上就是將一顆普通的樹轉換成了二叉樹的形式。因此說二叉樹爲何這麼重要,由於萬變不離其中呀。看到這,其實也透露出樹和二叉樹之間的轉換關係,許多二叉樹上的性質和操做也能夠藉此運用在普通的樹結構中。
3 樹的遍歷
學過二叉樹的同窗想必應該對前序遍歷、中序遍歷、後序遍歷、中序遍歷爛熟於心了吧,不管是迭代仍是非迭代的寫法,都是基礎得不能再基礎的東西了。而對於普通的樹而言,因爲每一個結點子樹的個數並不必定,所以很差規定前、中、後序的順序。
因此通常而言對於樹的遍歷方式有兩種,根據根結點被遍歷的前後可分爲先根遍歷和後根遍歷。
樹的先根遍歷是先訪問樹的根節點,而後依次遍歷根結點的各個子樹。如此遞推。當將一顆普通樹轉換爲對應的二叉樹時(孩子兄弟表示法),其實就至關因而前序遍歷。
樹的後根遍歷就不用多說了吧,跟先根遍歷相反,先訪問根結點的各顆子樹,再訪問樹根結點。而樹的後根遍歷就至關於轉換後二叉樹的中序遍歷。不信的話你試試。
4 樹、森林和二叉樹的相互轉換
寫到這,忽然發現好像忘記介紹森林是什麼東西了。其實森林的概念很簡單,就是不少顆樹。對,就是這樣。
樹、森林和二叉樹本質上都是相似的結構,所以相互之間能夠進行轉換。任意一個森林或者一棵樹均可以對應表示爲一顆二叉樹,而任何一顆二叉樹也可以對應到一個森林或一棵樹上。
樹轉換爲二叉樹,咱們在前面已經介紹過,就是經過樹的孩子兄弟表示法。經過孩子兄弟法進行表示時,每個樹均可以用一顆惟一的二叉樹來表示。可是轉換過來的二叉樹卻有一個很是顯著的特色。仔細觀察。

很顯然,這不是一顆平衡的二叉樹。而且,根節點是沒有右子樹的,我敢確定的說。這是由於根節點是沒有兄弟結點的,它只有孩子結點,因此在轉換爲二叉樹以後,必定是沒有右子樹的。
不過這樣的缺陷能夠在森林中進行彌補。因爲森林中有不少棵樹,所以能夠將其它樹做爲右子樹。具體的實現步驟,先將森林中的每一棵樹轉換爲二叉樹,再將第一顆樹的根結點做爲轉換後的二叉樹的根。第一棵樹的左子樹做爲轉換後二叉樹根結點的左子樹,第二棵樹做爲轉換後二叉樹的右子樹。第三顆樹做爲轉換後二叉樹根結點的右子樹的右子樹。以此類推。
我們來舉個例子。這裏有一個由三顆樹構成的森林。

將上面三棵樹分辨轉換二叉樹是如下形式。

而後將綠色二叉樹做爲藍色二叉樹根節點的右子樹,將黃色二叉樹做爲綠色二叉樹根節點的右子樹,就能夠獲得森林轉換爲二叉樹的結果。

根據以上的規則,一樣能夠將一顆二叉樹轉換爲樹和森林。
5 總結
在數據結構中,估計樹和森林不算很熱門的結構,甚至許多工做過不少年的老碼農都未曾用過。寫這篇文章的時候,我也在想樹和森林到底在實際中有什麼用,彷佛最重要的部分就是將一顆普通的樹轉換成二叉樹來處理。可是我想這就是它的價值所在吧。
許多真實場景中,可能數據之間的關係並不能直接經過二叉樹來表示和存儲,一開始可能都須要經過多叉樹或者各類畸形的樹結構來定義關係。這樣的樹確定是不適用於快速的處理和訪問的,所以每每須要將這些奇形怪狀的樹轉換爲規則的二叉樹來進行進一步的處理。最終爲了迴歸到具體的應用,也須要將二叉樹從新分解爲最初的樹或者森林結構來得到應用意義。
總的來講,存在便是真理。不怕用不到,就怕想不到。
- end -
本文分享自微信公衆號 - 業餘碼農(Amateur_coder)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。