Python数据结构学习笔记——树和图

简介: Python数据结构学习笔记——树和图

一、树的概念


树是一种数据结构,树由结点及连接结点的边组成,每个树有且只有一个根结点,除了根结点以外,其它每个结点都与其有唯一的父结点相连,其中根结点到其它每个结点也有且只有一条路径。

1、二叉树

若树中每个节点最多有两个子结点,则称为二叉树,即每个结点的子结点不超过两个,由根结点分叉的两个结点中,左边称为左子树,右边称为右子树,如下图:

1667133497305.jpg

2、满二叉树

若一个深度为k的树,具有2k-1个结点,则称该树为满二叉树,如下图该树的深度为k=3,其结点数为2k-1=23-1=7个:

1667133506507.jpg

而如下图这两个树都不是满二叉树:

1667133517567.jpg

3、完全二叉树

若一个深度为k的树,其结点为n个,对树中的结点按从上到下、从左到右的顺序进行编号,当每一个结点都与同样深度为k的满二叉树中的结点一一对应时,则称该树为完全二叉树。


完全二叉树中,从根节点至倒数第二层满足满二叉树,其最后一层的结点可以不完全填满,可以说若一棵二叉树是满二叉树时,则它一定是完全二叉树,如下图这两个树都是完全二叉树,但它们不是满二叉树:

1667133529845.jpg

4、完满二叉树

无子结点的结点称为叶子结点,若一个树的非叶子结点的结点数都为2,即都有两个子结点时,则称这个树为完满二叉树,如下图:

1667133540597.jpg


二、二叉树的实现


二叉树的python代码实现有两种方法,第一种称作“列表的列表”的形式,也就是列表的反复嵌套,第二种称作“结点与引用”,通过设置一个类。


(一)列表的列表


1、二叉树的结点实现和输出

在Python中,我们可以通过“列表的列表”这种方式来实现树的python代码,即列表的多层嵌套,例如下面这个树的实现python代码:

Tree = ["1", ["2", ["4", [], []], ["5", [], []]], ["3", [], []]]


1667133556072.jpg

我们可以通过列表的索引和切片来输出树中的结点,如下代码:

Tree = ["1", ["2", ["4", [], []], ["5", [], []]], ["3", [], []]]
print(Tree[0])  # 输出根结点
print(Tree[1])  # 输出该树的左子树
print(Tree[2])  # 输出该树的右子树
print(Tree[1][0])
print(Tree[1][1])
print(Tree[1][1][0])
print(Tree[1][2][0])
print(Tree[1][1][1])
print(Tree[2:])


运行结果如下:

1667133587142.jpg

2、二叉树的创建

二叉树的创建通过创建一个列表,由于二叉树由根结点和左子树和右子树组成,一开始将左右子树定义为空列表,即根结点为r,左右子树此时都为[]。

定义一个函数BinaryTree()用于创建一个二叉树,如下代码:

def BinaryTree(r):
    return [r, [], []]  # 返回二叉树


3、左右子树的结点添加

我们要向已经创建好的二叉树中添加左右子树(左右子树中的结点),所以要先获取当前的左或右子树对应的列表(可能为空),然后将新的左或右子树添加进去,从而实现在二叉树的任何位置实现结点添加。

定义两个函数insertLeft()与insertRight()分别用于左右子树的结点添加,如下代码:

def insertLeft(root, newBranch):  # 新建一个左子树,将其作为当前结点的左子结点
    t = root.pop(1)  # pop()函数用于删除列表中指定元素
    if len(t) > 1:
        root.insert(1, [newBranch, t, []])  # insert()函数用于将元素插入到列表的指定位置
    else:
        root.insert(1, [newBranch, [], []])
    return root
def insertRight(root, newBranch):  # 新建一个右子树,将其作为当前结点的右子结点
    t = root.pop(2)  # pop()函数用于删除列表中指定元素
    if len(t) > 1:
        root.insert(2, [newBranch, [], t])  # insert()函数用于将元素插入到列表的指定位置
    else:
        root.insert(2, [newBranch, [], []])
    return root


4、另外定义几个函数用于返回二叉树以及结点存储的对象,getRootVal()函数给出参数root从而返回当前结点存储的对象;setRootVal()函数给出参数root和newVal在当前结点存储参数newVal的对象;getLeftChild()和getRightChild()函数给出参数root用于返回当前结点的左/右结点所对应的子二叉树,如下代码:

def getRootVal(root):  # 返回当前结点存储的对象
    return root[0]
def setRootVal(root, newVal):  # 在当前结点中存储参数newVal的对象
    root[0] = newVal
def getLeftChild(root):  # 返回当前结点的左子结点所对应的子二叉树
    return root[1]
def getRightChild(root):  # 返回当前结点的右子结点所对应的子二叉树
    return root[2]


(二)结点与引用


另外一种对树的定义方法是通过定义一个类,类中含有根结点以及左右子树的属性,如下图:

1667133692200.jpg

1、BinaryTree类的创建

通过创建一个BinaryTree类作为树的初始化,其中包括根结点rootObj,以及空的左右子树,代码如下:

# 创建一个树BinaryTree类
class BinaryTree:
    # 定义一个构造方法用于创建新的结点,参数rootObj作为此时的根结点
    def __init__(self, rootObj):
        self.key = rootObj  # 根结点
        self.leftChild = None  # 此时左子树为空
        self.rightChild = None  # 此时右子树为空


2、左右子树的结点添加

左右子树的结点添加要考虑两种情况,一是向无子结点的结点下添加,二是向已有子结点的结点下添加,如下以左结点添加为例:

1667133706710.jpg

在类下定义两个方法,insertLeft()和insertRight()分别用于添加左/右结点,都含有一个参数newNode用于接收结点,其代码实现如下:

# 插入左结点
    def insertLeft(self, newNode):
        if self.leftChild is None:  # 无左结点时
            self.leftChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)  # 实例化对象,创建新的结点
            t.left = self.leftChild
            self.leftChild = t
    # 插入右结点
    def insertRight(self, newNode):
        if self.rightChild is None:  # 无右结点时
            self.rightChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)  # 实例化对象,创建新的结点
            t.left = self.rightChild
            self.rightChild = t


3、另外定义几个函数用于返回二叉树以及结点存储的对象,getRootVal()函数返回当前结点存储的对象;setRootVal()函数给出参数obj用于在当前结点存储对象;getLeftChild()和getRightChild()函数给出参数用于返回当前结点的左/右结点所对应的子二叉树,如下代码:

def getRightChild(self):
        return self.rightChild
    def getLeftChild(self):
        return self.leftChild
    def setRootVal(self, obj):
        self.key = obj
    def getRootVal(self):
        return self.key

程序的完整代码如下:

# 创建一个树BinaryTree类
class BinaryTree:
    # 定义一个构造方法用于创建新的结点,参数rootObj作为此时的根结点
    def __init__(self, rootObj):
        self.key = rootObj  # 根结点
        self.leftChild = None  # 此时左子树为空
        self.rightChild = None  # 此时右子树为空
    # 插入左结点
    def insertLeft(self, newNode):
        if self.leftChild is None:  # 无左结点时
            self.leftChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)  # 实例化对象,创建新的结点
            t.left = self.leftChild
            self.leftChild = t
    # 插入右结点
    def insertRight(self, newNode):
        if self.rightChild is None:  # 无右结点时
            self.rightChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)  # 实例化对象,创建新的结点
            t.left = self.rightChild
            self.rightChild = t
    def getRightChild(self):
        return self.rightChild
    def getLeftChild(self):
        return self.leftChild
    def setRootVal(self, obj):
        self.key = obj
    def getRootVal(self):
        return self.key


如下是一个二叉树,通过程序实现:

1667133719868.jpg

代码如下:

tree = BinaryTree(1)
print(tree.getRootVal())
print(tree.getLeftChild())
tree.insertLeft(2)
tree.insertRight(3)
print(tree.getLeftChild().getRootVal())
print(tree.getRightChild().getRootVal())
tree.insertLeft(4)
tree.insertRight(5)
print(tree.getLeftChild().getRootVal())
print(tree.getRightChild().getRootVal())
tree.insertLeft(6)
print(tree.getLeftChild().getRootVal())


运行结果如下:

1667133744262.jpg


三、图的概念


图是一种数据结构,前面所讲的树其实就是一种特殊的图,图的两个顶点之间通过一条边来使其联系,这里的边可以是单向或双向,若一个图中所有的边都为单向,则称整个图为有向图,如下(其中边上的数字代表权重):

1667133756816.jpg

1、图的定义和权重

图定义为G =(V,E),其中V是一个顶点集合,E是一个边集合,每一条边都是一个二元组(v,w),且v,w∈V,可以向边的二元组中再添加一个元素,用于表示权重,即从图上一个顶点到另一个顶点所需的成本,若一个有向图带有权重,则称为带权有向图。

例如,上图的带权有向图可以通过两个集合来描述该图:

V = {a, b, c, d, e, f}
E = {(a, f, 1), (f, e, 2), (e, d, 2), (b, a, 1), (b, c, 3), (c, d, 5), (c, g, 4), (g, a, 2)}

1667133773926.jpg


2、路径

路径是由边连接的顶点组成的序列,不带权重的图的路径长度是路径上的边数,带权重的图的路径长度是路径上边的权重之和,例如上图中,从c到a的路径是顶点序列(c,g,a),其相对应的边是{(c, g, 4), (g, a, 2)},如下:

1667133785830.jpg

3、环

环是有向图中的一条起点和终点为同一个顶点的路径,比如下图的路径(a,b,c,d,e,f,a)就是一个环。

1667133795544.jpg

若一个图没有环,则称为无环图,没有环的有向图称为有向无环图,也称为DAG,例如前面的这个图就是一个DAG:

1667133805287.jpg


四、图的实现


图的实现有两种常用的方法实现,分别是邻接矩阵和邻接表,邻接矩阵适合有很多边的图,而邻接表适合稀疏连接的图。


(一)邻接矩阵


通过邻接矩阵实现图,具体步骤是通过一个二维矩阵表示,其中的数字代表顶点到顶点的权重,如下:

1667133826030.jpg


(二)邻接表


具体代码如下:

class Vertex:
    def __init__(self, key):
        self.id = key
        self.connectedTo = {}
    def addNeighbor(self, nbr, weight=0):
        self.connectedTo[nbr] = weight
    def __str__(self):
        return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])
    def getConnections(self):
        return self.connectedTo.keys()
    def getId(self):
        return self.id
    def getWeight(self, nbr):
        return self.connectedTo[nbr]
class Graph:
    def __init__(self):
        self.vertList = {}
        self.numVertices = 0
    def addVertex(self, key):
        self.numVertices = self.numVertices + 1
        newVertex = Vertex(key)
        self.vertList[key] = newVertex
        return newVertex
    def getVertex(self, n):
        if n in self.vertList:
            return self.vertList[n]
        else:
            return None
    def __contains__(self, n):
        return n in self.vertList
    def addEdge(self, f, t, cost=0):
        if f not in self.vertList:
            nv = self.addVertex(f)
        if t not in self.vertList:
            nv = self.addVertex(t)
        self.vertList[f].addNeighbor(self.vertList[t], cost)
    def getVertices(self):
        return self.vertList.keys()
    def __iter__(self):
        return iter(self.vertList.values())
g = Graph()
for i in range(7):
    g.addVertex(i)
g.addEdge("a", "f", 1)
g.addEdge("f", "e", 2)
g.addEdge("e", "d", 2)
g.addEdge("b", "a", 1)
g.addEdge("b", "c", 3)
g.addEdge("c", "d", 5)
g.addEdge("c", "g", 4)
g.addEdge("g", "a", 2)
for v in g:
    for w in v.getConnections():
        print("( %s , %s )" % (v.getId(), w.getId()))


运行结果如下:

1667133855753.jpg

相关文章
|
2月前
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
55 0
|
6天前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
101 66
|
2月前
|
存储 索引 Python
Python编程数据结构的深入理解
深入理解 Python 中的数据结构是提高编程能力的重要途径。通过合理选择和使用数据结构,可以提高程序的效率和质量
149 59
|
2月前
|
存储 开发者 Python
Python 中的数据结构与其他编程语言数据结构的区别
不同编程语言都有其设计理念和应用场景,开发者需要根据具体需求和语言特点来选择合适的数据结构
|
10天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
47 20
|
2月前
|
存储 开发者 索引
Python 中常见的数据结构
这些数据结构各有特点和适用场景,在不同的编程任务中发挥着重要作用。开发者需要根据具体需求选择合适的数据结构,以提高程序的效率和性能
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
68 5
|
2月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
103 16