0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

AVL 树和普通的二叉查找树的详细区别分析

算法与数据结构 2018-01-15 14:36 次阅读

背景

AVL 树是一棵平衡的二叉查找树,于 1962 年,G. M. Adelson-Velsky 和 E. M. Landis 在他们的论文《An algorithm for the organization of information》中发表。

所谓的平衡之意,就是树中任意一个结点下左右两个子树的高度差不超过 1。(本文对于树的高度约定为:空结点高度是 0,叶子结点高度是 1。)

那 AVL 树和普通的二叉查找树有何区别呢?如图,如果我们插入的是一组有序上升或下降的数据,则一棵普通的二叉查找树必然会退化成一个单链表,其查找效率就降为 O(n)。而 AVL 树因其平衡的限制,可以始终保持 O(logn) 的时间复杂度。

具体实现与代码分析

在我们进行完插入或删除操作后,很可能会导致某个结点失去平衡,那么我们就需要把失衡结点旋转一下,使其重新恢复平衡。

经过分析,不管是插入还是删除,它们都会有四种失衡的情况:左左失衡,右右失衡,左右失衡,右左失衡。因此每次遇到失衡时,我们只需判断一下是哪个失衡,再对其进行相对应的恢复平衡操作即可。

好,下面以插入操作为例,来看下这四种失衡的庐山真面目。(以下统一约定:红色结点为新插入结点,y 结点为失衡结点)

(1)左左失衡

 AVL 树和普通的二叉查找树的详细区别分析

所谓的左左,即 "失衡结点" 的左子树比右子树高 2,左孩子下的左子树比右子树高 1。

我们只需对 "以 y 为根的子树" 进行 "左左旋转 (ll_rotate)" 即可。一次旋转后,恢复平衡。

Node * AVL::ll_rotate(Node * y)

{

Node * x = y->left;

y->left = x->right;

x->right = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

(2)右右失衡

所谓的右右,即 "失衡结点" 的右子树比左子树高 2,右孩子下的右子树比左子树高 1。

我们只需对 "以 y 为根的子树" 进行 "右右旋转 (rr_rotate)" 即可。一次旋转后,恢复平衡。

Node * AVL::rr_rotate(Node * y)

{

Node * x = y->right;

y->right = x->left;

x->left = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

(3)左右失衡

 AVL 树和普通的二叉查找树的详细区别分析

所谓的左右,即 "失衡结点" 的左子树比右子树高 2,左孩子下的右子树比左子树高 1。

观察发现,若先对 "以 x 为根的子树" 进行 "右右旋转 (rr_rotate)",此时 "以 y 为根的子树" 恰好符合 "左左失衡",所以再进行一次 "左左旋转 (ll_rotate)"。两次旋转后,恢复平衡。

Node * AVL::lr_rotate(Node * y)

{

Node * x = y->left;

y->left = rr_rotate(x);

return ll_rotate(y);

}

(4)右左失衡

 AVL 树和普通的二叉查找树的详细区别分析

所谓的右左,即 "失衡结点" 的右子树比左子树高 2,右孩子下的左子树比右子树高 1。

观察发现,若先对 "以 x 为根的子树" 进行 "左左旋转 (ll_rotate)",此时 "以 y 为根的子树" 恰好符合 "右右失衡",所以再进行一次 "右右旋转 (rr_rotate)"。两次旋转后,恢复平衡。

Node * AVL::rl_rotate(Node * y)

{

Node * x = y->right;

y->right = ll_rotate(x);

return rr_rotate(y);

}

插入操作

插入成功后,在递归回溯时依次对经过的结点判断是否失衡,若失衡就需要对其进行对应的旋转操作使其恢复平衡,在这期间,原先作为一棵子树的根结点就会因为旋转被替换,因此设置insert_real( )返回的是新根结点,这样就可以实时更新根结点。

插入操作实现代码如下:

int AVL::get_height(Node * node)

{

if (node == nullptr)

return 0;

return node->height;

}

int AVL::get_balance(Node * node)

{

if (node == nullptr)

return 0;

return get_height(node->left) - get_height(node->right);

}

Node * AVL::insert_real(int key, Node * node)

{

if (node == nullptr)

return new Node(key);

if (key < node->key)

node->left = insert_real(key, node->left);

else if (key > node->key)

node->right = insert_real(key, node->right);

else

return node;

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) > 0)

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) < 0)

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

void AVL::insert(int key)

{

header->left = insert_real(key, header->left);

}

查找操作

Node * AVL::find_real(int key, Node * node)

{

if (node == nullptr)

return nullptr;

if (key < node->key)

return find_real(key, node->left);

else if (key > node->key)

return find_real(key, node->right);

else

return node;

}

Node * AVL::find(int key)

{

return find_real(key, header->left);

}

删除操作

删除操作的四种失衡情况和插入操作一样,读者可以参考前文。下面是删除操作的实现代码:

Node * AVL::erase_real(int key, Node * node)

{

if (node == nullptr)

return node;

if (key < node->key)

node->left = erase_real(key, node->left);

else if (key > node->key)

node->right = erase_real(key, node->right);

else

{

if (node->left && node->right)

{

// 找到后继结点

Node * x = node->right;

while (x->left)

x = x->left;

// 后继直接复制

node->key = x->key;

// 转化为删除后继

node->right = erase_real(x->key, node->right);

}

else

{

Node * t = node;

node = node->left ? node->left : node->right;

delete t;

if (node == nullptr)

return nullptr;

}

}

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) >= 0) // 需要加等号

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) <= 0) // 需要加等号

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

void AVL::erase(int key)

{

header->left = erase_real(key, header->left);

}

完整代码

/**

*

* author : 刘毅(Limer)

* date : 2017-08-17

* mode : C++

*/

#include

#include

using namespace std;

struct Node

{

int key;

int height;

Node * left;

Node * right;

Node(int key = 0)

{

this->key = key;

this->height = 1;

this->left = this->right = nullptr;

}

};

class AVL

{

private:

Node * header;

private:

Node * ll_rotate(Node * y);

Node * rr_rotate(Node * y);

Node * lr_rotate(Node * y);

Node * rl_rotate(Node * y);

void destroy(Node * node);

int get_height(Node * node);

int get_balance(Node * node);

Node * insert_real(int key, Node * node);

Node * find_real(int key, Node * node);

Node * erase_real(int key, Node * node);

void in_order(Node * node);

public:

AVL();

~AVL();

void insert(int key);

Node * find(int key);

void erase(int key);

void print();

};

Node * AVL::ll_rotate(Node * y)

{

Node * x = y->left;

y->left = x->right;

x->right = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

Node * AVL::rr_rotate(Node * y)

{

Node * x = y->right;

y->right = x->left;

x->left = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

Node * AVL::lr_rotate(Node * y)

{

Node * x = y->left;

y->left = rr_rotate(x);

return ll_rotate(y);

}

Node * AVL::rl_rotate(Node * y)

{

Node * x = y->right;

y->right = ll_rotate(x);

return rr_rotate(y);

}

void AVL::destroy(Node * node)

{

if (node == nullptr)

return;

destroy(node->left);

destroy(node->right);

delete node;

}

int AVL::get_height(Node * node)

{

if (node == nullptr)

return 0;

return node->height;

}

int AVL::get_balance(Node * node)

{

if (node == nullptr)

return 0;

return get_height(node->left) - get_height(node->right);

}

Node * AVL::insert_real(int key, Node * node)

{

if (node == nullptr)

return new Node(key);

if (key < node->key)

node->left = insert_real(key, node->left);

else if (key > node->key)

node->right = insert_real(key, node->right);

else

return node;

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) > 0)

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) < 0)

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

Node * AVL::find_real(int key, Node * node)

{

if (node == nullptr)

return nullptr;

if (key < node->key)

return find_real(key, node->left);

else if (key > node->key)

return find_real(key, node->right);

else

return node;

}

Node * AVL::erase_real(int key, Node * node)

{

if (node == nullptr)

return node;

if (key < node->key)

node->left = erase_real(key, node->left);

else if (key > node->key)

node->right = erase_real(key, node->right);

else

{

if (node->left && node->right)

{

// 找到后继结点

Node * x = node->right;

while (x->left)

x = x->left;

// 后继直接复制

node->key = x->key;

// 转化为删除后继

node->right = erase_real(x->key, node->right);

}

else

{

Node * t = node;

node = node->left ? node->left : node->right;

delete t;

if (node == nullptr)

return nullptr;

}

}

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) >= 0) // 需要加等号

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) <= 0) // 需要加等号

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

void AVL::in_order(Node * node)

{

if (node == nullptr)

return;

in_order(node->left);

cout << node->key << " ";

in_order(node->right);

}

AVL::AVL()

{

header = new Node(0);

}

AVL::~AVL()

{

destroy(header->left);

delete header;

header = nullptr;

}

void AVL::insert(int key)

{

header->left = insert_real(key, header->left);

}

Node * AVL::find(int key)

{

return find_real(key, header->left);

}

void AVL::erase(int key)

{

header->left = erase_real(key, header->left);

}

void AVL::print()

{

in_order(header->left);

cout << endl;

}

int main()

{

AVL avl;

// test "insert"

avl.insert(7);

avl.insert(2);

avl.insert(1); avl.insert(1);

avl.insert(5);

avl.insert(3);

avl.insert(6);

avl.insert(4);

avl.insert(9);

avl.insert(8);

avl.insert(11); avl.insert(11);

avl.insert(10);

avl.insert(12);

avl.print(); // 1 2 3 4 5 6 7 8 9 10 11 12

// test "find"

Node * p = nullptr;

cout << ((p = avl.find(2)) ? p->key : -1) << endl;   //  2

cout << ((p = avl.find(100)) ? p->key : -1) << endl; // -1

// test "erase"

avl.erase(1);

avl.print(); // 2 3 4 5 6 7 8 9 10 11 12

avl.erase(9);

avl.print(); // 2 3 4 5 6 7 8 10 11 12

avl.erase(11);

avl.print(); // 2 3 4 5 6 7 8 10 12

return 0;

}

起初构造的 AVL 树为下图:

总结

和二叉查找树相比,AVL 树的特点是时间复杂度更稳定,但缺点也是很明显的。

插入操作中,至多需要一次恢复平衡操作,递归回溯的量级为 O(logn)。有一点需要我们注意,在对第一个失衡结点进行恢复平衡后,递归回溯就应该立即停止(因为失衡结点的父亲及其祖先们肯定都是处于平衡状态的)。

但让 "递归的回溯" 中途停止,不好实现,所以我上面的编码程序都不可避免的会继续回溯,直到整棵树的根结点,而这些回溯都是没有必要的。(谢谢 LLL 的提醒,若在结点中增设父亲结点,就可以解决递归回溯的问题)

删除操作中,若存在失衡,则至少需要一次恢复平衡操作,递归回溯的量级亦为 O(logn)。与插入操作不同,当对第一个失衡结点恢复平衡后,它的父亲或者是它的祖先们也可能是非平衡的(见下图,删除 1),所以删除操作的回溯很有必要。

没有参照物对比的探讨是没有意义的,所以此文就止于此吧,有兴趣的朋友可以看下我后面《红黑树》及《AVL 树与红黑树的对比》的文章。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • AVL
    AVL
    +关注

    关注

    0

    文章

    14

    浏览量

    10030
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12300

原文标题:一文读懂 AVL 树

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    二叉查找(GIF动图讲解)

    二叉查找(Binary Search Tree),也称二叉搜索,是指一棵空或者具有下列性质
    发表于 07-29 15:24

    二叉树层次遍历算法的验证

    实现二叉树的层次遍历算法,并对用”A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”创建的二叉树进行测试。
    发表于 11-28 01:05 2067次阅读
    <b class='flag-5'>二叉树</b>层次遍历算法的验证

    详解电源二叉树到底是什么

    作为数据结构的基础,分很多种,像 AVL 、红黑二叉搜索....今天我想分享的是关于
    的头像 发表于 06-06 15:05 9921次阅读
    详解电源<b class='flag-5'>二叉树</b>到底是什么

    PCB板设计的电源二叉树分析详细资料说明

    本文档的主要内容详细介绍的是PCB板设计的电源二叉树分析详细资料说明。
    发表于 07-29 08:00 0次下载
    PCB板设计的电源<b class='flag-5'>二叉树</b><b class='flag-5'>分析</b><b class='flag-5'>详细</b>资料说明

    C语言二叉树代码免费下载

    本文档的主要内容详细介绍的是C语言二叉树代码免费下载。
    发表于 08-27 08:00 1次下载

    红黑(Red Black Tree)是一种自平衡的二叉搜索

    平衡(Balance):就是当结点数量固定时,左右子树的高度越接近,这棵二叉树越平衡(高度越低)。而最理想的平衡就是完全二叉树/满二叉树,高度最小的二叉树
    的头像 发表于 07-01 15:05 5596次阅读
    红黑<b class='flag-5'>树</b>(Red Black Tree)是一种自平衡的<b class='flag-5'>二叉</b>搜索<b class='flag-5'>树</b>

    二叉树操作的相关知识和代码详解

    是数据结构中的重中之重,尤其以各类二叉树为学习的难点。在面试环节中,二叉树也是必考的模块。本文主要讲二叉树操作的相关知识,梳理面试常考的内容。请大家跟随小编一起来复习吧。 本篇针对面
    的头像 发表于 12-12 11:04 2000次阅读
    <b class='flag-5'>二叉树</b>操作的相关知识和代码详解

    二叉树的前序遍历非递归实现

    我们之前说了二叉树基础及二叉的几种遍历方式及练习题,今天我们来看一下二叉树的前序遍历非递归实现。 前序遍历的顺序是, 对于中的某节点,先遍历该节点,然后再遍历其左子树,最后遍历其右子
    的头像 发表于 05-28 13:59 1902次阅读

    如何修剪二叉搜索

      如果不对递归有深刻的理解,本题有点难。单纯移除一个节点那还不够,要修剪! 669. 修剪二叉搜索   给定一个二叉搜索,同时给定最小边界L 和最大边界 R。通过修剪
    的头像 发表于 10-11 14:16 1349次阅读

    二叉排序树AVL如何实现动态平衡

    熟悉的二叉树种类有二叉搜索(排序、查找)二叉平衡、伸展
    的头像 发表于 10-28 17:02 1761次阅读
    <b class='flag-5'>二叉排序树</b><b class='flag-5'>AVL</b>如何实现动态平衡

    C语言数据结构:什么是二叉树

    完全二叉树:完全二叉树是效率很高的数据结构。对于深度为K,有n个节点的二叉树,当且仅当每一个节点都与深度为K的满二叉树中编号从1至n的节点一一对应时,称为完全
    的头像 发表于 04-21 16:20 2407次阅读

    怎么就能构造成二叉树呢?

    一直跟着公众号学算法的录友 应该知道,我在二叉树:构造二叉树登场!,已经讲过,只有 中序与后序 和 中序和前序 可以确定一颗唯一的二叉树。前序和后序是不能确定唯一的二叉树的。
    的头像 发表于 07-14 11:20 1478次阅读

    使用C语言代码实现平衡二叉树

    这篇博客主要总结平衡二叉树,所以,二叉排序树知识不会提及,但是会用到。
    的头像 发表于 09-21 11:00 1025次阅读

    二叉树的代码实现

    二叉树的主要操作有遍历,例如有先序遍历、中序遍历、后序遍历。在遍历之前,就是创建一棵二叉树,当然,还需要有删除二叉树的算法。
    的头像 发表于 01-18 10:41 1187次阅读
    <b class='flag-5'>二叉树</b>的代码实现

    C++自定义二叉树并输出二叉树图形

    使用C++构建一个二叉树并输出。
    的头像 发表于 01-10 16:29 1650次阅读
    C++自定义<b class='flag-5'>二叉树</b>并输出<b class='flag-5'>二叉树</b>图形