二叉树详解及C++实现

一、什么是二叉树?

二叉树(Binary Tree)是一种重要的树形数据结构,它的每个节点最多有两个子节点,分别称为左子节点右子节点。二叉树的子树具有明确的左右顺序,不能随意交换。

1.1 二叉树的基本特性

  • i 层最多有 2 个节点(根节点为第1层);

  • 深度为 k 的二叉树最多有 2 - 1 个节点(深度为树的最大层数);

  • 对于任意一棵二叉树,若叶子节点数为 n₀ ,度为2的节点数为 n₂ ,则必有n₀ = n₂ + 1

1.2 常见二叉树类型

  1. 满二叉树 :深度为 k 且节点数为 2 - 1 的二叉树(每一层都充满节点);

  2. 完全二叉树 :深度为 k ,前 k-1 层为满二叉树,第 k 层节点从左到右连续排列(便于数组存储);

  3. 二叉搜索树(BST):左子树所有节点值 < 根节点值,右子树所有节点值 > 根节点值,左右子树也为二叉搜索树(支持高效查找、插入);

  4. 平衡二叉树(AVL树):左右子树深度差(平衡因子)的绝对值不超过1,保证查找效率稳定。

二、二叉树的C++实现核心思路

二叉树的实现有两种常见方式:链式存储 (最常用)和数组存储(适合完全二叉树)。本文重点讲解链式存储,通过结构体/类定义节点,用指针关联父子节点。

2.1 节点结构定义

每个节点包含3部分:数据域、左子节点指针、右子节点指针。用模板类实现可适配多种数据类型。

2.2 核心操作

二叉树的核心操作包括:初始化、节点插入、遍历(前序/中序/后序/层序)、节点查找、节点删除、求深度/节点数等。

三、完整C++实现代码

3.1 头文件与节点定义

cpp 复制代码
#include <iostream>
#include <queue>  // 用于层序遍历
using namespace std;

// 二叉树节点模板类
template <typename T>
class BinaryTreeNode {
public:
    T data;                     // 数据域
    BinaryTreeNode<T>* left;    // 左子节点指针
    BinaryTreeNode<T>* right;   // 右子节点指针

    // 构造函数
    BinaryTreeNode(T val) : data(val), left(nullptr), right(nullptr) {}
};

// 二叉搜索树类(继承节点特性,实现核心操作)
template <typename T>
class BinarySearchTree {
private:
    BinaryTreeNode<T>* root;  // 根节点指针

    // 辅助函数:递归插入节点(私有,对外隐藏实现)
    BinaryTreeNode<T>* insertNode(BinaryTreeNode<T>* node, T val) {
        if (node == nullptr) {
            return new BinaryTreeNode<T>(val);  // 空树则创建新节点作为根
        }

        // 按二叉搜索树规则插入左/右子树
        if (val < node->data) {
            node->left = insertNode(node->left, val);
        } else if (val > node->data) {
            node->right = insertNode(node->right, val);
        }
        // 若val已存在,不插入(避免重复)
        return node;
    }

    // 辅助函数:递归前序遍历(根-左-右)
    void preOrderTraversal(BinaryTreeNode<T>* node) const {
        if (node != nullptr) {
            cout << node->data << " ";    // 访问根节点
            preOrderTraversal(node->left);  // 遍历左子树
            preOrderTraversal(node->right); // 遍历右子树
        }
    }

    // 辅助函数:递归中序遍历(左-根-右)
    void inOrderTraversal(BinaryTreeNode<T>* node) const {
        if (node != nullptr) {
            inOrderTraversal(node->left);   // 遍历左子树
            cout << node->data << " ";    // 访问根节点
            inOrderTraversal(node->right);  // 遍历右子树
        }
    }

    // 辅助函数:递归后序遍历(左-右-根)
    void postOrderTraversal(BinaryTreeNode<T>* node) const {
        if (node != nullptr) {
            postOrderTraversal(node->left); // 遍历左子树
            postOrderTraversal(node->right); // 遍历右子树
            cout << node->data << " ";    // 访问根节点
        }
    }

    // 辅助函数:递归查找节点
    BinaryTreeNode<T>* searchNode(BinaryTreeNode<T>* node, T val) const {
        if (node == nullptr || node->data == val) {
            return node;  // 空树或找到节点,返回该节点
        }
        if (val < node->data) {
            return searchNode(node->left, val);  // 向左子树查找
        } else {
            return searchNode(node->right, val); // 向右子树查找
        }
    }

    // 辅助函数:找到右子树的最小节点(用于删除节点时替换)
    BinaryTreeNode<T>* findMinNode(BinaryTreeNode<T>* node) const {
        BinaryTreeNode<T>* current = node;
        // 循环找到最左节点(最小节点)
        while (current != nullptr && current->left != nullptr) {
            current = current->left;
        }
        return current;
    }

    // 辅助函数:递归删除节点
    BinaryTreeNode<T>* deleteNode(BinaryTreeNode<T>* node, T val) {
        if (node == nullptr) {
            return nullptr;  // 空树,直接返回
        }

        // 1. 查找要删除的节点
        if (val < node->data) {
            node->left = deleteNode(node->left, val);
        } else if (val > node->data) {
            node->right = deleteNode(node->right, val);
        } else {
            // 2. 找到要删除的节点,分三种情况处理
            // 情况1:叶子节点(无左右子节点)
            if (node->left == nullptr && node->right == nullptr) {
                delete node;
                return nullptr;
            }
            // 情况2:只有一个子节点(左或右)
            else if (node->left == nullptr) {
                BinaryTreeNode<T>* temp = node->right;
                delete node;
                return temp;
            } else if (node->right == nullptr) {
                BinaryTreeNode<T>* temp = node->left;
                delete node;
                return temp;
            }
            // 情况3:有两个子节点
            else {
                // 找到右子树的最小节点(或左子树的最大节点)作为替换节点
                BinaryTreeNode<T>* temp = findMinNode(node->right);
                node->data = temp->data;  // 替换数据
                // 删除替换节点(替换节点必为叶子或只有一个子节点)
                node->right = deleteNode(node->right, temp->data);
            }
        }
        return node;
    }

    // 辅助函数:递归计算树的深度
    int treeDepth(BinaryTreeNode<T>* node) const {
        if (node == nullptr) {
            return 0;  // 空树深度为0
        }
        int leftDepth = treeDepth(node->left);   // 左子树深度
        int rightDepth = treeDepth(node->right); // 右子树深度
        return max(leftDepth, rightDepth) + 1;   // 当前深度=子树最大深度+1
    }

    // 辅助函数:递归计算节点总数
    int nodeCount(BinaryTreeNode<T>* node) const {
        if (node == nullptr) {
            return 0;
        }
        return nodeCount(node->left) + nodeCount(node->right) + 1;  // 左+右+当前节点
    }

    // 辅助函数:递归销毁树(避免内存泄漏)
    void destroyTree(BinaryTreeNode<T>* node) {
        if (node != nullptr) {
            destroyTree(node->left);   // 先销毁左子树
            destroyTree(node->right);  // 再销毁右子树
            delete node;               // 最后销毁当前节点
        }
    }

public:
    // 构造函数:初始化空树
    BinarySearchTree() : root(nullptr) {}

    // 析构函数:销毁树,释放内存
    ~BinarySearchTree() {
        destroyTree(root);
    }

    // 对外接口:插入节点
    void insert(T val) {
        root = insertNode(root, val);
    }

    // 对外接口:前序遍历
    void preOrder() const {
        cout << "前序遍历(根-左-右):";
        preOrderTraversal(root);
        cout << endl;
    }

    // 对外接口:中序遍历(二叉搜索树中序遍历为升序)
    void inOrder() const {
        cout << "中序遍历(左-根-右):";
        inOrderTraversal(root);
        cout << endl;
    }

    // 对外接口:后序遍历
    void postOrder() const {
        cout << "后序遍历(左-右-根):";
        postOrderTraversal(root);
        cout << endl;
    }

    // 对外接口:层序遍历(广度优先,用队列实现)
    void levelOrder() const {
        if (root == nullptr) {
            cout << "树为空!" << endl;
            return;
        }
        cout << "层序遍历(广度优先):";
        queue<BinaryTreeNode<T>*> q;
        q.push(root);  // 根节点入队

        while (!q.empty()) {
            BinaryTreeNode<T>* current = q.front();
            q.pop();
            cout << current->data << " ";  // 访问当前节点

            // 左子节点入队
            if (current->left != nullptr) {
                q.push(current->left);
            }
            // 右子节点入队
            if (current->right != nullptr) {
                q.push(current->right);
            }
        }
        cout << endl;
    }

    // 对外接口:查找节点
    bool search(T val) const {
        BinaryTreeNode<T>* result = searchNode(root, val);
        return result != nullptr;
    }

    // 对外接口:删除节点
    void remove(T val) {
        root = deleteNode(root, val);
    }

    // 对外接口:获取树的深度
    int depth() const {
        return treeDepth(root);
    }

    // 对外接口:获取节点总数
    int count() const {
        return nodeCount(root);
    }

    // 对外接口:判断树是否为空
    bool isEmpty() const {
        return root == nullptr;
    }
};

3.2 测试代码(主函数)

cpp 复制代码
int main() {
    // 创建二叉搜索树对象(存储int类型)
    BinarySearchTree<int> bst;

    // 插入节点
    int arr[] = {5, 3, 7, 2, 4, 6, 8};
    for (int val : arr) {
        bst.insert(val);
    }

    // 遍历测试
    bst.preOrder();   // 预期:5 3 2 4 7 6 8
    bst.inOrder();    // 预期:2 3 4 5 6 7 8(升序)
    bst.postOrder();  // 预期:2 4 3 6 8 7 5
    bst.levelOrder(); // 预期:5 3 7 2 4 6 8

    // 查找测试
    int searchVal = 4;
    if (bst.search(searchVal)) {
        cout << "找到节点:" << searchVal << endl;
    } else {
        cout << "未找到节点:" << searchVal << endl;
    }

    // 深度和节点数测试
    cout << "树的深度:" << bst.depth() << endl;  // 预期:3
    cout << "节点总数:" << bst.count() << endl;  // 预期:7

    // 删除节点测试(删除叶子节点4)
    bst.remove(4);
    cout << "删除节点4后,中序遍历:";
    bst.inOrder();  // 预期:2 3 5 6 7 8

    // 删除节点测试(删除有一个子节点的3)
    bst.remove(3);
    cout << "删除节点3后,中序遍历:";
    bst.inOrder();  // 预期:2 5 6 7 8

    // 删除节点测试(删除有两个子节点的7)
    bst.remove(7);
    cout << "删除节点7后,中序遍历:";
    bst.inOrder();  // 预期:2 5 6 8

    // 空树测试
    BinarySearchTree<int> emptyBst;
    if (emptyBst.isEmpty()) {
        cout << "空树测试:当前树为空!" << endl;
    }

    return 0;
}

四、代码说明与核心要点

4.1 模板类的使用

采用模板类 BinaryTreeNode<T>BinarySearchTree<T>,可灵活适配 int、float、string 等多种数据类型,提高代码复用性。

4.2 递归与非递归实现

  • 递归实现:插入、遍历、查找、删除、求深度等操作均采用递归,代码简洁直观,符合二叉树的递归特性;

  • 非递归实现:层序遍历采用队列(广度优先搜索BFS),避免递归栈溢出问题,适合深度较大的树。

4.3 内存管理

实现 destroyTree 递归销毁函数,在析构函数中调用,确保所有节点内存被释放,避免内存泄漏。

4.4 二叉搜索树的特性

中序遍历二叉搜索树可得到升序序列,这是二叉搜索树的核心特性,可用于快速验证树的正确性,也可基于此实现排序功能。

五、编译与运行

5.1 编译命令

将代码保存为 binary_tree.cpp,使用 g++ 编译:

bash 复制代码
g++ binary_tree.cpp -o binary_tree

5.2 运行结果

text 复制代码
前序遍历(根-左-右):5 3 2 4 7 6 8 
中序遍历(左-根-右):2 3 4 5 6 7 8 
后序遍历(左-右-根):2 4 3 6 8 7 5 
层序遍历(广度优先):5 3 7 2 4 6 8 
找到节点:4
树的深度:3
节点总数:7
删除节点4后,中序遍历:2 3 5 6 7 8 
删除节点3后,中序遍历:2 5 6 7 8 
删除节点7后,中序遍历:2 5 6 8 
空树测试:当前树为空!

六、常见问题与注意事项

  1. 递归栈溢出:对于深度极大的二叉树,递归操作可能导致栈溢出,此时需改用非递归实现(如用栈模拟递归);

  2. 重复节点处理:本文实现中不允许重复节点,若需支持重复节点,可修改插入逻辑(如将重复节点插入左子树或右子树);

  3. 平衡问题:普通二叉搜索树在极端情况下(如有序插入)会退化为链表,查找效率从 O(logn) 降至 O(n),此时需使用平衡二叉树(AVL树、红黑树);

  4. 数组存储适用场景 :完全二叉树可用数组存储,节点 i 的左子节点为 2i+1 ,右子节点为 2i+2 ,根节点为 0,节省指针占用的内存。

七、总结

二叉树是树形结构的基础,二叉搜索树作为其重要变种,支持高效的查找、插入、删除操作。本文通过C++模板类实现了二叉搜索树的完整功能,包括多种遍历方式、节点操作、内存管理等核心模块,并提供了详细的测试代码。

相关推荐
xiaoxiaoxiaolll1 天前
《Light: Science & Applications》超表面偏振态与偏振度完全独立控制新范式
学习
Ahtacca1 天前
解决服务间通信难题:Spring Boot 中 HttpClient 的标准使用姿势
java·spring boot·后端
宋情写1 天前
JavaAI05-Chain、MCP
java·人工智能
CSDN_RTKLIB1 天前
C++取余符号%
开发语言·c++
仍然.1 天前
JavaDataStructure---排序
数据结构·算法·排序算法
C++chaofan1 天前
Java 并发编程:synchronized 优化原理深度解析
java·开发语言·jvm·juc·synchronized·
better_liang1 天前
每日Java面试场景题知识点之-Docker容器化部署
java·docker·微服务·devops·容器化·企业级开发
WBluuue1 天前
Codeforces Good Bye 2025 Div1+2(ABCDE)
c++·算法
悟空码字1 天前
SpringBoot整合Kafka,实现高可用消息队列集群
java·spring boot·后端