個人掘金專欄算法
上節是線性結構,這節的樹是「非線性」結構。bash
這個定義很奇怪,它是一個遞歸定義,由於用樹來定義了樹。post
但這並無什麼問題,由於讓 m = 0,那麼一個節點就是一棵樹。而後咱們能夠用若干個這樣的樹組成更大的樹。spa
樹也有非遞歸形式的定義,但遞歸定義彷佛是最合適的。由於天然界中的樹也是由幼樹的樹芽逐漸長出有本身的樹芽的子樹芽。3d
從如今開始,咱們假定全部的樹都是有序的,除非另有說明。指針
好比下面兩棵樹是不一樣的樹:code
注意:二叉樹不是樹的特殊狀況。二叉樹徹底是另外一個概念,雖然它跟樹有不少聯繫。例以下圖:cdn
對於二叉樹,這是兩棵不一樣的樹;對於樹,這兩個是相同的樹。(在這一點上我我的表示不太理解,對於樹來講它們能夠是不一樣的樹哇)。blog
畫樹的時候,能夠把根畫在下面,也能夠把根畫在上面,甚至左邊。繼承
最推薦的畫法是把根畫在上面,緣由以下:
還能夠用不少其餘方式來表示樹。
上圖能夠表示爲下面三種不一樣的形式:
一個代數公式也定義了一個隱含的樹,a - b * (c/d + e/f)
表示:
有三個主要的方法遍歷二叉樹:
設 T 存儲了二叉樹的節點的地址(頭指針),A 是一個棧,做爲輔助。步驟以下:
P <- T
。A <= P
(把 P 壓入 A),而後置 P <- LeftLink(P)
,並返回至第二步。P <= A
(彈出棧頂內存放入 P);若是 A 爲空,則算法結束Node(P)
,而後置 P <- RightLink(P)
,並返回至第二步。用人類的話來講,就是
新手乍看會以爲奇怪,這只是先遍歷了左邊,再遍歷了右邊而已啊,哪有在中間訪問父節點?
這個就須要新手本身動手模擬一遍整個過程了,確實有在「中間」訪問父節點哦。
能夠用幾乎相同的算法來描述先根序遍歷,後根序稍微難一點,因此通常來講咱們優先使用前兩種遍歷方式。
前驅就是上一個節點(能夠是子節點也能夠是父節點),後繼就是下一個節點。因爲我認爲這兩個術語十分無聊,因此下文統一以大白話來敘述。
假設 P 指向一個二叉樹,那麼
爲何用 $
呢?由於 $
是對稱的,因此能夠表示對稱序(中根序)。
後根就不講了,省得記混。
咱們能夠把二叉樹中的每一個節點表示爲 [LeftLink, Node, RightLink],那麼樹
就能夠表示爲:
這種表示方法會出現不少空連接,空連接甚至比有用的連接還要多,怎麼解決這個問題呢?
看圖:
那麼虛線具體指向哪個比它高的節點呢?答案是
從新開圖,能夠得出中根中序遍歷的順序是:
D B A E G C H F J
好了,咱們知道怎麼畫穿線了,那麼怎麼用內存表示呢?
給每一個節點再加兩個 bit:[LeftTag, LeftLink, Node, RightLink, RightTag]
LeftTag 和 RightTag 的值只能是 0 或 1,0 表示沒有虛線(有實線),1 表示有虛線。
由於咱們能夠經過中根遍歷算法肯定具體的位置,因此就不須要再存一個地址了。
穿線的存在使得遍歷二叉樹不須要額外的輔助棧。
書中對如何證實兩個數類似和等價給出了形式化的步驟,有興趣能夠自行翻開 311 頁查看。
樹和二叉樹的區別咱們複習一下:
這一節研究「咱們是否能把一棵樹表示爲一棵二叉樹」。
考慮有以下兩棵樹
咱們把每棵樹的連接刪掉,而後把全部擁有同一個父親的節點橫向連接(哥哥指向弟弟),而後讓每一個父親只連接它的大兒子(爸爸指向大兒子),再把兩個根節點連起來,就獲得了這樣的圖:
接下來就神奇了,把上圖順時針轉 45 度,而後微調一下,就獲得了二叉樹!
這個過程是可逆的,因此任何二叉樹都對應於惟一片森林。
爲何會這樣呢?由於每一個節點至多有一個大兒子,至多有一個弟弟呀,因此每一個節點的度至多爲 2,這知足二叉樹的定義。
經過數學概括飯,很容易證實三棵樹的森林,也能夠轉化成二叉樹。以此類推。
這個轉換過程叫作「森林和二叉樹之間的天然對應」。
形式化的描述以下:
對於具備 n+1 個節點的樹 T,和具備 n 個節點的二叉樹,令 F = (T1, T2, ..., Tn) 是 T 的子樹組成的森林,二叉樹 B(F) 的定義以下:
天然對應轉行過程其實就是對樹進行先根遍歷。
咱們把上面的兩棵樹用另外一種形式從新表示一下:
(A(B,C(K)), D(E(H),F(J),G))
對其進行先根遍歷,獲得 A B C K D E H F J G,竟然就是把括號去掉而已!
這就是爲何把這種遍歷叫作「天然對應」了,由於真的很天然。
再舉個例子:
對書的章節號組成的樹進行先根遍歷,獲得的結果是
1
1.1
1.1.1
1.1.2
1.2
1.2.1
1.3
1.3.1
1.3.2
複製代碼
夠不夠天然?
再舉個例子,歐洲的王位繼承。
因此,只須要把皇室的族譜先根遍歷一下,就能夠獲得王位繼承順序了。
夠不夠天然?
因此先根序是列出樹中節點最天然的方法。
接下來是後根序。
(A(B,C(K)), D(E(H),F(J),G))
的先根序是
A B C K D E H F J G
後根序則是
B K C A H E J F G D
這其實就是把原樹 (A(B,C(K)), D(E(H),F(J),G))
的書寫方式改一下,把父節點寫在括號右邊,而不是左邊:
((B, (K)C)A, ((H)E,(J)F,G)D)
經過把對應的定義作對比,能夠觀察出:
在二叉樹全部的空節點的地方加上特殊節點,就獲得了一個擴充的二叉樹。
假設有 n 個圓圈節點,s 個方形節點,那麼邊的數量就是 n + s - 1
。
因爲每一個圓圈對應兩條邊,因此邊的數量也能夠表示成 2n
。
因而咱們獲得 n + s - 1 = 2n
,由此推出 s = n + 1
。
s = n + 1
意味着,特殊的方形節點老是比圓形節點多一個。
即便 n = 0,這個公式也成立。
圖:通常把圖定義爲點(叫作頂點) + 鏈接某些不一樣頂點的線(叫作邊)的集合。簡而言之,圖 = 頂點 + 邊
。一對頂點之間最多有一條邊。若是兩個頂點被一條邊鏈接,則稱這兩個頂點是「相鄰」的。
通路:若是 和
是兩個頂點,
=
,
相鄰與
,
=
,那麼咱們就說 (
,
, ...,
) 是從 V 到 V' 的一條通路,長度爲 n。
簡單通路:若是 ,
, ...,
都不相同,並且
,
, ...,
也都不相同,則這條通路的簡單的。這句話的意思是
和
能夠相同,只要中途的點都不相同便可。
擴充二叉樹的外部通路長度
E 的定義是「從根到每一個方形節點的通路長度之和」。
擴充二叉樹的內部通路長度
I 的定義是「從根到每一個圓形節點的通路長度之和」。
上圖中的 E = 3 + 3 + 2 + 3 + 4 + 4 + 3 + 3 = 25
,I = 2 + 1 + 0 + 2 + 3 + 1 + 2 = 11
。
而且能夠證實, 。
圖中從左到右給全部節點編了號。你很容易發現
所以,徹底二叉樹能夠順序存儲,且結構隱含在節點的下標中。
假設有 個實數
...
,把這些實數做爲擴充二叉樹的方形節點,能夠畫出不一樣的擴充二叉樹。求如何找出「帶權通路長度」最小的二叉樹(其中帶權通路長度
是指
乘以其到根節點的通路長度
的積的累加之和)。
假設這 m 個數字是 2 3 4 11,那麼就有三種擴充二叉樹:
三者的帶權通路長度爲別是 4*2+2*3+3*3+11*1=34
、3*2+4*3+11*3+2*1=53
和 2*2+11*2+3*2+4*2=40
。最小的是第一個。
求最小帶權通路長度的樹的漂亮算法是由 D.Huffman(哈夫曼)發現的。
舉例:求 2,3,4,7,11,13,17,19,23,29,31,41 的最優樹