Java实现二叉搜索树的插入、删除和搜索

前言

二叉树类和树节点类的定义

插入

删除

搜索

判断二叉搜索树

后记


前言

        二叉搜索树是树数据结构中比较简单的类型,它通过定义左节点比父节点小,右节点比父节点大,将信息分为两种可能,达到搜索复杂度为O(log_{2}n)。关于二叉搜索树的定义细节,网上博文比较多,这方面就不在赘述。该博文侧重点是对二叉搜索树实现插入、删除和搜索的操作。

二叉树类和树节点类的定义

        二叉树类只包含一个根节点root和节点数量size,因为左右节点需要与父节点进行比较,因此需要泛型上界。具体代码如下:

public class BinarySearchTree<T extends Comparable<T>>{
    private Node<T> root;
    private int size;
    public BinarySearchTree(){
    }
    .
    .
    .
    private static class Node<T extends Comparable<T>>{
        public T value;
        public Node<T> left;
        public Node<T> right;
        public Node<T> father;
        public Node(T value, Node<T> parent){
            this.value = value;
            this.parent = parent;
        }
        public String toString(){
            return key.toString();
        }
    }
}

插入

        插入节点主要步骤为:1、判断根节点是否为空,为空、则该节点为根节点。

                                          2、根节点不为空,则与其比较,小于就往左子树找,大于就往右子树找。

                                          3、如果找到相等的节点,直接退出,二叉搜索树不能重复。

                                          4、找到其父节点后,按照大小比较,设置其为父节点的左节点还是右节点,size++即可。

        具体代码如下:

public boolean insert(T value){
    if(value == null){
        throw new IllegalArgumentException("The value of e connot be null.");
    }
    Node<T> node = root;
    if(node == null){
        root = new Node<>(value, null);
        size++;
        return true;
    }
    Node<T> parent = null;
    int cmp = 0;
    do{
        parent = node;
        cmp = value.compareTo(node.value);
        if(cmp > 0){
            node = node.right;
        }else if(cmp < 0){
            node = node.left;
        }else{
            return false;
        }
    }while(node != null);
    Node<T> newNode = new Node<>(value, parent);
    if(cmp > 0){
        parent.right = newNode;
    }else{
        parent.left = newNode;
    }
    size++;
    return true;
}

删除

        删除一个节点,我们必须用新的节点来替换它,并且保证该节点能满足二叉树的要求,即该节点比左节点大,比右节点小。节点删除的步骤为:1、找到需要被删除的节点,如果找不到,则直接返回空。

                    2、找到以后,就需要在它的左右子树中找到替换它的节点,因为要满足二叉树要求,所以替换节点要么是左子树的最大节点(前驱节点),要么是右子树的最小节点(后继节点)。

                    3、我们将需要被删除节点的值设置成替换节点的值,就能实现节点删除的功能,因此我们真正删除的,其实是替换节点,因为替换节点有一个特点,就是,它最多只有一个子节点,我们只需要用这个子节点移动到它的位置即可,这样操作就会比较简单。此时,原先的替换节点的变成真正的被删除节点,而它的子节点只是一个移动节点,原先的被删除节点现在是被替换节点。

        具体的图示如下:

        1、寻找被删除节点的代码如下:

private Node<T> node(Object value){
    if(value== null){
        return null;
    }
    Node<T> node = root;
    Comparable<? super T> comparator = (Comparable<? super T>) value;
    int cmp = 0;
    while(node != null){
        cmp = comparator.compareTo(node.value);
        if(cmp > 0){
            node = node.right;
        }else if(cmp == 0){
            return node;
        }else {
            node = node.left;
        }
    }
    return null;
}

        2、寻找前驱节点代码如下:

private Node<T> predecessor(Node<T> node){
    if(node == null){
        return null;
    }else if(node.left != null){
        node = node.left;
        while(node.right != null){
            node = node.right;
        }
        return node;
    }else{
        Node<T> parent = node.parent;
        while(parent != null && node == parent.left){
            node = parent;
            parent = node.parent;
        }
        return parent;
    }
}

             寻找后继节点代码如下:

private Node<T> successor(Node<T> node){
    if(node == null){
        return null;
    }else if(node.right != null){
        node = node.right;
        while(node.left != null){
            node = node.left;
        }
        return node;
    }else{
        Node<T> parent = node.parent;
        while(parent != null && node == parent.right){
            node = parent;
            parent = node.parent;
        }
        return parent;
    }
}

        3、最终删除节点的代码如下:

public boolean remove(Object value){
    Node<T> replaced = node(value);
    if(replaced == null){
        return false;
    }
    Node<T> removed = replaced;
    if(replaced.left != null && replaced.right != null){
        removed = successor(replaced);
    }
    Node<T> moved = removed.left != null ? removed.left : removed.right;
    Node<T> parent = removed.parent;
    if(moved != null){
        moved.parent = parent;
    }
    if(parent == null){
        root = moved;
    }else if(removed == parent.left){
        parent.left = moved;
    }else {
        parent.right = moved;
    }
    if(replaced != removed){
        replaced.value = removed.value;
    }
    size--;
    return true;
}

        如果被删除的节点最多只有一个子节点,那么就无需使用前驱(或者后继)节点来替换它,只需要使用它的子节点来接替它即可,配合前面的删除步骤以及图示来了解这段代码,会更加清晰。

搜索

        搜索主要应用于对集的二叉搜索树结构,我们是单集二叉搜索树结构,因此搜索主要的目的是为了判断是否存在该元素的节点。判断一个节点是否存在,其实可以直接使用前面的node函数。具体代码如下:

public boolean contains(Object value){
    return node(value) != null;
}

判断二叉搜索树

        利用左子树的节点小于父节点,右子树的节点大于父节点的性质进行判断,具体代码如下:

public boolean isValid(){
    Node<T> node = root;
    if(node == null){
        return true;
    }
    return isValid(node.left, node) && isValid(node.right, node);
}

private boolean isValid(Node<T> node, Node<T> parent){
    if(node == null){
        return true;
    }
    if(node == parent.left && node.value.compareTo(parent.value) >= 0){
        return false;
    }
    if(node == parent.right && node.value.compareTo(parent.value) <= 0){
        return false;
    }
    return isValid(node.left, node) && isValid(node.right, node);
}

后记

        二叉搜索树的插入和删除相对来说比较简单,但是它却是理解二叉搜索树的基础,二叉搜索树的遍历是该数据结构的真正核心,我也将会写一篇博文对其进行介绍。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
做一门精致,全面详细的 java数据结构与算法!!!让天下没有难学的数据结构,让天下没有难学的算法,不吹不黑,我们的讲师及其敬业,可以看到课程视频,课件,代码的录制撰写,都是在深夜,如此用心,其心可鉴,他不掉头发,谁掉头发???总之你知道的,不知道的,我们都讲,并且持续更新,走过路过,不要错过,不敢说是史上最全的课程,怕违反广告法,总而言之,言而总之,这门课你值得拥有,好吃不贵,对于你知识的渴求,我们管够管饱话不多说,牛不多吹,我们要讲的本门课程内容:稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问题、八皇后问题、算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值