使用Java实现简单的二叉查找树

术语:

二叉树(Binary Tree)是一种特殊的树,树中的每一个节点最多有两个子节点,这两个这两个节点一个叫做左孩子,一个叫做右孩子

二叉查找树(Binary Search Tree)是一颗二叉树,对于树上的每一个节点x,它的左子树上所有后代节点的值总是小于x的值,它的右子树上所有后代的值总是大于x的值

一颗二叉查找树

一颗二叉查找树

1. 基本实现:

一般的BST都是以键值对的形式记录数据,本文为了简单起见,以字符串作为键的类型,以Value作为值的类型(Value为泛型类型,其类型在创建该树的时候指定)。感兴趣的可以看《算法(第四版)》

public class BST<Value> {
    private Node root;
    
    private class Node{
        private String key;
        private Value value;
        private Node left;
        private Node right;

        Node(String key,Value value){this.key=key;this.value=value;}
    }
    /*关于二叉查找树的一些方法,具体实现见下文*/
    //添加节点
    public void add(String key,Value value){}

    //根据键来获取值
    public Value get(String key){return 0;}

    //获取键最小的那个节点
    public Node min(){return null;}

    //获取键最大的那个节点
    public Node max(){return null;}

    //删除节点
    public void delete(String key){}

    //对二叉树进行中序遍历
    public Iterable<String> inorderTraverse(){return null;}
}
复制代码

2. 根据键查找值:

根据键查找记录。如果树包含该键则查找命中,返回键对应的值;如果不包含则查找未命中,返回null

//根据键来获取值
public Value get(String key){
    return get(root,key);
}

private Value get(Node x,String key){
    //在以x节点为根节点的树中查找key
    //如果能找到,则返回键对应的值,如果找不到则返回null
    if(x==null) return null;                	    //如果当前节点为null,则直接返回null

    int cmp=key.compareTo(x.key);           	    //比较当前节点和待找节点的key
    if(cmp>0) 		return get(x.right,key);    //在当前节点的右子树中继续查找
    else if(cmp<0) 	return get(x.left,key);     //在当前节点的左子树中继续查找
    else 		return x.value;             //两者的key相等,则查找命中
}
复制代码

3. 插入键值对:

给出一个键值对,如果树中没有该键,则新建一个节点并将该节点挂到查找末尾的空链接上;如果树中有该键,则更新对应的值

//插入新节点
public void add(String key,Value value){
    root=add(root,key,value);
}

private Node add(Node x,String key,Value value){
    //在以x节点为根节点的树中插入节点
    if(x==null) return new Node(key,value);     	//x为空,表示已经到达树的叶子节点
    												//则直接新键节点并挂在空链接上

    int cmp=key.compareTo(x.key);               	//比较将要插入的节点和节点x的key
    if(cmp>0)       x.right=add(x.right,key,value);     //在x的右子树中插入节点
    else if(cmp<0)  x.left=add(x.left,key,value);       //在x的左子树中插入节点
    else            x.value=value;              	//树已经有key对应的节点,则直接更新其值
    return x;                                   	//返回节点x的引用;
}
复制代码

4. 获取键最小的那个节点:

如果根节点的左链接为空,则树中最小节点即为根节点

如果根节点的左链接不为空,则根节点的左子树中的最小节点即为全树的最小节点

因此我们可以根据递归来找到最小节点

获取最大节点的算法于此类似,区别就是递归节点的右子树

//获取键最小的那个节点
public Node min(){
    return min(root);
}

private Node min(Node x){
    if(x.left==null) return x;
    return min(x.left);
}
复制代码

5. 删除键最小的那个节点:

//删除键最小的那个节点
public void deleteMin(){
    if(root==null) return;
    root=deleteMin(root);
}

private Node deleteMin(Node x){
    if(x.left==null) x=x.right;		       //如果x的左子节点为空,则说明x即为将要删除的最小节点
    						//此时将x引用(x父节点的左链接)赋予x的右子节点
    						//原来的节点因为没有指向它的引用而被垃圾回收
    else x.left=deleteMin(x.left);
    return x;
}
复制代码

6. 删除任意节点:

根据键删除二叉树的节点的算法比较复杂,基本步骤如下:

  1. 找到要删除的节点,使用临时变量t指向该节点
  2. 找到节点t右子树中的最小节点,使用临时变量x指向该节点
  3. 使用deleteMin方法删除t右子树中的最小节点并恢复右子树
  4. x的左链接指向t的左子树,x的右链接指向t的右子树
  5. 将x与t的父节点建立连接
//删除节点
public void delete(String key){
    root=delete(root,key);
}

private Node delete(Node x,String key){

    if(x==null) return null;                    //未找到键值为key的节点
    int cmp=key.compareTo(x.key);
    if(cmp>0) x.right=delete(x.right,key);      //从x的左节点删除节点
    else if(cmp<0) x.left=delete(x.left,key);   //从x的右节点删除节点
    else{
        if(x.right==null) return x.left;        
        if(x.left==null) return x.right;
        Node t=x;
        x=min(t.right);
        x.right=deleteMin(t.right);				//删除t的右子树中的最小节点并恢复右子树
        x.left=t.left;
    }
    return x;
}
复制代码

7. 对二叉查找树进行中序遍历

对二叉查找树的中序遍历会得到一个根据键有序节点的集合

使用递归的方式进行遍历的代码并不复杂

//对二叉树进行中序遍历
public List<Node> inorderTraverse(){
    List<Node> list=new ArrayList<>();
    inorderTraverse(root,list);
    return list;
}

private void inorderTraverse(Node x,List<Node> list){
    if(x==null) return;
    inorderTraverse(x.left,list);
    list.add(x);
    inorderTraverse(x.right,list);
}
复制代码

8. 在控制台中打印该二叉查找树

public void print(){
    System.out.println("***************************************");
    print(root,0);
    System.out.println("***************************************");
}

private void print(Node x,int floor){
    if(x==null) return ;
    print(x.left,floor+1);
    for (int i = 0; i < floor; i++) {
        System.out.print("----");
    }
    System.out.println(x.key+" "+x.value);
    print(x.right,floor+1);
}
复制代码

9. 测试用例

例子中的键为String类型而值为Integer类型。测试用例接受在控制台以$add string的形式添加数据,string为key,对应的值为字符串添加的顺序,$del string为删除,$show 为打印,$exit为退出

public static void main(String[] args) {
    BST<Integer> sibst = new BST<>();

    int i=1;
    String str;
    boolean flag=true;
    Scanner in = new Scanner(System.in);
    String[] words;

    while(flag){
        str=in.nextLine();
        if(str.equals("")) continue;
        words = str.split(" ");
        switch (words[0]){
            case "$exit": {
                flag=false;
                break;
            }
            case "$add":{
                sibst.add(words[1],i++);
                break;
            }
            case "$del":{
                sibst.delete(words[1]);
                break;
            }
            case "$show":{
                sibst.print();
                break;
            }
            default:{
                System.out.println("错误命令");
            }
        }
    }
}
复制代码

10. 完整的代码

BST.java

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class BST<Value> {

    private Node root;

    private class Node{
        private String key;
        private Value value;
        private Node left;
        private Node right;

        Node(String key,Value value){
            this.key=key;
            this.value=value;
        }
    }

    //添加节点
    public void add(String key,Value value){
        root=add(root,key,value);
    }

    public Node add(Node x,String key,Value value){
        //在以x节点为根节点的树中插入节点
        if(x==null) return new Node(key,value);         //x为空,表示已经到达树的叶子节点
                                                        //则直接新键节点并挂在空链接上

        int cmp=key.compareTo(x.key);                   //比较将要插入的节点和节点x的key
        if(cmp>0)       x.right=add(x.right,key,value); //在x的右子树中插入节点
        else if(cmp<0)  x.left=add(x.left,key,value);   //在x的左子树中插入节点
        else            x.value=value;                  //树已经有key对应的节点,则直接更新其值
        return x;                                       //更新节点x;
    }

    //根据键来获取值
    public Value get(String key){
        return get(root,key);
    }

    private Value get(Node x,String key){
        //在以x节点为根节点的树中查找key
        //如果能找到,则返回键对应的值,如果找不到则返回null
        if(x==null) return null;                //如果当前节点为null,则直接返回null

        int cmp=key.compareTo(x.key);           //比较当前节点和待找节点的key
        if(cmp>0) return get(x.right,key);      //在当前节点的右子树中继续查找
        else if(cmp<0) return get(x.left,key);  //在当前节点的右子树中继续查找
        else return x.value;                    //两者的key相等,则查找命中
    }

    //获取键最小的那个节点
    public Node min(){
        return min(root);
    }

    private Node min(Node x){
        if(x.left==null) return x;
        return min(x.left);
    }

    //删除键最小的那个节点
    public void deleteMin(){
        if(root==null) return;
        root=deleteMin(root);
    }

    private Node deleteMin(Node x){
        if(x.left==null) x=x.right;
        else x.left=deleteMin(x.left);
        return x;
    }

    //获取键最大的那个节点
    public Node max(){
        return null;
    }

    //删除节点
    public void delete(String key){
        root=delete(root,key);
    }

    private Node delete(Node x,String key){

        if(x==null) return null;                    //未找到键值为key的节点
        int cmp=key.compareTo(x.key);
        if(cmp>0) x.right=delete(x.right,key);      //从x的左节点删除节点
        else if(cmp<0) x.left=delete(x.left,key);   //从x的右节点删除节点
        else{
            if(x.right==null) return x.left;
            if(x.left==null) return x.right;
            Node t=x;
            x=min(t.right);
            x.right=deleteMin(t.right);
            x.left=t.left;
        }
        return x;
    }

    //对二叉树进行中序遍历
    public List<Node> inorderTraverse(){

        List<Node> list=new ArrayList<>();
        inorderTraverse(root,list);
        return list;
    }

    private void inorderTraverse(Node x,List<Node> list){
        if(x==null) return;
        inorderTraverse(x.left,list);
        list.add(x);
        inorderTraverse(x.right,list);
    }

    public void print(){
        System.out.println("***************************************");
        print(root,0);
        System.out.println("***************************************");
    }

    private void print(Node x,int floor){
        if(x==null) return ;
        print(x.left,floor+1);

        for (int i = 0; i < floor; i++) {
            System.out.print("----");
        }
        System.out.println(x.key+" "+x.value);

        print(x.right,floor+1);
    }

    public static void main(String[] args) {
        BST<Integer> sibst = new BST<>();

        int i=0;
        String str;
        boolean flag=true;
        Scanner in = new Scanner(System.in);
        String[] words;

        while(flag){
            str=in.nextLine();
            if(str.equals("")) continue;
            words = str.split(" ");
            switch (words[0]){
                case "$exit": {
                    flag=false;
                    break;
                }
                case "$add":{
                    sibst.add(words[1],i++);
                    break;
                }
                case "$del":{
                    sibst.delete(words[1]);
                    break;
                }
                case "$show":{
                    sibst.print();
                    break;
                }
                default:{
                    System.out.println("错误命令");
                }
            }
        }
    }
}
复制代码

猜你喜欢

转载自juejin.im/post/5df36e81e51d4557e94fe5e0