玩轉遍歷樹

本書不是一本關於Python編程的書,因爲主題太廣泛了。話雖如此,但對於Python的入門書籍來說,詳細討論遞歸編程並不常見;而遞歸編程技術通常很適合處理樹;遞歸編程也具有函數式編程語言風格的通用編程策略,在執行並行開發時非常有用。讀者可以在處理非常大的數據集時採用它們進行開發。

系統發生樹的概念與計算機科學略有不同。系統發生樹可以是有根樹(這時它們具有正常的樹數據結構)或是無根樹,後者它們可用無向的非循環圖表示。系統發生樹的邊也可以有權重。因此,在閱讀文檔時請注意這一點;如果內容是由系統發生學家編寫的,那麼讀者可以這樣理解樹(有根和無根),而大多數其他文檔將使用無向無環圖來表示無根樹。話雖如此,在本祕笈中,將假設所有的樹都是有根的。

最後請注意,雖然這裏的祕笈主要是爲了幫助讀者理解遞歸算法和樹結構,但最後一部分實際上非常實用的,且對於下一個祕笈的使用至關重要。在這裏插入圖片描述如何做
參看下列步驟:
1,首先,加載RAxML產生的所有埃博拉病毒的樹,如下在這裏插入圖片描述2,然後,計算每個節點的級別(即到達根節點的距離):在這裏插入圖片描述
。 DendroPy的節點對象有一個叫level的方法,它是用來比較用的,但這裏將介紹一種遞歸算法,我們將用它來實現。
。請注意該函數是如何工作的:它是從seed_node開始調用的,該節點是在我們假設處理有根樹的根節點。根節點默認的級別是0。該函數再對它的所有子節點調用函數自身,同時把級別加一。然後,對每一個非葉節點(也就是內節點),重複這個調用過程,直到遞歸至到達葉節點。
。對於如果是葉節點,那麼就打印輸出該葉節點的級別(當然我們也可以對內節點同樣這麼做),結果顯示和DendroPy的內部函數輸出具有相同的信息。
3,進而,計算每個節點的高度,某個節點的高度是從該節點開始的最大向下路徑(即到達葉節點)的邊的數目,如下所示:在這裏插入圖片描述
這裏,我們將使用相同的遞歸策略,每個節點將返回它到它父節點的高度;如果該節點爲葉節點,其高度爲0;否則其高度值將是所有子節點的高度的最大值加上
請注意這裏用到一個map函數作用在一個lambda函數上,它用來得到當前節點的所有子節點的所有的高度值;然後選擇它們中的最大值,這裏的max函數實現了一種歸約(reduce)的功能,它能夠對所有報告的值進行總結。如果讀者能把該過程和MapReduce框架聯繫起來,你就想對了;這些就是受到了函數式編程語言風格的啓發
4,現在計算每個節點的子節點,這點應該很容易理解:
在這裏插入圖片描述5,這裏打印輸出所有的葉子節點,這看上去是顯而易見的:
在這裏插入圖片描述
請注意至今爲止我們開發的所有函數都對樹使用了一種非常清楚的遍歷模式:讀者先調用第一個子代,然後子代再調用它的子代,以此類推;只有在此之後,你纔可以用深度優先(depth-first)的模式調用下一個子代,但是我們可以用不同的模式進行遍歷。
6,現在用廣度優先(breath-first)的方式打印輸出葉節點,即這裏將首先打印那些具有低級別的節點(距離根節點較近的節點),如下所示:在這裏插入圖片描述
在解釋該算法前,讓我們先看看這個運行結果和前一個結果有什麼不同。對初學者來說,看一下後面的圖。如果用深度優先順序,會得到Y、A、X、B和C;而如果用廣度優先,將得到X、B、C、Y和A。不同的遍歷模式將影響節點是如何被訪問的,這通常很重要。
關於前面的代碼,這裏將用一種完全不同的策略來實現迭代算法,我們將採用先進先出(FIFO,First-in First-out)的隊列(queue)來排序節點。請注意Python的deque可以有效率的實現先進先出,也可以實現後進先出(LIFO,Last-in First-out),這是因爲它採用了有效率的數據結構對隊列的兩端處理都很好。
該算法從把根節點放入隊列開始,當隊列非空時,從隊列前端提取節點,如果它是內部節點,就把該節點的所有子節點放到隊列後邊。
我們將迭代這個過程,直到隊列爲空。強烈建議讀者用筆和紙畫圖記錄該例子,理解這個例子的實現,這裏的代碼雖然短小,但不是很簡單。在這裏插入圖片描述
7,讓我們回到真實的數據集。由於這裏有太多的數據要可視化,我們將產生一個剪裁後的版本,這裏我們去除了一些具有同一的病毒種的子樹;在EBOV的例子中,是具有同一爆發期的子樹。同時這裏還對樹進行了階梯化(ladderize ),也即對子節點按照其子節點數目進行排序:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章