关于二叉树中的点到底用“结点”还是“结点”。结点被认为是一个有处理能力的实体,比如网络上的一台计算机;而结点则只是一个交叉点,像“结绳记事”,打个结做个标记,仅此而已。还有就是,要记住:一般算法中点的都是结点。
二叉搜索树又叫二叉排序树、二叉查找树,支持多种操作,包括Search、Minimum、Maximum、Predecessor、Successor、Insert和Delete等操作,且其基本操作所花费的时间与这棵树的高度成正比。
完全二叉树的最坏运行时间为: O ( l o g n ) O({\bf log}\ n) O(log n),树如果由 n n n个结点的组成的线性链,则最好运行时间为: O ( n ) O(n) O(n)。
二叉搜索树的定义
二叉搜索树又称为做二叉排序树、二叉查找树。其要么是一课空树,要么是一个满足以下性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的关键字均小于根结点关键字
- 若它的右子树不空,则右子树上所有结点的关键字均大于根结点关键字
- 它的左右子树依旧是二叉搜索树
- 没右关键字相等的结点
算法导论中指出,左右子树均可以包含和根结点大小相等的元素
二叉搜索树具有的特点:
- 按中序遍历二叉搜索树所得的中序序列是一个递增的有序序列。
- 统一个数据集合可构造的二叉搜索树不唯一,但中序序列相同。
Search
二叉搜索树在搜索某个关键字遇到结点x
时,将关键字k
与x.key
比较:
- 如果
k==x.key
,查找终止并返回x
。 - 如果
k<x.key
,继续在x
的左子树中查找,如果左子树为空则返回null
。 - 如果
k>x.key
,继续在x
的右子树中查找,如果左子树为空则返回null
。
上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:
public TreeNode search(TreeNode node, int key) {
while (node != null) {
if (node.val == key) return node;
else if (node.val > key) node = node.left;
else node = node.right;
}
return node;
}
Minimum
二叉搜索树从根结点沿着左孩子的指针直到遇到null
,并返回左孩子指针为null
的结点。
上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:
public TreeNode minimum(TreeNode node) {
while (node.left != null) node = node.left;
return node;
}
Maximum
二叉搜索树从根结点沿着右孩子的指针直到遇到null
,并返回右孩子指针为null
的结点。
上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:
public TreeNode maximum(TreeNode node) {
while (node.right != null) node = node.right;
return node;
}
Successor
二叉搜索树寻找后继分为两种情况:
- 结点
x
的右子树非空,那么x
的后继就是x
右子树的最左结点(右子树中最小的结点)。 - 结点
x
的右子树为空,那么简单地从x
沿着树向上搜索,直到搜索到当前根结点是父结点的左子树的根为止,此时该父结点是x
的后继。
上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:
public TreeNode successor(TreeNode node) {
if (node.right != null) return minimum(node.right);
while (node.p != null && node == node.p.right) node = node.p;
return node.p;
}
Predecessor
二叉搜索树寻找前驱分为两种情况:
- 结点
x
的左子树非空,那么x
的前驱就是x
左子树的最右结点(左子树中最大的结点)。 - 结点
x
的右子树为空,那么简单地从x
沿着树向上搜索,直到搜索到当前根结点是父结点的右子树的根为止,此时该父结点是x
的前驱
上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:
public TreeNode predecessor(TreeNode node) {
if (node.left != null) return maximum(node.left);
while (node.p != null && node == node.p.left) node = node.p;
return node.p;
}
Insert
插入结点之后仍要满足二叉搜索树的特性。如果要插入的值为z
,二叉搜索树在结点x
处的处理情况分为两种:
- 如果当前结点为空,直接将
z
值插入; - 如果当前结点不空:
- 如果
z<x.key
,继续在x
的左子树中插入 - 如果
z>x.key
,继续在x
的右子树中插入
- 如果
其代码为:
public TreeNode insert(TreeNode root, int z) {
if (root == null) {
root = new TreeNode(z);
return root;
}
TreeNode node = root;
while (true) {
if (z < node.val) {
if (node.left == null) {
node.left = new TreeNode(z);
node.left.p = node;
break;
}
node = node.left;
}
if (z > node.val) {
if (node.right == null) {
node.right = new TreeNode(z);
node.right.p = node;
break;
}
node = node.right;
}
}
return root;
}
Delete
删除结点之后仍要满足二叉搜索树的特性。在二叉搜索树T
中删除一个结点z
分为三种情况:
- 如果结点
z
没有孩子结点,则直接删除该结点 - 如果结点
z
只有左子树或者右子树中的一个,则直接继承:将该子树移到被删除结点的位置 - 如果结点
z
拥有两个子树,则用后继或者先驱结点取代被删除的结点。
其代码为:
public TreeNode delete(TreeNode node, int x) {
TreeNode root = node;
while (node != null) {
if (node.val == x) {
if (node.left != null && node.right != null) { // 情况 3
TreeNode maximum = maximum(node.left);
node.val = maximum.val;
if (maximum.p.left != null && maximum.p.left.val == maximum.val) maximum.p.left = maximum.left;
if (maximum.p.right != null && maximum.p.right.val == maximum.val) maximum.p.right = null;
} else if (node.left == null && node.right == null) { // 情况 1
if (node.p.left != null && node.p.left.val == x) node.p.left = null;
if (node.p.right != null && node.p.right.val == x) node.p.right = null;
} else { // 情况 2
if (node.left != null) {
node.val = node.left.val;
node.right = node.left.right;
if (node.left.left != null) node.left.left.p = node;
if (node.left.right != null) node.left.right.p = node;
node.left = node.left.left;
} else if (node.right != null) {
node.val = node.right.val;
node.left = node.right.left;
if (node.right.left != null) node.right.left.p = node;
if (node.right.right != null) node.right.right.p = node;
node.right = node.right.right;
}
}
break;
}
if (node.val > x && node.left != null) node = node.left;
if (node.val < x && node.right != null) node = node.right;
}
return root;
}
算法导论第三版中给出了构建深度接近 O ( l o g n ) O({\bf log}\ n) O(log n)的随机构建二叉搜索树,按照随机的次序插入关键字到一颗空树中。为降低树的高度还出现了各种“平衡"搜索树。
完整代码:
package org.example;
public class Template {
public TreeNode search(TreeNode node, int key) {
while (node != null) {
if (node.val == key) return node;
else if (node.val > key) node = node.left;
else node = node.right;
}
return node;
}
public TreeNode minimum(TreeNode node) {
while (node.left != null) node = node.left;
return node;
}
public TreeNode maximum(TreeNode node) {
while (node.right != null) node = node.right;
return node;
}
public TreeNode successor(TreeNode node) {
if (node.right != null) return minimum(node.right);
while (node.p != null && node == node.p.right) node = node.p;
return node.p;
}
public TreeNode predecessor(TreeNode node) {
if (node.left != null) return maximum(node.left);
while (node.p != null && node == node.p.left) node = node.p;
return node.p;
}
public TreeNode insert(TreeNode root, int z) {
if (root == null) {
root = new TreeNode(z);
return root;
}
TreeNode node = root;
while (true) {
if (z < node.val) {
if (node.left == null) {
node.left = new TreeNode(z);
node.left.p = node;
break;
}
node = node.left;
}
if (z > node.val) {
if (node.right == null) {
node.right = new TreeNode(z);
node.right.p = node;
break;
}
node = node.right;
}
}
return root;
}
public TreeNode delete(TreeNode node, int x) {
TreeNode root = node;
while (node != null) {
if (node.val == x) {
if (node.left != null && node.right != null) {
TreeNode maximum = maximum(node.left);
node.val = maximum.val;
if (maximum.p.left != null && maximum.p.left.val == maximum.val) maximum.p.left = maximum.left;
if (maximum.p.right != null && maximum.p.right.val == maximum.val) maximum.p.right = null;
} else if (node.left == null && node.right == null) {
if (node.p.left != null && node.p.left.val == x) node.p.left = null;
if (node.p.right != null && node.p.right.val == x) node.p.right = null;
} else {
if (node.left != null) {
node.val = node.left.val;
node.right = node.left.right;
if (node.left.left != null) node.left.left.p = node;
if (node.left.right != null) node.left.right.p = node;
node.left = node.left.left;
} else if (node.right != null) {
node.val = node.right.val;
node.left = node.right.left;
if (node.right.left != null) node.right.left.p = node;
if (node.right.right != null) node.right.right.p = node;
node.right = node.right.right;
}
}
break;
}
if (node.val > x && node.left != null) node = node.left;
if (node.val < x && node.right != null) node = node.right;
}
return root;
}
public static void main(String[] args) {
Template template = new Template();
TreeNode left = new Template().new TreeNode(3);
TreeNode right = new Template().new TreeNode(6);
TreeNode root = new Template().new TreeNode(4, left, right);
left.p = root;
right.p = root;
System.out.println(template.search(root, 5));
System.out.println(template.minimum(root).val);
System.out.println(template.maximum(root).val);
TreeNode successor = template.successor(root);
System.out.println(successor == null ? null : successor.val);
TreeNode predecessor = template.predecessor(right);
System.out.println(predecessor == null ? null : predecessor.val);
TreeNode node = template.insert(root, 1);
System.out.println(node.left.left.p.val);
TreeNode node2 = template.insert(root, 0);
TreeNode node3 = template.insert(root, 2);
TreeNode node1 = template.delete(root, 1);
System.out.println(node1.left.left.right.val);
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode p;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
题目链接
参考文献:
- 二叉搜索树
- 二叉搜索树及其操作详解
- Thomas,H.Cormen,Charles,E.Leiserson,Ronald,L.Rivest,Clifford,Stein,殷建平,徐云,王刚,刘晓光,苏明,邹恒明,王宏志. 算法导论(原书第3版)[J]. 计算机教育, 2013(10):1.