平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。

首先我们知道,当插入一个节点,从此插入点到树根节点路径上的所有节点的平衡都可能被打破,如何解决这个问题呢?

这里不讲大多数书上提的什么平衡因子,什么最小不平衡子树,实际上让人(me)更加费解。实际上你首要做的就是先找到第一个出现不平衡的节点,也就是从插入点到root节点的路径上第一个出现不平衡的节点,即深度最深的那个节点A,对以它为根的子树做一次旋转或者两次旋转,此时这个节点的平衡问题解决了,整个往上路径经过的节点平衡问题也随之解决。

注:AVL 树也是一种二叉查找树,故删除策略可以参照前面文章来实现,只是删除节点后,如果平衡被打破,则也需要进行旋转以保持平衡。

After
deletion, retrace the path back up the tree (parent of the replacement)
to the root, adjusting the balance factors as needed.

下面先来分析下不平衡发生的四种情况:

1、An insertion into the left subtree of the left child of A; (LL)

2、An insertion into the right
subtree of the left child of A;(RL)

3、An
insertion into the left subtree of the
right child of A;(LR)

4、An
insertion into theright  child of A;(RR)

旋转方法:

1、A 和 A's child 顺时针旋转 singlerotateLL()

4、A 和 A's child 逆时针旋转 singlerotateRR()

2、A's child 和 A's grandchild 逆时针旋转,A 和 A's new child 顺时针旋转  doublerotateRL()

3、A's child 和 A's grandchild 顺时针旋转,A 和 A's
new child 逆时针旋转 doublerotateLR()

可以看出1,4对称;2,3对称,实际在实现 rotate 函数的时候实现1和4 即可,2和3直接调用1&4的实现。在实现1&4时需要传递需要旋转的子树的root
node 作为参数,如 nodePtr doublerotateRL(A) { A->left = singlerotateRR(A->left);  return singlerotateLL(A);}

当然,这样说还是对应不上,下面上图分析。

第一种情况举例:

现在想要插入的点是6,请看是否符合第一种情况的描述。8是不是深度最深的发生不平衡的点?6是不是插入在A的左孩子的左子树?符合是吧,那就直接按上述方法顺时针旋转7和8,效果是右图。当然这只是逻辑上的视图而已,真正函数实现也不难,就是修改两个指针指向,待会再谈。第4种情况是对称的,就不说了。

下面来看第三种情况示例:

现在要插入的点是14,请看是否符合第3种情况的描述。6是不是深度最深的发生不平衡的点?14是不是插入在A的右孩子的左子树?符合是吧,那就先顺时针旋转7和15,中间结果如下图所示:

现在7是A的new child了是吧,那就逆时针旋转6和7就可以了。

接着来分析singlerotateLL() 和doublerotateRL() 的实现,剩下两个函数就是对称的了。

首先是singlerotateLL(),看下图:

改动其实很简单,先把Y解绑当k2的左孩子,接着k2成为k1的右孩子,代码如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
 
/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2)
{
    AVLNodePtr k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = Max(height(k2->left), height(k2->right)) + 1;
    k1->height = Max(height(k1->left), k2->height) + 1;
    return k1;
}

接着是doublerotateRL()的实现,看下图:

很明显B或者C的插入使得k3(A)不平衡了,那么首先应该是k1和k2逆时针旋转,所以调用

k3->left = singlerotateRR(k3->left);

接着是k3和new child 顺时针旋转,调用singlerotateLL(k3);

so easy, 代码如下:

 C++ Code 
1
2
3
4
5
 
static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{
    k3->left = singlerotateRR(k3->left);
    return singlerotateLL(k3);
}

完整的测试代码如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
 
#include <stdio.h>
#include <stdlib.h>

struct AVLNode;
typedef struct AVLNode *AVLNodePtr;

struct AVLNode
{
    int element;
    AVLNodePtr left;
    AVLNodePtr right;
    int height;
};

void makeempty(AVLNodePtr T)
{
    if (T == NULL)
        return;
    else
    {
        makeempty(T->left);
        makeempty(T->right);
        free(T);
    }
}

static int height(AVLNodePtr p)
{
    if (p == NULL)
        return -1;
    else
        return p->height;
}

static int Max(int ln, int rn)
{
    return ln > rn ? ln : rn;
}

/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2)
{
    AVLNodePtr k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = Max(height(k2->left), height(k2->right)) + 1;
    k1->height = Max(height(k1->left), k2->height) + 1;
    return k1;
}

static AVLNodePtr singlerotateRR(AVLNodePtr k1)
{
    AVLNodePtr k2 = k1->right;
    k1->right = k2->left;
    k2->left = k1;
    k1->height = Max(height(k1->left), height(k1->right)) + 1;
    k2->height = Max(k1->height, height(k2->right)) + 1;
    return k2;
}

static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{
    k3->left = singlerotateRR(k3->left);
    return singlerotateLL(k3);
}

static AVLNodePtr doublerotateLR(AVLNodePtr k3)
{
    k3->right = singlerotateLL(k3->right);
    return singlerotateRR(k3);
}

AVLNodePtr insert(int X, AVLNodePtr T)
{
    if (T == NULL)
    {
        /* create and return a one-node tree */
        T = (AVLNodePtr)malloc(sizeof(struct AVLNode));
        if (T == NULL)
        {
            printf("out of space!");
            exit(1);
        }
        else
        {
            T->element = X;
            T->height = 0;
            T->left = T->right = NULL;
        }
    }

else if (X < T->element)
    {
        T->left = insert(X, T->left);
        if (height(T->left) - height(T->right) == 2)
        {
            if (X < T->left->element)
                T = singlerotateLL(T);
            else
                T = doublerotateRL(T);
        }
    }

else if (X > T->element)
    {
        T->right = insert(X, T->right);
        if (height(T->right) - height(T->left) == 2)
        {
            if (X > T->right->element)
                T = singlerotateRR(T);
            else
                T = doublerotateLR(T);
        }
    }
    /* else X is in the tree already; we'll do nothing */
    T->height = Max(height(T->left), height(T->right)) + 1;
    return T;
}

void inorder(AVLNodePtr T)
{
    if (T == NULL)
        return;
    else
    {
        inorder(T->left);
        printf("%d ", T->element);
        inorder(T->right);
    }
}

int main(void)
{
    int arr[] = {3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8, 9};
    AVLNodePtr T = NULL;
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
        T = insert(arr[i], T);

inorder(T);
    makeempty(T);
    return 0;
}

代码将数组元素插入后,中序遍历后输出,即1~16的顺序排列。

注意:输入数组元素就不要搞成有序的了,如果代码中没有调整的实现,整个树就是个右斜树,但即使实现了调整,也会使得每插入一次就调整一次,何必内耗啊。很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

注:理解旋转函数的实现关键在于理解实现时的限制:

The
key to understanding how a rotation functions
is to understand its constraints. In particular the order of the leaves
of the tree (when read left to right for example) cannot change
(another way to think of it is that the order that the leaves would be
visited in a depth first search must be the same
after the operation as before ).

Another constraint is the main property of
a binary search tree, namely that the right child is greater than the parent and the left child is lesser than the parent.

Detailed illustration:


Using the terminology of Root for
the parent node of the subtrees to rotate, Pivot for the node which will become the new parent node,RS for rotation side upon to rotate and OS for opposite side of rotation. In the above diagram for the root
Q, the RS is C and the OS is P. The pseudo code for the rotation is:

参考:

《data structure and algorithm analysis in c》

《Data Structures》

平衡二叉树AVL - 插入节点后旋转方法分析的更多相关文章

  1. 平衡二叉树AVL插入

    平衡二叉树(Balancedbinary tree)是由阿德尔森-维尔斯和兰迪斯(Adelson-Velskiiand Landis)于1962年首先提出的,所以又称为AVL树. 定义:平衡二叉树或为 ...

  2. JQ 添加节点和插入节点的方法总结

    转载来源:http://blog.csdn.net/ss1106404013/article/details/49274345 添加节点的jQuery方法: append().prepend().ap ...

  3. K:平衡二叉树(AVL)

    相关介绍:  二叉查找树的查找效率与二叉树的形状有关,对于按给定序列建立的二叉排序树,若其左.右子树均匀分布,则查找过程类似于有序表的二分查找,时间复杂度变为O(log2n).当若给定序列原来有序,则 ...

  4. 平衡二叉树,AVL树之图解篇

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  5. 图解:平衡二叉树,AVL树

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  6. 插入节点insertBefore()

    http://www.imooc.com/code/1699 插入节点insertBefore() insertBefore() 方法可在已有的子节点前插入一个新的子节点. 语法: insertBef ...

  7. js进阶 11-9/10/11 jquery创建和插入节点

    js进阶 11-9/10/11 jquery创建和插入节点 一.总结 一句话总结: 1.jquery插入节点8个方法? 内部之前,内部之后,之前,之后:各两个 append()和appendTo() ...

  8. 【算法】论平衡二叉树(AVL)的正确种植方法

    参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>       ...

  9. AVL树的插入操作(旋转)图解

    =================================================================== AVL树的概念       在说AVL树的概念之前,我们需要清楚 ...

随机推荐

  1. Java主流Web Service框架介绍:CXF和Axis2

    CXF和Axis2是目前java平台上最主流的两个框架,虽然两个项目都隶属ASF,但却是基于不同思想和风格实现的,因此也各有所长.   CXF:http://cxf.apache.org/ 是由过去的 ...

  2. Android之批量加载图片OOM问题解决方案

    一.OOM问题出现的场景和原因 一个好的app总少不了精美的图片,所以Android开发中图片的加载总是避免不了的,而在加载图片过程中,如果处理不当则会出现OOM的问题.那么如何彻底解决这个问题呢?本 ...

  3. Android Notification实现推送消息过程中接受到消息端有声音及震动及亮屏提示

    在Android Notification状态栏通知一文中,简单实现了消息的推送效果,这里就接着上文说一下,当用户接受到消息时的提示效果 // 5-增加震动及声音及亮屏 notification.de ...

  4. CSDN问答频道“华章杯”11月排行榜活动开始,丰厚奖品等你拿

    CSDN问答频道月度排行榜,是CSDN问答频道从3月开始举办的活动,旨在鼓励更多用户参与提问和解答,创造一个良好的互帮互助氛围,使参与者在问和答的过程中得到技术水平的提升,也希望大家能在技术交流中结交 ...

  5. 2014年ENVI/IDL遥感应用与开发培训班-11月重庆站 開始报名了

    主办单位: 中国遥感应用协会 Esri中国信息技术有限公司 内容简单介绍: 依据中国遥感应用协会栾恩杰理事长推动国内遥感技术和应用的指示精神,2014年中国遥感应用协会组织培训交流部与Esri中国信息 ...

  6. Android 之 AndroidManifest.xml 详解(一)

    当Android启动一个应用程序组件之前,它必须知道哪些个组件是存在的,所以开发人员在开发过程中,必须将应用程序中出现的组件一一在AndroidManifest.xml文件中" 声明 &qu ...

  7. IDEA+Gradle相关资料

    要注意下载的版本,不能过于陈旧,否则会报错,导致无法正常配置. 具体配置参考:https://my.oschina.net/u/1994816/blog/297967 IDEA下载地址:https:/ ...

  8. 从零开始学JavaScript三(变量)

    一.变量 ECMAscript变量是松散型变量,所谓松散型变量,就是变量名称可以保存任何类型的数据,每个变量仅仅是一个用于保存值的占位符. 定义变量时要使用var操作符 如: var message; ...

  9. Docker 安装使用

    1:安装 在Linux上安装Docker,要求64位操作系统,并且内核版本需求为3.10以上,查看本机Linux内核版本: [root@localhost ~]# uname -r -.el7.x86 ...

  10. Android:子线程向UI主线程发送消息

    在Android里,UI线程是不同意被堵塞的.因此我们要将耗时的工作放到子线程中去处理. 那么子线程耗时处理后要如何通知UI线程呢? 我们能够在UI主线程中创建一个handler对象,然后通过重写其h ...