码迷,mamicode.com
首页 > 其他好文 > 详细

树(三)——自平衡二叉树(AVL)

时间:2015-09-12 13:31:11      阅读:192      评论:0      收藏:0      [点我收藏+]

标签:

简介

自平衡二叉树(AVL)属于二叉平衡树的一类,此类树主要完成一个从键到值的查找过程,即字典(或映射),它维护树高度的方式与其他数据结构不同。

自平衡规则:

  1. AVL树的左、右子树都是AVL树
  2. 左、右子树的高度差不超过1

在数据结构中,最常见的数据间关系的抽象就是集合Collection字典Dictionary

集合就是线性表(元素允许重复),而字典是一种非多键映射关系(键不允许重复)。

对集合而言,一个班中的所有学生构成一个集合,可以是有序的(有序集合)也可以是无序的(无序集合),查找的时间复杂度一般是O(n),很低。集合的典型就是C#中的List,STL中的vector。集合主要用于存储数据,很少用于查找。如要用于查找,那么可以选择基于有序表的二分查找,相应时间复杂度是O(logn),而要想从无序表转变为有序表,就是各种排序算法的使命了。

而对字典而言,如同根据一个学生的学号找到他这次考试的成绩一样,主要是用于查找的。这是从键到值的单值映射,键不能重复,值可以重复。根据键找到值是字典的工作。如果是平衡查找树,就为O(logn),实现一般以红黑树为主(比AVL简单),如Java的TreeMap、STL的map;如果是哈希表,就为O(1),如Java的HashMap。

源码

avltree.h btree.cpp

树的建立

在这里,只讲元素的插入,而对于删除操作,由于其复杂性,暂且不能实现。

结点的数据结构为:

template<class T>
struct AVLNode
{
    T data;
    AVLNode *lchild, *rchild;
    int BF; // 平衡因子
};

前面与二叉树一样,BF用于累计高度差,因为查询高度是一个枯燥的递归操作。

创建结点:

template<class T, class N>
N* AVLTree<T, N>::New()
{
    N* newp = BiTree<T, N>::New();
    newp->BF = 0;
    return newp;
}

按数组插入:

template<class T, class N>
AVLTree<T, N>::AVLTree(const vector<T>& s)
{
    if (s.empty()) throw "数组不能为空";
    root = new N;
    root->data = s[0];
    root->BF = 0;
    root->lchild = NULL;
    root->rchild = NULL;
    for (int i = 1; i < s.Length(); i++) Insert(s[i]);
}

结点的插入

对于AVL,结点的插入是一项复杂的工作。由于插入操作,树原本的平衡被破坏,需要重新调整。

基本步骤是:

  1. 二分查找,找到相应的空位置,并插入(若已存在则忽略),同时存储查找路径
  2. 从路径逆着向上找到第一个最小不平衡子树的根结点的父结点,接下来就是对这个子树进行调整
  3. 调整子树,有LL、LR、RL、RR四种情况

1)顺藤摸瓜

stack<N*> path; // 存储插入前的查找路径(便于回溯)

    //////////////////////////////////////////////////////////////////////////
    // 插入操作
    N *p = root;
    while (true)
    {
        path.push(p);
        if (newp->data < p->data) // 插入值小于根结点,入左子树
        {
            if (p->lchild != NULL)
            {
                p = p->lchild; // 值小于LL,则递归入L
            }
            else
            {
                p->lchild = newp; break; // 根结点无左孩子,正好插入
            }
        }
        else if (newp->data > p->data) // 插入值大于根结点,入右子树
        {
            if (p->rchild != NULL)
            {
                p = p->rchild; // 值大于RR,则递归入R
            }
            else
            {
                p->rchild = newp; break; // 根结点无右孩子,正好插入
            }
        }
        else // 插入值等于根结点,返回
        {
            delete newp; return false;
        }
    }

2)找到“罪魁祸首”

// 调整插入路径上结点的BF,定位失衡的结点*p及其父结点*parent

    N *child = NULL;  // *child作为*p的孩子结点
    p = newp;
    N *parent = path.top(); // *parent是*p的父结点
    path.pop();
    while (true)
    {
        if (parent->lchild == p) // p是parent的左孩子,那么parent的BF++
            parent->BF++;  // BF的变化:-1->0->1->2
        else // p是parent的右孩子,那么parent的BF--
            parent->BF--;  // BF的变化:1->0->-1->-2

        if (parent->BF == 2 || parent->BF == -2) // *parent是失衡结点(第一个|BF|>=2)
            break; // 找到最小不平衡子树的根结点

        if (parent->BF == 0) // 在插入新结点后,*parent的左右子树高度相等(BF为0),说明以*parent为根的子树高度未增加
            return true;       // 所以路径中的其余祖先结点无需调整BF

        if (path.empty()) // 直到树的根结点,在path中没有结点|BF|超出2,所以不必调整(导致有BF为+1,-1等下次插入再行调整)
            return true;

        child = p; p = parent; parent = path.top(); // 由path向上回溯
        path.pop();
    }

3)各居各位

//////////////////////////////////////////////////////////////////////////
    // *parent失衡,以下代码进行调整
    N *ancestor = path.empty() ? NULL : path.top(); // ancestor为parent的双亲结点
    if (!path.empty()) path.pop();

    //////////////////////////////////////////////////////////////////////////

    if (parent->BF == 2 && p->lchild == child)    // LL
    {
        ////                                      A(parent,root),BF(2)
        //                                      |
        //                  B(p),BF(1)__________|_____AR
        //                  |
        //        BL(X)_____|_____BR

        ////                  B(p,root),BF(0)
        //                  |                 
        //        BL(X)_____|___________________A(parent),BF(0)
        //                                      |
        //                               BR_____|_____AR

        parent->lchild = p->rchild;
        p->rchild = parent;

        parent->BF = 0;
        p->BF = 0;

        if (ancestor != NULL)
        {
            if (ancestor->lchild == parent) // 修改p的双亲为ancestor
                ancestor->lchild = p;
            else
                ancestor->rchild = p;
        }
        else
        {
            root = p;
        }

        return true;
    }

    //////////////////////////////////////////////////////////////////////////

    if (parent->BF == -2 && p->rchild == child)    // RR
    {
        ////                  A(parent,root),BF(-2)
        //                  |                
        //           AL_____|___________________|
        //                                      B(p),BF(-1)
        //                               BL_____|_____BR(X)

        ////                                      B(p,root),BF(0)
        //                                      |
        //                  A(parent),BF(0)_____|_____BR(X)
        //                  |
        //           AL_____|_____BL

        parent->rchild = p->lchild; // 和LL只有这两句不一样
        p->lchild = parent;

        parent->BF = 0;
        p->BF = 0;

        if (ancestor != NULL)
        {
            if (ancestor->lchild == parent) // 修改p的双亲为ancestor
                ancestor->lchild = p;
            else
                ancestor->rchild = p;
        }
        else
        {
            root = p;
        }

        return true;
    }

    //////////////////////////////////////////////////////////////////////////

    if (parent->BF == 2 && p->rchild == child)    // LR
    {
        ////                                      A(parent,root),BF(2)
        //                                      |
        //                  B(p),BF(-1)_________|_____AR
        //                  |
        //           BL_____|_____C(pc),BF(1)
        //                        |
        //             CL(X)______|______CR

        // 后(减少一层)
        //                                C(pc,root),BF(0)
        //                                |
        //                  B(p),BF(0)____|_____A(parent),BF(-1)
        //                  |                   |
        //           BL_____|_____CL(x)  CR_____|_____AR

        N *pc = p->rchild;
        parent->lchild = pc->rchild;
        p->rchild = pc->lchild;
        pc->lchild = p;
        pc->rchild = parent;

        pc->BF = 0;
        p->BF = 0;
        parent->BF = -1;

        if (ancestor != NULL)
        {
            if (ancestor->lchild == parent) // 修改pc的双亲为ancestor
                ancestor->lchild = pc;
            else
                ancestor->rchild = pc;
        }
        else
        {
            root = pc;
        }

        return true;
    }

    //////////////////////////////////////////////////////////////////////////

    if (parent->BF == -2 && p->lchild == child)    // RL
    {
        ////                  A(parent,root),BF(-2)
        //                  |
        //           AL_____|___________________B(p),BF(1)
        //                                      |
        //                        C(pc),BF(-1)__|______BR
        //                        |
        //                 CL_____|_____CR(X)

        // 后(减少一层)
        //                                 C(pc,root),BF(0)
        //                                 |
        //                  A(parent),BF(1)|_____B(p),BF(0)
        //                  |                    |
        //           AL_____|_____CL   CR(x)_____|_____BR

        N *pc = p->lchild;
        parent->rchild = pc->lchild;
        p->lchild = pc->rchild;
        pc->lchild = parent;
        pc->rchild = p;

        pc->BF = 0;
        p->BF = 0;
        parent->BF = 1;

        if (ancestor != NULL)
        {
            if (ancestor->lchild == parent) // 修改pc的双亲为ancestor
                ancestor->lchild = pc;
            else
                ancestor->rchild = pc;
        }
        else
        {
            root = pc;
        }

        return true;
    }

    return true;
}

调整过程全部是旋转操作

总结

由于AVL操作的复杂性,因而不常用AVL,现在主要使用红黑树(RBT),它们的区别是:

  • AVL讲究整体平衡,因而要求严格、操作复杂
  • RBT追求局部平衡,因而实现较AVL简单

树(三)——自平衡二叉树(AVL)

标签:

原文地址:http://www.cnblogs.com/bajdcc/p/4802834.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!