樹(tree)是一種抽象數據類型或是實現這種抽象數據類型的數據結構,用來模擬具備樹狀結構性質的數據集合
它具備如下的特色:
①每一個節點有零個或多個子節點;
②沒有父節點的節點稱爲根節點;
③每個非根節點有且只有一個父節點;
④除了根節點外,每一個子節點能夠分爲多個不相交的子樹;
node
二叉樹:每一個節點最多含有兩個子樹的樹稱爲二叉樹。
python
二叉樹中一些專業術語:算法
節點的高度
:節點到葉子結點的最長路徑,好比C節點的高度是2(L->F是1,F->C是2)節點的深度
:節點到根節點的所經歷的邊的個數好比C節點的高度是1(A->C,只有一條邊,因此深度=1)節點的層
:節點的高度樹的高度
:根節點的高度基於二叉樹衍生的多種樹型結構:數據庫
滿二叉樹
:除最後一層無任何子節點外,每一層上的全部結點都有兩個子結點。也能夠這樣理解,除葉子結點外的全部結點均有兩個子結點。節點數達到最大值,全部葉子結點必須在同一層上
數據結構
徹底二叉樹
:設二叉樹的深度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h 層全部的結點都連續集中在最左邊,這就是徹底二叉樹
app
滿二叉樹和徹底二叉樹對比:
post
二叉查找樹
: 也稱二叉搜索樹,或二叉排序樹。其定義也比較簡單,要麼是一顆空樹,要麼就是具備以下性質的二叉樹:
(1)若任意節點的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值;
(2) 若任意節點的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;
(3) 任意節點的左、右子樹也分別爲二叉查找樹;
(4) 沒有鍵值相等的節點。性能
定義
: 平衡二叉搜索樹,又被稱爲AVL樹,且具備如下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹設計
平衡二叉樹出現緣由
:
因爲普通的二叉查找樹會容易失去」平衡「,極端狀況下,二叉查找樹會退化成線性的鏈表,致使插入和查找的複雜度降低到 O(n) ,因此,這也是平衡二叉樹設計的初衷。那麼平衡二叉樹如何保持」平衡「呢?根據定義,有兩個重點,一是左右兩子樹的高度差的絕對值不能超過1,二是左右兩子樹也是一顆平衡二叉樹。
指針
平衡二叉樹的建立
:
平衡二叉樹是一棵高度平衡的二叉查找樹。因此,要構建跟維繫一棵平衡二叉樹就比普通的二叉樹要複雜的多。在構建一棵平衡二叉樹的過程當中,當有新的節點要插入時,檢查是否因插入後而破壞了樹的平衡,若是是,則須要作旋轉去改變樹的結構
avl樹每次插入刪除會進行大量的平衡度計算致使IO數量巨大而影響性能。因此出現了紅黑樹。一種二叉查找樹,但在每一個節點增長一個存儲位表示節點的顏色,能夠是紅或黑(非紅即黑)
定義
:
紅黑樹有兩個重要性質
:
一、紅節點的孩子節點不能是紅節點;
二、從根到葉子節點的任意一條路徑上的黑節點數目同樣多。
這兩條性質確保該樹的高度爲logN,因此是平衡樹。
優點
:
紅黑樹的查詢性能略微遜色於AVL樹,由於他比avl樹會稍微不平衡最多一層,也就是說紅黑樹的查詢性能只比相同內容的avl樹最多多一次比較,可是,紅黑樹在插入和刪除上完爆avl樹,avl樹每次插入刪除會進行大量的平衡度計算,而紅黑樹爲了維持紅黑性質所作的紅黑變換和旋轉的開銷,相較於avl樹爲了維持平衡的開銷要小得多
使用場景
:
定義
:
B樹是爲實現高效的磁盤存取而設計的多叉
平衡搜索樹。(B樹和B-tree這兩個是同一種樹)
產生緣由
:
B樹是一種查找樹,咱們知道,這一類樹(好比二叉查找樹,紅黑樹等等)最初生成的目的都是爲了解決某種系統中,查找效率低的問題。
B樹也是如此,它最初啓發於二叉查找樹,二叉查找樹的特色是每一個非葉節點都只有兩個孩子節點。然而這種作法會致使當數據量很是大時,二叉查找樹的深度過深
,搜索算法自根節點向下搜索時,須要訪問的節點也就變的至關多。
若是這些節點存儲在外存儲器中,每訪問一個節點,至關於就是進行了一次I/O操做,隨着樹高度的增長,頻繁的I/O操做必定會下降查詢的效率。
定義
:
B樹是一種平衡的多分樹,一般咱們說m階的B樹,它必須知足以下條件:
全部葉子都出如今同一水平
,沒有任何信息(高度一致)。特色
:
B+樹是應文件系統所需而產生的B樹的變形樹
B+樹有兩種類型的節點:內部結點(也稱索引結點)和葉子結點。內部節點就是非葉子節點,內部節點不存儲數據,只存儲索引,數據都存儲在葉子節點。
內部結點中的key都按照從小到大的順序排列,對於內部結點中的一個key,左樹中的全部key都小於它,右子樹中的key都大於等於它。葉子結點中的記錄也按照key的大小排列。
每一個葉子結點都存有相鄰葉子結點的指針,葉子結點自己依關鍵字的大小自小而大順序連接
父節點存有右孩子的第一個元素的索引。
最核心的特色以下:
(1)多路非二叉
(2)只有葉子節點保存數據
(3)搜索時至關於二分查找
(4)增長了相鄰接點的指向指針
B+樹爲何時候作數據庫索引
:因爲B+樹的數據都存儲在葉子結點中,分支結點均爲索引,方便掃庫,只須要掃一遍葉子結點便可,可是B樹由於其分支結點一樣存儲着數據,咱們要找到具體的數據,須要進行一次中序遍歷按序來掃。簡單來講就是:B+樹查詢某一個數據時掃描葉子節點便可;而B樹須要中序遍歷整個樹,因此B+樹更快。
爲何說B+樹比B樹更適合數據庫索引?
1)B+樹的磁盤讀寫代價更低
B+樹的內部結點並無指向關鍵字具體信息的指針。所以其內部結點相對B 樹更小。若是把全部同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了;
2)B+樹查詢效率更加穩定
因爲非終結點並非最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。因此任何關鍵字的查找必須走一條從根結點到葉子結點的路。全部關鍵字查詢的路徑長度相同,致使每個數據的查詢效率至關;
3)B+樹便於範圍查詢(最重要的緣由,範圍查找是數據庫的常態)
B樹在提升了IO性能的同時並無解決元素遍歷效率低下的問題,正是爲了解決這個問題,B+樹應用而生。B+樹只須要去遍歷葉子節點就能夠實現整棵樹的遍歷。並且在數據庫中基於範圍的查詢是很是頻繁的,而B樹不支持這樣的操做或者說效率過低;
B樹的範圍查找用的是中序遍歷,而B+樹用的是在鏈表上遍歷;
樹的建立有不少種方式,分爲迭代建立和遞歸建立。下面分別介紹這兩種建立數的方式。
建立的樹:
該建立方法是按照層次建立,第一層建立好以後第二層,第二層完成後建立第三層。
class Node(object): def __init__(self,value=-1,left=None,right=None): self.value = value self.left = left self.right = right class Tree(object): def __init__(self, root=None): self.root = root def insert(self,element): node = Node(element) if self.root == None: self.root = node else: queue = [] queue.append(self.root) while queue: cur = queue.pop(0) if cur.left == None: cur.left = node return elif cur.right == None: cur.right = node return else: queue.append(cur.left) queue.append(cur.right) def output(self, root): if root == None: return print(root.value) self.output(root.left) self.output(root.right) one = Tree() for i in range(10): one.insert(i) one.output(one.root)
該建立方式是遞歸建立,前提是將樹的數據組織成一個徹底二叉樹的形式
class Node(object): def __init__(self,value=None): self.value = value self.left = None self.right = None def create_two(index, length, arr): if index > length: return None node = Node(arr[index]) node.left = create_two(index*2+1, length, arr) node.right = create_two(index*2+2, length, arr) return node def BFS(root): queue = [root] while queue: cur = queue.pop(0) print(cur.value) if cur.left: queue.append(cur.left) if cur.right: queue.append(cur.right) arr = [1,2,3,4,None,None,None,None,None] length = len(arr) -1 head = create_two(0,length, arr) print(head.value) print(head.left) print(head.right) BFS(head)
樹的遍歷方式有不少種,能夠分爲五類:
實現遍歷的方式中有能夠分爲遞歸和迭代
class TreeNode(object): def __init__(self,value=None): self.value = value self.left = None self.right = None class Tree(object): def __init__(self): self.root = TreeNode(None) self.arr = [] def create(self,value): if self.root.value is None: self.root = TreeNode(value) else: queue = [self.root] while queue: node = queue.pop(0) if node.left: queue.append(node.left) else: node.left = TreeNode(value) return if node.right: queue.append(node.right) else: node.right = TreeNode(value) return # 遞歸、前序遍歷 def preorder(self,root): if root is None: return self.arr.append(root.value) self.preorder(root.left) self.preorder(root.right) # 遞歸、中序遍歷 def inorder(self,root): if root is None: return self.inorder(root.left) self.arr.append(root.value) self.inorder(root.right) # 遞歸、後序遍歷 def postorder(self,root): if root is None: return self.postorder(root.left) self.postorder(root.right) self.arr.append(root.value) # 迭代、前序遍歷 def preorder_two(self,root): stack = [root] arr = [] while stack: cur = stack.pop() arr.append(cur.value) if cur.right: stack.append(cur.right) if cur.left: stack.append(cur.left) print(arr) # 迭代、後序遍歷 def postorder_two(self,root): stack = [root] arr = [] while stack: cur = stack.pop() arr.append(cur.value) if cur.left: stack.append(cur.left) if cur.right: stack.append(cur.right) print(arr[::-1]) # 迭代、中序遍歷 def inorder_two(self,root): cur = root stack = [] arr = [] while cur or stack: while cur: stack.append(cur) cur = cur.left node = stack.pop() arr.append(node.value) cur = node.right print(arr) # 層次遍歷 def levelorder(self,root): queue = [root] arr = [] while queue: cur = queue.pop(0) arr.append(cur.value) if cur.left: queue.append(cur.left) if cur.right: queue.append(cur.right) print(arr) # 子數遍歷,返回從根節點到每個葉子節點的一條路徑 # 子數遍歷,返回從根節點到每個葉子節點的一條路徑 def zishu(self,root,arr): if not root.left and not root.right: print(arr) return if root.left: self.zishu(root.left, arr + [root.left.value]) if root.right: self.zishu(root.right, arr + [root.right.value]) tree = Tree() for i in range(10): tree.create(i) print('--------------------遞歸--------------------------') tree.preorder(tree.root) print(tree.arr) tree.arr = [] tree.inorder(tree.root) print(tree.arr) tree.arr = [] tree.postorder(tree.root) print(tree.arr) print('--------------------迭代--------------------------') tree.preorder_two(tree.root) tree.inorder_two(tree.root) tree.postorder_two(tree.root) print('--------------------層次--------------------------') tree.levelorder(tree.root) print('--------------------子數--------------------------') tree.arr = [] tree.zishu(tree.root, [tree.root.value]) print(tree.arr)