二叉搜索树的定义
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。
用JAVA定义二叉搜索树的节点
TreeNode有三个成员变量,int类型的变量val用来存储节点对应的值,TreeNode类型的节点left指向一颗由小于该节点的所有节点组冲的二叉搜索树,right指向一颗由大于该节点的所有节点组冲的二叉搜索树。
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
二叉搜索树的相关操作
1.查找
一般来说,在符号表中查找一个键可能得到两种结果。如果含有该键的结点存在于表中,我们的查找就命中了,然后返回相应的值。否则查找未命中(并返回null)。根据数据表示的递归结构我们马上就能得到,在二又查找树中查找一个键的递归算法:如果树是空的,则查找未命中;如果被查找的键和根结点的键相等,查找命中,否则我们就(递归地)在适当的子树中继续查找。如果被查找的键较小就选择左子树,较大则选择右子树。查找使用的函数get()如下:
private TreeNode get(TreeNode root,int v){
if(root==null){
return null;
}
if(root.getVal()==v) {
return root;
}
else if(root.getVal()>v){
return get(root.left,v);
}
else {
return get(root.right,v);
}
}
2.插入
插入函数的实现逻辑和递归查找很相似:如果树是空的,就返回一个含有该键值对的新结点;如果被查找的键小于根结点的键,我们会继续在左子树中插人该键,否则在右子树中插人该键。插入使用的put函数如下
private TreeNode put(TreeNode node, int v){
if(node==null){
TreeNode newNode = new TreeNode(v);
return newNode;
}
if(v<node.val){
node.left = put(node.left,v);
}
else{
node.right = put(node.right,v);
}
return node;
}
3.获取二叉查找树中最大/最小值节点
如果根结点的左链接为空,那么一棵二叉查找树中最小的键就是根结点;如果左链接非空,那么树中的最小键就是左子树中的最小键。这描述了min()方法的递归实现,找出最大键的方法也是类似的,只是变为查找右子树而已。
min()方法的JAVA代码如下:
private TreeNode min(TreeNode root){
if(root==null){
return null;
}
if(root.left==null){
return root;
}
return min(root.left);
}
max()方法的JAVA代码如下:
private TreeNode max(TreeNode root){
if(root==null){
return null;
}
if(root.right==null){
return root;
}
return min(root.right);
}
4.向上取整和向下取整
向下取整(floor):如果给定的键key小于二叉查找树的根结点的键,那么小于等于key的最大键(floor)一定在根结点的左子树中;如果给定的键key大于二叉查找树的根结点,那么只有当根结点右子树中存在小于等于key的结点时,小于等于key的最大键才会出现在右子树中,否则根结点就是小于等于key的最大键。这段描述说明了floor()方法的递归实现。
将“左”变为“右”(同时将小于变为大于)就能够得到ceil()的算法。
向下取整函数的JAVA代码如下:
private TreeNode floor(TreeNode root,int v){
if(root==null)
return null;
if(root.getVal()==v)
return root;
else if(root.getVal()>v)
return floor(root.left,v);
/*用t来记录从root的右子树中是否找到了v的floor值*/
TreeNode t = floor(root.right, v);
/*如果右子树中没有floor值那么root就是floor值*/
if(t==null)
return root;
else
return t;
}
向上取整函数的JAVA代码如下:
private TreeNode ceil(TreeNode root,int v){
if(root==null)
return null;
if(root.getVal()==v)
return root;
else if(root.getVal()<v)
return ceil(root.right,v);
/*用t来记录从root的左子树中是否找到了v的ceil值*/
TreeNode t = ceil(root.left, v);
/*如果左子树中没有ceil值那么root就是ceil值*/
if(t==null)
return root;
else
return t;
}
5.选择操作
假设我们想找到排名为k的键。如果左子树中的结点数t加上父节1即t+1大于k,那么我们就继续(递归地)在左子树中查找排名为k的键;如果等于k,我们就返回根结点中的键;
如果小于k我们就(递归地)在右子树中查找排名为(k-t)的键。这段描述既说明了select()方法的递归实现。
选择操作的JAVA代码如下,其中size()函数用来计算树的节点数目
public int select(TreeNode root, int k) {
int size = size(root.left,k)+1;
if(size==k)
return root.val;
else if(size>k)
return select(root.left,k);
else
return select(root.right,k-size);
}
// 定义size()函数用来求以root为根节点的树有多少个
private int size(TreeNode root,int k){
if(root==null)
return 0;
int left_size = size(root.left,k);
int right_size = size(root.right,k);
return left_size+right_size+1;
}
6.排名
rank()是select()的逆方法,它会返回给定键的排名。它的实现和select()类似:如果给定的键和根结点的键相等,我们返回左子树中的结点总数加1;如果给定的键小于根结点,我们会返回该键在左子树中的排名(递归计算)如果给定的键大于根结点,我们会返回根结点的排名加上它在右子树中的排名(递归计算)。
private int rank(TreeNode root,int v){
if(root==null){
return 1;
}
if(v<root.val()){
return rank(root.left,v);
}
else if(v>root.val()){
return rank(root.right,v)+size(root.left)+1;
}
else {
return size(root.left)+1;
}
}
7.排序
二叉搜索树的中序遍历结果就是二叉搜索树按照升序排序的结果。
private void inOrder(TreeNode root, List<Integer> ret){
if(root==null)
return ;
inOrder(root.left);
ret.add(root.val);
inOrder(root.right);
}
8.删除二叉搜索树的任一节点
删除二叉搜索树任意节点的步骤如下:
(1)将指向即将被删除的结点的链接保存为t
(2)将x指向它的后继结点min(t.right);
(3)将x的右链接(原本指向一棵所有结点都大于x.val的二叉查找树)指向deleteMin(t.right),也就是在删除后所有结点仍然都大于x.val的子二叉查找树;
(4)将x的左链接(本为空)设为t.left(其下所有的键都小于被删除的结点和它的后继结点)。
删除操作的JAVA代码为:
public TreeNode deleteNode(TreeNode root, int key) {
if(root==null){
return null;
}
if(root.val==key) {
TreeNode x = getMin(root.right);
if(x==null){
return root.left;
}
TreeNode rightNode = deleteMin(root.right);
x.right = rightNode;
x.left = root.left;
root = null;
return x;
}
else if(root.val>key){
root.left = deleteNode(root.left,key);
}
else {
root.right = deleteNode(root.right,key);
}
return root;
}
private TreeNode deleteMin(TreeNode root){
if(root==null){
return null;
}
if(root.left==null){
return root.right;
}
TreeNode rightNode = deleteMin(root.left);
root.left = rightNode;
return root;
}
private TreeNode getMin(TreeNode root){
if(root==null){
return null;
}
if(root.left==null){
return root;
}
return getMin(root.left);
}
参考文献:
Robert Sedgewick,Kevin Wayne 算法第4版