目录
二叉搜索树的概念
二叉树的实现
结点类
函数接口总览
实现二叉树
二叉搜索树的应用
K模型
KV模型
二叉搜索树的性能分析
二叉搜索树的概念
二叉搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,其具有以下几个性质:
- 每个节点至多有两个子节点:分别称为左子节点和右子节点。
- 左子树上的所有节点的值都小于根节点的值。
- 右子树上的所有节点的值都大于根节点的值。
- 每个节点的左右子树也都是二叉搜索树。
这些性质确保了在二叉搜索树中进行查找、插入和删除操作具有良好的性能。具体地,这些操作在平均情况下的时间复杂度为 O(logn),其中 n 是树中节点的数量。不过,在最坏情况下(树退化成链表),时间复杂度可能会降为 O(n)。
下面是一个二叉搜索树的示例:
在这个二叉搜索树中:
- 根节点是 8。
- 根节点的左子树包含节点 3、1、6、4 和 7,这些节点的值都小于 8。
- 根节点的右子树包含节点 10、14 和 13,这些节点的值都大于 8。
二叉树的实现
结点类
要实现二叉搜索树,我们首先需要实现一个结点类:
- 结点类当中包含三个成员变量:结点值、左指针、右指针。
- 结点类当中只需实现一个构造函数即可,用于构造指定结点值的结点。
template<class K>
struct BSTreeNode
{K _key; // 结点值BSTreeNode<K>* _left; // 左指针BSTreeNode<K>* _right; // 右指针// 构造函数BSTreeNode(const K& key = K()): _key(key), _left(nullptr), _right(nullptr){}
};
函数接口总览
//二叉搜索树
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://构造函数BSTree();//拷贝构造函数BSTree(const BSTree<K>& t);//赋值运算符重载函数BSTree<K>& operator=(BSTree<K> t);//析构函数~BSTree();//插入函数bool Insert(const K& key);//删除函数bool Erase(const K& key);//查找函数Node* Find(const K& key);//中序遍历void InOrder();
private:Node* _root; //指向二叉搜索树的根结点
};
为了在实现其他接口的过程中方便随时检查,最好实现一个二叉搜索树的中序遍历接口,当我们对二叉搜索树进行一次操作后,可以调用中序遍历接口对二叉搜索树进行遍历,若二叉搜索树进行操作后的遍历结果仍为升序,则可以初步判断所实现的接口是正确。
//中序遍历的子函数
void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left); //遍历左子树cout << root->_key << " "; //遍历根结点_InOrder(root->_right); //遍历右子树
}
//中序遍历
void InOrder()
{_InOrder(_root);cout << endl;
}
实现二叉树
代码如下:
// 定义二叉搜索树模板类
template<class K>
class BSTree
{
private:BSTreeNode<K>* _root; // 树的根结点// 辅助函数:递归拷贝树BSTreeNode<K>* CopyTree(BSTreeNode<K>* root){if (root == nullptr)return nullptr;BSTreeNode<K>* newNode = new BSTreeNode<K>(root->_key);newNode->_left = CopyTree(root->_left);newNode->_right = CopyTree(root->_right);return newNode;}// 辅助函数:递归销毁树void DestroyTree(BSTreeNode<K>* root){if (root != nullptr){DestroyTree(root->_left); // 递归销毁左子树DestroyTree(root->_right); // 递归销毁右子树delete root; // 删除当前结点}}// 辅助函数:递归插入BSTreeNode<K>* InsertRecursive(BSTreeNode<K>* root, const K& key){if (root == nullptr){return new BSTreeNode<K>(key); // 找到插入位置后创建新结点}if (key < root->_key){root->_left = InsertRecursive(root->_left, key); // 递归插入左子树}else if (key > root->_key){root->_right = InsertRecursive(root->_right, key); // 递归插入右子树}return root;}// 辅助函数:递归删除BSTreeNode<K>* DeleteRecursive(BSTreeNode<K>* root, const K& key){if (root == nullptr)return root;if (key < root->_key){root->_left = DeleteRecursive(root->_left, key); // 在左子树中删除}else if (key > root->_key){root->_right = DeleteRecursive(root->_right, key); // 在右子树中删除}else{if (root->_left == nullptr){BSTreeNode<K>* temp = root->_right;delete root; // 删除当前结点return temp;}else if (root->_right == nullptr){BSTreeNode<K>* temp = root->_left;delete root; // 删除当前结点return temp;}BSTreeNode<K>* temp = MinValueNode(root->_right); // 找到右子树中最小值结点root->_key = temp->_key; // 用右子树中最小值替换当前结点root->_right = DeleteRecursive(root->_right, temp->_key); // 删除右子树中的最小值结点}return root;}// 辅助函数:找到最小值结点BSTreeNode<K>* MinValueNode(BSTreeNode<K>* node){BSTreeNode<K>* current = node;while (current && current->_left != nullptr){current = current->_left; // 找到最左端的结点即为最小值结点}return current;}// 辅助函数:递归查找BSTreeNode<K>* SearchRecursive(BSTreeNode<K>* root, const K& key) const{if (root == nullptr || root->_key == key)return root;if (key < root->_key)return SearchRecursive(root->_left, key); // 在左子树中查找return SearchRecursive(root->_right, key); // 在右子树中查找}public:// 构造函数,初始化空树BSTree(): _root(nullptr){}// 拷贝构造函数BSTree(const BSTree<K>& other): _root(nullptr){_root = CopyTree(other._root); // 深拷贝另一棵树}// 赋值运算符重载函数BSTree<K>& operator=(const BSTree<K>& other){if (this != &other){DestroyTree(_root); // 销毁当前树_root = CopyTree(other._root); // 深拷贝另一棵树}return *this;}// 析构函数,销毁树~BSTree(){DestroyTree(_root); // 递归销毁树中所有结点}// 插入函数(非递归)void InsertIterative(const K& key){if (_root == nullptr){_root = new BSTreeNode<K>(key); // 插入根结点return;}BSTreeNode<K>* parent = nullptr;BSTreeNode<K>* current = _root;while (current != nullptr){parent = current;if (key < current->_key){current = current->_left; // 移动到左子结点}else if (key > current->_key){current = current->_right; // 移动到右子结点}else{return; // 不插入重复值}}if (key < parent->_key){parent->_left = new BSTreeNode<K>(key); // 插入左子结点}else{parent->_right = new BSTreeNode<K>(key); // 插入右子结点}}// 插入函数(递归)void Insert(const K& key){_root = InsertRecursive(_root, key); // 调用递归插入函数}// 删除函数(非递归)void DeleteIterative(const K& key){BSTreeNode<K>* parent = nullptr;BSTreeNode<K>* current = _root;while (current != nullptr && current->_key != key){parent = current;if (key < current->_key){current = current->_left; // 移动到左子结点}else{current = current->_right; // 移动到右子结点}}if (current == nullptr)return;if (current->_left == nullptr || current->_right == nullptr){BSTreeNode<K>* newCurrent;if (current->_left == nullptr){newCurrent = current->_right;}else{newCurrent = current->_left;}if (parent == nullptr){_root = newCurrent; // 删除根结点}else if (current == parent->_left){parent->_left = newCurrent; // 删除左子结点}else{parent->_right = newCurrent; // 删除右子结点}delete current;}else{BSTreeNode<K>* p = nullptr;BSTreeNode<K>* temp;temp = current->_right;while (temp->_left != nullptr){p = temp;temp = temp->_left;}if (p != nullptr){p->_left = temp->_right;}else{current->_right = temp->_right;}current->_key = temp->_key;delete temp;}}// 删除函数(递归)void Delete(const K& key){_root = DeleteRecursive(_root, key); // 调用递归删除函数}// 查找函数(非递归)BSTreeNode<K>* SearchIterative(const K& key) const{BSTreeNode<K>* current = _root;while (current != nullptr && current->_key != key){if (key < current->_key){current = current->_left; // 移动到左子结点}else{current = current->_right; // 移动到右子结点}}return current; // 返回找到的结点或 nullptr}// 查找函数(递归)BSTreeNode<K>* Search(const K& key) const{return SearchRecursive(_root, key); // 调用递归查找函数}
};
用法和预期效果:
-
构造函数
- 用法:
BSTree<int> tree;
- 预期效果:创建一个空的二叉搜索树。
- 用法:
-
拷贝构造函数
- 用法:
BSTree<int> tree2 = tree1;
- 预期效果:深拷贝
tree1
,创建一个新的二叉搜索树tree2
,其结构和tree1
相同。
- 用法:
-
赋值运算符重载函数
- 用法:
tree2 = tree1;
- 预期效果:深拷贝
tree1
到tree2
,覆盖tree2
原来的内容。
- 用法:
-
析构函数
- 用法:当树对象生命周期结束时自动调用。
- 预期效果:递归销毁树中所有结点,释放内存。
-
插入函数(非递归)
- 用法:
tree.InsertIterative(10);
- 预期效果:在树中插入值为
10
的结点。
- 用法:
-
插入函数(递归)
- 用法:
tree.Insert(10);
- 预期效果:在树中插入值为
10
的结点。
- 用法:
-
删除函数(非递归)
- 用法:
tree.DeleteIterative(10);
- 预期效果:在树中删除值为
10
的结点。
- 用法:
-
删除函数(递归)
- 用法:
tree.Delete(10);
- 预期效果:在树中删除值为
10
的结点。
- 用法:
-
查找函数(非递归)
- 用法:
BSTreeNode<int>* node = tree.SearchIterative(10);
- 预期效果:在树中查找值为
10
的结点,返回指向该结点的指针,如果未找到则返回nullptr
。
- 用法:
-
查找函数(递归)
- 用法:
BSTreeNode<int>* node = tree.Search(10);
- 预期效果:在树中查找值为
10
的结点,返回指向该结点的指针,如果未找到则返回nullptr
。
- 用法:
二叉搜索树的应用
二叉搜索树(BST)是一种重要的数据结构,广泛应用于各种算法和系统中。以下是一些常见的应用:
- 符号表:在编译器中,二叉搜索树可以用来实现符号表,用于存储变量和函数的名称及其属性。
- 字典:二叉搜索树可以用来实现字典(例如键值对存储),支持高效的插入、删除和查找操作。
- 优先队列:可以使用二叉搜索树来实现优先队列,其中元素按照优先级排列,支持快速的插入和删除操作。
- 数据库索引:在数据库系统中,二叉搜索树可以用作索引结构,以加速查询操作。
- 排序和搜索:二叉搜索树天然地支持中序遍历,从而可以对元素进行排序和高效搜索。
K模型
K模型是基于二叉搜索树的一种简化形式,主要用于处理单个键(key)的存储和查询。每个结点只包含一个键,不涉及值(value)。
比如:给定一个单词,判断该单词是否拼写正确。具体方式如下:
- 以单词集合中的每个单词作为key,构建一棵二叉搜索树。
- 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
KV模型
KV模型是二叉搜索树的扩展形式,用于处理键值对(key-value)的存储和查询。每个结点包含一个键和一个值。
比如:英汉词典就是英文与中文的对应关系,即<word, Chinese>就构成一种键值对。具体方式如下:
以<单词, 中文含义>为键值对,构建一棵二叉搜索树。注意:二叉搜索树需要进行比较,键值对比较时只比较key。
查询英文单词时,只需给出英文单词就可以快速找到与其对应的中文含义。
二叉搜索树的性能分析
时间复杂度
-
查找、插入和删除
- 最优情况:当树是平衡的(完全平衡二叉树),时间复杂度为O(log n)。
- 最坏情况:当树退化成链表(每个结点只有一个子结点),时间复杂度为O(n)。
-
遍历
- 中序遍历、先序遍历、后序遍历的时间复杂度均为O(n),因为需要访问每个结点。
空间复杂度
-
空间使用
- 空间复杂度为O(n),n为树中的结点数。
-
递归调用栈
- 在最坏情况下(树退化成链表),递归调用栈的空间复杂度为O(n)。
平衡性
- 平衡二叉树:如AVL树、红黑树等,保证在最坏情况下也能达到O(log n)的时间复杂度。
- 普通二叉搜索树:如果输入数据是随机的,树大概率接近平衡。但如果输入数据是有序的(或接近有序),树可能退化为链表,导致性能下降。
性能优化
- 自平衡二叉搜索树:如AVL树、红黑树、Splay树等,通过自动调整树的结构,保证树的平衡,从而提升性能。
- B树和B+树:特别适用于数据库索引,支持高效的磁盘存取操作。
- 散列:对于一些应用,哈希表(Hash Table)可以提供更快的查找、插入和删除操作,但不适用于需要排序的场景。
总结
二叉搜索树(BST)是一种基础而重要的数据结构,广泛应用于符号表、字典、优先队列和数据库索引等场景。K模型和KV模型分别处理集合和字典的需求。BST的性能在很大程度上取决于树的平衡性,使用自平衡树可以保证最坏情况下的性能。此外,对于特定应用场景,选择合适的数据结构(如B树或哈希表)也非常重要。