python常用算法(5)——树,二叉树与AVL树(一)

简介: python常用算法(5)——树,二叉树与AVL树

1,树

  树是一种非常重要的非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示。树在计算机领域中也得到了广泛应用,如在编译源程序时,可用树表示源程序的语法结构。又如在 数据库系统中,树型结构也是信息的重要组织形式之一。一切具有层次关系的问题都可以用树来描述。

  树(Tree)是元素的集合。树的定义是递归的,树是一种递归的数据结构。比如:目录结构。树是由n个结点组成的集合:如果n=0,那这就是一颗空树;如果 n>0,那么存在1个结点作为树的根节点,其他结点可以分为m个集合,每个集合本身又是一棵树。

  • 1,树的根结点没有前驱结点,除根结点之外所有结点有且只有一个前驱结点。
  • 2,树中所有结点可以有零个或者多个后继结点。
1.1  树的术语
  1. 根节点:树的第一个节点,没有父节点的节点
  2. 叶子节点:不带分叉的节点
  3. 树的深度(高度):就是分了多少层
  4. 孩子节点,父节点:节点与节点之间的关系

  如下图,我们分别解释:

   1)B是K的祖先结点,K是B的子孙节点,E是K的双亲节点,K是E的孩子节点,K是L的兄弟节点。

  2)树中一个结点的子节点个数为该节点的度,树中结点最大度数为树的度。

  3)度大于0为节点结点,度等于0为叶子结点。

  4)结点层次如图,结点深度时从根结点从顶往下累加,结点高度从低往上累加,树的高度(深度)是树的最大层数。

  5)有序树:从左到右有次序,有关联。反之为无序树。

  6)两结点之间的路径是两个结点之间所经过的结点序列构成的,路径长度是路径上所经过的边的个数。

  7)森林是 m (m >=0)棵互不相交的集合。

  上面观察实际上给了我们一种严格的定义树的方法:

  • 1,树是元素的集合
  • 2,该集合可以为空,这时树中没有元素,我们称树为空树(empty tree)
  • 3,如果该集合不为空,那么该集合有一个根节点,以及0个或者多个子树。根节点与他的子树的根节点用一个边(edge)相连。
1.2  树的实现

  树的示意图已经给出了树的一种内存实现方法:每个节点存储元素和多个指向子节点的指针。然而,子节点数目的是不确定的。一个父节点可能有大量的子节点,而另一个父节点可能只有一个子节点,而树的增删节点操作会让子节点的数目发生进一步的变换。这种不确定性就可能就可能带来大量的内存相关操作,并且容易造成内存的浪费。

  一种经典的实现方法如下:

  树的内存实现:拥有同一父节点的两个结点互为兄弟节点(sibling)。上图的实现方式中,每个节点包含一个指针指向第一个子节点,并且有另一个指针指向他的下一个兄弟节点。这样,我们就可以用统一的,确定的结构来表示每个节点。

1.3  树的实例——模拟文件系统

  代码如下:

#_*_coding:utf-8_*_
 
class Node:
    def __init__(self, name, type='dir'):
        self.name = name
        self.type = type  # 'dir'  or ; 'file'
        self.children = []
        self.parent = None
        # 链式存储
 
    def __repr__(self):
        return self.name
 
 
class FileSystemTree:
    def __init__(self):
        self.root = Node("/")  # 首先我们创建一个根目录
        self.now = self.root
    def mkdir(self, name):
        # 创建一个文件目录,所以我们必须保证name是以 /结尾,如果没有,我们就加
        if name[-1] != '/':
            name += '/'
        node = Node(name)
        # 创建一个文件目录
        self.now.children.append(node)
        node.parent = self.now
    def ls(self):
        # 展示当前文件夹下的文件
        return self.now.children
    def cd(self, name):
        # 切换到指定目录  注意:支持绝对路径和相对路径
        # 相对路径是从now的路径下开始,而绝对路径是从root路径下开始找
        if name[-1] != '/':
            name += '/'
        if name == '../':
            self.now = self.now.parent
            return
        for child in self.now.children:
            if child.name == name:
                # 如果传入的目录名等于孩子的目录名,我们直接切换
                self.now = child
                return
        raise ValueError("invalid dir")
tree = FileSystemTree()
tree.mkdir('var/')
tree.mkdir('bin/')
tree.mkdir('usr/')
print(tree.ls())  # [var/, bin/, usr/]
tree.cd('bin/')
print(tree.ls())  # []
print(tree.root.children)  # [var/, bin/, usr/]

2,二叉树

2.1  二叉树的定义

  二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接。

  二叉树是一种特殊的树,它具有以下特点:

  1)至多只有两棵子树,二叉树有左右之分,次序不能颠倒,也是递归形式定义。

  2)或者为空二叉树,即 n=0

  3)或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一颗二叉树。

  4)每个节点至多有两个节点,即每个节点的度最多为2

  5)二叉树中所有节点的形态有5种:空节点,无左右子树的节点,只有左子树的节点,只有右子树的节点和具有左右子树的节点

  二叉树的定义如下:
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子
 
a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")
g = BiTreeNode("G")
 
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
 
root = e
print(root.lchild.rchild.data)
  

  二叉树的节点定义

class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子
2.2  二叉树与度为2的有序树的区别:

  1)度为2的树至少有3个结点,而二叉树可以为空。

  2)左右次数。

2.3  二叉树的存储方式

  二叉树的存储结构分为链式存储结构和顺序存储结构(列表)

二叉树的顺序存储方式

   思考:父节点和左孩子节点的编号下标有什么关系?

   0-1   1-3   2-5   3-7  4-9               i  ---->   2i+1

  父节点和右孩子节点的编号下标有有什么关系?

  0-2   1-4  2-6  3-8  4-10               i  ----->  2i+2

二叉树的链式存储

  结构采用链式存储二叉树中的数据元素,用链建立二叉树中结点之间的关系。二叉树最常用的链式存储结构是二叉链,每个节点包含三个域,分别是数据元素域 data,左孩子链域 LChild 和 右孩子链域 rChild。与单链表带头结点和不带头节点的两种情况相似,二叉链存储结构的二叉树也有带头结点和不带头节点两种。

2.4   二叉树的遍历

  那么如何遍历一颗二叉树呢?其实有两种通用的遍历树策略:

深度优先搜索(DFS)

  在这个策略中,我们采用深度作为优先级,以便从根开始一直到达某个确定的叶子,然后再返回根到达另一个分支。

  深度优先搜索策略又可以根据根节点,左孩子和右孩子的相对顺序被细分为先序遍历,中序遍历和后序遍历。

宽度优先搜索(BFS)

  我们按照高度顺序一层一层的访问整棵树,高层次的节点将会被低层次的节点先被访问到。

下图中的顶点按照访问的顺序编号,按照1-2-3-4-5 的顺序来比较不同的策略:

  下面学习二叉树的遍历方式,以下图的二叉树为例,我们分别学习前序遍历,中序遍历,后序遍历,层次遍历。

前序遍历

  思想:先访问根节点,再先序遍历左子树,然后再序遍历右子树。总的来说是 根——左——右

  前序遍历如图所示:

  代码如下:

# 二叉树的前序遍历
def pre_order(root):
    if root:
        print(root.data)  # 先打印根节点
        pre_order(root.lchild)
        pre_order(root.rchild)
# pre_order(root)
'''
E
A
C
B
D
G
F
'''
中序遍历

  思想:先中序访问左子树,再序访问根节点,最后中序遍历右子树。总的来说是 左——根——右

  中序遍历如图所示:

  代码如图所示:

# 中序遍历
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data)
        in_order(root.rchild)
# in_order(root)
'''
A
B
C
D
E
G
F
'''
后序遍历

  思想:先后续访问左子树,然后后续访问右子树,最后访问根,总的来说是  左——右——根

  后序遍历如图所示:

  代码如下:

# 后序遍历
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data)
post_order(root)
'''
B
D
C
A
F
G
E
'''
层次遍历(宽度优先遍历)

  思想:利用队列,依次将根,左子树,右子树存入队列,按照队列先进先出规则来实现层次遍历。

  代码如下:

from collections import deque
def level_order(root):
    queue = deque()
    queue.append(root)
    while len(queue) > 0:  # 只要队不空
        node = queue.popleft()
        print(node.data)
        if node.lchild:
            queue.append(node.lchild)
        if node.rchild:
            queue.append(node.rchild)
 
level_order(root)
'''
E
A
G
C
F
B
D
'''

3  几个特殊的二叉树

3.1  满二叉树

  满二叉树作为一种特殊的二叉树,它是指:除了叶子节点,所有节点都有两个孩子(左子树和右子树),并且所有叶子节点深度都一样。

  其特点有:

  • 1)叶子节点只能出现在最下面一层
  • 2)非叶子节点度一定是2
  • 3)在同样深度的二叉树中,满二叉树的节点个数最多,节点个数为:2h-1,其h为树的深度。
3.2  完全二叉树

  完全二叉树是由满二叉树引申而来,假设二叉树深度为 h,那么除了第h层外,之前的每一层(1~h-1)的节点数都达到最大,即没有空的位置,而且第K层的子节点也都集中在左子树上(顺序)。

  其具有以下特点:

  • 1)叶子节点可以出现在最后一层或倒数第二层
  • 2)最后一层的叶子节点一定集中在左部连续位置
  • 3)完全二叉树严格按层序编号(可利用数组或列表实现,满二叉树同理)
  • 4)若一个节点为叶子节点,那么编号比其大的节点均为叶子节点
3.3  二叉排序树

  一颗二叉树或者空二叉树,如:左子树上所有关键字均小于根结点的关键字,右子树上的所有结点的关键字均大于根结点的关键字,左子树和右子树各是一颗二叉排序树。

3.4  平衡二叉树

  树上任何一结点的左子树和右子树的深度只差不超过1 。

4, 二叉搜索树(BST)

4.1 二叉搜索树的定义

  二叉搜索树(Binary Search Tree),又名二叉排序树(Binary Sort Tree)。

  由于二叉树的子节点数目确定,所以可以直接采用下图方式在内存中实现。每个节点有一个左子节点(left children)和右子节点(right children)。左子节点是左子树的根节点,右子节点是右子树的根节点。

  如果我们给二叉树加一个额外的条件,就可以得到一种被称为二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大。

  二叉搜索树是一颗二叉树且满足性质:设x是二叉树的一个节点。如果 y 是 x 左子树的一个节点,那么 y.key <= x.key;如果y 是x 的右子树的一个节点,那么 y.key >= x.key。

4.2  二叉搜索树的性质
  • 1)若左子树不为空,则左子树上所有节点的值均小于或等于它的根节点的值
  • 2)若右子树不为空,则右子树上所有节点的值均大于或等于它的跟节点的值
  • 3)左右子树也分别为二叉搜索树

  二叉搜索树,注意树中元素的大小。二叉搜索树可以方便的实现搜索算法。在搜索元素 x 的时候,我们可以将 x 和根节点比较:

  • 1,如果 x 等于根节点,那么找到 x ,停止搜索(终止条件)
  • 2,如果 x 小于根节点,那么搜索左子树
  • 3,如果 x 大于根节点,那么搜索右子树

  二叉搜索树所需要进行的操作次数最多与树的深度相等。n个结点的二叉搜索树的深度最多为 n ,最少为 log(n).

4.3  二叉搜索树的插入操作

  从根节点开始,若插入的值比根节点的值小,则将其插入根节点的左子树;若比根节点的值大,则将其插入根节点的右子树。该操作可以使用递归进行实现。

  代码如下:

class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子
        self.parent = None
 
 
class BST:
    def __init__(self, li=None):
        self.root = None
        if li:
            for val in li:
                self.insert_no_rec(val)
 
    def insert(self, node, val):
        if not node:
            node = BiTreeNode(val)
        elif val < node.data:
            node.lchild = self.insert(node.lchild, val)
            node.lchild.parent = node
        elif val > node.data:
            node.rchild = self.insert(node.rchild, val)
            node.rchild.parent = node
        return node
    def insert_no_rec(self, val):
        p = self.root
        if not p:  # 空树
            self.root = BiTreeNode(val)
            return
        while True:
            if val < p.data:
                if p.lchild:
                    p = p.lchild
                else:  # 左孩子不存在
                    p.lchild = BiTreeNode(val)
                    p.lchild.parent = p
                    return
            elif val > p.data:
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = BiTreeNode(val)
                    p.rchild.parent = p
                    return
            else:
                return

python常用算法(5)——树,二叉树与AVL树(二)https://developer.aliyun.com/article/1542699

相关文章
|
4天前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
2天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
垃圾识别分类系统。本系统采用Python作为主要编程语言,通过收集了5种常见的垃圾数据集('塑料', '玻璃', '纸张', '纸板', '金属'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对图像数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。然后使用Django搭建Web网页端可视化操作界面,实现用户在网页端上传一张垃圾图片识别其名称。
15 0
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
|
2天前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
11 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
2天前
|
机器学习/深度学习 人工智能 算法
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
蔬菜识别系统,本系统使用Python作为主要编程语言,通过收集了8种常见的蔬菜图像数据集('土豆', '大白菜', '大葱', '莲藕', '菠菜', '西红柿', '韭菜', '黄瓜'),然后基于TensorFlow搭建卷积神经网络算法模型,通过多轮迭代训练最后得到一个识别精度较高的模型文件。在使用Django开发web网页端操作界面,实现用户上传一张蔬菜图片识别其名称。
9 0
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
|
7天前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
23 5
|
6天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
23 2
|
7天前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
13 2
|
10天前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
20 0
|
15天前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
18 3
|
22天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。