二叉搜索树(BST)

二叉搜索树的定义及性质

二叉搜索树(\(\texttt{Binaty Search Tree}\))简称 \(\texttt{BST}\),它拥有“\(\texttt{BST}\) 性质”,这也是平衡树的基础。

给定一棵二叉树,树上的每一个节点带有一个权值。所谓“\(\texttt{BST}\) 性质”是指,对于树中的任意一个节点:

  1. 该节点的权值不小于它的左子树中任意节点的权值;
  2. 该结点的权值不大于它的右子树中任意节点的权值。

满足上述性质的二叉树就是一棵 “二叉查找树”(\(\texttt{BST}\))

比如:

同时珂以发现,它的中序遍历为 \(\{1,2,3,4,5,6,7,8,9,10\}\),就是将这所有权值从小到大排序后的结果。


二叉搜索树的一些操作


1. 建立

为了避免越界,减少边界情况的特殊判断,一般在 \(\texttt{BST}\) 中额外插入一个权值为 \(+\infty\) 和一个权值为 \(-\infty\) 的节点。
仅由这两个节点构成的 \(\texttt{BST}\) 就是一棵初始的空的 \(\texttt{BST}\)

为了方便起见,在接下来的操作中,假设 \(\texttt{BST}\) 不会含有权值相同的节点(即使有也可以用一个 \(cnt\) 数组记录出现的次数)。

struct BST {
	int ls, rs;
	int val;
	int cnt;
	int siz;
}a[N]; 
int tot, root, inf = 0x7fffffff;

int New(int val) {
	a[++tot].val = val;
	return tot;
}

void build() {
	New(-inf);
	New(inf);
	root = 1, a[1].rs = 2;
} 

2. 检索

这个比较简单,就不放代码了,只给出思路。

假如说要找一个权值为 \(val\) 的节点。

设当前搜索到的节点为 \(p\),则一开始 \(p = root = 1\)

  1. \(a[p].val == val\),则已经找到;

  2. \(a[p].val < val\)

    (1)若 \(p\) 无左子节点,则说明不存在 \(val\)

    (2)若 \(p\) 有左子节点,则继续检索 \(p\) 的左子树。

  3. \(a[p].val > val\)

    (1)若 \(p\) 无右子节点,则说明不存在 \(val\)

    (2)若 \(p\) 有右子节点,则继续检索 \(p\) 的右子树。

3. 插入

\(\texttt{BST}\) 中插入一个新的值 \(val\)

\(\texttt{BST}\) 的检索过程相似。

在发现要走向的 \(p\) 无子节点,说明 \(val\) 不存在时,直接建立权值为 \(val\) 的新节点作为 \(p\) 的新节点。

void insert(int &p, int val) { //注意p是引用,其父节点的 l 或 r 值会被同时更新
	if(p == 0) { 
		p = New(val);
		return ;
	}
	if(val == a[p].val) {
		a[p].cnt++;
		return ;
	}
	if(val < a[p].val) insert(a[p].l, val);
	else insert(a[p].r, val);
}

4. 求前驱/后缀

\(val\) 的前驱是指在 \(\texttt{BST}\) 中权值小于 \(val\) 中的权值中最大的那个。同理,\(val\) 的后继是指在 \(\texttt{BST}\) 中权值大于 \(val\) 中的权值中最小的那个。

以求前驱为例,先初始化 \(ans\) 为权值为 \(+\infty\) 的那个节点的编号,然后在 \(\texttt{BST}\) 中检索 \(val\)。在检索过程中,每经过一个节点 \(p\),都尝试能不能让它更新\(ans\) 作为要求的前驱的候选答案。

检索完成后,有三种可能的结果:

  1. 没有找到 \(val\),则 \(val\) 的前驱就是 \(ans\)

  2. 找到了权值为 \(val\) 的节点 \(p\),但是 \(p\) 没有左子树,则 \(val\) 无前驱,\(ans\) 还是答案。

  3. 找到了权值为 \(val\) 的节点 \(p\),且 \(p\) 有左子树,则说明答案是 \(p\) 左子树中的最大值,从 \(p\) 的左子节点出发,然后一直向右走,就找到了答案。

int get_pre(int val) { //求前驱
	int ans = 1; //a[1].val = -0x7fffffff
	int p = root;
	while(p) {
		if(val == a[p].val) { //检索成功
			if(a[p].ls) {
				p = a[p].ls; //从左子节点出发
				while(a[p].rs) p = a[p].rs; //一直向右走
				ans = p;
			}
			break;
		}
		//每经过一个点,都尝试更新前驱
		if(val > a[p].val && a[p].val > a[ans].val) ans = p;
		p = val > a[p].val ? a[p].rs : a[p].ls; //检索
	}
	return ans;
}

int get_next(int val) { //求后继
	int ans = 2; //a[2].val = 0x7fffffff
	int p = root;
	while(p) {
		if(val == a[p].val) {
			if(a[p].rs) {
				p = a[p].rs;
				while(a[p].ls) p = a[p].ls;
				ans = p;
			}
			break;
		}
		if(val < a[p].val && a[p].val < a[ans].val) ans = p;
		p = val > a[p].val ? a[p].rs : a[p].ls;
	}
	return ans;
}

//递归写法
int get_pre(int p, int val) {
    if(!p) return -inf;
    if(val <= tr[p].val) return get_pre(tr[p].ls, val);
    return max(tr[p].val, get_pre(tr[p].rs, val));
}

int get_next(int p, int val) {
    if(!p) return inf;
    if(val >= tr[p].val) return get_next(tr[p].rs, val);
    return min(tr[p].val, get_next(tr[p].ls, val));
}

5. 删除

\(\texttt{BST}\) 中删除权值为 \(val\) 的节点。

首先通过检索找到权值为 \(val\) 的节点 \(p\)

\(p\) 是叶节点,则直接删除就行了。

\(p\) 有一个儿子,则删除 \(p\),再让 \(p\) 的儿子代替 \(p\) 的位置。

\(p\) 有两个儿子,就有点麻烦了,要先求出 \(val\) 的后继节点 \(next\),因为 \(next\) 无左子树,所以可以直接删除 \(next\),并让 \(next\) 的右子树代替 \(next\) 的位置,再让 \(next\) 代替 \(p\) 的位置,删除 \(p\) 即可。

void remove(int &p, int val) {
	if(p == 0) return ;
	if(val == a[p].val) {
		if(!a[p].ls) p = a[p].rs; //没有左子树,则右子树代替 p 的位置,注意 p 是引用 
		else if(!a[p].rs) p = a[p].ls; //没有右子树,则左子树代替 p 的位置,注意 p 是引用 
		else { //有两个儿子 
			//求后继节点
			int next = a[p].rs;
			while(a[next].ls > 0)  next = a[next].ls;
			//next 一定无左子树
			remove(a[p].rs, a[next].val);
			//让节点 next 代替节点 p 的位置
			a[next].ls = a[p].ls, a[next].rs = a[p].rs;
			p = next; 
		}
		return ;
	}
	if(val < a[p].val) remove(a[p].ls, val);
	else remove(a[p].rs, val);
}

二叉搜索树的时间复杂度

在随机数据中,\(\texttt{BST}\) 一次操作的期望时间复杂度为 \(O(\log n)\),但是,要是按照顺序向 \(\texttt{BST}\) 中插入一个有序序列,\(\texttt{BST}\) 就会退化成一条链,这时候平均每次操作的时间复杂度就会变为 \(O(n)\)。这时候我们称这种左右子树大小相差很大的 \(\texttt{BXT}\) 是“不平衡”的。

而为了维持 \(\texttt{BST}\) 的平衡,就产生了许多数据结构,我们称之为平衡树

\(\texttt{To be continue}\dots \dots\)

posted @ 2024-02-01 10:20  Brilliant11001  阅读(4)  评论(0编辑  收藏  举报