数据结构之二叉搜索树详解
Posted 小赵小赵福星高照~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之二叉搜索树详解相关的知识,希望对你有一定的参考价值。
二叉搜索树
文章目录
二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一颗空树,或者具有以下性质:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
最坏查找高度次就可以确认一个值在不在树中,查找时间复杂度:O(N)(最坏情况:全部是左子树或者全部是右子树):
注意不是O(logN),只有在当树的形状接近完全二叉树或者满二叉树,才能达到logN,在实际中搜索二叉树在极端情况下没办法保证效率,所以我们要对搜索二叉树进一步学习它的特性扩展延伸:AVLTree和红黑树,他们对搜索二叉树,左右高度提出要求,非常接近完全二叉树,他们效率可以达到O(logN),AVLTree 红黑树以及B树系列都是在搜索树基础上演变出来,各有特点,适用于不同的场景
二叉搜索树的操作
二叉搜索树的节点结构
struct BSTreeNode//二叉搜索树节点
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
;
二叉搜索树的结构
#include<iostream>
using namespace std;
template<class K>
class BSTree
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
private:
Node* _root;
;
二叉搜索树的查找
若根节点不为空:如果根节点key等于查找key,则返回该节点,如果根节点key大于查找key,则在其左子树查找,如果根节点key小于查找key,则在其右子树查找,否则返回NULL
Node* Find(const K& key)
Node* cur = _root;
while(cur)
if(cur->_key > key)
cur = cur->__left;
else if(cur->_key < key)
cur = cur->_right;
else
return cur;
return NULL;
二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接插入
b. 树不为空,按照二叉搜索树性质查找插入位置,插入新节点
bool Insert(const K& key)
//当根节点为空时,新创建一个节点即为根节点
if(_root == nullptr)
_root = new Node(key);
return true;
Node* cur = _root;
Node* parent = nullptr;//记录父亲节点,以便找到位置后进行与父亲链接
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);
//链接
if(parent->_key > key)
parent->_left = cur;
else
parent->_right = cur;
return true;
二叉搜索树的遍历
中序访问二叉搜索树,打印有序数组
void _InOrder(Node* root)
if(_root == nullptr)
return;
InOrder(root->_left);
cout<<root->_key<<" ";
InOrder(root->_right);
void InOrder()
_InOrder(_root);//套一层的目的是可以将_root私有成员传过去,外面就不用传_droot
int main()
BSTree<int> t;
int a[] = 5,3,4,1,7,8,2,6,0,9;
for(auto e:a)
t.insert(e);
t.Inorder();
二叉搜索树的删除
删除的原则:保持搜索树的结构
删除一个值等于key的节点,分情况分析:
- 要删除的是叶子节点非常好处理,删除自己,父亲指向自己的位置的指针置空
- 要删除的节点是只有一个孩子的节点,删除当前节点,把孩子交给父亲顶替自己位置
- 要删除的是有两个孩子的节点不好处理,解决方案是替换法删除:
去孩子里面找一个值(左子树的最大节点,左子树最右节点就是最大的,或者右子树的最小节点,右子树最左节点就是最小的)能替换自己位置的节点,这两个节点去替换后,都符合特征1或者2,可以直接删除
注意:情况1可以当成特征2的情况去处理
删除之找右子树的最左节点:
bool Erase(const K& key)
Node* cur = _root;
Node* parent = nullptr;
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;
if(cur == parent->_left)//删除节点是父亲的啥就把孩子赋给父亲的啥
parent->_left = cur->_right;
else
parent->_right = cur->_right;
else if(cur->_right == nullptr)
if(cur == _root)
_root = cur->_left;
if(cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
else
//左右都不为空,替换法删除
Node* rightMin = cur->_right;
//Node* parent = nullptr;//这里不能设置成nullptr,当rightMin走到这里就是要删除节点的右子树中最左节点时,下面的循环进不去,此时parent就是nullptr,后面访问parent会报错
Node* parent = cur;
while(rightMin->_left)
parent = rightMin;
rightMin = rightMin->_left;
//走到了最左
cur->_key = rightMin->_key;
if(rightMin == parent->left)//不一定肯定rightMin就是parent的左孩子,当Node* parent = nullptr这个代码的注释说明的情况发生时,rightMin就是parent的右孩子
parent->_left = rightMin->_right;
else
parent->_right = rightMin->_right;
delete rightMin;
return true;
return false;
删除之找左子树的最右节点:
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//cur->_key == key
//找到了,准备开始删除
if(cur->_left == nullptr)
if(cur == _root)//当要删除的节点是根节点时
_root = cur->_right;
else
//情况1:_right = nullptr
//情况2:_right != nullptr
//情况1和情况2合并
if(parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
else if(cur->_right == nullptr)
if(cur == _root)//当要删除的节点是根节点时
_root = cur->_left;
else
//情况1:_left = nullptr
//情况2:_left != nullptr
//情况1和情况2合并
if(parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
else//左右都不为nullptr,替换法删除
//情况3
//先找到左子树的最大节点(最右边的节点)
Node* leftMax = cur->_left;
Node* maxParent = cur;//这里不能给nullptr,需要给cur,因为下面的循环可能进不去(当此时的leftMax就是最大节点时),此时如果给了nullptr,maxParent就为空,下面就会访问空指针出错
while(leftMax->right)
maxParent = leftMax;
leftMax = leftMax->_right;
//找到了
//交换值
cur->_key = leftMax->_key;
//删除
if(maxParent->_left == leftMax)
maxParent->_left = leftMax->_left;
else
maxParent->_right = leftMax->_left;
delete leftMax;
//递归
/*Node* leftMax = cur->_left;
while(leftMax->right)
leftMax = leftMax->_right;
//找到了
//用临时变量来保存这个替换的值
K max = leftMax->_key;
//递归调用
this->Erase(max);//值为max的这个值一定没有右孩子,即递归终止条件
cur->_key = min;*/
return true;
return flase;
二叉搜索树插入的递归写法
如果root的key小于要插入节点的key,则递归到右子树去插入,如果root的key大于要插入节点的key,则递归到左子树去插入,如果相等说明已经存在这个key,插入失败,这里参数列表的root设置为引用,就可以解决链接的问题:
bool _InsertR(Node*& root,const K& key)//引用很巧,相当于root是传过来的别名,注意这里的引用是关键,我们插入时的位置找到时,并不知道链接关系该怎么处理,这里传引用,就解决了这个问题
if(root == nullptr)
root = new Node(key);
return true;
else
if(root->_key<key)
return _Insert(root->_right,key);
else if(root->_key > key)
return _Insert(root->_left,key);
else
return false;
bool InsertR(const K& key)
return _InsertR(_root,key)
二叉搜索树查找的递归写法
如果root的key小于要插入节点的key,则递归到右子树去查找,如果root的key大于要插入节点的key,则递归到左子树去查找,如果相等说明找到了:
Node* _FindR(Node* root,const K& key)
if(root == nullptr)
return root;
if(root->_key < key)
_FindR(root->_right,key);
else if(root->_key > key)
_FindR(root->_left,key);
else
//找到了
return root;
Node* FindR(const K& key)
return _FindR(_root,key);//套一层,因为递归需要将根节点传过去
二叉搜索树删除的递归写法
如果root的key小于要删除节点的key,则递归到右子树去删除,如果root的key大于要删除节点的key,则递归到左子树去删除,如果相等说明找到了要删除的节点,进行删除,删除又回到了上面非递归写法讲解的那三种情况:
-
要删除的是叶子节点非常好处理,删除自己,父亲指向自己的位置的指针置空
-
要删除的节点是只有一个孩子的节点,删除当前节点,把孩子交给父亲顶替自己位置
-
要删除的是有两个孩子的节点不好处理,解决方案是替换法删除:
去孩子里面找一个值(左子树的最大节点,左子树最右节点就是最大的,或者右子树的最小节点,右子树最左节点就是最小的)能替换自己位置的节点,这两个节点去替换后,都符合特征1或者2,可以直接删除
注意:情况1可以当成特征2的情况去处理
删除右子树最小节点转换成删除右子树中的rightMin这个节点,这个节点肯定没有左孩子,所以属于上面情况,这里可以递归处理:
bool _EraseR(Node*& root,const K& key)
if(root == nullptr)
return false;
if(root->_key < key)
return _Erase(root->_right,key);
else if(root->_key > key)
return _Erase(root->_left,key);
else
//找到了,删除
if(root->_left == nullptr)
Node* del = root;
root = root->_right;
delete del;
else if(root->_right == nullptr)
Node* del = root;
root = root->_left;
delete del;
else
//左右节点都存在,替代法删除
Node* rightMin = root->_right;
while(rightMin->_left)
minRight = minRight->_left;
root->_key = rightMin->_key;
return _EraseR(root->_right,rightMin->_key);//删除右子树最小节点转换成删除右子树中的rightMin这个节点,这个节点肯定没有左孩子,所以属于上面情况
//如果右子树越大,付出代价就越大,相当于重新查找了一遍
return true;
bool EraseR(const K& key)
_EraseR(_root,const K& key);
二叉搜索树增删查改操作代码
#include<iostream>
using namespace std;
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()
:_root(nullptr)
//拷贝构造
BSTree(const BSTree<K>& t)
_root = _Copy(t._root);
//现代赋值重载
//t1 = t2;
BSTree<K>& operator=(BSTree<K> t)
std::swap(_root,t._root);
return *this;<以上是关于数据结构之二叉搜索树详解的主要内容,如果未能解决你的问题,请参考以下文章