【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现(一)https://developer.aliyun.com/article/1617404
2.5.3 第三种情况(替换法)
使用替换法删除,简单回顾
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
被替换的节点需要满足左子树最大节点或者右字数的最小节点其中之一即可。比如满足左子树最大节点,进行交换,该节点满足比左子树都要大,比右子树都要小。
替换法删除的具体流程:
- 先找到需要被删除节点和被替换节点,进行swap交换数字
- 通过第一、二种情况进行删除操作
- 那么需要设置两个指针去需要被替换节点
Node* RightMinParent = cur; Node* RightMin = cur->_right; //找到右子树最小的值 while (RightMin->_left) { RightMinParent = RightMin; RightMin = RightMin->_left; } //找到 swap(cur->_key, RightMin->_key); if (RightMinParent->_left == RightMin) { RightMinParent->_left = RightMin->_right; } else { RightMinParent->_right = RightMin->_right; } delete RightMin; } return true;
实现该逻辑的具体细节:
- 这里我选择找到右子树的最小节点,那么只需要关注左边的情况就行了,这也是为什么是
while (RightMin->_left)
- 首先就是第一、二种情况删除的做法
RightMinParent
不能设置为空指针当删除根节点就会有问题,直接设置为cur
以上三种情况都需要考虑需要被删除节点为根节点
2.6 采用中序遍历二叉搜索树
void InOrder() { _InOrder(_root); cout << endl; } private: void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_key << " "; _InOrder(root->_right); }
中序遍历能够按顺序输出树中所有节点的值。从根节点开始,中序遍历的顺序是【左子树 , 根节点 , 右子树】
这一过程在BST中恰好能保证节点按从小到大的顺序排列。将内部实现放在private
部分,可以避免外部代码错误地调用内部方法,导致程序行为不可预测或出错。通过控制访问权限。外部代码不应该直接操作树的节点,而应该通过公开的接口方法来访问和操作树。
三、改造二叉搜索树,进行实际应用
K模型:K模型即只有key作为关键码,结构种只需要存储key即可,关键码即为需要搜索到的值
使用场景:判断单词是否拼写正确
- 将词库中所有单词集合中的每个单词作为key,构建一颗二叉搜索树
- 在二叉搜索树中查找单词是否存在,存在则为正确,否则错误
KV模型:每一个关键码key,都有与之对应的值Value,即
的键值对
使用场景:翻译语言(底层主要还是B树)
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文
就构成一种键值对
- 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是
就构成一种键值对
// 改造二叉搜索树为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<T>* _pLeft; BSTNode<T>* _pRight; K _key; V _value }; template<class K, class V> class BSTree { typedef BSTNode<K, V> Node; typedef Node* PNode; public: BSTree(): _pRoot(nullptr){} PNode Find(const K& key); bool Insert(const K& key, const V& value) bool Erase(const K& key) private: PNode _pRoot; }; void TestBSTree3() { // 输入单词,查找单词对应的中文翻译 BSTree<string, string> dict; dict.Insert("string", "字符串"); dict.Insert("tree", "树"); dict.Insert("left", "左边、剩余"); dict.Insert("right", "右边"); dict.Insert("sort", "排序"); // 插入词库中所有单词 string str; while (cin>>str) { BSTreeNode<string, string>* ret = dict.Find(str); if (ret == nullptr) { cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl; } else { cout << str << "中文翻译:" << ret->_value << endl; } } } void TestBSTree4() { // 统计水果出现的次数 string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" }; BSTree<string, int> countTree; for (const auto& str : arr) { // 先查找水果在不在搜索树中 // 1、不在,说明水果第一次出现,则插入<水果, 1> // 2、在,则查找到的节点中水果对应的次数++ //BSTreeNode<string, int>* ret = countTree.Find(str); auto ret = countTree.Find(str); if (ret == NULL) { countTree.Insert(str, 1); } else { ret->_value++; } } countTree.InOrder(); }
四、二叉搜索树的性能分析
插入和删除操作都必须先查找的,查找效率代表了二叉搜索树中各个操作的性能
对于对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜
- 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树)
- 最差情况下,二叉搜索树退化为单支树(或者类似单支)
如果退化成单支树,二叉搜索树的性能就失去了 ,我们后续章节学习的AVL树和红黑树就可以上场了
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现(三)https://developer.aliyun.com/article/1617406