C++——二叉搜索树
1.二叉搜索树概念
二叉搜索树(Binary Search Tree,BST) 是一种特殊的二叉树,它具有以下性质:
- 每个节点有一个值(通常是数字或者可以比较大小的其他数据类型)。
- 对于每个节点,左子树中的所有节点值都小于该节点的值,右子树中的所有节点值都大于该节点的值。
- 每个节点的左、右子树也都是二叉搜索树。

1.1 二叉搜索树的基本操作:
- 插入:从根节点开始,找到合适的位置(比当前节点小的去左子树,比当前节点大的去右子树),直到空节点处插入新节点。
- 查找:从根节点开始,若目标值小于当前节点值,继续在左子树查找;若目标值大于当前节点值,继续在右子树查找。
- 删除:有三种情况:
- 删除的节点是叶子节点(没有子节点):直接删除。
- 删除的节点有一个子节点:用该子节点替代被删除的节点。
- 删除的节点有两个子节点:用右子树中的最小节点或左子树中的最大节点替代被删除节点。
1.2 性能分析
-
时间复杂度(Time Complexity)
- 最优情况:当树保持平衡时,每个操作的时间复杂度为O(log n),其中n是树中的节点数。
- 最坏情况:如果树退化成一个链表(例如每次插入的值都比当前节点大或者小),那么操作的时间复杂度为O(n)。
- 平均情况:对于随机插入的节点,树的平均高度接近log n,因此操作的时间复杂度通常是O(log n)。
-
空间复杂度(Space Complexity)
二叉搜索树的空间复杂度主要取决于树的高度。对于n个节点的树,空间复杂度为O(n),因为每个节点都需要存储数据和指向左右子树的指针。
2. 二叉搜索树的实现
二叉搜索树的初步实现:
template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key): _left(nullptr), _right(nullptr), _key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree(const BSTree<K>& t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destory(_root);_root == nullptr;}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root->_key == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}void Destory(Node* root){if (root == nullptr){return;}Destory(root->_left);Destory(root->_right);delete(root);}Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* copy = new Node(root->_key);copy->_left = Copy(root->_left);copy->_right = Copy(root->_right);return copy;}private:Node* _root = nullptr;
};
2.1二叉搜索树的插入
在二叉搜索树中,每个节点都有一个键值。其插入操作遵循特定的规则:
- 左子树的节点值小于根节点的值;
- 右子树的节点值大于根节点的值。
过程:
-
如果树为空,则直接创建一个新的节点,并将其赋值给根指针
_root。 -
如果树不为空,按照二叉搜索树的规则,若插入的值大于当前节点的值,则向右子树移动;若插入的值小于当前节点的值,则向左子树移动,直到找到一个空位来插入新节点。
-
如果允许插入相等的值,遇到与当前节点值相等的情况时,可以选择将其插入到右子树或左子树。这时,确保插入逻辑的一致性,避免重复值总是被插入到某一侧。
这样,我们保证插入操作能够在符合二叉搜索树规则的同时,保持树的结构合理性。
bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}cur = new Node(key);if (parent->_key< key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

这段代码使用了迭代方式来插入二叉搜索树的节点,避免了递归的深度。在树的查找过程中,迭代地比较当前节点与目标值的大小,决定向左子树还是右子树递归。如果插入位置为空,则创建新的节点并将其连接到父节点。插入操作会检查是否插入重复的节点,如果有相同的键值,则返回 false,避免重复插入,并且插入操作中判断左右子树的顺序不能随意交换。
2.2二叉搜索树的查找
在二叉搜索树中,查找操作是非常高效的,因为树的结构保持了一定的排序规则:左子树所有节点的值小于当前节点的值,右子树所有节点的值大于当前节点的值。
二叉搜索树的查找过程:
-
比较根节点与目标值:
- 如果目标值等于当前节点的值,则查找成功。
- 如果目标值小于当前节点的值,则继续在当前节点的左子树查找。
- 如果目标值大于当前节点的值,则继续在当前节点的右子树查找。
-
时间复杂度:
- 最好的情况是树是完全平衡的,此时查找时间复杂度是 O(log n)。
- 最坏的情况是树退化成链表(例如每次插入的值都比当前节点大或小),此时查找的时间复杂度是 O(n)。
bool Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{cout << "找到了" << endl;return true; }}cout << "没找到了" << endl;return false;
}
2.3二叉搜索树的删除
删除过程分析:
-
删除叶子节点:
如果节点没有左右子树,直接将其父节点指向空,删除该节点。 -
删除只有一个子节点的节点:
将其父节点的指针指向它的唯一子节点,然后删除该节点。 -
删除有两个子节点的节点:
需要找到一个替代节点。通常我们选择右子树的最小节点(中序后继)或左子树的最大节点(中序前驱)。然后将该节点的值替换到要删除的节点,并递归删除该替代节点。

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur = parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;}}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur = parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}}//两个孩子else{Node* pMinRight = cur;Node* minRight = cur->_right;while (minRight->_left){pMinRight = minRight;minRight = minRight->_left;}swap(cur->_key, minRight->_key);if (pMinRight->_left = minRight){pMinRight->_left = minRight->_right;}else{pMinRight->_right = minRight->_right;}delete minRight;}return true;}}return false;
}
3.key/value搜索
每个关键码(key)都有对应的值(value),其中值(value)可以是任意类型的对象。在树的结构中,每个节点除了存储关键码(key),还需要存储与之对应的值(value)。在进行插入、删除或查找操作时,仍然遵循以关键码(key)为关键字的二叉搜索树规则,可以快速地查找到对应关键码(key)所对应的值(value)。
template<class K, class V>
struct BSTreeNode
{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key; V _value; BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}
};template<class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:BSTree() = default;BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destory(_root);_root = nullptr;}bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{Node* pMinRight = cur;Node* minRight = cur->_right;while (minRight->_left){pMinRight = minRight;minRight = minRight->_left;}swap(cur->_key, minRight->_key);if (pMinRight->_left == minRight){pMinRight->_left = minRight->_right;}else{pMinRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}void Destory(Node* root){if (root == nullptr){return;}Destory(root->_left);Destory(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copy = new Node(root->_key, root->_value);copy->_left = Copy(root->_left);copy->_right = Copy(root->_right);return copy;}private:Node* _root = nullptr;
};
