一、什么是二叉树?
二叉树(Binary Tree)是一种重要的树形数据结构,它的每个节点最多有两个子节点,分别称为左子节点 和右子节点。二叉树的子树具有明确的左右顺序,不能随意交换。
1.1 二叉树的基本特性
-
第 i 层最多有 2 个节点(根节点为第1层);
-
深度为 k 的二叉树最多有 2 - 1 个节点(深度为树的最大层数);
-
对于任意一棵二叉树,若叶子节点数为 n₀ ,度为2的节点数为 n₂ ,则必有n₀ = n₂ + 1。
1.2 常见二叉树类型
-
满二叉树 :深度为 k 且节点数为 2 - 1 的二叉树(每一层都充满节点);
-
完全二叉树 :深度为 k ,前 k-1 层为满二叉树,第 k 层节点从左到右连续排列(便于数组存储);
-
二叉搜索树(BST):左子树所有节点值 < 根节点值,右子树所有节点值 > 根节点值,左右子树也为二叉搜索树(支持高效查找、插入);
-
平衡二叉树(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
空树测试:当前树为空!
六、常见问题与注意事项
-
递归栈溢出:对于深度极大的二叉树,递归操作可能导致栈溢出,此时需改用非递归实现(如用栈模拟递归);
-
重复节点处理:本文实现中不允许重复节点,若需支持重复节点,可修改插入逻辑(如将重复节点插入左子树或右子树);
-
平衡问题:普通二叉搜索树在极端情况下(如有序插入)会退化为链表,查找效率从 O(logn) 降至 O(n),此时需使用平衡二叉树(AVL树、红黑树);
-
数组存储适用场景 :完全二叉树可用数组存储,节点 i 的左子节点为 2i+1 ,右子节点为 2i+2 ,根节点为 0,节省指针占用的内存。
七、总结
二叉树是树形结构的基础,二叉搜索树作为其重要变种,支持高效的查找、插入、删除操作。本文通过C++模板类实现了二叉搜索树的完整功能,包括多种遍历方式、节点操作、内存管理等核心模块,并提供了详细的测试代码。