『資料結構』樹(Tree)

weixin_33850890發表於2018-07-11

<a id="markdown-1-概念" name="1-概念"></a>

1. 概念

  • 雙親
  • 左右孩子
  • 左右子樹
  • 森林
  • 結點,葉子,邊,路徑
  • 高度 h
  • 遍歷(前中後層)
  • 結點數 n
    <a id="markdown-2-二叉查詢樹" name="2-二叉查詢樹"></a>

2. 二叉查詢樹

又名排序二叉樹,對於每個結點, 如果有,其左孩子不大於它,右孩子不小於它

通過前序遍歷或者後序遍歷就可以得到有序序列(升序,降序)

常用三種操作, 插入,刪除,查詢,時間複雜度是
h是樹高, 但是由於插入,刪除而導致樹不平衡, 即可能

<a id="markdown-21-隨機構造的二叉查詢樹" name="21-隨機構造的二叉查詢樹"></a>

2.1. 隨機構造的二叉查詢樹

下面可以證明,隨機構造,即輸入序列有 中, 每種概率相同的情況下, 期望的樹高

(直接搬運演算法導論上面的啦>_<)


7130568-69c57614410f6abd.png

<a id="markdown-22-平均結點深度" name="22-平均結點深度"></a>

2.2. 平均結點深度

一個較 上面定理 弱的結論:

一棵隨機構造的二叉查詢樹,n 個結點的平均深度為

類似 RANDOMIZED-QUICKSORT 的證明過程, 因為快排 遞迴的過程就是一個遞迴 二叉樹.
隨機選擇樞紐元就相當於這裡的某個子樹的根結點 在所有結點的大小隨機排名, 如 i. 然後根結點將剩下的結點劃分為左子樹(i-1)個結點, 右子樹(n-i)個結點.

7130568-6bf2b5a6d286adca.png

<a id="markdown-23-不同的二叉樹數目catalan-num" name="23-不同的二叉樹數目catalan-num"></a>

2.3. 不同的二叉樹數目(Catalan num)

給定,組成二叉查詢樹的數目.
由上面的證明過程, 可以容易地分析得出, 任選第 i 個數作為根, 由於二叉查詢樹的性質, 其左子樹
應該有 i-1個結點, 右子樹有 n-i個結點.
如果記 n 個結點 的二叉查詢樹的數目為
則有遞推公式

然後我們來看<<演算法導論>>(p162,思考題12-4)上怎麼求的吧( •̀ ω •́ )y
設生成函式

下面證明
易得
對比的 x 的各次係數,分別是
當 k=0,
當 k>0

所以
由此解得

在點 x=0 處,
用泰勒公式得

所以對應係數

這個數叫做 Catalan 數
<a id="markdown-24-好括號列" name="24-好括號列"></a>

2.4. 好括號列

王樹禾的<<圖論>>(p42)上用另外的方法給出Catalan數, 並求出n結點 二叉查詢數的個數

首先定義好括號列,有:

  • 空列,即沒有括號叫做好括號列
  • 若A,B都是好括號列, 則串聯後 AB是好括號列
  • 若A是好括號列, 則 (A)是好括號列

充要條件: 好括號列 左右括號數相等, 且從左向右看, 看到的右括號數不超過左括號數

定理: 由 n個左括號,n個右括號組成的好括號列個數為

證明:
由 n左n右組成的括號列有 個.
設括號列為壞括號列,
由充要條件, 存在最小的 j, 使得中右括號比左括號多一個,
由於是最小的 j, 所以 為右括號, 為右括號
把中的左括號變為右括號, 右變左,記為

則括號列為好括號列
可好可壞,且有n-1個右,n+1個左, 共有個.

所以壞括號列 與括號列 , 有個

那麼好括號列有

推論: n個字元,進棧出棧(出棧可以在棧不為空的時候隨時進行), 則出棧序列有 c(n)種

這種先入後出的情形都是這樣


7130568-235b542c14b6c82b.png

<a id="markdown-3-基數樹radixtree" name="3-基數樹radixtree"></a>

3. 基數樹(radixTree)

7130568-cc84ec3ffd7c3d28.png

<a id="markdown-4-字典樹trie" name="4-字典樹trie"></a>

4. 字典樹(trie)

又叫字首樹(preifx tree).適用於儲存有公共字首的字串集合. 如果直接儲存, 而很多字串有公共字首, 會浪費掉儲存空間.
字典樹可以看成是基數樹的變形, 每個結點可以有多個孩子, 每個結點儲存的是一個字元, 從根沿著結點走到一個結點,走過的路徑形成字元序列, 如果有合適的單詞就可以輸出.

當然,也可以同理得出字尾樹
<a id="markdown-41-ac-自動機" name="41-ac-自動機"></a>

4.1. AC 自動機

Aho-Corasick automation,是在字典樹上新增匹配失敗邊(失配指標), 實現字串搜尋匹配的演算法.


7130568-3a6ff51c0bdd0ee0.png

圖中藍色結點 表示存在字串, 灰色表示不存在.
黑色邊是父親到子結點的邊, 藍色邊就是失配指標.

藍色邊(終點稱為起點的字尾結點): 連線字串終點到在圖中存在的, 最長嚴格字尾的結點. 如 caa 的嚴格字尾為 aa,a, 空. 而在圖中存在, 且最長的是字串 a, 則連線到這個字串的終點 a.

綠色邊(字典字尾結點): 終點是起點經過藍色有向邊到達的第一個藍色結點.

下面摘自 wiki

在每一步中,演算法先查詢當前節點的 “孩子節點”,如果沒有找到匹配,查詢它的字尾節點(suffix) 的孩子,如果仍然沒有,接著查詢字尾節點的字尾節點的孩子, 如此迴圈, 直到根結點,如果到達根節點仍沒有找到匹配則結束。

當演算法查詢到一個節點,則輸出所有結束在當前位置的字典項。輸出步驟為首先找到該節點的字典字尾,然後用遞迴的方式一直執行到節點沒有字典字首為止。同時,如果該節點為一個字典節點,則輸出該節點本身。

輸入 abccab 後演算法的執行步驟如下:


7130568-85329df49fa54685.png

<a id="markdown-5-平衡二叉樹" name="5-平衡二叉樹"></a>

5. 平衡二叉樹

上面的二叉查詢樹不平衡,即經過多次插入,刪除後, 其高度變化大, 不能保持的效能
而平衡二叉樹就能.
平衡二叉樹都是經過一些旋轉操作, 使左右子樹的結點高度相差不大,達到平衡
有如下幾種
<a id="markdown-51-avl-tree" name="51-avl-tree"></a>

5.1. AVL Tree

平衡因子: 右子樹高度 - 左子樹高度
定義: 每個結點的平衡因子屬於{0,-1,1}

7130568-aaf92117118f8773.gif
AVL_Tree_Example(from wiki).gif

7130568-d3552412c97bc9a2.png
from wiki

<a id="markdown-52-splaytree" name="52-splaytree"></a>

5.2. splayTree

伸展樹, 它的特點是每次將訪問的結點通過旋轉旋轉到根結點.
其實它並不平衡. 但是插入,查詢,刪除操作 的平攤時間是
有三種旋轉,下面都是將訪問過的 x 旋轉到 根部
<a id="markdown-521-zig-step" name="521-zig-step"></a>

5.2.1. Zig-step

7130568-747a88861d7acde8.png
zig

<a id="markdown-522-zig-zig-step" name="522-zig-zig-step"></a>

5.2.2. Zig-zig step

7130568-8a688b1a66a3da21.png
zig-zig

<a id="markdown-523-zig-zag-step" name="523-zig-zag-step"></a>

5.2.3. Zig-zag step

7130568-c3d7e8aeb7c834ec.png
zig-zag

<a id="markdown-53-read-black-tree" name="53-read-black-tree"></a>

5.3. read-black Tree

同樣是平衡的二叉樹, 以後單獨寫一篇關於紅黑樹的.

<a id="markdown-54-treap" name="54-treap"></a>

5.4. treap

前面提到, 隨機構造的二叉查詢樹高度為 ,以及在演算法 general 中說明了怎樣 隨機化(shuffle)一個給定的序列.

所以,為了得到一個平衡的二叉排序樹,我們可以將給定的序列隨機化, 然後再進行構造二叉排序樹.

但是如果不能一次得到全部的資料,也就是可能插入新的資料的時候,該怎麼辦呢? 可以證明,滿足下面的條件構造的結構相當於同時得到全部資料, 也就是隨機化的二叉查詢樹.

7130568-f8fd5006a58ce451.png
treap

這種結構叫 treap, 不僅有要排序的關鍵字 key, 還有隨機生成的,各不相等的關鍵字priority,代表插入的順序.

  • 二叉查詢樹的排序性質: 雙親結點的 key 大於左孩子,小於右孩子
  • 最小(大)堆的堆序性質: 雙親的 prority小於(大於) 孩子的 prority

插入的實現: 先進行二叉查詢樹的插入,成為葉子結點, 再通過旋轉 實現 上浮(堆中術語).
將先排序 key, 再排序 prority(排序prority 時通過旋轉保持 key 的排序)

<a id="markdown-6-總結" name="6-總結"></a>

6. 總結

還有很多有趣的樹結構,
比如斜堆, 競賽樹(贏者樹,輸者樹,線段樹, 索引樹,B樹, fingerTree(不知道是不是譯為手指樹233)...
這裡就不詳細介紹了, 如果以後有時間,可能挑幾個單獨寫一篇文章

<a id="markdown-7-附程式碼" name="7-附程式碼"></a>

7. 附程式碼

github地址

<a id="markdown-71-二叉樹binarytree" name="71-二叉樹binarytree"></a>

7.1. 二叉樹(binaryTree)

from functools import total_ordering

@total_ordering
class node:
    def __init__(self,val,left=None,right=None,freq = 1):
        self.val=val
        self.left=left
        self.right=right
        self.freq = freq
    def __lt__(self,nd):
        return self.val<nd.val
    def __eq__(self,nd):
        return self.val==nd.val
    def __repr__(self):
        return 'node({})'.format(self.val)

class binaryTree:
    def __init__(self):
        self.root=None
    def add(self,val):
        def _add(nd,newNode):
            if nd<newNode:
                if nd.right is None:nd.right = newNode
                else:_add(nd.right,newNode)
            elif nd>newNode:
                if nd.left is None:nd.left = newNode
                else : _add(nd.left,newNode)
            else:nd.freq +=1
        _add(self.root,node(val))
    def find(self,val):
        prt= self._findPrt(self.root,node(val),None)
        if prt.left and prt.left.val==val:
            return prt.left
        elif  prt.right and prt.right.val==val:return prt.right
        else :return None
    def _findPrt(self,nd,tgt,prt):
        if nd==tgt or nd is None:return prt
        elif nd<tgt:return self._findPrt(nd.right,tgt,nd)
        else:return self._findPrt(nd.left,tgt,nd)
    def delete(self,val):
        prt= self._findPrt(self.root,node(val),None)
        if prt.left and prt.left.val==val:
            l=prt.left
            if l.left is None:prt.left = l.right
            elif l.right is None : prt.left = l.left
            else:
                nd = l.left
                while nd.right is not None:nd = nd.right
                nd.right = l.right
                prt.left = l.left
        elif  prt.right and prt.right.val==val:
            r=prt.right
            if r.right is None:prt.right = r.right
            elif r.right is None : prt.right = r.left
            else:
                nd = r.left
                while nd.right is not None:nd = nd.right
                nd.right = r.right
                prt.left = r.left

    def preOrder(self):
        def _p(nd):
            if nd is not None:
                print(nd)
                _p(nd.left)
                _p(nd.right)
        _p(self.root)

<a id="markdown-72-字首樹trie" name="72-字首樹trie"></a>

7.2. 字首樹(Trie)

class node:
    def __init__(self,val = None):
        self.val = val
        self.isKey = False
        self.children = {}
    def __getitem__(self,i):
        return self.children[i]
    def __iter__(self):
        return iter(self.children.keys())
    def __setitem__(self,i,x):
        self.children[i] = x
    def __bool__(self):
        return self.children!={}
    def __str__(self):
        return 'val: '+str(self.val)+'\nchildren: '+' '.join(self.children.keys())
    def __repr__(self):
        return str(self)

class Trie(object):

    def __init__(self):
        self.root=node('')
        self.dic ={'insert':self.insert,'startsWith':self.startsWith,'search':self.search}

    def insert(self, word):
        """
        Inserts a word into the trie.
        :type word: str
        :rtype: void
        """
        if not word:return
        nd = self.root
        for i in word:
            if i in nd:
                nd = nd[i]
            else:
                newNode= node(i)
                nd[i] = newNode
                nd = newNode
        else:nd.isKey = True
    def search(self, word,matchAll='.'):
        """support matchall function  eg,  'p.d' matchs 'pad' , 'pid'
        """
        self.matchAll = '.'
        return self._search(self.root,word)
    def _search(self,nd,word):
        for idx,i in enumerate(word):
            if i==self.matchAll :
                for j in nd:
                    bl =self._search(nd[j],word[idx+1:])
                    if bl:return True
                else:return False
            if i  in nd:
                nd = nd[i]
            else:return False
        else:return nd.isKey
    def startsWith(self, prefix):
        """
        Returns if there is any word in the trie that starts with the given prefix.
        :type prefix: str
        :rtype: bool
        """
        nd = self.root
        for i in prefix:
            if i in  nd:
                nd= nd[i]
            else:return False
        return True
    def display(self):
        print('preOrderTraverse  data of the Trie')
        self.preOrder(self.root,'')
    def preOrder(self,root,s):
        s=s+root.val
        if  root.isKey:
            print(s)
        for i in root:
            self.preOrder(root[i],s)

<a id="markdown-73-贏者樹winnertree" name="73-贏者樹winnertree"></a>

7.3. 贏者樹(winnerTree)

class winnerTree:
    '''if i<lowExt    p = (i+offset)//2
       else           p = (i+n-1-lowExt)//2
       offset is a num 2^k-1 just bigger than n
        p is the index of tree
        i is the index of players
        lowExt is the double node num of the lowest layer of the tree
    '''
    def __init__(self,players,reverse=False):
        self.n=len(players)
        self.tree = [0]*self.n
        players.insert(0,0)
        self.players=players
        self.reverse=reverse
        self.getNum()
        self.initTree(1)
    def getNum(self):
        i=1
        while 2*i< self.n:i=i*2
        if 2*i ==self. n:
            self.lowExt=0
            self.s = 2*i-1
        else:
            self.lowExt = (self.n-i)*2
            self.s = i-1
        self.offset = 2*i-1
    def treeToArray(self,p):
        return 2*p-self.offset if p>self.s else 2*p+self.lowExt-self.n+1
    def arrayToTree(self,i):
        return (i+self.offset)//2 if i<=self.lowExt else (i-self.lowExt+ self.n-1)//2
    def win(self,a,b):
        return a<b if self.reverse else a>b
    def initTree(self,p):
        if p>=self.n:
            delta = p%2  #!!! good job  notice delta mark the lchild or rchlid
            return self.players[self.treeToArray(p//2)+delta]
        l = self.initTree(2*p)
        r = self.initTree(2*p+1)
        self.tree[p] = l if self.win(l,r) else r
        return self.tree[p]
    def winner(self):
        idx = 1
        while 2*idx<self.n:
            idx = 2*idx if self.tree[2*idx] == self.tree[idx] else idx*2+1
        num = self.treeToArray(idx)
        num = num+1 if self.players[num] !=self.tree[1] else num
        return self.tree[1],num
    def getOppo(self,i,x,p):
        oppo=None
        if 2*p<self.n:oppo=self.tree[2*p]
        elif i<=self.lowExt:oppo=self.players[i-1+i%2*2]
        else:
            lpl= self.players[2*p+self.lowExt-self.n+1]
            oppo = lpl if lpl!=x else self.players[2*p+self.lowExt-self.n+2]
        return oppo
    def update(self,i,x):
        ''' i is 1-indexed  which is the num of player
            and x is the new val of the player '''
        self.players[i]=x
        p = self.arrayToTree(i)
        oppo =self.getOppo(i,x,p)
        self.tree[p] = x if self.win(x,oppo) else oppo
        p=p//2
        while p:
            l = self.tree[p*2]
            r = None
            if 2*p+1<self.n:r=self.tree[p*2+1]   #notice this !!!
            else:r = self.players[2*p+self.lowExt-self.n+1]
            self.tree[p] = l if self.win(l,r) else r
            p=p//2

<a id="markdown-74-左斜堆" name="74-左斜堆"></a>

7.4. 左斜堆

from functools import total_ordering
@total_ordering

class node:
    def __init__(self,val,freq=1,s=1,left=None,right=None):
        self.val=val
        self.freq=freq
        self.s=s
        if left is None or right is None:
            self.left = left if left is not None else right
            self.right =None
        else:
            if left.s<right.s:
                left,right =right, left
            self.left=left
            self.right=right
            self.s+=self.right.s
    def __eq__(self,nd):
        return self.val==nd.val
    def __lt__(self,nd):
        return self.val<nd.val
    def __repr__(self):
        return 'node(val=%d,freq=%d,s=%d)'%(self.val,self.freq,self.s)

class leftHeap:
    def __init__(self,root=None):
        self.root=root
    def __bool__(self):
        return self.root is not None
    @staticmethod
    def _merge(root,t):  #-> int
        if root is None:return t
        if t is None:return root
        if root<t:
            root,t=t,root
        root.right = leftHeap._merge(root.right,t)
        if root.left is None or root.right is None:
            root.s=1
            if root.left is None:
                root.left,root.right = root.right,None
        else:
            if root.left.s<root.right.s:
                root.left,root.right = root.right,root.left
            root.s = root.right.s+1
        return root
    def insert(self,nd):
        if not isinstance(nd,node):nd = node(nd)
        if self.root is None:
            self.root=nd
            return
        if self.root==nd:
            self.root.freq+=1
            return
        prt =self. _findPrt(self.root,nd,None)
        if prt is None:
            self.root=leftHeap._merge(self.root,nd)
        else :
            if prt.left==nd:
                prt.left.freq+=1
            else:prt.right.freq+=1
    def remove(self,nd):
        if not isinstance(nd,node):nd = node(nd)
        if self.root==nd:
            self.root=leftHeap._merge(self.root.left,self.root.right)
        else:
            prt = self._findPrt(self.root,nd,None)
            if prt is not None:
                if prt.left==nd:
                    prt.left=leftHeap._merge(prt.left.left,prt.left.right)
                else:
                    prt.right=leftHeap._merge(prt.right.left,prt.right.right)
    def find(self,nd):
        if not isinstance(nd,node):nd = node(nd)
        prt = self._findPrt(self.root,nd,self.root)
        if prt is None or prt==nd:return prt
        elif prt.left==nd:return prt.left
        else:return prt.right
    def _findPrt(self,root,nd,parent):
        if not isinstance(nd,node):nd = node(nd)
        if root is None or root<nd:return None
        if root==nd:return parent
        l=self._findPrt(root.left,nd,root)
        return  l if l is not None else self._findPrt(root.right,nd,root)
    def getTop(self):
        return self.root
    def pop(self):
        nd = self.root
        self.remove(self.root.val)
        return nd
    def levelTraverse(self):
        li = [(self.root,0)]
        cur=0
        while li:
            nd,lv = li.pop(0)
            if cur<lv:
                cur=lv
                print()
                print(nd,end=' ')
            else:print(nd,end=' ')
            if nd.left is not None:li.append((nd.left,lv+1))
            if nd.right is not None:li.append((nd.right,lv+1))

相關文章