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

简介

自平衡二叉树(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简单
时间: 2024-10-10 20:27:26

树(三)——自平衡二叉树(AVL)的相关文章

图解平衡二叉树,AVL树(一)

图解平衡二叉树,AVL树(一) 学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建立的树如图2,才能够最大地体现二叉树的优点. 在上述的例子中,图2就是一棵平衡二叉树.科学家们提出平衡二叉树,就是为了让树的查找性能得到最大的体现(至少我是这样理解的,欢迎批评改正).下面进入今天的正题,平衡二叉树. AVL的定义 平衡二叉查找树:简称平衡二叉树.由前

AVL树学习(平衡二叉树)

一.基本概念 AVL树既是平衡二叉树.AVL树的定义首先要求该树是二叉查找树(满足排序规则),并在此基础上增加了每个节点的平衡因子的定义,一个节点的平衡因子是该节点的左子树树高减去右子树树高的值. =========================================================================== 1. 二叉查找树:又称二叉排序树/二叉搜索树,具有以下性质: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值: (2)若右子树不空,则

java数据结构与算法之平衡二叉树(AVL树)的设计与实现

[版权申明]未经博主同意,不允许转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/53892797 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) java数据结构与算法之栈(Stack)设

数据结构之树篇3——平衡二叉树(AVL树)

引入 上一篇写了二叉排序树,构建一个二叉排序树,如果构建序列是完全有序的,则会出现这样的情况: 显然这种情况会使得二叉搜索树退化成链表.当出现这样的情况,二叉排序树的查找也就退化成了线性查找,所以我们需要合理调整二叉排序树的形态,使得树上的每个结点都尽量有两个子结点,这样整个二叉树的高度就会大约在\(log(n)\) 左右,其中 \(n\) 为结点个数. 基本性质 ? AVL树也称为平衡二叉树,是一种自平衡的二叉排序树,本质上仍然是一颗二叉排序树,只是增加了"平衡"的要求,平衡是指,对

平衡二叉树 AVL 的插入节点后旋转方法分析

平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1. 首先我们知道,当插入一个节点,从此插入点到树根节点路径上的所有节点的平衡都可能被打破,如何解决这个问题呢? 这里不讲大多数书上提的什么平衡因子,什么最小不平衡子树,实际上让人(me)更加费解.实际上你首要做的就是先找到第一个出现不平衡的节点,也就是高度最高的那个节点A,对以它为根的子树做一次旋转或者两次旋转,此时这个节点的平衡问题解决了,整个往

数据结构快速回顾——平衡二叉树 AVL (转)

平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵树,所以它又叫AVL树.平衡二叉树要求对于每一个节点来说,它的左右子树的高度之差不能超过1,如果插入或者删除一个节点使得高度之差大于1,就要进行节点之间的旋转,将二叉树重新维持在一个平衡状态.这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(

树:BST、AVL、红黑树、B树、B+树

我们这个专题介绍的动态查找树主要有: 二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree).这四种树都具备下面几个优势: (1) 都是动态结构.在删除,插入操作的时候,都不需要彻底重建原始的索引树.最多就是执行一定量的旋转,变色操作来有限的改变树的形态.而这些操作所付出的代价都远远小于重建一棵树.这一优势在<查找结构专题(1):静态查找结构概论 >中讲到过. (2) 查找的时间复杂度大体维持在O(log(N))数量级上.可能有些结构在最差的情况下效率将

哈夫曼树(三)之 Java详解

前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 哈夫曼树的介绍 Huffman Tree,中文名是哈夫曼树或霍夫曼树,它是最优二叉树. 定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树. 这

判断一颗树是否为平衡二叉树

struct BinaryTreeNode { int m_Value; BinaryTreeNode* m_pLeft; BinaryTreeNode* m_pRight; }; int height(BinaryTreeNode*node ,bool&balance) { if(node==NULL) { return 0;} int R=node->right?height(node->right,balance)+1:0; if(!balance) return 0; int

树-二叉平衡树AVL

基本概念 AVL树:树中任何节点的两个子树的高度最大差别为1. AVL树的查找.插入和删除在平均和最坏情况下都是O(logn). AVL实现 AVL树的节点包括的几个组成对象: (01) key -- 是关键字,是用来对AVL树的节点进行排序的. (02) left -- 是左孩子. (03) right -- 是右孩子. (04) height -- 是高度.即空的二叉树的高度是0,非空树的高度等于它的最大层次(根的层次为1,根的子节点为第2层,依次类推). AVL旋转算法 AVL失衡四种形态