搜索二叉树的概念
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
通俗来说,左节点的值要比其根节点的值小, 右节点的值要比其根节点的值大。
例子:
搜索二叉树的功能介绍及实现
基本函数
搜索二叉树节点
template<class K>
struct BSTreeNode
{
typedef BSTreeNode<K> Node;
Node* _left;
Node* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{
}
};
搜索二叉树的成员变量
Node* _root;
构造函数
BSTree(Node* root = nullptr)
{
_root = root;
}
析构函数
~BSTree()
{
Destroy(_root);
}
void Destroy(Node* root)//后序遍历删除
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
拷贝构造函数
这里要用深拷贝,用浅拷贝会造成析构两次的后果。
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
BSTree(const BSTree<K>& t)//这里要用深拷贝
{
_root = Copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
搜索二叉树的遍历
中序遍历
void _InOrder(Node* root)//中序遍历
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
注意直接调用InOther()需要传根节点,但是我们无法类外访问根节点(根节点访问权限是private)。
解决方法:
1.套一层函数,在类内对根节点进行访问。
void InOrder()
{
_InOrder(_root);
cout << endl;
}
使用:
BSTree<int> t;
int arr[] = { 8,3,1,10,6,4,7,14,13 };
for (auto e : arr)
{
t.insert(e);
}
t._InOrder();
2.写一个获取根节点的函数,实现在类外访问根节点。
Node* GetRoot()
{
return _root;
}
使用:
BSTree<int> t;
int arr[] = { 8,3,1,10,6,4,7,14,13 };
for (auto e : arr)
{
t.insert(e);
}
t._InOrder(t.GetRoot());
缺陷是类外访问了private的成员,不安全。
搜索二叉树的查找
非递归版本
直接进行比较,将插入的key值与cur节点的值进行比较,比cur节点大将cur更新为右孩子节点,比cur节点小就将cur更新为左孩子节点,直到遇到key值与cur节点值相同的情况,返回true,若最后遇到空则返回false。
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;
}
递归版本
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;
}
}
搜索二叉树的插入
功能:给一个值,插入在二叉搜索树合适的位置(不能插入相同的值)。
非递归版本
直接进行比较,将插入的key值与cur节点的值进行比较,向左右节点进行移动直到遇到空时插入到该位置,此过程需要保存上一个节点的位置用来进行插入节点。
bool insert(const K& key)
{
//检查空树,防止parent==null时直接访问其成员
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 if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
//连接右节点
parent->_right = cur;
}
else
{
//连接左节点
parent->_left = cur;
}
}
递归版本
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;
}
}
搜索二叉树的删除
功能:给定一个值(这里是查找的功能,实际上找到了的是这个值所在的节点),删除该节点。
非递归版本
首先是要找到要删除的节点,这一步与之前查找一样,我们把大致的框架搭建起来:
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)//待找节点比cur节点值大
{
parent = cur;//存一下父节点
cur = cur->_right;//cur移动到右节点
}
else if (cur->_key > key)//待找节点比cur节点值大
{
parent = cur;
cur = cur->_left;
}
else//找到了该节点
{
//对节点进行删除
}
}
下面对删除节点进行分析
情况分析:对于要删除的节点有三种情况
1.该节点无子节点
直接对该节点进行删除。该步骤可以归到2中,因为可以把nullptr当做一个节点连接到父节点中。
2.该节点仅有一个子节点
托孤法删除:将该节点的子节点连接到该节点的父节点,再删除该节点。
1.当该节点有父节点时
根据该节点的父节点的位置不同,有两种情况:
1.该节点是父节点的左节点
2.该节点是父节点的右节点
if (cur->_left == nullptr)
{
if (cur == parent->_right)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (cur == parent->_right)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
delete cur;
return true;
}
2.当该节点无父节点时,此时该节点是根
if (cur->_left == nullptr)
{
if (cur == _root)//直接删根时,将根进行转让
{
_root = cur->_right;
}
else
{
if (cur == parent->_right)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (cur == _root)//直接删根时,将根进行转让
{
_root = cur->_left;
}
else
{
if (cur == parent->_right)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
delete cur;
return true;
}
3.该节点有两个子节点
无法用托孤法将孩子节点连接到父亲节点,因为有两个孩子都连接到父节点,可能会导致父节点出现三个子节点的情况,不符合二叉树的结构。
替换法删除:找一个能替换“要删除的节点”的节点a,交换值,转换为删除a节点。
什么节点能替换?
- 左子树的最大节点(左子树的最右节点)
- 右子树的最小节点(右子树的最左节点)
原因:(以左子树的最左节点为例)以要删除的节点为基准,这个节点的值一定小于右子树的所有节点的值,大于左子树的所有节点。这时找到左子树的最左节点,记为Mi点,该节点是左子树的最小的节点,但也大于基准节点的值,这个Mi节点与基准点是相邻的(按大小关系来说),交换两个相邻的节点不改变其他位置的顺序关系,也就是仅增加一个逆序数,这个时候将Mi点删除后,这个逆序数消失,所有数的相对位置关系都变成顺序。
替换法的代码如下:
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)//在右子树找到最左节点
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;//将最左节点的值赋给待删节点的值
if (rightMin == rightMinParent->_left)
{
rightMinParent->_left = rightMin->_left;
}
else
{
rightMinParent->_right = rightMin->_right;
}
delete rightMin;
return true;
综合这几种情况,总代码如下:
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)//待找节点比cur节点值大
{
parent = cur;//存一下父节点
cur = cur->_right;//cur移动到右节点
}
else if (cur->_key > key)//待找节点比cur节点值大
{
parent = cur;
cur = cur->_left;
}
else//找到了该节点
{
//进行删除
if (cur->_left == nullptr)
{
if (cur == _root)//直接删根时
{
_root = cur->_right;
}
else
{
if (cur == parent->_right)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_right)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
delete cur;
return true;
}
else//当两个节点都不为空时,替换法
{
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMin == rightMinParent->_left)
{
rightMinParent->_left = rightMin->_left;
}
else
{
rightMinParent->_right = rightMin->_right;
}
delete rightMin;
return true;
}
}
}
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;
if (root->_right == nullptr)
{
root = root->_left;
}
else if (root->_left == nullptr)
{
root = root->_right;
}
else
{
Node* rightMin = root->_right;
while (rightMin->_left)
{
rightMin = rightMin->_left;
}
swap(root->_key, rightMin->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
搜索二叉树的类型
K模型:快速查找一个值在不在
上文中就是一个K模型,给定key值查找的是key值。
例子:彩票,直接通过号码在库中查找是否中奖。
KV模型:快速通过一个值(key)查找另一个值(value)在不在?
在节点的结构体中加入一个value值,这样通过key值查找到节点时,可以访问节点的value值,从而实现了通过一个值(key)查找另一个值(value)。
template<class K,class V>
struct BSTreeNode
{
typedef BSTreeNode<K,V> Node;
Node* _left;
Node* _right;
K _key;
V _value;
BSTreeNode(const K& key,const K& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
,_value(value)
{
}
};
例子:英文字典,通过英文查找释意。