二叉搜索树(没有重复元素)的特征是:对于树中的每一个节点,它的左子树中的节点的值都小于该节点的值,而它的右子树中节点的值都大于该节点的值。
表示二叉搜索树
二叉搜索树(Binary Search Tree, BST)可以用一个链式节点的集合来表示二叉树。 每个节点都包含一个数值和两个称为left和right的链接,分别指向左孩子和右孩子。
1 | /** This inner class is static, because it does not access |
变量root指向根节点。如果树为空,root的值为null。
树的遍历
二叉树分为根节点、左子树和右子树,分别表示为 +、1、2。
二叉树本身是递归定义的,相应的遍历很自然就成为一种递归问题。
递归遍历操作的关键点是递归体和递归出口:
- 递归出口是二叉树的空子树或叶节点,此时为空操作,递归不继续进行,只能回退;
- 递归体是对二叉树根节点或左、右子树进行相应处理。
基于递归的遍历算法易于编写,操作简单,但可读性差,系统需要维护相应的工作栈,效率不是很高。递归转化为非递归的基本思想是如何实现原本是系统完成的递归工作栈,为此,可以仿照递归执行过程中工作栈状态变化而得到。
对二叉树进行前序、中序和后序遍历时都开始于根节点或结束于根节点,经由路线也相同。彼此差别在于对节点访问时机的选择不同。三种遍历方式都是沿着左子树不断深入下去,当到达二叉树左下节点而无法往下深入时,就向上逐一返回,行进到最近深入时曾遇到节点的右子树,然后进行同样的深入和返回,直到最终从根节点的右子树返回到根节点。
这样,遍历时返回顺序与深入节点顺序恰好相反,因此可以在实现二叉树遍历过程中,使用一个工作栈来保存当前深入到的节点信息,以供后面返回需要时使用。
中序遍历(inorder traversal)
遍历顺序为: 1+2 可以递增顺序显示BST中所有节点。
中序遍历的黄金口诀:当前节点为空,从栈中弹出一个元素,当前节点向右移动;当前节点不为空,压栈,当前节点向左移动
- 中序遍历访问左子二叉树
- 访问根节点
- 中序遍历访问右子二叉树
1 | public void inorder() { |
1 | public void inorder() { |
前序遍历(preorder traversal)
+12 深度优先遍历法(depth-first traversal)与前序遍历法相同。
- 访问根节点
- 前序遍历访问左子二叉树
- 前序遍历访问右子二叉树
1 | public void preorder() { |
1 | public void preorder() { |
后序遍历(postorder traversal)
12+
- 后序遍历访问左子二叉树
- 后序遍历访问右子二叉树
- 访问根节点
1 | public void postorder() { |
1 | public void postorder() { |
广度优先遍历(breadth-first traversal)
1 | public void breadthFirstTraversal() { |
搜索一个元素
二叉搜索树中搜索一个元素,可以从根节点向下扫描,知道找到匹配元素,或者达到一棵空子树为止。1
2
3
4
5
6
7
8
9
10
11
12
13public boolean search(E e) {
TreeNode<E> current = root; // 当前指针指向根节点
while (current != null) {
if (e.compareTo(current.element) < 0) {
current = current.left; // 比当前指针的元素小,则往左
} else if (e.compareTo(current.element) > 0) {
current = current.right; // 比当前指针的元素大,则往右
} else { // 元素匹配
return true; // 找到元素 return true
}
}
return false;
}
插入一个元素
BST中插入一个元素,需要确定在书中插入的位置,关键思路是确定新节点的父节点所在的位置。
- 如果树是空的,使用新元素创建一个根节点;
- 否则,寻找新节点的父节点的位置
- 为该元素创建一个新节点,如果新元素的值小于父元素的值,左子节点;如果新元素的值大于父元素的值,右子节点;BST没有重复元素,重复则不插入
1 | public boolean insert(E e) { |
删除BST中的一个元素
为了从一棵二叉搜索树中删除一个元素,首先需要定位该元素位置,然后再删除该元素以及重新连接树前,考虑两种情况–该节点有或者没有左子节点。
情况1:当前节点没有左子结点。只需将该节点的父节点和该节点的右子节点相连。如果当前节点是叶子节点,属于情况1;
情况2:当前节点有左子结点。假设rightMost指向包含current节点的左子树中的最大元素的节点,而parentOfRightMost指向rightMost节点的父节点。使用rightMost节点中的元素替代current节点中的元素值,将parentOfRightMost节点和rightMost节点的左子节点相连,然后删除rightMost节点。 rightMost作为最大值不能有右节点,但是可能会有左子节点!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
56public boolean delete(E e) {
// 如果current为root,那么parent为null
TreeNode<E> parent = null; // 指向current节点的父节点
TreeNode<E> current = root; // 指向二叉搜索树中包含该元素的节点
while (current != null) { // 递归寻找current节点
if (e.compareTo(current.element) < 0) {
parent = current;
current = current.left;
} else if (e.compareTo(current.element) > 0) {
parent = current;
current = current.right;
} else {
break; // 找到包含e的current节点
}
}
if (current == null) {
return false; // 元素不在树内
}
// 情况1:当前节点没有左子节点
if (current.left == null) {
//只需将该节点的父节点和该节点的右子节点相连
if (parent == null) {
root = current.right;
} else {
if (e.compareTo(parent.element) < 0) {
parent.left = current.right; // e是父节点的左子结点
} else {
parent.right = current.right; // e是父节点的右子结点
}
}
} else {
// 情况2:当前节点有左子节点
TreeNode<E> parentOfRightMost = current; // rightMost节点的父节点
TreeNode<E> rightMost = current.left; // 当前节点的左子树最右端的节点
while (rightMost.right != null) {
parentOfRightMost = rightMost;
rightMost = rightMost.right; // 向右不断递归
}
// 用rightMost节点的内容替换current中的内容
current.element = rightMost.element;
// rightMost的父节点和rightMost的左子节点相连
if (parentOfRightMost.right == rightMost) {
// rightMost是右子节点
parentOfRightMost.right = rightMost.left;
} else {
// rightMost是左子节点
parentOfRightMost.left = rightMost.left;
}
}
size--;
return true;
}
找出叶子节点和非叶子结点
1 | public int getNumberOfLeaves() { |
1 | public int getNumberOfNonLeaves() { |
实现equals和clone方法
1 | public boolean equals(BST<E> tree) { |
1 | public BST<E> clone() throws CloneNotSupportedException { |
BST类
使用一个Tree的接口来定义树的所有常用操作,提供AbstractTree的抽象类部分实现了Tree,最后实现了BST类。
Tree接口
1 | public interface Tree<E> extends Iterable<E>{ |
AbstractTree抽象类
1 | public abstract class AbstractTree<E> implements Tree<E> { |
具体的BST
1 | import java.util.ArrayList; |
BST的测试用例
1 | import java.util.Iterator; |