目录
二叉搜索树的概念
⼆叉搜索树的性能分析
⼆叉搜索树的插⼊
⼆叉搜索树的查找
⼆叉搜索树的删除
中序遍历结果为升序序列
二叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树
• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值• 它的左右⼦树也分别为⼆叉搜索树• ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我 们学习map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等 值,multimap/multiset⽀持插⼊相等值
⼆叉搜索树的性能分析
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为: log 2 N最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为: N所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为: O ( N )中序遍历二叉搜索树得到一个升序序列
⼆分查找也可以实现 O (log 2 N ) 级别的查找效率,但是⼆分查找有两⼤缺陷:1. 需要存储在⽀持下标随机访问的结构中,并且有序。 (有序数组)2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数 据。
⼆叉搜索树的插⼊
1. 树为空,则直接新增结点,赋值给root指针2. 树不空,按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位 置,插⼊新结点。3. 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插 ⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛)
bool Insert(const K& key){if (_root == nullptr){_root = new Node(key); // 1、若根为空,则就插入在此处return true;}Node* parent = nullptr; // 使用parent保存父节点,用于后续链接Node* cur = _root;while (cur){if (cur->_key < key) // 2、不断递归寻找合适插入位置要满足左小右大的特点{parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{cout << "所要插入的值已经存在" << endl;return false;}}cur = new Node(key);if (parent->_key > key) // 3、找到之后使用parent和cur进行新建结点的接入parent->_left = cur;elseparent->_right = cur;return true;}
⼆叉搜索树的查找
如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x,如下图,查找3,要
找到1的右孩⼦的那个3返回。
bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key == key)return true;else if (cur->_key > key)cur = cur->_left;elsecur = cur->_right;}return false;}
⼆叉搜索树的删除
父要接管删除节点的剩余结点
若cur的左为空,那么父节点要处理cur的右子树
若cur的右为空,那么父节点要处理cur的左子树
若cur的左右均不为空,那么就找cur右子树的最左结点进行接管,最后处理最左节点的右子树
三种情况,最终处理时又都有俩种 孩子在左、在右的情况。
bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur) // 遍历搜索树,寻找需要删除的结点位置{if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{// 此时找到了需要删除的结点位置,进行删除操作// 我的左为空,父亲处理我的右if (cur->_left == nullptr){if (cur == _root) // 左为空,并且cur为根_root = cur->_right;if (cur == parent->_right)parent->_right = cur->_right;else if (cur == parent->_left)parent->_left = cur->_right;delete cur;}// 我的右为空,父亲处理我的左else if (cur->_right == nullptr){if (cur == _root)// 右为空,并且cur为根_root = cur->_left;if (cur == parent->_right)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}else{// 左右均不为空,需要找右子树的最左(最小)结点来接管我的位置Node* minRightNode = cur->_right; //通过minRightNode 和 minRightParentNode* minRightParent = cur; //找右子树最左节点while (minRightNode->_left) {minRightParent = minRightNode;minRightNode = minRightNode->_left; // 一直向最左移动,注意保存父节点位置}cur->_key = minRightNode->_key; // 将结点的_key值进行替换即可if (minRightParent->_right != minRightNode) // 处理minRightNode结点,只有俩种情况,一种minRightNode是minRightParent的左孩子、另一种是右孩子minRightParent->_left = minRightNode->_right; // 如果minRightNode是左孩子,那么minRightParent要接管它的右子树elseminRightParent->_right = minRightNode->_right; // 如果minRightNode是右孩子,那么minRightParent要接管它的左子树delete minRightNode;}return true;}}return false;}
中序遍历结果为升序序列
最开始没有写 InOrder() 函数,在main中进行调用时发生了 t.InOrder(??) 无法给左式括号内传参的问题,所以又加入了一个InOrder 和 _InOrder()俩个递归函数嵌套的形式。
template<class K>
class BSTree
{typedef BSTNode<K> Node;
public: void InOrder(){_InOrder(_root); cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;
};