数据结构与算法06-树结构(二叉树)

介绍

树也是基于结点的数据结构,但树里面的每个结点,可以含有多个链分别指向其他多个结点。

基于树的数据结构有很多种,但本章只关注其中一种——二叉树。二叉树是一种遵守以下规则的树。

  • 每个结点的子结点数量可为 0、1、2。
  • 如果有两个子结点,则其中一个子结点的值必须小于父结点,另一个子结点的值必须大于父结点。

以下是一个二叉树的例子,其中结点的值是数字。
在这里插入图片描述

二叉树在查找、插入和删除上引以为傲的 O(log N)效率,使其成为了存储和修改有序数据的一大利器。它尤其适用于需要经常改动的数据,虽然在查找上它跟有序数组不相伯仲,但在插入和删除方面,它迅速得多。

定义

class TreeNode:
    """
    二叉树节点的定义。

    该类用于构建二叉树的节点结构,每个节点包含一个值(val)、左子节点(left)和右子节点(right)。
    
    Attributes:
        val: 节点的值,默认为0。
        left: 左子节点,默认为None。
        right: 右子节点,默认为None。
    """
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

操作

查找

二叉树的查找算法先从根结点开始:
(1) 检视该结点的值。
(2) 如果正是所要找的值,太好了!
(3) 如果要找的值小于当前结点的值,则在该结点的左子树查找。
(4) 如果要找的值大于当前结点的值,则在该结点的右子树查找。

在这里插入图片描述

推广开来,我们会说二叉树查找的时间复杂度是 O(log N)。因为每行进一步,我们就把剩余的结点排除了一半(不过很快就能看到,只在最好情况下,即理想的平衡二叉树才有这样的效率)。再与二分查找比较,它也是每次尝试会排除一半可能性的 O(log N)算法,可见二叉树查找跟有序数组的二分查找拥有同样的效率。

要说二叉树哪里比有序数组更亮眼,那应该是插入操作。

插入

假设要插入一个45节点,最终找到的位置是在40的右侧插入。
在这里插入图片描述
这个例子里,插入花了 5 步,包括 4 步查找和 1 步插入。入这 1 步总是发生在查找之后,所以总共 log N + 1 步。按照忽略常数的大 O 来说,就是 O(log N)步。

这就是二叉树的高效之处。有序数组查找需要 O(log N),插入需要 O(N),而二叉树都是只要O(log N)。

删除

两种情况:

  • 如果要删除的结点没有子结点,那直接删掉它就好。
  • 如果要删除的结点有一个子结点,那删掉它之后,还要将子结点填到被删除结点的位
    置上。

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

  • 如果要删除的结点有两个子结点,则将该结点替换成其后继结点。一个结点的后继结点,就是所有比被删除结点大的子结点中,最小的那个。
  • 如果后继结点带有右子结点,则在后继结点填补被删除结点以后,用此右子结点替代后继结点的父节点的左子结点。
class Node:
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None

def delete_node(root, key):
    if not root:
        return root

    # 深度优先搜索,找到要删除的节点
    if key < root.key:
        root.left = delete_node(root.left, key)
    elif key > root.key:
        root.right = delete_node(root.right, key)
    else:
        # 找到要删除的节点
        if not root.left:
            temp = root.right
            root = None
            return temp
        elif not root.right:
            temp = root.left
            root = None
            return temp

        # 找到右子树的最小节点来替换
        temp = find_min(root.right)
        root.key = temp.key
        root.right = delete_node(root.right, temp.key)

    return root

def find_min(node):
    # 找到右子树的最小节点
    current = node
    while current.left is not None:
        current = current.left
    return current

# 示例
root = Node(50)
root.left = Node(30)
root.right = Node(70)
root.left.left = Node(20)
root.left.right = Node(40)
root.right.left = Node(60)
root.right.right = Node(80)

# 打印树的中序遍历
def in_order_traversal(node):
    if node:
        in_order_traversal(node.left)
        print(f"{node.key} ", end="")
        in_order_traversal(node.right)
        
print("Original tree:")
in_order_traversal(root)

root = delete_node(root, 50)
print("\nAfter deleting 50:")
in_order_traversal(root)


跟查找和插入一样,平均情况下二叉树的删除效率也是 O(log N)。因为删除包括一次查找,以及少量额外的步骤去处理悬空的子结点。有序数组的删除则由于需要左移元素去填补被删除元素产生的空隙,最终导致 O(N)的时间复杂度。

案例

比如说你正在做一个书目维护的应用,它需要具备以下功能。

  1. 该应用可以将书名依照字母序打印。
  2. 该应用可以持续更新书目。
  3. 该应用可以让用户从书目中搜索书名。

使用python语言,利用二叉树作为数据结构实现以上功能。
在这里插入图片描述

class BookNode:
    def __init__(self, title):
        self.title = title
        self.left = None
        self.right = None

class BST:
    def __init__(self):
        self.root = None

    def insert(self, title):
        if not self.root:
            self.root = BookNode(title)
        else:
            self._insert(self.root, title)

    def _insert(self, node, title):
        if title < node.title:
            if not node.left:
                node.left = BookNode(title)
            else:
                self._insert(node.left, title)
        else:
            if not node.right:
                node.right = BookNode(title)
            else:
                self._insert(node.right, title)

    def inorder_traversal(self):
        if self.root:
            self._inorder_traversal(self.root)

    def _inorder_traversal(self, node):
        if node:
            self._inorder_traversal(node.left)
            print(node.title, end="==>")
            self._inorder_traversal(node.right)

    def search(self, title):
        if not self.root:
            return False
        return self._search(self.root, title)

    def _search(self, node, title):
        if not node or node.title == title:
            return node is not None
        return self._search(node.left, title) if title < node.title else self._search(node.right, title)


# 使用示例
bst = BST()
books = ["Alice in Wonderland", "Pride and Prejudice", "The Great Gatsby", "To Kill a Mockingbird", "War and Peace"]
for book in books:
    bst.insert(book)

print("Books in alphabetical order:")
bst.inorder_traversal()

print("\nSearching for 'Pride and Prejudice':", bst.search("Pride and Prejudice"))
print("Searching for 'Moby Dick':", bst.search("Moby Dick"))  # 不存在的书名

总结

二叉树是一种强大的基于结点的数据结构,它既能维持元素的顺序,又能快速地查找、插入和删除。尽管比它的近亲链表更为复杂,但它更有用。

值得一提的是,树形的数据结构除了二叉树以外还有很多种,包括堆、B 树、红黑树、2-3-4树等。它们也各有自己适用的场景。

下一章,我们还会遇见另一种基于结点的数据结构——图。图是社交网络和地图软件等复杂
应用的核心组成部分,强大且灵活。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

码蹄杯 2024 初赛第一场

MC0301 求个最大值 code: #include<bits/stdc.h> #define int long long #define endl \nusing namespace std;int n;void solve(){cin >> n;int mx -1;for(int i 0;i < n;i ){int x; cin >> x;mx max(mx,x);}cout << mx << endl; }sig…

JAVA流程控制break,continue,goto

1.break在任何循环语句的主体成分&#xff0c;均可用break控制循环的流程。break用于强行退出循环&#xff0c;不执行循环中剩余的语句。&#xff08;break语句也在switch语句中使用&#xff09; 如图&#xff1a;break语句强行退出循环&#xff0c;结果输出1~30便结束&#xf…

防火墙基础基础篇:NAT转发功能之——Easy IP方式详解

防火墙基础基础篇&#xff1a;NAT转发功能之——Easy IP方式详解 1. 概念 Easy IP 是一种简化版的动态NAPT&#xff08;Network Address and Port Translation&#xff09;技术。在Easy IP中&#xff0c;我们只使用一个公网IP地址&#xff0c;无需建立公有IP地址池。这个公网…

【数据库专家揭秘】MySql数据库设计黄金法则,让你的数据更稳定、更高效!

文章目录 引言一、明确需求&#xff0c;合理规划二、规范命名&#xff0c;提高可读性三、选择合适的数据类型四、优化表结构五、性能优化六、注重安全性总结 引言 在当今数字化时代&#xff0c;数据库已成为企业信息管理的核心。而在众多数据库系统中&#xff0c;MySql以其稳定…

jar包部署到服务器,修改jar包配置文件

jar包部署到服务器 打包项目1.jar包分离2.整体打包配置文件配置文件分离整体打包修改配置文件 打包项目 maven项目打包有两种&#xff0c;一是将自己的项目和依赖包分离&#xff0c;二是打包成一个jar包 1.jar包分离 需要在pom文件中引入依赖 <build><finalName&…

积鼎流体仿真软件VirtualFlow: 锂电池液冷散热数值计算

电池包在运作的时候会产生大量的热&#xff0c;热会在电池包内积累&#xff0c;随着车辆的使用&#xff0c;电池包内的部件会老化损伤&#xff0c;安全隐患极高&#xff0c;如何给电池包散热就显得非常重要。本文采用积鼎VirtualFlow对电芯、冷板以及冷却液进行散热仿真计算&am…

进程线程(一.2)

进程与线程&#xff08;一&#xff09; 并发编程并发与并行高并发 进程特征什么是进程&#xff1f;线程&#xff1f;进程与程序的区别进程与线程区别进程的五状态进程的种类 查看进程命令ps auxps axjpstreekill 进程的创建fork函数fork总结vfork函数fork与vfork区别 获取进程I…

30天变现5位数,涨粉2w,用AI做治愈系插图,太香了!(附工具教程)

大家好&#xff0c;我是设计师阿威 前段时间和一位朋友聊天&#xff0c;他说现在靠 AI 赚到钱&#xff0c;基本不可能&#xff01; 我竟然一时不知道说什么好。 虽然我并不认同他的说法&#xff0c;但也没有再说什么了。 因为人们往往会根据自己已有的认知体系&#xff0c;…

vivado BD_ADDR_SPACE、BD_CELL

描述 地址空间或bd_addr_space对象是一个分配的逻辑可寻址空间 主机接口上的内存&#xff0c;或连接到AXI主机的AXI接口端口上的内存 块设计外部。 Vivado Design Suite的IP集成商遵循行业标准IP-XACT数据 用于捕获内存需求和功能的格式。有些区块可以有一个 与多个主接口相关联…

电力电子功率模块在工程应用中测温NTC的使用

电力电子功率模块在工程应用中测温NTC的使用 1.概述2.什么是NTC3.模块内部NTC3.1 绝缘隔离措施3.2 NTC热量考虑 4.使用模拟方法测量NTC温度4.1 分压电阻大小 5.使用数字方法测量NTC温度 1.概述 最近做项目的时候突然被问到一个问题。做实验测温用的NTC到底怎么用&#xff1f;为…

【西瓜书】5.神经网络

1.概念 有监督学习正向传播&#xff1a;输入样本---输入层---各隐层---输出层反向传播&#xff1a;误差以某种形式在通过隐层向输入层逐层反转&#xff0c;并将误差分摊给各层的所有单元&#xff0c;以用于修正各层的权值激活函数&#xff1a;也叫阶跃函数&#xff0c;目的是引…

特征工程技巧——字符串编码成数字序列

这段时间在参加比赛&#xff0c;发现有一些比赛上公开的代码&#xff0c;其中的数据预处理步骤值得我们参考。 平常我们见到的都是数据预处理&#xff0c;现在我们来讲一下特征工程跟数据预处理的区别。 数据预处理是指对原始数据进行清洗、转换、缩放等操作&#xff0c;以便为…

深入理解序列化:概念、应用与技术

在计算机科学中&#xff0c;序列化&#xff08;Serialization&#xff09;是指将数据结构或对象状态转换为可存储或传输的格式的过程。这个过程允许将数据保存到文件、内存缓冲区&#xff0c;或通过网络传输至其他计算机环境&#xff0c;不受原始程序语言的限制。相对地&#x…

MySQL(三) - 基础操作

一、索引 由于我们在使用数据库的时候&#xff0c;大部分操作的都是查询操作&#xff0c;但是我们每一次进行查询都需要遍历一遍表中所有数据&#xff0c;这会花费O(n)的时间&#xff0c;因此数据引入了“索引” 也就是在底层使用了数据结构来进行优化查询的操作&#xff0c;但…

C++ Primer 第五版 第15章 面向对象程序设计

面向对象程序设计基于三个基本概念&#xff1a;数据抽象、继承和动态绑定。 继承和动态绑定对编写程序有两方面的影响&#xff1a;一是我们可以更容易地定义与其他类相似但不完全相同的新类&#xff1b;二是在使用这些彼此相似的类编写程序时&#xff0c;我们可以在一定程度上…

java面试题及答案2024,java2024最新面试题及答案(之一)

发现网上很多Java面试题都没有答案&#xff0c;所以花了很长时间搜集整理出来了这套Java面试题大全&#xff0c;希望对大家有帮助哈~ 本套Java面试题大全&#xff0c;全的不能再全&#xff0c;哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别&#xff1f; JDK&#xff1a;Ja…

day26-单元测试

1. 单元测试Junit 1.1 什么是单元测试&#xff1f;&#xff08;掌握&#xff09; 1.2 Junit的特点&#xff1f;&#xff08;掌握&#xff09; 1.3 基本用法&#xff1a;&#xff08;掌握&#xff09; 实际开发中单元测试的使用方式&#xff08;掌握&#xff09; public class …

开源利器AnythingLLM:你的私人ChatGPT构建利器,支持主流多种大模型

开源利器AnythingLLM&#xff1a;你的私人ChatGPT构建利器&#xff0c;支持主流多种大模型 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备…

阿里云服务器接入百度云防护后显示502原因

最近&#xff0c;发现很多使用了阿里云服务器的网站出现502的情况 经百度云防护技术排查发现阿里云机房对百度云防护的IP进行了拦截&#xff0c;原因近期可能是百度云防护的IP请求过于频繁&#xff0c;导致阿里云机房策略把百度云的IP当成了攻击IP。 解决办法是提交工单让阿里…

ProxySQL + MySQL MGR 实现读写分离实战

文章目录 前言1、ProxySQL 介绍1.1、ProxySQL 如何工作1.2、ProxySQL 工作原理 2、ProxySQL 安装与读写分离实战2.1、ProxySQL 安装2.2、读写分离配置2.3、读写分离实战2.4、SpringBoot 整合 前言 该文章实践之前&#xff0c;需要搭建MySQL MGR集群&#xff0c;关于 MySQL MGR…