简介
在计算机科学中,二叉树(英语:Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。
特点
- 天然递归结构
- 动态数据结构
- 具有唯一根节点
- 每个节点最多有 2 个孩子
- 每个节点最多有一个父亲
- 每个节点的左子树或右子树也是二叉树
- 二叉树不一定是“满”的
图示

二叉搜索树特点
二叉搜索树是二叉树的一种
每一颗子树也是二叉搜索树
存储的元素必须有可比较性
二叉搜索树的每个节点的值:大于其左子树的所有节点的值,小于其右子树的所有节点的值,如下图:

设计思路

设计思路基本遵循上面思维导图的描述,但在具体实现过程中,我们需要额外考虑一些问题:
- 树的底层使用什么数据结构更合适?
- 数组?
- 链表?
- 如何将数据存储到树中?
- 应该以什么方式进行数据存储?
- 递归?
- 非递归?
- 应该以什么方式进行数据存储?
- 如何查询获取到树中的数据?
- 查询什么样的数据?
- 最小值?
- 最大值?
- 指定值?
- 如果查询的数据不存在,应该返回什么样的结果?
- 需要查询显示什么样的结果?为什么需要这种结果?
- 使用什么样的顺序去查询数据?是依次一查到底?还是一级一级查?
- 查询什么样的数据?
- 如何删除树中的数据?
- 删除什么样的数据?
- 最大值?
- 最小值?
- 指定值?
- 删除数据后会不会导致到树结构的松动,需不需要一些额外措施处理?
- 删除什么样的数据?
上面的问题一步一步理清后,我们就可以进行代码实现了。
代码实现
基本定义
二叉树和链表一样,同样可由节点组成,只是树的节点存在根节点,并且存在两个分别指向左右两个孩子的指针。
下面,我们看下代码实现: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
32public class BinarySearchTree<E extends Comparable<E>> {
// 定义节点
private class Node {
private E e;
// 左右节点
private Node left, right;
public Node(E e) {
this.e = e;
}
}
// 根节点
private Node root;
// 节点的个数
private int size;
public BinarySearchTree() {
}
// 获取节点的个数
public int size() {
return size;
}
// 判空
public boolean isEmpty() {
return size == 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// 增加一个值为 e 的节点
public void add(E e) {
root = add(root, e);
}
private Node add(Node node, E e) {
if (node == null) {
size++;
return new Node(e);
} else if (e.compareTo(node.e) < 0) {
node.left = add(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = add(node.right, e);
}
return node;
}
// 是否包含值为 e 的节点
public boolean contains(E e) {
return contains(root, e);
}
private boolean contains(Node node, E e) {
if (node == null) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else {
return contains(node.right, e);
}
}
删除节点
要想删除一个节点,我们先聊一聊怎么获取最小最大节点。由二叉搜索树的定义知道:二叉搜索树的每个节点的值大于其左子树的所有节点的值,小于其右子树的所有节点的值,那么最小的值一定在最左下角的那个节点里,最大的值一定在最右下角的那个节点里,所以有以下代码:
1 | // 获取最小节点 |
那我们怎么删除一个值最小的节点或一颗最大的节点呢?
分2种情况:
- 如果该最小(大)节点没有孩子,直接删除,下图最小节点13,最大节点42

- 如果该最小(大)节点有右(左)孩子,将右(左)孩子上移到它的位置


转换成代码逻辑:如果当前节点的的左子树为 null,代表该节点为最小节点,返回该节点的右子树(右子树为null返回的也是null,相当于直接删除该节点),不为null代表该节点不是最小节点,递归当前节点的左节点,最后返回当前节点(递归到底层再回溯)
上述为删除最小节点,删除最大节点与其原理相反。
1 | // 返回删除的最小节点值 |
好了,现在我们可以开始删除想删的节点了!分为3种情况:
- 删除只有左孩子的节点
- 删除只有右孩子的节点
- 删除具有左孩子和右孩子的节点,如下图:


前两种均以在前面讨论过,这里只讨论第 3 种情况:
如果该节点既有左孩子又有右孩子,找到右孩子中值最小的节点,用它顶替该节点(当然也可以找到左孩子中值最大的节点,用它顶替该节点,2种方法2选1)由于前面我们写了怎么找树中最小节点或最小节点的方法,这里就可以拿来用啦
删除代码:
1 | // 删除包含指定值的节点 |
二叉树遍历
二叉树的遍历分为:
- 前序遍历(DLR)
- 中序遍历(LDR)
- 后序遍历(LRD)
- 层序遍历
前序遍历(DLR)
定义:二叉树遍历的一种,也叫做先根遍历、先序遍历、前序周游,可记做根左右。
步骤:前序遍历首先访问根结点,之后遍历左子树,最后遍历右子树。
代码实现(基于源代码):
1 | //前序遍历 |
中序遍历(LDR)
定义:二叉树遍历的一种,也叫做中根遍历、中序周游。
步骤:中序遍历首先遍历左子树,之后访问根结点,最后遍历右子树。
代码实现(基于源代码):
1 | //中序遍历 |
后序遍历(LRD)
定义:二叉树遍历的一种,也叫做后根遍历、后序周游,可记做左右根。
步骤:先左后右再根,即首先遍历左子树,之后遍历右子树,最后访问根结点。
后序遍历有递归算法和非递归算法两种,代码实现(基于源代码):
1 | // 后序遍历 |
层序遍历
定义:也叫广度优先遍历。
设二叉树的根节点所在层数为 1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层的节点,接着是第三层的节点,以此类推。自上而下,自左至右逐层访问树结点的过程就是层序遍历。
代码实现(基于源代码):
1 | //层序遍历 |
测试代码
1 |
|
测试结果
1 | 41 |
与预期相符。
文章信息
| 时间 | 说明 |
|---|---|
| 2018-12-07 | 初稿 |