用python实现树,字典树,堆,图,并查集

本文深入解析树、字典树、堆、图及并查集等非线性数据结构,涵盖概念、功能与Python实现,包括二叉树、完全二叉树、Trie树、大顶堆、图的邻接表与矩阵表示、深度与广度优先遍历及并查集操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文内容讲解几种常见的非线性数据结构(树,字典树,堆,图,并查集)的概念,功能及其实现。

树 (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))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值