首页 > 其他分享 >最早发明的自平衡二叉树:AVL

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

时间:2024-12-29 19:42:36浏览次数:1  
标签:ch val rs int ls AVL 二叉树 now 发明

前言

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

节点定义

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

在 AVL 中,我们认为 \(\left|\text{BF}\right|\le 1\),也就是 BF 为 \(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\) 的高度。
这里的 rotate(x,1) 表示将 \(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;
}

平衡维护

如果它是平衡的,那么我们更新节点。
否则,我们考虑左子树过高的情况(右儿子同理),即 \(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\) 为高度为 \(n\) 的 AVL 树所包含的最少节点数,则有

\[f_n= \begin{cases} 1&(n=1)\\ 2&(n=2)\\ f_{n-1}+f_{n-2}+1& (n>2) \end{cases} \]

根据常系数非齐次线性差分方程的解法,\(\{f_n+1\}\) 是一个斐波那契数列。这里 \(f_n\) 的通项为:

\[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 \]

斐波那契数列以指数的速度增长,对于树高 \(n\) 有:

\[n<\log_{\phi} (f_n+1)<\frac{3}{2}\log_2 (f_n+1) n\]

因此 AVL 树的高度为 \(O(\log f_n)\),这里的 \(f_n\) 为结点数。

代码

P3369

标签:ch,val,rs,int,ls,AVL,二叉树,now,发明
From: https://www.cnblogs.com/fush/p/18639442

相关文章

  • 【数据结构】二叉树
    二叉树1.树型结构(了解)1.1概念1.2概念(重要)1.3树的表示形式(了解)1.4树的应用2.二叉树(重点)2.1概念2.2两种特殊的二叉树2.3二叉树的性质2.4二叉树的存储2.5二叉树的基本操作2.5.1前置说明2.5.2二叉树的遍历2.5.3二叉树的基本操作2.6二叉树相关oj题【本节......
  • 二叉树中的最大路径和(递归)
    二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。路径和 是路径中各节点值的总和。给你一个二叉树的根节点 root ,返回其 最大路径和 。 示例......
  • 头歌实训数据结构与算法-二叉树及其应用(第7关:由前序和中序遍历序列构造二叉树)
    任务描述本关任务要求采用前序遍历序列和中序遍历序列构造二叉树。相关知识给定一棵二叉树的前序遍历序列和中序遍历序列可以构造出这棵二叉树。例如前序序列是ABDECFG,中序序列是DBEAFCG,那么这颗二叉树的结构如图1所示。树结点结构定义为:structBTNode{    c......
  • 深入探索哈夫曼编码与二叉树的遍历
    编码表(将字符转换成二进制01数字)定长的编码方式不定长的编码方式压缩率很高,但是会产生数据歧义哈夫曼编码出现的次数越多,权重分配的值越小。哈夫曼树,左1右0,转换成编码哈夫曼编码(压缩率高,数据不会产生歧义)哈夫曼编码----->二叉......
  • 106.从中序与后序遍历构造二叉树
    106.从中序与后序遍历构造二叉树中序:左根右后序:左右根思路:中序遍历需要定位根节点的坐标前序和后序需要定位子树根节点的坐标1.构造map方便通过root->value拿到该值在中序中的下标(in_root)2.从后序的最后一个值拿到当前root的value3.通过map拿到in_root4.构造此时结......
  • 【257. 二叉树的所有路径 简单】
    题目:给你一个二叉树的根节点root,按任意顺序,返回所有从根节点到叶子节点的路径。叶子节点是指没有子节点的节点。示例1:输入:root=[1,2,3,null,5]输出:[“1->2->5”,“1->3”]示例2:输入:root=[1]输出:[“1”]提示:树中节点的数目在范围[1,100]内-100<=N......
  • 代码随想录——贪心23监控二叉树
    思路这道题目首先要想,如何放置,才能让摄像头最小的呢?从题目中示例,其实可以得到启发,我们发现题目示例中的摄像头都没有放在叶子节点上!这是很重要的一个线索,摄像头可以覆盖上中下三层,如果把摄像头放在叶子节点上,就浪费的一层的覆盖。所以把摄像头放在叶子节点的父节点位置,才......
  • 二叉树和哈希表
    二叉树二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。二叉树的递归定义为:二叉树或者是一棵空树,或者是一棵由一个根节点和两棵互不相交的分别称作根的左子树和右子树所组成的非空树,左子树和右子树又同样都是二叉树。下面相关代码实现都利用了......
  • 105. 从前序与中序遍历序列构造二叉树
    题目链接解题思路:首先我们得知道人工怎么建这棵树。先序遍历[0,R1]第一个节点,就是根。然后我们在中序遍历[0,R2]找到根的位置,假如是x,那么,中序遍历中[0,x-1]就是左子树,中序遍历中[x+1,R2]就是右子树。那么先序遍历呢?左子树节点个数是x个,先序遍历是要先遍历完左子树,才能到......
  • 103. 二叉树的锯齿形层序遍历
    题目链接解题思路:和层序遍历有明显的不同。通过观察,可以得到,当前层,和下一层的顺序是「相反」的,遇到这种相反的问题,考虑用栈。本题就是用两个栈,一个栈在放入时,先放左儿子,再放右儿子,另一个栈在放入时,先放右儿子,再放左儿子。然后两个栈交替使用即可。为什么能得到这个思路?观察......