一棵樹是一些節點的集合。這個集合能夠是空集,若非空,則一棵樹由稱做 根(root) 的節點 r 和 0 個或以上的非空子樹組成。這些子樹中每一棵的根都被來自根 r 的**一條有向邊(edge)**鏈接。node
父子節點,葉子節點定義略。。。算法
從節點 n<sub>1</sub> 到 n<sub>k</sub> 的 路徑(path) 定義爲節點 n<sub>1</sub>, n<sub>2</sub>, ..., n<sub>k</sub> 的一個序列,使得對於 1 ≤ i ≤ k,節點 n<sub>i+1</sub> 是 節點 n<sub>i</sub> 的父親。這條路徑的 長度(length) 定義爲該路徑上邊的條數,即k - 1。數據庫
可見任意兩節點 n<sub>i</sub> 和 n<sub>k</sub> 之間是否存在一條路徑取決於 n<sub>i</sub> 是不是 n<sub>k</sub> 的祖先節點。且若是有,也僅有一條。(由於上面樹的定義裏,一個非根節點有且只有一條邊)性能
而從根節點到任意節點均存在一條惟一的路徑,這條路徑的長度被稱爲該節點的 深度(depth)。一棵樹的深度被定義爲其最深的一個節點的深度。優化
按照以前算法分析的某條定理:若是一個算法可使用常數時間將問題縮小爲原問題的一部分,那麼這個算法的複雜度爲 O(logN)。樹在正常狀況下的查找上顯然符合這個特徵。code
能夠將樹當作是序列的變形,它在序列之上容許了多子節點。排序
二叉樹即每一個節點的子節點數均不超過 2 的狀況。索引
爲了便於操做,實際的樹一般會在定義之上再增長一些特性,如rem
下面是一個使用字典實現的二叉查找樹,功能方面只實現了 __contains__
方法:it
class Node(dict): def __init__(self, value, parent=None, left=None, right=None): self['value'] = value self['count'] = 1 if parent: self['parent'] = parent if left: self['left'] = left if right: self['right'] = right class Tree(object): def __init__(self, init_value): self.root = Node(init_value) def _find_near(self, value): node = self.root try: while True: if value == node['value']: return node elif value < node['value']: node = node['left'] else: node = node['right'] except KeyError: return node def add(self, value): node = self._find_near(value) if value == node['value']: node['count'] += 1 elif value < node['value']: node['left'] = Node(value) else: node['right'] = Node(value) def rem(self, value): node = self._find_near(value) if node['value'] == value and node['count'] > 0: node['count'] -= 1 def __contains__(self, value): node = self._find_near(value) return node['value'] == value and node['count'] > 0
容易看到,二叉樹可能出現一個極端狀態,即全部節點的子節點數均不超過 1 ,此時的樹實際上就是鏈表。而其深度爲 N-1.
所以使一棵樹擁有儘量低的深度,對於查找性能來講顯得尤其重要,這個優化的過程便稱爲平衡,即取,使每一個節點的左右子樹的深度儘量平衡之意。
平衡是個很麻煩的事情,它意味着每次更新操做後都有可能要變動樹的結構。此過程稱爲旋轉(ratation)。如 AVL 樹,要求每一個節點的左子樹和右子樹的高度最多差 1.
B-樹的 B 並無什麼肯定的意思,尤爲不要理解爲 Binary,由於通常它並不實現爲二叉樹,而是多路樹。
階爲 M 的 B 樹是這樣一種樹:
這個定義看起來不是很直觀,能夠這樣理解:
M 階 B樹的數據部分是一個序列。除了這個序列外,還額外存在一個樹型結構,用於索引這個序列。具體方法爲:序列從前向後每 M 個元素便向上生出一個父節點,父節點的值等於這 M 個元素的最小值(或最大值,取決於序列的排序),而後這批父節點再每 M 個向上生出次級父節點,如此反覆直到生出根來。
固然,實際 B樹的生成過程是正好相反的。也相應的有更多須要考慮的問題。
B樹每一個節點的子節點數均小於等於 M。另外爲了儘可能下降樹的深度,咱們還規定內部節點的子節點數需大於等於 M/2 (ceil),當不知足此條件時,就要將子節點合併。
由此咱們能夠算出,M 階 B樹的深度最小能夠爲 Log<sub>M</sub>N,最大也不過是 Log<sub>M/2</sub>N。另外由於每一個節點最多有 M 個子節點,因此一次節點內分支選擇的複雜度爲 LogM。故搜索的複雜度爲 (M/2)Log<sub>M/2</sub>N,可化簡爲 O(LogN)
。增刪操做可能須要 O(M) 的時間來調整節點數據,所以增刪操做的複雜度爲 O(MLog<sub>M</sub>N) ,可化簡爲 O((M/LogM)LogN)
。
由前面的增刪操做複雜度公式前的常數部分 M/LogM
可得 M 的最佳值爲 (2, 3, 4),當再高時,插入效率會變低。而搜索複雜度與 M 無關。
B樹也是數據庫經常使用的一種索引,所以 M 的選擇更多會參考實際的應用場景。如存儲在機械硬盤上的 SQL 數據庫,硬盤尋址操做的開銷比連續讀要高得多,所以使內部節點所佔空間儘可能接近單扇區可用容量是最好的作法。如 512 字節的扇區容量,每一個節點元素佔 4 字節的話,M 就能夠設置爲 128。對於 SSD,或者更大扇區的磁盤,這個數字均可作相應調整。