python数据结构与算法-14_树与二叉树

树和二叉树

前面我们讲了两种使用分治和递归解决排序问题的归并排序和快速排序,堆排序先就此打住,因为涉及到树的概念,所以我们先来讲讲树。
讲完了树之后后面我们开始介绍一种有用的数据结构堆(heap), 以及借助堆来实现的堆排序,相比前两种排序算法要稍难理解和实现一些。

这里先简单讲讲树的概念。树结构是一种包括节点(nodes)和边(edges)的拥有层级关系的一种结构, 它的形式和家谱树非常类似:

在这里插入图片描述

如果你了解 linux 文件结构(tree 命令),它的结构也是一棵树。我们快速看下树涉及到的一些概念:
在这里插入图片描述

  • 根节点(root): 树的最上层的节点,任何非空的树都有一个节点
  • 路径(path): 从起始节点到终止节点经历过的边
  • 父亲(parent):除了根节点,每个节点的上一层边连接的节点就是它的父亲(节点)
  • 孩子(children): 每个节点由边指向的下一层节点
  • 兄弟(siblings): 同一个父亲并且处在同一层的节点
  • 子树(subtree): 每个节点包含它所有的后代组成的子树
  • 叶子节点(leaf node): 没有孩子的节点成为叶子节点

二叉树

了解完树的结构以后,我们来看树结构里一种简单但是却比较常用的树-二叉树。
二叉树是一种简单的树,它的每个节点最多只能包含两个孩子,以下都是一些合法的二叉树:

在这里插入图片描述
在这里插入图片描述

通过上边这幅图再来看几个二叉树相关的概念:

  • 节点深度(depth): 节点对应的 level 数字
  • 树的高度(height): 二叉树的高度就是 level 数 + 1,因为 level 从 0开始计算的
  • 树的宽度(width): 二叉树的宽度指的是包含最多节点的层级的节点数
  • 树的 size:二叉树的节点总个数。

一棵 size 为 n 的二叉树高度最多可以是 n,最小的高度是 $ \lfloor lgn \rfloor + 1 $,这里 log 以 2 为底简写为
lgn,和算法导论保持一致。这个结果你只需要用高中的累加公式就可以得到。

一些特殊的二叉树

在了解了二叉树的术语和概念之后,我们来看看一些特殊的二叉树,后续章节我们会用到:

满二叉树(full binary tree)

如果每个内部节点(非叶节点)都包含两个孩子,就成为满二叉树。下边是一些例子,它可以有多种形状:

在这里插入图片描述

完美二叉树(perfect binary tree)

当所有的叶子节点都在同一层就是完美二叉树,毫无间隙填充了 h 层。

在这里插入图片描述

完全二叉树(complete binary tree)

当一个高度为 h 的完美二叉树减少到 h-1,并且最底层的槽被毫无间隙地从左到右填充,我们就叫它完全二叉树。
下图就是完全二叉树的例子:

在这里插入图片描述

二叉树的表示

说了那么多,那么怎么表示一棵二叉树呢?其实你发现会和链表有一些相似之处,一个节点,然后节点需要保存孩子的指针,我以构造下边这个二叉树为例子:
我们先定义一个类表示节点:

在这里插入图片描述

class BinTreeNode(object):def __init__(self, data, left=None, right=None):self.data, self.left, self.right = data, left, right

当然和链表类似,root 节点是我们的入口,于是乎定义一个 二叉树:

class BinTree(object):def __init__(self, root=None):self.root = root

怎么构造上图中的二叉树呢,似乎其他课本没找到啥例子(有些例子是写了一堆嵌套节点来定义,很难搞清楚层次关系),我自己定义了一种方法,首先我们输入节点信息,仔细看下边代码,叶子节点的 left 和 right 都是 None,并且只有一个根节点 A:

node_list = [{'data': 'A', 'left': 'B', 'right': 'C', 'is_root': True},{'data': 'B', 'left': 'D', 'right': 'E', 'is_root': False},{'data': 'D', 'left': None, 'right': None, 'is_root': False},{'data': 'E', 'left': 'H', 'right': None, 'is_root': False},{'data': 'H', 'left': None, 'right': None, 'is_root': False},{'data': 'C', 'left': 'F', 'right': 'G', 'is_root': False},{'data': 'F', 'left': None, 'right': None, 'is_root': False},{'data': 'G', 'left': 'I', 'right': 'J', 'is_root': False},{'data': 'I', 'left': None, 'right': None, 'is_root': False},{'data': 'J', 'left': None, 'right': None, 'is_root': False},
]

然后我们给 BinTreeNode 定义一个 build_from 方法,当然你也可以定义一种自己的构造方法:

class BinTree(object):def __init__(self, root=None):self.root = root@classmethoddef build_from(cls, node_list):"""通过节点信息构造二叉树第一次遍历我们构造 node 节点第二次遍历我们给 root 和 孩子赋值最后我们用 root 初始化这个类并返回一个对象:param node_list: {'data': 'A', 'left': None, 'right': None, 'is_root': False}"""node_dict = {}for node_data in node_list:data = node_data['data']node_dict[data] = BinTreeNode(data)for node_data in node_list:data = node_data['data']node = node_dict[data]if node_data['is_root']:root = nodenode.left = node_dict.get(node_data['left'])node.right = node_dict.get(node_data['right'])return cls(root)
btree = BinTree.build_from(node_list)

大功告成,这样我们就构造了一棵二叉树对象。下边我们看看它的一些常用操作。

二叉树的遍历

不知道你有没有发现,二叉树其实是一种递归结构,因为单独拿出来一个 subtree 子树出来,其实它还是一棵树。那遍历它就很方便啦,我们可以直接用递归的方式来遍历它。但是当处理顺序不同的时候,树又分为三种遍历方式:

  • 先(根)序遍历: 先处理根,之后是左子树,然后是右子树
  • 中(根)序遍历: 先处理左子树,之后是根,最后是右子树
  • 后(根)序遍历: 先处理左子树,之后是右子树,最后是根

我们来看下实现,其实算是比较直白的递归函数:

class BinTreeNode(object):def __init__(self, data, left=None, right=None):self.data, self.left, self.right = data, left, rightclass BinTree(object):def __init__(self, root=None):self.root = root@classmethoddef build_from(cls, node_list):"""通过节点信息构造二叉树第一次遍历我们构造 node 节点第二次遍历我们给 root 和 孩子赋值:param node_list: {'data': 'A', 'left': None, 'right': None, 'is_root': False}"""node_dict = {}for node_data in node_list:data = node_data['data']node_dict[data] = BinTreeNode(data)for node_data in node_list:data = node_data['data']node = node_dict[data]if node_data['is_root']:root = nodenode.left = node_dict.get(node_data['left'])node.right = node_dict.get(node_data['right'])return cls(root)def preorder_trav(self, subtree):""" 先(根)序遍历:param subtree:"""if subtree is not None:print(subtree.data)    # 递归函数里先处理根self.preorder_trav(subtree.left)   # 递归处理左子树self.preorder_trav(subtree.right)    # 递归处理右子树node_list = [{'data': 'A', 'left': 'B', 'right': 'C', 'is_root': True},{'data': 'B', 'left': 'D', 'right': 'E', 'is_root': False},{'data': 'D', 'left': None, 'right': None, 'is_root': False},{'data': 'E', 'left': 'H', 'right': None, 'is_root': False},{'data': 'H', 'left': None, 'right': None, 'is_root': False},{'data': 'C', 'left': 'F', 'right': 'G', 'is_root': False},{'data': 'F', 'left': None, 'right': None, 'is_root': False},{'data': 'G', 'left': 'I', 'right': 'J', 'is_root': False},{'data': 'I', 'left': None, 'right': None, 'is_root': False},{'data': 'J', 'left': None, 'right': None, 'is_root': False},
]
btree = BinTree.build_from(node_list)
btree.preorder_trav(btree.root)    # 输出 A, B, D, E, H, C, F, G, I, J

怎么样是不是挺简单的,比较直白的递归函数。如果你不明白,视频里我们会画个图来说明。

二叉树层序遍历

除了递归的方式遍历之外,我们还可以使用层序遍历的方式。层序遍历比较直白,就是从根节点开始按照一层一层的方式遍历节点。
我们可以从根节点开始,之后把所有当前层的孩子都按照从左到右的顺序放到一个列表里,下一次遍历所有这些孩子就可以了。

    def layer_trav(self, subtree):cur_nodes = [subtree]  # current layer nodesnext_nodes = []while cur_nodes or next_nodes:for node in cur_nodes:print(node.data)if node.left:next_nodes.append(node.left)if node.right:next_nodes.append(node.right)cur_nodes = next_nodes  # 继续遍历下一层next_nodes = []

还有一种方式就是使用一个队列,之前我们知道队列是一个先进先出结构,如果我们按照一层一层的顺序从左往右把节点放到一个队列里,
也可以实现层序遍历:

    def layer_trav_use_queue(self, subtree):q = Queue()q.append(subtree)while not q.empty():cur_node = q.pop()print(cur_node.data)if cur_node.left:q.append(cur_node.left)if cur_node.right:q.append(cur_node.right)from collections import deque
class Queue(object):  # 借助内置的 deque 我们可以迅速实现一个 Queuedef __init__(self):self._items = deque()def append(self, value):return self._items.append(value)def pop(self):return self._items.popleft()def empty(self):return len(self._items) == 0

反转二叉树

之所以单拎出来说这个是因为 mac 下著名的 brew 工具作者据说是因为面试 google 白板编程没写出来反转二叉树跪了。然后人家就去了苹果 😂。其实吧和遍历操作相比也没啥太大区别,递归交换就是了:

    def reverse(self, subtree):if subtree is not None:subtree.left, subtree.right = subtree.right, subtree.leftself.reverse(subtree.left)self.reverse(subtree.right)

练习题

  • 请你完成二叉树的中序遍历和后序遍历以及单元测试
  • 树的遍历我们用了 print,请你尝试换成一个 callback,这样就能自定义处理树节点的方式了。
  • 请问树的遍历操作时间复杂度是多少?假设它的 size 是 n
  • 你能用非递归的方式来实现树的遍历吗?我们知道计算机内部使用了 stack,如果我们自己模拟如何实现?请你尝试完成
  • 只根据二叉树的中序遍历和后序遍历能否确定一棵二叉树?你可以举一个反例吗?

源码

# -*- coding: utf-8 -*-from collections import dequeclass Queue(object):  # 借助内置的 deque 我们可以迅速实现一个 Queuedef __init__(self):self._items = deque()def append(self, value):return self._items.append(value)def pop(self):return self._items.popleft()def empty(self):return len(self._items) == 0class Stack(object):def __init__(self):self._items = deque()def push(self, value):return self._items.append(value)def pop(self):return self._items.pop()def empty(self):return len(self._items) == 0class BinTreeNode(object):def __init__(self, data, left=None, right=None):self.data, self.left, self.right = data, left, rightclass BinTree(object):def __init__(self, root=None):self.root = root@classmethoddef build_from(cls, node_list):"""build_from:param node_list: {'data': 'A', 'left': None, 'right': None, 'is_root': False}"""node_dict = {}for node_data in node_list:data = node_data['data']node_dict[data] = BinTreeNode(data)for node_data in node_list:data = node_data['data']node = node_dict[data]if node_data['is_root']:root = nodenode.left = node_dict.get(node_data['left'])node.right = node_dict.get(node_data['right'])return cls(root)def preorder_trav(self, subtree):if subtree is not None:print(subtree.data)self.preorder_trav(subtree.left)self.preorder_trav(subtree.right)def preorder_trav_use_stack(self, subtree):"""递归的方式其实是计算机帮我们实现了栈结构,我们可以自己显示的用栈来实现"""s = Stack()if subtree:s.push(subtree)while not s.empty():top_node = s.pop()print(top_node.data)    # 注意这里我用了 print,你可以用 yield 产出值然后在调用的地方转成 listif top_node.right:s.push(top_node.right)if top_node.left:s.push(top_node.left)def inorder_trav(self, subtree):if subtree is not None:self.inorder_trav(subtree.left)print(subtree.data)self.inorder_trav(subtree.right)def yield_inorder(self, subtree):  # for val in yield_inorder(root): print(val)if subtree:yield from self.inorder(subtree.left)yield subtree.valyield from self.inorder(subtree.right)def reverse(self, subtree):if subtree is not None:subtree.left, subtree.right = subtree.right, subtree.leftself.reverse(subtree.left)self.reverse(subtree.right)def layer_trav(self, subtree):cur_nodes = [subtree]next_nodes = []while cur_nodes or next_nodes:for node in cur_nodes:print(node.data)if node.left:next_nodes.append(node.left)if node.right:next_nodes.append(node.right)cur_nodes = next_nodes  # 继续遍历下一层next_nodes = []def layer_trav_use_queue(self, subtree):q = Queue()q.append(subtree)while not q.empty():cur_node = q.pop()print(cur_node.data)if cur_node.left:q.append(cur_node.left)if cur_node.right:q.append(cur_node.right)node_list = [{'data': 'A', 'left': 'B', 'right': 'C', 'is_root': True},{'data': 'B', 'left': 'D', 'right': 'E', 'is_root': False},{'data': 'D', 'left': None, 'right': None, 'is_root': False},{'data': 'E', 'left': 'H', 'right': None, 'is_root': False},{'data': 'H', 'left': None, 'right': None, 'is_root': False},{'data': 'C', 'left': 'F', 'right': 'G', 'is_root': False},{'data': 'F', 'left': None, 'right': None, 'is_root': False},{'data': 'G', 'left': 'I', 'right': 'J', 'is_root': False},{'data': 'I', 'left': None, 'right': None, 'is_root': False},{'data': 'J', 'left': None, 'right': None, 'is_root': False},
]btree = BinTree.build_from(node_list)
print('====先序遍历=====')
btree.preorder_trav(btree.root)print('====使用 stack 实现先序遍历=====')
btree.preorder_trav_use_stack(btree.root)print('====层序遍历=====')
btree.layer_trav(btree.root)
print('====用队列层序遍历=====')
btree.layer_trav_use_queue(btree.root)btree.reverse(btree.root)
print('====反转之后的结果=====')
btree.preorder_trav(btree.root)

延伸阅读

  • 《Data Structures and Algorithms in Python》 13 章 Binary Trees
  • https://www.geeksforgeeks.org/iterative-preorder-traversal/

Leetcode 练习

  • leetcode binary-tree-preorder-traversal
    二叉树的先序遍历

  • leetcode binary-tree-inorder-traversal/
    二叉树的中序遍历

  • leetcode binary-tree-postorder-traversal
    二叉树的后序遍历

  • leetcode binary-tree-right-side-view
    使用树的层序遍历我们能实现一个树的左右视图,比如从一个二叉树的左边能看到哪些节点。 请你尝试做这个练习题

  • leetcode construct-binary-tree-from-preorder-and-postorder-traversal
    根据二叉树的 前序和后序遍历,返回一颗完整的二叉树。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/162592.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python命令行 引导用户填写ssh登录信息

字多不看,直接体验: 待补充 演示代码 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/23 9:20 file: 引导用户填写ssh接口信息.py desc: xxxxxx """# region 引入必要的依赖 impor…

【图像分类】基于深度学习的垃圾分类系统的设计与实现(ResNet网络,附代码和数据集)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。(专栏订阅用户订阅专栏后免费提供数据集和源码一份,超级VIP用户不在服务范围之内,不想订阅专栏的兄弟们可以私信…

el-table表格排序(需要后端判别),el-table导出功能(向后端发送请求)

&#xff08;1&#xff09;表格排序 &#xff08;2&#xff09;简单的table导出功能&#xff08;需要后台支撑&#xff09;必须要有iframe &#xff08;3&#xff09;页面所有代码&#xff1a; <template><div class"mainContainer"><el-form:model&…

使用Pytorch从零开始构建DCGAN

在本文中&#xff0c;我们将深入研究生成建模的世界&#xff0c;并使用流行的 PyTorch 框架探索 DCGAN&#xff08;生成对抗网络 (GAN) 的一种变体&#xff09;的实现。具体来说&#xff0c;我们将使用 CelebA 数据集&#xff08;名人面部图像的集合&#xff09;来生成逼真的合…

网络安全等级保护收费标准?

不同省份价格会略有不同&#xff0c;二级等保一般不低于5万元;三级等保不低于9万元&#xff0c;个别省份也可能7万也能办理&#xff0c;根据企业实际情况和省市选定的代理机构确定。 等级保护二级? 第二级等保是指信息系统受到破坏后&#xff0c;会对公民、法人和其他组织的合…

《YOLOv8创新改进》专栏指导书册 手把手创新教程

&#x1f680;&#x1f680;&#x1f680;YOLOv8改进专栏&#xff1a;http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8&#xff0c;从入门到创新&#xff0c;轻轻松松搞定科研&#xff1b; 本专栏为订阅者提供答疑服务&#xff0c;每一篇提供源代码和详细的每一个步骤改进地方。…

Navicat 技术指引 | 适用于 GaussDB 的模型功能

Navicat Premium&#xff08;16.2.8 Windows版或以上&#xff09; 已支持对 GaussDB 主备版的管理和开发功能。它不仅具备轻松、便捷的可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结构同步、协同合作、数据迁移等&#xff09;&#xff0c;这…

工业交换机具备哪些功能?

在工业网络中&#xff0c;工业交换机起着至关重要的作用&#xff0c;具备多样功能和广泛的应用。 1、工业交换机的作用是实现不同网络设备之间的互联。它能够连接各种不同类型的设备&#xff0c;如计算机、服务器、传感器和监控设备&#xff0c;实现设备间的相互通信和数据传输…

应用高斯高通滤波器提取图像轮廓

任务要求&#xff1a; 图为HALCON中的例图“tooth_rim”&#xff0c;请用高斯高通滤波器提取图像的轮廓。 任务分析&#xff1a; 图像的边缘对应频谱的高频部分&#xff0c;可以通过构造一个高频滤波器&#xff0c;过滤掉图像的低频部分&#xff0c;从而得到图像的边缘。HALC…

苹果怎么关闭悬浮球?让我来解答您的疑惑!

悬浮球是苹果设备上的一种可进行自定义的快捷操作功能&#xff0c;它可以位于手机屏幕的任意位置&#xff0c;以浮动的方式显示。然而&#xff0c;有时候悬浮球对某些朋友来说可能会变得多余&#xff0c;那么苹果怎么关闭悬浮球呢&#xff1f;接下来&#xff0c;小编将为大家揭…

docker compose搭建渗透测试vulstudy靶场示例

前言 渗透测试&#xff08;Penetration test&#xff09;即网络安全工程师/安全测试工程师/渗透测试工程师通过模拟黑客&#xff0c;在合法授权范围内&#xff0c;通过信息搜集、漏洞挖掘、权限提升等行为&#xff0c;对目标对象进行安全测试&#xff08;或攻击&#xff09;&am…

详解——菱形继承及菱形虚拟继承

目录 一&#xff0c;菱形继承 1.1单继承 1.2多继承 1.3菱形继承 1.4菱形继承的问题 1.5虚拟继承解决数据冗余和二义性的原理 二.继承的总结和反思 一&#xff0c;菱形继承 C三大特性——继承-CSDN博客 1.1单继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继…

anaconda安装配置

创建分区 conda create -n cpu 安装Cpu版本 https://pytorch.org/ conda install pytorch torchvision torchaudio cpuonly -c pytorch 激活环境 conda activate cpu 验证 退出当前分区 conda deactivate 安装GPU版本 创建分区conda create -n gpu 激活环境 conda…

Java面向对象(高级)-- final关键字的使用

文章目录 一、 final的意义二、 final的使用&#xff08;1&#xff09; final修饰类&#xff08;2&#xff09; final修饰方法&#xff08;3&#xff09; final修饰变量1. 修饰成员变量1.1 举例11.2 举例2 2. 修饰局部变量2.1 举例12.2 举例2 &#xff08;4&#xff09;final搭…

java协程操作mysql数据库

我的项目&#xff1a; nanshaws/nettyWeb: 复习一下netty&#xff0c;并打算做一个web项目出来 (github.com) 最近在项目中分别添加了虚拟线程操作mysql数据库&#xff0c;和用协程操作mysql数据库 同理先跟我这个博客操作一下前面的&#xff1a;就单纯代码的时候进行修改&a…

TikTok与精神健康:社交媒体在压力时代的作用

在当今数字化和社交化的时代&#xff0c;社交媒体已成为人们生活中不可或缺的一部分。其中&#xff0c;TikTok作为一款备受欢迎的短视频应用&#xff0c;不仅改变了人们的娱乐方式&#xff0c;也对精神健康产生了深远的影响。 本文将深入探讨TikTok在压力时代对精神健康的作用…

2023年中国油田工程建设市场规模现状及行业竞争分析[图]

油田工程建设是在确定油气田有开发生产的价值的基础上&#xff0c;进行系统的工程建设&#xff0c;油田工程建设包括井场建设、管道施工、土石方工程、道路建设及绿化等服务。 油田工程建设主要内容 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#xff09; 油田服务…

抖音商城小程序源码系统 附带完整的搭建教程

大家好啊&#xff0c;今天小编来给大家分享一款抖音商城小程序源码系统。这可是当下最热门的的项目之一。。抖音作为国内最大的短视频平台之一&#xff0c;拥有庞大的用户群体和丰富的社交功能。为了满足用户在抖音上购物和交易的需求&#xff0c;抖音商城小程序应运而生。 以…

租车系统开发/多功能租车平台微信小程序源码/汽车租赁系统源码/汽车租赁小程序系统

源码介绍&#xff1a; 多功能租车平台微信小程序源码&#xff0c;作为汽车租赁、摩托车租车平台系统源码&#xff0c;是小程序系统。基于微信小程序的汽车租赁系统源码。 开发环境及工具&#xff1a; 大等于jdk1.8&#xff0c;大于mysql5.5&#xff0c;idea&#xff08;eclip…

拆解:淘宝客新玩法之微信淘礼金创建怎么做

最近看到一种新的淘宝客玩法&#xff0c;迫不及待的想分享给大家。微信公众号查券大家都不陌生&#xff0c;也有不少人都在做这个。最近看到有人在做微信公众号创建淘礼金。之所以说这个玩法新是因为目前大多数淘客还在做返利。返利有周期长、提现有门槛等痛点。 微信公众号创建…