概念
二叉搜索树也叫二叉排序树(也可以是空树),它是具备以下特征的二叉树:
1.若它的左子树不为空,则它的所有左子树的值都小于根结点。
2.若它的右子树不为空,则它的所有右子树的值都大于根结点。
3.它的左右子树也分别为二叉搜索树。
二叉树的应用
1.K模型(set):
即只有K作为关键码的模型,结构中只存储Key。
我们经常用这个模型来判断有没有的场景,比如一个单词在一个词典里面有没有。
2.K-V模型(map):
每一个关键码Key都有一个对应的Val。
这种对应关系我们可以用来找单词的出现频率,英文对应的中文的场景。
代码实现(K-V模型非递归)
#pragma once
#pragma once
#include<iostream>
#include <queue>
using namespace std;
namespace hzj {
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _val;
BSTreeNode(const K& key, const V& val)
:
_left(nullptr),
_right(nullptr),
_key(key),
_val(val)
{}
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
BSTree()
:_root(nullptr)
{}
bool Insert(const K& key, const V& val)
{
if (_root == nullptr)
{
_root = new Node(key, val);
return true;
}
Node* prev = _root;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
prev = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
prev = cur;
cur = cur->_right;
}
else
{
cout << "该元素已经存在" << endl;
return false;
}
}
cur = new Node(key, val);
if (key < prev->_key)
{
prev->_left = cur;
}
else
{
prev->_right = cur;
}
return true;
}
Node* Find(const K& key)
{
if (_root == nullptr)
{
return nullptr;
}
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* prev = _root;
while (cur)
{
if (key < cur->_key)
{
prev = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
prev = cur;
cur = cur->_right;
}
else
{
if (cur->_left == nullptr)//左为空
{
if (cur == _root)
{
_root = _root->_right;
}
if (cur->_right == nullptr)//左右均为空
{
if (prev->_left == cur)
{
prev->_left = nullptr;
}
else
{
prev->_right = nullptr;
}
}
else //右不为空
{
if (cur == prev->_right)
{
prev->_right = cur->_right;
}
else
{
prev->_left = cur->_right;
}
}
}
else if (cur->_right == nullptr)//右为空,左不为空
{
if (cur == _root)
{
_root = _root->_left;
}
if (cur == prev->_right)
{
prev->_right = cur->_left;
}
else
{
prev->_left = cur->_left;
}
}
else //左右均不为空,此时可以选择找cur左树的最大值或者右树的最小值
{
//此时不需要处理特殊情况(cur==_root),因为这里与prev无关,不会造成野指针问题。
Node* leftMax = cur->_left;//此处用找左树的最大值做法
Node* prevMax = cur;
while (leftMax->_right)
{
prevMax = leftMax;
leftMax = leftMax->_right;
}
swap(cur->_key, leftMax->_key);
swap(cur->_val, leftMax->_val);
if (prevMax->_left == leftMax) // 一种特殊情况,就是左树没有右结点
{
prevMax->_left = leftMax->_left;
}
else
{
prevMax->_right = leftMax->_left;
}
cur = leftMax;
}
//cout << endl;
delete cur;
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
}
void PreOrder()
{
_PreOrder(_root);
}
void LevelOrder() // 层序遍历
{
_LevelOrder(_root);
}
private:
Node* _root;
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_val << " ";
_InOrder(root->_right);
}
void _PreOrder(Node* root)
{
if (root == nullptr) return;
cout << root->_key << ":" << root->_val << " ";
_PreOrder(root->_left);
_PreOrder(root->_right);
}
void _LevelOrder(Node* root)
{
queue<Node*> q;
q.push(root);
while (q.size())
{
Node* tmp;
int n = q.size();
for (int i = 0; i < n; ++i)
{
tmp = q.front();
q.pop();
cout << tmp->_key << " : " << tmp->_val;
if (tmp->_left) q.push(tmp->_left);
if (tmp->_right) q.push(tmp->_right);
}
cout << endl;
}
}
};
void test1()
{
BSTree<string, string> dict;
dict.Insert("insert", "插入");
dict.Insert("erase", "删除");
dict.Insert("left", "左边");
dict.Insert("string", "字符串");
string str;
while (cin >> str)
{
auto ret = dict.Find(str);
if (ret)
{
cout << str << ":" << ret->_val << endl;
}
else
{
cout << "单词拼写错误" << endl;
}
}
string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
// 统计水果出现的次
BSTree<string, int> countTree;
for (auto str : strs)
{
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_val++;
}
}
countTree.InOrder();
}
void test2()
{
BSTree<string, string> dict;
dict.Insert("insert", "插入");
dict.Insert("erase", "删除");
dict.Insert("left", "左边");
dict.Insert("string", "字符串");
dict.InOrder();
cout << endl;
dict.Erase("insert");
dict.Erase("erase");
dict.Erase("left");
dict.Erase("string");
dict.InOrder();
}
}
二叉搜索树代码实现唯一比较困难的地方就是删除函数(Erase),因为如何将目标结点删除后仍然保持二叉搜索树的结构有点复杂,需要耐心分析多种情况。其中要注意的是,当删除的目标结点左右都不为空时最为复杂,我们可以通过找到目标结点左树的最大值,或者右树的最小值,找到后与目标结点的值进行交换,然后再进行尾部删除。
二叉搜索树的性能分析
因为插入和删除都需要查找,所以查找效率代表了二叉搜索树中各个操作的性能。
总结:
最好的情况查找次数为高度次,时间复杂度就是O(logn),最坏会退化为单分支树,那么时间复杂度也就是O(n)。