🌏博客主页:PH_modest的博客主页
🚩当前专栏:C++跬步积累
💌其他专栏:
🔴 每日一题
🟡 Linux跬步积累
🟢 C语言跬步积累
🌈座右铭:广积粮,缓称王!
文章目录
二叉搜索树的概念
二叉搜索树(BST,Binary Search Tree)又称二叉排序数,它或者是一颗空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
- 若他的右子树不为空,则右子树上所有节点的值都大于根节点的值。
- 它的左右子树也分别为二叉搜索树。
注意: 由它的性质可以得出,中序遍历二叉搜索树得到的一组数据是有序的,中序遍历的顺序即是:左 - 根 - 右。
下图就是一个二叉搜索树:
二叉搜索树操作
int a[ ] = {8,3,1,10,6,4,7,14,13};
二叉搜索树的查找
- 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
- 最多查找高度次,走到空,还没找到,这个值就不存在。
二叉树的插入
- 树为空,则直接新增节点,赋值给root指针
- 树不空,按二叉搜索树性质查找插入位置,插入新节点
若不为空树,具体插入操作如下:
- 若待插入节点的值小于根节点的值,则需要将节点插入到左子树。
- 若待插入节点的值大于根节点的值,则需要将节点插入到右子树。
- 若待插入节点的值等于根节点的值,则插入失败。
二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的节点可能分下面四种情况:
- 要删除的节点无孩子节点
- 要删除的节点只有左孩子节点
- 要删除的节点只有右孩子节点
- 要删除的节点有左、右孩子节点
看起来有待删除节点有4中情况,实际情况1可以与情况2或者3合并起来,因此真正的删除过程如下:
- 情况2:删除该节点且使删除节点的双亲节点指向被删除节点的左孩子节点——直接删除。
- 情况3:删除该节点且使删除节点的双亲节点指向被删除节点的右孩子节点——直接删除。
- 情况4:在它的右子树中寻找中序下的第一个节点(关键码最小),用它的值填补到被删除节点中,再来处理该节点的删除问题——替换法删除
二叉搜索树的实现
节点类
要实现二叉搜索树,我们首先需要实现一个节点类:
- 节点类中包含三个成员变量:节点值、左指针、右指针。
- 节点类当中只需要实现一个构造函数即可,用于构造指定节点值的节点。
template<class K>
struct BSTreeNode
{
K _key; //节点值
BSTreeNode<K>* _left; //左节点
BSTreeNode<K>* _right; //右节点
//构造函数
BSTreeNode(const K& key = 0)
:_val(key)
,_left(nullptr)
,_right(nullptr)
{
}
};
构造函数
构造函数只需要创建一个空树即可:
BSTree()
:_root(nullptr)
{
}
拷贝构造函数
Node* _Copy(Node* root)
{
if (root == nullptr)//如果为空,直接返回
{
return nullptr;
}
Node* newnode = new Node(root->_key);//拷贝根节点
newnode->_left = _Copy(root->_left);//拷贝左子树
newnode->_right = _Copy(root->_right);//拷贝右子树
return newnode;//返回拷贝的树
}
BSTree(const BSTree<K>& t)
{
_root = _Copy(t._root);//拷贝二叉搜索树
}
赋值运算符重载函数
在这里提供一个现代写法,使用swap函数,直接交换两个对象的二叉搜索树的根节点的指针,也就间接实现了二叉树的交换。注意是返回对象的引用,这样可以连续赋值,例如:a=b=c;
BSTree<K>& operater = (BSTree<K> t)
{
swap(_root,t._root);
return *this;
}
析构函数
析构函数可以调用一个_Destory(),通过_Destory()函数来逐个释放每个节点,因为析构函数不方便设置参数,所以可以通过定义一个私有的函数来封装一下,析构函数直接调用即可。
void _Destory(Node* root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
}
~BSTree()
{
_Destory(_root);
_root = nullptr;
}
中序遍历
中序遍历暴露在外面的函数依旧是不带参数的,所以为了方便,可以定义一个子函数,通过调用子函数来进行封装。
思路:
- 先判断是否为空,为空则直接退出
- 然后通过 左 - 根 - 右 来进行遍历,通过该方式得到的数组是递增的
- 可以通过递归调用,依次往下查找,先递归左子树
- 当左子树递归到最深处时,会返回,然后遍历根,此时只需要输出当前值即可
- 然后就是继续遍历右子树
该递归就是根据中序遍历的性质来写的,只需要考虑清楚什么时候输出值即可:遍历完左端点返回之后
void _Inoder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inoder(root->_left);
cout << root->_key << " ";
_Inoder(root->_right);
}
void Inoder()
{
_Inoder(_root);
cout << "\n";
}
插入函数
- 首先任然需要先判断是不是空树,如果是空树,就直接在空节点上插入
- 如果不是空树就遍历,按照搜索二叉树的特性,进行判断,直至找到正确的插入的点
- 这里需要注意的是,需要定一个parent来存储上一个点的地址,便于将待插入的值连接上去
- 最后连接时判断一下是连在左端点还是右端点
bool Insert(const K& key)
{
if (_root == nullptr