带你一起学会并实现AVL树!!

168 阅读13分钟

AVL树创立过程

目录

  1. 引言

  2. AVL树的特性

  3. AVL树的一个基本框架

  4. AVL树的节点插入

  5. AVL树的节点删除

  6. 整体代码

  7. 总结

1.引言

我们知道一棵正常的搜索二叉树,可以是下面这样的:

屏幕截图 2024-11-29 080858.png

通过直接观察我们就能够知道了这棵搜索二叉树的时间复杂度是O(n),我们现在可以先把它进行一个简单的赋值操作:

屏幕截图 2024-11-29 081150.png

这里就能看到,假如我们想要去找到6这个节点,我们需要找3次才能找到,这样的效率有点低

但假如我们把其改成这样呢?

屏幕截图 2024-11-29 082900.png

这样我们再去找6这个节点就仅需要2次即可,而且总路径更短了

所以这就是本篇要讲的内容了,平衡二叉树了,又称AVL树,其名字也是起于其创始人,这里就不展开讲了

2.AVL树的特性

  1. 搜索二叉树:将大于根节点(包括子树的根节点)的值域的节点放到其相应的右子树上 ,将小于根节点(包括子树的根节点)的值域的节点放到其相应的左子树上
  2. AVL树:是在有搜索二叉树的前提下,还需要保证左右两棵子树(所有的节点)的深度不超过1

这我们可以一起来看一棵AVL树,便于我们理解

屏幕截图 2024-11-29 083643.png

特性一:有搜索二叉树的特性

我们可以指通过观察我们就可以直接得到其符合我们的搜索二叉书的特性

特性二:左右两棵子树的深度不超过1

这个我们可以先介绍一个平衡因子的概念,就是左子树深度-右子树深度,对于特性二就是要求我们的所有平衡因子的绝对值不超过1

屏幕截图 2024-11-29 084122.png

很明显这是满足的,而当满足了以上两种特性的时候,我们才能称这棵树为AVL树

3.AVL树的基本框架

(1).AVL树的定义

typedef struct AVLTree
{
    int val;//值域
    struct AVLTree* left;//左孩子
    struct AVLTree* right;//右孩子
    int height;//表示树的高度
}Node;

(2).销毁AVL树

//为了防止内存泄漏问题,我们最后需要销毁AVL树
void Destroy(Node** proot)
{
    //通过后序遍历进行销毁
    if(*proot == NULL)
        return;
    Destroy((*proot)->left);
    Destroy((*proot)->right);
    free(*proot);
    *proot = NULL;
}

(3).遍历AVL树

//我们知道先序遍历和中序遍历可以确定一颗唯一的树

//先序遍历
void preTraversal(Node* root)
{
    if(root == NULL)
        return;
    printf("%d ",root->val);
    preTraversal(root->left);
    preTraversal(root->right);
}

//中序遍历
void midTraversal(Node* root)
{
    if(root == NULL)
        return;
    midTraversal(root->left);
    printf("%d ",root->val);
    midTraversal(root->right);
}

(4).获得树高

int getHeight(Node* root)
{
    if(root == NULL)
        return 0;
    return root->height;
}

(5).获得平衡因子

int getBalance(Node* root)
{
    return getHeight(root->left)-getHeight(root->right);
}

(6).获得较大值

int getMax(int a, int b)
{
    return a > b ? a : b;
}

(7).左旋右旋

i.对于左旋右旋的基本理解

对于AVL树,我们要知道有一个左旋右旋的操作,像下面的这棵树

屏幕截图 2024-11-29 084723.png

对于这棵树,我们对其进行一个左旋的操作,这样就可以将其转变为我们上面对于AVL树的要求

屏幕截图 2024-11-29 084930.png

那么下面就来一起仔细来看看左旋右旋吧!

左旋

上面我们就已经进行了一次左旋的操作了

那假如此时左旋的时候,新节点假如原本有左子树,那这棵左子树应该放哪里呢?

屏幕截图 2024-11-29 085743.png

这个我们通过对于搜索二叉树的规则,我们也可以知道这棵左子树应该放到旧根节点的右子树上,这样才能满足我们对于搜索二叉树的要求

屏幕截图 2024-11-29 090214.png

所以根据这些操作我们就可以得到左旋操作的基本原理了

左旋的操作的原理:

  1. 将根节点的右子树变成新根节点
  2. 将新根节点的左子树接到旧根节点的右子树上

此时,或许有人疑问:不需要判断新根节点原本的左子树是否为空吗?

答案是不需要

因为我们知道原根节点的右子树已经变成了新根节点,那么现在连在旧根节点上的右子树为空了,即使再连一个空也没有区别,所以答案就出来了,右旋也是同理的

右旋

那么根据左旋我们就可以很快地得到右旋的基本原理了

右旋的操作的原理:

  1. 将根节点的左子树变成新根节点
  2. 将新根节点的右子树树接到旧根节点的左子树上

下面我们可以用一个例子来验证一下这是否正确的

屏幕截图 2024-11-29 091523.png

操作一:将根节点的左子树变成新根节点

屏幕截图 2024-11-29 092013.png

操作二:将新根节点的右子树树接到旧根节点的左子树上

屏幕截图 2024-11-29 092351.png

显然,这就变成了一棵标准的AVL树了

ii.左旋右旋的代码实现
//左旋
Node* leftRoate(Node* root)
{
    Node* newroot = root->right;//表示新根节点
    Node* T2 = newroot->left;//表示新节点原来的左子树
    root->right = T2;
    newroot->left = root;
    //更新树高
    newroot->height = getHeight(newroot);
    root->height = getHeight(root);
    return newroot;
}

//右旋跟左旋的方向对称
Node* rightRoate(Node* root)
{
    Node* newroot = root->left;//表示新根节点
    Node* T2 = newroot->right;//表示新节点原来的左子树
    root->left = T2;
    newroot->right = root;
    //更新树高
    newroot->height = getHeight(newroot);
    root->height = getHeight(root);
    return newroot;
}

4.AVL树的节点插入

(1).节点插入时可能碰见的问题

i.没有问题

有可能我们出现下面这种情况:

屏幕截图 2024-11-29 150727.png 此时我们插入一个10的节点:

屏幕截图 2024-11-29 150957.png

此时,我们计算其平衡因子:

屏幕截图 2024-11-29 151106.png

之前:

屏幕截图 2024-11-29 150912.png

此时发现这棵树对比之前依然是一棵AVL树,所以就不需要进行修改

ii.LL型失衡

这里的第一个L指的是该节点的左子树,第二L是指插入的节点是其父节点的左子树

面对这种情况的操作是:

对发生问题的节点进行右旋

下面就跟着这个例子一起来看一下:

屏幕截图 2024-11-29 151929.png

此时20这个节点的平衡因子超过了1,于是对其进行右旋的操作,于是得到下面

屏幕截图 2024-11-29 152123.png

此时就成功还原成一棵AVL树了

iii.LR型失衡

此时的第一个L就是该节点的左子树,第二个R是插入节点是其父节点的右子树

此时的操作是:

  1. 对该节点的左子树进行左旋
  2. 对该节点进行右旋

下面就跟着一个一个实例来掌握:

屏幕截图 2024-11-29 153007.png

操作一:对该节点的左子树进行左旋

屏幕截图 2024-11-29 153107.png

操作二:对该节点进行右旋

屏幕截图 2024-11-29 153312.png

这时候就将这棵树变成了AVL树

此时有人就会感到神奇:"这里的操作不就是先将LR型变成LL型,然后再对其进行LL的操作吗?"

这个也可以这么理解

至于RR型和RL型由于跟这里只是方向对称,所以就不再多做赘述了

(2).代码实现

Node* nodeInsert(Node* root, int x)
{
    if(root == NULL)
    {
        root = (Node*)malloc(sizeof(Node));
        root->left = NULL;
        root->right = NULL;
        root->height = 0;
        root->val = x;
        return root;
    }
    if(root->val < x)
    {
        root->right = nodeInsert(root->right, x);
    }
    else if(root->val > x)
    {
        root->left = nodeInsert(root->left, x);
    }
    else 
        return root;//一棵树上不能出现两个相同的节点
    root->height = 1 + getMax(getHeight(root->left), getHeight(root->right));
    //获得平衡因子
    int balance = getBalance(root);
    //LL型失衡
    if(balance>1 && getBalance(root->left)>0)
    {
        //右旋
        return rightRoate(root);
    }
    //LR型失衡
    else if(balance>1 && getBalance(root->left)<0)
    {
        //先对其左子树进行左旋
        root->left = leftRoate(root->left);
        //再对其进行右旋
        return rightRoate(root);
    }
    //RR型失衡
    else if(balance<-1 && getBalance(root->right)<0)
    {
        //左旋
        return leftRoate(root);
    }
    //RL型失衡
    else if(balance<-1 && getBalance(root->right)>0)
    {
        //先对其右子树进行右旋
        root->right = rightRoate(root->right);
        //再对其进行左旋
        return leftRoate(root);
    }
    //假如以上均不满足则直接返回
    return root;
}

5.AVL树的节点删除

(1).可能会遇见的问题

i.

对于节点删除其实跟节点的插入都差不多,只不过是得注意几个个问题

就是这种情况:

屏幕截图 2024-11-29 161523.png

对于这种情况我们感觉有点熟悉,但却一下子想不起来这是哪里的?

我们往上面翻,就可以发现它跟我们讲的右旋的例子几乎一模一样,这时候我们就知道了要对其进行右旋,所以我们的进行左右旋转的条件需要进行修改

屏幕截图 2024-11-29 162311.png

ii.删除的节点没有左右子树

这种时候就只需要我们直接删除即可,然后对其进行一个正常的检测是否平衡

iii.删除的节点只有左子树或右子树

这种时候也是将这个节点删除后,然后将其唯一存在的左子树或右子树取代上去即可

iiii.删除的节点同时有左子树和右子树

这种时候我们的处理方法可以说是比较麻烦的

我们首先得要知道搜索二叉树在中序遍历下是一个递升的数列,那么我们就要将该节点的后继替代掉这个节点,即这个节点的右子树的最左端的节点

这个我们可以看一个例子来理解一下

屏幕截图 2024-11-29 164845.png

这时候将30这个节点删除后,然后将其后替补上来,于是就可以得到如下

屏幕截图 2024-11-29 165237.png

这样我们的中序就不会被破坏,而且我们的搜索二叉树的特性依然保留

有人可能会发出疑问:"这里难道不需要往上检查吗?"

这里由于接下来会使用的是递归的方式,所以递归的过程中就会自动向上检查

(2).代码实现

Node* nodeDelete(Node* root, int key)
{
    if(root == NULL)
    {
        printf("没有该节点");
        return root;
    }
    if(root->val > key)
    {
        root->left = nodeDelete(root->left, key);
    }
    else if(root->val < key)
    {
        root->right = nodeDelete(root->right, key);
    }
    else 
    {
        if(root->left == NULL && root->right == NULL)
        {
            //没有左右子树
            free(root);
            root = NULL;
            return root;
        }
        else if(root->left != NULL && root->right == NULL)
        {
            //只有左子树
            Node* left = root->left;
            free(root);
            root = NULL;
            return left;
        }
        else if(root->left == NULL && root->right != NULL)
        {
            //只有右子树
            Node* right = root->right;
            free(root);
            root = NULL;
            return right;
        }
        else if(root->left != NULL && root->right != NULL)
        {
            //左子树和右子树都存在
			Node* cur = root->right;
            while(cur->left)
                cur = cur->left;
            root->val = cur->val;
            free(cur);
            return root;
        }
    }
    //更新树高
    root->height = 1 + getMax(getHeight(root->left), getHeight(root->right));
    //获得平衡因子
    int balance = getBalance(root);
    //LL型失衡
    if(balance>1 && getBalance(root->left)>=0)
    {
        //右旋
        return rightRoate(root);
    }
    //LR型失衡
    else if(balance>1 && getBalance(root->left)<=0)
    {
        //先对其左子树进行左旋
        root->left = leftRoate(root->left);
        //再对其进行右旋
        return rightRoate(root);
    }
    //RR型失衡
    else if(balance<-1 && getBalance(root->right)<=0)
    {
        //左旋
        return leftRoate(root);
    }
    //RL型失衡
    else if(balance<-1 && getBalance(root->right)>=0)
    {
        //先对其右子树进行右旋
        root->right = rightRoate(root->right);
        //再对其进行左旋
        return leftRoate(root);
    }
    //假如以上均不满足则直接返回
    return root;
}

6.整体代码

typedef struct AVLTree
{
    int val;//值域
    struct AVLTree* left;//左孩子
    struct AVLTree* right;//右孩子
    int height;//表示树的高度
}Node;

//为了防止内存泄漏问题,我们最后需要销毁AVL树
void Destroy(Node** proot)
{
    //通过后序遍历进行销毁
    if(*proot == NULL)
        return;
    Destroy(&(*proot)->left);
    Destroy(&(*proot)->right);
    free(*proot);
    *proot = NULL;
}

//我们知道先序遍历和中序遍历可以确定一颗唯一的树

//先序遍历
void preTraversal(Node* root)
{
    if(root == NULL)
        return;
    printf("%d ",root->val);
    preTraversal(root->left);
    preTraversal(root->right);
}

//中序遍历
void midTraversal(Node* root)
{
    if(root == NULL)
        return;
    midTraversal(root->left);
    printf("%d ",root->val);
    midTraversal(root->right);
}

//获得树高
int getHeight(Node* root)
{
    if(root == NULL)
        return 0;
    return root->height;
}

//获得平衡因子
int getBalance(Node* root)
{
    return getHeight(root->left)-getHeight(root->right);
}

//获得较大值
int getMax(int a, int b)
{
    return a > b ? a : b;
}

//左旋
Node* leftRoate(Node* root)
{
    Node* newroot = root->right;//表示新根节点
    Node* T2 = newroot->left;//表示新节点原来的左子树
    root->right = T2;
    newroot->left = root;
    //更新树高
    root->height = 1 + getMax(getHeight(root->left), getHeight(root->right));
	newroot->height = 1 + getMax(getHeight(newroot->left), getHeight(newroot->right));;
    return newroot;
}

//右旋跟左旋的方向对称
Node* rightRoate(Node* root)
{
    Node* newroot = root->left;//表示新根节点
    Node* T2 = newroot->right;//表示新节点原来的左子树
    root->left = T2;
    newroot->right = root;
    //更新树高
    root->height = 1 + getMax(getHeight(root->left), getHeight(root->right));
	newroot->height = 1 + getMax(getHeight(newroot->left), getHeight(newroot->right));
    return newroot;
}

//插入节点函数
Node* nodeInsert(Node* root, int x)
{
    if(root == NULL)
    {
        root = (Node*)malloc(sizeof(Node));
        root->left = NULL;
        root->right = NULL;
        root->height = 0;
        root->val = x;
        return root;
    }
    if(root->val < x)
    {
        root->right = nodeInsert(root->right, x);
    }
    else if(root->val > x)
    {
        root->left = nodeInsert(root->left, x);
    }
    else 
        return root;//一棵树上不能出现两个相同的节点
    root->height = 1 + getMax(getHeight(root->left), getHeight(root->right));
    //获得平衡因子
    int balance = getBalance(root);
    //LL型失衡
    if(balance>1 && getBalance(root->left)>0)
    {
        //右旋
        return rightRoate(root);
    }
    //LR型失衡
    else if(balance>1 && getBalance(root->left)<0)
    {
        //先对其左子树进行左旋
        root->left = leftRoate(root->left);
        //再对其进行右旋
        return rightRoate(root);
    }
    //RR型失衡
    else if(balance<-1 && getBalance(root->right)<0)
    {
        //左旋
        return leftRoate(root);
    }
    //RL型失衡
    else if(balance<-1 && getBalance(root->right)>0)
    {
        //先对其右子树进行右旋
        root->right = rightRoate(root->right);
        //再对其进行左旋
        return leftRoate(root);
    }
    //假如以上均不满足则直接返回
    return root;
}

//删除节点函数
Node* nodeDelete(Node* root, int key)
{
    if(root == NULL)
    {
        printf("没有该节点");
        return root;
    }
    if(root->val > key)
    {
        root->left = nodeDelete(root->left, key);
    }
    else if(root->val < key)
    {
        root->right = nodeDelete(root->right, key);
    }
    else 
    {
        if(root->left == NULL && root->right == NULL)
        {
            //没有左右子树
            free(root);
            root = NULL;
            return root;
        }
        else if(root->left != NULL && root->right == NULL)
        {
            //只有左子树
            Node* left = node->left;
            free(node);
            node = NULL;
            return left;
        }
        else if(root->left == NULL && root->right != NULL)
        {
            //只有右子树
            Node* right = node->right;
            free(node);
            node = NULL;
            return right;
        }
        else if(root->left != NULL && root->right != NULL)
        {
            //左子树和右子树都存在
			Node* cur = root->right;
            while(cur->left)
                cur = cur->left;
            root->val = cur->val;
            free(cur);
            return root;
        }
    }
    //更新树高
    root->height = 1 + getMax(getHeight(root->left), getHeight(root->right));
    //获得平衡因子
    int balance = getBalance(root);
    //LL型失衡
    if(balance>1 && getBalance(root->left)>=0)
    {
        //右旋
        return rightRoate(root);
    }
    //LR型失衡
    else if(balance>1 && getBalance(root->left)<=0)
    {
        //先对其左子树进行左旋
        root->left = leftRoate(root->left);
        //再对其进行右旋
        return rightRoate(root);
    }
    //RR型失衡
    else if(balance<-1 && getBalance(root->right)<=0)
    {
        //左旋
        return leftRoate(root);
    }
    //RL型失衡
    else if(balance<-1 && getBalance(root->right)>=0)
    {
        //先对其右子树进行右旋
        root->right = rightRoate(root->right);
        //再对其进行左旋
        return leftRoate(root);
    }
    //假如以上均不满足则直接返回
    return root;
}

//测试AVL树
void TestAVLTree()
{
	Node* root = NULL;
	root = insertNode(root, 10);
	root = insertNode(root, 20);
	root = insertNode(root, 30);
	root = insertNode(root, 40);
	root = insertNode(root, 50);
	root = insertNode(root, 60);
	root = insertNode(root, 70);
	printf("先序遍历:>");
	preOrder(root);
	printf("\n");
	printf("中序遍历:>");
	midOrder(root);
	printf("\n");
	root = deleteNode(root, 10);
	root = deleteNode(root, 20);
 	root = deleteNode(root, 30);
	printf("先序遍历:>");
	preOrder(root);
	printf("\n");
	printf("中序遍历:>");
	midOrder(root);
	printf("\n");
	destroyTree(&root);
}

int main()
{
    TestAVLTree();
    return 0;
}

此时的输出结果:

屏幕截图 2024-11-29 191737.png

经过简单的验证便能确定我们的AVL树成功被构建了

7.总结

AVL树最需要注意的就是平衡因子为何值的时候需要开始旋转,以及确定旋转的方向,还有最关键的就是树高及时更新,以免平衡因子有误,导致出错,那么期待我们在下一站的相遇!!!