1. 程式人生 > >演算法導論chapter12 二叉搜尋樹課後習題答案詳解

演算法導論chapter12 二叉搜尋樹課後習題答案詳解

英文答案https://wenku.baidu.com/view/000b5347b9f3f90f76c61b9b.html

12.1

  1. 定義

  2. 構建方法:12.1-1,2

  3. 遍歷:12.1-3,4

 

12.1-1 對於關鍵字集合 { 1,4,5,10,16,17,21 },分別畫出高度為2、3、4、5 和 6 的二叉搜尋樹。
ANSWER:

 

12.1-2 二叉搜尋樹性質與最小堆性質(見 6.1 節)之間有什麼不同?能使用最小堆性質在 O( n ) 時間內按序輸出一顆有 n 個結點的關鍵字嗎?可以的話,請說明如何做,否則解釋理由。
ANSWER:


① 最小堆只是根結點比兒子的關鍵字小,不能直接通過遍歷得到排序結果。二叉搜尋樹可以通過中序遍歷得到升序排序。
② 不能在 O( n ) 時間內按序輸出,因為最小堆提取根結點後需要用 O( lgn ) 時間維持最小堆的性質。而且基於比較的排序演算法下限 O( nlgn )。

 

12.1-3 設計一個執行中序遍歷的非遞迴演算法。(提示:一種容易的方法是使用棧作為輔助資料結構;另一種較複雜但比較簡潔的做法是不使用棧,但要假設能測試兩個指標是否相等。)
ANSWER:

# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution94(object):              ######二叉樹中序遍歷的非遞迴方法,用棧
    def inorderTraversal(self, root):   #先把根節點入棧,如果左子樹一直不為空,就一直入棧,直到 
                                  ##把所有左節點入棧,然後pop棧頂元素,指標指向棧頂元素的右子樹
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        stack=[]
        res=[]
        if not root:
            return []
        while root or stack:
            while root:
                stack.append(root)
                root=root.left
            if stack:
                a=stack.pop()
                root=a.right
                res.append(a.val)
        return res

複雜度分析

二叉樹遍歷的非遞迴實現,每個結點只需遍歷一次,故時間複雜度為O(n)。而使用了棧,空間複雜度為二叉樹的高度,故空間複雜度為O(n)。

Morris遍歷演算法最神奇的地方就是,只需要常數的空間即可在O(n)時間內完成二叉樹的遍歷。O(1)空間進行遍歷困難之處在於在遍歷的子結點的時候如何重新返回其父節點?在Morris遍歷演算法中,通過修改葉子結點的左右空指標來指向其前驅或者後繼結點來實現的。

 

12.1-4 對於一課有 n 個結點的樹,請設計在 Θ( n ) 時間內完成的先序遍歷演算法和後序遍歷演算法。

ANSWER:

# 先序列印二叉樹(遞迴)
def preOrderTraverse(node):
    if node is None:
        return None
    print(node.val)
    preOrderTraverse(node.left)
    preOrderTraverse(node.right)


# 後序列印二叉樹(遞迴)
def postOrderTraverse(node):
    if node is None:
        return None
    postOrderTraverse(node.left)
    postOrderTraverse(node.right)
    print(node.val)

 

12.1-5 因為在基於比較的排序模型中,完成 n 個元素的排序,其最壞情況下需要 Ω( nlgn ) 時間。試證明:任何基於比較的演算法從 n 個元素的任意序列中構造一棵二叉搜尋樹,其最壞情況下需要 Ω( nlgn ) 的時間。
ANSWER:

反證法:

       假設構造一棵二叉搜尋樹的最壞情況的時間 T( n ) < c1nlgn,而中序遍歷二叉搜尋樹只需要 Θ( n ) 的時間,所以通過構造二叉搜尋樹的比較排序時間為 T( n ) + Θ( n )< c2nlgn,與基於排序模型中,n 個元素的排序最壞情況的 Ω( nlgn ) 時間矛盾。假設不成立。得證

另解:

       構造二叉查詢樹是對一組雜亂無章的資料排序的過程,而基於比較的排序的時間為Ω(nlgn),所以構造這棵樹也要Ω(nlgn)。   

       (1)如果二叉排序樹T為空,則建立一個關鍵字為k的結點,將其作為根結點。
       (2)否則將k和根結點的關鍵字進行比較,如果相等則返回,如果k小於根結點的關鍵字則插入根結點的左子樹中,否則插入根結點的右子樹中。

12.2

查詢

前驅節點

找到該節點
若一個節點有左子樹,那麼該節點的前驅節點是其左子樹中val值最大的節點(也就是左子樹中所謂的rightMostNode)
若一個節點沒有左子樹,那麼判斷該節點和其父節點的關係 
 2.1 若該節點是其父節點的右邊孩子,那麼該節點的前驅結點即為其父節點。 
 2.2 若該節點是其父節點的左邊孩子,那麼需要沿著其父親節點一直向樹的頂端尋找,直到找到一個節點P,P節點是其父節點Q的右邊孩子(可參考例子2的前驅結點是1),那麼Q就是該節點的後繼節點

 

後繼節點

找到該節點
若一個節點有右子樹,那麼該節點的後繼節點是其右子樹中val值最小的節點(也就是右子樹中所謂的leftMostNode)
若一個節點沒有右子樹,那麼判斷該節點和其父節點的關係 
2.1 若該節點是其父節點的左邊孩子,那麼該節點的後繼結點即為其父節點 
2.2 若該節點是其父節點的右邊孩子,那麼需要沿著其父親節點一直向樹的頂端尋找,直到找到一個節點P,P節點是其父節點Q的左邊孩子(可參考例子2的前驅結點是1),那麼Q就是該節點的後繼節點

 

最大值節點

搜尋右子樹指導NULL

 

最小值節點

搜尋左子樹指導NULL

 

12.2-1 假設在某二叉查詢樹中,有1到1000之間的一些數,現要找出363這個數。下列的結點序列中,哪一個不可能是所檢查的序列?
a)2,252, 401,398,330,344,397,363               b)924,220,911,244,898,258,362,363
c)925,202,911,240,912,245,363                   d)2,399,387,219,266,382,381,278,363
e)935,278,347,621,299,392,358,363

abc三個數,遞增,遞減 或c在ab中間合法,c比ab都大或者都小不合法
c. 911與912不符合二叉查詢樹規則。e.347與299不符合二叉查詢樹規則。


12.2-2 寫出TREE-MINIMUM和TREE-MAXIMUM過程的遞迴版本。
 

def findMin(self, root):
        '''查詢二叉搜尋樹中最小值點'''
        if root.left:
            return self.findMin(root.left)
        else:
            return root
        
 def findMax(self, root):
        '''查詢二叉搜尋樹中最大值點'''
        if root.right:
            return self.findMax(root.right)
        else:
            return root

 

12.2-4 Bun教授認為他發現了二叉查詢樹的一個重要性質。假設在二叉查詢樹中,對某關鍵字k的查詢在一個葉結點結束,考慮三個集合:A,包含查詢路徑左邊的關鍵字;B,包含查詢路徑上的關鍵字;C,包含查詢路徑右邊的關鍵字。Bunyan教授宣稱,任何三個關鍵字a∈A,b∈B,c∈C,必定滿足a≤b≤c.請給出該命題的一個最小可能的反例。

查詢路徑(1→3→4)∈b ,2∈a,所以a>b。


12.2-5 證明:如果二叉查詢樹中的某個結點有兩個子女,則其後繼沒有左子女,其前驅沒有右子女。
設這個結點為x其左孩子x1右孩子x2,則有key[x1]≤key[x]≤key[x2]。存在x的右子樹,則後繼為右子樹中數值最小的左兒子x3,若後繼x3有左子女x4,則key[x4]≤key[x3],而key[x的右子樹]≥key[x],所以key[x3]≥key[x4]≥key[x],那麼x4就為結點x的後繼而非x3了。前驅沒有右子女也有類似的證明。這裡略過。


12.2-6 考慮一棵其關鍵字各不相同的二叉查詢樹T,證明:如果T中某個結點x的右子樹為空,且x有一個後繼y,那麼y就是x的最低祖先,且其左孩子也是x的祖先。(注意每個節點都是它自己的祖先)

最低祖先節點就是從根節點遍歷到給定節點時的最後一個相同節點,或最近公共父節點
若x是其後繼y的左孩子,那麼key[x]≤key[y],所以y是x的最低祖先,y的左孩子為x的祖先也就是x本身。
若x是其後繼y的右孩子,那麼key[x]≥key[y],這明顯與後繼定義矛盾,y是x的前驅。所以x不可能是後繼y的右子樹。
若x是y的左孩子y1的右孩子,那麼有key[y1]≤key[x],後繼y的左孩子y1是x的祖先,同時y是x的最低祖先。


12.2-7 對於一棵包含n個結點的二叉查詢樹,其中序遍歷可以這樣來實現:先用TREE-MINIMUM找出樹中的最小元素,然後再呼叫n-1次TREE-SUCCESSOR。證明這個演算法的執行時間為θ(n).

這個圖是任意結點樹中的一個小部分,但是他們的情況是類似的,都是從1-6順序遍歷的。本題的關鍵是這個演算法在一個包含n個結點的二叉查詢樹的n-1個邊上,通過這n-1分支中的每條邊至多2次(掃描過的路徑上有節點未被標記則一定由遠及近回來標記)(對於一棵子樹,a若需要先進入最後退出,考慮訪問時有連續的後繼,則回退的時候走k步,若不連續的後繼回退的時候被標記為後繼;b若不需要進入後退出則有些邊不需要走兩次),也就是說總時間是T<h+2(n-1)=lgn+2(n-1)<cn=>T(n)=O(n)


12.2-8證明:在一棵高度為h的二叉查詢樹中,無論從哪一個結點開始,連續k次呼叫TREE-SUCCESSOR所需時間都是O(k+h).
如果簡單的利用定義:每呼叫一次該函式就需要O(h)時間,呼叫k次就需要O(kh)時間。這種想法是沒有深入分析題目中函式具體呼叫過程。如果明白12.2-7題目核心內容。就知道,除了第一次呼叫該函式需要O(h)時間外,其餘的連續k-1次遍歷了連續的k-1個結點,這k-1個結點有k-2個邊,而每條邊最多遍歷2次。所以總時間T=O(h)+2(k-2)=O(h+k).


12.2-9 設T為一棵其關鍵字均不相同的二叉查詢樹,並設x為一個葉子結點,y為其父結點。證明:key[y]或者是T中大於key[x]的最小關鍵字,或者是T中小於key[x]的最大關鍵字

x是y的左孩子,y是在結點t的左子樹上,如圖a表達的關係可知:key[x]≤key[y]≤key[y的右子樹]≤key[t],可見 "key[y]或者是T中大於key[x]的最小關鍵字" .如圖b表達關係可知:key[t]≤key[x]≤key[y]≤key[y的右子樹]。也得到相同的答案,無非如圖ab兩種情況。
           
x是y的右孩子,y是在結點t的左子樹上,如圖a表達的關係可知:key[y的左子樹]≤key[y]≤key[x]≤key[t],可見 "或者key[y]是T中小於key[x]的最大關鍵字" .如圖b表達關係可知:key[t]≤key[y的左子樹]≤key[y]≤key[x],也可以得到相同的答案。 無非如圖ab兩種情況。