Live2D

二叉排序树

为什么要使用树这种存储结构?

首先要对数组和链表存储结构的优缺点进行分析:

            数组                               

对于未排序的数组:插入速度快(直接在尾部插入),但是查询速度慢(时间复杂度为O(n)),需要依次遍历。

对于有序数组:查询时间通过二分查找,时间复杂度为O(logn)。可是当查找到查找到值后,需要将后续的值依次后移,时间复杂度为O(n)。

对于有序数组的插入时间复杂度证明如下:查找O(logn)+后移O(n) = O(n)

              链表                                 

对于无序链表:查找的时间复杂度为O(n),插入的时间复杂度为O(1)

对于有序链表:查找和插入的时间复杂度均为O(n)

而数组和链表的删除操作时间复杂度均为O(n)

 

而对于树存储结构来说:平均查找,插入和删除的时间复杂度均为O(log(n)),并且构建容易,使用稳定。

我们通常采用二叉排序树来解决上述问题

二叉排序树需要满足这样的条件:对于树中任意一个非叶子节点,左子节点一定比当前节点值小,右子节点一定比当前节点值大。

like this:

 

 

基于Java完成二叉排序树的构建

 

首先创建节点类:

属性如下:

 

 

class Node{
    int value;//值
    Node left;//该结点的左子结点
    Node right;//该结点的右子节点
}

 

实现插入方法:

 public void add(Node node){
        if(node == null){
            return;
        }
        if(this.value > node.value){
            if(this.left == null){
                this.left = node;
            }else{
                this.left.add(node);
            }
        }else{
            if(this.right == null){
                this.right = node;
            }else{
                this.right.add(node);
            }
        }
    }

 

中序遍历:

public void infixOrder(){
        if(this.left != null){
            this.left.infixOrder();
        }
        System.out.println(this.value);
        if(this.right!=null){
            this.right.infixOrder();
        }
    }

 

根据value搜索相应的结点:

/*
思想类似于二分查找。比如:如果搜索的值比当前结点的值大,则往其右子树递归查找。
*/
public Node search(int value){
        if(this.value == value){
            return this;
        }else if(this.value < value){
            if(this.right == null){
                return null;
            }else{
                return this.right.search(value);
            }
        }else{
            if(this.left == null){
                return null;
            }else{
                return this.left.search(value);
            }
        }
    }

根据value值搜索相应结点的父节点:

在进行搜索的时候会有几种情况需要分类讨论:

搜索值:value   当前值:this.*.value *代表left或right

case0:  value = this.*.value

case1:value>this.*.value,右子结点存在时,需往右递归

case2:   value<this.*.value,左子结点存在时,需往左递归

case3:  其他情况均不满足,比如value>this.*.value,而右子结点不存在,左子结点存在时。

/**
     * 
     * @param value
     * @return 如果该结点的左子结点或右子结点的值等于value则返回
     */
    public Node getParentNode(int value){
        
        if((this.left != null && this.left.value == value )
            ||(this.right != null && this.right.value == value)){
            return this;
        }else if(this.value < value && this.right != null) {
            return this.right.getParentNode(value);
        }else if(this.value >=  value && this.left != null){
            return this.left.getParentNode(value);
        }else{
            return null;//没有对应的父节点
        }

    }

 

寻找当前结点右子树中value最小的结点:

该方法是为了删除方法作铺垫

/*
因为左子结点的值一定小于右子结点的值,所以当前结点的右子树中最小值需要一直往左递归得到
结合图看思路很清晰
*/
 public int getRightMinValueNode(){
        Node target = this;
        while(target.left != null){
            target = this.left;
        }

        return target.value;
    }

 

接着构造二叉搜索树:

 

public class BinarySortTreeDemo {
    private Node root;//根结点
}

通过调用Node对象的方法实现二叉搜索树的search,getParent,delRightMinValueNode(删除右子树最小结点):

 public Node search(int value){
        if(root ==null){
            return  null;
        }else{
            return root.search(value);
        }
    }
    public Node getParent(int value){
        if(root == null){
            return null;
        }else{
            return root.getParentNode(value);
        }
    }
    public int delRightMinValueNode(Node node){
        Node target = node;
        while(target.left != null){
            target = target.left;
        }
        delNode(target.value);
        return target.value;
    }

树添加新结点和中序遍历的方法:

public void add(Node node){
        if(root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
    public void infixOrder(){
        if(root == null){
            return;
        }else{
            root.infixOrder();
        }
    }

其实二叉排序树种最难处理的方法也就是删除方法。

需要考虑以下情况:

设删除的结点为:cur

其父结点为 :parent

根结点 :root

由易到难,我们首先考虑根节点的特殊情况:

  case0 :  root = null;直接返回

  case1:只存在root一个结点,则root.left = null; root.right = null;

接着考虑更一般化的情况:

  case2: 当结点为叶子结点时,无需考虑叶子结点的左右结点,只需要判断parent.left.value == value 还是

        parent.right.value == value 为true;相应地,将parent.left设置为null即可完成删除操作

  case3:当结点有且仅有一个子结点(左结点或者右节点),那么分类讨论:

      1.当该结点存在左子结点时:

        (a) 如果cur为父结点的左子结点,那么通过parent.left = cur.left完成删除

        (b) 如果cur为父节点的右子节点时,因为cur右子树的value一定大于cur的value也大于parent.value,

           所以通过parent.right = cur.left完成删除

      2.当该结点存在右子结点时,该类情况与1类似,不做展开。

  case4: 当结点的左右结点均存在时。需要在该结点的子树中找到一个结点来替换。

    如下图所示,假如删除的node.value为15,我们需要在红框内找一个结点,其值能完美替代15。

    删除的node的value需要满足比右子树所有值小,比左子树所有值大

 

 

    那么可以选择左子树中值最大的结点 :一直向右递归,得到node1

    或者选择右子树中值最小的结点:一直向左递归,得到node1

    找到上述结点node1后,将其值赋给要删除的结点,并删除结点node1完成删除操作

 

public  void delNode(int value) {
         //case0
        if (root == null) {
            return;
        }
         //case1
        if (root.left == null && root.right == null) {
            root = null;
            return;
        }
        Node cur = root.search(value);
        Node parent = root.getParentNode(value);
         //case2
        if (cur.left == null && cur.right == null) {
            if (parent.right != null && parent.right.value == value) {
                parent.right = null;
            } else if (parent.left != null && parent.left.value == value) {
                parent.left = null;
            }
            //case4 

        } else if (cur.right != null && cur.left != null) {
            int replacedValue = delRightMinValueNode(cur.right);
            cur.value = replacedValue;

        } else {
              //case3 type(a)
            if (cur.left != null) {
                if (parent.left.value == value) {
                    parent.left = cur.left;
                } else {
                    parent.right = cur.left;
                }
                //case3 type(b)
            } else {
                if (parent.right.value == value) {
                    parent.right = cur.right;
                } else if (parent.left.value == value) {
                    parent.left = cur.right;
                }
            }


        }
    }    

 

posted @ 2020-11-03 17:45  eminemrapgod  阅读(177)  评论(0编辑  收藏  举报