Hexo

点滴积累 豁达处之

0%

08 数据结构-AVL

AVL树本质还是一棵二叉查找树

1 概述

平衡二叉树是由前苏联的两位数学家G.M.Adelse-Velskil和E.M.Landis提出,因此一般也称作AVL树,AVL树本质还是一棵二叉查找树,只是在其基础上增加了“平衡”的要求。所谓平衡是指,对AVL树的任意结点来说,其左子树与右子树的高度之差的绝对值不超过1,其中左子树与右子树的高度因子之差称为平衡因子。

如下所示,就是一棵由{1,2,3,4,5,7,8}构建的AVL树:

Structure_AVL1

只要能随时保证每个结点平衡因子的绝对值不超过1,AVL的高度就始终能保持O(logn)级别,由于需要对每个结点都得到平衡因子,因此需要在树的结构中加入一个变量height来记录以当前结点为根结点的子树的高度。

2 创建

​ AVL树的创建是基于二叉查找树的插入代码的基础上,增加平衡操作的。需要从插入的结点开始从下往上判断结点是否失衡,因此,需要在调用insert函数以后,更新当前子树的高度,并在这之后根据树型来进行相应的平衡操作。那么,怎么进行平衡操作呢?AVL树的插入是需要采取左旋或者右旋操作的,即,插入后,由于插入操作,导致某棵子树的高度超过了另一棵子树高度的2个结点高度,这样就破坏了树的平衡性,需要做出调整。

2.1 右旋

如下所示一棵简单的AVL树,对其进行插入操作以后:

Structure_AVLright1

一棵简单的AVL树, 变成了下图这样的AVL树:

Structure_AVLtreeright2

​ 这样子就失衡了,所谓右旋操作,就是将这棵AVL树,从最靠近插入结点的失衡结点处,通过往右子树调整,使整棵树的每个结点的平衡因子变为正常,不如上图的树,离插入节点3最近的失衡结点是7,

则可以通过下图所示的操作,来平衡二叉树,即调整整棵树平衡因子:

Structure_AVLtreeright3

因此,还需要进行一次旋转,显然,继续右旋已经无法满足我们的需求,那么要如何进行操作,才能使这棵树回复平衡呢?(在后续中,会进行讲解)

2.2 左旋操作

左旋操作与右旋操作是类似的,都属于对子树的单旋转。

Structure_AVLtreeleft1

左旋与右旋一样,同样也存在这样的问题,如果该树的右子树的左结点存在,则单一通过左旋是做不到的,那么应该如何处理呢?

其实,以L和R来表示,插入结点的位置,有以下四种情况:

插入方式 描述 旋转方式
LL 在a的左子树根节点的左子树上插入节点而破坏平衡 右旋转
RR 在a的右子树根节点的右子树上插入节点而破坏平衡 左旋转
LR 在a的左子树根节点的右子树上插入节点而破坏平衡 先左旋后右旋
RL 在a的右子树根节点的左子树上插入节点而破坏平衡 先右旋后左旋

从上表可以看出,左旋和右旋两种情况中,左右结点若存在的话,就是上表中的RL和LR情况。则,只需要对两种情况分别按照上表采取相应的操作就可以解决,如下图所示:

Structure_AVLtree6

3 AVL代码示例

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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//节点类
class Node{
int value;
Node left;
Node right;

public Node(int value) {
this.value = value;
}

//返回左子树的高度
public int leftHeight(){
if(left == null){
return 0;
}
return left.height();
}

//返回右子树的高度
public int rightHeight(){
if(right == null){
return 0;
}
return right.height();
}

//返回当以该节点为根节点的树的高度
public int height(){
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height() ) + 1;
}

//左旋转
private void leftRotate(){
//创建新的节点,以当前根节点的值
Node newNode = new Node(value);
//把新的节点的左子树设置成当前节点的左子树
newNode.left = left;
//把新的节点的右子树设置成当前节点右子树的左子树
newNode.right = right.left;
//把当前节点的值替换成右子节点的值
value = right.value;
//把当前节点的右子树设置成当前节点右子树的右子树
right = right.right;
//把当前节点的左子树设置成新的节点
left = newNode;
}

//右旋转
public void rightRotate(){
Node newNode = new Node(value);
newNode.right = right;
newNode.left = left.right;
value = left.value;
left = left.left;
right = newNode;
}


@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}

//添加,递归形式添加
public void add(Node node){
if(node == null){
return;
}
//判断传入的节点的值,和当前子树的根节点的关系
if(node.value < this.value){
//如果当前节点左子节点为null
if(this.left == null){
this.left = node;
}else{
//递归的向左子树添加
this.left.add(node);
}
}else{
//如果当前节点右子节点为null
if(this.right == null){
this.right = node;
}else{
//递归的向右子树添加
this.right.add(node);
}
}

//当添加完节点后,如果(右子树的高度-左子树的高度) > 1
if(rightHeight() - leftHeight() > 1){
if(right != null && right.leftHeight() > left.rightHeight()){
//先对当前节点的左节点进行左旋转
right.rightRotate();
}
leftRotate();
}

//当添加完节点后,如果(左子树的高度-右子树的高度) > 1
if(leftHeight() - rightHeight() > 1){
//如果它的左子树的右子树高度大于它的左子树的高度
if(left != null && left.rightHeight() > left.leftHeight()){
//先对当前节点的左节点进行左旋转
left.leftRotate();
}
//对当前节点进行右旋转
rightRotate();
}
}

//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}

//查找要删除的节点

/**
*
* @param value
* @return
*/
public Node search(int value){
if(value == this.value){
return this;
}else if(value < this.value){
if(this.left != null){
return this.left.search(value);
}
}else{
if(this.right != null){
return this.right.search(value);
}
}
return null;
}

//查找要删除节点的父节点
public Node searchParent(int value){
//如果当前节点就是要删除的节点的父节点
if(this.left != null && this.left.value == value || (this.right != null && this.right.value == value)){
return this;
}else {
//如果查找的值小与当前节点的值, 并且左子节点不为null
if(value < this.value && this.left != null){
return this.left.searchParent(value); //左递归
}else if(value >= this.value && this.right != null){
return this.right.searchParent(value); //右递归
}else{
return null; // 没有找到父节点
}
}
}
}
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
//创建avltree
class AVLTree{
private Node root;

public Node getRoot() {
return root;
}

public void setRoot(Node root) {
this.root = root;
}

//添加
public void add(Node node){
if(root == null){
root = node;
}else{
root.add(node);
}
}


//
public void infixOrder(){
if(root != null){
root.infixOrder();
}else{
System.out.println("数为null");
}
}

//查找要删除的节点
public Node search(int value){
if(null == root){
return null;
}else{
return root.search(value);
}
}

//查找父节点
public Node searchParent(int value){
if(null == root){
return null;
}else{
return root.searchParent(value);
}
}

//1, 返回以node 为根节点的二叉树的最小节点的值
//2,删除node 为根节点的二叉树的最小节点
/**
*
* @param node 传入的节点
* @return 返回的以node为根节点的二叉排序树的最小节点的值
*/
public int delRightTreeMin(Node node){
Node target = node;
while(target.left != null){
target = target.left;
}
//这时target就指向了最小节点
//删除最小节点
delNode(target.value);

return target.value;
}

//删除节点
public void delNode(int value){
if(null == root){
return ;
}else{
//1,先找到需要删除的节点,targeNode
Node targetNode = search(value);
if(null == targetNode){
return ;
}

//如果targetNode没有父节点
if(null == root.left && null == root.right){
root = null;
return ;
}

//去找targetNode 的父节点
Node parent = searchParent(value);

if(null == targetNode.left && null == targetNode.right){ //叶子节点
//判断targetNode是父节点的左子节点还是右子节点
if(parent.left != null && parent.left.value == value){ //左子节点
parent.left = null;
}else if(parent.right != null && parent.right.value == value){ //右子节点
parent.right = null;
}
}else if(targetNode.left != null && targetNode.right != null){ // 删除有两颗子树的节点
int minValue = delRightTreeMin(targetNode.right);
targetNode.value = minValue;
}else{ // 删除有一颗子树的节点
//若果要删除的节点有左子节点
if(targetNode.left != null){
//targetNode是parent的左子节点
if(parent.left.value == value){
parent.left = targetNode.left;
}else{
parent.right = targetNode.right;
}
}else{ // 如果要删除的节点有右子节点
if(parent.left.value == value){
parent.left = targetNode.right;
}else{
parent.right = targetNode.right;
}
}

}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//测试
public class AVLTreeDemo {
public static void main(String[] args) {
// int[] arr = {4,3,6,5,7,8};
// int[] arr = {10,12,8,9,7,6};
int[] arr = {10,11,7,6,8,9};

//创建avltree
AVLTree avlTree = new AVLTree();
for(int i = 0; i < arr.length; i++){
avlTree.add(new Node(arr[i]));
}

System.out.println("树的高度 " + avlTree.getRoot().height());
System.out.println("树的左子树高度 " + avlTree.getRoot().leftHeight());
System.out.println("树的右子树高度 " + avlTree.getRoot().rightHeight());

avlTree.infixOrder();
// System.out.println(avlTree.getRoot().right.right);
}
}