『資料結構』樹(Tree)
<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. 隨機構造的二叉查詢樹
下面可以證明,隨機構造,即輸入序列有 中, 每種概率相同的情況下, 期望的樹高
(直接搬運演算法導論上面的啦>_<)
<a id="markdown-22-平均結點深度" name="22-平均結點深度"></a>
2.2. 平均結點深度
一個較 上面定理 弱的結論:
一棵隨機構造的二叉查詢樹,n 個結點的平均深度為
類似 RANDOMIZED-QUICKSORT 的證明過程, 因為快排 遞迴的過程就是一個遞迴 二叉樹.
隨機選擇樞紐元就相當於這裡的某個子樹的根結點 在所有結點的大小隨機排名, 如 i. 然後根結點將剩下的結點劃分為左子樹(i-1)個結點, 右子樹(n-i)個結點.
<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)種
這種先入後出的情形都是這樣
<a id="markdown-3-基數樹radixtree" name="3-基數樹radixtree"></a>
3. 基數樹(radixTree)
<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,是在字典樹上新增匹配失敗邊(失配指標), 實現字串搜尋匹配的演算法.
圖中藍色結點 表示存在字串, 灰色表示不存在.
黑色邊是父親到子結點的邊, 藍色邊就是失配指標
.
藍色邊(終點稱為起點的字尾結點): 連線字串終點到在圖中存在的, 最長嚴格字尾的結點. 如 caa 的嚴格字尾為 aa,a, 空. 而在圖中存在, 且最長的是字串 a, 則連線到這個字串的終點 a.
綠色邊(字典字尾結點): 終點是起點經過藍色有向邊到達的第一個藍色結點.
下面摘自 wiki
在每一步中,演算法先查詢當前節點的 “孩子節點”,如果沒有找到匹配,查詢它的字尾節點(suffix) 的孩子,如果仍然沒有,接著查詢字尾節點的字尾節點的孩子, 如此迴圈, 直到根結點,如果到達根節點仍沒有找到匹配則結束。
當演算法查詢到一個節點,則輸出所有結束在當前位置的字典項。輸出步驟為首先找到該節點的字典字尾,然後用遞迴的方式一直執行到節點沒有字典字首為止。同時,如果該節點為一個字典節點,則輸出該節點本身。
輸入 abccab 後演算法的執行步驟如下:
<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}
<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
<a id="markdown-522-zig-zig-step" name="522-zig-zig-step"></a>
5.2.2. Zig-zig step
<a id="markdown-523-zig-zag-step" name="523-zig-zag-step"></a>
5.2.3. Zig-zag step
<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)一個給定的序列.
所以,為了得到一個平衡的二叉排序樹,我們可以將給定的序列隨機化, 然後再進行構造二叉排序樹.
但是如果不能一次得到全部的資料,也就是可能插入新的資料的時候,該怎麼辦呢? 可以證明,滿足下面的條件構造的結構相當於同時得到全部資料, 也就是隨機化的二叉查詢樹.
這種結構叫 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. 附程式碼
<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))
相關文章
- js資料結構--樹(tree)JS資料結構
- 資料結構-Tree資料結構
- 前端樹形Tree資料結構使用-🤸🏻♂️各種姿勢總結前端資料結構
- 資料結構——樹資料結構
- 資料結構-樹資料結構
- LayUI—tree樹形結構的使用UI
- 【資料結構】K-D Tree資料結構
- 資料結構 - 樹,初探資料結構
- 資料結構 - AVL 樹資料結構
- 前端資料結構--樹前端資料結構
- 資料結構-字典樹資料結構
- 資料結構之「樹」資料結構
- 資料結構學習之樹結構資料結構
- 七、基本資料結構(樹形結構)資料結構
- 資料結構(樹):二叉樹資料結構二叉樹
- 【2024-ZR-C Day 6】資料結構(4):樹(重、長)鏈剖分、虛樹、dsu on tree資料結構
- 資料結構之樹( 線段樹,字典樹)資料結構
- 資料結構 - 樹,再探資料結構
- [資料結構] 劃分樹資料結構
- 資料結構-線段樹資料結構
- 資料結構--紅黑樹資料結構
- 資料結構丨字首樹資料結構
- 資料結構——樹和森林資料結構
- 【資料結構】搜尋樹資料結構
- 資料結構之「AVL樹」資料結構
- 資料結構-佇列-樹資料結構佇列
- [譯] Golang 資料結構:樹Golang資料結構
- 資料結構之「霍夫曼樹」資料結構
- 資料結構之「B樹」資料結構
- 資料結構中樹形結構簡介資料結構
- Java將List集合組裝成樹(Tree)樹結構組裝Java
- 詳解資料庫儲存的資料結構LSM Tree資料庫資料結構
- 大資料————決策樹(decision tree)大資料
- 資料結構——樹狀陣列資料結構陣列
- 資料結構--2-3樹資料結構
- 【資料結構】淺談主席樹資料結構
- 資料結構 - 二叉樹資料結構二叉樹
- 資料結構-二叉樹資料結構二叉樹