二叉搜索树的概念
给出一组数据:int arr[] = { 2,4,5,6,8,9,3,7,1,0};
关系模型为下图所示:
通过上图可以发现二叉搜索树:
非空左子树所有值比根节点小
非空右子树的所有值都比根结点大
左右子树都是新的二叉搜索树
二叉树的搜索方式:
所以在寻找一个数时,都是经过根,与根进行比较然后比根小的在左,比根大的在右
二叉搜索树的中序遍历是一个有序的数列:
关系模型
中序结果:0 1 2 3 4 5 6 7 8 9
二叉树的模拟实现:
注意:一定要自己动手多画图要不容易乱(绕)
(非递归方式)
结点的构造:
创建一个结点必须要写出结点的类,对结点进行封装
注意:不要忘记要对类进行默认初始化构造函数的编写
template<class k>
struct BSTreenode
{
BSTreenode<k>* left;
BSTreenode<k>* right;
k _key;
BSTreenode(k key)
:left(nullptr)
, right(nullptr)
, _key(key)
{
}
};
二叉搜索树的编写:
template<class k>
class BSTree
{
typedef BSTreenode<k> node;//重写结点名字,方便
private:
node* _root=nullptr;//定义头指针,在声明时赋值,就不写初始化构造函数
};
插入insert:
如何插入呢?
是不是要和树里的数据进行对比,找到正确的位置然后插入。
1、
要确定插在哪里要确定位置,用双指针法因为要找到合适位置(nullptr)后就知道该位置的上一个结点然后连接(探测指针为nullptr该位置就是要插入的位置)
例子图举例子不是真的:
所以插入的结点时nullptr
bool insert(k key)
{
//两种情况;
//树里没有数据和有数据俩种情况
if (_root == nullptr)
{
_root = new node(key);//开辟一个node对象然后调用初始化构造
return true;
}
else
{
//有数据,要找到正确位置
node* parent = nullptr;
node* cur = _root;
while (cur)
{
if (cur->_key>key)//证明待插入元素应该往左边
{
parent = cur;//cur是探测作用,parent是保存上一个指针地址
cur = cur->left;
}
else if (cur->_key < key)//证明待插入元素应该往右边
{
parent = cur;
cur = cur->right;
}
}
//当cur为nullptr就要找到待插入位置。
//但是,还要确定插在parent的左还是右
if (parent->_key>key)
{
parent->left = new node(key);
return true;
}
else
{
parent->right = new node(key);
return true;
}
return false;
}
删除erase(重点):
这个很重要分很多个步骤
下面分析要在找到删除结点的情况下
1、看图(如图上要求)如何删除2呢?
例子图举例子不是真的:
观察2中没有左子树也没有右子树或者说2的左指向nullptr,右边也指向nullptr
所以父节点的右指针可以连接2的左nullptr和右nullptr
2、如图,当待删的结点有一个结点时
第一、还要将分为待删结点1和待删结点8的子结点是左结点还是右结点
第二、通过上图发现要删除结点还要拥有它的父类结点(双指针)
第三、待删结点剩下的结点要与父类连接(可以理解孙子结点与爷爷连接)
注意:待删结点的子结点有可能在左也可能在右,就是强调第一种情况,左右的情况
其实没有结点的情况也归这里管
以上就是当只有一个待删结点时注意的事项
例子图:
实现代码
//找到了,分情况讨论
//只有一种的情况下:待删结点的子结点为右边
//如果cur两边都没有结点符合
if (cur->left == nullptr)
{
//确定parent的左连接到cur还是右连接到cur
if (parent->left == cur)
{
parent->left = cur->right;
}
else
{
parent->right = cur->right;
}
delete cur;
return true;
}//待删结点的子结点为左边
else if (cur->right == nullptr)
{
//确定parent的左连接到cur还是右连接到cur
if (parent->left == cur)
{
parent->left = cur->left;
}
else
{
parent->right = cur->left;
}
delete cur;
return true;
}
还有一种情况最为复杂待删结点两个结点都有:
用替代法:就是在二叉搜索树中找一个可以替代该结点的数,将结点赋值,然后删除赋值结点的原位置,没有破坏二叉收搜索树的结构
比如5,左子树的最大结点可以满足(在左子树一直往右走)
右子树的最小结点可以满足(在右子树一直往左走)
例子图:
将4或6赋值给5后删除调黑色圈圈的位置(用那个删那个)
实现代码(这里有错的)有一种情况是删不了的
//只剩下两个结点的情况
node* rightsmall = cur->right;
node* rightsmallparent = nullptr;
while (rightsmall->left)//一直往左走就找到右子树的最小的
{//当这个条件结束rightsmall的左边已经没有值了所以就是最小的
rightsmallparent = cur;
rightsmall = rightsmall->left;
}
cur->_key = rightsmall->_key;//赋值
//替代完成
//删除替代的原位置
if (rightsmall->left == nullptr)
{
rightsmallparent->left = rightsmall->right;//因为rightsmall一直往左走
//所以是rightsmallparent的左孩子
}
else
{
rightsmallparent->left = rightsmall->left;
}
delete rightsmall;
return true;
就是删4的时候
rightsmallparent初始化为nullptr会导致没有进入循环因为rightsmall->left==nullptr,因为就会导致rightsmallparent为nullptr空去调用
改rightsmallparent初始化为cur
//只剩下两个结点的情况
node* rightsmall = cur->right;
node* rightsmallparent = cur;
while (rightsmall->left)//一直往左走就找到右子树的最小的
{//当这个条件结束rightsmall的左边已经没有值了所以就是最小的
rightsmallparent = rightsmall;
rightsmall = rightsmall->left;
}
cur->_key = rightsmall->_key;//赋值
//替代完成
//删除替代的原位置
if (rightsmall->left == nullptr)
{
if (rightsmallparent->left == rightsmall)
{
rightsmallparent->left = rightsmall->right;
}
else
{
rightsmallparent->right = rightsmall->right;
}
//因为rightsmall一直往左走
//所以是rightsmallparent的左孩子
}
else
{
if (rightsmallparent->left == rightsmall)
{
rightsmallparent->left = rightsmall->left;
}
else
{
rightsmallparent->right = rightsmall->left;
}
}
delete rightsmall;
return true;
}
return false;
把4删调了
中序遍历
对象名 . Inorder(this)有隐藏的this指针不方便所以在this内部中调用函数Inorder()让函数调用函数就不会有this参与
void _InOrder(node* root)//递归调用
{
if (root == nullptr)
{
return;
}
_InOrder(root->left);
cout << root->_key << " ";
_InOrder(root->right);
}
void InOrder()//对象.Inorder(this)有隐藏的this指针不方便所以在this函数中再次调用
{
_InOrder(_root);
cout << endl;
}
Find查找:
bool Find(k key)
{
if (_root == nullptr)
{
return false;
}
else
{
node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->right;
}
else if (cur->_key > key)
{
cur = cur->left;
}
else
{
return true;
}
}
}
}
完整代码
template<class k>
struct BSTreenode
{
BSTreenode<k>* left;
BSTreenode<k>* right;
k _key;
BSTreenode(k key)
:left(nullptr)
, right(nullptr)
, _key(key)
{
}
};
template<class k>
class BSTree
{
typedef BSTreenode<k> node;//重写结点名字,方便
public:
bool erase(const k&key)
{
//先去找
//双指针法
node* cur =_root;
node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->left;
}
else if(cur->_key<key)
{
parent = cur;
cur = cur->right;
}
else
{
//找到了,分情况讨论
//只有一种的情况下:待删结点的子结点为右边
//如果cur两边都没有结点符合
if (cur->left == nullptr)
{
//确定parent的左连接到cur还是右连接到cur
if (parent->left == cur)
{
parent->left = cur->right;
}
else
{
parent->right = cur->right;
}
delete cur;
return true;
}//待删结点的子结点为左边
else if (cur->right == nullptr)
{
//确定parent的左连接到cur还是右连接到cur
if (parent->left == cur)
{
parent->left = cur->left;
}
else
{
parent->right = cur->left;
}
delete cur;
return true;
}
else
{
//只剩下两个结点的情况
node* rightsmall = cur->right;
node* rightsmallparent = cur;
while (rightsmall->left)//一直往左走就找到右子树的最小的
{//当这个条件结束rightsmall的左边已经没有值了所以就是最小的
rightsmallparent = rightsmall;
rightsmall = rightsmall->left;
}
cur->_key = rightsmall->_key;//赋值
//替代完成
//删除替代的原位置
if (rightsmall->left == nullptr)
{
if (rightsmallparent->left == rightsmall)
{
rightsmallparent->left = rightsmall->right;
}
else
{
rightsmallparent->right = rightsmall->right;
}
//因为rightsmall一直往左走
//所以是rightsmallparent的左孩子
}
else
{
if (rightsmallparent->left == rightsmall)
{
rightsmallparent->left = rightsmall->left;
}
else
{
rightsmallparent->right = rightsmall->left;
}
}
delete rightsmall;
return true;
}
return false;
}
}
}
bool insert(k key)
{
//两种情况;
//树里没有数据和有数据俩种
if (_root == nullptr)
{
_root = new node(key);//开辟一个node对象然后调用初始化构造
return true;
}
else
{
//有数据,要找到正确位置
node* parent = nullptr;
node* cur = _root;
while (cur)
{
if (cur->_key>key)//证明待插入元素应该往左边
{
parent = cur;//cur是探测作用,parent是保存上一个指针地址
cur = cur->left;
}
else if (cur->_key < key)//证明待插入元素应该往右边
{
parent = cur;
cur = cur->right;
}
}
//当cur为nullptr就要找到待插入位置。
//但是,还要确定插在parent的左还是右
if (parent->_key>key)
{
parent->left = new node(key);
}
else
{
parent->right = new node(key);
}
}
}
void _InOrder(node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->left);
cout << root->_key << " ";
_InOrder(root->right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool Find(k key)
{
if (_root == nullptr)
{
return false;
}
else
{
node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->right;
}
else if (cur->_key > key)
{
cur = cur->left;
}
else
{
return true;
}
}
}
}
private:
node* _root=nullptr;//定义头指针
};
int main()
{
BSTree<int> T;
int arr[] = { 2,4,5,6,8,9,3,7,1,0 };
for (auto e : arr)
{
T.insert(e);
}
T.InOrder();
T.erase(7);
T.InOrder();
T.erase(5);
T.InOrder();
T.erase(4);
T.InOrder();
}