AVL树及相关操作

AVL树定义

  • AVL树是带有平衡条件的二叉搜索树。对于 N N 个节点的AVL树,其深度必然为 O(logN) O ( log ⁡ N )
  • 一颗AVL树其每个节点的左子树和右子树的高度最多相差1(空树的高度定义为-1)。

AVL树的旋转

  • 保证AVL树平衡的关键是插入元素过程中的旋转调整。旋转分四种情况:单左旋,单右旋,双左旋及双右旋。

1.单左旋:插入某个元素之后,某个结点的左子树的高度比右子树的高度之差大于1。且插入的元素在其左子树的左端。则需进行单左旋调整树的结构。

《AVL树及相关操作》
上图所示,当插入的元素 x x x>k1>k2 x > k 1 > k 2 ,位于 k1 k 1 左端。节点 k2 k 2 的左子树 k1 k 1 高度比其右子树高度之差大于1,则对 k2 k 2 进行调整。首先把 k1 k 1 的右孩子变成 k2 k 2 的左孩子,然后将整个以 k2 k 2 为根节点的子树变成 k1 k 1 的右孩子。调整之后满足AVL二叉搜索树的定义。
主要代码如下:

AvlTree SingleRotateWithLeft(AvlTree K2)    // 单左旋函数
{
    AvlNode* K1 = new AvlNode;  // 创建临时结点K1指向K2的左孩子
    K1 = K2->left;              
    K2->left = K1->right;       // K1的右孩子变成K2的左孩子
    K1->right = K2;             // K1的右孩子变成K2,此时K1转变为子树的根节点
    K2->height = Height(K2->left) > Height(K2->right) ? Height(K2->left) + 1 : Height(K2->right) + 1;
    K1->height = Height(K1->left) > Height(K2) ? Height(K1->left) + 1 : Height(K2) + 1;
    return K1;
}

2.双左旋:插入某个元素之后,某个结点的左子树的高度比右子树的高度之差大于1。且插入的元素在其左子树的右端。则需进行双左旋调整树的结构(单左旋不能修复如下图所示)。
《AVL树及相关操作》
则此时考虑情形:
《AVL树及相关操作》
如图,当插入的元素 x x k3<x<k1 k 3 < x < k 1 ,会使得 x x 位于 k1 k 1 的右端。这时 k3 k 3 的左子树与右子树高度之差大于1。进行双左旋,首先拆分 k2 k 2 ,分别将 k2 k 2 的左孩子变为 k1 k 1 的右孩子, k2 k 2 的右孩子变为 k3 k 3 的左孩子。然后把调整之后的 k1 k 1 k3 k 3 分别变成 k2 k 2 的左孩子和右孩子。代码实现如下:

AvlTree DoubleRotateWithLeft(AvlTree K3)    // 双左旋函数,调整以K3为结点的子树
{
    AvlNode* K1 = K3->left;     // 创建临时结点K1指向K3的左孩子
    AvlNode* K2 = K3->left->right;  // 创建临时结点K2指向K3的左孩子的右孩子
    K3->left = K2->right;       // K2的右孩子变为K3的左孩子
    K1->right = K2->left;       // K2的左孩子变为K1的右孩子
    K2->left = K1;              // K2的left指向K1
    K2->right = K3;             // K2的right指向K3,此时K2转变为子树的根节点

    K1->height = Height(K1->left) > Height(K1->right) ? Height(K1->left) + 1 : Height(K1->right) + 1;
    K3->height = Height(K3->left) > Height(K3->right) ? Height(K3->left) + 1 : Height(K3->right) + 1;
    K2->height = Height(K1) > Height(K3) ? Height(K1) + 1 : Height(K3) + 1;
    return K2;
}

3.双左旋双右旋:参照单左旋与双左旋。另外:双左旋可以看成单右旋加单左旋;双右旋可以看成单左旋加单右旋。

  • 单右旋代码:
AvlTree SingleRotateWithRight(AvlTree K2)   // 单右旋函数,调整以K3为根结点的子树
{
    AvlNode* K1 = new AvlNode;  
    K1 = K2->right;
    K2->right = K1->left;       
    K1->left = K2;              
    K2->height = Height(K2->left) > Height(K2->right) ? Height(K2->left) + 1 : Height(K2->right) + 1;
    K1->height = Height(K1->right) > Height(K2) ? Height(K1->right) + 1 : Height(K2) + 1;

    return K1;
}
  • 双右旋代码(单左旋+单右旋):
AvlTree DoubleRotateWithRight(AvlTree K3)   // 双右旋函数,调整以K3根为结点的子树
{
    AvlNode* K1 = K3->right;
    AvlNode* K2 = K3->right->left;

    K1 = SingleRotateWithLeft(K1);          // 先对K3的右孩子进行单左旋

    return SingleRotateWithRight(K3);  // 在对K3进行单右旋,双右旋 = 单左旋+单右旋
}

AVL树及相关操作

AVL树的基本数据结构

  • 与二叉搜索树相比,AVL树每个结点的数据结构中多个一个用于表示高度的整型数据。数据结构声明如下:
typedef struct TreeNode {   // 定义AVL树的结点结构
    ElemType data;      // 结点包含数据值
    struct TreeNode* left;      // 左孩子
    struct TreeNode* right;     // 右孩子
    int height;
}AvlNode, *AvlTree;

AVL树的插入操作

  • 与二叉搜索树不同的是,AVL树插入之后需要对节点的左子树和右子树的高度进行检查,不满足AVL条件,则进行调整。
AvlTree insert(AvlTree T, ElemType x)
{
    if (T == nullptr)
    {   // 创建一个新结点
        AvlNode* pT = new AvlNode;
        pT->data = x;
        pT->left = nullptr;
        pT->right = nullptr;
        pT->height = 0;
        T = pT;             // 树中的空结点赋为pT
    }
    else if (x < T->data)
    {
        T->left = insert(T->left, x);       // 先正常插入,再调整
        if (Height(T->left) - Height(T->right) == 2)    // 若左子树比右子树高2,则不平衡,需要进行调整
        {
            if (x < T->left->data)          // 若插入的结点小于其左孩子的数据,则需要通过单左旋调整树的结构
                T = SingleRotateWithLeft(T);
            else
                T = DoubleRotateWithLeft(T);// 若插入的结点大于其左孩子的数据,则需要通过双左旋调整树的结构
        }
    }
    else if (x > T->data)
    {
        T->right = insert(T->right, x);
        if (Height(T->right) - Height(T->left) == 2)        // 若右子树比左子树高2,则不平衡,需要进行调整
        {
            if (x > T->right->data)         // 若插入的结点大于其右孩子的数据,则需要通过单右旋调整树的结构
                T = SingleRotateWithRight(T);
            else
                T = DoubleRotateWithRight(T); // 若插入的结点小于右孩子的数据,则需要通过双左旋调整树的结构
        }
    }

    T->height = (Height(T->left) > Height(T->right)) ? Height(T->left)+1 : Height(T->right)+1;
    /*T->height++;*/
    return T;
}

附AVL树相关操作完成代码

#include<iostream>
#include<stack>
using namespace std;

typedef int ElemType;

typedef struct TreeNode {   // 定义AVL树的结点结构
    ElemType data;      // 结点包含数据值
    struct TreeNode* left;      // 左孩子
    struct TreeNode* right;     // 右孩子
    int height;
}AvlNode, *AvlTree;

int Height(AvlNode *pT)
{
    if (pT == nullptr)
        return -1;
    else
        return pT->height;
}

AvlTree SingleRotateWithLeft(AvlTree K2)    // 单左旋函数
{
    AvlNode* K1 = new AvlNode;  // 创建临时结点K1指向K2的左孩子
    K1 = K2->left;              
    K2->left = K1->right;       // K1的右孩子变成K2的左孩子
    K1->right = K2;             // K1的右孩子变成K2,此时K1转变为子树的根节点
    K2->height = Height(K2->left) > Height(K2->right) ? Height(K2->left) + 1 : Height(K2->right) + 1;
    K1->height = Height(K1->left) > Height(K2) ? Height(K1->left) + 1 : Height(K2) + 1;

    return K1;
}

AvlTree DoubleRotateWithLeft(AvlTree K3)    // 双左旋函数,调整以K3为结点的子树
{
    AvlNode* K1 = K3->left;     // 创建临时结点K1指向K3的左孩子
    AvlNode* K2 = K3->left->right;  // 创建临时结点K2指向K3的左孩子的右孩子
    K3->left = K2->right;       // K2的右孩子变为K3的左孩子
    K1->right = K2->left;       // K2的左孩子变为K1的右孩子
    K2->left = K1;              // K2的left指向K1
    K2->right = K3;             // K2的right指向K3,此时K2转变为子树的根节点

    K1->height = Height(K1->left) > Height(K1->right) ? Height(K1->left) + 1 : Height(K1->right) + 1;
    K3->height = Height(K3->left) > Height(K3->right) ? Height(K3->left) + 1 : Height(K3->right) + 1;
    K2->height = Height(K1) > Height(K3) ? Height(K1) + 1 : Height(K3) + 1;
    return K2;
}

AvlTree SingleRotateWithRight(AvlTree K2)   // 单右旋函数,调整以K3为根结点的子树
{
    AvlNode* K1 = new AvlNode;  
    K1 = K2->right;
    K2->right = K1->left;       
    K1->left = K2;              
    K2->height = Height(K2->left) > Height(K2->right) ? Height(K2->left) + 1 : Height(K2->right) + 1;
    K1->height = Height(K1->right) > Height(K2) ? Height(K1->right) + 1 : Height(K2) + 1;

    return K1;
}

AvlTree DoubleRotateWithRight(AvlTree K3)   // 双右旋函数,调整以K3根为结点的子树
{
    AvlNode* K1 = K3->right;
    AvlNode* K2 = K3->right->left;

    K1 = SingleRotateWithLeft(K1);          // 先对K3的右孩子进行单左旋

    return SingleRotateWithRight(K3);  // 在对K3进行单右旋,双右旋 = 单左旋+单右旋

}
AvlTree insert(AvlTree T, ElemType x)
{
    if (T == nullptr)
    {   // 创建一个新结点
        AvlNode* pT = new AvlNode;
        pT->data = x;
        pT->left = nullptr;
        pT->right = nullptr;
        pT->height = 0;
        T = pT;             // 树中的空结点赋为pT
    }
    else if (x < T->data)
    {
        T->left = insert(T->left, x);       // 先正常插入,再调整
        if (Height(T->left) - Height(T->right) == 2)    // 若左子树比右子树高2,则不平衡,需要进行调整
        {
            if (x < T->left->data)          // 若插入的结点小于其左孩子的数据,则需要通过单左旋调整树的结构
                T = SingleRotateWithLeft(T);
            else
                T = DoubleRotateWithLeft(T);// 若插入的结点大于其左孩子的数据,则需要通过双左旋调整树的结构
        }
    }
    else if (x > T->data)
    {
        T->right = insert(T->right, x);
        if (Height(T->right) - Height(T->left) == 2)        // 若右子树比左子树高2,则不平衡,需要进行调整
        {
            if (x > T->right->data)         // 若插入的结点大于其右孩子的数据,则需要通过单右旋调整树的结构
                T = SingleRotateWithRight(T);
            else
                T = DoubleRotateWithRight(T); // 若插入的结点小于右孩子的数据,则需要通过双左旋调整树的结构
        }
    }

    T->height = (Height(T->left) > Height(T->right)) ? Height(T->left)+1 : Height(T->right)+1;
    /*T->height++;*/
    return T;
}

void PostOrderAvlTree(AvlTree T)        // 借助栈的非递归后序遍历Avl二叉查找树
{
    stack<AvlNode *> ST;        // 利用标准模板库里面栈模板创建一个栈ST
    AvlNode * pT = T;           // 创建临时结点指向树的根节点
    AvlNode * lastvisit = nullptr;  // 创建一个临时结点表示上一次访问的结点
    while (pT || !ST.empty())   // pT不为空或栈不为空则执行循环
    {
        if (pT)                 // pT不为空,入栈
        {
            ST.push(pT);
            pT = pT->left;      // 左子树依次入栈
        }
        else
        {   // 左子树都入了栈,则考虑访问和右子树的情况
            /*pT = ST.top(); if (pT->right == nullptr || pT->right == lastvisit) { cout << pT->data << " "; lastvisit = pT; ST.pop(); pT = nullptr; } else { pT = pT->right; }*/
            pT = ST.top();      // 取栈顶元素作为当前结点
            if (pT->right && pT->right != lastvisit)        // 考虑当前结点pT是否有右子树和是否被访问
            {
                pT = pT->right;                     // 若右子树不为空且未被访问,pT指向其右子树
            }
            else
            {   // 若当前结点的右子树为空或者其右子树已经被访问,则访问当前结点
                cout << pT->data << "(h:" << pT->height << ")" << " ";
                lastvisit = pT;         // 标记当前节点被访问
                ST.pop();               // 当前结点出栈
                pT = nullptr;           // 令pT为nullptr,考虑新的栈顶元素即其父节点
            }

        }
    }

}


int main()
{
    const ElemType rawdata[] = { 19, 13, 9, 8, 23, 39, 4, 2, 75, 100, 43, 58 }; AvlTree myTree = nullptr; for (int i = 0;i < sizeof(rawdata) / sizeof(ElemType);i++) { myTree = insert(myTree, rawdata[i]); // 向树中插入给定数据 } cout << "The Postorder print of the tree is: \n"; PostOrderAvlTree(myTree); delete myTree; system("pause"); return 0; }
  • 操作运行结果
The Postorder print of the tree is:
2(h:0) 4(h:1) 9(h:0) 8(h:2) 19(h:0) 23(h:1) 58(h:0) 100(h:0) 75(h:1) 43(h:2) 13(h:3)
    原文作者:AVL树
    原文地址: https://blog.csdn.net/weixin_40170902/article/details/80735501
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞