当前位置: 首页 > news >正文

二叉树理论基础

二叉树理论基础详解

概述

二叉树是数据结构中最基础也是最重要的非线性数据结构之一。它不仅在算法设计中广泛应用,更是许多高级数据结构的基础。掌握二叉树的各种类型和特性,对于理解更复杂的树形结构和图算法至关重要。

二叉树的基本概念

二叉树(Binary Tree) 是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。

基本术语

  • 根节点(Root): 树的顶层节点,没有父节点
  • 叶子节点(Leaf): 没有子节点的节点
  • 内部节点(Internal Node): 至少有一个子节点的节点
  • 度(Degree): 节点的子节点数量
  • 深度(Depth): 从根到该节点的路径长度
  • 高度(Height): 从该节点到最远叶子节点的路径长度

二叉树的种类

根据不同的结构和性质,二叉树可以分为以下几种主要类型:

满二叉树

定义: 如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。

等价定义: 深度为k的满二叉树有2^k-1个节点。

特点:

  • 每一层都被完全填满
  • 所有叶子节点都在同一层
  • 节点总数 = 2^深度 - 1
  • 第i层的节点数 = 2^(i-1)

示例:

       1/   \2     3/ \   / \4   5 6   7

数学性质:

  • 设深度为h,则节点总数N = 2^h - 1
  • 叶子节点数 = 2^(h-1)
  • 内部节点数 = 2^(h-1) - 1

完全二叉树

定义: 在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。

特点:

  • 从上到下、从左到右填充节点
  • 最底层节点集中在左侧
  • 可以用数组表示(不使用索引0)
  • 父节点索引i,左子节点索引2i,右子节点索引2i+1

示例:

       1/   \2     3/ \   /4   5 6

重要应用:

  • 堆(Heap): 完全二叉树是堆的基础结构
  • 优先级队列: 底层实现通常基于堆
  • 数组表示: 可以高效地用数组存储

二叉搜索树

定义: 二叉搜索树(Binary Search Tree, BST)是一种有序的二叉树,具有以下性质:

  1. 若左子树不空,则左子树上所有结点的值均小于根结点的值
  2. 若右子树不空,则右子树上所有结点的值均大于根结点的值
  3. 左、右子树也分别为二叉搜索树

特点:

  • 中序遍历得到有序序列
  • 支持高效的查找、插入、删除操作
  • 平均时间复杂度O(log n),最坏情况O(n)

示例:

       8/   \3     10/ \     \1   6     14/ \   /4   7 13

操作复杂度:

  • 查找: O(log n) - O(n)
  • 插入: O(log n) - O(n)
  • 删除: O(log n) - O(n)

平衡二叉搜索树

定义: 平衡二叉搜索树是一种特殊的二叉搜索树,其中任意节点的左右子树高度差的绝对值不超过1。

主要类型:

  1. AVL树: 最早的平衡二叉搜索树
  2. 红黑树: 应用最广泛的平衡树
  3. B树/B+树: 用于数据库和文件系统

AVL树特点:

  • 任意节点左右子树高度差≤1
  • 插入/删除后需要重新平衡
  • 查找效率稳定在O(log n)

示例:

       8/   \4     12/ \   / \2   6 10  14/     \
1       7

实际应用与底层实现

C++ STL中的实现

基于平衡二叉搜索树的容器:

  • std::map: 有序键值对
  • std::set: 有序集合
  • std::multimap: 有序多重键值对
  • std::multiset: 有序多重集合

基于哈希表的容器:

  • std::unordered_map: 无序键值对
  • std::unordered_set: 无序集合

性能对比

操作平衡BST哈希表
查找O(log n)O(1) 平均
插入O(log n)O(1) 平均
删除O(log n)O(1) 平均
有序遍历O(n)不支持

选择建议

使用平衡BST当:

  • 需要有序遍历
  • 需要找到最接近的值
  • 内存使用要求严格

使用哈希表当:

  • 只需要快速查找
  • 不需要有序性
  • 数据量较大

总结

二叉树作为基础数据结构,其各种变体在实际应用中发挥着重要作用:

  1. 满二叉树: 结构最规整,常用于理论分析(一定是完全二叉树)
  2. 完全二叉树: 堆的基础,数组存储效率高
  3. 二叉搜索树: 有序数据的高效管理
  4. 平衡二叉搜索树: 保证操作效率的稳定

二叉树的存储方式

二叉树的存储方式主要分为两种:链式存储顺序存储。这两种方式各有优缺点,适用于不同的场景。

链式存储

定义: 链式存储通过指针将分布在内存中不同地址的节点串联起来,每个节点包含数据域和指针域。
在这里插入图片描述

节点结构:

struct TreeNode {int val;                    // 数据域TreeNode* left;             // 左子节点指针TreeNode* right;            // 右子节点指针TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

特点:

  • 节点在内存中分布不连续
  • 通过指针连接各个节点
  • 空间利用率相对较低(需要存储指针)
  • 插入、删除操作灵活
  • 适合表示任意形状的二叉树

示例:

       1/   \2     3/ \   / \4   5 6   7

链式存储的内存布局:

节点1: [val=1, left→节点2, right→节点3]
节点2: [val=2, left→节点4, right→节点5]
节点3: [val=3, left→节点6, right→节点7]
节点4: [val=4, left=null, right=null]
节点5: [val=5, left=null, right=null]
节点6: [val=6, left=null, right=null]
节点7: [val=7, left=null, right=null]

优点:

  • 结构直观,易于理解
  • 插入、删除操作方便
  • 不需要预先分配固定空间
  • 适合表示不规则树形结构

缺点:

  • 空间开销较大(每个节点需要存储指针)
  • 随机访问效率低
  • 缓存局部性差

顺序存储

定义: 顺序存储使用数组来存储二叉树,节点按照特定规则在数组中排列。

在这里插入图片描述

存储规则:

  • 根节点存储在索引0位置
  • 对于索引为i的节点:
    • 左子节点索引:2*i + 1
    • 右子节点索引:2*i + 2
    • 父节点索引:(i-1)/2(向下取整)

数组表示:

// 对于完全二叉树,可以用数组表示
int tree[] = {1, 2, 3, 4, 5, 6, 7};

索引关系:

       1/   \2     3/ \   / \4   5 6   7
索引:  0  1  2  3  4  5  6
值:    1  2  3  4  5  6  7节点关系:
- 节点1(索引0): 左子=2(索引1), 右子=3(索引2)
- 节点2(索引1): 左子=4(索引3), 右子=5(索引4)
- 节点3(索引2): 左子=6(索引5), 右子=7(索引6)

特点:

  • 节点在内存中连续分布
  • 不需要存储指针
  • 空间利用率高
  • 随机访问效率高
  • 适合完全二叉树

适用场景:

  • 完全二叉树
  • 堆(Heap)实现
  • 需要频繁随机访问的场景

局限性:

  • 对于非完全二叉树,空间浪费严重
  • 插入、删除操作复杂
  • 需要预先知道树的大小

存储方式对比

特性链式存储顺序存储
内存分布不连续连续
空间效率较低(需要指针)较高
访问效率随机访问慢随机访问快
插入删除灵活方便复杂
适用树型任意二叉树完全二叉树
缓存友好性
实现复杂度简单中等

实际应用选择

选择链式存储当:

  • 树结构不规则
  • 需要频繁插入删除
  • 内存充足
  • 追求代码简洁性

选择顺序存储当:

  • 完全二叉树
  • 需要频繁随机访问
  • 内存紧张
  • 追求访问效率

混合使用:
在实际应用中,有时会结合两种方式的优点:

  • 使用链式存储表示树结构
  • 使用数组缓存热点数据
  • 根据访问模式动态调整

二叉树的遍历方式

二叉树的遍历是访问树中所有节点的过程,主要有两种基本遍历方式:深度优先遍历广度优先遍历

深度优先遍历 (DFS)

深度优先遍历先往深走,遇到叶子节点再往回走。根据访问根节点的时机,可以分为三种:

前序遍历 (Preorder Traversal)

访问顺序: 中 → 左 → 右

特点:

  • 先访问根节点
  • 再遍历左子树
  • 最后遍历右子树
  • 常用于复制二叉树、前缀表达式

示例:

       1/   \2     3/ \   / \4   5 6   7前序遍历结果: [1, 2, 4, 5, 3, 6, 7]

应用场景:

  • 打印目录结构
  • 表达式树的前缀表示
  • 二叉树的序列化
中序遍历 (Inorder Traversal)

访问顺序: 左 → 中 → 右

特点:

  • 先遍历左子树
  • 再访问根节点
  • 最后遍历右子树
  • 对于BST,中序遍历得到有序序列

示例:

       1/   \2     3/ \   / \4   5 6   7中序遍历结果: [4, 2, 5, 1, 6, 3, 7]

应用场景:

  • 二叉搜索树的有序遍历
  • 表达式树的中缀表示
  • 二叉树的验证
后序遍历 (Postorder Traversal)

访问顺序: 左 → 右 → 中

特点:

  • 先遍历左子树
  • 再遍历右子树
  • 最后访问根节点
  • 常用于释放内存、后缀表达式

示例:

       1/   \2     3/ \   / \4   5 6   7后序遍历结果: [4, 5, 2, 6, 7, 3, 1]

应用场景:

  • 释放二叉树内存
  • 表达式树的后缀表示
  • 计算目录大小

广度优先遍历 (BFS)

广度优先遍历一层一层地访问节点,也称为层序遍历

层序遍历 (Level Order Traversal)

访问顺序: 按层从左到右访问

特点:

  • 从根节点开始,逐层访问
  • 每层从左到右访问
  • 使用队列实现
  • 常用于求树的宽度、层数

示例:

       1/   \2     3/ \   / \4   5 6   7层序遍历结果: [[1], [2, 3], [4, 5, 6, 7]]

应用场景:

  • 求二叉树的最大宽度
  • 求二叉树的最小深度
  • 打印二叉树的层次结构

遍历方式的实现

递归实现

前序遍历递归:

void preorder(TreeNode* root) {if (root == nullptr) return;// 访问根节点cout << root->val << " ";// 遍历左子树preorder(root->left);// 遍历右子树preorder(root->right);
}

中序遍历递归:

void inorder(TreeNode* root) {if (root == nullptr) return;// 遍历左子树inorder(root->left);// 访问根节点cout << root->val << " ";// 遍历右子树inorder(root->right);
}

后序遍历递归:

void postorder(TreeNode* root) {if (root == nullptr) return;// 遍历左子树postorder(root->left);// 遍历右子树postorder(root->right);// 访问根节点cout << root->val << " ";
}
迭代实现

前序遍历迭代:

vector<int> preorderTraversal(TreeNode* root) {vector<int> result;if (root == nullptr) return result;stack<TreeNode*> st;st.push(root);while (!st.empty()) {TreeNode* node = st.top();st.pop();result.push_back(node->val);if (node->right) st.push(node->right);if (node->left) st.push(node->left);}return result;
}

层序遍历迭代:

vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> result;if (root == nullptr) return result;queue<TreeNode*> q;q.push(root);while (!q.empty()) {int levelSize = q.size();vector<int> level;for (int i = 0; i < levelSize; i++) {TreeNode* node = q.front();q.pop();level.push_back(node->val);if (node->left) q.push(node->left);if (node->right) q.push(node->right);}result.push_back(level);}return result;
}

遍历方式的选择

选择深度优先遍历当:

  • 需要访问所有节点
  • 问题具有递归性质
  • 内存使用要求严格

选择广度优先遍历当:

  • 需要按层处理
  • 求最短路径
  • 需要找到最近的节点

记忆技巧:

  • 前中后序指的是根节点的访问时机
  • 前序: 根节点在最前面
  • 中序: 根节点在中间
  • 后序: 根节点在最后面

二叉树定义

链式存储的二叉树节点的定义方式。

C++代码如下:

struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

python:

class TreeNode:def __init__(self, val, left = None, right = None):self.val = valself.left = leftself.right = right
http://www.lqws.cn/news/515881.html

相关文章:

  • python的kivy框架界面布局方法详解
  • 智能手机是人类的寄生物
  • 高通手机跑AI系列之——人脸变化算法
  • 机器学习复习
  • 《MySQL 技术内幕(第5版)》逐章精华笔记第七章
  • 【学习笔记】3.3 Decoder-Only PLM
  • 芯片战争升级:进口马维尔VS自研中兴微,解码格行随身WiFi性能密码,格行随身WIFI到底行不行
  • 《从0到1:C/C++音视频开发自学指南》
  • 大语言模型的通用局限性与全球技术演进
  • 【智能协同云图库】智能协同云图库第二弹:用户管理系统后端设计与接口开发
  • CSS基础3
  • 将Python Tkinter程序转换为手机可运行的Web应用 - 详细教程
  • Nginx + Tomcat 负载均衡搭建
  • 数字孪生技术引领UI前端设计潮流:沉浸式体验的新篇章
  • CVPR-2025 | 上交拥挤无序环境下的具身导航最新基准!RoboSense:以机器人为中心的具身感知与导航大规模数据集
  • POJ3050-Hopscotch(穷竭搜索:DFS)
  • 构造函数和析构函数
  • 基于SSM框架+mysql实现的监考安排管理系统[含源码+数据库+项目开发技术手册]
  • 【iSAQB软件架构】架构模式
  • 微分转动与角速度:三维空间中的矩阵向量形式及其Python实现
  • Fiddler抓包工具与性能调优:如何结合Charles与Wireshark优化网络调试
  • 【机器学习深度学习】常见激活函数
  • AudioTrack使用
  • 网络安全就业方向与现实发展分析:机遇、挑战与未来趋势
  • Three.js项目实战:从零搭建小米SU7三维汽车
  • 《内心强大不怯场》读书笔记5
  • 南宫NG·28(中国)相信品牌力量/Vue 3 中的状态管理 —— 从 Vuex 到 Pinia 的全面过渡
  • NCCN Guidelines Navigator:数智化工具引领肿瘤精准治疗新纪元
  • 运维打铁: 系统内核参数调优实战
  • ‌RESTful API 设计规范