前三種遍歷演算法的**實現
由遍歷結果推導二叉樹結構
二叉樹的建立
樹、森林、二叉樹的轉換
樹與森林的遍歷
赫夫曼樹及其應用
寫在後面的廢話
不同於線性表,二叉樹的結構是非線性的,其遍歷方式也有所不同。
我們先給二叉樹的遍歷乙個定義:指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。
啊這,乍看上去,光是要保證全部訪問就挺難的了,怎麼還要保證只訪問一次呢?每個結點都可能有兩個指標,我用乙個指標怎麼能走完整個二叉樹呢?
別急嗷,一步一步來。先看看二叉樹遍歷的次序吧
二叉樹的遍歷方法主要有前序遍歷、中序遍歷、後序遍歷和層序遍歷三種方式。為了方便理解這種「序」,我們把下圖中如同fce這樣由雙親和左孩子右孩子組成的結構稱為乙個「三元結構」(注:這是乙個非官方的叫法,請不要在其他人面前提起,否則會被笑的)三元結構中的雙親可能是另乙個三元結構的孩子。三種遍歷方法中的「序」指的是三元結構中雙親結點的訪問次序。
後序遍歷的方式是,先左孩子,然後右孩子,最後雙親結點。
就不copy圖了嗷
與另外兩種遍歷方法相同,要考慮某個三元結構中左孩子在另乙個三元結構中的位置。
圖示二叉樹按後序遍歷的訪問順序是:a b d c h m g e f
老朋友了,從上到下,從左到右遍歷就完事了嗷
前三中遍歷演算法是通過遞迴實現的,請看**
突然發現上篇部落格沒有定義二叉樹的結點,補上
struct treenode
;void
preorder
(treenode *head)
//假設已經成功建立了二叉樹,並且頭指標head指向二叉樹的根
這就是遞迴的巧妙之處了!我們只管設計演算法,按照前序遍歷的要求呼叫函式就好了,剩下的事情程式會幫我們完成!
也許聰明的你已經注意到了,另外兩種遍歷方法豈不是只要調整一下**的位置就可以了?
中序遍歷就是
//中序遍歷
void
midorder
(treenode *head)
//假設已經成功建立了二叉樹,並且頭指標head指向二叉樹的根
後序遍歷就是
//後序遍歷
void
lastorder
(treenode *head)
//假設已經成功建立了二叉樹,並且頭指標head指向二叉樹的根
看來定義雖然對人工遍歷很不友好,但是寫成**就很方便!
不過相應的,對人工很友好的層序遍歷就很難通過**實現了。
在學資料結構時,經常會遇到這樣的題目:已知一棵二叉樹,其前序遍歷的結果為a b c e f d,中序遍歷的結果為b a e f c d,則該二叉樹的結構為?
首先,前序遍歷的第乙個必為根,所以根是a;
第二,a的下乙個結點為b,所以b是a的孩子,又因為中序遍歷中b在a前,所以b是a的左孩子;
第三,中序遍歷的結果中,a的左側只含b,所以a的左子樹只含b,e f c d都屬於a的右子樹。所以a b c,c為a的右孩子;
第四,在中序遍歷中,e f在c的左側,所以e f是c的左子樹,又因為前序遍歷e在f前,所以e是c的左孩子。
第五,中序遍歷,f在e右側,所以f是e的右孩子。
最後,易推d是c的右孩子。
最終結果如圖。
要特別注意的是,如果只知道前序遍歷和後序遍歷是不足以確定一棵二叉樹的。試著對圖中的兩棵二叉樹做前序遍歷和後序遍歷,你會發現結果是相同的。
在講二叉樹的建立前,先介紹一下擴充套件二叉樹。我們知道,二叉樹的結點有兩個指標域,分別存放指向左孩子和右孩子的指標。如果沒有左孩子或右孩子,則相應的指標為空。
對原二叉樹進行補充,使得每個原二叉樹的結點都有兩個孩子,這樣得到的一棵二叉樹就稱為原二叉樹的擴充套件二叉樹。
如圖,是一棵由a b c d四個結點組成的二叉樹的擴充套件二叉樹。
我們用#來標記補充的結點,並人工算出前序遍歷的結果為:a b # # c d # # #
那麼對於這樣的一棵二叉樹,我們可以通過和前序遍歷一樣的演算法,來實現二叉樹的生成,請看**
void
createtree
(treenode *head)
else
}}
不知道大家還記不記得長子這個概念,值得是乙個結點的最左邊的孩子。把一棵樹轉為一棵二叉樹的想法,其實就是把長子所有的右兄弟變為其右孩子,長子作為雙親的左孩子。
第一步,用上面講的方法,把所有的樹都轉為二叉樹。如果已經是二叉樹了,也應當按照上面的方法,把其轉為符合要求的二叉樹(比如根只能有左孩子)
第二步,第一棵二叉樹不動,從第二課二叉樹開始,依次把後一棵二叉樹的根節點作為前一棵二叉樹的根結點的右孩子,用線連線起來。
判斷一棵二叉樹能不能轉為森林,就要看根結點有沒有右孩子。有,就是森林;沒有,就是樹
具體操作是上述兩個步驟的逆過程,不贅述了。
兩種方式:先根遍歷、後根遍歷
先根遍歷:先訪問樹的根結點,然後按先根遍歷依次訪問每棵子樹
後根遍歷:先依次後根遍歷每棵子樹,再訪問根結點
(遞迴定義)
兩種方式:前序遍歷、後序遍歷
相當於分別按先根遍歷和後根遍歷的方式去依次訪問森林裡的每一棵樹
路徑:從樹中乙個結點到另乙個結點之間的分支構成兩個結點之間的路徑
路徑長度:路徑上的分支數目成為路徑長度(可以數從乙個結點到另乙個結點走了幾根槓)
樹的路徑長度:從樹根到每乙個結點的路徑長度之和
結點的帶權路徑長度:從該結點到樹根之間的路徑長度與結點上權的乘積
樹的帶權路徑長度(wpl):樹中所有葉子結點的帶權路徑長度之和。
其中,wpl最小的二叉樹稱為赫夫曼樹。
我們先假設某二叉樹有權值的葉子結點為:a15 b20 c30 d35(赫夫曼樹的使用場景使得這種假設成為可能,且權值只會出現在葉子結點上)
第一步:按權值從小到大排序。
第二步:取頭兩個最小權值的結點作為乙個新節點n1的兩個子節點,權值相對較小的為左孩子。在這裡,a是左孩子,b是右孩子,新結點n1的權值為兩者之和35
第三步:將新結點n1放入原結點構成的組合中,刪除a、b兩個結點,重新排序,重複上述過程直到將所有的結點都放入二叉樹中。最終結果如圖。
開個傳送門:link這位博主講的非常清楚。
赫夫曼編碼在過去廣泛的應用於長距離的傳遞資訊,因為其能夠有效地提公升資訊的傳遞速度。不過傳遞資訊的雙方必須使用同一棵赫夫曼樹,才能夠實現資訊的加密與解密。
在學習赫夫曼編碼時,我產生了乙個疑惑。因為無論是教材,還是這位博主的文章裡,都是隨機拿了乙個字串做例子,並且臨時計算出頻度,進而生成了赫夫曼樹。難道每傳送一次資訊都有一棵獨特的赫夫曼樹,給對方傳送資訊前還要先傳一棵赫夫曼樹???
其實這個想法也是有夠天真的,這必然是事先進行語言學調查,雙方事先生成了一棵約定俗成的赫夫曼樹。
再過三天就開學了,估計開學前只能把圖的基本內容學完。開學後又是緊張的學習生活啦,想想就覺得挺累的。不過所幸能學到很多知識,也算是某種意義上的補償吧!
另外,如果你是大學生,建議常去圖書館自習嗷~感覺再圖書館學習效率高了不少。
最後還是要提一句,看我的部落格也就圖一樂,真要學知識還得看看其他大牛的部落格,或者去買一本教材。
資料結構與演算法之樹(下)
這裡所講的堆不是之前的堆疊,而是一種樹。他總是一棵完全二叉樹,其根節點總是不小於或不大於他的子孫節點。每乙個根節點都可以視為堆頂,因為對於堆的每棵子樹都可以視為完全二叉樹。正是因為堆擁有這樣的性質我們將他用來實現優先佇列 優先佇列意思就是每次出隊其中權重較大的元素 當然可以用陣列或鍊錶來實現,但他們...
資料結構自學筆記五 樹(上)
結點間的關係 樹的其他概念 樹的儲存結構 二叉樹寫在結尾的話 樹是n個結點的有限集。n 0時稱為空樹 n 0時為非空樹。在任意一棵非空樹中 有且只有乙個特定的結點,稱為根 root 當樹不只有乙個結點時,其餘節點可分為m個互不相交的有限集,其中每乙個集合本身又是一棵樹,並且稱為根的子樹。乙個結點擁有...
資料結構 並查集 自學筆記
為了能夠動態維護若干個不重疊的集合,並支援合併於查詢,我們使用乙個樹形結構儲存每個集合,樹上的每個節點都是乙個元素,根節點是集合的代表元素。並查集的操作包括兩個 get 查詢元素屬於哪乙個集合 返回根節點 merge 將兩個元素 或集合 合併。路徑壓縮 按秩合併 路徑壓縮 可以在每次get操作時,將...