AVL树基本结构

AVL树是通过 balance factor 平衡因子来控制树的高度不会失衡的,每个节点的 balance factor 定义为右子树的高度减去左子树的高度,如果是平衡的,那么 balance factor 的绝对值不会超过1,如果超过了1,那么就需要进行旋转操作来保持平衡。

在维护AVL树的时候,基本操作只有 左旋 he 右旋 两种,其示意图如下:
左旋右旋示意图
不难发现,左旋和右旋都有保证树的高度不变的性质。

其中左旋的代码如下:

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
void AVL::roteLeft(Node *A, Node *C) {
#ifdef DEBUG
cout << "Rote left \n";
#endif
if (A == this->root) {
//处理A是根节点的情况
Node *B = A->leftChild;
Node *D = C->leftChild;
Node *E = C->rightChild;
//开始重新连接
C->leftChild = A;
C->rightChild = E;
A->leftChild = B;
A->rightChild = D;
//重新设立父节点
A->parent = C;
C->parent = nullptr;
if (B != nullptr)
B->parent = A;
if (D != nullptr)
D->parent = A;
if (E != nullptr)
E->parent = C;
this->root = C;
} else {
Node *parent = A->parent;
bool isLeft = (A == parent->leftChild);
Node *B = A->leftChild;
Node *D = C->leftChild;
Node *E = C->rightChild;
//开始重新连接
C->leftChild = A;
C->rightChild = E;
A->leftChild = B;
A->rightChild = D;
//重新设立父节点
A->parent = C;
C->parent = parent;
if (B != nullptr)
B->parent = A;
if (D != nullptr)
D->parent = A;
if (E != nullptr)
E->parent = C;
//连接上一个父节点
if (isLeft) {
parent->leftChild = C;
} else {
parent->rightChild = C;
}
}
}

右旋代码如下:

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
void AVL::roteRight(Node *A, Node *B) {
#ifdef DEBUG
cout << "Rote right \n";
#endif
if (A == this->root) {
//处理A是根节点
Node *D = B->leftChild;
Node *E = B->rightChild;
Node *C = A->rightChild;
//重新连接
B->leftChild = D;
B->rightChild = A;
A->leftChild = E;
A->rightChild = C;
//重新设立父节点的连接
A->parent = B;
B->parent = nullptr;
if (C != nullptr)
C->parent = A;
if (D != nullptr)
D->parent = B;
if (E != nullptr)
E->parent = A;
this->root = B;
} else {
Node *parent = A->parent;
bool isLeft = (A == parent->leftChild);
Node *D = B->leftChild;
Node *E = B->rightChild;
Node *C = A->rightChild;
//重新连接
B->leftChild = D;
B->rightChild = A;
A->leftChild = E;
A->rightChild = C;
//重新设立父节点的连接
A->parent = B;
B->parent = parent;
if (C != nullptr)
C->parent = A;
if (D != nullptr)
D->parent = B;
if (E != nullptr)
E->parent = A;
if (isLeft) {
parent->leftChild = B;
} else {
parent->rightChild = B;
}
}
}

AVL树的插入和维护

插入流程

AVL树在插入的过程中和二叉搜索树是一致的,都是需要找到中序遍历的前驱,然后在叶子结点出插入,但是由于插入之后,树的形态可能发生改变,需要根据balance factor进行对应的修正。

插入后的维护

首先,证明每次插入最多只需要旋转一次使得树重新获得平衡:

假设在节点插入之前树是满足AVL的性质的,在插入一个叶子结点之后,假设存在一个节点A,使得A在旋转调整之后,树重新满足AVL的平衡性质。使用反证法证明这件事,假设存在A和A’两个节点需要旋转,容易知道A和A’至少有一个在另一个的子树中(插入叶子节点只会 影响插入路径上的点),不妨假设A’在A的子树中,那么在A’经过旋转之后,子树的高度不变,回复平衡,A的平衡因子不再发生变化,那么就无需进行旋转了。

根据上述证明,可以想到,如果想要不使用递归找到这个点,关键就是需要找到在插入路径上面最后一个bf非0的节点,只有这个节点附近才需要调整。代码如下:

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
Node *cur = this->root;
Node *parentOfCur = nullptr;
Node *lastNonZero = nullptr;
//搜索插入节点的位置
while (cur != nullptr) {
if (cur->bf != 0) {
lastNonZero = cur;
}
if (cur->value < val) {
parentOfCur = cur;
cur = cur->rightChild;
} else if (cur->value == val) {
return false;
} else {
parentOfCur = cur;
cur = cur->leftChild;
}
}
if (parentOfCur->value > val) {
parentOfCur->leftChild = insertNode;
insertNode->parent = parentOfCur;
} else {
parentOfCur->rightChild = insertNode;
insertNode->parent = parentOfCur;
}

这里通过迭代的方式,从根节点开始遍历,搜索插入节点的位置,并且实时更新最后一个bf非0的节点

找到bf非0的节点之后,需要根据这个节点和他的左右儿子的bf,选择相应的调整方式,具体如下(此处的bf是插入节点后重新计算的bf):

  • bf = 0:直接返回无需调整
  • bf = 1/-1:写错了,建议检查一下
  • bf = 2:
    • 右子节点bf = 1:左单旋一次,两个节点的bf重新调整成0
    • 右子节点bf = -1:右左双旋 注意,这里的bf需要根据右子节点的左子结点的bf分三类讨论
    • 右子节点bf = 0: 左单旋一次,bf调整为一个1一个-1
  • bf = -2:
    • 左子节点bf = -1:右单旋一次,两个节点的bf重新调整成0
    • 左子节点bf = 1:左右双旋 注意,这里的bf需要根据左子节点的右子结点的bf分三类讨论
    • 左子节点bf = 0: 右单旋一次,bf调整为一个1一个-1

具体代码如下:

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
if (lastNonZero->bf == 2) {
//向右侧插入

if (lastNonZero->rightChild->bf == 1) {
Node *A = lastNonZero;
Node *C = lastNonZero->rightChild;
roteLeft(lastNonZero, lastNonZero->rightChild);
//调整bf
A->bf = 0;
C->bf = 0;
} else if (lastNonZero->rightChild->bf == -1) {
Node *A = lastNonZero;
Node *C = A->rightChild;
Node *D = C->leftChild;
roteRight(C, D);
roteLeft(A, D);
if (D->bf == 0) {
A->bf = D->bf = C->bf = 0;
} else if (D->bf == 1) {
A->bf = -1;
D->bf = C->bf = 0;
} else if (D->bf == -1) {
C->bf = 1;
A->bf = D->bf = 0;
}
} else {
Node *A = lastNonZero;
Node *C = lastNonZero->rightChild;
roteLeft(lastNonZero, lastNonZero->rightChild);
//调整bf
A->bf = 1;
C->bf = -1;
}
} else if (lastNonZero->bf == -2) {
if (lastNonZero->leftChild->bf == -1) {
Node *A = lastNonZero;
Node *B = A->leftChild;
roteRight(A, B);
A->bf = 0;
B->bf = 0;
} else if (lastNonZero->leftChild->bf == 1) {
Node *A = lastNonZero;
Node *B = A->leftChild;
Node *E = B->rightChild;
roteLeft(B, E);
roteRight(A, E);
if (E->bf == 0) {
A->bf = B->bf = E->bf = 0;
} else if (E->bf == 1) {
E->bf = A->bf = 0;
B->bf = -1;
} else if (E->bf == -1) {
A->bf = 1;
B->bf = 0;
E->bf = 0;
} else throw Exception("Not a valid bf", Exception::CANNOT_REACH_HERE);
} else {
Node *A = lastNonZero;
Node *B = A->leftChild;
roteRight(A, B);
A->bf = -1;
B->bf = 1;
}
}

这里一定要仔细,虽然左右看起来是对称的,但是这个对称不是想象中那么简单的,还得自己推一遍

插入部分完整代码

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
bool AVL::Insert(int val) {
Node *insertNode = new Node(val);
//处理空树的情况
if (this->root == nullptr) {
this->root = insertNode;
return true;
} else {
Node *cur = this->root;
Node *parentOfCur = nullptr;
Node *lastNonZero = nullptr;
//搜索插入节点的位置
while (cur != nullptr) {
if (cur->bf != 0) {
lastNonZero = cur;
}
if (cur->value < val) {
parentOfCur = cur;
cur = cur->rightChild;
} else if (cur->value == val) {
return false;
} else {
parentOfCur = cur;
cur = cur->leftChild;
}
}
if (parentOfCur->value > val) {
parentOfCur->leftChild = insertNode;
insertNode->parent = parentOfCur;
} else {
parentOfCur->rightChild = insertNode;
insertNode->parent = parentOfCur;
}
//开始判断bf的调整
if (lastNonZero == nullptr) {
// cout << "Modify on root \n";
cur = this->root;
lastNonZero = root;
} else {
// cout << "Modify on node " << lastNonZero->value << endl;
// lastNonZero->print();
cur = lastNonZero;
}
while (cur != nullptr) {
if (cur->value < val) {
cur->bf++;
cur = cur->rightChild;
} else if (cur->value == val) {
//注意这里应该是搜索到了插入的节点
break;
} else {
cur->bf--;
cur = cur->leftChild;
}
}
// cout << "The bf of last none zero is " << lastNonZero->bf << endl;
if (lastNonZero->bf == 2) {
//向右侧插入

if (lastNonZero->rightChild->bf == 1) {
Node *A = lastNonZero;
Node *C = lastNonZero->rightChild;
roteLeft(lastNonZero, lastNonZero->rightChild);
//调整bf
A->bf = 0;
C->bf = 0;
} else if (lastNonZero->rightChild->bf == -1) {
Node *A = lastNonZero;
Node *C = A->rightChild;
Node *D = C->leftChild;
roteRight(C, D);
roteLeft(A, D);
if (D->bf == 0) {
A->bf = D->bf = C->bf = 0;
} else if (D->bf == 1) {
A->bf = -1;
D->bf = C->bf = 0;
} else if (D->bf == -1) {
C->bf = 1;
A->bf = D->bf = 0;
}
} else {
Node *A = lastNonZero;
Node *C = lastNonZero->rightChild;
roteLeft(lastNonZero, lastNonZero->rightChild);
//调整bf
A->bf = 1;
C->bf = -1;
}
} else if (lastNonZero->bf == -2) {
if (lastNonZero->leftChild->bf == -1) {
Node *A = lastNonZero;
Node *B = A->leftChild;
roteRight(A, B);
A->bf = 0;
B->bf = 0;
} else if (lastNonZero->leftChild->bf == 1) {
Node *A = lastNonZero;
Node *B = A->leftChild;
Node *E = B->rightChild;
roteLeft(B, E);
roteRight(A, E);
if (E->bf == 0) {
A->bf = B->bf = E->bf = 0;
} else if (E->bf == 1) {
E->bf = A->bf = 0;
B->bf = -1;
} else if (E->bf == -1) {
A->bf = 1;
B->bf = 0;
E->bf = 0;
} else throw Exception("Not a valid bf", Exception::CANNOT_REACH_HERE);
} else {
Node *A = lastNonZero;
Node *B = A->leftChild;
roteRight(A, B);
A->bf = -1;
B->bf = 1;
}
}
}
return true;
}

AVL树的删除和维护

删除一个节点比插入要更为复杂,具体来说,需要找到一个高度变矮的最小子树,逻辑如下:

  • 第一次从根节点出发,找到在数值上等于被删除的节点的位置
    • 如果这个节点没有子节点或者子节点只有一个,那么这个节点就是要删除的节点
    • 如果这个节点有两个子节点,那么就需要寻找中序下面的直接前驱,即向左走一步然后一直向右走。找到前驱之后,实际删除的节点是它的前驱,然后把他的前驱的值放到原来的节点上
  • 第二次倒过来走,由于删除一个节点,它自己肯定变矮,所以倒过来判断,可以假定上一个节点的左子树或者右子树一定变矮
    • 如果左子树变矮,以当前节点为根节点的树高度变矮的情况是
      • bf = -1:当前左子树本来较高
      • bf = 1 且 cur->rightChild->bf != 0:这个自己画图推理一下就知道了
    • 右子树变矮是完全反过来的

寻找这种树的代码如下:

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
Node *AVL::findNoShorter(int val, Node *&toDelete, Node *&theNode) {
stack<Node *> stack1;
Node *noShorter = nullptr;
// Node *toDelete = nullptr;
Node *cur = this->root;
bool isFind = false;
while (cur != nullptr) {
stack1.push(cur);
if (cur->value > val) {
cur = cur->leftChild;
} else if (cur->value < val) {
cur = cur->rightChild;
} else {
isFind = true;
theNode = cur;
if (cur->leftChild == nullptr || cur->rightChild == nullptr) {
toDelete = cur;
break;
} else {
cur = cur->leftChild;
stack1.push(cur);
while (cur->rightChild != nullptr) {
cur = cur->rightChild;
stack1.push(cur);
}
toDelete = cur;
break;
}
}
}
if (!isFind) {
throw Exception("No node to delete", Exception::NO_NODE_TO_DELETE);
} else {
Node *next = nullptr;
bool shorter = true;
while (!stack1.empty()) {
cur = stack1.top();
stack1.pop();
if (cur == toDelete) {
next = cur;
continue;
}
if (cur->leftChild == next) {
//左子树变矮
if (shorter && (cur->bf == -1 || (cur->bf == 1 && cur->rightChild->bf != 0))) {
shorter = true;
} else {
shorter = false;
while (!stack1.empty()) stack1.pop();
noShorter = cur;
break;
}
} else if (cur->rightChild == next) {
//右子树变矮
if (shorter && (cur->bf == 1 || (cur->bf == -1 && cur->leftChild->bf != 0))) {
shorter = true;
} else {
shorter = false;
while (!stack1.empty()) stack1.pop();
noShorter = cur;
break;
}
} else {
cout << "Can not reach here";
exit(-1);
}
next = cur;
}
}
if (noShorter == nullptr) noShorter = this->root;
return noShorter;
}

寻找完这个不会变矮的最小子树之后,从这个节点出发,沿着删除的路径依次向下遍历,直到到达物理上需要删除的节点,沿途一路调整对应的bf,调整逻辑如下:

  • 如果是左子树变矮
    • 当前的bf = -1或0则只需要修正bf的值,无需旋转
    • 当前的bf = 1:
      • 右子节点的bf = 1,进行一次左单旋,两个点的bf调整为0
      • 右子节点的bf = 0,还是一次左单旋,两个点的bf改为1和-1
      • 右子节点的bf = -1,需要进行右左双旋,bf的修正同insert的,注意三种情况
  • 如果是右子树变矮
    • 当前的bf = 1或0则只需要修正bf的值,无需旋转
    • 当前的bf = -1:
      • 左子节点的bf = -1,进行一次右单旋,两个点的bf调整为0
      • 左子节点的bf = 0,还是一次右单旋,两个点的bf改为1和-1
      • 左子节点的bf = 1,需要进行左右双旋,bf的修正同insert的,注意三种情况

修正方案比较复杂,建议自己推理一遍,代码如下:

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
bool AVL::Delete(int val) {
Node *toDelete = nullptr;
Node *noShorter = nullptr;
Node *theNode = nullptr;
try {
noShorter = findNoShorter(val, toDelete, theNode);
}
catch (Exception &exp) {
if (exp.getcode() & Exception::NO_NODE_TO_DELETE) {
return false;
} else throw std::move(exp);
}
int val_ = toDelete->value;
// cout << "1 \n";
Node *cur = noShorter;
// cout << "2\n";
if (toDelete == this->root) {
if (toDelete->leftChild != nullptr) {
this->root = toDelete->leftChild;
} else if (toDelete->rightChild != nullptr) {
this->root = toDelete->rightChild;
}
delete toDelete;
return true;
}
while (true) {
if (cur == toDelete) {
bool isLeft = (cur == cur->parent->leftChild);
if (cur->leftChild == nullptr && cur->rightChild == nullptr) {
if (isLeft) {
cur->parent->leftChild = nullptr;
} else cur->parent->rightChild = nullptr;
delete cur;
theNode->value = val_;
return true;
} else if (cur->leftChild == nullptr) {
if (isLeft) {
cur->parent->leftChild = cur->rightChild;
cur->rightChild->parent = cur->parent;
} else {
cur->parent->rightChild = cur->rightChild;
cur->rightChild->parent = cur->parent;
}
delete cur;
theNode->value = val_;
return true;
} else if (cur->rightChild == nullptr) {
if (isLeft) {
cur->parent->leftChild = cur->leftChild;
cur->leftChild->parent = cur->parent;
} else {
cur->parent->rightChild = cur->leftChild;
cur->leftChild->parent = cur->parent;
}
delete cur;
theNode->value = val_;
return true;
} else throw Exception("delete node with two child", Exception::CANNOT_REACH_HERE);
break;
}
if (cur->value > val_) {
//在左侧子树删除,左侧子树变矮
if (cur->bf <= 0) {
cur->bf++;
cur = cur->leftChild;
} else {
if (cur->rightChild->bf == 1) {
Node *P = cur;
Node *Q = cur->rightChild;
roteLeft(P, Q);
P->bf = 0;
Q->bf = 0;
} else if (cur->rightChild->bf == -1) {
Node *P = cur;
Node *Q = cur->rightChild;
Node *R = Q->leftChild;
roteRight(Q, R);
roteLeft(P, R);
P->bf = R->bf == 1 ? -1 : 0;
Q->bf = R->bf == -1 ? 1 : 0;
R->bf = 0;
} else {
Node *P = cur;
Node *Q = P->rightChild;
roteLeft(P, Q);
Q->bf = -1;
P->bf = 1;
}
cur = cur->leftChild;
}
} else if (cur->value < val_) {
//在右侧删除,右侧子树变矮
if (cur->bf >= 0) {
cur->bf--;
cur = cur->rightChild;
} else {
if (cur->leftChild->bf == -1) {
Node *P = cur;
Node *Q = cur->leftChild;
roteRight(P, Q);
P->bf = 0;
Q->bf = 0;
} else if (cur->leftChild->bf == 1) {
Node *P = cur;
Node *Q = cur->leftChild;
Node *R = Q->rightChild;
roteLeft(Q, R);
roteRight(P, R);
if (R->bf == 0) {
P->bf = Q->bf = R->bf = 0;
} else if (R->bf == 1) {
R->bf = 0;
P->bf = 0;
Q->bf = -1;
} else {
R->bf = 0;
Q->bf = 0;
P->bf = 1;
}
} else {
Node *P = cur;
Node *Q = P->leftChild;
roteRight(P, Q);
Q->bf = 1;
P->bf = -1;
}
cur = cur->rightChild;
}
} else {
if (cur == toDelete) {
throw Exception("Can not reach here", Exception::CANNOT_REACH_HERE);
}
}
}
theNode->value = val_;
return true;
}

验证树是否合法

在这里给出一个check函数,可以验证构建的树是否是合法的

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
bool AVL::check() {
try {
check(this->root);
return true;
} catch (Exception &exp) {
if (exp.getcode() & Exception::DEBUG_ERROR) {
cout << exp.what() << " " << ios::hex << exp.getcode() << endl;
exit(-1);
return false;
}
}
}

int AVL::check(Node *cur) {
// cout << "checking Node ";
// cur->print();
int height = 0;
int heightL = 0;
int heightR = 0;

if (cur->leftChild != nullptr) {
heightL = check(cur->leftChild);
}
if (cur->rightChild != nullptr)
heightR = check(cur->rightChild);
if (cur->bf != heightR - heightL) {
cur->print();
cout << "heightR = " << heightR << " heightL = " << heightL << " bf= " << cur->bf << endl;
throw Exception("Error BF counting", Exception::DEBUG_ERROR | Exception::DEBUG_ERROR_BF);
}
if (cur->leftChild != nullptr && cur->value <= cur->leftChild->value) {
throw Exception("Left is too large", Exception::DEBUG_ERROR | Exception::DEBUG_ERROR_ERROR_ORDER);
}
if (cur->rightChild != nullptr && cur->rightChild->value <= cur->value) {
throw Exception("Right is too small", Exception::DEBUG_ERROR | Exception::DEBUG_ERROR_ERROR_ORDER);
}
if (cur->bf >= 2 || cur->bf <= -2) {
throw Exception("bf out of range", Exception::DEBUG_ERROR | Exception::DEBUG_ERROR_BF);
}
if (cur->leftChild != nullptr && cur->leftChild->parent != cur) {
throw Exception("error connection on left", Exception::DEBUG_ERROR | Exception::DEBUG_ERROR_CONNECTION);
}
if (cur->rightChild != nullptr && cur->rightChild->parent != cur) {
throw Exception("error connection on right", Exception::DEBUG_ERROR | Exception::DEBUG_ERROR_CONNECTION);
}
height = max(heightL, heightR) + 1;
return height;
}

所有程序的完整代码详见github