二叉搜索树学习笔记

定义:

二叉查找树是满足以下条件的二叉树:

    1.左子树上的所有节点值均小于根节点值;

    2.右子树上的所有节点值均不小于根节点值;

    3.左右子树也满足上述两个条件。

public class SearchBinaryTree {
    
    private Node root;
    private int size;
    public SearchBinaryTree() {
        super();
    }

static class Node{
        Node parent;
        Node leftChild;
        Node rightChild;
        int val;
        public Node(Node parent, Node leftChild, Node rightChild,int val) {
            super();
            this.parent = parent;
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            this.val = val;
        }
        
        public Node(int val){
            this(null,null,null,val);
        }
        
        public Node(Node node,int val){
            this(node,null,null,val);
        }

    }

}

插入

    二叉查找树的插入过程如下:

    1.若当前的二叉查找树为空,则插入的元素为根节点;

    2.若插入的元素值小于根节点值,则将元素插入到左子树中;

    3.若插入的元素值不小于根节点值,则将元素插入到右子树中。

 public boolean add(int val){
        if(root == null){
            root = new Node(val);
            size++;
            return true;
        }
        Node node = getAdapterNode(root, val);
        Node newNode = new Node(val);
        if(node.val > val){
            node.leftChild = newNode;
            newNode.parent = node;
        }else if(node.val < val){
            node.rightChild = newNode;
            newNode.parent = node;
        }else{
            // 暂不做处理
        }
        size++;19         return true;
    }
    
    /**
     * 获取要插入的节点的父节点,该父节点满足以下几种状态之一
     *  1、父节点为子节点
     *  2、插入节点值比父节点小,但父节点没有左子节点
     *  3、插入节点值比父节点大,但父节点没有右子节点
     *  4、插入节点值和父节点相等。
     *  5、父节点为空
     *  如果满足以上5种情况之一,则递归停止。
     * @param node
     * @param val
     * @return
     */
    private Node getAdapterNode(Node node,int val){
        if(node == null){
            return node;
        }
        // 往左子树中插入,但没左子树,则返回
        if(node.val > val && node.leftChild == null){
            return node;
        }
        // 往右子树中插入,但没右子树,也返回
        if(node.val < val && node.rightChild == null){
            return node;
        }
        // 该节点是叶子节点,则返回
        if(node.leftChild == null && node.rightChild == null){
            return node;
        }
        
        if(node.val > val && node.leftChild != null){
            return getAdaptarNode(node.leftChild, val);
        }else if(node.val < val && node.rightChild != null){
            return getAdaptarNode(node.rightChild, val);
        }else{
            return node;
        }
    }

删除

   1、要删除的节点没有左右子节点,如上图的D、E、G节点

   2、要删除的节点只有左子节点,如B节点

   3、要删除的节点只有右子节点,如F节点

   4、要删除的节点既有左子节点,又有右子节点,如 A、C节点

对于前面三种情况,可以说是比较简单,第四种复杂了。下面先来分析第一种

 若是这种情况,比如 删除D节点,则可以将B节点的左子节点设置为null,若删除G节点,则可将F节点的右子节点设置为null。具体要设置哪一边,看删除的节点位于哪一边。

第二种,删除B节点,则只需将A节点的左节点设置成D节点,将D节点的父节点设置成A即可。具体设置哪一边,也是看删除的节点位于父节点的哪一边。

第三种,同第二种。

第四种,也就是之前说的有点复杂,比如要删除C节点,将F节点的父节点设置成A节点,F节点左节点设置成E节点,将A的右节点设置成F,E的父节点设置F节点(也就是将F节点替换C节点),还有一种,直接将E节点替换C节点。那采用哪一种呢,如果删除节点为根节点,又该怎么删除?

 对于第四种情况,可以这样想,找到C或者A节点的后继节点,删除后继节点,且将后继节点的值设置为C或A节点的值。先来补充下后继节点的概念。

一个节点在整棵树中的后继节点必满足,大于该节点值得所有节点集合中值最小的那个节点,即为后继节点,当然,也有可能不存在后继节点。

但是对于第四种情况,后继节点一定存在,且一定在其右子树中,而且还满足,只有一个子节点或者没有子节点两者情况之一。具体原因可以这样想,因为后继节点要比C节点大,又因为C节点左右子节一定存在,所以一定存在右子树中的左子节点中。就比如C的后继节点是F,A的后继节点是E。

有了以上分析,那么实现也比较简单了,代码如下

public boolean remove(int val){
        Node node = getNode(val);
        if(node == null){
            return false;
        }
        if(node.leftChild == null){// 1、左节点不存在,右节点可能存在,包含两种情况  ,两个节点都不存在和只存在右节点
            transplant(node, node.rightChild);
        }else if(node.rightChild == null){//2、左孩子存在,右节点不存在
            transplant(node, node.leftChild);
        }else{// 3、两个节点都存在
            Node successor = getSuccessor(node);// 得到node后继节点 
            if(successor.parent != node){// 后继节点存在node的右子树中。
                transplant(successor, successor.rightChild);// 用后继节点的右子节点替换该后继节点
                successor.rightChild = node.rightChild;// 将node节点的右子树赋给后继节点的右节点,即类似后继与node节点调换位置
                successor.rightChild.parent = successor;// 接着上一步  给接过来的右节点的父引用复制
            }
            transplant(node, successor);
            successor.leftChild = node.leftChild;
            successor.leftChild.parent = successor;
        }
        return true;
    }
    /**
     * 将child节点替换node节点
     * @param root    根节点
     * @param node    要删除的节点
     * @param child   node节点的子节点
     */
    private void transplant(Node node,Node child){
        /**
         * 1、先判断 node是否存在父节点
         *    1、不存在,则child替换为根节点
         *    2、存在,则继续下一步
         * 2、判断node节点是父节点的那个孩子(即判断出 node是右节点还是左节点),
         *    得出结果后,将child节点替换node节点 ,即若node节点是左节点 则child替换后 也为左节点,否则为右节点
         * 3、将node节点的父节点置为child节点的父节点
         */
        
        if(node.parent == null){
            this.root = child;
        }else if(node.parent.leftChild == node){
            node.parent.leftChild = child;
        }else if(node.parent.rightChild == node){
            node.parent.rightChild = child;
        }
        if(child != null){
            child.parent = node.parent;
        }
    }

查找:

public Node getNode(int val){
        Node temp = root;
        int t;
        do{
            t = temp.val-val;
            if(t > 0){
                temp = temp.leftChild;
            }else if(t < 0){
                temp = temp.rightChild;
            }else{
                return temp;
            }
        }while(temp != null);
        return null;
    }

二叉搜索树遍历

public void print(){
        print(root);
    }
    private void print(Node root){
        if(root != null){
            print(root.leftChild);
            System.out.println(root.val);// 位置在中间,则中序,若在前面,则为先序,否则为后续
            print(root.rightChild);
        }
    }

猜你喜欢

转载自blog.csdn.net/wind_cp/article/details/82748386