每一个节点最多含有两个子树的树称为二叉树。在二叉树的概念下又衍生出满二叉树和完全二叉树的概念。
image.png

满二叉树

除最后一层无任何子节点外,每一层上的全部结点都有两个子结点。也能够这样理解,除叶子结点外的全部结点均有两个子结点。节点数达到最大值,全部叶子结点必须在同一层上
image.png

完全二叉树

若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层全部的结点都连续集中在最左边,这就是完全二叉树。
image.png

二叉树的遍历方式:遍历顺序

  • 先序遍历:先根节点->遍历左子树->遍历右子树
  • 中序遍历:遍历左子树->根节点->遍历右子树
  • 后序遍历:遍历左子树->遍历右子树->根节点

示例:
image.png

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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package test;

import lombok.Data;

/**
* @author 李璟
* @Package test
* @date 2021/12/4 9:46
* @email lijing@njhgroup.cn
* @Copyright 北京新里程叮铃科技有限公司
*/
@Data
public class TreeNode {

private String value;

private TreeNode left;

private TreeNode right;

public TreeNode(String value) {
this.value = value;
}

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

/**
* 前序遍历
*/
public void preOrder() {
// 输出父节点
System.out.print(this.getValue());
// 递归左子树,前序遍历
TreeNode leftChild = this.getLeft();
if (leftChild != null) {
leftChild.preOrder();
}

// 递归右子树,前序遍历
TreeNode rightChild = this.getRight();
if (rightChild != null) {
rightChild.preOrder();
}
}

/**
* 中序遍历
*/
public void infixOrder() {
// 递归左子树,中序遍历
TreeNode leftChild = this.getLeft();
if (leftChild != null) {
leftChild.infixOrder();
}

// 输出父节点
System.out.print(this.getValue());

// 递归右子树,中序遍历
TreeNode rightChild = this.getRight();
if (rightChild != null) {
rightChild.infixOrder();
}
}

/**
* 后序遍历
*/
public void postOrder() {
// 递归左子树,后序遍历
TreeNode leftChild = this.getLeft();
if (leftChild != null) {
leftChild.postOrder();
}

// 递归右子树,后序遍历
TreeNode rightChild = this.getRight();
if (rightChild != null) {
rightChild.postOrder();
}

// 输出父节点
System.out.print(this.getValue());
}
}

package test;

import cn.hutool.core.util.ObjectUtil;
import lombok.Data;

/**
* @author 李璟
* @Package test
* @date 2021/12/4 9:48
* @email lijing@njhgroup.cn
* @Copyright 北京新里程叮铃科技有限公司
*/
@Data
public class BinaryTree {

private TreeNode root;

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

public void preOrder() {
if (ObjectUtil.isNotEmpty(root)) {
root.preOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}

public void infixOrder() {
if (ObjectUtil.isNotEmpty(root)) {
root.infixOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}

public void postOrder() {
if (ObjectUtil.isNotEmpty(root)) {
root.postOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
}

package test;

/**
* @author 李璟
* @Package test
* @date 2021/12/4 10:05
* @email lijing@njhgroup.cn
* @Copyright 北京新里程叮铃科技有限公司
*/

public class Test {

public static void main(String[] args) {
// 创建树
BinaryTree tree = new BinaryTree();

// 创建节点
TreeNode A = new TreeNode("A");
TreeNode B = new TreeNode("B");
TreeNode C = new TreeNode("C");
TreeNode D = new TreeNode("D");
TreeNode E = new TreeNode("E");
TreeNode F = new TreeNode("F");
TreeNode G = new TreeNode("G");
TreeNode H = new TreeNode("H");
TreeNode K = new TreeNode("K");

// 关系
A.setLeft(B);
B.setRight(C);
C.setLeft(D);
A.setRight(E);
E.setRight(F);
F.setLeft(G);
G.setLeft(H);
G.setRight(K);
tree.setRoot(A);


System.out.print("前序遍历测试 ");
tree.preOrder();
System.out.println();
System.out.print("中序遍历测试 ");
tree.infixOrder();
System.out.println();
System.out.print("后序遍历测试 ");
tree.postOrder();
}
}

执行结果:
前序遍历测试 ABCDEFGHK
中序遍历测试 BDCAEHGKF
后序遍历测试 DCBHKGFEA

二叉查找树:有顺序的树

二叉查找树定义:又称为是二叉排序树(Binary Sort Tree)或二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

  1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
  3. 左、右子树也分别为二叉排序树;
  4. 没有键值相等的节点

二叉查找树相比于其余数据结构的优点在于查找、插入的时间复杂度较低为O ( log n ) 。二叉查找树进行中序遍历,即可得到有序的数列,二叉查找树的高度决定了二叉查找树的查找效率。
image.png
二叉查找树也会存在某些特殊情况,因此我们就需要平衡二叉树来提高二叉查找树的查找效率
image.png

平衡二叉树AVL

一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值。

为什么要让高度差不超过1?这样才能保证整棵树的深度最小。我们可以看到,其高度一般都良好地维持在O(log(n)),大大降低了操作的时间复杂度。
image.png
引起结点数量变化的操作才可能导致平衡被改变,也就是删除和插入操作
image.png
可以通过对树进行简单的修复来让其重新恢复到平衡,而这样的简单操作我们就称之为旋转,当然旋转也有单旋转和双旋转之分。

假设结点X是失衡点,它必须重新恢复平衡,由于任意结点的孩子结点最多有两个,而且导致失衡的必要条件是X结点的两棵子树高度差为2(大于1),因此一般只有以下4种情况可能导致X点失去平衡:

  1. 在结点X的左孩子结点的左子树中插入元素
  2. 在结点X的左孩子结点的右子树中插入元素
  3. 在结点X的右孩子结点的左子树中插入元素
  4. 在结点X的右孩子结点的右子树中插入元素

第一情况和第四情况是对称的,可以通过单旋转来解决,而第二种情况和第三情况是对称的,需要双旋转来解决。

左左单旋转(LL)

image.png
左图在原始AVL树插入7结点后,结点9变为失衡点,树再满足AVL性质,因此需要对9结点进行左左单旋转(即向右旋转)后,得到右图。此时不需要操作树的根结点(6)。

右右单旋转(RR)

image.png

左右双旋转(LR)

  • 第一次旋转:图1这里我们可把5、6组成的子树看成前面的右右旋转情景,进行左向旋转,得到图2。5变为6的左子树同时6的左子树,7变成6的右子树,其他不变,到此第一次旋转完成。
  • 第二次旋转:将6、8组成的子树看作左左情景,进行向右旋转,由图2得到图3。8变成6的右孩子结点,并且6的右子树7变成8的左子树,第二次旋转完成。树也重新恢复到平衡。

image.png

右左双旋转(RL)

image.png

如何判断进行单旋转还是双旋转

  1. 单旋转: 插入点不介于 不满足AVL条件的树根 和 树根对应孩子节点之间
  2. 双旋转:插入点介于不满足AVL条件的树根 和 树根对应孩子节点之间