目录
一、二叉搜索树概念
二、二叉搜索树操作
2.1 二叉搜索树的查找
2.2 二叉搜索树的插入
2.3 二叉搜索树的删除
2.4 二叉搜索树的中序遍历
三、二叉搜索树的实现
3.1 单值结构
3.2 KV双值结构
四、二叉搜索树的应用
五、二叉搜索树的性能分析
编程题训练
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13}; // 前序遍历
2.1 二叉搜索树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
b、最多查找高度次,走到到空,还没找到,这个值不存在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 _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.2 二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点bool Insert(const K& key) { if (_root == nullptr) { _root = new Node(key); return true; } Node* parent = nullptr; Node* cur = _root; while (cur) { parent = cur; if (cur->_key < key) { cur = cur->_right; } else if (cur->_key > key) { cur = cur->_left; } else { return false; } } cur = new Node(key); if (parent->_key < key) { parent->_right = cur; } else { parent->_left = cur; } 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; }
2.3 二叉搜索树的删除首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有四种情况,实际上a可以与情况b或者c合并起来,因此只需考虑如下三种情况:
情况1:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况2:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况3:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除
代码实现如下: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 { // 准备删除 20:15继续 if (cur->_left == nullptr) {//左为空 if (cur == _root) { _root = cur->_right; } else { if (cur == parent->_left) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } } } else if (cur->_right == nullptr) {//右为空 if (cur == _root) { _root = cur->_left; } else { if (cur == parent->_left) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } } } else {//左右都不为空 // 右树的最小节点(最左节点) Node* parent = cur; Node* subLeft = cur->_right; while (subLeft->_left) { parent = subLeft; subLeft = subLeft->_left; } swap(cur->_key, subLeft->_key); if (subLeft == parent->_left) parent->_left = subLeft->_right; else parent->_right = subLeft->_right; } return true; } } 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 { // 删除 if (root->_left == nullptr) { Node* del = root; root = root->_right; delete del; return true; } else if (root->_right == nullptr) { Node* del = root; root = root->_left; delete del; return true; } else { Node* subLeft = root->_right; while (subLeft->_left) { subLeft = subLeft->_left; } swap(root->_key, subLeft->_key); // 转换成在子树去递归删除 return _EraseR(root->_right, key); } } }
2.4 二叉搜索树的中序遍历
// 递归实现 void _InOrder(Node* root) { if (root == nullptr) return; _InOrder(root->_left); cout << root->_key << " "; _InOrder(root->_right); }
3.1 单值结构
下面代码中部分仅给出函数声明,可以在2.1~2.3找到函数实现 template
struct BSTreeNode { BSTreeNode * _left; BSTreeNode * _right; K _key; BSTreeNode(const K& key) :_left(nullptr) ,_right(nullptr) ,_key(key) {} }; template class BSTree { typedef BSTreeNode Node; public: bool Insert(const K& key); bool Find(const K& key); bool Erase(const K& key); 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 _EraseR(Node*& root, const K& key); bool _InsertR(Node*& root, const K& key); bool _FindR(Node* root, const K& key); void _InOrder(Node* root) { if (root == nullptr) return; _InOrder(root->_left); cout << root->_key << " "; _InOrder(root->_right); } private: Node* _root = nullptr; };
3.2 KV双值结构
下面代码中部分仅给出函数声明,可以在2.1~2.3找到函数实现 template
struct BSTNode { BSTNode(const K& key = K(), const V& value = V()) : _left(nullptr), _right(nullptr), _key(key), _value(value) {} BSTNode * _left; BSTNode * _right; K _key; V _value; }; template class BSTree { typedef BSTNode Node; public: // KV-插入 bool Insert(const K& key, const V& value) { if (_root == nullptr) { _root = new Node({ key,value }); return true; } Node* cur = _root; Node* prev = _root; // 一般不考虑存在重复的key while (cur) { if (key > cur->_key) { prev = cur; cur = cur->_right; } else if (key < cur->_key) { prev = cur; cur = cur->_left; } else return false; //数据冗余,不插入 } Node* newnode = new Node({ key,value }); if (prev->_key < key) { prev->_right = newnode; } else { prev->_left = newnode; } return true; } // 查找-Node Node* Find(const K& key) { //根据BST树的特性来find; if (_root == nullptr) return nullptr; Node* cur = _root; //原则上来说 是没有重复key存在的 while (cur) { if (key > cur->_key) { cur = cur->_right; } else if (key < cur->_key) { cur = cur->_left; } else return cur; } return nullptr; } // 中序遍历 void InOrder() { _InOrder(_root); cout << endl; } // 删除 bool EraseR(const K& key) { return _EraseR(_root, key); } private: bool _EraseR(Node*& root, const K& key); void _InOrder(Node* root) { if (root == nullptr) return; _InOrder(root->_left); cout << root->_key << " : " << root->_value << " "; _InOrder(root->_right); } private: Node* _root = nullptr; };
1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
- 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
- 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即
的键值对 。该种方式在现实生活中非常常见:
比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对;
再比如:统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对。 void test_KV_BSTree() { 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(); }
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点 在二叉搜索树的深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们下一篇博客中的AVL树和红黑树就可以上场了
leetcode/newcode题号快速查询
606. 根据二叉树创建字符串
102. 二叉树的层序遍历
107. 二叉树的层序遍历 II
236. 二叉树的最近公共祖先
二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
105. 从前序与中序遍历序列构造二叉树
106. 从中序与后序遍历序列构造二叉树
144. 二叉树的前序遍历
94. 二叉树的中序遍历
145. 二叉树的后序遍历