http://www.cnblogs.com/miloyip/archive/2010/02/25/1673114.html
問題定義
如果我們把二叉樹看成一個圖,父子節點之間的連線看成是雙向的,我們姑且定義"距離"為兩節點之間邊的個數。寫一個程式求一棵二叉樹中相距最遠的兩個節點之間的距離。
《程式設計之美: 求二叉樹中節點的最大距離》的另一個解法 書上的解法
書中對這個問題的分析是很清楚的,我嘗試用自己的方式簡短覆述。
計算一個二叉樹的最大距離有兩個情況:
- 情況A: 路徑經過左子樹的最深節點,通過根節點,再到右子樹的最深節點。
- 情況B: 路徑不穿過根節點,而是左子樹或右子樹的最大距離路徑,取其大者。
隻需要計算這兩個情況的路徑距離,并取其大者,就是該二叉樹的最大距離。
《程式設計之美: 求二叉樹中節點的最大距離》的另一個解法 我也想不到更好的分析方法。
但接着,原文的實作就不如上面的清楚 (源碼可從這裡下載下傳):
06 | int nMaxLeft; // 左子樹中的最長距離 |
07 | int nMaxRight; // 右子樹中的最長距離 |
08 | char chValue; // 該節點的值 |
14 | void FindMaxLen(NODE* pRoot) |
22 | // 如果左子樹為空,那麼該節點的左邊最長距離為0 |
23 | if (pRoot -> pLeft == NULL) |
25 | pRoot -> nMaxLeft = 0; |
28 | // 如果右子樹為空,那麼該節點的右邊最長距離為0 |
29 | if (pRoot -> pRight == NULL) |
31 | pRoot -> nMaxRight = 0; |
34 | // 如果左子樹不為空,遞歸尋找左子樹最長距離 |
35 | if (pRoot -> pLeft != NULL) |
37 | FindMaxLen(pRoot -> pLeft); |
40 | // 如果右子樹不為空,遞歸尋找右子樹最長距離 |
41 | if (pRoot -> pRight != NULL) |
43 | FindMaxLen(pRoot -> pRight); |
47 | if (pRoot -> pLeft != NULL) |
50 | if (pRoot -> pLeft -> nMaxLeft > pRoot -> pLeft -> nMaxRight) |
52 | nTempMax = pRoot -> pLeft -> nMaxLeft; |
56 | nTempMax = pRoot -> pLeft -> nMaxRight; |
58 | pRoot -> nMaxLeft = nTempMax + 1; |
62 | if (pRoot -> pRight != NULL) |
65 | if (pRoot -> pRight -> nMaxLeft > pRoot -> pRight -> nMaxRight) |
67 | nTempMax = pRoot -> pRight -> nMaxLeft; |
71 | nTempMax = pRoot -> pRight -> nMaxRight; |
73 | pRoot -> nMaxRight = nTempMax + 1; |
77 | if (pRoot -> nMaxLeft + pRoot -> nMaxRight > nMaxLen) |
79 | nMaxLen = pRoot -> nMaxLeft + pRoot -> nMaxRight; |
這段代碼有幾個缺點:
- 算法加入了侵入式(intrusive)的資料nMaxLeft, nMaxRight
- 使用了全局變量 nMaxLen。每次使用要額外初始化。而且就算是不同的獨立資料,也不能在多個線程使用這個函數
- 邏輯比較複雜,也有許多 NULL 相關的條件測試。
我的嘗試
我認為這個問題的核心是,情況A 及 B 需要不同的資訊: A 需要子樹的最大深度,B 需要子樹的最大距離。隻要函數能在一個節點同時計算及傳回這兩個資訊,代碼就可以很簡單:
17 | RESULT GetMaximumDistance(NODE* root) |
21 | RESULT empty = { 0, -1 }; // trick: nMaxDepth is -1 and then caller will plus 1 to balance it as zero. |
25 | RESULT lhs = GetMaximumDistance(root->pLeft); |
26 | RESULT rhs = GetMaximumDistance(root->pRight); |
29 | result.nMaxDepth = max(lhs.nMaxDepth + 1, rhs.nMaxDepth + 1); |
30 | result.nMaxDistance = max(max(lhs.nMaxDistance, rhs.nMaxDistance), lhs.nMaxDepth + rhs.nMaxDepth + 2); |
計算 result 的代碼很清楚;nMaxDepth 就是左子樹和右子樹的深度加1;nMaxDistance 則取 A 和 B 情況的最大值。
為了減少 NULL 的條件測試,進入函數時,如果節點為 NULL,會傳回一個 empty 變量。比較奇怪的是 empty.nMaxDepth = -1,目的是讓調用方 +1 後,把目前的不存在的 (NULL) 子樹當成最大深度為 0。
除了提高了可讀性,這個解法的另一個優點是減少了 O(節點數目) 大小的侵入式資料,而改為使用 O(樹的最大深度) 大小的棧空間。這個設計使函數完全沒有副作用(side effect)。
測試代碼
以下也提供測試代碼給讀者參考 (頁數是根據第7次印刷,節點是由上至下、左至右編号):
view source print ?
01 | void Link(NODE* nodes, int parent, int left, int right) |
04 | nodes[parent].pLeft = &nodes[left]; |
07 | nodes[parent].pRight = &nodes[right]; |
13 | NODE test1[9] = { 0 }; |
17 | Link(test1, 3, 7, -1); |
18 | Link(test1, 5, -1, 8); |
19 | cout << "test1: " << GetMaximumDistance(&test1[0]).nMaxDistance << endl; |
21 | // P. 242 Graph 3-13 left |
22 | NODE test2[4] = { 0 }; |
24 | Link(test2, 1, 3, -1); |
25 | cout << "test2: " << GetMaximumDistance(&test2[0]).nMaxDistance << endl; |
27 | // P. 242 Graph 3-13 right |
28 | NODE test3[9] = { 0 }; |
29 | Link(test3, 0, -1, 1); |
31 | Link(test3, 2, 4, -1); |
33 | Link(test3, 4, 7, -1); |
34 | Link(test3, 5, -1, 8); |
35 | cout << "test3: " << GetMaximumDistance(&test3[0]).nMaxDistance << endl; |
38 | // Same as Graph 3-2, not test |
41 | NODE test4[9] = { 0 }; |
45 | Link(test4, 5, 7, -1); |
46 | Link(test4, 6, -1, 8); |
47 | cout << "test4: " << GetMaximumDistance(&test4[0]).nMaxDistance << endl; |
你想到更好的解法嗎?