最早发明的自平衡二叉树:AVL

前言

更好的阅读体验
默认读者会基本的 BST 操作。

节点定义

平衡因子:BF(BalanceFactor),左子树高 − - 右子树高。
平衡树是让树的形态尽可能像完全二叉树,而不是链。

在 AVL 中,我们认为 ∣ BF ∣ ≤ 1 \left|\text{BF}\right|\le 1 BF1,也就是 BF 为 0 , 1 , − 1 0,1,-1 0,1,1 时的子树是平衡的,否则就是不平衡的。

struct node{
    int ch[2], size, val, h;//h 是树高  
}d[N];
int root, tot;
#define ls(x) d[x].ch[0]  
#define rs(x) d[x].ch[1]  
#define getBF(x) (d[ls(x)].h - d[rs(x)].h)//计算平衡因子  

旋转

rotate 操作是把某个给定节点上移一个位置,并保证二叉搜索树的性质不改变。
旋转操作分为左旋和右旋(图上节点是编号)。
图
我们来模拟一下右旋的操作(红色是要删除的,蓝色是更改后的)。
图
这样就完成了一次旋转。
而在实现中,我会把左右旋写在一起。
这里的 rotate(x,0) 表示将 x x x 的左儿子提到 x x x 的高度。
这里的 rotate(x,1) 表示将 x x x 的右儿子提到 x x x 的高度。

void rotate(int&now, int dir){
    int t = d[now].ch[dir];
    d[now].ch[dir] = d[t].ch[!dir];
    d[t].ch[!dir] = now;
    pushup(now), pushup(t), now = t;
}

平衡维护

如果它是平衡的,那么我们更新节点。
否则,我们考虑左子树过高的情况(右儿子同理),即 B F > 1 BF > 1 BF>1
我们又两种可能:
左儿子的左子树较高(图中的数字是树高):
tu
左儿子的右子树较高:
我们先转换成第一种,再平衡。
tu

void maintain(int&x){//引用  
    int BF = getBF(x);
    if(BF > 1){
        if(getBF(ls(x)) <= 0)rotate(ls(x), 1);//转换成第一种。  
        rotate(x, 0);
    }
    else if(BF < -1)(getBF(rs(x)) >= 0) && (rotate(rs(x), 0), 1), rotate(x, 1);	  
    else if(x)pushup(x);
}

插入

这里我们递归插入,然后在返回时维护平衡即可。

void insert(int&now, int val){
    if(!now)return void(now = newnode(val));//空节点  
    if(d[now].val < val)insert(rs(now), val);
    else insert(ls(now), val);
    maintain(now);//维护平衡  
}

删除

如果删除节点最多有一个儿子,那么我们用它的儿子顶替它。
否则和后继交换。

记得在返回时维护。

void del(int&now, int val){
    if(!now)return;
    if(d[now].val == val){
        int w = now;
        if(ls(now) && (w = rs(now))){//和后继交换,并删除后继  
            while(ls(w))w = ls(w);
            d[now].val = d[w].val, del(rs(now), d[w].val);
        }
        else now = ls(now) ? ls(now) : rs(now);//和儿子交换  
    }
    else if(d[now].val < val)del(rs(now), val);
    else del(ls(now), val);
    maintain(now);
}

复杂度证明

f n f_n fn 为高度为 n n n 的 AVL 树所包含的最少节点数,则有
f n = { 1 ( n = 1 ) 2 ( n = 2 ) f n − 1 + f n − 2 + 1 ( n > 2 ) f_n= \begin{cases} 1&(n=1)\\ 2&(n=2)\\ f_{n-1}+f_{n-2}+1& (n>2) \end{cases} fn= 12fn1+fn2+1(n=1)(n=2)(n>2)
根据常系数非齐次线性差分方程的解法, { f n + 1 } \{f_n+1\} {fn+1} 是一个斐波那契数列。这里 f n f_n fn 的通项为:
f n = 5 + 2 5 5 ( 1 + 5 2 ) n + 5 − 2 5 5 ( 1 − 5 2 ) n − 1 f_n=\frac{5+2\sqrt{5}}{5}\left(\frac{1+\sqrt{5}}{2}\right)^n+\frac{5-2\sqrt{5}}{5}\left(\frac{1-\sqrt{5}}{2}\right)^n-1 fn=55+25 (21+5 )n+5525 (215 )n1
斐波那契数列以指数的速度增长,对于树高 n n n 有:
n < log ⁡ ϕ ( f n + 1 ) < 3 2 log ⁡ 2 ( f n + 1 ) n n<\log_{\phi} (f_n+1)<\frac{3}{2}\log_2 (f_n+1) n n<logϕ(fn+1)<23log2(fn+1)n
因此 AVL 树的高度为 O ( log ⁡ f n ) O(\log f_n) O(logfn),这里的 f n f_n fn 为结点数。

代码

P3369

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值