二叉树 二叉平衡树 B树 B+树

1 二叉树

这是最基础的树形结构。

在这里插入图片描述
  • 定义:每个节点最多只有两个子节点(左子树和右子树)。
  • 优点:比链表快,插入和查找的平均时间复杂度是 O(log N)。

缺点:不稳定。
在极端情况下(比如插入的数据本身已经是有序的,如 6,7,8,9),它会退化成一条链表。

在这里插入图片描述

此时查找时间复杂度会降到 O(N),性能急剧下降。

2 二叉平衡树

为了解决二叉树的退化问题,平衡树诞生了。

在这里插入图片描述
  • 定义:在二叉树的基础上增加了约束:左右子树的高度差不能超过 1
  • 工作原理:每次插入或删除数据时,如果平衡被破坏,它会通过旋转操作来自动调整结构,使其始终保持平衡。
  • 优点:避免了二叉树退化成链表的问题,查找效率非常稳定,始终维持在 O(log N)。
  • 缺点:
    树太高:当数据量非常大时(例如几百万条),log N 的深度可能依然有 20-30 层。每一次从磁盘读取一层数据,就是一次磁盘 I/O,非常慢。
    数据量问题:它适用于内存中的查找,但不适用于海量数据的磁盘存储。
  • 平衡二叉树插入数据,导致破坏平衡型的类型分为了四种:LL、LR、RR、RL,具体解决方法可参考:参考

3 B树

当数据量大到无法全部放入内存,必须存放在磁盘上时,B 树出现了。它是一种多路搜索树。

在这里插入图片描述
  • 核心思想:不再像二叉树那样只有一个节点一个数据,而是一个节点可以存储多个值,并且拥有多个子节点(多叉的)。
  • 结构特点:
    每个节点包含键值和数据(或指针)。
    节点内的键值从小到大排序。
    所有节点(包括根节点和中间节点)都可能存储实际的数据。
  • 优点:
    降低了树的高度:因为一个节点存了多个值,树变得更矮更胖,从而减少了磁盘 I/O 次数。
  • 缺点:
    查询不稳定:数据可能存在于根节点,也可能存在于叶子节点。查找根节点就找到的话很快,但找到叶子节点就慢一些。
    范围查询麻烦:要进行范围查询(如 > 20),需要多次从根节点遍历到叶子节点(中序遍历),比较繁琐。

4 B+树

B+ 树是 B 树的升级版,也是目前关系型数据库(如 MySQL 的 InnoDB 引擎)最常用的索引结构。

在这里插入图片描述
  • 核心改进:
    数据只存储在叶子节点:非叶子节点(内节点)只存储指针和键值,不存储数据。这就意味着非叶子节点可以容纳更多的索引项,让树变得更矮更宽。
    叶子节点之间形成有序链表:所有的叶子节点通过指针连接成了一个有序的链表。
  • 为什么数据库用 B+ 树而不是 B 树?
    1.范围查询能力极强:比如查 age > 18。B+ 树只需要在叶子节点的链表上顺序遍历即可,不需要像 B 树那样回溯到父节点。这正是数据库最常用的操作。
    2.查询效率稳定:任何数据的查找都必须从根节点走到叶子节点,路径长度一样。这使得查询时间非常稳定,便于数据库优化器进行评估。
    3.更充分利用磁盘预读:非叶子节点不存数据,占用的空间更小,操作系统一次 I/O 可以读取更多的索引项到内存中。

5 二叉树遍历方式

  • 定义二叉树
classTreeNode:"""二叉树节点"""def__init__(self, val=0, left=None, right=None): self.val = val # 节点的值 self.left = left # 左子节点 self.right = right # 右子节点

5.1 深度优先DFS

classBinaryTree:"""二叉树操作类"""def__init__(self, root=None): self.root = root 

5.1.1 前序遍历

defpreorder_recursive(self, node):"""前序遍历:根 -> 左 -> 右"""if node isNone:return[] result =[node.val] result.extend(self.preorder_recursive(node.left)) result.extend(self.preorder_recursive(node.right))return result 

5.1.2 中序遍历

definorder_recursive(self, node):"""中序遍历:左 -> 根 -> 右"""if node isNone:return[] result = self.inorder_recursive(node.left) result.append(node.val) result.extend(self.inorder_recursive(node.right))return result 

5.1.3 后序遍历

defpostorder_recursive(self, node):"""后序遍历:左 -> 右 -> 根"""if node isNone:return[] result = self.postorder_recursive(node.left) result.extend(self.postorder_recursive(node.right)) result.append(node.val)return result 

5.2 广度优先BFS

deflevel_order(self):"""层序遍历,使用队列"""ifnot self.root:return[] result =[] queue =[self.root]# 用列表模拟队列while queue: level_size =len(queue) level_nodes =[]for i inrange(level_size): node = queue.pop(0)# 取出队列头部 level_nodes.append(node.val)if node.left: queue.append(node.left)if node.right: queue.append(node.right) result.append(level_nodes)# 按层存储return result 

Read more

吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比

吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文: * 容器适配器 * STL标准库中stack和queue的底层结构 * deque的简单介绍(了解) * deque的缺陷 * 为什么选择deque作为stack和queue的底层默认容器 * stack的介绍和使用 * Satck的介绍 * Stack的使用 * stack的模拟实现 * queue的介绍和使用 * queue的介绍 * queue的使用 * queue的模拟实现 * priority_queue的介绍和使用 * priority_queue的介绍 * priority_queue的使用 * 在OJ中的使用 * priority_queue的模拟实现 * STL标准库中对于sta

By Ne0inhk
【C++:搜索二叉树】二叉搜索树从理论到实战完全解读:原理、两种场景下的实现

【C++:搜索二叉树】二叉搜索树从理论到实战完全解读:原理、两种场景下的实现

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶、测试开发要点全知道 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的C++专栏简介: 目录 C++的两个参考文档 前言 1  ~>  理解二叉搜索树 1.1  二叉搜索树的概念 1.2  博主手记:核心特性 1.2.1  多元化的结构: 灵活的数据结构 1.2.2  天然的搜索优势:擅长搜索的数据结构 2  ~>  二叉搜索树性能分析 2.

By Ne0inhk
【C++贪心 DFS】2673. 使二叉树所有路径值相等的最小代价|1917

【C++贪心 DFS】2673. 使二叉树所有路径值相等的最小代价|1917

本文涉及知识点 C++贪心 反证法 决策包容性 C++DFS LeetCode2673. 使二叉树所有路径值相等的最小代价 给你一个整数 n 表示一棵 满二叉树 里面节点的数目,节点编号从 1 到 n 。根节点编号为 1 ,树中每个非叶子节点 i 都有两个孩子,分别是左孩子 2 * i 和右孩子 2 * i + 1 。 树中每个节点都有一个值,用下标从 0 开始、长度为 n 的整数数组 cost 表示,其中 cost[i] 是第 i + 1 个节点的值。每次操作,你可以将树中 任意 节点的值

By Ne0inhk
C++socket网络编程——udp服务器

C++socket网络编程——udp服务器

目录 一.端口号 VS  PID 二.套接字编程的类型 三.socket编程接口 四.基于udp的服务端和客户端全部代码 客户端 服务端 五.解释与运行 一些细节: 六.总结 一.端口号 VS  PID pid已经能够标识一台主机上的一个唯一一个进程了,为什么还需要端口号? 1. 不是所有的进程都需要网络通信,但是所有的进程都需要都pid; 2. 系统和网络功能解耦。         另外,一个进程可以绑定多个端口,但一个端口只能被一个进程绑定。         系统内定的端口号【0,1023】一般都要有固定的应用层协议使用,如http:80,https:443。 二.套接字编程的类型 1. 域间套接字编程——同一个机器内 2. 原始套接字编程——网络工具 3. 网络套接字编程—

By Ne0inhk