用python講解數據結構之樹的遍歷

樹的結構

樹(tree)是一種抽象數據類型或是實現這種抽象數據類型的數據結構,用來模擬具備樹狀結構性質的數據集合
它具備如下的特色:
①每一個節點有零個或多個子節點;
②沒有父節點的節點稱爲根節點;
③每個非根節點有且只有一個父節點;
④除了根節點外,每一個子節點能夠分爲多個不相交的子樹;
node

樹的分類

二叉樹

二叉樹:每一個節點最多含有兩個子樹的樹稱爲二叉樹。
python

二叉樹中一些專業術語:算法

  • 父節點:A節點就是B節點的父節點,B節點是A節點的子節點
  • 兄弟節點:B、C這兩個節點的父節點是同一個節點,因此他們互稱爲兄弟節點
  • 根節點:A節點沒有父節點,咱們把沒有父節點的節點叫作根節點
  • 葉子節點:圖中的H、I、J、K、L節點沒有子節點,咱們把沒有子節點的節點叫作葉子節點
  • 節點的高度:節點到葉子結點的最長路徑,好比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數量巨大而影響性能。因此出現了紅黑樹。一種二叉查找樹,但在每一個節點增長一個存儲位表示節點的顏色,能夠是紅或黑(非紅即黑)

定義:

  1. 每一個節點非紅即黑;
  2. 根節點是黑的;
  3. 每一個葉節點(葉節點即樹尾端NULL指針或NULL節點)都是黑的;
  4. 如圖所示,若是一個節點是紅的,那麼它的兩兒子都是黑的;
  5. 對於任意節點而言,其到葉子點樹NULL指針的每條路徑都包含相同數目的黑節點;
  6. 每條路徑都包含相同的黑節點;

紅黑樹有兩個重要性質
一、紅節點的孩子節點不能是紅節點;
二、從根到葉子節點的任意一條路徑上的黑節點數目同樣多。
這兩條性質確保該樹的高度爲logN,因此是平衡樹。

優點
紅黑樹的查詢性能略微遜色於AVL樹,由於他比avl樹會稍微不平衡最多一層,也就是說紅黑樹的查詢性能只比相同內容的avl樹最多多一次比較,可是,紅黑樹在插入和刪除上完爆avl樹,avl樹每次插入刪除會進行大量的平衡度計算,而紅黑樹爲了維持紅黑性質所作的紅黑變換和旋轉的開銷,相較於avl樹爲了維持平衡的開銷要小得多

使用場景

  1. 普遍用於C ++的STL中,地圖和集都是用紅黑樹實現的;
  2. 着名的Linux的的進程調度徹底公平調度程序,用紅黑樹管理進程控制塊,進程的虛擬內存區域都存儲在一顆紅黑樹上,每一個虛擬地址區域都對應紅黑樹的一個節點,左指針指向相鄰的地址虛擬存儲區域,右指針指向相鄰的高地址虛擬地址空間;
  3. IO多路複用的epoll的的的實現採用紅黑樹組織管理的的的sockfd,以支持快速的增刪改查;
  4. Nginx的的的中用紅黑樹管理定時器,由於紅黑樹是有序的,能夠很快的獲得距離當前最小的定時器;
  5. Java的的的中TreeMap中的中的實現;

B樹

定義
B樹是爲實現高效的磁盤存取而設計的多叉平衡搜索樹。(B樹和B-tree這兩個是同一種樹)

產生緣由
B樹是一種查找樹,咱們知道,這一類樹(好比二叉查找樹,紅黑樹等等)最初生成的目的都是爲了解決某種系統中,查找效率低的問題。
B樹也是如此,它最初啓發於二叉查找樹,二叉查找樹的特色是每一個非葉節點都只有兩個孩子節點。然而這種作法會致使當數據量很是大時,二叉查找樹的深度過深,搜索算法自根節點向下搜索時,須要訪問的節點也就變的至關多。
若是這些節點存儲在外存儲器中,每訪問一個節點,至關於就是進行了一次I/O操做,隨着樹高度的增長,頻繁的I/O操做必定會下降查詢的效率。

定義:
B樹是一種平衡的多分樹,一般咱們說m階的B樹,它必須知足以下條件:

  1. 每一個節點最多隻有m個子節點。
  2. 每一個非葉子節點(除了根)具備至少⌈ m/2⌉子節點。
  3. 若是根不是葉節點,則根至少有兩個子節點。
  4. 具備k個子節點的非葉節點包含k -1個鍵。
  5. 全部葉子都出如今同一水平,沒有任何信息(高度一致)。

特色:

  1. 關鍵字集合分佈在整棵樹中;
  2. 多路,非二叉樹
  3. 每一個節點既保存索引,又保存數據
  4. 搜索時至關於二分查找

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)

樹的遍歷

樹的遍歷方式有不少種,能夠分爲五類:

  1. 前序遍歷
  2. 中序遍歷
  3. 後序遍歷
  4. 層次遍歷
  5. 子樹遍歷

實現遍歷的方式中有能夠分爲遞歸和迭代

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)

相關文章
相關標籤/搜索