当前位置: 首页 > news >正文

【C++】二叉搜索树(图码详解)

一. 概念

又称二叉排序树、二叉查找树

性质、判定:
        1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
        2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
        3. 它的左右子树都是二叉搜索树

二. 实现

BinarySearchTree.h

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){ }
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){ }bool Insert(const K& key) {}bool Find(const K& key) {}bool Erase(const K& key) {}void InOrder() {}private:void _InOrder(Node* root) {}private:Node* _root;
};void TestBSTree1()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t;for (auto e : a){t.Insert(e);}t.InOrder();t.Erase(4);t.InOrder();t.Erase(6);t.InOrder();t.Erase(7);t.InOrder();t.Erase(3);t.InOrder();for (auto e : a){t.Erase(e);}t.InOrder();
}

1. 查找

从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
最多找高度次:O(N)

红黑树、AVL树:O(logN)

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;
}

2. 插入

树为空,则直接新增节点,赋值给 root 指针
树不空,按二叉搜索树性质查找插入位置,插入新节点

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;
}

3. 中序遍历

中序遍历(左子树、根、右子树)二叉搜索树的结果是排序的结果

void InOrder(Node* root)
{if (root == nullptr){return;}InOrder(root->_left);cout << root->_key << " ";InOrder(root->_right);
}

有问题,我们在外面用对象调用中序遍历要传私有成员变量 _root,但是私有我们不能在类外面用

BSTree<int> t;
t.InOrder();

可以这样解决:

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

4. 删除

要删的节点有3种情况:
1. 没有孩子:托孤
2. 有1个孩子:托孤
3. 有2个孩子:和左子树的最大节点(左子树的最右节点) 右子树的最小节点(右子树的最左节点) 替换

	bool Erase(const K& key){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 // 树中找到了要删除的节点cur{// ......delete cur;return true;}}return false;}

cur 左为空(也解决了没有孩子,左右都为空):

else // 树中找到了要删除的节点cur
{// cur左为空if (cur->_left == nullptr){if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}// ......delete cur;return true;
}

但有这种特殊情况:

else // 树中找到了要删除的节点cur
{// cur左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}// ......delete cur;return true;
}

cur 右为空:同理

else // 树中找到了要删除的节点cur
{// cur左为空if (cur->_left == nullptr) { }// cur右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else // parent->_left == cur{parent->_left = cur->_left;}}}// ......delete cur;return true;
}

cur 左右都不为空:替换 以左子树的最大节点(左子树的最右节点)为例

注意:leftMax 是左子树的最右节点,leftMax 这个节点一定不会有右子树,可能有左子树

注意:
       
这里是左右都不为空的情况,而且我们要去左子树找最右节点,所以 leftMax 可直接定义为 cur->_left;parent 可直接定义为 cur
        如果 leftMax 定义为 cur,parent 定义为 nullptr,例3会坑

注意:替换后要通过找到父亲直接删(一定可以直接删,因为 leftMax 右一定为空)。不能递归删(7 < 8,在右子树找,找不到,删不了)。因为搜索树的结构变了,而且无法传根,无法控制;进而导致不满足二叉搜索树的性质

else // 树中找到了要删除的节点cur
{// cur左为空if (cur->_left == nullptr) { }// cur右为空else if (cur->_right == nullptr) { }// cur左右都不为空else{// 找替代节点Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else // parent->_right == leftMax{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;
}

三. 递归版实现

C++里,凡是树形结构递归,都要单独写子函数。因为递归是子问题,要控制子树

BinarySearchTree.h

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){ }
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){ }void InOrder(){_InOrder(_root);cout << endl;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}private:bool _FindR(Node* root, const K& key) {}bool _InsertR(Node*& root, const K& key) {}bool _EraseR(Node*& root, const K& key) {}void _InOrder(Node* root) {}private:Node* _root;
};

1. 查找

比根大,在右子树找;比根小,在左子树找;到空还没找到,则不存在

bool FindR(const K& key)
{return _FindR(_root, key);
}bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}
}

2. 插入

插入的值 < 根:往左子树去插入
插入的值 > 根:往右子树去插入
插入的值 == 根:插入失败
走到空的地方就可以插入

怎么插入?new Node(key),但还要找父亲,怎么解决?加引用成为 Node*& root

这里指针的作用:链接树
这里引用的作用:下一层改变影响上一层

bool InsertR(const K& key)
{return _InsertR(_root, key);
}bool _InsertR(Node*& root, const K& key)
{if (root == nullptr){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;}
}

如果是空树,root 是 _root 的别名,new 的第一个节点刚好给 _root
如果不是空树,层层往下递归,前面的引用不起作用,每一层(每一个栈帧都有一个引用

6 是对象,把左指针这个成员传给下一层,下一层的 root 是 6 的左指针的别名(引用此时不发挥作用)
5 > 4,把 4 的右指针往下传,root 4 的右指针的别名
4 的右指针为空 ==> 插入
new节点,给 root,对 root 修改,就是对 4 的右指针修改
这一句赋值,直接就链接上了,不用找父亲,不用比较大小

3. 删除

先找有没有要删的节点,找到了就删,同样分3种情况:左为空、右为空、左右都为空

bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{if (root == nullptr) // 树里没有return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else // 找到了,准备删{Node* del = root;if (root->_left == nullptr) // 1.左为空{root = root->_right;}else if (root->_right == nullptr) // 2.右为空{root = root->_left;}else // 3. 左右都不为空{ }delete del;return true;}
}

3 < 6,3 的右指针往下传,root 是 3 的右指针的别名
此时 root 是 6,找到了,开始删:root 左为空,把 root(3的右指针)赋值为 root 的右指针
root 的右指针指向 7 ==> 3 的右指针指向 7,完成了链接关系

root 是 _root 的别名,上来就找到了,开始删:
root 不为空,root = root->_right 就是 _root = _root->_right;


左右都为空:找替代节点(以找左树的最右节点为例,最右节点的右一定为空)

以刚开始就找到要删的 8 为例:
        转化为删红圈的节点。非递归实现一定可以找父亲,直接删;不能递归删

我们现在有了引用,root 是 _root 的别名
但在 root 当前位置发挥不了作用,因为不需要改 _root,所以不能直接在最大的树删除

可以转化为在蓝圈的树中删,递归往下走,一定是右为空的情况。那时,root 是 6 的右指针的别名
走这个情形:

else // 3. 左右都不为空
{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);
}

这种方法在替换后,会在左子树再找一遍要删除的节点,但代价不大

第11行递归进去之后,不会再次走这个左右都不为空的 else

4. 析构、拷贝、赋值

析构:析构也得写子函数,因为要递归,析构函数都没有参数
        二叉树:用后序遍历删除,循环不好用

拷贝:不能调 Insert,会改变树的形状
        走前序遍例赋值

默认的拷贝构造是浅拷贝,会出错,要自己实现深拷贝

赋值:现代写法

BSTree(const BSTree<K>& t)
{_root =  Copy(t._root);
}BSTree<K>& operator=(BSTree<K> t)
{swap(_root, t._root);return *this;
}~BSTree()
{Destroy(_root);
}void Destroy(Node*& root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr; // 这就是传引用的原因
}Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_key);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;
}

非递归+递归整体代码

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){ }
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){ }BSTree(const BSTree<K>& t){_root =  Copy(t._root);}BSTree<K>& operator=( BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);}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;}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;}bool Erase(const K& key){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 // 树中找到了要删除的节点cur{// cur左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else // parent->_left == cur{parent->_left = cur->_right;}}}// cur右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else // parent->_left == cur{parent->_left = cur->_left;}}}// cur左右都不为空 else{// 找替代节点Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else // parent->_right == leftMax{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}private:void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr; // 这就是传引用的原因}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_key);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){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;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr) // 树里没有return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else // 找到了,准备删{Node* del = root;if (root->_left == nullptr) // 1.左为空{root = root->_right;}else if (root->_right == nullptr) // 2.右为空{root = root->_left;}else // 3. 左右都不为空{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root;
};void TestBSTree1()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t;for (auto e : a){t.InsertR(e);}t.InOrder();t.EraseR(4);t.InOrder();t.EraseR(6);t.InOrder();t.EraseR(7);t.InOrder();t.EraseR(3);t.InOrder();for (auto e : a){t.EraseR(e);}t.InOrder();
}void TestBSTree2()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t;for (auto e : a){t.InsertR(e);}BSTree<int> t1(t);t.InOrder();t1.InOrder();
}

四. 应用模型

1. key 的搜索模型

快速判断在不在的场景

门禁系统、小区车辆出入系统 ……

2. key_value 的搜索模型

通过一个值找另一个值

商场的车辆出入系统、高铁实名制车票系统 ……

namespace key_value
{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){ }};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:BSTree():_root(nullptr){ }void InOrder(){_InOrder(_root);cout << endl;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key, value);}bool EraseR(const K& key){return _EraseR(_root, key);}private: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;}}bool _InsertR(Node*& root, const K& key, const V& value){if (root == nullptr){root = new Node(key, value);return true;}if (root->_key < key){return _InsertR(root->_right, key, value);}else if (root->_key > key){return _InsertR(root->_left, key, value);}else{return false;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr) // 树里没有return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else // 找到了,准备删{Node* del = root;if (root->_left == nullptr) // 1.左为空{root = root->_right;}else if (root->_right == nullptr) // 2.右为空{root = root->_left;}else // 3. 左右都不为空{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root;};
}

拼写检查:读取词库放到一颗搜索树;读取单词,看在不在树中,不在则拼写错误

void TestBSTree1()
{BSTree<string, string> dict;dict.InsertR("hello", "你好");dict.InsertR("tree", "树");dict.InsertR("apple", "苹果");dict.InsertR("day", "天");string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.FindR(str);if (ret != nullptr){cout << ret->_value << endl;}else{cout << "没有此单词" << endl;}}
}

统计出现次数

void TestBSTree2()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果","苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (auto& str : arr){BSTreeNode<string, int>* ret = countTree.FindR(str);if (ret == nullptr){countTree.InsertR(str, 1);}else{ret->_value++;}}countTree.InOrder();
}

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

http://www.dtcms.com/a/502785.html

相关文章:

  • MySQL:14.mysql connect
  • 建设工程消防信息网站网站开发全程实例课本代码
  • 用excel绘制茎叶图
  • 龙岗优化网站建设门户网站建设需要多少
  • 东莞seo网站制作报价网站有域名没备案
  • 网站开发框架图自建wordpress主题
  • 网站公司模板多少工资
  • 江西建设厅网站dede手机网站模板哦
  • 网站运维平台建设原则西乡网站的建设
  • 帮网站网站做推广被抓会判刑吗凡科做的网站百度不到
  • 一台服务器怎么做多给网站wordpress用户投稿
  • 提供免费主页空间的网站中石油第七建设公司网站
  • 顺德网站建设基本流程高柏企业管理咨询有限公司
  • 深圳公司建立网站网络建设专业石家庄
  • 基于jsp的电商网站开发wordpress模板带小程序源码
  • 大秦建设集团有限责任公司官方网站开发一个游戏软件多少钱
  • 大学网站栏目建设都哪些网站可以做gif
  • 做企业网站那家好湖北雨水最新消息
  • 湖南seo网站多少钱微信软文范例大全100
  • 怎么做监控网站idc机房运维
  • 网站国际推广专业网站建设提供商
  • 新加坡域名注册网站重庆营销网站
  • 网站psd切图做响应式效果常用网站建设软件
  • 企业彩铃网站源码搭建游戏
  • 济南哪里有建网站深圳找工作的网站
  • 网站轮播图居中代码怎么写全球可以做外贸的社交网站
  • 高端网站有哪些优势免费咨询法律平台
  • 蛋糕网站建设规划书龙岗互联网公司
  • 广州网站推广制作常见的跨境电商平台有哪些
  • 炫酷网站设计网站超市安装