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

相关文章
|
6天前
|
存储 缓存 算法
Python中常用数据结构与算法的性能优化探讨
Python中常用数据结构与算法的性能优化探讨
28 3
|
3天前
|
存储 索引 Python
Python学习笔记
Python支持多变量赋值,如`a=b=c=1`和`a, b, c = 1, 2, "runoob"`。数据类型分为不可变(数字、字符串、元组)和可变(列表、字典、集合)。示例中展示了变量赋值、类型检查(`isinstance()`与`type()`的区别)以及运算操作,包括除法、乘方。字符串处理涉及索引、切片、连接和转义字符。列表、元组和集合的创建、访问和操作也进行了演示,例如列表的索引、切片、连接、重复和集合的运算。此外,还介绍了字典的使用,以及`lambda`函数和socket编程的基本概念。
3 0
|
5天前
|
机器学习/深度学习 算法 API
【机器学习】Python中的决策树算法探索
决策树作为机器学习中的一种基础且强大的算法,因其易于理解和实现、能够处理分类和回归任务的特性而广受欢迎。本文旨在深入浅出地介绍决策树算法的基本原理,并通过Python编程语言实践其应用,帮助读者掌握如何利用Python构建及优化决策树模型。本文预计分为以下几个部分:决策树基础理论、Python中实现决策树的库介绍、实战案例分析、模型评估与调优方法,以及决策树算法的局限性与未来展望。
13 0
|
6天前
|
数据处理 Python
深入理解Python的数据结构:列表与元组
深入理解Python的数据结构:列表与元组
19 1
|
6天前
|
存储 算法 搜索推荐
深度解析:Python中的高效数据结构与算法实现
深度解析:Python中的高效数据结构与算法实现
23 0
|
11天前
|
存储
数据结构(树)
数据结构(树)
21 1
|
13天前
|
存储 分布式数据库
【数据结构】树和二叉树
【数据结构】树和二叉树
|
13天前
|
索引 Python
Python数据结构——元组
Python数据结构——元组
19 0
|
13天前
|
存储 索引 Python
Python数据结构——字符串
Python数据结构——字符串
25 0
|
13天前
|
索引 Python 容器
Python数据结构——列表
Python数据结构——列表
7 0