美文网首页
二叉排序树

二叉排序树

作者: 一个追寻者的故事 | 来源:发表于2020-03-27 15:00 被阅读0次

二叉排序树(也叫二叉搜索树、查找树)。

具有如下特点:

或者是一颗空树,或者是一颗具有如下性质的树:
 1)若左子树不为空,那么左子树上面的所有节点的关键字值都比根节点的关键字值小
 2)若右子树不为空,那么右子树上面的所有节点的关键字值都比根节点的关键字值大
 3)左右子树都为二叉树
 4)没有重复值

代码实践

1、树的表示形式:孩子表示法
    /**
     * 树中的节点
     */
    public static class TreeNode {
        int data;          //本例中以 int 型数据为例
        TreeNode leftChild;
        TreeNode rightChild;
        TreeNode parent;
        
        public TreeNode(int data) {
            this.data = data;
        }
    }
2、添加结点
    /**
     * 添加节点
     */
    public TreeNode put(int data) {
        if (root == null) {  //空树
            TreeNode node = new TreeNode(data);
            root = node;
            return node;
        }

        TreeNode node = root;
        TreeNode parent = null;
        while (node != null) {
            parent = node;
            if (data < node.data) {
                node = node.leftChild;
            } else if (data > node.data) {
                node = node.rightChild;
            } else {  // 插入节点重复,直接返回
                return node;
            }
        }

        TreeNode newNode = new TreeNode(data);
        if (data < parent.data) {
            parent.leftChild = newNode;
        } else {
            parent.rightChild = newNode;
        }
        //切记别忘了(有点类似于双向链表的那种)
        newNode.parent = parent;

        return newNode;
    }
3、树的遍历(中序LCR)

1、递归形式

    
    /**
     * 中序遍历
     */
    public void midOrderTraverse(TreeNode root) {
        if (root == null) {
            return;
        }

        //LDR
        midOrderTraverse(root.leftChild);
        System.out.printf(root.data + " ");
        midOrderTraverse(root.rightChild);
    }

2、非递归形式

    /**
     * 中序遍历 非递归形式
     */
    public void midOrderTraverse(TreeNode root){
        if (root == null) return;

        /**
         * Stack 出站的顺序即 中序遍历 的顺序
         */
        Stack stack = new Stack();

        TreeNode target = root;
        while (target != null){
            stack.push(target);
            target = target.leftChild;
        }

        while (!stack.isEmpty()){
            TreeNode node = (TreeNode) stack.pop();

            System.out.print(node.data + " ");

            TreeNode tmp = node.rightChild;
            while (tmp != null){
                stack.push(tmp);
                tmp = tmp.leftChild;
            }
        }
    }
4、查找结点
    /**
     * 查找一个节点
     */
    public TreeNode searchNode(int data) {
        if (root == null) {
            return null;
        }
        TreeNode node = root;
        while (node != null) {
            if (node.data == data) {
                return node;
            } else if (data > node.data) {
                node = node.rightChild;
            } else if (data < node.data) {
                node = node.leftChild;
            }
        }
        return null;
    }
5、删除节点(难点)

删除节点时,所有可能出现的情况:

image.png

1、一种比较精简的实现方式:

    /**
     * 删除一个结点:参考TreeMap的方式,值赋值,效率高。
     * <p>
     * 这样的代码写起来精简,稍微不易读。每种条件判断排列组合以后,覆盖了所有情况。
     *
     * @param p 要删除的节点。
     *             使用 :
     *             TreeNode node = searchNode(3);
     *             tree.delNode(node);
     */
    public void delNode(TreeNode p) {
        if(p == null) throw new NoSuchElementException();

        //左右孩子都存在的情况
        if (p.leftChild != null && p.rightChild != null) {
            TreeNode s = successor(p);
            p.data = s.data;
            p = s;
        }

        TreeNode replacement = p.leftChild != null ? p.leftChild : p.rightChild;
        if (replacement != null) {
            replacement.parent = p.parent;

            if(p.parent == null){
                root = replacement;
            } else if (p.parent.leftChild == p) {
                p.parent.leftChild = replacement;
            } else if (p.parent.rightChild == p) {
                p.parent.rightChild = replacement;
            }

            //注意这样写法
            p.leftChild = p.rightChild = p.parent = null;

        } else if (p.parent == null) {
            root = null;
        } else {
            if (p.parent.leftChild == p) {
                p.parent.leftChild = null;
            } else {
                p.parent.rightChild = null;
            }
        }

    }

    //寻找一个节点的后继节点
    private TreeNode successor(TreeNode t) {
        if (t == null) {
            return null;
        } else if (t.rightChild != null) {
            TreeNode p = t.rightChild;
            while (p.leftChild != null) {
                p = p.leftChild;
            }
            return p;
        } else {
            TreeNode p = t.parent;
            TreeNode ch = t;
            while (p != null && ch == p.rightChild) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

2、引用赋值(代码复杂,思路简单)

/**
     * 删除一个结点:注意各个指针之间的关系,没用的指针及时废掉。
     *
     * @param node 要删除的节点。
     *             使用 :
     *             TreeNode node = searchNode(3);
     *             tree.delNode(node);
     */
    public void delNode(TreeNode node) {
        if (node == null) {
            throw new NoSuchElementException();
        } else {
            //先得到父节点,方便后边的操作
            TreeNode parent = node.parent;
            if (node.leftChild == null && node.rightChild == null) {   //1、如果node是叶子节点
                if (parent == null) {    //根节点
                    root = null;
                } else if (parent.leftChild == node) { //是某一个节点的左节点
                    parent.leftChild = null;
                } else if (parent.rightChild == node) {// 是某一个节点的右节点
                    parent.rightChild = null;
                }
                node.parent = null;
            } else if (node.leftChild != null && node.rightChild == null) { //2、node只有左子树
                if (parent == null) { //根节点
                    root = node.leftChild;
                    node.leftChild = null;  //断开连接
                    root.parent = null; //注意,因为root的parent为空,如果不设置会出问题
                } else {
                    if (parent.leftChild == node) {   //要删除的节点是父亲的左节点
                        parent.leftChild = node.leftChild;
                        node.leftChild.parent = parent;
                    } else {  //要删除的节点是父亲的右节点
                        parent.rightChild = node.leftChild;
                        node.leftChild.parent = parent;
                    }
                    node.leftChild = null;
                    node.parent = null;
                }
            } else if (node.leftChild == null && node.rightChild != null) { //3、node只有右子树
                if (parent == null) {
                    root = node.rightChild;
                    node.rightChild = null;
                    root.parent = null; //注意,因为root的parent为空,如果不设置会出问题
                } else {
                    if (parent.leftChild == node) {   //要删除的节点是父亲的左节点
                        parent.leftChild = node.rightChild;
                        node.rightChild.parent = parent;
                    } else if (parent.rightChild == node) { //要删除的节点是父亲的右节点
                        parent.rightChild = node.rightChild;
                        node.rightChild.parent = parent;
                    }
                    node.rightChild = null;
                    node.parent = null;
                }
            } else if (node.leftChild != null && node.rightChild != null) {//4、有左右两个孩子
                if (node.rightChild.leftChild == null) { //如果被删除节点的右孩子节点的左子树为空,则直接补上右孩子节点
                    if (parent == null) {    //要删除的是根节点
                        root = node.rightChild;
                        node.rightChild.leftChild = node.leftChild;
                        node.leftChild.parent = node.rightChild;

                        node.leftChild = null;
                        node.rightChild = null;
                        root.parent = null; //尽管只移动了root指针,此时要记得置空
                    } else {
                        if (parent.leftChild == node) {  //要删除的节点是父节点的左节点
                            parent.leftChild = node.rightChild;
                            node.rightChild.parent = parent;

                            node.rightChild.leftChild = node.leftChild;
                            node.leftChild.parent = node.rightChild;

                        } else {//要删除的节点是父节点的右节点
                            parent.rightChild = node.rightChild;
                            node.rightChild.parent = parent;

                            node.rightChild.leftChild = node.leftChild;
                            node.leftChild.parent = node.rightChild;
                        }

                        node.leftChild = null;
                        node.rightChild = null;
                        node.parent = null;
                    }
                } else { //如果被删除节点的右孩子节点的左子树不为空,则补上左子树中最小的一个
                    TreeNode minLeftNode = getMinLeftTreeNode(node.rightChild);

                    //处理好最左侧节点的事情
                    minLeftNode.parent.leftChild = minLeftNode.rightChild;
                    if (minLeftNode.rightChild != null) {
                        minLeftNode.rightChild.parent = minLeftNode.parent;
                    }

                    //设置移动节点 的左孩子
                    minLeftNode.leftChild = node.leftChild;
                    node.leftChild.parent = minLeftNode;

                    //设置移动节点 的右孩子
                    minLeftNode.rightChild = node.rightChild;
                    node.rightChild.parent = minLeftNode;

                    if (parent == null) {//要删除的节点是根节点
                        //设置为根节点
                        root = minLeftNode;
                        root.parent = null;
                    } else {
                        if (parent.leftChild == node) {   //要删除的节点是自己父亲节点的左节点
                            parent.leftChild = minLeftNode;
                            minLeftNode.parent = parent;
                        } else { //要删除的节点是自己父亲节点的右节点
                            parent.rightChild = minLeftNode;
                            minLeftNode.parent = parent;
                        }
                    }

                    node.leftChild = null;
                    node.rightChild = null;
                    node.parent = null;
                }
            }
        }
    }

    /**
     * 获取一个二叉排序树中最小的节点
     */
    private TreeNode getMinLeftTreeNode(TreeNode node) {
        if (node == null) {
            return null;
        } else {
            TreeNode curNode = node;
            while (curNode.leftChild != null) {
                curNode = curNode.leftChild;
            }
            return curNode;
        }
    }

完整代码:


/**
 * <p>Description  : 二叉排序树</p>
 * 或者是一颗空树,或者是一颗具有如下性质的树:
 * 1)若左子树不为空,那么左子树上面的所有节点的关键字值都比根节点的关键字值小
 * 2)若右子树不为空,那么右子树上面的所有节点的关键字值都比根节点的关键字值大
 * 3)左右子树都为二叉树
 * 4)没有重复值
 */
public class SearchBinaryTree {
    //根节点
    public TreeNode root;

   /**
     * 树中的节点
     */
    public static class TreeNode {
        int data;          //本例中以 int 型数据为例
        TreeNode leftChild;
        TreeNode rightChild;
        TreeNode parent;

        public TreeNode(int data) {
            this.data = data;
        }
    }

    /**
     * 添加节点
     */
    public TreeNode put(int data) {
        if (root == null) {  //空树
            TreeNode node = new TreeNode(data);
            root = node;
            return node;
        }

        TreeNode node = root;
        TreeNode parent = null;
        while (node != null) {
            parent = node;
            if (data < node.data) {
                node = node.leftChild;
            } else if (data > node.data) {
                node = node.rightChild;
            } else {  // 插入节点重复,直接返回
                return node;
            }
        }

        TreeNode newNode = new TreeNode(data);
        if (data < parent.data) {
            parent.leftChild = newNode;
        } else {
            parent.rightChild = newNode;
        }
        //切记别忘了(有点类似于双向链表的那种)
        newNode.parent = parent;

        return newNode;
    }

    /**
     * 中序遍历 非递归形式
     */
    public void midOrderTraverse(TreeNode root){
        if (root == null) return;

        /**
         * Stack 出站的顺序即 中序遍历 的顺序
         */
        Stack stack = new Stack();

        TreeNode target = root;
        while (target != null){
            stack.push(target);
            target = target.leftChild;
        }

        while (!stack.isEmpty()){
            TreeNode node = (TreeNode) stack.pop();

            System.out.print(node.data + " ");

            TreeNode tmp = node.rightChild;
            while (tmp != null){
                stack.push(tmp);
                tmp = tmp.leftChild;
            }
        }
    }

    /**
     * 查找一个节点
     */
    public TreeNode searchNode(int data) {
        if (root == null) {
            return null;
        }
        TreeNode node = root;
        while (node != null) {
            if (node.data == data) {
                return node;
            } else if (data > node.data) {
                node = node.rightChild;
            } else if (data < node.data) {
                node = node.leftChild;
            }
        }
        return null;
    }

    /**
     * 删除一个结点:参考TreeMap的方法,值复制,效率高。
     * <p>
     * 这样的代码写起来精简,稍微不易读。每种条件判断排列组合以后,覆盖了所有情况。
     *
     * @param p 要删除的节点。
     *             使用 :
     *             TreeNode node = searchNode(3);
     *             tree.delNodeNew(node);
     */
    public void delNode(TreeNode p) {
        if(p == null) throw new NoSuchElementException();

        //左右孩子都存在的情况
        if (p.leftChild != null && p.rightChild != null) {
            TreeNode s = successor(p);
            p.data = s.data;
            p = s;
        }

        TreeNode replacement = p.leftChild != null ? p.leftChild : p.rightChild;
        if (replacement != null) {
            replacement.parent = p.parent;

            if(p.parent == null){
                root = replacement;
            } else if (p.parent.leftChild == p) {
                p.parent.leftChild = replacement;
            } else if (p.parent.rightChild == p) {
                p.parent.rightChild = replacement;
            }

            //注意这样写法
            p.leftChild = p.rightChild = p.parent = null;

        } else if (p.parent == null) {
            root = null;
        } else {
            if (p.parent.leftChild == p) {
                p.parent.leftChild = null;
            } else {
                p.parent.rightChild = null;
            }
        }

    }

    //寻找一个节点的后继节点
    private TreeNode successor(TreeNode t) {
        if (t == null) {
            return null;
        } else if (t.rightChild != null) {
            TreeNode p = t.rightChild;
            while (p.leftChild != null) {
                p = p.leftChild;
            }
            return p;
        } else {
            TreeNode p = t.parent;
            TreeNode ch = t;
            while (p != null && ch == p.rightChild) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
}

相关文章

  • 详解Java二叉排序树

    姓名: 李小娜 [嵌牛导读] :这篇文章主要介绍了Java二叉排序树,包括二叉排序树的定义、二叉排序树的性质、二叉...

  • Java数据结构:二叉排序树(BST)

    一、基本介绍 二叉排序树 BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一...

  • 2018-06-19/20 机试准备09

    数据结构 四、二叉排序树 对二叉排序树进行中序遍历 结果必然是一个递增序列 所以通过建立二叉排序树可以对无序序列进...

  • 二叉搜索树(BST)

    构造一棵二叉排序树的目的,其实并不是为了排序,而是为了提高查找的效率。 那么什么是二叉排序树呢?二叉排序树具有以下...

  • Binary Search Tree

    如果二叉排序树是平衡的,则n个节点的二叉排序树的高度为 ,其查找效率为 ,近似于折半查找。如果二叉排序树完全不平衡...

  • 红黑树

    二叉排序树 非空二叉排序树具有如下特点: 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点...

  • 数据结构之二叉排序树

    二叉排序数 1.二叉排序树介绍 二叉排序树:BST: (Binary Sort(Search) Tree), 对于...

  • 数据结构学习第四弹 二叉排序树

    二叉排序树又称为二叉搜索树或二叉查找树,这是一种插入、删除和检索记录效率都很高的树结构 二叉排序树概念 二叉排序树...

  • 数据结构与算法——基础篇(五)

    二叉排序树——BST——Binary Sort(Search) Tree 二叉排序树的出现是为了解决数组的查询快,...

  • 看图说话之平衡二叉排序树

    本文在看图说话之二叉排序树的基础上介绍了平衡二叉排序树,结构性较好的二叉排序树其插入和删除操作的时间复杂度接近Lo...

网友评论

      本文标题:二叉排序树

      本文链接:https://www.haomeiwen.com/subject/ljmluhtx.html