二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,适合用于实现高效的搜索、插入和删除操作
节点结构
- 每个节点都包含一个值(通常是一个可比较的数据元素)
- 最多两个子节点,分别称为左子节点和右子节点
- 左子节点的值小于当前节点的值,而右子节点的值大于当前节点的值
- 所以一个结点的左子树里的数值一定比它小,右子树一定比他大
中序遍历
对BST进行中序遍历(In-Order Traversal)会以升序访问树中的所有节点
唯一性
BST中不允许重复的值,每个节点的值都必须是唯一的
左子树和右子树也是BST
BST的左子树和右子树本身也是BST
这个性质递归地应用于树的每个子树
但是,如果BST不平衡(即左右子树的高度差太大),它的性能可能会下降,因此需要采取措施来确保树的平衡
例如使用平衡二叉树(AVL树)或红黑树等
由于搜索树的特性,我们需要按照它来进行相应的插入/删除/查找
并且搜索树的实现有两种方式 -- 递归 / 非递归
插入
- 首先防止该树是空树
- 然后就是,找到要插入结点应该在的位置
- (要注意:因为树中无重复结点,所以当找到相等数值的点,直接返回false,而不执行插入)
- 除此之外,还要注意:因为我们要改变的是结构体内的指针指向(也就是结点内的指向),所以需要该结点的指针(也就是上一个结点的指针),所以需要在找的过程中,父结点位置的指针也需要拿到
查找
- 查找的思路很简单,因为搜索树有自己特定的结构,所以很容易查找
- 当前结点比要查找的结点数值小时,就往它的右树走;大了就去左树
删除
首先就是要先找到要删除的结点
其次就是要判断
- 如果该结点无子树,直接将它的父结点指空就行
- 如果该结点仅有一个子树,直接让父结点指向它的子树
- 这两种情况可以合并(因为无子树,也就相当于子树是空)
- 并且要防止删除根结点的情况 -- 因为我们会使用父结点来连接它的子树
- 但根结点没有父结点,所以需要特殊处理
接下来就是最麻烦的一种情况,该结点左右子树均存在
- 一旦要将它删除,就需要找到这个结点的代替结点(要满足搜索树的结构特性)
- 要么是该结点的左子树中的最大结点
- 要么是右子树中的最小结点
我们这里寻找左子树的最大结点:
- (最大结点,也就意味着它没有右子树)
- 所以直接让它和删除结点互换数值后,父结点指向最大结点的左子树即可
- 但是要注意,我们无法确定这个最大结点是在父结点的左还是右
- (一般来说应该在右,但如果最大结点没有发生变化,那就是在左,初始值就是删除结点的左结点)
最后我们要删除的是找到的最大结点,所以需要将它的位置赋值给cur(最后统一释放cur)
注意
- 要注意的是 -- 递归一般都需要传入结点指针参数,但是在类外使用该函数时,无法传入(因为根结点指针是私有成员)
- 所以可以设计子函数,让子函数完成递归过程,父函数作为外部调用的接口
- 递归函数的代码量一般都比非递归的少,因为实际的过程是由编译器来完成,我们只需要写出统一的方法即可
插入
子函数的使用:
- 注意这里的root是引入!!!
- 引入可有大用处了,可以直接赋值(这里是引用传入,直接可以修改本体;非递归那里是修改类内的成员变量/修改父结点的指向)
- 然后通过搜索树的特性,去合适的地方找到插入的位置
- (注意,这里root使用的时候,实际上它都是根结点的引用,或者父结点的左指针/右指针的引用,所以可以直接修改,而不用拿到父结点的指针)
查找
- 查找逻辑就很简单,利用特性不断寻找就行,找到了就返回该指针,找到空就返回空
- 要注意,在递归函数调用前,如果有返回值,最好都加上return,可以让函数一旦拿到返回值后,就一层层直接返回
删除
看着好像很多,但其实主要是注释,代码量比非递归的少太多了
- 还是一样的,先查找
- 当找到该结点时,还是要先判断有无左右子树
- 如果无子树/仅有一边子树,可以直接让父结点改变指向(在这里就直接让root更新就行)
- 因为root实际上是当前结点(根结点)/当前结点的父结点的左/右指针别名
- 如果左右都有,还是一样的,先找到代替的结点
- 但接下来的操作就不一样了,因为递归最重要的就是复用代码
- 所以我们试着将这种情况 --> 可以被处理的无子树/仅有一边子树的情况
- 可以先交换数值,那么我们就可以转变成在左子树中删除(原先要删除结点的数值)的结点
- (如果不交换,咋找也没办法,必须得交换才行,交换了才能修改此时该结点左右子树的存在状态)
- 然后进行递归,最终会在前两种情况下进行处理
- 最后释放结点
拷贝构造
很简单的逻辑,就一层一层往下拷贝,遇到空/左右均拷贝完毕就返回到上一层
代码
namespace key { template
class BsTree_node { public: BsTree_node(const K &key) : _data(key), _left(nullptr), _right(nullptr) {} K _data; BsTree_node *_left; BsTree_node *_right; }; template class BsTree { typedef BsTree_node node; public: // 构造+析构+拷贝 BsTree() : _root(nullptr) {} ~BsTree() { _destroy(_root); } BsTree(const BsTree &t) { _root = copy(t._root); } BsTree &operator=(BsTree t) { std::swap(_root, t._root); return *this; } // 遍历 void inOrder() { _inOrder(_root); std::cout << std::endl; } // 各种功能 bool insert(const K &key); bool insert_R(const K &key) { return _insert_R(_root, key); } node *find(const K &key); node *find_R(const K &key) { return _find_R(_root, key); } bool erase(const K &key); bool erase_R(const K &key) { return _erase_R(_root, key); } private: // 所需的子函数 void _destroy(node *root) { if (root == nullptr) { return; } _destroy(root->_left); _destroy(root->_right); delete root; root = nullptr; } void _inOrder(node *root) // 这里必须得传入参数,不然没法递归,但_root是私有成员,没法在类外调用,所以可以外部包装一层函数 { if (root == nullptr) { return; } _inOrder(root->_left); std::cout << root->_data << " "; _inOrder(root->_right); } node *copy(node *root) { node *cp = nullptr; if (root == nullptr) { return nullptr; } cp = new node(root->_data); cp->_left = copy(root->_left); cp->_right = copy(root->_right); return cp; } bool _insert_R(node *&root, const K &key); node *_find_R(node *root, const K &key); bool _erase_R(node *&root, const K &key); private: // 成员 node *_root; }; // 函数实现 template bool BsTree ::insert(const K &key) { if (_root == nullptr) { _root = new node(key); } else { node *cur = _root, *parent = cur; while (cur) { if (key > cur->_data) { parent = cur; cur = cur->_right; } else if (key < cur->_data) { parent = cur; cur = cur->_left; } else { return false; } } if (key < parent->_data) { parent->_left = new node(key); } else { parent->_right = new node(key); } } return true; } template bool BsTree ::_insert_R(typename BsTree ::node *&root, const K &key) { if (root == nullptr) { // 该插入了 root = new node(key); // 此时root是它父结点的左指针/右指针的别名,直接修改即可 return true; } if (key > root->_data) { return _insert_R(root->_right, key); // return 是为了让该函数完成操作返回true/false时,可以快速出[递归出的一堆函数],因为只要返回值,当前函数就执行完成辽 } else if (key < root->_data) { return _insert_R(root->_left, key); } else { return false; } } // template typename BsTree ::node *BsTree ::find(const K &key) { node *root = _root; while (root) { if (key > root->_data) { root = root->_right; } else if (key < root->_data) { root = root->_left; } else { return root; } } return nullptr; } template typename BsTree ::node *BsTree ::_find_R(typename BsTree ::node *root, const K &key) { if (root == nullptr) { return nullptr; } if (key > root->_data) { return _find_R(root->_right, key); } else if (key < root->_data) { return _find_R(root->_left, key); } else { return root; } } // template bool BsTree ::erase(const K &key) { node *cur = _root, *parent = cur; while (cur) // 找到删除的结点 { if (key > cur->_data) { parent = cur; cur = cur->_right; } else if (key < cur->_data) { parent = cur; cur = cur->_left; } else { break; } } if (cur == nullptr) { return false; } // 此时cur指向要被删除的结点 if (cur->_left == nullptr) // cur左为空 { if (cur == _root) // 防止cur此时是根结点 { _root = cur->_right; } else { // 判断cur在parent的左边还是右边,以此让父亲继承儿子的右子树 if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } } } 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 { // 当左右子树均在时,找到左子树中最大数,让他当这块的根结点 node *tmp = cur->_left, *p = cur; while (tmp->_right != nullptr) { p = tmp; tmp = tmp->_right; } // tmp指向此时左子树的最大值,即tmp无右子树 // 现在要判断tmp在p的左边还是右边(因为有可能tmp的位置就是最大值了) if (p->_left == tmp) { p->_left = tmp->_left; } else { p->_right = tmp->_left; } std::swap(cur->_data, tmp->_data); cur = tmp; } delete cur; return true; } template bool BsTree ::_erase_R(typename BsTree ::node *&root, const K &key) { if (root == nullptr) { return false; } if (key > root->_data) { return _erase_R(root->_right, key); // return 是为了让该函数完成操作返回true/false时,可以快速出[递归出的一堆函数],因为只要返回值,当前函数就执行完成辽 } else if (key < root->_data) { return _erase_R(root->_left, key); } else // 找到了 { node *tmp = root; // 后续删除用 if (root->_left == nullptr) { root = root->_right; // 此时root是它父结点左指针/右指针的别名,直接将其修改至root的非空结点即可 } else if (root->_right == nullptr) { root = root->_left; } else { // 两边都不为空的话,就得找该结点的代替结点了(左子树中最大的结点) node *tr = root->_left; while (tr->_right != nullptr) { tr = tr->_right; } std::swap(root->_data, tr->_data); return _erase_R(root->_left, tr->_data); // 此时已经互换值了,如果还在root找,万一rt就是root的left,换了就不符合平衡树了,也就根本找不到 // 但既然tr可以代替root,那root自然也能代替tr,也就是说tr的左右子树是符合平衡树的,所以直接在root的左子树找就行 } delete tmp; // 左子树中寻找时,肯定最终会找到,并且处理掉了那个结点的左子树,就会走到这里,删除,然后层层返回true tmp = nullptr; return true; } } }
和key版本的差不多,只不过为key值多了个与其关联的value值
比如,可以实现翻译功能(将中文与对应英文相互绑定)
实现基本就是复用key版本的代码,只需要修改一下模板参数和函数参数即可
代码
namespace key_value { template
class BsTree_node { public: BsTree_node(const K &key, const V &value) : _key(key), _value(value), _left(nullptr), _right(nullptr) {} K _key; V _value; BsTree_node *_left; BsTree_node *_right; }; template class BsTree { typedef BsTree_node node; public: // 构造+析构+拷贝 BsTree() : _root(nullptr) {} ~BsTree() { _destroy(_root); } BsTree(const BsTree &t) { _root = copy(t._root); } BsTree &operator=(BsTree t) { std::swap(_root, t._root); return *this; } // 遍历 void inOrder() { _inOrder(_root); std::cout << std::endl; } // 各种功能 bool insert(const K &key, const V &value); node *find(const K &key); bool erase(const K &key); private: // 所需的子函数 void _destroy(node *root) { if (root == nullptr) { return; } _destroy(root->_left); _destroy(root->_right); delete root; root = nullptr; } void _inOrder(node *root) // 这里必须得传入参数,不然没法递归,但_root是私有成员,没法在类外调用,所以可以外部包装一层函数 { if (root == nullptr) { return; } _inOrder(root->_left); std::cout << root->_key << ": " << root->_value << " "; _inOrder(root->_right); } node *copy(node *root) { node *cp = nullptr; if (root == nullptr) { return nullptr; } cp = new node(root->_key, root->_value); cp->_left = copy(root->_left); cp->_right = copy(root->_right); return cp; } private: // 成员 node *_root; }; // 函数实现 template bool BsTree ::insert(const K &key, const V &value) { if (_root == nullptr) { _root = new node(key, value); } else { node *cur = _root, *parent = cur; while (cur) { if (key > cur->_key) { parent = cur; cur = cur->_right; } else if (key < cur->_key) { parent = cur; cur = cur->_left; } else { return false; } } if (key < parent->_key) { parent->_left = new node(key, value); } else { parent->_right = new node(key, value); } } return true; } // template typename BsTree ::node *BsTree ::find(const K &key) { node *root = _root; while (root) { if (key > root->_key) { root = root->_right; } else if (key < root->_key) { root = root->_left; } else { return root; } } return nullptr; } // template bool BsTree ::erase(const K &key) { node *cur = _root, *parent = cur; while (cur) // 找到删除的结点 { if (key > cur->_data) { parent = cur; cur = cur->_right; } else if (key < cur->_data) { parent = cur; cur = cur->_left; } else { break; } } if (cur == nullptr) { return false; } // 此时cur指向要被删除的结点 if (cur->_left == nullptr) // cur左为空 { if (cur == _root) // 防止cur此时是根结点 { _root = cur->_right; } else { // 判断cur在parent的左边还是右边,以此让父亲继承儿子的右子树 if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } } } 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 { // 当左右子树均在时,找到左子树中最大数,让他当这块的根结点 node *tmp = cur->_left, *p = cur; while (tmp->_right != nullptr) { p = tmp; tmp = tmp->_right; } // tmp指向此时左子树的最大值,即tmp无右子树 // 现在要判断tmp在p的左边还是右边(因为有可能tmp的位置就是最大值了) if (p->_left == tmp) { p->_left = tmp->_left; } else { p->_right = tmp->_left; } std::swap(cur->_data, tmp->_data); cur = tmp; } delete cur; cur=nullptr; return true; } }
void TestBSTree()
{
BSTree 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->_value << endl;
}
else
{
cout << "单词拼写错误" << endl;
}
}
// 统计水果出现的次数
string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
BSTree countTree;
for (auto str : strs)
{
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
可以做出简易的翻译器:
计数器: