算法与数据结构之二叉树(Binary Tree)


一、什么是二叉树(Binary Tree)

定义:

二叉树是一种树形结构,其中每个节点最多有两个子节点,分别称为左子节点(left child)右子节点(right child)

每个节点最多有 2 个分支,这两个分支可以为空。


例子

复制代码
        A
       / \
      B   C
     / \   \
    D   E   F
  • A 是根节点(Root)
  • B、C 是 A 的孩子(Child)
  • D、E、F 是叶子节点(Leaf)
  • B 的父节点是 A
  • D 的兄弟节点是 E

二、二叉树的基本术语

名称 含义
根节点(Root) 没有父节点的节点
叶子节点(Leaf) 没有子节点的节点
父节点(Parent) 有子节点的节点
子节点(Child) 父节点的下属节点
兄弟节点(Sibling) 具有相同父节点的节点
节点深度(Depth) 从根到该节点的边数
节点高度(Height) 从该节点到叶子的最长路径
层级(Level) 节点的深度 + 1
满二叉树(Full Binary Tree) 所有非叶子节点都有两个子节点
完全二叉树(Complete Binary Tree) 除最后一层外,其他层节点全满,最后一层从左到右依次填充

三、二叉树的分类

类型 定义
普通二叉树 任意结构
满二叉树 所有非叶节点都有两个孩子,叶子都在同一层
完全二叉树 除最后一层外全满,最后一层从左向右填充
二叉搜索树(BST) 左子树 < 根 < 右子树
平衡二叉树(AVL Tree) 左右子树高度差 ≤ 1
红黑树(Red-Black Tree) 一种自平衡二叉搜索树
线索二叉树(Threaded Binary Tree) 用空指针指向前驱/后继节点以加速遍历

四、二叉树的性质

设二叉树中第 i i i 层最多有 2 i − 1 2^{i-1} 2i−1 个节点:

  1. 第 i i i 层最多节点数:
    N i = 2 i − 1 N_i = 2^{i-1} Ni=2i−1

  2. 深度为 k k k 的二叉树最多节点数:
    N max ⁡ = 2 k − 1 N_{\max} = 2^k - 1 Nmax=2k−1

  3. 具有 n n n 个节点的二叉树,高度至少为:
    h min ⁡ = ⌈ log ⁡ 2 ( n + 1 ) ⌉ h_{\min} = \lceil \log_2(n + 1) \rceil hmin=⌈log2(n+1)⌉

  4. 若叶子节点数为 n 0 n_0 n0,度为 2 的节点数为 n 2 n_2 n2,则:
    n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1


五、二叉树的存储方式

1.顺序存储(数组存储)

适用于 完全二叉树

设根节点下标为 1,则:

  • 左孩子:2*i
  • 右孩子:2*i + 1
  • 父节点:i / 2

例如:

复制代码
        A(1)
       /   \
   B(2)     C(3)
   / \     /
D(4) E(5) F(6)

数组存储:

复制代码
[ _, A, B, C, D, E, F ]

2.链式存储(指针结构)

cpp 复制代码
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

每个节点保存两个指针,分别指向左、右子节点。


六、二叉树的遍历(Traversal)

遍历是访问二叉树所有节点的过程。

1.深度优先遍历(DFS)

  • 前序遍历(Preorder) :根 → 左 → 右

    顺序:Root, Left, Right

  • 中序遍历(Inorder) :左 → 根 → 右

    顺序:Left, Root, Right

  • 后序遍历(Postorder) :左 → 右 → 根

    顺序:Left, Right, Root


示例

复制代码
        A
       / \
      B   C
     / \   \
    D   E   F
遍历类型 结果顺序
前序 A B D E C F
中序 D B E A C F
后序 D E B F C A

递归实现(以中序为例)

cpp 复制代码
void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);
    cout << root->val << " ";
    inorder(root->right);
}

2.广度优先遍历(BFS)

层序遍历(Level Order Traversal)

利用队列(Queue)实现。

cpp 复制代码
void levelOrder(TreeNode* root) {
    if (!root) return;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        TreeNode* node = q.front(); q.pop();
        cout << node->val << " ";
        if (node->left) q.push(node->left);
        if (node->right) q.push(node->right);
    }
}

结果:

复制代码
A B C D E F

七、二叉树的应用

应用场景 示例
表达式解析树 编译器、计算器((a+b)*c
二叉搜索树 BST 查找、排序
堆(Heap) 优先队列(最小堆、最大堆)
哈夫曼树(Huffman Tree) 数据压缩
决策树(Decision Tree) 机器学习
KD 树 / Octree 空间索引与点云处理

八、构建与销毁

cpp 复制代码
TreeNode* buildTree() {
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);
    return root;
}

void destroy(TreeNode* root) {
    if (!root) return;
    destroy(root->left);
    destroy(root->right);
    delete root;
}

九、复杂度与性能

操作 平均复杂度 最坏情况
插入 O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(n) O(n)(链化)
删除 O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(n) O(n)
查找 O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(n) O(n)
遍历 O ( n ) O(n) O(n) O ( n ) O(n) O(n)

为避免退化,可使用 AVL TreeRed-Black Tree


十、总结表

分类 特点 示例
普通二叉树 任意结构 一般树结构
满二叉树 每个非叶子都有两个孩子 理论分析
完全二叉树 从左至右填满
二叉搜索树 左 < 根 < 右 查找树
平衡树 高度差≤1 AVL、红黑树
线索树 空指针指向前驱/后继 遍历优化

十一、 综合示例-二叉搜索树(BST)

下面实现l一个完整的 Binary Search Tree 类,包括:

  • 节点定义
  • 插入节点
  • 查找节点
  • 删除节点
  • 三种深度优先遍历(前序 / 中序 / 后序)
  • 层序遍历
  • 销毁树

1.数据结构定义

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;

// 二叉树节点定义
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

2.二叉搜索树类定义

cpp 复制代码
class BinarySearchTree {
private:
    TreeNode* root;

    // 插入节点(递归)
    TreeNode* insert(TreeNode* node, int val) {
        if (!node) return new TreeNode(val);
        if (val < node->val) node->left = insert(node->left, val);
        else if (val > node->val) node->right = insert(node->right, val);
        return node;
    }

    // 查找节点(递归)
    bool search(TreeNode* node, int val) {
        if (!node) return false;
        if (val == node->val) return true;
        else if (val < node->val) return search(node->left, val);
        else return search(node->right, val);
    }

    // 找到最小值节点
    TreeNode* findMin(TreeNode* node) {
        while (node->left) node = node->left;
        return node;
    }

    // 删除节点
    TreeNode* remove(TreeNode* node, int val) {
        if (!node) return nullptr;
        if (val < node->val) node->left = remove(node->left, val);
        else if (val > node->val) node->right = remove(node->right, val);
        else {
            // 情况 1:无子节点
            if (!node->left && !node->right) {
                delete node;
                return nullptr;
            }
            // 情况 2:只有一个子节点
            else if (!node->left) {
                TreeNode* temp = node->right;
                delete node;
                return temp;
            } else if (!node->right) {
                TreeNode* temp = node->left;
                delete node;
                return temp;
            }
            // 情况 3:有两个子节点
            else {
                TreeNode* minNode = findMin(node->right);
                node->val = minNode->val;
                node->right = remove(node->right, minNode->val);
            }
        }
        return node;
    }

    // 遍历函数
    void preorder(TreeNode* node) {
        if (!node) return;
        cout << node->val << " ";
        preorder(node->left);
        preorder(node->right);
    }

    void inorder(TreeNode* node) {
        if (!node) return;
        inorder(node->left);
        cout << node->val << " ";
        inorder(node->right);
    }

    void postorder(TreeNode* node) {
        if (!node) return;
        postorder(node->left);
        postorder(node->right);
        cout << node->val << " ";
    }

    // 销毁整棵树
    void destroy(TreeNode* node) {
        if (!node) return;
        destroy(node->left);
        destroy(node->right);
        delete node;
    }

public:
    BinarySearchTree() : root(nullptr) {}
    ~BinarySearchTree() { destroy(root); }

    void insert(int val) { root = insert(root, val); }
    void remove(int val) { root = remove(root, val); }
    bool search(int val) { return search(root, val); }

    // 层序遍历
    void levelOrder() {
        if (!root) return;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            TreeNode* node = q.front(); q.pop();
            cout << node->val << " ";
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }

    // 打印不同遍历结果
    void printOrders() {
        cout << "Preorder: "; preorder(root); cout << "\n";
        cout << "Inorder: "; inorder(root); cout << "\n";
        cout << "Postorder: "; postorder(root); cout << "\n";
        cout << "Level order: "; levelOrder(); cout << "\n";
    }
};

3.主程序示例

cpp 复制代码
int main() {
    BinarySearchTree bst;

    // 插入节点
    bst.insert(50);
    bst.insert(30);
    bst.insert(70);
    bst.insert(20);
    bst.insert(40);
    bst.insert(60);
    bst.insert(80);

    cout << "Binary Search Tree traversals:\n";
    bst.printOrders();

    cout << "\nSearch for 60: " << (bst.search(60) ? "Found" : "Not Found") << "\n";

    cout << "\nRemove 70\n";
    bst.remove(70);

    cout << "After deletion:\n";
    bst.printOrders();
    return 0;
}

输出结果

复制代码
Binary Search Tree traversals:
Preorder: 50 30 20 40 70 60 80
Inorder: 20 30 40 50 60 70 80
Postorder: 20 40 30 60 80 70 50
Level order: 50 30 70 20 40 60 80

Search for 60: Found

Remove 70
After deletion:
Preorder: 50 30 20 40 80 60
Inorder: 20 30 40 50 60 80
Postorder: 20 40 30 60 80 50
Level order: 50 30 80 20 40 60

示例分析

操作 说明
插入 构建一棵 BST,左小右大
查找 二分性质快速定位
删除 三种情况(无子、单子、双子)
遍历 四种遍历全面展示树结构
销毁 递归释放所有节点,避免内存泄漏

相关推荐
小龙报2 小时前
《算法通关指南:算法基础篇 --- 一维前缀和 — 1. 【模板】一维前缀和,2.最大子段和》
c语言·数据结构·c++·算法·职场和发展·创业创新·visual studio
树在风中摇曳2 小时前
LeetCode 1658 | 将 x 减到 0 的最小操作数(C语言滑动窗口解法)
c语言·算法·leetcode
不夜牛仔3 小时前
算法笔记17 - 贪心算法介绍与思路 | 路灯摆放问题 | 活动安排问题 | 最低字典序拼接 | 金条分割问题 | 项目投资问题
笔记·算法·贪心算法
.柒宇.3 小时前
力扣hoT100之找到字符串中所有字母异位词(java版)
java·数据结构·算法·leetcode
松岛雾奈.2304 小时前
机器学习--KNN算法中的距离、范数、正则化
人工智能·算法·机器学习
兮山与4 小时前
算法33.0
算法
Brduino脑机接口技术答疑4 小时前
支持向量机(SVM)在脑电情绪识别中的学术解析与研究进展
人工智能·算法·机器学习·支持向量机·数据分析
xier_ran4 小时前
深度学习:Mini-batch 大小选择与 SGD 和 GD
人工智能·算法·机器学习
王璐WL4 小时前
【数据结构】单链表的经典算法题
数据结构·算法