数据结构与算法(八)——二叉树(Binary Tree)
树(Tree)介绍
树(Tree):一种非线性表结构。是由n(n>=1)个有限结点组成一个具有层次关系的集合。
特点
每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。
高度(Height)、深 度(Depth)、层(Level)
节点的高度 = 节点到叶子节点的最长路径(边数)
节点的深度 = 根节点到这个节点所经历的边的个数
节点的层数 = 节点的深度+1
树的高度 = 根节点的高度
种类
无序树、有序树、二叉树、满二叉树、完全二叉树、平衡二叉树(avl)、二叉查找树(二叉搜索树、BST)、霍夫曼树、红黑树、B树
二叉树(Binary Tree)
二叉树,每个节点最多有两个子节点,分别是左子节点和右子节点。
存储一棵二叉树,我们有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。
二叉树的遍历
广度优先(BFS)
层次遍历:先访问离根节点最近的节点。
深度优先(DFS):前序遍历、中序遍历和后序遍历
前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印 它的右子树。
中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它 的右子树。
后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打 印这个节点本身。
二叉查找树(Binary Search Tree)
二叉查找树也叫二叉搜索树(BST),是为了实现快 速查找而生的。不仅支持快速查找一个数据,还支持快速插入、删除一个数据。
二叉查找树要求,在树中的任意一个节点,其左子树中的每 个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
平衡二叉查找树的高度接近 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。
二叉查找树的查找操作
查找一个节点
- 找根节点,等于就返回;
- 比根节点小,左子树中递归查找;
- 比根节点大,右子树中递归查找。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class BinarySearchTree {
private Node tree;
public Node find(int data) {
Node p = tree;
while (p != null) {
if (data < p.data) p = p.left;
else if (data > p.data) p = p.right;
else return p;
}
return null;
}
public static class Node {
private int data;
private Node left;
private Node right;
public Node(int data) {
this.data = data;
}
}
}
二叉查找树的插入操作
- 从根节点开始,依次比较要插入的数据和节点的大小关系;
- 插入的数据比节点数值大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;不为空,就再递归遍历右子树,查找插入位置;
- 插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;不为空,就再递归遍历左子树,查找插入位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public void insert(int data) {
if (tree == null) {
tree = new Node(data);
return;
}
Node p = tree;
while (p != null) {
if (data > p.data) {
if (p.right == null) {
p.right = new Node(data);
return;
}
p = p.right;
} else { // data < p.data
if (p.left == null) {
p.left = new Node(data);
return;
}
p = p.left;
}
}
}
二叉查找树的删除操作
要删除节点的子节点个数的不同,分三种情况来处理。
- 要删除的节点没有子节点,只需要直接将父节点中,指向要删除节点的指 针置为 null。
- 要删除的节点只有一个子节点(只有左子节点或者右子节点),只需要更 新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
- 要删除的节点有两个子节点。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上,然后再删除掉这个最小节点。
1 | public void delete(int data) { |
散列表很高效,为什么还要用二叉查找树?
- 散列表中的数据是无序存储的;二叉查找树有序;
- 散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定;
- 尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,加起来就不一定比平衡二叉查找树的效率高;
- 散列表的构造比二叉查找树要复杂,需要考虑的东西很多;
- 为了避免过多的散列冲突,散列表装载因子不能太大,特别是基于开放寻址法解决冲突的散 列表,不然会浪费一定的存储空间。