特征
( 1 ) 左子树上所有结点的值,都小于根;右子树上所有结点的值,都大于根
( 2 ) 同时它的左右子树也满足上述性质
( 3 ) 默认的二叉搜索树不允许键值冗余(不能有相同的值).
( 4 ) 中序遍历的结果是升序.
代码实现
--成员属性
二叉搜索树中仅保存根结点指针.
template<class K>
//结点类型
struct BinarySearchTreeNode
{
BinarySearchTreeNode(const K& key = K())
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
typedef BinarySearchTreeNode<K> BSTreeNode;
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
};
//二叉搜索树
template<class K>
class BinarySearchTree
{
typedef BinarySearchTreeNode<K> Node;
private:
Node* _root;
}
--查找
从根开始找:
若要查找的key比当前结点的键值大,说明在当前结点的右子树中;
若key比当前结点的键值小,说明在当前结点的左子树中.
bool find(const K& key)
{
Node* cur = _root;
while (cur != nullptr)
{
if (key == cur->_key)
return true;
else if (key < cur->_key)
cur = cur->_left;
else if (key > cur->_key)
cur = cur->_right;
}
return false;
}
//递归实现,内部封装_findR,在类外不需要传_root
public:
bool findR(const K& key)
{
return _findR(_root, key);
}
private:
bool _findR(Node* root, const K& key)
{
if (root == nullptr) return false;
if (root->_key == key)
return true;
else if (key > root->_key)//去右子树找
return _findR(root->_right, key);
else//去左子树找
return _findR(root->_left, key);
}
--插入
找到 插入位置 和 插入位置的父结点,进行链接
bool insert(const K& key)
{
Node* newNode = new Node(key);
//空树
if (_root == nullptr)
{
_root = newNode;
return true;
}
//找到插入位置
Node* cur = _root;
//记录插入位置的父亲结点
Node* parent = nullptr;
while (cur != nullptr)
{
//如果搜索树有该key,插入失败
//否则按搜索树的规则向后找插入位置
if (key == cur->_key)
return false;
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
}
//正式插入,比父亲结点大就插入到右边,否则插入到左边
if (key > parent->_key)
parent->_right = newNode;
else if (key < parent->_key)
parent->_left = newNode;
return true;
}
//如果形参是Node*,root就是局部变量,root的修改不会影响二叉搜索树
//如果改成Node*&, root就是 上一层父亲结点的孩子指针 别名
bool _insertR(Node*& root, const K& key)
{
//找到插入位置
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (key > root->_key)//如果key比根大,往右子树插入
return _insertR(root->_right, key);
else if (key < root->_key)//key比根小,往左子树插入
return _insertR(root->_left, key);
else
return false;
}
--删除
( 1 ) 找到要删除的结点
( 2 ) 待删除结点的情况:
//1 要删除的结点只有1/0个孩子
if (cur->_left == nullptr)//如果删除结点的左孩子为空,就把它的右孩子托付给parent
{
//注意_root可能就是要删除的结点,此时要修改_root
if (cur == _root)
_root = cur->_right;
else if (parent->_left == cur)
parent->_left = cur->_right;
else if (parent->_right == cur)
parent->_right = cur->_right;
delete cur;
}
else if (cur->_right == nullptr)//如果删除结点的右孩子为空,就把它的左孩子托付给parent
{
if (_root == cur)
_root = cur->_left;
else if (parent->_left == cur)
parent->_left = cur->_left;
else if (parent->_right == cur)
parent->_right = cur->_left;
delete cur;
}
若删除结点有两个孩子:
使用替换法删除 —— 在删除结点的子树中找可以顶替该位置的值,
把该值赋值给删除结点,然后删除掉被替换的结点.
可以用删除结点的左子树最大结点的值替换,或者用删除结点的右子树最小结点的值替换.
被替换结点要么没有孩子结点,要么只有一个孩子结点,可以直接删除
//要删除的结点有两个孩子(替换法)
//用cur的左子树最大结点 或者 cur的右子树最小结点 可以替换删除
Node* max = cur->_left;
Node* maxParent = cur;
//找到替换结点和它的父亲结点
while (max->_right != nullptr)
{
maxParent = max;
max = max->_right;
}
//把替换结点的键值交给cur
cur->_key = max->_key;
//删除替换结点max,max可能有左孩子
if (maxParent->_left == max)
maxParent->_left = max->_left;
else
maxParent->_right = max->_left;
delete max;
完整代码
bool erase(const K& key)
{
// 1 找到要删除的结点
Node* cur = _root;
Node* parent = nullptr;
while (cur != nullptr)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else//成功找到要删除的结点,开始删除
{
//1 要删除的结点只有1/0个孩子
if (cur->_left == nullptr)//如果删除结点的左孩子为空,就把它的右孩子托付给parent
{
//注意_root可能就是要删除的结点,此时要修改_root
if (cur == _root)
_root = cur->_right;
else if (parent->_left == cur)
parent->_left = cur->_right;
else if (parent->_right == cur)
parent->_right = cur->_right;
delete cur;
}
else if (cur->_right == nullptr)//如果删除结点的右孩子为空,就把它的左孩子托付给parent
{
if (_root == cur)
_root = cur->_left;
else if (parent->_left == cur)
parent->_left = cur->_left;
else if (parent->_right == cur)
parent->_right = cur->_left;
delete cur;
}
else//要删除的结点有两个孩子(替换法)
{
//用cur的左子树最大结点 或者 cur的右子树最小结点 可以替换删除
Node* max = cur->_left;
Node* maxParent = cur;
//找到替换结点和它的父亲结点
while (max->_right != nullptr)
{
maxParent = max;
max = max->_right;
}
//把替换结点的键值交给cur
cur->_key = max->_key;
//删除替换结点
if (maxParent->_left == max)
maxParent->_left = max->_left;
else
maxParent->_right = max->_left;
delete max;
}
return true;
}
}
//没有找到要删除的结点,删除失败
return false;
}
bool _eraseR(Node*& root, const K& key)
{
if (root == nullptr) return false;
if (key > root->_key)
return _eraseR(root->_right, key);
else if (key < root->_key)
return _eraseR(root->_left, key);
//开始删除
//1 右为空
//2 左为空
//3 左右都不为空
Node* del = root;//保存要删除的结点
if (root->_left == nullptr)
{
root = root->_right;//这里的root已经是 上一层父结点的孩子指针 别名
delete del;
}
else if (root->_right == nullptr)
{
root = root->_left;
delete del;
}
else//root有两个子结点,用替换法删除
{//用删除结点root的左子树的最大结点来替换删除
Node* max = root->_left;
Node* maxParent = root;
while (max->_right != nullptr)
{
maxParent = max;
max = max->_right;
}
root->_key = max->_key;
if (maxParent->_left == max)
maxParent->_left = max->_left;
else
maxParent->_right = max->_left;
delete max;
}
return true;
}
--插入删除测试代码
//中序遍历二叉树递归实现
void InOrder()const
{
_InOrderR(_root);
cout << endl;
}
void _InOrderR(Node* root)const
{
if (root == nullptr) return;
_InOrderR(root->_left);
cout << root->_key << " ";
_InOrderR(root->_right);
}
void testMyBSTreeInsertErase()
{
BinarySearchTree<int> bsTree;
//插入删除
bsTree.insert(2);
bsTree.insert(1);
bsTree.insert(3);
bsTree.insert(6);
bsTree.insert(5);
bsTree.insert(4);
bsTree.InOrder();
for (int i = 0; i < 6; ++i)
{
bsTree.erase(i+1);
bsTree.InOrder();
}
//递归版本
BinarySearchTree<int> bsTreeR;
//插入删除
bsTreeR.insertR(2);
bsTreeR.insertR(1);
bsTreeR.insertR(3);
bsTreeR.insertR(6);
bsTreeR.insertR(5);
bsTreeR.insertR(4);
bsTreeR.InOrder();
for (int i = 0; i < 6; ++i)
{
bsTreeR.eraseR(i + 1);
bsTreeR.InOrder();
}
}
--拷贝构造函数与赋值运算符重载
BinarySearchTree(const BinarySearchTree& copy)
{
_root = _copy(copy._root);
}
//拷贝root这棵树,返回拷贝出来的树的根
Node* _copy(Node* root)
{
if (root == nullptr)
return nullptr;
//先拷贝根,再递归拷贝左子树、右子树
Node* copyRoot = new Node(root->_key);
Node* copyLeft = _copy(root->_left);
Node* copyRight = _copy(root->_right);
//链接
copyRoot->_left = copyLeft;
copyRoot->_right = copyRight;
return copyRoot;
}
//赋值运算符重载,复用拷贝构造
//copy是形参,出了作用域会销毁
//把this的_root交换给copy,让copy帮忙销毁原空间
//同时拿到copy的拷贝完成的树.
BinarySearchTree& operator=(BinarySearchTree copy)
{
swap(copy._root, _root);
return *this;
}
--析构函数
~BinarySearchTree()
{
_destory(_root);
}
void _destory(Node* root)
{
if (root == nullptr)
return;
//后序遍历
_destory(root->_left);
_destory(root->_right);
delete root;
}
Key模型与KeyValue模型
Key的搜索模型:用来判断关键字key在不在,二叉搜索树中key默认不可修改.
上述二叉搜索树的实现就是Key的搜索模型.
例:检查一篇英文文档中的单词拼写是否正确.
( 1 ) 将词库里的单词,全部插入到一棵二叉搜索树中(字符串可以比较大小)
( 2 ) 依次检查每个单词,是否在该二叉搜索树中.
KeyValue的搜索模型:通过key去找value,
搜索树中key默认不可修改,但是value可以修改.
即将value绑定在key中,但是二叉搜索树内的比较和查找,都只看key,
找到key,就相当于找到了对应的value,同时可以借此修改value
例:字符串中,统计字符出现次数
//KeyValue模型 find 返回 结点的指针,可以用来修改对应的value
Node* find(const K& key)
{
Node* cur = _root;
while (cur != nullptr)
{
if (key == cur->_key)
return cur;
else if (key < cur->_key)
cur = cur->_left;
else if (key > cur->_key)
cur = cur->_right;
}
return nullptr;
}
void InOrder()const
{
_InOrderR(_root);
cout << endl;
}
void _InOrderR(Node* root)const
{
if (root == nullptr) return;
_InOrderR(root->_left);
cout << root->_key << "->"<< root->_val <<endl;
_InOrderR(root->_right);
}
void testKV()
{
string str = "aazzccddd";
yh::BinarySearchTree<char, int> countChar;
for (auto& x : str)
{
yh::BinarySearchTreeNode<char, int>* exist = countChar.find(x);
//如果该字符存在,
if (exist != nullptr)
++exist->_val;
else//否则插入字符,同时字符绑定的value设为1
countChar.insert(x, 1);
}
countChar.InOrder();
}
普通二叉搜索树的缺陷
普通的二叉搜索树,它查找的时间复杂度是O(h),h是树的高度.
即它查找的效率,与它的高度相关.
在极端情况下,会一直往 左子树/右子树 插入,查找效率就会大大降低.
最坏情况下,h = N.
因此,二叉搜索树需要改进,改进的方案就是 平衡树.
平衡树有 红黑树、AVL树等:
每次插入结点或删除结点时,都会按一定的规则,判断是否需要旋转来保持搜索树的平衡,
使树的高度基本保持在 logN.