目录
(一)搜索二叉树 分析
1.结构:
- 搜索二叉树主要功能有:搜索,增删,排序(中序遍历)。
- 搜索二叉树的存储特点:在二叉树的基础上,左节点的键总是小于父节点的键,右节点的键总是大于父节点的键。在深层意义上,我们存储的数据需要有一个键(以此键作为标准来排序)。
- 搜索二叉树中nullptr也被看成一个节点,这一点在递归中有很大妙用。
- 父子节点的继承关系:我们假设 3为父节点,删除了3,父节点的位置将要由它的子节点继承,但这不改变子节点与它下一代节点的继承方式,问题的关键:“到底是左孩子节点还是右孩子节点顶替了父节点3"。这是插入和删除节点的时候我们最关心的问题。多继承问题是复杂的 ,所以我们将其转化为单继承问题解决,即”左/右孩子顶替了父节点“两种情况讨论。其他情况也划归成这两种情况。
2. 性能分析:
2.1BST与二分查找:
谈起二分查找,在排序中算是很优秀的搜索算法,但是二分查找的条件要求存储数据必须是有序的数组/链表,然而对于一批维护的数据需要伴随着频繁的增删改查,以及排序算法多次调用带来的代价也就大了起来,随之程序的效率就会大大下降。而BST更像二分查找的存储升级版,可以有序的增删数据。
相比之下,BST的存储和搜索就很均衡。BST在搜索中表现得也很好,每向下移动一个深度,就消除大约50%的潜在节点(接近O(log n))。比如我们要找到2,我们就可以从根(5)开始,向左移动,意味着我们立刻消除了4个可能的节点。继续向1移动,我们又消除了一个可能的节点。然后在1的右面我们找到了节点,只通过了3步实现了的节点。
2.2树的平衡性将影响BST的性能。
这个例子就很极端,当树不是最优或不平衡时候,BST的搜索性变差,O(n)可能成为最坏的情况。所以根据键值选取合适的根节点,调整存储顺序是很重要的因素。
(二)搜索二叉树实现
1.非递归实现
1.1查找
Node* 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 cur;
}
}
return nullptr;
}
1.2插入
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 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;
}
return true;
}
1.3定点删除
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else//找到了,开始删除
{
//1.左为空或者右为空,把另一个孩子交给父亲管理,删除自己
if (cur->_left == nullptr) //待删除节点左孩子为空
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else {
parent->_right = cur->_right;
}
}
delete cur;
}
else if (cur->_right == nullptr)//待删除节点右孩子为空
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
else //2.左右孩子都不为空
{
//找到右树的最小节点去替代删除
Node* minRightParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minRightParent = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;
//将右孩子交给父节点管理
if (minRight == minRightParent->_left)
{
minRightParent->_left = minRight->_right;
}
else
{
minRightParent->_right = minRight->_right;
}
delete minRight;
return true;
}
}
}
return false;
}
1.4 中序遍历(输出排序)
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
2.递归实现
2.1 递归与非递归版本对比
1.一般工程中不建议使用非递归版本,额外开销很大。
2.非递归版本和递归版本间可以相互转化。
3.递归关注状态的分析。
2.2 关键点分析
递归的特点:一个子过程结束后自动链接下一个子过程。可以回溯到上一个节点,也可以传递到下一个节点。
当前 _root节点A 子过程进行中
接下来 _root->right节点B 子过程未进行 可以调用节点A,B,C
接下来 _root->right节点C 子过程未进行 可以调用节点B,C....
2.3代码实现
1.插入
bool _InsertR(Node*& root, const K& key)
{
if (root == NULL) // 插入
{
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;
}
}
2.查找
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return nullptr;
}
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else if (root->_key > key)
{
return _FindR(root->_left, key);
}
else
{
return root;
}
}
3.删除:
在右树查找并删掉替换节点,然后将最小值赋值给父节点。
bool _EraseR(Node*& root, const K& key)
{
if (root == NULL)
{
return false;
}
if (root->_key < key)
{
return _EraseR(root->_right, key);
}
else if (root->_key > key)
{
return _EraseR(root->_left, key);
}
else
{
// 找到了,root就是要删除的节点
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
}
else if (root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete del;
}
else
{
Node* minRight = root->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
K min = minRight->_key;
// 转换成在root的右子树删除min
_EraseR(root->_right, min);
root->_key = min;
}
return true;
}
}
(三)二叉搜索树应用
1.场景一:用于排序《key模型》
key模型,就是唯一属性作为键值,完成搜索和存储。要求唯一键具有"可比性"。主要应用场景是对一组不重复数据,进行搜索,排序,增删等操作。key模型的单一属性,决定被操作数据不可以重复。
2.场景二:用于搜索《key--value模型》
key-value模型,要求设置唯一键,对其余属性数据进行查找,增删,排序等操作。应用场景:对于英文词典,汉译英,英译汉都是key-value场景。再比如我们可以用key-value模型统计重复元素出现的次数。同时,key-value允许被操作的数据重复。
1.定义:
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _value;
BSTreeNode(const K& key, const V& value)
: _left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
2.查找:
Node* Find(const K& key)
{
Node* cur = _root;
if (_root == nullptr)
{
return nullptr;
}
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if(cur->_key<key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
3. 插入:
V& operator[](const K& key)
{
pair<Node*, bool> ret = Insert(key, V());
return ret.first->_val;
}
pair<Node*, bool> Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return make_pair(_root, true);
}
//查找要插入的位置
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else//没找到+树中已有该值
{
return make_pair(cur, false);
}
}
cur=new Node(key,value);
if (cur->_key<parent->_key)
{
parent->_left = cur;
}
else {
parent->_right = cur;
}
return make_pair(cur, true);
}
4.删除:
// 如果树中不存在key,返回false
// 存在,删除后,返回true
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
if (nullptr == cur)
{
return false;
}
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else//找到了
{
if (cur->_left == 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 == _root)
{
_root = cur->_left;
}
else {
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else {
parent->_right = cur->_left;
}
}
delete cur;
}
else {
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
parent = minRight;
minRight = minRight->_left;
}
//cur = new Node(minRght->_key,minRight->_value);
cur->_key = minRight->_key;
cur->_val = minRight->_val;
if (parent->_left == minRight)
{
parent->_left = minRight->_right;
}
else {
parent->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
5.*测试:
一般我们将BST类实例化的时候,这个程序就有了功能。
例:维护一组英语单词数据<以英文作为键,中文作为属性>,可以提供英译汉,扩充字典,删除单词等功能。
void TestBSTree()
{
KV::BSTree<string, string> dict;
dict.InsertR("hello", "你好");
dict.InsertR("baby", "婴儿");
dict.InsertR("cat", "猫");
dict.InsertR("dog", "狗");
dict.InsertR("sort", "排序");
// ...插入词库中所有单词
string str;
while (cin>>str)
{
KV::BSTreeNode<string, string>* ret = dict.FindR(str);
if (ret == nullptr)
{
cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
}
else
{
cout << str << "中文翻译:" << ret->_value << endl;
}
}
}