二叉樹的Python實現

樹的定義與基本術語

  樹型結構是一類重要的非線性數據結構,其中以樹和二叉樹最爲經常使用,是以分支關係定義的層次結構。樹結構在客觀世界中普遍存在,如人類社會的族譜和各類社會組織機構;在計算機領域中也有普遍應用,如在編譯程序中,可用樹來表示源程序的語法結構;在數據庫系統中,樹型結構也是信息的重要組織形式之一;在機器學習中,決策樹,隨機森林,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:
            # 對第二層及其之後的層數進行操做, 在level_order中添加節點而不是數據
            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):
        # 空的樹高度爲0, 只有root節點的樹高度爲1
        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()

    # 利用Graphviz實現二叉樹的可視化
    def print_tree(self, save_path='./Binary_Tree.gv', label=False):

        # colors for labels of nodes
        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,實現了以下方法:
  1. 初始化方法:該樹存放的數據爲data,左子樹,右子樹爲left和right,默認均爲None;
  2. preorder()方法:遞歸實現二叉樹的先序遍歷;
  3. inorder()方法:遞歸實現二叉樹的中序遍歷;
  4. postorder()方法:遞歸實現二叉樹的後序遍歷;
  5. levelorder()方法:遞歸實現二叉樹的層序遍歷;
  6. height()方法:計算二叉樹的高度;
  7. leaves()方法:計算二叉樹的葉子結點;
  8. print_tree()方法:利用Graphviz實現二叉樹的可視化,須要設置的參數爲save_path和label,save_path爲文件保存路徑,默認的保存路徑爲當前路徑下的Binary_Tree.gv,能夠用戶本身設置;label爲是否在Graphviz文件中添加二叉樹的左右子樹的標籤,用於分清哪棵是左子樹,哪棵是右子樹,能夠用用戶本身設置。
  若咱們須要實現圖3的示例二叉樹,完整的Python代碼以下:
from Binary_Tree import BTree

# 構造二叉樹, BOTTOM-UP METHOD
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()

# 利用Graphviz進行二叉樹的可視化
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:])
    # print(level_order)

    # BTree_list: 這一層全部的節點組成的列表
    # forword_level: 上一層節點的數據組成的列表
    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: # 二叉樹的層數大於1

        # 建立最後一層的節點列表
        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 = list(range(1,19))
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()

# 利用Graphviz進行二叉樹的可視化
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… 。其使用的例子以下:

相關文章
相關標籤/搜索