BST 二叉查找树
\(BST\) 二叉查找树
概念
二叉查找树(Binary Search Tree,\(BST\))是一种具有特殊性质的二叉树:
-
树中每个节点均有权值
-
对于一个节点 \(u\),当 \(u\) 存在左子树时,其左子树内所有节点的值均小于(等于) \(u\) 的值
-
对于一个节点 \(u\),当 \(u\) 存在右子树时,其左子树内所有节点的值均大于 \(u\) 的值
-
树中每一个子树均为 \(BST\),如下图所示:
graph TB 1((1)) 2((2)) 3((3)) 4((4)) 5((5)) 6((6)) 7((7)) 8((8)) 9((9)) 2-->1 2-->4 4-->3 4-->A((空)) 5-->2 5-->8 6-->B((空)) 6-->7 8-->6 8-->9
基本操作
1. \(BST\) 的遍历
已知一颗 \(BST\),若要从小到大输出它的节点权值,只需对它中序遍历(左中右符合定义中从小到大的顺序),时间复杂度为 \(O(n)\)
void print(int u) {
if (!u) return ; // 空节点返回
print(leftson[u]); // 遍历左子树
cout << val[p] << endl; // 输出u的值
print(rightson[u]); // 遍历右子树
}
2. \(BST\) 查询元素位置
\(BST\) 查询元素位置的方法与二分查找相似:
- 从根开始查找
- 如果当前值与查找值相同,那么查找成功
- 如果查找值小于当前值,那么在左子树继续查找
- 同理,如果查找值大于当前值,那么在右子树继续查找
- 如果当前节点为空,则查找失败
int find(int u,int x) {
if (!u) return 0; // 查找失败
if (x == val[u]) return u; // 查找成功,返回节点编号
if (x < val[p]) return find(leftson[u],x); // 查找左子树
else return find(rightson[u],x); // 查找右子树
}
\(BST\) 查询最值
利用 \(BST\) 中左子树在子树中永远最小的定义,我们可以得到与上类似的方法:
- 从根开始查找
- 始终查找左子树
- 当左子树为空时,该节点的值为 \(BST\) 中的最小值
int findMin(int u) {
if (!u) return 0; // 空节点
if (!leftson[u]) return u; // 不存在左子树
findMin(leftson[u]); // 查询左子树
}
\(BST\) 节点的插入
\(BST\) 插入节点的方法与二分查找相似,不断树中缩小范围,最后确定合适的位置插入:
- 从根开始插入
- 如果插入值小于当前值,那么在左子树继续查找
- 同理,如果插入值大于当前值,那么在右子树继续查找
- 如果当前节点为空,则在这个位置建立新节点,节点值为插入值
注意:在步骤 \(2\),\(3\) 中,对于相同权值的点,要么将它插在其中一个子树(本文不考虑重复),要么在节点中储存一个计数器记录有几个相同的节点
void insert(int &u,int x) {
if (!u) u = newNode(x); // 新建节点
else if (x < val[u]) insert(leftson[u],x); // 查询左子树
else if (x > val[u]) insert(rightson[u],x); // 查询右子树
}
\(BST\) 节点的删除
\(BST\) 删除节点的方法较为复杂,其分为三种情况,令删除节点编号为 \(u\):
-
\(u\) 为叶子节点,直接删除
graph TB 1((1)) 2((2)) 3((3)) 2-->1 2-->3 A((空)) B((2)) C((3)) B-->A B-->C -
\(u\) 只有一个子节点,删除它后,我们用它的唯一的子节点替补它的位置
graph TB 1((1)) 2((2)) 3((3)) 4((4)) 3-->2 2-->1 3-->4 A((1)) C((3)) D((4)) C-->A C-->D -
\(u\) 有两个子节点,我们的方法是用 \(u\) 右子树中的最小值来代替它,并将该最小值节点删除(根据 \(BST\) 的定义,这个点一定为前两种情况)
如下图(删除 \(4\) 号):
graph TB 1((1)) 2((2)) 3((3)) 4(((4))) 5((5)) 6((6)) 7((7)) 2-->1 2-->4 4-->3 4-->7 7-->5 7-->A((空)) 5-->B((空)) 5-->6 a1((1)) a2((2)) a3((3)) a4(((5))) a5[5] a6((6)) a7((7)) a2-->a1 a2-->a4 a4-->a3 a4-->a7 a7-->a5 a7-->aA((空)) a5-->aB((空)) a5-->a6 b1((1)) b2((2)) b3((3)) b5(((5))) b6((6)) b7((7)) b2-->b1 b2-->b5 b5-->b3 b5-->b7 b7-->b6 b7-->bA((空))
观察 \(1\),\(2\) 可以发现,这两种情况是相同的(叶子节点可以被当作右子树为空的节点)
int deleteMin(int &u) { // 删除该子树最小的节点
if (!leftson[u]) {
int t = val[u];
u = rightson[u]; // 将右子树替补上来
return t;
}
return deleteMin(leftson[u]);
}
void deleteNum(int &u,int x) {
if (val[u] == x) {
if (leftson[u] && rightson[u]) val[u] = deleteMin(rightson[u]); // 情况3
else u = (leftson[u] ? leftson[u] : rightson[u]);
return ;
}
if (val[x] > p) deleteNum(leftson[u],x);
else deleteNum(rightson[u],x);
}
时间复杂度
我们发现,当出现特殊的数据时,时间复杂度可能退化为 \(O(n)\):
这时,我们需要对这个数据结构进行优化(\(Treap\),\(Splay\)树 ……)
Treap

浙公网安备 33010602011771号