二叉搜索树的实现与应用场景
本章目标
1.二叉搜索树的概念
2.二叉搜索树的性能分析
3.二叉搜索树的实现
4.二叉搜索树的key/key-value的实现场景
1.二叉搜索树的概念
二叉搜索树又叫二叉排序树,它是具有以下性质的树
1.它的左子树不为空,并且左子树上的值都小于根节点
2.它的右子树不为空,并且右子树上的值都大于根节点
3.它的左右结点都是二叉搜索树
4.二叉搜索树支持插入相等的值,也可以不支持插入相等的值,在后续我们会介绍map/set,以及mutiset和mutimap,前者就是不支持插入相等的值,后者就是支持插入相等的值
这是支持插入相等值的二叉搜索树
5.为什么这棵树又叫做二叉排序树,因为这颗树是严格遵守左边小右边大的方式.
当我们去按中序遍历去走一边,它就会排好升序,所以叫二叉排序树.
2.二叉搜索树的性能分析
1.对于当前二叉搜索树,如果它的搜索树接近一棵完全二叉树,那么它的时间复杂度就是logn的级别.因为二叉搜索树它会用要查找的值和当前结点的值进行比对,如果比他它就是它的右边,比他小就去它的左边,无论怎么样它都会往下走一层.它的查找次数也就是二叉搜索树的层数.
2.如果二叉搜索树退化成一颗单枝树的话,它的时间复杂度就到了N的地步.这是最坏的情况.
综上所述二叉搜索树的增删查的效率是O(N).
3.在前面我们学习过二分查找,它也能达到logn的效率,但是它有两个弊端.
1.它要求数据有序.2它如果进行中部的插入或者删除的话它的效率是很低的,它需要进行移动数据.
3.二叉搜索树的实现
对于二叉搜索树,它的结点结构与我们之前学过的二叉树并无区别.我们用一个类进行封装.
template <class K>
struct BStree_Node
{typedef BStree_Node<K> Node;Node* left = nullptr;Node* right = nullptr;K key;BStree_Node(const K& val = K()):key(val){;}};
因为我们写的二叉搜索树是一个泛型的,我们使用模板去控制类型.
3.1.二叉搜索树的插入
1.树为空,我们直接将结点赋值给根节点.
2.如果根结点不为空,大就让它往右边走,小就让他往左边走,找到空位,插入.
3.如果插入相等的值,可以插入到左边也可以插入到右边,但是要保证所以插入相等的值都要插入到左边,或者右边.
bool insert(const K& val)
{if (_root == nullptr){_root = new Node(val);return true;}Node* cur = _root;Node* parents = nullptr;while (cur){if (cur->key < val){parents = cur;cur = cur->right;}else if (cur->key > val){parents = cur;cur = cur->left;}else{return false;}}cur = new Node(val);if (parents->key > val){parents->left = cur;}else{parents->right = cur;}return true;
}
因为我们的二叉树是一颗三叉链.我们需要保证它的插入的结点,也要和它前面的结点相连,但是我们并不知道,它是插入在它的左边还是右边,我们可以重复判断一下.
如果我们支持插入相同的值的话,我们只需要让它的后两个判断合并,因为mutiset是肯定会插入成功的,我们实现也应当如此.
bool insert(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* cur = _root;Node* parents = nullptr;while (cur){if (cur->key < val){parents = cur;cur = cur->right;}else {parents = cur;cur = cur->left;}//else//{// //return false;// parents = cur;// cur = cur->left;//}}cur = new Node(val);if (parents->key >= val){parents->left = cur;}else{parents->right = cur;}return true;}
相等时支持插入应该实现的代码
3.2搜索二叉树的查找
1.它的查找功能是插入的前部分的逻辑,我们至于要让大的向左走,小的向右边走即可.
2.如果在查找时,它的值已经被找到.我们返回当前结点.
3.如果遍历到最后,我们还没有找到,我们就让它返回nullptr.
4.如果支持查找相等值,一般我们应该返回中序遍历的第一个值例如下图
我们要返回的就是1下面的3.
Node* find(const K& val)
{Node* cur = _root;//Node* result = nullptr;while (cur){if (cur->key < val){cur = cur->right;}else if (cur->key > val){cur = cur->left;}else{return cur;//result = cur;//cur = cur->left;}}//return result;return nullptr;
}
相等时查找的代码就是将注释中的代码放出来,并替换.
3.3搜索二叉树的删除
对于二叉树的删除
如果元素不存在就返回false.
如果元素存在分为下面四种情况
1.左右孩子均为空
2.左孩子为空
3,右孩子为空
4.左右孩子均不为空
(1)我们可以让孩子的父亲的的该结点置空,然后删除该结点.
(2,3)我们让父亲结点对应的孩子结点的指针去指向它们剩余的那个孩子,然后删除该结点
(4),我们无法直接直接删除该结点,我们要从它们已经有的结点去找到它们的代替替换掉,然后删除.这个结点是当前结点的左子树的最大(最右)结点,右子树的最小(最左)结点.
然后让它们的值交换,让被替换的结点结点的父亲的结点的对应孩子结点的指针去接管它们剩下的结点.(也可能没有).删除掉该被替换的结点.
bool erase(const K& val)
{Node* cur = _root;Node* parents = nullptr;while (cur){if (cur->key < val){parents = cur;cur = cur->right;}else if (cur->key > val){parents = cur;cur = cur->left;}else{//删除if (cur->left == nullptr){if (cur == _root){_root = cur->right;}else{if (parents->right == cur){parents->right = cur->right;}else{parents->left = cur->right;}}delete cur;}else if (cur->right == nullptr){if (cur == _root){_root = cur->left;}else{if (parents->left == cur){parents->left = cur->left;}else{parents->right = cur->left;}}delete cur;}else{Node* pminright = cur;Node* minright = cur->right;while (minright->left){pminright = minright;minright = minright->left;}std::swap(cur->key, minright->key);if (pminright->left == minright)pminright->left = minright->right;elsepminright->right = minright->right;delete minright;}return true;}}return false;
}
3.4在类内部的递归–中序遍历
我们在类内部实现的中序遍历,我们一般只提供接口,我们不会把私有成员暴露出来.
void inorder()
{_inorder(_root);cout << endl;
}
private:void _inorder(Node* root){if (root == nullptr){return;}_inorder(root->left);cout << root->key << " ";_inorder(root->right);}
3.5拷贝与析构
拷贝和析构我们都采用递归的形式,前置走前序,或者走后序.
因为拷贝和析构格式的特殊我们也是提供接口的形式在内部调用
BStree(const BStree<K>& t)
{_root=Copy(t._root);
}
BStree<K>& operator=(BStree<K> t)
{swap(_root, t._root);return *this;
}
~BStree()
{destory(_root);cout << "~BStree" << endl;
}
private:
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;
}
void destory(Node* root)
{if (root == nullptr)return;destory(root->left);destory(root->right);delete root;
}
Node* _root = nullptr;
赋值运算符重载,采用现代写法,交换的写法.
在这里我们已经写过了构造函数,我们要主动写一个无参的作为默认构造.
我们在这里用default强制生成.
BStree()=default;
4.二叉搜索树的key/key-value的实现场景
1 key搜索场景:
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。
场景1:⼩区⽆⼈值守⻋库,⼩区⻋库买了⻋位的业主⻋才能进⼩区,那么物业会把买了⻋位的业主的⻋牌号录⼊后台系统,⻋辆进⼊时扫描⻋牌在不在系统中,在则抬杆,不在则提⽰⾮本⼩区⻋辆,⽆法进⼊。
场景2:检查⼀篇英⽂⽂章单词拼写是否正确,将词库中所有单词放⼊⼆叉搜索树,读取⽂章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提⽰。
2.key/value搜索场景:
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树性质了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时查找到了英⽂对应的中⽂。
场景2:商场⽆⼈值守⻋库,⼊⼝进场时扫描⻋牌,记录⻋牌和⼊场时间,出⼝离场时,扫描⻋牌,查找⼊场时间,⽤当前时间-⼊场时间计算出停⻋时⻓,计算出停⻋费⽤,缴费后抬杆,⻋辆离场。
场景3:统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。
template <class K,class V>
class BStree
{typedef BStree_Node<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);cout << "~BStree" << endl;}bool insert(const K& val,const V& value){if (_root == nullptr){_root = new Node(val,value);return true;}Node* cur = _root;Node* parents = nullptr;while (cur){if (cur->key < val){parents = cur;cur = cur->right;}else if (cur->key > val){parents = cur;cur = cur->left;}else{return false;}}cur = new Node(val,value);if (parents->key > val){parents->left = cur;}else{parents->right = cur;}return true;}void inorder(){_inorder(_root);cout << endl;}Node* find(const K& val){Node* cur = _root;while (cur){if (cur->key < val){cur = cur->right;}else if (cur->key > val){cur = cur->left;}else{return cur;}}return nullptr;}bool erase(const K& val){Node* cur = _root;Node* parents = nullptr;while (cur){if (cur->key < val){parents = cur;cur = cur->right;}else if (cur->key > val){parents = cur;cur = cur->left;}else{//删除if (cur->left == nullptr){if (cur == _root){_root = cur->right;}else{if (parents->right == cur){parents->right = cur->right;}else{parents->left = cur->right;}}delete cur;}else if (cur->right == nullptr){if (cur == _root){_root = cur->left;}else{if (parents->left == cur){parents->left = cur->left;}else{parents->right = cur->left;}}delete cur;}else{Node* pminright = cur;Node* minright = cur->right;while (minright->left){pminright = minright;minright = minright->left;}std::swap(cur->key, minright->key);if (pminright->left == minright)pminright->left = minright->right;elsepminright->right = minright->right;delete minright;}return true;}}return false;}
private:void _inorder(Node* root){if (root == nullptr){return;}_inorder(root->left);cout << root->key << " "<<root->value;_inorder(root->right);}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;}void destory(Node* root){if (root == nullptr)return;destory(root->left);destory(root->right);delete root;}Node* _root = nullptr;
};
key-val结构二叉搜索树代码,只需要修改模板参数,以及插入查找等地方修改,整体逻辑是跟key走的,并无太多差异.