二叉搜索树你懂了吗?
什么是二叉搜索树?
二叉搜索树就是当我们需要对一组非常庞大的数字进行搜索时,什么样的数据结构才能让我们用尽量少的时间来搜索到我们想要找的数字呢?(当然也可以是其他数据类型,这里我用数字来举例)
这个时候我们想到了树型结构,可是树型结构也得需要一定的规律插入才能达到我们想要的效果呀。这个时候就诞生了二叉搜索树。
二叉搜索树规定,第一个插入的是头结点,后来插入的值如果比当前结点的值小,就往左子树走;如果比当前结点的值大,就往左子树上走。以此类推,找到最合适的一个空结点进行插入。(当然如果你想让大的在左边也是可以的,这个取决于你)
理想的二叉搜索树:
举个栗子?
当个我按顺序插入数字 {7}、{3, 9}、{1, 5, 8, 10}。(大括号的顺序不可以打乱,大括号内的顺序可以打乱)
我们心中的理想的二叉树是这样的:
这个时候我来搜索任意一个结点的时间复杂度是 O(Log2 N);
代码实现:
#pragma once
#include <iostream>
template <class T>
struct BSTNode
{
BSTNode(const T& val = T())
:_val(val),
_left(nullptr),
_right(nullptr)
{}
T _val;
BSTNode<T>* _left;
BSTNode<T>* _right;
};
template <class T>
class BSTree
{
public:
typedef BSTNode<T> Node;
typedef Node* pNode;
BSTree(const pNode root = nullptr)
:_root(root)
{}
pNode Find(const T& val)
{
if (_root == nullptr)
return nullptr;
pNode cur = _root;
while (cur)
{
if (cur->_val == val)
{
return cur;
}
else if (cur->_val > val)
{
cur = cur->_left;
}
else if (cur->_val < val)
{
cur = cur->_right;
}
}
return nullptr;
}
bool Insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
pNode cur = _root;
pNode parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_val > val)
{
cur = cur->_left;
}
else if (cur->_val < val)
{
cur = cur->_right;
}
else
{
return false;
}
}
pNode newNode = new Node(val);
if (parent->_val > val)
{
parent->_left = newNode;
}
else
{
parent->_right = newNode;
}
return true;
}
bool Erase(const T& val)
{
if (_root == nullptr)
{
return false;
}
pNode cur = _root;
pNode parent = nullptr;
while (cur)
{
if (cur->_val == val)
{
break;
}
else if (cur->_val > val)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_val < val)
{
parent = cur;
cur = cur->_right;
}
}
if (cur->_left == nullptr && cur->_right == nullptr)
{
if (cur != _root)
{
if (parent->_left == cur)
parent->_left = nullptr;
else
parent->_right = nullptr;
}
else
{
_root = nullptr;
}
delete cur;
cur = nullptr;
}
else if (cur->_left == nullptr && cur->_right != nullptr)
{
if (cur != _root)
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else if (parent->_right == cur)
{
parent->_right = cur->_right;
}
}
else
{
_root = _root->_right;
}
delete cur;
cur = nullptr;
}
else if (cur->_left != nullptr && cur->_right == nullptr)
{
if (cur != _root)
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else if (parent->_right == cur)
{
parent->_right = cur->_left;
}
}
else
{
_root = _root->_left;
}
delete cur;
cur = nullptr;
}
else
{
pNode exchange = cur;
if (cur == _root)
cur = cur->_left;
pNode newNode = cur;
pNode nParent = nullptr;
while (newNode)
{
if (newNode->_right)
{
nParent = newNode;
newNode = newNode->_right;
}
else
break;
}
if (nParent == nullptr)
{
exchange->_left = newNode->_left;
}
exchange->_val = newNode->_val;
if (nParent)
nParent->_right = nullptr;
delete newNode;
newNode = nullptr;
}
return true;
}
void Inorder(void)
{
_Inorder(_root);
std::cout << std::endl;
}
private:
void _Inorder(const pNode root)
{
if (root)
{
_Inorder(root->_left);
std::cout << root->_val << " ";
_Inorder(root->_right);
}
}
pNode _root;
};
因为注释乱码了,所以把注释删了,以后尽量尝试写英文注释
这里其实查找和插入都不复杂,这里可以自己看很容易看懂。
比较复杂的是删除,删除分几种情况:
- 当要删除的
cur
(就是指要删除的结点) 是叶子结点的时候,这个时候只需要将这个cur
的父结点对应这个cur
的那个指针置空,然后直接删除即可。 - 当要删除的
cur
只有左子树的时候,这个时候就让cur
的父结点对应cur
的指针指向cur
的左子树,然后删除cur
即可。 - 当要删除的
cur
只有右子树的时候,这与上一条类似。 - 当要删除的
cur
左右子树都存在的时候,有两个方法,我们可以在cur
的左子树里找一个值最大的结点,或者在cur
的右子树里找一个值最小的结点。把找到的结点的值赋值给cur
,然后删除这个找到的结点即可。(想想,不管是哪种寻找方法,都可以保持二叉搜索树的性质不改变对吗?比如在左子树里找一个值最大的结点"放在"cur
上,可以保证cur
的左子树的所有结点的值都比cur
的值小,cur
右子树的所有结点的值都比cur
大。)
注意:二叉搜索树的中序遍历是一个有序的。
二叉搜索树的缺点:
但是,有这样一个情况,如果插入一组有序的数字呢?就比如 {1, 2, 3, 4, 5, 6, 7, 8, 9}
这个时候二叉搜索树是这样的:
这个时候再去搜索任何一个结点的时间复杂度就又变成了 O(N)。这跟我们的初衷相违背,我们就是想要查找的时候能快一点。
所以这个时候就有大佬发明了 AVL树。
下篇会介绍 AVL树。
叮~ ?