搜索树
二叉搜索树
定义
- 结点由关键字值表征
- 假定所有结点关键字值不同
- 具有如下性质
- 可以是空二叉树
- 若左子树不空,则其上所有结点的关键字值均小于根结点
- 若右子树不空,则其上所有结点的关键字值均大于根结点
- 左右子树也分别为二叉搜索树
性质
中序遍历->关键字值递增
搜索操作
- 空——>失败
- 小于——>左子树
- 大于——>右子树
- 等于——>结束
插入操作
- 查找插入元素的位置
- 从根结点向下搜索
- 搜索失败的地方作为元素 x 的插入位置
- 有重复元素,返回 Duplicate,不进行重复插入!
- 搜索到达空子树,则不含重复元素
- 生成新结点
- 构造新结点 p 存放新元素 x
- 插入新结点(可能修改根结点)
- 新结点在间断处的下一层
- 左/右 取决于关键字大小的比较
- 原二叉搜索树为空树,则新结点为新树的根
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| TreeNode *insert(TreeNode *node, int key) { if (node == nullptr) { return new TreeNode(key); }
if (key < node->val) { node->left = insert(node->left, key); } else { node->right = insert(node->right, key); }
return node; }
|
删除操作
删除叶子结点
直接删
删除有一个孩子的结点
先把目标结点的前一个结点和后一个结点相连,再把目标结点释放
删除有两个孩子的结点
在目标结点的后代中选一个替代者覆盖待删除结点,替代者要满足:
- 能保持二叉搜索树的有序性(待删除结点左子树结点值<=代替者<=待删除结点右子树结点值)
- 容易删除(1个或没有孩子)
然后删除重复的替代者
代替者的选择方法
中序遍历,目标结点的前一项或后一项
1 2 3 4 5 6 7 8 9 10 11
| TreeNode *getMinValueNode(TreeNode *node) { TreeNode *current = node; while (current && current->left != nullptr) { current = current->left; } return current; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| TreeNode *deleteNode(TreeNode *node, int key) { if (node == nullptr) { return node; }
if (key < node->val) { node->left = deleteNode(node->left, key); } else if (key > node->val) { node->right = deleteNode(node->right, key); } else { if (node->left == nullptr) { return node->right; } else if (node->right == nullptr) { return node->left; }
TreeNode *minLargerNode = getMinValueNode(node->right); node->val = minLargerNode->val; node->right = deleteNode(node->right, minLargerNode->val); } return node; }
|
二叉平衡树(AVL树)
定义
二叉平衡树:
- 根的左右子树高度差的绝对值不超过 1
- 根的左右子树都是二叉平衡树
结点平衡因子:左子树高度-右子树高度
使用二叉平衡树可以将树的高度稳定在对数级别,这样可以较好的提高搜索等的性能
插入运算
基本原则
- 先按普通二叉搜索树方法插入,保持排序性
- 再判断平衡性是否被破坏,决定是否要调整
怎么检查平衡性
从最后一次更新的结点向上回溯,重新判断结点平衡因子,出现平衡因子绝对值大于 1 的情况,即为平衡性被破坏
关键代码:
1 2 3 4
| int getBalance(AVLTreeNode<T>* node) { return node ? getHeight(node->left) - getHeight(node->right) : 0; }
|
怎么调整恢复平衡性
- 找到需要调整的最小子树
- 调整
最小非平衡结点:离新插入结点最近的非平衡结点
单旋转方法
适用于:LL(RR)插入类型,即新结点插入左子树的左子树,或右子树的右子树——>即直线形式的插入
双旋转方法
适用于 LR(RL)插入类型——>即折线形式的插入
速通方法
转来转去看懵逼了,想想办法吧
不管是单旋转调整还是双旋转调整:
- 找到最小非平衡结点
- 将离根结点最近的3个结点直接构成一个子树进行重排,排完将根结点插回去
- 新子树除了新根结点的分支需要修改,其它直接保留就行
- 新根结点的原分支中,只有第一个子节点需要调整,如果它还有后续,不需要改动
B-树
m叉搜索树的定义及性质
方块:空树(失败结点),不是叶子结点——>搜索某个关键字不在树中时到达的子树
- 每个结点可以有超过两棵子树
- 每两棵子树间夹一个关键字
- 结点中关键字、同一层关键字都是递增的
- 如果有的话,关键字:左<中<右
一个 m 叉搜索树的结点中,最多存放 m-1 个元素和 m 个指向子树的指针,且每个结点中包含的元素个数总是比包含的指针数少 1
性质
- 高度为 h 的 m 叉搜索树中最多有 mh-1 个元素
$$
结点数_{max}=1+m+…+m^{h-1}=\frac{m^h-1}{m-1}
$$
且每个结点最多有 m-1 个元素
- 含有 N 个元素的 m 叉搜索树高度在 logm(N+1) 到 N 之间
内搜索和外搜索
内存的存取速度比磁盘快 一w~百w,设法减少磁盘存取操作的次数是外搜索算法设计应充分考虑的问题
内搜索:集合足够小,可以在内存中搜索(一般:二叉平衡树)
外搜索:内存不够大,只能放在外存中处理(B-树)
B-树的定义及性质
空树或满足下列特性:
- 根结点至少有 2 个孩子,根结点可以只含有 1 个元素
- 除根结点和失败结点外的所有结点至少有 m/2(向上取整)个孩子
- 所有失败结点在同一层
性质
- 元素总数 N = 失败结点数 s - 1
- 含有 N 个元素的 m 阶 B-树的高度 h 有:h<=1+logm/2(N+1)/2,其中 m/2 向上取整
B-树的插入
搜索
搜索新元素,存在就不插入;若不存在,将新元素插在失败结点的上一层叶子结点中
插入
将新元素和一个空指针插入
检查和分裂
以 m/2(向上取整)处的元素为分割点:该元素前,该元素,该元素后,共3部分
前一段和原父结点匹配,将该元素上移成为后一段的父结点
检查该元素所在的结点,如果还出现上溢出,重复操作;若根结点产生分裂,将导致 B-树 长高一层
B-树的删除
搜索
删除
叶结点:直接删除,检查下溢出
非叶结点:替代操作——右侧子树上的最小元素取代
然后把该最小元素删除,检查下溢出
下溢出
如下图,r 处出现下溢出,此时需要向同胞兄弟结点借用元素(如果左右两侧兄弟有富余的话)
借用结点对应的父结点元素
父结点缺失的位置向兄弟节点借
将元素和结点一起借走(如果有子树,一起借走)
若发生下溢出且无富余元素的兄弟,可将下溢出结点与其左/右兄弟合并(优先左),同时将双亲结点中夹在左右侧子树的元素下移到合并后的结点中形成新的结点
如果由于并操作,导致根结点中的一个元素被删除,且该结点只包含一个元素,则根结点变空结点,B-树矮一层
代码示例
代码只需要使用两个库
1 2
| #include <iostream> #include <vector>
|
通过模板和类对相关操作进行封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| template<typename T> class BTreeNode { public: bool isLeaf; std::vector<T> keys; std::vector<BTreeNode<T>*> children; int degree;
BTreeNode(int _degree, bool _isLeaf) : degree(_degree), isLeaf(_isLeaf) {}
void insertNonFull(T key); void splitChild(int i, BTreeNode<T>* y); void traverse(); void remove(T key); void fill(int idx); void merge(int idx); bool search(T key); void borrowFromPrev(int idx); void borrowFromNext(int idx); };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template<typename T> class BTree { private: BTreeNode<T>* root; int degree; public: BTree(int _degree) : root(nullptr), degree(_degree) {}
void traverse() { if (root) { root->traverse(); } }
void insert(T key); void remove(T key); bool search(T key) { return root ? root->search(key) : false; } };
|
在 BTreeNode 中添加多种方法作为成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| template<typename T> bool BTreeNode<T>::search(T key) { int idx = 0; while (idx < keys.size() && keys[idx] < key) { idx++; }
if (idx < keys.size() && keys[idx] == key) { return true; }
if (isLeaf) { return false; }
return children[idx]->search(key); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| template<typename T> void BTreeNode<T>::insertNonFull(T key) { int i = keys.size() - 1;
if (isLeaf) { keys.push_back(key); while (i >= 0 && keys[i] > key) { keys[i + 1] = keys[i]; i--; } keys[i + 1] = key; } else { while (i >= 0 && keys[i] > key) { i--; } if (children[i + 1]->keys.size() == 2 * degree - 1) { splitChild(i + 1, children[i + 1]); if (keys[i + 1] < key) { i++; } } children[i + 1]->insertNonFull(key); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template<typename T> void BTreeNode<T>::splitChild(int i, BTreeNode<T>* y) { BTreeNode<T>* z = new BTreeNode<T>(y->degree, y->isLeaf); z->keys.resize(degree - 1);
for (int j = 0; j < degree - 1; j++) { z->keys[j] = y->keys[j + degree]; }
if (!y->isLeaf) { z->children.resize(degree); for (int j = 0; j < degree; j++) { z->children[j] = y->children[j + degree]; } }
keys.insert(keys.begin() + i, y->keys[degree - 1]); children.insert(children.begin() + i + 1, z); y->keys.resize(degree - 1); }
|
下面是插入操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| template<typename T> void BTree<T>::insert(T key) { if (root == nullptr) { root = new BTreeNode<T>(degree, true); root->keys.push_back(key); } else { if (root->keys.size() == 2 * degree - 1) { BTreeNode<T>* s = new BTreeNode<T>(degree, false); s->children.push_back(root); s->splitChild(0, root); int i = 0; if (s->keys[0] < key) { i++; } s->children[i]->insertNonFull(key); root = s; } else { root->insertNonFull(key); } } }
|
然后是删除操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| template<typename T> void BTreeNode<T>::remove(T key) { int idx = 0; while (idx < keys.size() && keys[idx] < key) { idx++; }
if (idx < keys.size() && keys[idx] == key) { if (isLeaf) { keys.erase(keys.begin() + idx); } else { if (children[idx]->keys.size() >= degree) { T pred = children[idx]->keys.back(); remove(pred); keys[idx] = pred; } else if (children[idx + 1]->keys.size() >= degree) { T succ = children[idx + 1]->keys.front(); remove(succ); keys[idx] = succ; } else { merge(idx); children[idx]->remove(key); } } } else { if (isLeaf) { return; }
bool shouldMerge = (idx == keys.size()); if (children[idx]->keys.size() < degree) { fill(idx); } if (shouldMerge && idx > keys.size()) { children[idx - 1]->remove(key); } else { children[idx]->remove(key); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| template<typename T> void BTreeNode<T>::fill(int idx) { if (idx != 0 && children[idx - 1]->keys.size() >= degree) { borrowFromPrev(idx); } else if (idx != keys.size() && children[idx + 1]->keys.size() >= degree) { borrowFromNext(idx); } else { if (idx != keys.size()) { merge(idx); } else { merge(idx - 1); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| template<typename T> void BTreeNode<T>::borrowFromPrev(int idx) { BTreeNode<T>* child = children[idx]; BTreeNode<T>* sibling = children[idx - 1];
child->keys.insert(child->keys.begin(), keys[idx - 1]); keys[idx - 1] = sibling->keys.back(); sibling->keys.pop_back();
if (!child->isLeaf) { child->children.insert(child->children.begin(), sibling->children.back()); sibling->children.pop_back(); } }
template<typename T> void BTreeNode<T>::borrowFromNext(int idx) { BTreeNode<T>* child = children[idx]; BTreeNode<T>* sibling = children[idx + 1];
child->keys.push_back(keys[idx]); keys[idx] = sibling->keys.front(); sibling->keys.erase(sibling->keys.begin());
if (!child->isLeaf) { child->children.push_back(sibling->children.front()); sibling->children.erase(sibling->children.begin()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| template<typename T> void BTreeNode<T>::merge(int idx) { BTreeNode<T>* child = children[idx]; BTreeNode<T>* sibling = children[idx + 1];
child->keys.push_back(keys[idx]); child->keys.insert(child->keys.end(), sibling->keys.begin(), sibling->keys.end());
if (!child->isLeaf) { child->children.insert(child->children.end(), sibling->children.begin(), sibling->children.end()); }
keys.erase(keys.begin() + idx); children.erase(children.begin() + idx + 1); }
template<typename T> void BTree<T>::remove(T key) { if (!root) { std::cout << "树为空,无法删除\n"; return; }
if (!search(key)) { std::cout << "关键字 " << key << " 不存在,无法删除\n"; return; }
root->remove(key);
if (root->keys.empty()) { BTreeNode<T>* oldRoot = root; if (root->isLeaf) { root = nullptr; } else { root = root->children[0]; } delete oldRoot; } }
|
输出操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template<typename T> void BTreeNode<T>::traverse() { int i; for (i = 0; i < keys.size(); i++) { if (!isLeaf) { children[i]->traverse(); } std::cout << keys[i] << " "; } if (!isLeaf) { children[i]->traverse(); } }
|
通过主函数进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| int main() { BTree<int> bTree(3);
bTree.remove(4);
bTree.insert(10); bTree.insert(20); bTree.insert(5);
bTree.remove(4); bTree.insert(6); bTree.insert(12); bTree.insert(30); bTree.insert(7); bTree.insert(17);
std::cout << "B-树的遍历结果为: "; bTree.traverse(); std::cout << std::endl;
bTree.remove(10); std::cout << "B-树的删除10后的遍历结果为: "; bTree.traverse(); std::cout << std::endl;
return 0; }s
|
输出结果
1 2 3 4
| 树为空,无法删除 关键字 4 不存在,无法删除 B-树的遍历结果为: 5 6 7 10 12 17 20 30 B-树的删除10后的遍历结果为: 5 6 7 12 17 20 30
|