線索二叉樹之初步剖析(獻給那些想形象思考二叉樹遍歷過程的人)


       對於二叉樹的遍歷通常習慣采用遞歸的方法,當樹的規模很大的時候,遞歸的深度就會很深,這就導致了對空間的浪費。在此,我們先不討論二叉樹遍歷的本質,以及遞歸的詳細過程。我先先來研究一下,二叉樹本身:

圖1  二叉樹

圖1所示為一個二叉樹的結構,我們注意結點的特征。結點包含了三個數據:存儲值,指向左子節點的左指針,以及指向右子節點的右指針。

我們考慮在計算機中的實現:對於每一個節點,我們都需要分配相同大小的空間。能夠指向左右子節點的指針,是有效的,因為我們能夠通過這些指針訪問到下一個左、右子節點。但一棵樹中,還存在着空指針,尤其是葉子結點。這些空指針沒有被有效的利用,嚴格意義上講,就是一種浪費。很容易發現,對於一個含有n個結點的二叉樹,實際上有2n個指針域(每個結點有左右兩個指針)。現在思考有幾個指針得到了利用,多少個沒有得到利用。除了頭結點沒有被指針指向(假設先不考慮頭指針),其余的n-1個節點都被一個指針指向。因此這2n個指針中,有n-1個指針得到了利用,則言下之意,還剩下(2n-(n-1))=n+1個指針時空指針域。那么這些沒有被用到的指針可以被利用嗎?

答案是肯定的,這些空指針域可以被利用,而且很好用。

我們先來回顧二叉樹的遍歷過程,以圖1所示二叉樹為例。考慮它的中序遍歷:從根節點A開始,先找它的左子結點,是B;再看B,B也有左子結點,是D;D還有左子結點,是H;再看H,H沒有左子結點了。所以中序遍歷第一個元素為H,再看H有沒有右子結點,沒有,因此H以及H的后續結點(都為空)訪問完畢,此時我們來訪問它的前序結點,是D。..........直到遍歷所有結點。

 在此,我們思考這樣一個問題:我們是如何在訪問到了H,回頭找到D的,進一步找到B的.....。對,我們的核心機制在棧,是通過彈棧的方式。我們找到了后續訪問的元素。且先不談論棧這種機制帶來的巨大內存消耗,我們思考這樣一個問題:假如我們知道了D的位置(僅僅是知道了D的位置,而不是知道了所有元素的位置),我們想知道樹中,訪問完了D,與D近鄰的下面一個元素是誰???

如果我們沒有更為優良的機制,我們的做法只能是:做一遍二叉樹遍歷(對,就是通過棧機制),遍歷至少D之前所有的元素和接下來一個元素,我們才知道D接下來是誰?如果我們想訪問C接下來的一個元素是誰呢?,對不起,只能遺憾的告訴你,我們要他媽遍歷整個二叉樹了。說到了這里,你對這個查找過程是不是看起來很眼熟??似曾相識???對,沒錯,這他媽就是單向鏈表的查找,時間復雜度為O(n)的單向鏈表查找!!!!(是不是恍然大悟,現在你明白了普通二叉樹遍歷的本質是什么了吧!!!)。

你可能覺得這也太蠢了吧,找一個元素我要遍歷整顆樹,沒錯,我也是這么想的,這是真的蠢。。。。那么有沒有某種方式,使得查找不要這么蠢呢?有,這就是線索二叉樹。

鑒於我畫圖本領不強,我就不就圖1所示二叉樹進行線索二叉樹的詳細講解了,對此,來個簡單的二叉樹結構

比如:

圖2  一個較為簡單的二叉樹

上述圖2是一個較為簡單的二叉樹,在上文中我們已經提到過,對於這樣一個普通的二叉樹,其遍歷過程更像是一個單向鏈表的實現。我們對這個二叉樹進行中序遍歷,得到結果為:DBAECF。

正如上述所說,我們可以采用棧的方式來實現,但是很明顯,棧的實現在空間資源消耗,以及效率上都不高。

我們再回到上述那個問題,假如我們知道了D,想知道如何訪問B,按照我們使用棧的方式,OK,你只能再遍歷B以前所有的元素。這種方式真的沙雕。假如我們在第一次遍歷時,使得D的空指針指向B(對,就是那個沒用到的指針,這個時候用到了)。於是,上述結構變成了這樣一種結構:

 圖3  線索二叉樹中序遍歷路徑

上述是一個線索二叉樹的構件圖。來看看這個圖,我們在第一次遍歷這個圖的時候,假如遍歷到了D,繼續遍歷B,我們就將D指向B,同樣的B指向了A,這樣我們就知道每個元素的鄰近元素是誰。此時有沒有發現,這個線索二叉樹的遍歷過程,很像一個東西的構建?是什么?對,就是雙向鏈表的建立!!!!這就是線索二叉樹遍歷的本質:構建了一個類似雙向鏈表的結構。使得我們對結點的訪問效率更高!!

   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM