1. 程式人生 > >(原創)線索二叉樹那點小破事

(原創)線索二叉樹那點小破事

rar 並且 抽象 頭結點 nbsp 樹的遍歷 ros 區別 怎麽

線索二叉樹

二叉樹的基本定義結構我們都很熟悉,節點數據加上孩紙指針,左孩子指娘家,右孩子指婆家,我們來看這個例子:

技術分享圖片

我們會發現,有些孩子並沒有地方可以去,例子中的樹一共十個結點,十一個空閑指針,由此引出我們對於空閑指針的計算公式:一個有 n 個結點的二叉樹有 2n 個指針域,而 n 個結點會產生 n-1 個分支,每個分支對應其指向孩子的指針域,所以空閑指針數: 2n - (n-1) = n+1 ,這麽多指針域我們怎樣規劃能避免這些指針便宜null呢?

大佬們提出了這樣一個用途,中序遍歷這棵樹我們可以得到: HDIBJEAFCG ,很輕松。那麽我們可以愉快的知道 I 是 B 的前驅,或者 J 是 B 的後繼這樣的信息,那是借助了遍歷,我們能不能把這些空的指針指向前驅和後繼呢,當然闊以。

但是馬上有人站出來說,那指向孩子的指針原來就存在了,這後來的野生指針和指向孩子的指針可怎麽區分呢,大佬們就決定再加個量來區分指針的類型,如果是指向孩子的就用 0 表示,用作前驅後繼的就用 1 表示,使用的時候進行遍歷就可以一勞永逸地取得前驅後繼的信息,OK,看看結構的定義變成啥樣了。

技術分享圖片

和原來差不多對不對,多了個枚舉變量的定義

OK,現在我們的線索二叉樹可以算正式出爐遼:

我們把指向前驅和後繼的指針稱為線索,加上線索的二叉鏈表稱為線索鏈表,相應的二叉樹就稱為線索二叉樹 (Threaded Binary Tree)

來看一看抽象化結點的結構:技術分享圖片

  • ltag = 0 或者 Link,表示這個結點的左指針指向左孩子,ltag = 1 或者 Thread,表示這個結點的左指針指向前驅
  • rtag = 0 或者 Link,表示這個結點的右指針指向右孩子,rtag = 1 或者 Thread,表示這個結點的右指針指向後繼

技術分享圖片改頭換面的例子二叉鏈表

中序線索化遍歷的實現

技術分享圖片

我們來讀一下代碼,樸素的中序遍歷的框架其實還在,中間加粗的是線索化的代碼,另外多了 pre 的指針,我們可以發現這個指針永遠比我們的主角指針 p 慢一拍,它永遠在 p 的前驅結點,每次到了一個結點,我們先判斷一哈這個結點有沒有左子樹,如果沒有,就使這個節點的標記值置Thread,表示這個結點被前驅大爺承包了,然後就是讓指針 p 的左孩子管 pre 叫幹爹, 表示這個結點的前驅就定下了;然後是後繼,我們會發現這個和前驅有點不同,這個先看前驅結點的右孩子是否為空,即 如果 pre -> rchild 為空,就把這個引到 p 指針,我們說 p 指針走的快一步,所以這就找到了後繼,順理成章 pre->rtag = Thread,pre -> rchild = p; 完美! 這兩步表示一輪線索化的一個輪回,弄完這些讓 pre 再向前走一步,就是 pre = p,剩下的和中序遍歷沒什麽區別

我們給這個二叉鏈表加上一個頭指針,並且讓頭結點的左孩子指向樹根節點,讓中序的第一個節點指回頭結點的左孩子指針,用頭結點的右指針指向中序遍歷的最後一個結點,並且把這個指針指回頭結點的右指針,形成雙向的指針循環,這樣的好處就在於既可以從第一個結點開始沿中序的後繼向後遍歷,也可以利用從頭開始前序遍歷(例子及表述來自《大話數據結構》)

技術分享圖片

附上帶頭結點的線索二叉樹中序遍歷:

技術分享圖片

技術分享圖片

首先定義指向根的頭結點 p,我們先看外層這個最大的循環,這個循環條件為 p != T ,這個條件首先排除了空樹的遍歷,其次按照剛才對帶頭節點二叉線索表的分析,最後的 p 指針會在窮途末路時等於 T ,這相當於 p 指向頭結點,這時 p = T ,故如果不加 p != T ,則會無限循環下去。進入循環,首先是判定p -> ltag 等於Link,內容是指向左子樹,那麽這一個循環表示從根開始直到沒有左子樹的末端,路徑是A →B→D→H,然後打印H,目前 p 的右指針是指向直接後繼D的,所以下面一個進入第二個while,打印出D,D的右邊是孩子指針,第十五行指向I,然後重復整個過程,相當於從小樹到大樹以鏈表搜索的形勢進行完整的中序遍歷,完整順序參照下圖

技術分享圖片

好的,我們今天的線索二叉樹就到這裏遼,最後附上一段我偷來的話:

在實際問題中 ,如果所用的二叉樹需經常遍歷或查找結點時需要某種遍歷序列中的前驅和後繼,那麽采用線索二叉鏈表的存儲結構就是非常不錯的選擇。

(原創)線索二叉樹那點小破事