AVL树(平衡二叉查找树)

首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树。

一、二叉查找树

1、二叉树查找树的相关特征定义

二叉树查找树,又叫二叉搜索树,是一种有顺序有规律的树结构。它可以有以下几个特征来定义它:

(1)首先它是一个二叉树,具备二叉树的所有特性,他可以有左右子节点(左右孩子),可以进行插入,删除,遍历等操作;

(2)如果根节点有左子树,则左子树上的所有节点的值均小于根节点上的值,如果根节点有右子树,则有字数上的所有节点的值均大于根节点的值;

(3)它的中序遍历是一个有序数组。

下图即是一个比较典型的二叉查找树:

2、二叉查找树的插入操作

首先,我们记住一点,二叉查找树的插入操作,插入后的节点一定为叶子节点,你可以这样想,一个刚插入的节点,它肯定没有孩子节点,这样它只能是叶子结点。这样,二叉查找树的插入操作就显得很简单,我们只要将插入的值依次和相应的根节点比较:

(1)如果根节点为空时,则创建一个节点,这个节点的值为插入的值,将该节点赋值给根节点;

(2)如果比根节点值小,则进入左子树,获得左子树的根节点;(即这个节点一定是插入左子树的某一位置)

(3)如果比根节点值大,则进入右子树,获得右子树的根节点;(即这个节点一定是插入左子树的某一位置)

例如:利用上图插入值为1的节点,我们的操作如下:

(1)首先插入的值1与根节点值5比较,比5小,则进入左子树,获得左子树的根节点

(2)将插入的值1与当前根节点值3比较,比3小,则进入左子树,获得左子树的根节点

(3)将插入的值1与当前根节点值2比较,比2小,则进入左子树,获得左子树的根节点

(4)判断发下当前根节点为null,创建值为1的节点,并将该节点赋值给当前根节点。

则插入后的二叉查找树为:

3、二叉查找树的删除操作

二叉查找树相对于插入会相对麻烦一点,但是我们大致可以将删除的情况分为以下四种情况:

(1)删除的是叶子节点,即节点没有孩子节点,这个最为简单直接将其删除就可以;

例如:我们要删除值为1的叶子节点:

(2)删除的节点只有有左孩子节点,则将左孩子节点直接代替根节点;

例如:删除节点值为2的节点

(3)删除的节点只有有右孩子节点,则将右孩子节点直接代替根节点;(跟上述(2)基本相同,这里不进行举例)

(4)删除的节点既有左孩子也有右孩子,这种情况相对复杂,我们尽量不去改动此节点上层的节点,我们去左右子树找一个与此节点的值比较相近的节点来代替这个节点即可,然后删除代替节点原本节点(而很显然这个节点一定为叶子节点)。我们采取的方法两种是:

1)在左子树中找到最大的值节点,将改值复制给根节点,然后删除左子树找到的节点;

2)在右子树中找到最小的值节点,将改值复制给根节点,然后删除右子树找到的节点;

例如:我们想要删除值为5的节点(我们采取的是1)的方式)

4、二叉查找树的中序遍历

二叉查找树的中序遍历是非常有意义的,所以这里我只介绍中序遍历,二叉查找树的所有遍历和正常的二叉树遍历没有区别。

中序遍历的执行顺序为:左 根 右,根据二叉查找树的性质,我们可以清楚知道这是一个有序数组。

例如遍历上述的树:

中序遍历的结果为:1-2-3-4-5-6-7-8

二、平衡树

1、平衡树的相关定义

平衡树相对一般树多引入了一个高度的概念,即每个节点记录了以此节点为根节点的子树的高度。然后每个节点的所有子树的高度差必须小于等于平衡值(一般为1)。

节点的高度规定:

(1)若节点为null时,节点的高度为-1;

(2)若节点不为null时,节点的高度为子树的高度的最大值+1;

以二叉平横树为例:

2、关于平横树的平衡操作

对于平衡树来说,往往是在插入和删除操作之后,导致原本的平横树失衡,这里我们不进行插入和删除操作的讲解了,而且这里我也不准备对平衡操作进行讲解,因为平衡的话我们必须要有平衡的限制条件,不然我们对于一个情况来说,我们平衡操作不唯一,价值性其实不高。我准备在平衡二叉查找树中讲解平衡操作,这样有二叉查找树的相关要求限制平衡操作,而且这种平衡会显得很意义。

三、AVL(平衡二叉查找树)

写了这么久,终于引出了AVL树了,不容易啊。

1、AVL(平衡二叉查找树)的相关特性和定义

我们可以通过二叉查找树和平衡树来定义AVL树:

(1)首先它有二叉查找树的所有特性,如果根节点有左子树,则左子树上的所有节点的值均小于根节点的值,如果根节点有右子树,则有字数上的所有节点的值均大于根节点的值。

(2)每个节点都会有一个高度值,左子树的高度值和右子树的高度值差值应该小于规定的平衡值(一般为1)。

总结在一起为:AVL树是一个带有平衡条件约束的二叉查找树。

2、AVL树的平衡操作(旋转)

首先,作为一个树形结构,它一定有插入和删除操作,之前不是说了平衡树的平衡操作是在插入删除操作之后进行的么,为什么不先将AVL树的插入和删除?AVL树的插入和删除操作和二叉查找树几乎一模一样,唯一区别就是插入操作删除操作之后会有一个平衡操作。所以在这里我们只需要讲清楚如何进行平衡操作结合前面讲解的二叉查找树的插入删除操作就可以明白AVL树的插入删除操作了。另外AVL树的遍历操作和二叉查找树一模一样。

AVL树的平衡旋转操作总共有四种情况:每次都是k1处失衡

补充:

(1)与左孩子节点单旋转(以根为基准)

(2)与右孩子节点单旋转(以根为基准)

在这里我们对单旋转进行一下总结,虽然图中给的是三个明确的节点,但事实上只有两个节点(红色圈起来的节点)进行相应的旋转,发生了相应的状态的改变,另一个节点k3是没有状态改变的。

(3)双旋转,先根的左孩子和其右孩子旋转,然后根最后与左孩子节点单旋转

(4)双旋转,先根的右孩子和其左孩子旋转,然后根最后与右孩子节点单旋转

总结:之前说单旋转只涉及到两个节点的状态发生变化,而双旋转则是三个节点状态都发生改变。其实双旋转的过程也是两个单旋转的过程,而且这两个单旋转也就是上面所说的两个单旋转的情况,比如说:情况(4),是k2和k3节点先进行了(2)旋转,转化为类似(1)情况,然后在k1和k3进行(1)旋转。

3、AVL树的相关操作的代码实现(Java)

  1 package Tree;
  2 /*
  3  * avl树是一种平衡二叉查找树,了解了avl树的相关操作,将会有利于对排序和树的知识的理解
  4  */
  5 /*
  6  * 创建avl树节点,这个树的节点有:
  7  * 1、左右子树的引用
  8  * 2、树节点存储的值
  9  * 3、该节点树高度差
 10  */
 11 class AVLTreeNode{
 12     public AVLTreeNode avlLeft;//左孩子
 13     public AVLTreeNode avlRight;//右孩子
 14     public int data;//节点存储的值
 15     public int height;//节点树高度值
 16     public AVLTreeNode(int data,AVLTreeNode avlLeft,AVLTreeNode avlRight){
 17         this.avlLeft=avlLeft;
 18         this.data=data;
 19         this.avlRight=avlRight;
 20     }
 21 }
 22
 23 public class AVLTree {
 24     private static final int ALLOWED_IMBALANCE=1;//规定了AVL树允许左右子树高度差值的最大值
 25     //用于计算当前节点的树高度差
 26     public int height(AVLTreeNode t){
 27         int mark=t==null? -1:t.height;
 28         return mark;
 29     }
 30
 31     /*
 32      * 插入操作(传入参数)
 33      * m 插入的新的节点值
 34      * t AVL树的根结点
 35      */
 36     public AVLTreeNode insert(int m,AVLTreeNode t){
 37         if(t==null){
 38            return t=new AVLTreeNode(m, null, null);
 39         }
 40         if(t.data>m){
 41             t.avlLeft=insert(m,t.avlLeft);
 42         }else if(t.data<m){
 43             t.avlRight=insert(m, t.avlRight);
 44         }else{
 45             ;//防止出现添加相同数字的现象
 46         }
 47         return balance(t);
 48     }
 49
 50     /*
 51      * AVL树的删除操作
 52      * m 要删除节点的值
 53      * t AVL树的根节点
 54      */
 55     public AVLTreeNode delete(int m,AVLTreeNode t){
 56         if(t==null){
 57             return t;
 58         }
 59         if(t.data>m){
 60             t.avlLeft=delete(m, t.avlLeft);
 61         }else if(t.data<m){
 62             t.avlRight=delete(m,t.avlRight);
 63         }else if(t.avlLeft!=null&&t.avlRight!=null){//如果左右子树非空,该如何进行删除操作
 64             //这个时候找到左子树最大的元素或右子树最小的元素来填充这个位置,在这里我选择右子树最小的
 65             t.data=findMin(t.avlRight).data;
 66             //同时我们获得右子树删除右子树上多余的值(原先的最小值)
 67             t.avlRight=delete(t.data,t.avlRight);
 68         }else{//当右子树为空或左子树为空,该如何进行删除操作,简单,左子树为空,删除操作直接将右子树的节点替代根就完事
 69             t=(t.avlLeft!=null)?t.avlLeft:t.avlRight;
 70         }
 71         return balance(t);
 72     }
 73
 74     /*
 75      * 用来查找二叉查找树种最小的元素
 76      */
 77     public AVLTreeNode findMin(AVLTreeNode t){
 78         //根据二叉查找树的特点,树的最小节点一定是在最左边的子树上,我们只需要不停的寻找它的左子树即可
 79         while(t.avlLeft!=null){
 80             t=t.avlLeft;
 81         }
 82         return t;
 83     }
 84
 85     /*
 86      * 用于平衡二叉查找树的(AVL树核心方法)
 87      */
 88     public AVLTreeNode balance(AVLTreeNode t){
 89         if(t==null)
 90             return t;
 91         if(height(t.avlLeft)-height(t.avlRight)>ALLOWED_IMBALANCE){
 92             if(height(t.avlLeft.avlLeft)>height(t.avlLeft.avlRight)){//第一种情况
 93                 t=rotateWithLeftChild(t);
 94             }else{//第三种情况
 95                 t=doubleWithLeftChild(t);
 96             }
 97         }else if(height(t.avlRight)-height(t.avlLeft)>ALLOWED_IMBALANCE){
 98             if(height(t.avlRight.avlLeft)<height(t.avlRight.avlRight)){//第二种情况
 99                 t=rotateWithRightChild(t);
100             }else{//第四种情况
101                 t=doubleWithRightChild(t);
102             }
103         }else{
104             ;//第三种就是已经平衡,不进行任何操作
105         }
106         t.height=Math.max(height(t.avlLeft), height(t.avlRight))+1;
107         return t;
108     }
109
110     /*
111      * 平衡操作需要的旋转操作
112      */
113     //与左孩子节点单旋转(以根为基准)
114     public AVLTreeNode rotateWithLeftChild(AVLTreeNode k1){
115         AVLTreeNode k2=k1.avlLeft;
116         k1.avlLeft=k2.avlRight;
117         k2.avlRight=k1;
118         k1.height=Math.max(height(k1.avlLeft), height(k1.avlRight))+1;//计算以这个节点为根的树的高度
119         k2.height=Math.max(height(k2.avlLeft), k1.height)+1;
120         return k2;
121     }
122
123     //与右孩子节点单旋转(以根为基准)
124     public AVLTreeNode rotateWithRightChild(AVLTreeNode k1){
125         AVLTreeNode k2=k1.avlRight;
126         k1.avlRight=k2.avlLeft;
127         k2.avlLeft=k1;
128         k1.height=Math.max(height(k1.avlLeft),height(k1.avlRight))+1;
129         k2.height=Math.max(height(k2.avlRight), k1.height)+1;
130         return k2;
131     }
132
133     //双旋转,先根的左孩子和其右孩子旋转,然后根最后与左孩子节点单旋转
134     public AVLTreeNode doubleWithLeftChild(AVLTreeNode k1){
135         //首先的思想是将双旋转转换为之前习惯的单旋转的情况,
136         k1.avlLeft=rotateWithRightChild(k1.avlLeft);
137         //然后通过调用一次与左孩子节点单旋转
138         AVLTreeNode k2=rotateWithLeftChild(k1);
139         return k2;
140     }
141
142     //双旋转,先根的左孩子和其右孩子旋转,然后根最后与左孩子节点单旋转
143         public AVLTreeNode doubleWithRightChild(AVLTreeNode k1){
144             //首先的思想是将双旋转转换为之前习惯的单旋转的情况,
145             k1.avlRight=rotateWithLeftChild(k1.avlRight);
146             //然后通过调用一次与左孩子节点单旋转
147             AVLTreeNode k2=rotateWithRightChild(k1);
148             return k2;
149         }
150
151
152       /*
153        * 中序遍历的方法(递归)
154        */
155         public void midTravleTree(AVLTreeNode h){
156             if(h!=null){
157                 midTravleTree(h.avlLeft);
158                 System.out.print(h.data+" ");
159                 midTravleTree(h.avlRight);
160             }
161         }
162
163         /*
164          * 测试
165          */
166
167         //public void static
168         public static void main(String[] args) {
169             AVLTree avlTree=new AVLTree();
170             AVLTreeNode t=null;
171             t=avlTree.insert(1, t);
172             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
173             t=avlTree.insert(2, t);
174             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
175             t=avlTree.insert(3, t);
176             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
177             t=avlTree.insert(4, t);
178             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
179             t=avlTree.insert(5, t);
180             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
181             t=avlTree.insert(6, t);
182             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
183             t=avlTree.insert(7, t);
184             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
185             t=avlTree.insert(8, t);
186             System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
187             //中序遍历一下avl树
188             avlTree.midTravleTree(t);
189             System.out.println();//换行
190             t=avlTree.delete(4, t);
191             System.out.println("t.height:"+t.height);//每次删除后后进行查一次树的高度
192             t=avlTree.delete(6, t);
193             System.out.println("t.height:"+t.height);//每次删除后后进行查一次树的高度
194             //中序遍历一下avl树
195             avlTree.midTravleTree(t);
196         }
197 }

运行结果:

t.height:0
t.height:1
t.height:1
t.height:2
t.height:2
t.height:2
t.height:2
t.height:3
1 2 3 4 5 6 7 8
t.height:2
t.height:2
1 2 3 5 7 8 

四、说一些自己感想

AVL树的出现,一方面为排序提供了方便,另一方面也提高了树结构的查询效率,查询的时间复杂度为O(logn)。

时间: 2024-10-15 17:18:17

AVL树(平衡二叉查找树)的相关文章

AVL树平衡旋转详解

AVL树平衡旋转详解 概述 AVL树又叫做平衡二叉树.前言部分我也有说到,AVL树的前提是二叉排序树(BST或叫做二叉查找树).由于在生成BST树的过程中可能会出现线型树结构,比如插入的顺序是:1, 2, 3, 4, 5, 6, 7..., n.在BST树中,比较理想的状况是每个子树的左子树和右子树的高度相等,此时搜索的时间复杂度是log(N).可是,一旦这棵树演化成了线型树的时候,这个理想的情况就不存在了,此时搜索的时间复杂度是O(N),在数据量很大的情况下,我们并不愿意看到这样的结果. 现在

平衡二叉搜索树(AVL树,红黑树)数据结构和区别

平衡二叉搜索树(Balanced Binary Search Tree) 经典常见的自平衡的二叉搜索树(Self-balancing Binary Search Tree)有 ① AVL树 :Windows NT 内核中广泛使用 ② 红黑树:C++ STL(比如 map.set )Java 的 TreeMap.TreeSet.HashMap.HashSet  Linux 的进程调度  Ngix 的 timer 管理 1 AVL树  vs  红黑树 ①AVL树 平衡标准比较严格:每个左右子树的高度

Linux内核之于红黑树and AVL树

为什么Linux早先使用AVL树而后来倾向于红黑树?       实际上这是由红黑树的实用主义特质导致的结果,本短文依然是形而上的观点.红黑树可以直接由2-3树导出,我们可以不再提红黑树,而只提2-3树,因为 2-3树的操作太简单.另外,任何红黑树的操作和特性都可以映射到2-3树中.因此红黑树和AVL树的比较就成了2-3树和AVL树的比较. 它们俩的区别在哪?2-3树的平衡是完美平衡的,但是树杈数量却可以是3个,而AVL树差一点点就完美平衡的标准二叉树,它只允许子树的高度差最多为1. 可见这么看

算法学习 - 平衡二叉查找树实现(AVL树)

平衡二叉查找树 平衡二叉查找树是很早出现的平衡树,因为所有子树的高度差不超过1,所以操作平均为O(logN). 平衡二叉查找树和BS树很像,插入和删除操作也基本一样,但是每个节点多了一个高度的信息,在每次插入之后都要更新树的每个节点的高度,发现不平衡之后就要进行旋转. 单旋转 单旋转是碰到左左或者右右的情况下所使用的方法. 例如: 3 2 1 这种情况就需要旋转,因为3是根节点,它的左子树高度为0,右子树高度为2,相差超过1了,所以要进行旋转,而这是右右的情况,所以是单旋转. 2 / 1 3 这

平衡二叉查找树——AVL树

二叉查找树在最坏情况下高度可能为N-1,即插入元素时后插入的元素总比以前插入的元素大或者小.为了解决这种不平衡的情况,引入了平衡条件来限制树中节点的深度不能过深,其中最老的一种平衡树称为AVL树.这种树限制树中每个节点的左右子树的高度相差不能超过一.(另一种更严格的树限制节点的左右子树高度必须相等,但这样的树要求树中的节点数目为2的k次幂减1,是一种理想平衡树,但是要求太严格,无法实际使用.) AVL树平衡条件分析 AVL树是一棵特殊的二叉查找树,对AVL树的操作中,除了插入操作与普通二叉查找树

二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree)的比较

http://www.iteye.com/topic/614070 此少侠总结的特棒,直接收藏了. 我们这个专题介绍的动态查找树主要有: 二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree).这四种树都具备下面几个优势: (1) 都是动态结构.在删除,插入操作的时候,都不需要彻底重建原始的索引树.最多就是执行一定量的旋转,变色操作来有限的改变树的形态.而这些操作所付出的代价都远远小于重建一棵树.这一优势在<查找结构专题(1):静态查找结构概论 >中讲到

AVL树-自平衡二叉查找树(Java实现)

在计算机科学中,AVL树是最先发明的自平衡二叉查找树.AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 "An algorithm for the organization of information" 中发表了它. 一.AVL树的旋转规律 AVL树的基本操作一般涉及运做同在不平衡的二叉查找树所运做的同样的算法.但是要进行预先或随后做一次或多次所谓的"AVL旋转". 假设由于在二叉排序树上插入

[重温数据结构]一种自平衡二叉查找树avl树的实现方法

最近重温<数据结构>,发现其中的东西我上学的时候几乎都没写过... 惭愧啊.于是打算一一写点.算是对我逝去青春的纪念... avl树的代码网上有很多, 这个在平衡二叉树里面算是比较简单的. 需要注意的就是树的平衡因子和其子树的平衡因子符号相反的旋转情况.这个在函数avl_rotate_right等中有处理. 目前只做了插入,删除等哪天想做了再做吧. avl.h 1 #define INSERT_ERROR -1 2 #define INSERT_OK 1 3 4 #define LEFT_LE

平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】

平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是将有向树的所有边看成无向边形成的树状图.树是一种递归的数据结构,所以我们研究树也是按照递归的方式去研究的. 2.什么是二叉树. 我们给出二叉树的递归定义如下: (1)空树是一个二叉树. (2)单个节点是一个二叉树. (3)如果一棵树中,以它的左右子节点为根形成的子树都是二叉树,那么这棵树本身也是二叉