二叉搜索树的概念:
二叉搜索树本质上是一个二叉树,它的特点是,要么为空,要么具有以下性质:
1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3.它的左右子树也分别为二叉搜索树
二叉搜索树也被叫做二叉查找树或者二叉排序树
二叉树的实现:
这里实现增删查改:
节点定义:
val的类型根据传入的模板参数定义,left和right为指针,分别指向左孩子右孩子,同时给了默认构造,新节点构造将val设为形参的值,left,right都为空:
二叉树结构定义:
只需要一个节点指针指向根节点:
构造函数:
root置为空:
插入:
先特殊处理,如果树为空,直接new出一个node,让root指向它:
这里用的是循环,cur同样是一个node指针,让cur移动,寻找合适的位置.搜索二叉树的节点插入一定是在叶子节点插入,所以插入很好实现.
//insert() 插入节点
//循环
bool insert(K val)
{
Node* cur = root_;
//树为空,new出一个新节点,让root指向它
if (cur== nullptr)
{
root_ = new Node(val);
return true ;
}
//root_不为空,找到合适的位置
//通常来说,搜索二叉树的插入要遍历二叉树,找到一个合适位置的空节点,将该节点插入
//如果要插入的节点已经存在,仅返回该节点的位置
Node* parent=nullptr;
while (cur != nullptr)
{
parent = cur;
if (val > cur->val)
{
cur = cur->right;
}
else if (val < cur->val)
{
cur = cur->left;
}
//来到空节点,我们需要操作的是该空节点的父节点,所以还需要一个变量记录父节点
//这里是val值已经存在的情况,直接返回
else
{
return false;
}
}
//找到位置直接插入
//由于现在只知道,要插入的地方是parent位置的孩子节点,但是不知道是左孩子还是右孩子,所以还需要再判断
if (val > parent->val)
{
parent->right = new Node(val);
}
else if (val < parent->val)
{
parent->left = new Node(val);
}
return true;
}
假设要插入如一个5:
这里要用一个parent变量,假设如果没有parent'变量,cur来到空的地方,说明这就是一个合适的位置,我new出一个node,但是谁链接我呢?这个时候我不知道cur位置的父节点是谁,这里就单链表一样,新增或者插入一个新节点实际上更重要的是改变父子节点的链接关系.
查找:
查找只需要返回要查找的节点的地址,不需要改变父子节点的链接关系,所以if判断以下节点的val值,只要跟传入的形参相等就返回该节点地址.
Node* search(K val)
{
Node* cur = root_;
while (cur != nullptr)
{
if (val == cur->val)
{
return cur;
}
if (val > cur->val)
{
cur = cur->right;
}
else if (val < cur->left)
{
cur = cur->left;
}
}
return nullptr;
}
查找的流程跟插入类似,去掉parent指针.
删除:
自己实现的时候只想说,挺麻烦(T.T)
//删除节点
//删除节点不能单纯的delete
//如果删除的节点只有一个孩子,那就让该孩子直接替换父亲的位置
//但是如果有两个孩子,就比较麻烦,最简单的就是找一个合适的叶子节点替换
void erase(K val)
{
Node* replace = nullptr;
Node* cur = root_;
Node* parent = nullptr;
//先找到要删除的节点
while (cur->val != val)
{
parent = cur;
if (val > cur->val)
{
cur = cur->right;
}
else if (val < cur->val)
{
cur = cur->left;
}
}
//循环结束后 cur就是要擦除的节点,
//分情况处理 ,cur只有一个孩子 ,没有孩子或者有两个孩子
//没有孩子节点
if (cur->left == nullptr && cur->right == nullptr)
{
delete cur;
return;
}
//一个孩子节点
//要先判断cur是parent的左节点还是右节点,然后parent直接连接cur的孩子
if (cur->left != nullptr&&cur->right==nullptr)
{
if (cur == root_)
{
root_ = root_->left;
return;
}
if (parent->left == cur)
{
parent->left = cur->left;
}
else
{
parent->right = cur->left;
}
delete cur;
return;
}
else if(cur->right!=nullptr&& cur->left==nullptr)
{
if (cur == root_)
{
root_ = root_->right;
return;
}
if (parent->left == cur)
{
parent->left = cur->right;
}
else
{
parent->right = cur->right;
}
delete cur;
return;
}
//如果有cur有两个孩子进行替换
//用cur左树的最大节点替换,或者用右子树的最小节点替换
//用右树的最小节点
if (cur->left != nullptr && cur->right != nullptr)
{
replace = cur->right;
parent = cur;
while (replace->left != nullptr)
{
parent = replace;
replace = replace->left;
}
//循环结束时,replace就是要替换cur的节点
//同时有一个需要特殊处理的场景,就是右子树的根节点就是最小值,也就是右子树没有左节点
//这个时候需要将replace的值直接替换给cur,然后cur->left=replace->right;
cur->val = replace->val;
parent->left = replace->right;
delete replace;
return;
}
//最后调试发现需要对cur==root特殊处理
}
为了保证二叉搜索树的特质,删除的时候麻烦就麻烦在孩子节点的处理:
1.删除叶子节点,直接delete该节点,还要注意将父节点的链接置空
2.删除只有一个孩子的节点cur,只需要让父亲指向cur的孩子
3.删除两个孩子的节点:如果要选择删除cur节点并且调正父子的链接关系,你会发现,整颗子树都要调整,开销太大,所以我们不这么做
而是用替换法,选择左子树的最大节点或者右子树的最小节点替换该cur节点.这里你如果不选择叶子节点替换,而是选择一个同样带有两个孩子的节点替换,就是个死循环了.,问题还是那个问题.
尝试把这颗树压平展开你就能发现,实际上不论是整棵树还是子树,它的最左节点就是该树或者子树的最小值,最右节点就是最大值.
左子树的值都比根节点小,右子树的值都比根节点大
当你要删除某节点的时候,你把树的动态展开过程想一下,你就能想到,要删除的这个节点在这个有序数组中你需要做什么调整.也能直观的感觉到为什么是要左子树的最大节点或者右子树的最小节点,这里假设要删除3,不是用4替换就是用1替换:
`