二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
也就是说一棵二叉搜索树的任一个根节点,它的左子树所有节点的值都是小于根节点的值的,它的右子树所有结点的值都是大于根节点的值的。
二叉搜索树查找的时间复杂度:
只有当是满二叉树或者是完全二叉树时间复杂度才是〇(logN)!!
解释:
此时树的高速就是结点的个数,同时如果数据量过大,而且是递归查找的话,很有可能会有爆栈的风险!!
在以后我们会学习平衡二叉树,就是为了解决上述情况的问题。
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;
private:
//没有参数是不能递归的
void DestroyTree(Node* root)
{
if (root == nullptr)
return;
DestroyTree(root->_left);
DestroyTree(root->_right);
delete root;
}
Node* CopyTree(Node* root)
{
if (root == nullptr)
return nullptr;
Node* copyNode = new Node(root->_key);
copyNode->_left = CopyTree(root->_left);
copyNode->_right = CopyTree(root->_right);
return copyNode;
}
public:
//强制编译器自己生成构造函数 -- C++11
BSTree() = default;
/*BSTree()
:_root(nullptr)
{}*/
//前序遍历递归拷贝
BSTree(const BSTree<K>& t)
{
_root = CopyTree(t._root);
}
//t1 = t2; -- 任何赋值重载都可以用现代写法
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
DestroyTree(_root);
_root = nullptr;
}
构造函数:
拷贝构造:
析构函数:
采用递归方式的缺点就是如果数的结点个数足够多的时候,就会有爆栈的风险!!
在二叉搜索树中找某个值:
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
根据二叉搜索树的性质,查找规则很简单:
所以再没有平衡二叉搜索树的情况下,查找的时间复杂度为〇(N)
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
parent = cur;
cur = cur->_left;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
根据搜索二叉树的特性,插入规则如下:
二叉搜索树的删除,是一件非常麻烦的事情
分析问题:
以删除7这个结点为例:
核心步骤:
代码如下:
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了就分三种情况
//该结点有一个孩子 -- 左为空 or 右为空(托孤)
//该结点有两个孩子 -- 替换法
//第一种情况:该结点有一个孩子且该结点的左为空
if (cur->_left == nullptr)
{
//当删除的是根节点的时候
//if(parent == nullptr)
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
//第二种情况:该结点有一个孩子且该结点的右为空
else if (cur->_right == nullptr)
{
if (cur == parent)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
//两个孩子都不为空(替换法删除)
else
{
//我们这里统一找右树最左结点(最小)
//右子树的最小结点替代
//minParent一开始不能给空,因为右子树的跟一开始就可能是minRight
//Node* minParent = nullptr; -- 循环直接不能进去
Node* minParent = cur;
//从右子树的根开始
Node* minRight = cur->_right;
//找最左结点(最小)
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
//交换
swap(minRight->_key, cur->_key);
//**return Erase(key); -- 这是错的,因为这里已经不符合搜索树的规则了
//递归过程中找不到想要想要删除的数(交换到后头的数)
//直接赋值
//cur->_key = minRight->_key;
//删除
//找到最小结点,此结点一定是该结点父亲结点的左孩子
//此结点一定没有左孩子(一定是左为空),有可能有右孩子,也可能没有右孩子
//此时只需要将父亲的左指向该结点的右孩子即可
//删除完成
if (minParent->_left == minRight)
{
minParent->_left = minRight->_right;
}
else if (minParent->_right == minRight)
{
minParent->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
代码解释,如下图:
找要删除结点的左子树的最大值节点:
同理,找右子树的最小值节点也是一样的道理
递归版本理解起来就相对与非递归版本更好理解了,直接看代码
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else if (root->_key > key)
{
return _FindR(root->_left, key);
}
else
{
return true;
}
}
逐层递归查找即可…
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool _InsertR(Node*& root, const K& key)
{
//没有父指针,胜似父指针
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
{
return _InsertR(root->_right, key);
}
else if (root->_key > key)
{
return _InsertR(root->_left, key);
}
else
{
return false;
}
}
该如何链接上树呢?
用了一个指针的引用就解决了问题
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
//递归是用来找要删除的结点
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _EraseR(root->_right, key);
}
else if (root->_key > key)
{
return _EraseR(root->_left, key);
}
else
{
Node* del = root;
//root是要删除结点的左结点/右结点的别名
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* minRight = root->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
swap(root->_key, minRight->_key);
return _EraseR(root->_right, key);
//转换成在root->_right(右子树)中去删除key
//这里删除这个key一定会走左为空的场景(找最小)
}
delete del;
return true;
}
}
相等时就开始删除了(递归只是用来查找要删除的数的位置)
分三种情况删除:
总的来说递归版本比非递归版本更容易理解,删除过程参考非递归删除过程……(有异曲同工之妙)