1.二叉搜索树的概念
1.它有两种存在形式,首先就是空树,其次就是下面的几种表现形式
2.若它的左子树不为空,则左子树的所有值小于根节点的值
3.若它的右子树不为空,则右子树的所有值大于根节点的值
4.它的左右子树都是二叉搜索树
2.二叉搜索树的实现
1.节点
1.结合二叉树的知识,二叉搜索树分别写两个类,一个是节点的类,一个是整个二叉树的类
template<class T>
struct BSTNode
{
BSTNode(const T& val = T())
:_val (val)
,_left(nullptr)
,_right(nullptr)
{}
BSTNode<T>* _left;
BSTNode<T>* _right;
T _val;
};
2.注意事项:无论何时调用类模板,都要把模板参数给出
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
private:
Node* _node;
};
3.为了方便调用节点模板,先把它typedef为node
2.构造函数
BSTree()
:_node(nullptr)
{}
3.拷贝构造函数
BSTree(const BSTree<T>& b)
{
_root = Copy(b._node);
}
Node* Copy(Node *b)
{
if (b == nullptr)
return nullptr;
Node* p = new Node(b->_val);
p->_left = Copy(b->_left);
p->_right = Copy(b->_right);
return p;
}
1.为了方便用递归,我们用别的函数实现拷贝,再用拷贝构造函数调用那个函数
2.实现使用前序遍历
4.赋值操作符重载
BSTree<T>& operator=(BSTree<T>b)
{
swap(b._node, _node);
return *this;
}
1.临时拷贝一份对象再与根交换,采用现代写法
5.析构函数
~BSTree()
{
destory(_node);
}
void destory(Node* node)
{
if (node == nullptr)
return;
if (node->_left)
destory(node->_left);
if (node->_right)
destory(node->_right);
delete node;
}
1.同样的道理,另写一个函数使用后序遍历进行析构,再用析构函数调用这个函数
6.中序遍历二叉树
void _InOrder(Node* node)
{
if(node->_left)
_InOrder(node->_left);
cout << node->_val << endl;
if(node->_right)
_InOrder(node->_right);
}
void InOrder()
{
_InOrder(_node);
}
1.中序遍历可以得到二叉树的升序和降序排列
3.查找(时间复杂度O(longn))
1.迭代实现
Node* Find(const T& val)
{
Node* node = _node;
while (node)
{
if (node->_val == val)
return node;
else if (node->_val > val)
node = node->_left;
else
node = node->_right;
}
return nullptr;
}
1.当前节点值小于目标值朝右找,当前节点值大于目标值朝左找
2.找到则返回节点的指针,没找到返回nullptr
2.递归实现
bool RFind(const T& val)
{
return _RFind(_node, val);
}
bool _RFind(Node*node,const T& val)
{
if (node == nullptr)
return false;
if (val == node->_val)
return true;
return _RFind(node->_left, val) || _RFind(node->_left, val);
}
1.返回指针比较困难,所以直接返回真假
4.插入
1.迭代
bool Insert(const T& val)
{
if (_node == nullptr)
{
_node = new Node(val);
}
else
{
Node* parent = nullptr;
Node* child = _node;
Node* ptr = new Node(val);
while (child)
{
parent = child;
if (_node->_val > val)
{
child = child->_left;
}
else if (_node->_val < val)
{
child = child->_right;
}
else
return false;
}
if (parent->_val > val)
parent->_left = ptr;
if (parent->_val < val)
parent->_right = ptr;
}
return true;
}
1.传入的是引用,当发现根节点为空时直接修改根节点而不需要二级指针
2.定义父亲节点和孩子节点,在循环中找到那个对应的父类(要插入的数据就是他的孩子)
3.判断是放到父节点的左节点还是右节点
4.如果已经存在返回false,如果插入成功返回true
2.递归
bool RInsert(const T& val)
{
_RInsert(_node, val);
}
bool _RInsert(Node*& p, const T& val)
{
if (p == nullptr)
{
p = new Node(val);
return true;
}
if (p->_val > val)
return RInsert(p->_left, val);
else if (p->_val < val)
return RInsert(p->_right, val);
else
return false;
}
1.还是一个函数调用另一个函数
2.传的是引用,所以不需要父亲节点,直接修改即可
3.插入成功返回true,插入失败返回false
5.删除(难点)
1.迭代
bool Erase(const T& val)
{
if (_node == nullptr)
return true;
Node* cur = _node;
Node* parent_node = nullptr;
while (cur)
{
if (cur->_val == val)
break;
else if (cur->_val > val)
{
parent_node = cur;
cur = cur->_left;
}
else
{
parent_node = cur;
cur = cur->_right;
}
}
if (cur == nullptr)
return false;
if (cur->_left == nullptr)
{
if (parent_node == nullptr)
_node = _node->_right;
else if (parent_node->_left == cur)
parent_node->_left = cur->_right;
else if (parent_node->_right == cur)
parent_node->_right = cur->_right;
delete cur;
}
else if (cur->_right == nullptr)
{
if (parent_node == nullptr)
_node = _node->_left;
else if (parent_node->_left == cur)
parent_node->_left = cur->_left;
else
parent_node->_right = cur->_left;
delete cur;
}
else
{
Node* leftmax = cur->_left;
Node* parent_leftmax = cur;
while (leftmax->_right)
{
parent_leftmax = leftmax;
leftmax = leftmax->_right;
}
cur->_val = leftmax->_val;
if (parent_leftmax->_left)
parent_leftmax->_left = leftmax->_left;
else
parent_leftmax->_right = leftmax->_left;
delete leftmax;
}
}
1.首先查找元素是否在二叉搜索树中,如果不在则返回false,否则要删除的节点分为下面四种情况
1.要删除的节点无孩子节点
2.要删除的节点只有左孩子节点
3.要删除的节点只有右孩子节点
4.要删除的节点有左右孩子节点
2.实际情况中删除节点有4中情况,但情况1可以与2或3合并起来,真正的删除过程如下
1.情况2:删除该节点且使被删除节点的父节点指向被删除节点的左孩子节点
2.情况3:删除该节点且使被删除节点的父节点指向被删除节点的右孩子节点
3.情况4:在它的左子树中寻找最大的节点a(右树最小也可以),将它的值填补到被删除的节点中,再来处理节点a,再来处理节点a(替换法删除)
4.再此基础上,还要对每种情况下删除节点是否为根节点进行讨论
2.递归
bool _RErase(Node*&node,const T&val)
{
if (node == nullptr)
return false;
if (node->_val > val)
return _RErase(node->_left, val);
if (node->_val < val)
return _RErase(node->_right, val);
else
{
if (node->_left == nullptr)
{
Node* right = node->_right;
delete node;
node = right;
}
else if (node->_right == nullptr)
{
Node* left = node->_left;
delete node;
node =left;
}
else
{
Node* p = node->_left;
while (p->_right)
{
p = p->_right;
}
swap(node->_val , p->_val);
return _RErase(node->_left,val);
}
return true;
}
}
bool RErase(const T& val)
{
return _RErase(_node,val);
}
1.由于使用了引用,对于左子树和右子树为空不再需要父节点的帮助
2.若左右子树都不为空,则交换letfmax和node数值后,通过递归消除leftmax
3.最后递归传参不要直接传根节点,而是node->left,因为之前的交换打乱了二叉树的结构,只能确定node->_left这棵树还是结构正确的
6.二叉搜索树应用
1.k模型
1.K 模型即只有 key 作为关键码,结构中 只需要存储Key 即可,关键码即为需要搜索到 的值 。
比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:
1.以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
2.在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2.KV模型
1.每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对 。该种方式在现实生活中非常常见:
1.比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文<word, chinese>就构成一种键值对;
2.比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对
// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
{
BSTNode(const K& key = K(), const V& value = V())
: _pLeft(nullptr) , _pRight(nullptr), _key(key), _Value(value)
{}
BSTNode<T>* _pLeft;
BSTNode<T>* _pRight;
K _key;
V _value
};
template<class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
typedef Node* PNode;
public:
BSTree(): _pRoot(nullptr){}
PNode Find(const K& key);
bool Insert(const K& key, const V& value)
bool Erase(const K& key)
private:
PNode _pRoot;
};
void TestBSTree3()
{
// 输入单词,查找单词对应的中文翻译
BSTree<string, string> dict;
dict.Insert("string", "字符串");
dict.Insert("tree", "树");
dict.Insert("left", "左边、剩余");
dict.Insert("right", "右边");
dict.Insert("sort", "排序");
// 插入词库中所有单词
string str;
while (cin>>str)
{
BSTreeNode<string, string>* ret = dict.Find(str);
if (ret == nullptr)
{
cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
}
else
{
cout << str << "中文翻译:" << ret->_value << endl;
}
}
}
void TestBSTree4()
{
// 统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
BSTree<string, int> countTree;
for (const auto& str : arr)
{
// 先查找水果在不在搜索树中
// 1、不在,说明水果第一次出现,则插入<水果, 1>
// 2、在,则查找到的节点中水果对应的次数++
//BSTreeNode<string, int>* ret = countTree.Find(str);
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
以上为改进二叉搜索树KV结构,以上所有函数的测试原码笔者正在抓紧制作中,欢迎以后来看看哦