AVL树的原理及代码实现

前言

如果你还没有学习过二叉查找树,那么请你先去看看二叉查找树,因为AVL树便是从二叉查找树进化而来的,不看的话你无法理解AVL树。

链接:二叉查找树的原理及实现

如果你已经学习了二叉查找树,你会觉得二叉查找树性能在各方面都很好,就只有一丢丢的小毛病,那就是当数据非常坑时,二叉查找树退化成了一条单链表,这样各种操作的时间复杂度都变为O(n)了,怎么办呢,今天所要学习的AVL树便以其惊艳四座的高端技巧解决了这一问题,使其在任何情况下的各种操作复杂度都为O(logn)。

AVL树

AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的。
它是最先发明的自平衡二叉查找树,也被称为高度平衡树。

在介绍AVL树时,首先要介绍一个概念——平衡度。

平衡度 = 左子树的高度 – 右子树的高度

当树上所有节点平衡度为-1,0,1时,我们认为这棵树是平衡的,当有节点的平衡度绝对值 > 1时,我们认为这棵树是不平衡的,我们就要对这个节点进行调整。

《AVL树的原理及代码实现》

基本实现

存储实现:

AVL树与二叉查找树一样使用二叉链表实现,这样能够很好的理解,每个节点有一个元素存储值,两个指针分别指向它的左子树和右子树,还有一个元素来存储该节点的高度。

template <class elemType>
class AvlTree{
private:
    
    struct node{
        elemType data;
        node *left;
        node *right;
        int height;
        
        node(const elemType &x, node *ln, node *rn, int h = 1):data(x), left(ln), right(rn), height(h){}
    };
    
    node *root;
}

find操作:

AVL树的操作与二叉查找树的find操作原理一模一样,这里就不详细讲了,想看的可以去文首的二叉查找树的链接里看。

此处代码使用了非递归实现:

    elemType *find(const elemType &x){
        node *t = root;
        while(t != NULL && t -> data != x){
            if(t -> data > x){
                t = t -> right;
            }
            else{
                t = t -> left;
            }
        }
        
        if(t == NULL){
            return NULL;
        }
        else{
            return &(t -> data);
        }
    }

midOrder操作:

也与二叉查找树完全相同,中序遍历输出整个树的值,结果必然是一个从小到大序列。

    void midOrder(){
        midOrder(root);
    }
    void midOrder(node *p){
        if(p == NULL){
            return;
        }
        midOrder(p -> left);
        cout << p -> data << ' ';
        midOrder(p -> right);
    }

insert操作:

insert操作就与二叉查找树有些不同了,它不但要找到合适位置插入元素,还要判断插入后是不是破坏了树的平衡性,如果破坏了要对树进行调整。

先列举几种失去平衡的情况:

《AVL树的原理及代码实现》《AVL树的原理及代码实现》

LL: 在节点的左子树的左子树上插入节点,使节点平衡度变为2,失去平衡

LR:在节点的左子树的右子树上插入节点,使节点平衡度变为2,失去平衡

RR:在节点的右子树的右子树上插入节点,使节点平衡度变为-2,失去平衡

RL:在节点的右子树的左子树上插入节点,使节点平衡度变为-2,失去平衡

下面来说解决办法:

LL单旋转:

《AVL树的原理及代码实现》

如图,k2失去平衡,k2的左子树根节点k1顶替k2的位置,且k2的左指针指向其k1的右节点,k1的右指针指向k2,这样整个树恢复了平衡。

    void LL(node *&t){
        node *t1 = t -> left;
        t -> left = t1 -> right;
        t1 -> right = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
    }

RR单旋转: 

《AVL树的原理及代码实现》

如图,k1失去平衡,k1的左子树根节点k2顶替k1的位置,且k1的右指针指向其k2的左节点,k2的左指针指向k1,这样整个树恢复了平衡。

    void RR(node *&t){
        node *t1 = t -> right;
        t -> right = t1 -> left;
        t1 -> left = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
        
    }

LR双旋转:

《AVL树的原理及代码实现》

如图,k3失去了平衡,如果只对k3使用LL旋转,那样k2作为k3的子树,k1节点则会失去平衡,这时就需要两次旋转来实现。先对k1进行RR旋转,再对k3进行LL旋转,这样就恢复了平衡,是不是被如此酷炫的操作亮瞎了双眼。来看看代码实现:

    void LR(node *&t){
        RR(t -> left);
        LL(t);
    }

RL双旋转:

《AVL树的原理及代码实现》

与LR对称着来看就好。

    void RL(node *&t){
        LL(t -> right);
        RR(t);
    }

学会了这些操作之后,insert操作就很好理解了,只要在插入后判断一下是否平衡,若不平衡,对症下药调整一下就好。注意要注意每次插入后要从插入点到根节点一个一个更新height值,也就是代码的最后一行。

    void insert(const elemType &x, node *&t){
        if(t == NULL){
            t = new node(x, NULL, NULL);
        }
        else{
            if(x < t -> data){
                insert(x, t -> left);
                if(high(t -> left) - high(t -> right) == 2){
                    if(x < t -> left -> data){
                        LL(t);
                    }
                    else{
                        LR(t);
                    }
                }
            }
            else{
                if(x == t -> data){
                    cout << "The node has existed" << endl;
                    return;
                }
                else{
                    insert(x, t -> right);
                    if(high(t -> right) - high(t -> left) == 2){
                        if(x > t -> right -> data){
                            RR(t);
                        }
                        else{
                            RL(t);
                        }
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1; //!!!
    }

remove操作:

同样的道理,remove操作也在二叉查找树的基础上增加了判平衡操作,这里我们使用到了反向思维

这是RL操作的图:

《AVL树的原理及代码实现》

举个栗子,假入说A下面原来挂着一个x,现在我们把它删了,是不是就跟上述情况一样了呢。

其他几种情况自己对照着上面的四种情况这样理解一下,相信你会恍然大悟的。

    void remove(const elemType &x, node *&t){
        if(t == NULL){
            return;
        }
        if(x < t -> data){
            remove(x, t -> left);
            if(high(t -> right) - high(t -> left) == 2 ){
                if(t -> right -> left != NULL && high(t -> right -> left) > high(t -> right -> right)){
                    RL(t);
                }
                else{
                    RR(t);
                }
            }
        }
        else{
            if(x > t -> data){
                remove(x, t -> right);
                if(high(t -> left) - high(t -> right) == 2 ){
                    if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                        LR(t);
                    }
                    else{
                        LL(t);
                    }
                }
                
            }
            else{   //==
                if(t -> left != NULL && t -> right != NULL){
                    node *tmp = t -> right;
                    while(tmp -> left != NULL){
                        tmp = tmp -> left;
                    }
                    t -> data = tmp -> data;
                    remove(t -> data, t -> right);
                    if(high(t -> left) - high(t -> right) == 2 ){
                        if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                            LR(t);
                        }
                        else{
                            LL(t);
                        }
                    }
                }
                else{
                    node *old = t;
                    if(t -> left == NULL && t -> right == NULL){
                        delete old;
                    }
                    else{
                        if(t -> left!= NULL){
                            t = t -> left;
                        }
                        else{
                            t = t -> right;
                        }
                        delete old;
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1;
    }

完整代码:

#include <iostream>

using namespace std;

template <class elemType>
class AvlTree{
private:
    
    struct node{
        elemType data;
        node *left;
        node *right;
        int height;
        
        node(const elemType &x, node *ln, node *rn, int h = 1):data(x), left(ln), right(rn), height(h){}
    };
    
    node *root;
    
public:
    
    AvlTree(){
        root = NULL;
    }

    ~AvlTree(){
        clear(root);
    }
    
    void clear(node *t){
        if(t == NULL){
            return;
        }
        clear(t -> left);
        clear(t -> right);
        delete t;
    }
    
    elemType *find(const elemType &x){
        node *t = root;
        while(t != NULL && t -> data != x){
            if(t -> data > x){
                t = t -> right;
            }
            else{
                t = t -> left;
            }
        }
        
        if(t == NULL){
            return NULL;
        }
        else{
            return &(t -> data);
        }
    }
    
    void insert(const elemType &x){
        insert(x, root);
    }
    
    void insert(const elemType &x, node *&t){
        if(t == NULL){
            t = new node(x, NULL, NULL);
        }
        else{
            if(x < t -> data){
                insert(x, t -> left);
                if(high(t -> left) - high(t -> right) == 2){
                    if(x < t -> left -> data){
                        LL(t);
                    }
                    else{
                        LR(t);
                    }
                }
            }
            else{
                if(x == t -> data){
                    cout << "The node has existed" << endl;
                    return;
                }
                else{
                    insert(x, t -> right);
                    if(high(t -> right) - high(t -> left) == 2){
                        if(x > t -> right -> data){
                            RR(t);
                        }
                        else{
                            RL(t);
                        }
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1;
    }
    
    void remove(const elemType &x){
        remove(x, root);
    }
    
    void remove(const elemType &x, node *&t){
        if(t == NULL){
            return;
        }
        if(x < t -> data){
            remove(x, t -> left);
            if(high(t -> right) - high(t -> left) == 2 ){
                if(t -> right -> left != NULL && high(t -> right -> left) > high(t -> right -> right)){
                    RL(t);
                }
                else{
                    RR(t);
                }
            }
        }
        else{
            if(x > t -> data){
                remove(x, t -> right);
                if(high(t -> left) - high(t -> right) == 2 ){
                    if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                        LR(t);
                    }
                    else{
                        LL(t);
                    }
                }
                
            }
            else{   //==
                if(t -> left != NULL && t -> right != NULL){
                    node *tmp = t -> right;
                    while(tmp -> left != NULL){
                        tmp = tmp -> left;
                    }
                    t -> data = tmp -> data;
                    remove(t -> data, t -> right);
                    if(high(t -> left) - high(t -> right) == 2 ){
                        if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                            LR(t);
                        }
                        else{
                            LL(t);
                        }
                    }
                }
                else{
                    node *old = t;
                    if(t -> left == NULL && t -> right == NULL){
                        delete old;
                    }
                    else{
                        if(t -> left!= NULL){
                            t = t -> left;
                        }
                        else{
                            t = t -> right;
                        }
                        delete old;
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1;
    }
    
    int high(node *t){
        if(t == NULL){
            return 0;
        }
        else{
            return t -> height;
        }
    }
    
    void LL(node *&t){
        node *t1 = t -> left;
        t -> left = t1 -> right;
        t1 -> right = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
    }
    
    void LR(node *&t){
        RR(t -> left);
        LL(t);
    }
    
    void RR(node *&t){
        node *t1 = t -> right;
        t -> right = t1 -> left;
        t1 -> left = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
        
    }
    
    void RL(node *&t){
        LL(t -> right);
        RR(t);
    }
    
    int max(int a, int b){
        if(a > b){
            return a;
        }
        else{
            return b;
        }
    }
    
    void midOrder(){
        midOrder(root);
    }
    void midOrder(node *p){
        if(p == NULL){
            return;
        }
        midOrder(p -> left);
        cout << p -> data << ' ';
        midOrder(p -> right);
    }

    
};

总结

AVL树很好的解决了平衡二叉树在特殊情况下会退化成单链表的问题,这样AVL树就一直会保持在矮胖的状态,而不会成为一颗瘦高的树。AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。只要学好AVL树的旋转操作,就能学好AVL树。

 

(以上图片全部来自于skywang12345的博客:http://www.cnblogs.com/skywang12345/p/3576969.html,没办法,我画的话可能此生都别想看懂AVL树了)

 

 

 

 

 

 

    原文作者:AVL树
    原文地址: https://blog.csdn.net/qq_39747794/article/details/83654985
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞