樹是一種數據結構,好比:目錄結構。html
樹是一種能夠遞歸定義的數據結構。node
定義:樹是由n個節點組成的集合:python
若是n=0,那這是一棵空樹;數據庫
若是n>0,那存在1個節點做爲樹的根節點,其餘節點能夠分爲m個集合,每一個集合自己又是一棵樹。數據結構
根節點: 根節點(root)是樹的一個組成部分,也叫樹根。它是同一棵樹中除自己外全部節點的祖先,沒有父節點。app
葉子節點(終端節點):一棵樹當中沒有子節點(即度爲0)的結點稱爲葉子結點,簡稱「葉子」。 葉子是指度爲0的結點,又稱爲終端結點。dom
樹的深度(高度):樹中節點的最大層次。post
節點的度:一個節點含有的子樹的個數稱爲該節點的度。spa
樹的度:一棵樹中,最大的節點的度稱爲樹的度。3d
父節點(雙親節點):若一個節點含有子節點,則這個節點稱爲其子節點的父節點;
子樹:設T是有根樹,a是T中的一個頂點,由a以及a的全部後裔(後代)導出的子圖稱爲有向樹T的子樹。
class Node: def __init__(self, name, type='dir'): self.name = name self.type = type # 類型能夠是"dir"或"file" self.children = [] self.parent = None
"""鏈式存儲""" def __repr__(self): return self.name class FileSystemTree: def __init__(self): self.root = Node("/") # 根目錄 self.now = self.root # 當前目錄 def mkdir(self, name): """建立目錄""" if name[-1] != "/": name += "/" # 判斷當不是以"/"結尾,添加"/" node = Node(name) # 建立文件夾 self.now.children.append(node) node.parent = self.now def ls(self): """展現當前目錄下的全部目錄""" return self.now.children def cd(self, name): """切換路徑""" if name[-1] != "/": name += "/" # 判斷當不是以"/"結尾,添加"/" if name == "../": self.now = self.now.parent return for child in self.now.children: if child.name == name: self.now = child return raise ValueError("invalid dir") tree = FileSystemTree() tree.mkdir("var/") tree.mkdir("bin/") tree.mkdir("usr/") print(tree.root.children) # [var/, bin/, usr/] print(tree.ls()) # [var/, bin/, usr/] tree.cd("bin/") tree.mkdir("python/") print(tree.ls()) # [python/] tree.cd("../") print(tree.ls()) # [var/, bin/, usr/]
樹絕大多數的存儲都是和鏈表同樣鏈式存儲。日後指child;往前指parent。經過節點和節點間相互鏈接的關係來組成這麼一個數據結構。
二叉樹:度不超過2的樹。以下所示:
每一個節點最多有兩個孩子節點,兩個孩子節點被區分爲左孩子節點和右孩子節點。
一個二叉樹若是每一層的節點數都達到最大值,則這個二叉樹就是滿二叉樹。
葉節點只能出如今最下層和次下層,而且最下面一層的節點都集中在該層最左邊的若干位置的二叉樹。
滿二叉樹必定是徹底二叉樹,但徹底二叉樹不必定是滿二叉樹。堆是一個特殊的徹底二叉樹。
二叉樹這種數據結構在計算機中的存儲方法。
二叉樹的鏈式存儲:將二叉樹的節點定義爲一個對象,節點之間經過相似鏈表的連接方式來鏈接。
class BiTreeNode: def __init__(self, data): # data就是傳進去的節點值 self.data = data self.lchild = None self.rchild = None
代碼以下:
class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 # 建立二叉樹節點 a = BiTreeNode("A") b = BiTreeNode("B") c = BiTreeNode("C") d = BiTreeNode("D") e = BiTreeNode("E") f = BiTreeNode("F") g = BiTreeNode("G") # 節點鏈接 e.lchild = a e.rchild = g a.rchild = c c.lchild = b c.rchild = d g.rchild = f # 指定根節點 root = e print(root.lchild.rchild.data) # C
所謂順序存儲方式就是二叉樹用列表來存儲。以下圖所示就是用列表來存儲二叉樹。
如上圖二叉樹標出了元素所對應的索引,則能夠有如下結論:
父與左子下標關係:0-1 1-3 2-5 3-7 4-9
i (父)——>2i+1 (子)
若是已知父親節點爲i,那麼他的左孩子節點爲2i+1
父與右子下標關係:0-2 1-4 2-6 3-8 4-10
i (父)——>2i+2 (子)
若是知道父親節點爲i,那麼他的右孩子節點爲2i+2
知道左孩子求父節點:(n-1)/2=i
知道右孩子求父節點:(n-2)/2=i
訪問根節點操做發生在遍歷其左右子樹以前。
def pre_order(root): """前序遍歷""" if root: # 若是不爲空(遞歸條件) print(root.data, end=',') # 訪問本身 pre_order(root.lchild) # 遞歸左子樹 pre_order(root.rchild) # 遞歸右子樹 pre_order(root) # E,A,C,B,D,G,F,
訪問根節點的操做發生在遍歷其左右子樹之間。
def in_order(root): """中序遍歷""" if root: in_order(root.lchild) # 遞歸左子樹 print(root.data, end=',') # 訪問本身 in_order(root.rchild) # 遞歸右子樹 in_order(root) # A,B,C,D,E,G,F,
訪問根節點的操做發生在遍歷其左右子樹以後。
def post_order(root): """後序遍歷""" if root: post_order(root.lchild) # 遞歸左子樹 post_order(root.rchild) # 遞歸右子樹 print(root.data, end=",") # 訪問本身 post_order(root) # B,D,C,A,F,G,E,
層次遍歷很好理解,須要利用到隊列。不只適用二叉樹也適用多叉樹。
用一個隊列保存被訪問的當前節點的左右孩子以實現層序遍歷。
from collections import deque def level_order(root): """層次遍歷""" queue = deque() queue.append(root) while len(queue) > 0: # 只要隊不空 node = queue.popleft() # 出隊 print(node.data, end=',') if node.lchild: queue.append(node.lchild) if node.rchild: queue.append(node.rchild) level_order(root) # E,A,G,C,F,B,D,
例如:前序遍歷——EACBDGF;中序遍歷——ABCDEGF。
由此可知E是根節點,E的左邊包含ABCD,右邊包含GF。且A是根節點的左節點、G是根節點的右節點。
BCD是A的子節點,因爲中序遍歷ABCD可知A的左節點是空的,右節點包含BCD,由前序ACBD可知C是A的右子節點。再由中序遍歷BCD可知B是C的左節點,D是C的右節點。
GF是根節點右邊節點,G是右節點,F是G的子節點。由中序GF可知F是G節點的右節點。至此推導出整個樹。
二叉搜索樹是一顆二叉樹且知足性質:設x是二叉樹的一個節點。若是y是x左子樹的一個節點,那麼y.key <= x.key;若是y是x右子樹的一個節點,那麼y.key >= x.key。
總結來講:二叉搜索樹的左子樹不空,則左子樹上全部節點的值均小於它的根節點的值;若它的右子樹不空,則右子樹上全部節點的值均大於它根節點的值;它的左右子樹也都是二叉搜索樹。
class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 self.parent = None # 加了parent就是雙鏈表 class BST: def __init__(self, li=None): self.root = None if li: for val in li: self.insert_no_rec(val) def insert(self, node, val): """ 遞歸插入 :param node: 節點 :param val: 要插入的值 :return: """ if not node: node = BiTreeNode(val) elif val < node.data: node.lchild = self.insert(node.lchild, val) node.lchild.parent = node elif val > node.data: node.rchild = self.insert(node.lchild,val) node.rchild.parent = node # else: # "=" else不用寫了 return node def insert_no_rec(self, val): """非遞歸插入""" p = self.root if not p: # 空樹的狀況處理 self.root = BiTreeNode(val) return while True: if val < p.data: # 小於根節點往左邊走 if p.lchild: # 若是左孩子存在 p = p.lchild else: # 左子樹不存在 p.lchild = BiTreeNode(val) p.lchild.parent = p return elif val > p.data: # 大於根節點往右邊走 if p.rchild: # 若是右孩子存在 p = p.rchild else: # 右子樹不存在 p.rchild = BiTreeNode(val) p.rchild.parent = p return else: # 等於的時候,什麼都不幹(相似集合) return def pre_order(self, root): """前序遍歷""" if root: # 若是不爲空(遞歸條件) print(root.data, end=',') # 訪問本身 self.pre_order(root.lchild) # 遞歸左子樹 self.pre_order(root.rchild) # 遞歸右子樹 def in_order(self, root): """中序遍歷""" if root: self.in_order(root.lchild) # 遞歸左子樹 print(root.data, end=',') # 訪問本身 self.in_order(root.rchild) # 遞歸右子樹 def post_order(self, root): """後序遍歷""" if root: self.post_order(root.lchild) # 遞歸左子樹 self.post_order(root.rchild) # 遞歸右子樹 print(root.data, end=",") # 訪問本身 tree = BST([4,6,7,9,2,1,3,5,8]) tree.pre_order(tree.root) print("") tree.in_order(tree.root) print("") tree.post_order(tree.root) """ 4,2,1,3,6,5,7,9,8, 1,2,3,4,5,6,7,8,9, # 注意中序是有序的 1,3,2,5,8,9,7,6,4, """
能夠注意到中序遍歷輸出的是有序的,作以下驗證:
import random li = list(range(500)) random.shuffle(li) tree = BST(li) tree.in_order(tree.root) # 0,1,2,3,4,5,...,496,497,498,499
這是由於二叉搜索樹的性質致使二叉搜索樹的左孩子必定是最小的,所以它的中序序列必定是升序的。
class BST: """代碼省略""" def query(self, node, val): """ 遞歸查詢 :param node: 要遞歸的節點 :param val: 要查詢的值 :return: """ if not node: # 若是node是空,則找不到 return None # 遞歸終止條件 if val > node.data: # 大於node的值往右邊找 return self.query(node.rchild, val) elif val < node.data: # 小於node的值往左邊找 return self.query(node.lchild, val) else: return node # 值相同返回當前節點 def query_no_rec(self, val): """非遞歸查詢""" p = self.root while p: # 若是樹不爲空 if p.data < val: # 大於p的值往右邊找 p = p.rchild elif p.data > val: # 小於p的值往左邊找 p = p.lchild else: return p return None # 樹爲空,遞歸終止條件 import random li = list(range(0, 500, 2)) random.shuffle(li) tree = BST(li) print(tree.query_no_rec(3)) # None print(tree.query_no_rec(6)) # <__main__.BiTreeNode object at 0x103d01cc0> print(tree.query_no_rec(6).data) # 6
操做方法是:直接刪除
操做方法是:將此節點的父親與孩子鏈接,而後刪除該節點。
操做方法:將其右子樹的最小節點(該節點最多有一個右孩子)刪除,並替換當前節點。
class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 self.parent = None # 加了parent就是雙鏈表 class BST: """代碼省略""" def __remove_node_1(self, node): """狀況1:node是葉子節點""" if not node.parent: # 此葉子節點沒有父節點,說明樹中就這一個節點 self.root = None # 將這惟一的節點刪除 if node == node.parent.lchild: # node是父親的左孩子 node.parent.lchild = None # 父親與node斷聯繫 node.parent = None # node與父親斷聯繫(這句可寫可不寫) else: # node是父親的右孩子 node.parent.rchild = None # # 父親與node斷聯繫 def __remove_node_21(self, node): """狀況2-1:node只有一個左孩子""" if not node.parent: # 若是node是根節點 self.root = node.lchild # 將node的左孩子置爲根節點 node.lchild.parent = None # 將新根節點的父親設爲空 elif node == node.parent.lchild: # 若是node是它父親的左孩子 node.parent.lchild = node.lchild # node父節點的左孩子設爲node的左孩子 node.lchild.parent = node.parent # node左孩子的父節點設爲node的父節點 else: # 若是node是它父親的右孩子 node.parent.rchild = node.lchild # node父節點的右孩子指向node的左孩子 node.lchild.parent = node.parent # node左孩子的父親指向node的父節點 def __remove_node_22(self, node): """狀況2-2:node只有一個右孩子""" if not node.parent: # 若是node是根節點 self.root = node.rchild # 將node的右孩子置爲根節點 node.rchild.parent = None # 將新根節點的父親設爲空 elif node == node.parent.lchild: # 若是node是父親的左孩子 node.parent.lchild = node.rchild # 將node父節點的左孩子指向node的右孩子 node.rchild.parent = node.parent else: # 若是node是父親的右孩子 node.parent.rchild = node.rchild # 將node父節點的右孩子指向node的右孩子 node.rchild.parent = node.parent def delete(self, val): if self.root: # 若是不是空樹 node = self.query_no_rec(val) if not node: # 若是node不存在 return False if not node.lchild and not node.rchild: # 若是node是葉子節點 self.__remove_node_1(node) elif not node.rchild: # 若是沒有右孩子(只有一個左孩子) self.__remove_node_21(node) elif not node.lchild: # 若是沒有左孩子(只有一個右孩子) self.__remove_node_22(node) else: # 若是兩個孩子都有 min_node = node.rchild while min_node.lchild: # 一直查找node右孩子的左子樹的左孩子,直到沒有爲止 min_node = min_node.lchild node.data = min_node.data # 將min_node.data的值賦給node.data # 刪除min_node if min_node.rchild: # 若是min_node只有右孩子 self.__remove_node_22(min_node) else: # 若是min_node沒有孩子 self.__remove_node_1(min_node) tree = BST([1,4,2,5,3,8,6,9,7]) tree.in_order(tree.root) # 1,2,3,4,5,6,7,8,9, print("") tree.delete(4) tree.in_order(tree.root) # 1,2,3,5,6,7,8,9, print("") tree.delete(1) tree.delete(8) tree.in_order(tree.root) # 2,3,5,6,7,9,
平均狀況下,二叉搜索樹進行搜索的時間複雜度爲O(logn)。
最壞狀況下,二叉搜索樹可能很是偏斜,時間複雜度退化到O(n)。以下所示:
解決方案:
(1)隨機化的二叉搜索樹(打亂順序插入),有時是是否是插入的那打亂插入就很差用。
(2)AVL樹
B樹(B-Tree):B樹是一棵自平衡的多路搜索樹。經常使用於數據庫的索引,最經常使用數據庫的索引就是哈希表、B樹。
以下所示,一個節點存了兩個值,分紅了三路。