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

news2024/11/17 23:47:56

树和二叉树

前面我们讲了两种使用分治和递归解决排序问题的归并排序和快速排序,堆排序先就此打住,因为涉及到树的概念,所以我们先来讲讲树。
讲完了树之后后面我们开始介绍一种有用的数据结构堆(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

    @classmethod
    def 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 = node
            node.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, right


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

    @classmethod
    def 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 = node
            node.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 nodes
        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)


from collections import deque
class Queue(object):  # 借助内置的 deque 我们可以迅速实现一个 Queue
    def __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.left
            self.reverse(subtree.left)
            self.reverse(subtree.right)

练习题

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

源码

# -*- coding: utf-8 -*-


from collections import deque


class Queue(object):  # 借助内置的 deque 我们可以迅速实现一个 Queue
    def __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


class 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) == 0


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


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

    @classmethod
    def 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 = node
            node.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 产出值然后在调用的地方转成 list
                if 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.val
            yield from self.inorder(subtree.right)

    def reverse(self, subtree):
        if subtree is not None:
            subtree.left, subtree.right = subtree.right, subtree.left
            self.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.coloradmin.cn/o/1242189.html

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

相关文章

Hook+jsdom 解决cookie逆向

前言 记录下如何破cookie逆向 目标 目标网址:https://q.10jqka.com.cn/ 目标接口:http://q.10jqka.com.cn/index/index/board/all/field/zdf/order/desc/page/2/ajax/1/ 对抗:cookie反爬虫处理,关键字v,如图 解决步骤 1、JS中关键字查找 如上,我们找到了关键字 v,…

Webstorm 插件文件目录颜色分析——白蓝绿红黄灰

Webstorm 插件文件目录【白色、蓝色、绿色、红色、黄色、灰色】对应当前文件发生什么了,即文件夹当前状态。 WebStrom配置好git或SVN后文件颜色代表的含义: 白色:本地无修改内容 蓝色:文件内容有修改,暂未提交到git…

Re51:读论文 Language Models as Knowledge Bases?

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称:Language Models as Knowledge Bases? ArXiv网址:https://arxiv.org/abs/1909.01066 官方GitHub项目:https://github.com/facebookresearch/LAMA 本文是2019年…

深度学习之基于Tensorflow卷积神经网络鸟类目标识别检测系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Tensorflow的卷积神经网络(Convolutional Neural Networks,CNN)在鸟类目标识…

淡入淡出transition: right 1s

transition: right 1s; //重点直接改变right值 操作过快 这里用该方法实现1s内淡入淡出 达到效果目标

35的程序员被辞了可以自己接外包啊?为什么都那么悲观呢?

35的年纪,上有老下有小,即将步入中年危机,在这个节骨眼上被辞,能不悲观吗? 在这个年纪人们往往追求的是稳定的工作和生活,而进入一个自己不熟悉的行业并不是一个好的选择。 况且,你认为的外包…

【考研】数据结构(更新到循环链表)

声明&#xff1a;所有代码都可以运行&#xff0c;可以直接粘贴运行&#xff08;只有库函数没有声明&#xff09; 线性表的定义和基本操作 基本操作 定义 静态&#xff1a; #include<stdio.h> #include<stdlib.h>#define MaxSize 10//静态 typedef struct{int d…

【23真题】Top3!最高148分,数二英二!

今天分享的是23年西安交通大学815的信号与系统数字信号处理试题及解析。众所周知&#xff0c;Top3一共有10所&#xff0c;其中就包括了西安交大&#xff01; 本套试卷难度分析&#xff1a;平均分为117-128分&#xff0c;最高分为148分&#xff01;22年西安交大909/815的真题我…

一点DETR学习

DETR: 主要是为了学习query。 主要从两个方面&#xff1a;加偏好和缩短序列长度

rabbit MQ的延迟队列处理模型示例(基于SpringBoot延时插件实现)

rabbitMQ安装插件rabbitmq-delayed-message-exchange 交换机由此type 表示组件安装成功 生产者发送消息时设置延迟值 消息在交换机滞纳至指定延迟后&#xff0c;进入队列&#xff0c;被消费者消费。 组件注解类&#xff1a; package com.esint.configs;import org.springfra…

vue3的单组件的编写(三)【响应式 API 之 toRef 与 toRefs】

响应式 API 之 toRef 与 toRefs 前面讲了 ref 和 reactive 这两种响应式API &#xff0c;为了方便开发者使用&#xff0c;vue3 还出了两个用来 reactive 转换为 ref 的API&#xff0c;分别是 toRef 和 toRefs 。 &#x1f308;什么是toRef 与 toRefs 这两个API看拼写能猜到&…

深度学习之基于Pytorch照片图像转漫画风格网络系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 以下是一个基本的设计介绍&#xff1a; 数据准备&#xff1a;收集足够的真实照片和漫画图像&#xff0c;用于训练模…

SSM整合1

请求参数 (这里的形参数据都是SpringMvc注入的) controller里的方法不是我们来调用的 是由SpringMvc的前端控制器所调用的(前端控制器调用了处理器 由处理器和适配器去调用我们controller里的方法)&#xff0c;controller里的方法叫handler->处理器 SpringMVC的Controller方…

分布式锁之基于redis实现分布式锁(二)

2. 基于redis实现分布式锁 2.1. 基本实现 借助于redis中的命令setnx(key, value)&#xff0c;key不存在就新增&#xff0c;存在就什么都不做。同时有多个客户端发送setnx命令&#xff0c;只有一个客户端可以成功&#xff0c;返回1&#xff08;true&#xff09;&#xff1b;其他…

快速排序演示和代码介绍

快速排序的核心是(以升序为例)&#xff1a;在待排序的数据中指定一个数做为基准数&#xff0c;把所有小于基准数的数据放到基准数的左边&#xff0c;所有大于基准数的数据放在右边&#xff0c;这样的话基准数的位置就确定了&#xff0c;然后在两边的数据中重复上述操作

【好玩的开源项目】Linux系统之部署proxx扫清黑洞小游戏

【好玩的开源项目】Linux系统之部署proxx扫清黑洞小游戏 一、proxx小游戏介绍1.1 proxx小游戏简介1.2 开源地址 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、部署Node.js环境4.1 下载Node.js安装包4.…

使用docker命令_进入容器_登录mysql服务_并执行sql语句---Docker工作笔记005

今天就用到了,不得不说用docker用到的还是少,记录一下,常用的也就这些吧. 首先执行: docker ps [root@localhost dataease-1.18.9]# docker ps CONTAINER ID IMAGE COMMAND CREATED …

itext - PDF模板套打

项目需求&#xff1a;获取列表数据之后直接将数据生成一个pdf。因此需要使用到 itext 对pdf进行直接操作。 环境配置 需要为pdf添加文字域&#xff0c;因此需要安装Adobe Acrobat 准备一个空的PDF文件&#xff0c;如果有现成的模板更好 依赖配置&#xff0c;我们使用itext的7版…

设计模式-16-Spring源码中的设计模式

1-Spring之观察者模式 Java、Google Guava都提供了观察者模式的实现框架。Java提供的框架比较简单&#xff0c;只包含java.util.Observable和java.util.Observer两个类。Google Guava提供的框架功能比较完善和强大&#xff1a;通过EventBus事件总线来实现观察者模式。实际上&am…

mac电脑文件比较工具 UltraCompare 中文for mac

UltraCompare是一款功能强大的文件和文件夹比较工具&#xff0c;用于比较和合并文本、二进制和文件夹。它提供了丰富的功能和直观的界面&#xff0c;使用户能够轻松地比较和同步文件内容&#xff0c;查找差异并进行合并操作。 以下是UltraCompare软件的一些主要特点和功能&…