平衡树基础2——AVL树

一、结构定义:

1.性质:在二叉排序树的基础上,左右子树的树高之差,不能超过 1 。

2.重点:在这之后的平衡树,主要学习的就是平衡条件

二、结构操作

两个平衡树调整的重要操作:左旋、右旋

1.左旋:对于下面树形结构,A,B代表的是K3的左右子树。

左旋就相当于逆时针旋转,把K3当做根节点。

然后我们要进行父子关系的修改,我们知道,在这个平衡树中,A这个子树中所有节点都是在K1右半子树中的,所以我们可以把A直接改为K1的右子树,并将K1及其子树作为K3的左子树,他们都是比K3小的节点,这样也不会破坏平衡树结构。

2.右旋

过程类似,还用上面的图来解释。

顺时针旋转,会将K2作为根节点,那么K2原来的右子树要调整到新的K1为根节点的右半子树中,而K2原来右子树中的节点,一定是比K1小的,所以调整为K1的左子树即可。

三、AVL树的失衡类型

1.失衡条件:左右子树高度差大于等于 2

我们以这张图为例,去区分四个类型。

注意:

1.这是整个平衡树中的一个子树,K1只是子树的根节点。

2.我们从下往上看,ABCD四个子树都是平衡的,从K2、K3看下去也是平衡的,只有K1看下去不平衡,所以K1是第一个不平衡的节点。

LL:K1左子树更高,出现在A

操作:一次右旋即可。

理解:我们先理清楚,从K2看下去,是平衡的,所以A与B之间高度差在1之内而且A更高。我们不妨设A的树高为a,B树高为b,右旋之后,K2为根节点,那么左半边高度是a,而右半边,是b+1。我们知道b有两种情况,a = b 或 a = b + 1,所以b+1的取值和a之差也不会超过1。

对于C D,他们的树高最开始一定比A小2以上,+1之后,也不可能和A之差达到2,所以不用考虑。

LR:K1左子树更高,出现在B

操作:小左旋+大右旋,先对K2这半个子树进行一次左旋,然后对K1这整个子树进行右旋

理解:从K2往下看,左侧子树本来是平衡的,但是右边高左边低,我们左旋之后,不就左边高右边低了。这样整体上又变成LL型了。

注意:牢记K1是第一个不平衡节点,从K2往下看是平衡的,K2两边子树高度差在1之内,所以左旋一定会使K2的两个子树左子树更高。

RL:K1右子树更高,出现在C

类比LR型。

操作:先对右半子树小右旋,再整体左旋

RR:K1右子树更高,出现在D

类比LL型

操作:整体进行一次左旋

四、代码演示

1.结构定义与初始化操作

//结构定义
typedef struct Node {
    int key, h;//多了一个h存储树高
    struct Node* lchild, * rchild;
}Node;

//几个方便的宏
#define H(n) (n->h)
#define L(n) (n->lchild)
#define R(n) (n->rchild)
#define K(n) (n->key)

//创建一个虚拟空节点
Node __NIL;
#define NIL (&__NIL)
//__attribute__((constructor)) 
//这个作用是规定下面函数的性质,可以让他先于主函数执行
//但是我这个编译器不支持,还是要放到主函数中调用。
void init_NIL() {
    NIL->key = -1;
    NIL->h = 0;
    NIL->lchild = NIL->rchild = NULL;
    return;
}

2.节点获取与销毁

//获取节点
Node* getNewNode(int key) {
    Node* p = (Node*)malloc(sizeof(Node));
    p->key = key;
    p->lchild = p->rchild = NIL;//编程技巧,虚拟空节点
    p->h = 1;
    return p;
}

//销毁
void clear(Node* root) {
    if (root == NIL) return;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return;
}

3.左旋右旋以及失衡状态调整(重点)

这里的type_str这些与输出相关的语句,都是输出提示信息的,方便测试。

思路:通过获取树高,判断失衡类型,然后调用上面的左旋右旋。

//更新高度
void update_height(Node* root) {
    H(root) = (H(L(root)) > H(R(root)) ? H(L(root)): H(R(root))) + 1;
    return;
}

Node* left_rodate(Node* root) {
    cout << "left rodate : " << K(root) << endl;
    Node* new_node = root->rchild;
    root->rchild = new_node->lchild;
    new_node->lchild = root;
    update_height(root);//先更新子节点的高度
    update_height(new_node);
    return new_node;
}

Node* right_rodate(Node* root) {
    cout << "right rodate : " << K(root) << endl;
    Node* new_node = root->lchild;
    root->lchild = new_node->rchild;
    new_node->rchild = root;
    update_height(root);//先更新子节点的高度
    update_height(new_node);
    return new_node;
}

const char* type_str[5] = {
    "",
    "maintain type : LL",
    "maintain type : LR",
    "maintain type : RR",
    "maintain type : RL",
};

Node* maintain(Node* root) {
    if (abs(H(L(root)) - H(R(root))) <= 1) return root;//不需调整
    int type = 0;//用来输出时,查看失衡类型
    //判断失衡类型
    if (H(L(root)) > H(R(root))) {
        if (H(R(L(root))) > H(L(L(root)))) {
            //LR型
            root->lchild = left_rodate(root->lchild);//小左旋
            type += 1;
        }
        //LL和LR都需要这步,所以合并一起
        root = right_rodate(root);//大右旋
        type += 1;
    }
    else {
        type = 2;
        if (H(L(R(root))) > H(R(R(root)))) {
            //RL型
            root->rchild = right_rodate(root->rchild);//小右旋
            type += 1;
        }
        root = left_rodate(root);//大左旋
        type += 1;
    }
    cout << type_str[type] << endl;
    return root;
}

4.插入

注意更新树高和返回时要调整平衡条件就行。

//插入
//返回值是新的根节点。因为根节点会在平衡调整中变化
Node* insert(Node* root, int key) {
    if (root == NIL) return getNewNode(key);
    if (root->key == key) return root;//不重复插入
    if (key < root->key) root->lchild = insert(root->lchild, key);
    else root->rchild = insert(root->rchild, key);
    update_height(root);//插入后要更新树高
    return maintain(root);//调整返回
}

5.删除

这里和上一篇二叉搜素树的删除操作类似,只是出度0和1通过虚拟空节点这个技巧合并成一种情况了。


Node* predecessor(Node* root) {
    Node* temp = L(root);
    while (R(temp) != NIL) temp = R(temp);
    return temp;
}

//删除
Node* eraser(Node* root, int key) {
    if (root == NIL) return root;
    if (key < K(root)) L(root) = eraser(L(root), key);
    else if (key > K(root)) R(root) = eraser(R(root), key);
    else {
        //之前二叉搜索树采用的是分三种情况,这里用一种优化的新方法
        if (L(root) == NIL || R(root) == NIL) {
            //出度为1或0
            Node* temp = L(root) != NIL ? L(root) : R(root);
            free(root);
            return temp;
            //因为我们采用了NIL这个虚拟空节点,所以这里能兼容出度为0的情况
        }
        else {
            //出度为2
            Node* temp = predecessor(root);
            K(root) = K(temp);
            L(root) = eraser(L(root), K(temp));
        }
    }
    update_height(root);
    return maintain(root);
}

6.查找

//查找
Node* find(Node* root, int key) {
    if (root == NIL) return NIL;
    if (K(root) == key) return root;
    if (key < K(root)) return find(L(root), key);
    return find(R(root), key);
}

7.测试

void output(Node* root) {
    if (root == NIL) return;
    cout << "[key:" << K(root) << " h:" << H(root) << "]";
    cout << "left:" << K(L(root)) << " right" << K(R(root)) << endl;
    output(root->lchild);
    output(root->rchild);
    return;
}


int main()
{
    init_NIL();//虚拟空节点的初始化
    srand(time(0));

    //测试
    int x;
    Node* root = NIL;
    while (cin >> x && x != -1) {
        //插入
        cout << "插入" << x << "到AVL树中" << endl;
        root = insert(root, x);
        output(root);
    }
    while (cin >> x && x != -1) {
        //删除
        cout << "删除" << x << "从AVL树中" << endl;
        root = eraser(root, x);
        output(root);
    }
    while (cin >> x && x != -1) {
        //查找
        bool judge = false;
        if (find(root, x) != NIL) judge = true;
        if (judge) cout << "查找成功" << endl;
        else cout << "查找失败" << endl;
    }
    return 0;
}

五、总结

1.要学习左旋、右旋这两个二叉树平衡调整的重要操作。

2.可以借鉴虚拟空节点这个编程技巧

3.测试时的输出更能体现编程能力,学习type_str这个技巧

4.要记住AVL树的平衡和失衡条件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值