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

相关文章
|
3天前
|
机器学习/深度学习 人工智能 算法
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
文本分类识别系统。本系统使用Python作为主要开发语言,首先收集了10种中文文本数据集("体育类", "财经类", "房产类", "家居类", "教育类", "科技类", "时尚类", "时政类", "游戏类", "娱乐类"),然后基于TensorFlow搭建CNN卷积神经网络算法模型。通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型,并保存为本地的h5格式。然后使用Django开发Web网页端操作界面,实现用户上传一段文本识别其所属的类别。
16 1
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
|
4天前
|
存储 大数据 索引
解锁Python隐藏技能:构建高效后缀树Suffix Tree,处理大数据游刃有余!
通过构建高效的后缀树,Python程序在处理大规模字符串数据时能够游刃有余,显著提升性能和效率。无论是学术研究还是工业应用,Suffix Tree都是不可或缺的强大工具。
19 6
|
1天前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
8 2
|
4天前
|
算法 Python
震惊!Python 算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
在 Python 算法设计中,理解并巧妙运用时间复杂度和空间复杂度的知识,是实现高效、优雅代码的必经之路。通过不断地实践和优化,我们能够在这两个因素之间找到最佳的平衡点,创造出性能卓越的程序。
17 4
|
5天前
|
存储 算法 数据挖掘
高效文本处理新纪元:Python后缀树Suffix Tree,让数据分析更智能!
在大数据时代,高效处理和分析文本信息成为关键挑战。后缀树作为一种高性能的数据结构,通过压缩存储字符串的所有后缀,实现了高效的字符串搜索、最长公共前缀查询等功能,成为文本处理的强大工具。本文探讨Python中后缀树的应用,展示其在文本搜索、重复内容检测、最长公共子串查找、文本压缩及智能推荐系统的潜力,引领数据分析迈入新纪元。虽然Python标准库未直接提供后缀树,但通过第三方库或自定义实现,可轻松利用其强大功能。掌握后缀树,即掌握开启文本数据宝藏的钥匙。
23 5
|
1天前
|
存储 开发者 Python
从理论到实践:Python中Trie树与Suffix Tree的完美结合,开启编程新篇章!
在编程领域,高效的数据结构对于解决问题至关重要。本文通过一个案例分析,介绍如何在Python中结合使用Trie树(前缀树)和Suffix Tree(后缀树)。案例聚焦于开发具备高效拼写检查和文本相似度检测功能的文本编辑器。首先,通过构建Trie树快速检查单词是否存在;接着,利用Suffix Tree检测文本相似度。尽管Python标准库未直接提供Suffix Tree,但可通过第三方库或自定义实现。本文展示了高级数据结构在实际应用中的强大功能,并强调了理论与实践相结合的重要性。
9 1
|
1天前
|
存储 算法 Python
逆袭之路:掌握Python字典树Trie与后缀树,成为技术圈的耀眼新星!
在编程的征途上,每个人都渴望成为那个能够独当一面、解决复杂问题的技术高手。而掌握高级数据结构,如字典树(Trie)与后缀树(Suffix Tree),无疑是你逆袭路上的重要一步。这些数据结构不仅能够提升你的编码技能,还能让你在解决特定问题时游刃有余,从而在技术圈中脱颖而出,成为那颗耀眼的新星。
9 1
|
3天前
|
存储 算法 搜索推荐
Python进阶必备:字典树Trie与后缀树Suffix Array,效率提升的神器!
在Python编程中,掌握高效的数据结构对于提升程序性能至关重要。本文将深入探讨两种强大的字符串处理数据结构——字典树(Trie)与后缀数组(Suffix Array)。字典树,又称前缀树,适用于自动补全和拼写检查等功能。例如,在文本编辑器中实现自动补全时,字典树能够即时提供单词补全选项。后缀数组则用于存储字符串的所有后缀并按字典序排序,结合最长公共前缀(LCP)数组,可以高效解决许多字符串问题,如查找最长重复子串等。通过实际案例,我们将展示这两种数据结构的强大功能,帮助你在Python编程中更进一步。
10 2
|
5天前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
19 4
|
3天前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
14 1