樹的定義與基本術語
樹型結構是一類重要的非線性數據結構,其中以樹和二叉樹最爲經常使用,是以分支關係定義的層次結構。樹結構在客觀世界中普遍存在,如人類社會的族譜和各類社會組織機構;在計算機領域中也有普遍應用,如在編譯程序中,可用樹來表示源程序的語法結構;在數據庫系統中,樹型結構也是信息的重要組織形式之一;在機器學習中,決策樹,隨機森林,GBDT等是常見的樹模型。 樹(Tree)是n(n≥0)n(n≥0)個結點的有限集。在任意一棵樹中:(1)有且僅有一個特定的稱爲根(Root)的節點;(2)當n>1n>1時,其他節點可分爲m(m>0)m(m>0)個互不相交的有限集T1,T2,...,Tm,T1,T2,...,Tm,其中每個集合自己又是一棵樹,而且稱爲根的子樹(SubTree)。
在圖1,該樹一共有13個節點,其中A是根,其他節點分紅3個互不相交的子集:T1={B,E,F,K,L}T1={B,E,F,K,L},T2={C,G}T2={C,G},T3={D,H,I,J,M}T3={D,H,I,J,M};T1,T2和T3T1,T2和T3都是根A的子樹,且自己也是一棵樹。例如T1T1,其根爲B,其他節點分爲兩個互不相交的子集;T11={E,K,L}T11={E,K,L},T12={F}T12={F}。T11T11和T12T12都是B的子樹。而在T11T11中E是根,{K}{K}和{L}{L}是E的兩棵互不相交的子樹,其自己又是隻有一個根節點的樹。 接下來說一下樹的基本術語。 樹的結點包含一個數據元素及若干指向其子樹的分支。節點擁有的子樹數量稱爲節點的度(Degree)。在圖1中,A的度爲3,B的度爲2,C的度爲1,F的度爲0。度爲0的結點稱爲葉子(Leaf)結點。在圖1中,K,L,F,G,M,I,J都是該樹的葉子。度不爲0的結點稱爲分支結點。樹的度是指樹內個結點的度的最大值。 結點的子樹的根稱爲該結點的孩子(Child),相應地,該結點稱爲孩子的雙親(Parent)。在圖1,中,D是A的孩子,A是D的雙親。同一個雙親的孩子之間互稱兄弟(Sibling)。在圖1中,H,I,J互爲兄弟。結點的祖先是從根到該結點所經分支上的全部結點。在圖1中,M的祖先爲A,D,H。對應地,以某結點爲根的子樹中的任一結點都稱爲該結點的子孫。在圖1中,B的子孫爲E,F,K,L。 樹的層次(Level)是從根開始,根爲第一層,根的孩子爲第二層等。雙親在同一層的結點互爲同兄弟,在圖1中,K,L,M互爲堂兄弟。樹中結點的最大層次稱爲樹的深度(Depth)或高度,在圖1中,樹的深度爲4。 若是將樹中結點的各子樹當作從左到右是有次序的(即不能交換),則稱該樹爲有序樹,不然爲無序樹。 森林(Forest)是m(m≥0)m(m≥0)棵互不相交的樹的集合。對樹中每一個結點而言,其子樹的集合即爲森林。在機器學習模型中,決策樹爲樹型結構,而隨機森林爲森林,是由若干決策樹組成的森林。
二叉樹的定義與基本性質
二叉樹(Binary Tree)是一種特殊的樹型結構,它的特色是每一個結點至多有兩棵子樹(即二叉樹中不存在度大於2的結點),且二叉樹的子樹有左右之分,其次序不能任意顛倒(有序樹)。 根據二叉樹的定義,其具備下列重要性質:(這裏不給出證實,證實細節可參考清華大學出版社 嚴蔚敏 吳偉民的《數據結構(C語言版)》)
性質1)在二叉樹的第ii層上至多有2i−12i−1個結點(i≥1)(i≥1)。 性質2)深度爲kk的二叉樹至多有2k−12k−1個結點(k≥1)(k≥1)。 性質3)對任何一棵二叉樹,若是其葉子節點數爲n0n0,度爲2的結點數爲n2n2,則n0=n2+1n0=n2+1。
一棵深度爲kk且有2k−12k−1個結點的二叉樹稱爲滿二叉樹。深度爲kk,結點數數nn的二叉樹,當且僅當其每個結點都與深度爲kk的滿二叉樹中編號爲1至n的結點一一對應時,稱之爲徹底二叉樹。在下圖2中,(a)爲滿二叉樹,(b)爲徹底二叉樹。
性質4)具備nn個結點的徹底二叉樹的深度爲[log2n]+1[log2n]+1,其中[x][x]表示不大於x的最大整數。 性質5)若是對一棵有n個結點的徹底二叉樹的結點按層序編號(從第一層到最後一層,每層從左到右),則對任一結點i(1≤i≤n)i(1≤i≤n),有: (1)若是i=1,則結點i是二叉樹的根,無雙親;若是i>1,則其雙親結點爲[1/2]。 (2)若是2i>n,則結點i無左孩子;不然其左孩子是結點2i。 (3)若是2i+1>n,則結點i無右孩子;不然其右孩子是結點2i+1。
介紹完了二叉樹的定義及基本性質,接下來,咱們須要瞭解二叉樹的遍歷。所謂二叉樹的遍歷,指的是如何按某種搜索路徑巡防樹中的每一個結點,使得每一個結點均被訪問一次,並且僅被訪問一次。對於二叉樹,常見的遍歷方法有:先序遍歷,中序遍歷,後序遍歷,層序遍歷。這些遍歷方法通常使用遞歸算法實現。 先序遍歷的操做定義爲:若二叉樹爲空,爲空操做;不然(1)訪問根節點;(2)先序遍歷左子樹;(3)先序遍歷右子樹。 中序遍歷的操做定義爲:若二叉樹爲空,爲空操做;不然(1)中序遍歷左子樹;(2)訪問根結點;(3)中序遍歷右子樹。 後序遍歷的操做定義爲:若二叉樹爲空,爲空操做;不然(1)後序遍歷左子樹;(2)後序遍歷右子樹;(3)訪問根結點。 層序遍歷的操做定義爲:若二叉樹爲空,爲空操做;不然從上到下、從左到右按層次進行訪問。 如對於下圖3,
其先序遍歷、中序遍歷、後序遍歷、層序遍歷的結果爲:
先序遍歷爲:
18 7 3 4 11 5 1 3 6 2 4
中序遍歷爲:
3 7 4 18 1 5 3 11 2 6 4
後序遍歷爲:
3 4 7 1 3 5 2 4 6 11 18
層序遍歷爲:
[[18], [7, 11], [3, 4, 5, 6], [1, 3, 2, 4]]複製代碼
關於二叉樹的存儲結構,能夠選擇鏈式存儲結構。用於表示二叉樹的鏈表中的結點至少包含3個域:數據域和左、右指針。下面會給出如何利用利用鏈式存儲結構實現二叉樹(Python實現)。
二叉樹的Python實現
瞭解了二叉樹的基本狀況後,筆者使用Python實現了二叉樹,其完整的Python代碼(Binary_Tree.py)以下:
from graphviz import Digraph
import uuid
from random import sample
class BTree(object):
def __init__(self, data=None, left=None, right=None):
self.data = data
self.left = left
self.right = right
self.dot = Digraph(comment='Binary Tree')
def preorder(self):
if self.data is not None:
print(self.data, end=' ')
if self.left is not None:
self.left.preorder()
if self.right is not None:
self.right.preorder()
def inorder(self):
if self.left is not None:
self.left.inorder()
if self.data is not None:
print(self.data, end=' ')
if self.right is not None:
self.right.inorder()
def postorder(self):
if self.left is not None:
self.left.postorder()
if self.right is not None:
self.right.postorder()
if self.data is not None:
print(self.data, end=' ')
def levelorder(self):
def LChild_Of_Node(node):
return node.left if node.left is not None else None
def RChild_Of_Node(node):
return node.right if node.right is not None else None
level_order = []
if self.data is not None:
level_order.append([self])
height = self.height()
if height >= 1:
for _ in range(2, height + 1):
level = []
for node in level_order[-1]:
if LChild_Of_Node(node):
level.append(LChild_Of_Node(node))
if RChild_Of_Node(node):
level.append(RChild_Of_Node(node))
if level:
level_order.append(level)
for i in range(0, height):
for index in range(len(level_order[i])):
level_order[i][index] = level_order[i][index].data
return level_order
def height(self):
if self.data is None:
return 0
elif self.left is None and self.right is None:
return 1
elif self.left is None and self.right is not None:
return 1 + self.right.height()
elif self.left is not None and self.right is None:
return 1 + self.left.height()
else:
return 1 + max(self.left.height(), self.right.height())
def leaves(self):
if self.data is None:
return None
elif self.left is None and self.right is None:
print(self.data, end=' ')
elif self.left is None and self.right is not None:
self.right.leaves()
elif self.right is None and self.left is not None:
self.left.leaves()
else:
self.left.leaves()
self.right.leaves()
def print_tree(self, save_path='./Binary_Tree.gv', label=False):
colors = ['skyblue', 'tomato', 'orange', 'purple', 'green', 'yellow', 'pink', 'red']
def print_node(node, node_tag):
color = sample(colors,1)[0]
if node.left is not None:
left_tag = str(uuid.uuid1())
self.dot.node(left_tag, str(node.left.data), style='filled', color=color)
label_string = 'L' if label else ''
self.dot.edge(node_tag, left_tag, label=label_string)
print_node(node.left, left_tag)
if node.right is not None:
right_tag = str(uuid.uuid1())
self.dot.node(right_tag, str(node.right.data), style='filled', color=color)
label_string = 'R' if label else ''
self.dot.edge(node_tag, right_tag, label=label_string)
print_node(node.right, right_tag)
if self.data is not None:
root_tag = str(uuid.uuid1())
self.dot.node(root_tag, str(self.data), style='filled', color=sample(colors,1)[0])
print_node(self, root_tag)
self.dot.render(save_path) 複製代碼
在上述代碼中,筆者建立了二叉樹類BTree,實現了以下方法:
-
初始化方法:該樹存放的數據爲data,左子樹,右子樹爲left和right,默認均爲None;
-
preorder()方法:遞歸實現二叉樹的先序遍歷;
-
inorder()方法:遞歸實現二叉樹的中序遍歷;
-
postorder()方法:遞歸實現二叉樹的後序遍歷;
-
levelorder()方法:遞歸實現二叉樹的層序遍歷;
-
height()方法:計算二叉樹的高度;
-
leaves()方法:計算二叉樹的葉子結點;
-
print_tree()方法:利用Graphviz實現二叉樹的可視化,須要設置的參數爲save_path和label,save_path爲文件保存路徑,默認的保存路徑爲當前路徑下的Binary_Tree.gv,能夠用戶本身設置;label爲是否在Graphviz文件中添加二叉樹的左右子樹的標籤,用於分清哪棵是左子樹,哪棵是右子樹,能夠用用戶本身設置。
若咱們須要實現圖3的示例二叉樹,完整的Python代碼以下:
from Binary_Tree import BTree
right_tree = BTree(6)
right_tree.left = BTree(2)
right_tree.right = BTree(4)
left_tree = BTree(5)
left_tree.left = BTree(1)
left_tree.right = BTree(3)
tree = BTree(11)
tree.left = left_tree
tree.right = right_tree
left_tree = BTree(7)
left_tree.left = BTree(3)
left_tree.right = BTree(4)
right_tree = tree
tree = BTree(18)
tree.left = left_tree
tree.right = right_tree
print('先序遍歷爲:')
tree.preorder()
print()
print('中序遍歷爲:')
tree.inorder()
print()
print('後序遍歷爲:')
tree.postorder()
print()
print('層序遍歷爲:')
level_order = tree.levelorder()
print(level_order)
print()
height = tree.height()
print('樹的高度爲%s.' % height)
print('葉子節點爲:')
tree.leaves()
print()
tree.print_tree(save_path='E://BTree.gv', label=True)複製代碼
OK,當咱們運行上述代碼時,能夠獲得該二叉樹的一些信息,輸出結果以下:
先序遍歷爲:
18 7 3 4 11 5 1 3 6 2 4
中序遍歷爲:
3 7 4 18 1 5 3 11 2 6 4
後序遍歷爲:
3 4 7 1 3 5 2 4 6 11 18
層序遍歷爲:
[[18], [7, 11], [3, 4, 5, 6], [1, 3, 2, 4]]
樹的高度爲4.
葉子節點爲:
3 4 1 3 2 4複製代碼
該Python代碼的優點在於利用Graphviz實現了二叉樹的可視化,能夠形象直觀地獲得二叉樹的圖形。在上面的代碼中,咱們能夠看到,構建二叉樹不是很方便,須要手動地一個個結點去添加。那麼,若是當咱們須要根據某個列表,按列表順序去構建二叉樹時,即二叉樹的層序遍歷爲該列表,那又該怎麼辦呢?有什麼好的辦法嗎? 答案是必須有!按照某個列表去構建二叉樹的完整Python代碼以下:
from Binary_Tree import BTree
def create_BTree_By_List(array):
i = 1
level_order = []
sum = 1
while sum < len(array):
level_order.append(array[i-1:2*i-1])
i *= 2
sum += i
level_order.append(array[i-1:])
def Create_BTree_One_Step_Up(BTree_list, forword_level):
new_BTree_list = []
i = 0
for elem in forword_level:
root = BTree(elem)
if 2*i < len(BTree_list):
root.left = BTree_list[2*i]
if 2*i+1 < len(BTree_list):
root.right = BTree_list[2*i+1]
new_BTree_list.append(root)
i += 1
return new_BTree_list
if len(level_order) == 1:
return BTree(level_order[0][0])
else:
BTree_list = [BTree(elem) for elem in level_order[-1]]
for i in range(len(level_order)-2, -1, -1):
BTree_list = Create_BTree_One_Step_Up(BTree_list, level_order[i])
return BTree_list[0]
array = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
tree = create_BTree_By_List(array)
print('先序遍歷爲:')
tree.preorder()
print()
height = tree.height()
print('\n樹的高度爲%s.\n'%height)
print('層序遍歷爲:')
level_order = tree.levelorder()
print(level_order)
print()
print('葉子節點爲:')
tree.leaves()
print()
tree.print_tree(save_path='E://create_btree_by_list.gv', label=True)複製代碼
在上述程序中,筆者利用create_BTree_By_List()函數實現了按照某個列表去構建二叉樹,輸入的參數array爲列表,要求列表中至少有一個元素。運行上述程序,咱們獲得的26個大寫字母列表所構建的二叉樹的圖像以下:
先序遍歷爲:
A B D H P Q I R S E J T U K V W C F L X Y M Z G N O
樹的高度爲5.
層序遍歷爲:
[['A'], ['B', 'C'], ['D', 'E', 'F', 'G'], ['H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'], ['P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']]
葉子節點爲:
P Q R S T U V W X Y Z N O複製代碼
總結
二叉樹是不少重要算法及模型的基礎,好比二叉搜索樹(BST),哈夫曼樹(Huffman Tree),CART決策樹等。本文先介紹了樹的基本術語,二叉樹的定義與性質及遍歷、儲存,而後筆者本身用Python實現了二叉樹的上述方法,筆者代碼的最大亮點在於實現了二叉樹的可視化,這個功能是激動人心的。 在Python中,已有別人實現好的二叉樹的模塊,它是binarytree模塊,其官方文檔的網址爲:
pypi.org/project/bin… 。其使用的例子以下: