用python實現樹,字典樹,堆,圖,並查集

前言

本文內容講解幾種常見的非線性數據結構(樹,字典樹,堆,圖,並查集)的概念,功能及其實現。

樹 (tree) 是一種非常高效的非線性存儲結構。樹,自然中的樹有根,有葉子,對應在數據結構中的樹就是根節點、葉子節點。同一層的節點叫兄弟節點,鄰近不同層的叫父子節點。

樹又分門別類,分爲二叉樹、滿二叉樹和完全二叉樹

二叉樹:每個節點都至多有二個子節點的樹;

滿二叉樹:在二叉樹的基礎上,除了葉子節點外,每個節點都有左右兩個子節點,這種二叉樹叫做滿二叉樹;

完全二叉樹:對於一顆二叉樹,假設其深度爲d(d>1)。除第d層外的所有節點構成滿二叉樹,且第d層所有節點從左向右連續地緊密排列,這樣的二叉樹被稱爲完全二叉樹;

  • 樹的深度:一棵樹中節點的最大深度就是樹的深度,也稱爲高度

  • 節點深度:對任意節點x,x節點的深度表示爲根節點到x節點的路徑長度。所以根節點深度爲0,第二層節點深度爲1,以此類推

python實現二叉樹

class Node():
    def __init__(self,data):
        self.data = data    #相應的元素數據
        self.left = None    #左子節點
        self.right = None   #右級節點
    
    #當使用print輸出對象的時候,只要自己定義了__str__(self)方法,那麼就會打印在這個方法中return的數據
    #__str__方法需要返回一個字符串,當做這個對象的描寫
    def __str__(self):
        return str(self.data)

#二叉樹的實現邏輯
class Tree():
    def __init__(self):
        #根節點定義爲 root 永不刪除
        self.root = Node("root")

    #添加節點到二叉樹
    def add(self,data):
        node = Node(data)
        #如果根節點不存在,插入的節點則爲根節點
        if self.root is None:
            self.root = node
        else:
            #先在 q 列表中,添加二叉樹的根節點
            q = [self.root]
            while True:
                #先把root節點取出來(此時q列表爲空了),判斷左右子節點爲不爲空,若有一個爲空則插入,插入成功直接return結束循環
                #若都不爲空,則把root的左右子節點依次添加到q中,再按同樣的邏輯依次從root的左右子節點找位置插入,插入成功直接return結束循環
                pop_node = q.pop(0)
                #左子樹爲空則將點添加到左子樹
                if pop_node.left == None:
                    pop_node.left = node
                    return 
                #右子樹爲空則將點添加到右子樹
                elif pop_node.right == None:
                    pop_node.right = node
                    return
                else:
                    q.append(pop_node.left)
                    q.append(pop_node.right)

    #找到給定data的父節點
    def get_parent(self,data):
        #如果給定data爲root的data,返回None
        if self.root.data == data:
            return None
        else:
            #依次輪詢每一個節點的左右子節點,若這個節點的左右子節點的數據爲給定的data,則返回這個節點
            #否則依次添加其左右子節點進temp列表,不斷輪詢查找,若輪詢完所有的節點依舊沒有找到符合的節點,返回None
            temp = [self.root]
            while temp:
                pop_node = temp.pop(0)
                if pop_node.right is not None and pop_node.right.data == data:
                    return pop_node
                if pop_node.left is not None and pop_node.left.data == data:
                    return pop_node

                if pop_node.left is not None:
                    temp.append(pop_node.left)
                if pop_node.right is not None:
                    temp.append(pop_node.right)
            return None
    
    #刪除給定data的節點
    """
    刪除節點的情況有以下幾種:
    1、刪除的節點爲root,刪除失敗;
    2、刪除的節點無子節點,直接刪除
    3、刪除的節點只有一個子節點,其子節點接替刪除節點的位置
    4、刪除的節點有2個子節點。這裏面又要判斷刪除的節點的右子節點有無左子節點:
    > 如無,將右子節點替換刪除的節點即可;如有,找到最左子節點,去替換要刪除的節點,
    > 還要注意最左子節點可能也有右子節點,這個節點也要接到最左子節點的父節點上去
    """
    def delete(self,data):
        if self.root is None:
            return False

        #找出給定data的節點的父節點
        parent = self.get_parent(data)
        if parent:
            #確定待刪除節點
            del_node = parent.left if parent.left.data == data else parent.right
            #若待刪除節點的左子樹爲空
            if del_node.left is None:
                #如果待刪除節點是其父節點的左孩子
                if parent.left.data == data:
                    parent.left = del_node.right
                else:
                    parent.right = del_node.right
                del del_node
                return True
            #若待刪除節點的右子樹爲空
            elif del_node.right is None:
                # 如果待刪除節點是父節點的左孩子
                if parent.left.data == data:
                    parent.left = del_node.left
                # 如果待刪除節點是父節點的右孩子
                else:
                    parent.right = del_node.left
                # 刪除變量 del_node
                del del_node
                return True
            #當左右子樹都不爲空
            else:
                tmp_pre = del_node
                #待刪除節點的右子樹
                tmp_next = del_node.right
                #尋找待刪除節點右子樹中的最左葉子節點並完成替代
                #如果待刪除節點的右子樹的左子樹爲空
                if tmp_next.left is None:
                    tmp_pre.right = tmp_next.right
                    tmp_next.left = del_node.left
                    tmp_next.right = del_node.right
                
                else:
                    #while循環把tmp_next指向右子樹的最左葉子節點
                    while tmp_next.left:
                        tmp_pre = tmp_next
                        tmp_next = tmp_next.left
                    tmp_pre.left = tmp_next.right
                    tmp_next.left = del_node.left
                    tmp_next.right = del_node.right
                
                if parent.left.data == data:
                    parent.left == tmp_next

                else:
                    parent.right = tmp_next
                del del_node
                return True
        else:
            return False
    
    #二叉樹的中序遍歷    
    def inorder(self,node):
        if node is None:
            return []
        result = [node.data]
        left_data = self.inorder(node.left)
        right_data = self.inorder(node.right)
        return left_data + result + right_data

    #二叉樹的先序遍歷
    def preorder(self,node):
        if node is None:
            return []
        result = [node.data]
        left_data = self.preorder(node.left)
        right_data = self.preorder(node.right)
        return result + left_data + right_data

    #二叉樹的後序遍歷
    def postorder(self,node):
        if node is None:
            return []
        result = [node.data]
        left_data = self.preorder(node.left)
        right_data = self.preorder(node.right)
        return left_data + right_data + result


#作業:初始化創建 Node,Tree類;將1-10添加到二叉樹裏面(使用add());將三種遍歷過程打印出來
if __name__ == "__main__":
    t = Tree()
    for i in range(1,11):
        t.add(i)
    print("中序遍歷",t.inorder(t.root))
    print("先序遍歷",t.preorder(t.root))
    print("後序遍歷",t.postorder(t.root))

字典樹

字典樹介紹可見:看動畫輕鬆理解「Trie樹」

python實現字典樹

class TrieNode:
    #初始化字典樹節點,每個節點都是字典,其鍵爲某字符,其值爲對應的子節點
    def __init__(self):
        self.nodes = dict()
        #是否葉子節點
        self.is_leaf = False

    #插入單詞
    def insert(self,word:str):
        curr = self
        for char in word:
            if char not in curr.nodes:
                curr.nodes[char] = TrieNode()
            curr = curr.nodes[char]
        curr.is_leaf = True

    #插入多個單詞
    def insert_many(self,words:[str]):
        for word in words:
            self.insert(word)

    #查詢單詞是否在字典樹中
    def search(self,word:str):
        curr = self
        for char in word:
            if char not in curr.nodes:
                return False
            curr = curr.nodes[char]
        return curr.is_leaf

堆有兩點需要了解

  • 堆一般採用完全二叉樹;
  • 堆中的每一個節點都大於其左右子節點(大頂堆),或者堆中每一個節點都小於其左右子節點(小頂堆)。

python實現堆

import math

class Heap:
    def __init__(self):
        #初始化一個空堆,使用數組來初始化堆元素
        self.data_list = []

    #得到指定序號的節點的父節點的序號
    def get_parent_index(self,index):
        if index == 0 or index > len(self.data_list) - 1:
            return None
        else:
            return (index-1) >> 1

     #交換兩個序號的數據
    def swap(self,index_a,index_b):
        self.data_list[index_a],self.data_list[index_b] = self.data_list[index_b],self.data_list[index_a]

    #插入元素
    def insert(self,data):
        # 先把元素放在最後,然後從後往前依次堆化
        # 這裏以大頂堆爲例,如果插入元素比父節點大,則交換,直到最後
        self.data_list.append(data)
        index = len(self.data_list)-1
        parent = self.get_parent_index(index)
        while parent is not None and data > self.data_list[parent]:
            #交換操作
            self.swap(index,parent)
            index = parent
            parent = self.get_parent_index(parent)

    #刪除堆頂元素
    def removeMax(self):
        remove_data = self.data_list[0]
        self.data_list[0] = self.data_list[-1]
        del self.data_list[-1]
        #堆化
        self.heapify(0)
        return remove_data

    def heapify(self,index):
        #從上往下堆化
        total_index = len(self.data_list)
        while True:
            maxvalue_index = index
            if 2*index+1 < total_index and self.data_list[2*index+1] > self.data_list[maxvalue_index]:
                maxvalue_index = 2*index+1
            if 2*index+2 < total_index and self.data_list[2*index+2] > self.data_list[maxvalue_index]:
                maxvalue_index = 2*index+2
            if maxvalue_index == index:
                break
            self.swap(index,maxvalue_index)
            index = maxvalue_index

if __name__ == "__main__":
    myheap =  Heap()
    for i in range(10):
        myheap.insert(i+1)
    print("建堆:",myheap.data_list)
    print("刪除堆頂元素:",[myheap.removeMax() for i in range(10)])

圖的鄰接表實現

圖的點集可以用列表實現,圖的邊集可以用字典實現

python以鄰接表實現圖(無向圖)

#無向圖的鄰接表表現形式

class Graph(object):
    def __init__(self):
        self.nodes = []
        self.edge = {}
    
    def insert(self,a,b):
        #如果a不在圖的點集裏,則添加a
        if not(a in self.nodes):
            self.nodes.append(a)
            self.edge[a] = list()
        if not(b in self.nodes):
            self.nodes.append(b)
            self.edge[b] = list()
        #a連接b
        self.edge[a].append(b)
        #b連接a
        self.edge[b].append(a)

    #返回與a連接的點
    def succ(self,a):
        return self.edge[a]
    
    #返回圖的點集
    def show_nodes(self):
        return self.nodes
    
    def show_edge(self):
        print(self.edge)

if __name__ == "__main__":
    graph = Graph()
    graph.insert('0', '1')
    graph.insert('0', '2')
    graph.insert('0', '3')
    graph.insert('1', '3')
    graph.insert('2', '3')
    graph.show_edge()
圖的鄰接矩陣實現

python以鄰接矩陣實現圖(無向圖)

class Graph:
    def __init__(self,vertex):
        #圖有幾個節點
        self.vertex = vertex
        #初始化圖的鄰接矩陣,全爲0
        self.graph = [[0] * vertex for i in range(vertex)]

    def insert(self,u,v):
        #對存在連接關係的兩個點,在矩陣裏置1代表存在連接關係,沒有連接關係則置0
        self.graph[u-1][v-1] = 1
        self.graph[v-1][u-1] = 1

    #顯示圖的鄰接矩陣
    def show(self):
        for i in self.graph:
            for j in i:
                print(j,end = " ")
            print(" ")

if __name__ == "__main__":
    graph = Graph(5)
    graph.insert(1,4)
    graph.insert(4,2)
    graph.insert(4,5)
    graph.insert(2,5)
    graph.insert(5,3)
    graph.show()

圖的深度優先遍歷

圖的深度優先遍歷算法的主要思想是:

  • 首先以一個未被訪問過的頂點作爲起始頂點,沿當前頂點的邊選擇一條支路一直往下探尋未訪問過的頂點;

  • 當支路一頭扎到底,沒有未訪問過的頂點時,則回退上一個頂點,繼續往下探尋,簡而言之就是一頭扎到底,走到盡頭時就回退,然後再繼續一頭扎到底,循環往復;

深度優先遍歷算法簡而言之就是一頭扎到底,走到盡頭時就回退,然後再繼續一頭扎到底,循環往復。

python實現圖的深度優先遍歷算法

#圖的深度優先遍歷算法實現

def dfs(G,s,S=None,res=None):
    if S is None:
        # 儲存已經訪問節點
        S=set()
    if res is None:
        # 存儲遍歷順序
        res=[]
    res.append(s)
    S.add(s)
    for u in G[s]:
        if u in S:
            continue
        S.add(u)
        dfs(G,u,S,res)

    return res

G = {'0': ['1', '2'],
     '1': ['2', '3'],
     '2': ['3', '5'],
     '3': ['4'],
     '4': [],
     '5': []}

print(dfs(G, '0'))
圖的廣度優先遍歷

廣度優先搜索(BFS)是樹的按層次遍歷的推廣,它的基本思想是:首先訪問初始點 vi,並將其標記爲已訪問過,接着訪問 vi 的所有未被訪問過的鄰接點 vi1,vi2,…, vin,並均標記已訪問過,然後再按照 vi1,vi2,…, vin 的次序,訪問每一個頂點的所有未被訪問過的鄰接點,並均標記爲已訪問過,依次類推,直到圖中所有和初始點 vi 有路徑相通的頂點都被訪問過爲止。

python實現圖的廣度優先遍歷算法

#圖的廣度優先遍歷算法實現

def bfs(graph,start):
    # explored:已經遍歷的節點列表,queue:尋找待遍歷的節點隊列
    explored = []
    queue = [start]
    explored.append(start)
    while queue:
        #將要遍歷v節點
        v = queue.pop(0)
        for w in graph[v]:
            if w not in explored:
                explored.append(w)
                queue.append(w)

    return explored

G = {'0': ['1', '2'],
     '1': ['2', '3'],
     '2': ['3', '5'],
     '3': ['4'],
     '4': [],
     '5': []}

print(bfs(G,"0"))

並查集

python實現並查集

#用python實現一個並查集,思路見:https://segmentfault.com/a/1190000013805875

class Union_find:
    #並查集:類似於森林
    def __init__(self,data_list):
        self.father_dict = {}
        self.set_size = {}
        for node in data_list:
            self.father_dict[node] = node
            self.set_size[node] = 1

    #尋找父節點(最終父節點)
    def find(self,node):
        father = self.father_dict[node]
        if node != father:
            father = self.find(father)
        self.father_dict[node] = father
        return father

    #判斷兩個節點是不是同一個集合裏的,只需判斷兩個節點的父節點是不是同一個
    def is_same_set(self,node_a,node_b):
        return self.find(node_a) == self.find(node_b)

    #將兩個集合合併在一起
    def union(self,node_a,node_b):
        if node_a is None or node_b is None:
            return
        a_head = self.find(node_a)
        b_head = self.find(node_b)
        if a_head != b_head:
            a_set_size = self.set_size[a_head]
            b_set_size = self.set_size[b_head]
            #將小集合合併在大的集合裏
            if a_set_size >= b_set_size:
                self.father_dict[b_head] = a_head
                self.set_size[a_head] = a_set_size + b_set_size
            else:
                self.father_dict[a_head] = b_set_size
                self.set_size[b_head] = a_set_size + b_set_size

if __name__ == "__main__":
    a = [1,2,3,4,5]
    union_a = Union_find(a)
    union_a.union(1,2)
    union_a.union(3,5)
    union_a.union(3,1)
    print(union_a.is_same_set(2,5))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章