【C++进阶】二叉树进阶
【C++进阶】二叉树进阶
1. 介绍
二叉树在数据结构阶段已经学习过,本节取名"二叉树进阶"主要基于以下考虑:
- 承上启下:map和set的实现需要二叉搜索树作为基础
- 加深理解:理解二叉搜索树特性有助于掌握更复杂的树结构
- 时机合适:部分二叉树面试题难度较大,现在讲解更易吸收
- 工具优势:C++相比C语言更方便处理复杂数据结构
本节将通过二叉搜索树对二叉树知识进行系统性总结和提升。
2. 二叉搜索树(Binary Search Tree)
2.1 二叉搜索树概念
二叉搜索树(BST)又称二叉排序树或二叉查找树,它具有以下性质:
- 非空左子树的所有节点值都小于根节点的值
- 非空右子树的所有节点值都大于根节点的值
- 左右子树也都是二叉搜索树
// 二叉搜索树示例
// 8
// / \
// 3 10
// / \ \
// 1 6 14
// / \ /
// 4 7 13
2.2 二叉搜索树基本操作
2.2.1 查找操作
查找过程:
- 从根节点开始比较
- 目标值 > 当前节点值 → 向右子树查找
- 目标值 < 当前节点值 → 向左子树查找
- 目标值 = 当前节点值 → 查找成功
- 遇到空节点 → 查找失败
时间复杂度:O(h),h为树的高度
2.2.2 插入操作
插入过程:
- 树为空 → 直接创建根节点
- 树不为空 → 按BST性质找到插入位置
- 创建新节点并插入到合适位置
// 插入序列:8, 3, 1, 10, 6, 4, 7, 14, 13
// 最终形成的BST如上图所示
2.2.3 删除操作
删除节点分为三种情况:
- 叶子节点:直接删除
- 只有一个子节点:用子节点替代
- 有两个子节点:用前驱或后继节点替代
替换法删除:找到右子树的最小节点(或左子树的最大节点),用其值替换待删除节点,然后删除那个最小(大)节点。
// 删除示例:
// 删除7(叶子节点)→ 直接删除
// 删除14(有一个子节点)→ 用13替代
// 删除3、8(有两个子节点)→ 用前驱/后继替代
2.3 二叉搜索树实现
2.3.1 节点结构定义
template<class T>
struct BSTNode {BSTNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _data(data){}BSTNode<T>* _pLeft;BSTNode<T>* _pRight;T _data;
};
2.3.2 二叉搜索树类定义
template<class T>
class BSTree {typedef BSTNode<T> Node;typedef Node* PNode;
public:BSTree() : _pRoot(nullptr) {}~BSTree();// 查找操作PNode Find(const T& data) {PNode pCur = _pRoot;while (pCur) {if (data == pCur->_data) {return pCur;} else if (data < pCur->_data) {pCur = pCur->_pLeft;} else {pCur = pCur->_pRight;}}return nullptr;}// 插入操作bool Insert(const T& data) {// 树为空的情况if (nullptr == _pRoot) {_pRoot = new Node(data);return true;}// 查找插入位置PNode pCur = _pRoot;PNode pParent = nullptr;while (pCur) {pParent = pCur;if (data < pCur->_data) {pCur = pCur->_pLeft;} else if (data > pCur->_data) {pCur = pCur->_pRight;} else {// 元素已存在return false;}}// 创建新节点并插入pCur = new Node(data);if (data < pParent->_data) {pParent->_pLeft = pCur;} else {pParent->_pRight = pCur;}return true;}// 删除操作bool Erase(const T& data) {if (nullptr == _pRoot) return false;// 查找待删除节点PNode pCur = _pRoot;PNode pParent = nullptr;while (pCur) {if (data == pCur->_data) {break;} else if (data < pCur->_data) {pParent = pCur;pCur = pCur->_pLeft;} else {pParent = pCur;pCur = pCur->_pRight;}}if (nullptr == pCur) return false; // 节点不存在// 情况1:左子树为空if (nullptr == pCur->_pLeft) {if (pCur == _pRoot) {_pRoot = pCur->_pRight;} else {if (pParent->_pLeft == pCur) {pParent->_pLeft = pCur->_pRight;} else {pParent->_pRight = pCur->_pRight;}}delete pCur;}// 情况2:右子树为空else if (nullptr == pCur->_pRight) {if (pCur == _pRoot) {_pRoot = pCur->_pLeft;} else {if (pParent->_pLeft == pCur) {pParent->_pLeft = pCur->_pLeft;} else {pParent->_pRight = pCur->_pLeft;}}delete pCur;}// 情况3:左右子树都不为空else {// 找右子树的最小节点(最左节点)PNode minParent = pCur;PNode minNode = pCur->_pRight;while (minNode->_pLeft) {minParent = minNode;minNode = minNode->_pLeft;}// 用最小值替换当前节点pCur->_data = minNode->_data;// 删除最小节点if (minParent->_pLeft == minNode) {minParent->_pLeft = minNode->_pRight;} else {minParent->_pRight = minNode->_pRight;}delete minNode;}return true;}// 中序遍历void InOrder() {_InOrder(_pRoot);cout << endl;}private:void _InOrder(PNode pRoot) {if (pRoot) {_InOrder(pRoot->_pLeft);cout << pRoot->_data << " ";_InOrder(pRoot->_pRight);}}PNode _pRoot;
};
2.4 二叉搜索树的应用
2.4.1 K模型(纯Key模型)
特点:只存储关键词Key,用于判断元素是否存在。
应用场景:
- 单词拼写检查
- 敏感词过滤
- 存在性判断
// 单词拼写检查示例
BSTree<string> spellChecker;
// 加载词库中的所有单词
spellChecker.Insert("apple");
spellChecker.Insert("banana");
spellChecker.Insert("orange");string word;
while (cin >> word) {if (spellChecker.Find(word)) {cout << "拼写正确" << endl;} else {cout << "拼写错误" << endl;}
}
2.4.2 KV模型(Key-Value模型)
特点:存储键值对<Key, Value>,通过Key快速查找Value。
应用场景:
- 英汉词典
- 单词计数
- 缓存系统
// KV节点定义
template<class K, class V>
struct BSTNode {BSTNode(const K& key = K(), const V& value = V()): _pLeft(nullptr), _pRight(nullptr), _key(key), _value(value){}BSTNode<K, V>* _pLeft;BSTNode<K, V>* _pRight;K _key;V _value;
};// 英汉词典应用
void TestDictionary() {BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("sort", "排序");string word;while (cin >> word) {auto ret = dict.Find(word);if (ret == nullptr) {cout << "单词不存在: " << word << endl;} else {cout << word << " 的中文翻译: " << ret->_value << endl;}}
}// 单词计数应用
void TestWordCount() {string arr[] = {"苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉"};BSTree<string, int> countTree;for (const auto& str : arr) {auto ret = countTree.Find(str);if (ret == nullptr) {countTree.Insert(str, 1); // 第一次出现} else {ret->_value++; // 计数增加}}countTree.InOrder(); // 输出每种水果的出现次数
}
2.5 二叉搜索树的性能分析
2.5.1 性能影响因素
二叉搜索树的性能主要取决于树的高度,而树的高度又受插入顺序影响:
// 最优情况:完全二叉树
// 插入顺序:4, 2, 6, 1, 3, 5, 7
// 4
// / \
// 2 6
// / \ / \
// 1 3 5 7// 最差情况:单支树
// 插入顺序:1, 2, 3, 4, 5, 6, 7
// 1
// \
// 2
// \
// 3
// \
// 4
2.5.2 时间复杂度分析
| 情况 | 树形态 | 平均查找长度 | 时间复杂度 |
|---|---|---|---|
| 最优 | 完全二叉树 | log₂N | O(logN) |
| 平均 | 随机树 | 约1.39log₂N | O(logN) |
| 最差 | 单支树 | N/2 | O(N) |
2.5.3 性能优化方向
当二叉搜索树退化为单支树时,性能会急剧下降。为了解决这个问题,后续发展出:
- AVL树:严格平衡的二叉搜索树
- 红黑树:近似平衡的二叉搜索树(STL map/set的实现基础)
- B树系列:多路平衡搜索树(数据库索引基础)
3. 二叉树进阶面试题
以下题目更适合使用C++完成,难度较大但非常经典:
3.1 基础遍历与构造
-
二叉树创建字符串
- 根据二叉树创建字符串
-
二叉树的分层遍历
- 二叉树的层序遍历
-
二叉树的锯齿形层次遍历
- 二叉树层序遍历2
3.2 经典算法问题
-
二叉树的最近公共祖先
- 找到两个节点的最低公共祖先
- 解法:递归、路径记录
- 二叉树的最近公共祖先
-
二叉搜索树转排序双向链表
- 将BST转换为有序双向链表
- 解法:中序遍历+指针调整
3.3 遍历与重构
-
前序+中序构造二叉树
- 根据前序遍历和中序遍历重建二叉树
- 解法:递归分治
-
中序+后序构造二叉树
- 根据中序遍历和后序遍历重建二叉树
- 解法:递归分治
3.4 非递归遍历
-
二叉树前序遍历(非递归)
- 使用栈模拟递归过程
-
二叉树中序遍历(非递归)
- 左链入栈法
-
二叉树后序遍历(非递归)
- 双栈法或标记法
