目录
1.二叉搜索树概念
二叉搜索树又称二叉排序树,它可以是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
2.二叉搜索树的操作
1.查找
从根开始查找:
--若查找的值比根节点的值大, 向右查找.
--若比根节点的值小向左查找.
--循环下去,若到nullptr都没找到,返回false
2.插入
a.空树
1.直接将其设为根节点
b.非空树
1.根据二叉搜索树的性质,找到插入的位置
2.在该位置上新生成一个节点,并与父节点链接
(可能是左,也可能是右) 根据这个节点的key与parent的key值判断在那边链接
3.删除
1.找到要删除的节点
2.对该节点进行讨论:
a.该节点没有节点或只有1子个节点
--可以直接删除, 并将它的子节点给它的父点
b.该节点有2个子节点
--找到它左子树最大节点/右子树最小节点来代替它, 并将其删除
3.细节处理
当删除到root->left/right为空的时候, 需要更新头节点
3.二叉搜索树的实现
3.1定义BST
节点
template<class K > struct BSTreeNode { BSTreeNode* _left; BSTreeNode* _right; K _key; //构造函数 BSTreeNode(const K& k) :_left(nullptr) ,_right(nullptr) ,_key(k) {} };
BSTree
template<class K, class V> class BSTree { typedef BSTreeNode<K, V> Node; public: //功能 bool Insert(const K& key, const V& value); Node* Find(const K& key); bool Erase(const K& key); void _InOrder(Node* root); void InOrder(); private: Node* _root = nullptr; };
3.2功能实现
1.默认成员函数
构造函数
//构造函数 BSTree() :_root(nullptr) {}
拷贝构造
//拷贝构造 BSTree(const BSTree<K>& t) { Copy(t._root); } Node* Copy(Node* root) { if (root == nullptr) return nullptr; //前序遍历拷贝 Node* newRoot = new Node(root->_key); newRoot->_left = Copy(root->_left); newRoot->_right = Copy(root->_right); return newRoot; }
赋值运算符
//赋值运算符 BSTree<K>& operator=(BSTree<K> t) { swap(_root,t._root); return *this; }
析构函数
//析构函数 ~BSTree() { Destory(_root); } void Destory(Node*& root) { if (root == nullptr) return; Destory(root->_left); Destory(root->_right); delete root; root = nullptr; }
2.非递归
插入
a.空树
--插入的节点直接变为根节点
b.非空树
--根据二叉搜索树性质找到插入的位置
--与父节点链接(讨论parent的key与插入节点的key值来决定链接在parent的那边)
bool Insert(const K& key) { //a.空树(直接将该节点设为根节点) if (_root == nullptr) { _root = new Node(key); return true; } //b.非空树 //1.找到插入的位置 Node* cur = _root; Node* parent = nullptr; while (cur) { if (key < cur->_key) { parent = cur; cur = cur->_left; } else if (key > cur->_key) { parent = cur; cur = cur->_right; } else { return false; } } //2.链接节点 cur = new Node(key); if (parent->_key > key) { parent->_left = cur; } else { parent->_right = cur; } return true; }
查找
Node* Find(const K& key) { Node* cur = _root; while (cur) { if (key < cur->_key) { cur = cur->_left; } else if (key > cur->_key) { cur = cur->_right; } else { return cur; } } return nullpte; }
删除
找到删除节点的位置
1.删除的节点只有1个节点或者没有节点(直接删除,将cur的子节点给parent)
--没有节点也可以这么操作,相当于把空的子节点给父亲
--若有1个子节点,把该子节点给父亲 (讨论cur的位置,可能是p的左也可能是p的右)
2.删除的节点有2个节点: 找到它左子树最大的节点/ 右子树最小的节点代替它, 并删除
--找到删除的节点
--找到左子树最大节点/右子树最小节点来代替它(把cur存的key变为leftMax/rightMin的key)
--找该节点的过程, cur是在leftMax/rightMin的位置,删除cur节点
(当然要处理它的子节点,讨论一下leftMax是pleftMax左边还是右边)
3.处理空指针: 当删除的节点为cur, 并且cur只有左子树或者只有右子树,更新头节点
bool Erase(const K& key) { //找到要删除的节点 Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { //删除 //1.左为空 if (cur->_left == nullptr) { //处理删除到根节点只有左子树/右子树 if (cur == _root) { _root = cur->_right; } else { //将子节点给父节点 if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } } delete cur; } //2.右为空 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; } } } //3.左右不为空(找左子树最大节点/右子树最小节点) else { Node* pmaxLeft = cur; Node* maxLeft = cur->_left; while (maxLeft->_right) { pmaxLeft = maxLeft; maxLeft = maxLeft->_right; } cur->_key = maxLeft->_key; //把maxLeft的子节点交给其parent,然后删除maxLeft if (pmaxLeft->_left == maxLeft) { pmaxLeft->_left = maxLeft->_left; } else { pmaxLeft->_right = maxLeft->_left; } delete maxLeft; } return true; } } return false; }
3.递归
插入
//1.插入 bool _InsertR(Node*& root, const K& key) { if (root == nullptr) { root = new Node(key); return true; } if (root->_key > key) { return _InsertR(root->_left, key); } else if (root->_key < key) { return _InsertR(root->_right, key); } }
查找
根据二叉搜索树的性质查找:
//2.查找 bool _FindR(Node*& root, const K& key) { if (root == nullptr) { return false; } if (root->_key == key) return true; if (root->_key > key) return _FindR(root->_left, key); else return _FindR(root->_right, key); }
删除
//3.删除 bool _EraseR(Node*& root, const K& key) { if (root == nullptr) return false; //找到要删除的节点 if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { //删除 Node* del = root; //1.左为空 if (root->_left == nullptr) { root = root->_right; } //2.右为空 else if (root->_right == nullptr) { root = root->_left; } //3.左右不为空 else { Node* minRight = root->_right; while (minRight->_left) { minRight = minRight->_left; } swap(root->_key,minRight->_key ); //转换为在它的右子树去删除 return _EraseR(root->_right,key); } delete del; return true; } }
效果:
4.二叉搜索树的应用
- K模型:K模型即只有key作为关键码 ,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
- 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
- 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
- KV模型:每一个关键码key,都有与之对应的值Value,即**<Key, Value>的键值对** 。该种方
式在现实生活中非常常见:
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
- 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
示例
英汉词典
统计次数
中序遍历打印的时候,补上value就行:
达到上面的效果:把K改为KV型
节点:
BSTree
5.性能分析
最优: logN
最差: N