首先从这棵树开始,以后的模板参数都叫K(key关键类型)了
1.二叉搜索树的节点设置
cpp
template<class K>
struct BSTreeNode
{
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
BSTreeNode(const K& x)
:_key(x)
,_left(nullptr)
,_right(nullptr)
{ }
};
2.二叉树的大致框架
cpp
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
//using Node = BSTreeNode;
//二者的写法在目前的效果一样
private:
Node* _root = nullptr;
};
3.插入
二叉搜索树有去重和不去重的,下面两种都进行说明
cpp
//bool是用于确定insert是否成功的
//不能返回结点只是因为不能对节点中的值做修改
bool insert(const K& x)
{
if (_root == nullptr)
{
_root = new Node(x);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > x)
{
parent = cur;
cur = cur->_left;
}
else return false;
}
//这样就可以实现相同值的赋值
/*while (cur)
{
if (cur->_key < x)
{
parent = cur;
cur = cur->_right;
}
else
{
parent = cur;
cur = cur->_left;
}
}*/
cur = new Node(x);
//当cur指向新结点时,parent就找不到cur了
//但之前的cur的位置就是parent的位置
//因此parent只要学着cur再走一次就知道cur去哪了
if (parent->_key < x)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
4.中序遍历
二叉平衡树是小的在左边,大的在右边,因此中序遍历是就能优先走小的值再走大的值。
且由于递归需要参数root,但在类外面是无法有树的根节点的,因此要套一层壳。
cpp
void inorder()
{
_inorder(_root);
cout << endl;
}
private:
void _inorder(Node* root)
{
if (root == nullptr)return;
_inorder(root->_left);
cout << root->_key << " ";
_inorder(root->_right);
}
5.查找(也是只能知道在不在,无法知道节点的位置)
cpp
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;
}
6.删除
删除可以分为两种情况
1.被删去的结点只有一个孩子或没有孩子
2.被删去的结点有两个孩子
cpp
bool pop(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
//首先要找到要删出的结点
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了以后就分情况讨论
if (cur->_left == nullptr)
{
//cur是root时,后面删cur就变成删root了,因为此时
//parent与root并不同步
if (cur == _root)
{
_root = cur->_right;
delete cur;
return true;
}
else
{
//并不清楚cur是parent的哪个孩子,因此要分类讨论
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;
delete cur;
return true;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
//此处就是对两个孩子的处理
else
{
Node* replace = cur->_right;
Node* replacepar = cur;
while (replace->_left)
{
replacepar = replace;
replace = replace->_left;
}
cur->_key = replace->_key;
//此处也要找,因为当replace没有拐弯时,他是父亲的右孩子
if (replacepar->_left == replace)
{
replacepar->_left = replace->_right;
}
else
{
replacepar->_right = replace->_right;
}
delete replace;
}
}
}
return true;
}
replace是replacepar的左右结点的两种情况

平衡二叉树的用途:
1.查key
(1)小区停车场查车牌
(2)用英语库查文章是否有单词错误
2.用key/value查value
(1)记录一辆车在停车场停的时间
(2)一篇文章一个单词的出现次数
补充:时间复杂度也叫线性复杂度
7.写key/value的二叉树代码(只需要修改部分即可)
cpp
// 在结点处再加一个存vlaue的参数即可
template<class K,class V>
struct BSTreeNode
{
K _key;
V _value;
BSTreeNode<K,V>* _left;
BSTreeNode<K,V>* _right;
BSTreeNode(const K& x,const V& v)
:_key(x)
,_value(v)
, _left(nullptr)
, _right(nullptr)
{ }
};
template<class K,class V>
class BSTree
{
public:
typedef BSTreeNode<K,V> Node;
BSTree()
{ }
//再插多一个参数即可
bool insert(const K& x,const V& v)
{
if (_root == nullptr)
{
_root = new Node(x,v);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
//比较逻辑完全不用变
while (cur)
{
if (cur->_key < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > x)
{
parent = cur;
cur = cur->_left;
}
else return false;
}
cur = new Node(x, v);
if (parent->_key < x)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
void inorder()
{
_inorder(_root);
cout << endl;
}
//返回值修改为Node*,原因在于key不允许被修改,但value可以.
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;
}
bool pop(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
delete cur;
return true;
}
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;
delete cur;
return true;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else
{
Node* replace = cur->_right;
Node* replacepar = cur;
while (replace->_left)
{
replacepar = replace;
replace = replace->_left;
}
cur->_key = replace->_key;
if (replacepar->_left == replace)
{
replacepar->_left = replace->_right;
}
else
{
replacepar->_right = replace->_right;
}
delete replace;
}
}
}
return true;
}
private:
void _inorder(Node* root)
{
if (root == nullptr)return;
_inorder(root->_left);
//多打印了value
cout << root->_key << " " << root->_value;
_inorder(root->_right);
}
Node* _root;
};
用法一:查中文
cpp
int main()
{
gal::BSTree<string, string> tree;
tree.insert("love","喜欢");
tree.insert("genshin", "原神");
tree.insert("rewrite", "罚抄");
tree.insert("summer pocker", "夏日口袋");
string str;
while (cin >> str)
{
auto e = tree.find(str);
cout << str << "->" << e->_value << " ";
}
return 0;
}
2.统计单词出现次数
核心在于明白查的是string,计算用的是int,用tree中的结点来说明是否存在这个结点。
cpp
int main()
{
string arr[] = { "love","love","love","gal","gal","love","re","re"};
gal::BSTree<string, int> tree;
for (auto& e : arr)
{
auto x = tree.find(e);
if (x == nullptr)
{
tree.insert(e, 1);
}
else x->_value++;
}
//cout << tree.find("love")->_value << endl;
return 0;
}
8.set容器就是key类型的,map容其就是key/value类型的
9.析构和拷贝构造,与=重载
析构:
cpp
~BSTree()
{
destory(_root);
_root = nullptr;
}
private:
//析构由于要递归,因此还是要套壳
void destory(Node* _root)
{
if (_root == nullptr)return;
destory(_root->_left);
destory(_root->_right);
delete _root;
}
拷贝构造:
cpp
BSTree(const BSTree<K>& x)
{
_root = create(x._root);
}
private:
//也可以在同一个函数完成,这里无所谓
Node* create(Node* root)
{
//只能采用前序递归或层序遍历,不然树的结构会被破坏
if (root == nullptr)return nullptr;
Node* newnode = new Node(root->_key);
newnode->_left = create(root->_left);
newnode->_right = create(root->_right);
return newnode;
}
=重载
cpp
BSTree& operator=(BSTree<K>& tree)
{
BSTree tmp = tree;
std::swap(_root,tmp._root);
return *this;
}
发现一件事,当=重载的形参要调用拷贝构造时,编译器直接优化成去调拷贝构造了,因此只有像上面用引用然后设形参才能调到=重载中。