二叉樹漫遊——編程技術與技巧總結(上):遞迴技術
本系列分三篇文章,分別對二叉樹編程中的常用技術和技巧進行了總結。第一篇是關於二叉樹編程的遞迴技術的;第二篇討論如何將遞迴程式轉化為非遞迴程式;第三篇討論二叉樹編程的其它方法和技術。
一、 二叉樹編程概述
程式 =資料結構+演算法。更精確地說,任何程式的功能實現,從技術角度來說,通過選取合適的資料結構和高效的演算法即可做到。二叉樹是一種非線性資料結構,即每一個元素都可能有0個,1個,或2個後繼結點;這使得二叉樹編程比線性表編程增加了一些難度;另一方面,由於二叉樹具有天然的遞迴特性,要掌握二叉樹編程技術,必須懂得如何用遞迴來思考問題以及熟練掌握遞迴程式的編寫,這對初學者來說,無疑是一道必須跨越的門檻。
二、遞迴技術概述
遞迴技術並不神秘。從方法層面上看,遞迴技術通過使用相同的方法求解比原問題規模更小的子問題,併合並子問題的解而實現;遞迴與分治是緊密關聯的;從技術手段上看,遞迴通過相同的函數調用來實現。即有P(n)=
P(P(i),P(j), …,
P(s),G(1))。
要寫出遞迴程式,其實也並不困難,有三點技巧:A.分析和找出遞迴的部分;
B.確定遞迴調用的參數形式;
C.確定遞迴結束條件。掌握這些技巧,甚至不用對遞迴機製作過多瞭解,就能寫出優雅的遞迴代碼。在後面的樣本中,會逐漸給出一些相關的方法和技巧。
三、二叉樹的遞迴求解技術
二叉樹的遞迴求解通常可以歸納為以下步驟:
S1: 對根結點求解;
S2: 遞迴求解左子樹;
S3: 遞迴求解右子樹;
S4 :
合并根結點、左子樹、右子樹的解,進而得到原問題的解。
聰明的讀者馬上意識到,上述步驟與二叉樹的先序遍曆非常類似。針對具體的問題,S1,S2, S3
的順序可能有所變化。
A.分析和找出遞迴的部分:
很顯然,二叉樹根結點的左子樹和右子樹都是一棵二叉樹,因此,可以用相同的策略求解左右子樹;這就是可以遞迴的部分;
B.
確定遞迴調用的參數形式:
這一點也比較明顯,即將二叉樹的根結點作為調用參數。根據問題需要,可能會增加一些其它的參數,用於標示當前遍曆的深度,要返回的列表等。
C.確定遞迴條件。通常,根據應用將根結點需要滿足的條件作為遞迴結束條件。
這裡有一個小技巧:你可以先對A/A(B,)/A(,B)/A(B,C)這幾種基本的二叉樹結構進行分析,以掌握遞迴程式的行為和機制。
四、樣本
(1)二叉樹的先序、中序、後序遞迴遍曆。這個是最基本的,請讀者自行查閱相關書籍弄懂;
(2)求二叉樹的總結點數目。
A.分析和找出遞迴的部分:很顯然,二叉樹的總結點數目
=1 +
左子樹的總結點數目 +右子樹的總結點數目。
1表示根結點。
B.遞迴調用的參數: 二叉樹的根結點。
C.遞迴結束條件: 根結點為空白。
現在,就可以開始寫遞迴程式了。先寫遞迴結束時的情況,再寫繼續遞迴的情況。熟練掌握遞迴程式的編寫不是一蹴而就的,多練習就習慣了。
Publicint size(TreeNode root) {
if(root == null) { return 0; }
else{
return1 + size(root.getLChild()) + size(root.getRChild()) ;
}
}
(3)將二叉樹所有結點的左右子樹交換
初看起來,像是很困難;然而運用遞迴的思維,很容易就能想到:如果根結點不為空白,則交換根結點的左右子樹;遞迴求解左子樹;遞迴求解右子樹。於是,可以寫出遞迴程式:
publicvoid swapTree(TreeNode root)
{
if(root != null) {
TreeNodetmp = root.getLChild();
root.setLChild(root.getRChild());
root.setRChild(tmp);
swapTree(root.getLChild());
swapTree(root.getRChild());
}
}
(4)
求二叉樹的最長路徑(如果有多條,輸出其中一條)
這個問題咋看起來,沒發現明顯可以遞迴的地方。這時,就需要仔細地分析。首先,二叉樹的最長路徑必定包括非空根結點;其次,最長路徑必定在葉子結點處到達;那麼,二叉樹的最長路徑與其左右子樹的最長路徑有什麼關聯呢?可以很容易想到:二叉樹的最長路徑
=非空根結點
+Max(左子樹的最長路徑,右子樹的最長路徑)。這樣,就可以整理出思路:
LongestPath(root):
if(root != null) { //
若根結點不為空白,則儲存在最長路徑中
longestPath.add(root);
if(root.getLChild() == null && root.getRChild() == null) {
//到達葉子結點,可以輸出路徑
returnpath;
}
else{
//遞迴求解左子樹最長路徑
leftLongestPath= LongestPath(root.getLChild());
//遞迴求解右子樹最長路徑
rightLongestPath= LongestPath(root.getRChild());
if(leftLongestPath.size() >= rightLongestPath.size())
{
//
左子樹最長路徑大於或等於右子樹最長路徑,則取左子樹最長路徑
path.addAll(leftLongestPath);
}
else{
//
否則,取右子樹最長路徑
path.addAll(rightLongestPath);
}
}
}
由於二叉樹的遞迴求解通常非常簡潔,且運行效率也在可接受範圍內,因此,有人甚至建議:二叉樹的問題求解,首選遞迴技術。本文從方法層面上討論了如何編寫二叉樹的遞迴程式,這些方法和技巧使得,即使對遞迴的機制不甚瞭解,也可以寫出非常優雅的遞迴代碼。