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

在上一次https://www.cnblogs.com/webor2006/p/11530008.html中对二叉树进行了简单的入门,对于上节涉及到的树实用性倒不是太大,但是从今天开始接触的二叉树都是比较有实用价值的,像在JDK8之后很多的结构也都使用到了树,所以学好树是非常有必要的。

二叉排序树开端:

先来看一张二叉排序树的图:

它或者是一颗空树,或者是一颗具有如下性质的树:

1)若左子树不为空,那么左子树上面的所有节点的关键字值都比根节点的关键字值小
2)若右子树不为空,那么右子树上面的所有节点的关键字值都比根节点的关键字值大
3)左右子树都为二叉树
4)没有重复值(这一点在实际中可以忽略)

下面咱们手动来根据上面的规则来画一张二叉排序树,先来对这个树的规则了解清楚,比如数列“5、2、7、3、4、1、6”,下面来让它生成一个二叉排序树:

先拿5作为根结点:

 

 然后再拿2,由于它比5要小,所以放在5的左边:

再拿7,由于它比5要大,则放在它的右边:

接着再拿3,先跟5比,比它小则往它的左边找,此时则跟2进行对比,由于它比2要大,则放在2的右边,如下:

再拿4,它比5小则往左找,到了2,则于它比2要大则继续往它的右边找,则到了3,由于它比3也要大,则将其放到3的右边,如下:

 接着再拿1,比5比2都要小,则放在2的左边,如下:

最后6,由于它比5大,则往右找,由于它比7要小,则放到7的左边,最终整个二叉排序树就出来了:

好,如果对上面的树进行一下中序遍历(左中右)的话,其结果会让你吃惊:1、2、3、4、5、6、7,居然取出来的结果就变成有序的了。。

接下来咱们则用代码来生成上面的这棵树,我们之前在定义数的结构时也就是一个数据域,一个左结点,一个右结点,回忆一下:

但是!!此时需要增加一个信息了,也就它的父结点,为啥?这为之后的删除节点做准备,这里可以简单试想一下,假如我们要删除排序二叉树中的这样一个节点:

如果要删除的节点木有父节点的信息,请问下面的节点如何链接上它的父节点?所以咱们来定义一下:

主要操作: 

添加节点:

首先定义一个添加方法:

那接下添加肯定有比较大小的过程,首先如果没有根结点,则直接将要添加的数据作为根节点既可,如下:

接下来则是需要进行数据大小比较的,如果该数比节点小则往左找,比它大则往右找,代码如下:

当跳出循环则代表是已经找到待插入元素所存放的位置了,接下来则需要根据这个数据生成一个新节点将其插入到树当中,如下:

遍历:

咱们直接用中序遍历既可,遍历出来就是一个有序数列:

好,接下来咱们来验证一下程序是否写得有问题:

那如果有重复值呢?试一下:

以上就构建了一个去重复值的排序二叉树。

查询节点:

查询就比较简单了,待查找的数比当前结点要小则往左找,则它大则往右找,如下:

下面来调用一下:

嗯~~确实是木问题,接下来咱们来找一个找不到的值:

删除节点:

接下来最最复杂的操作来了,删除节点。。为啥难呢?下面写着就知道了,总之它有四种情况,下面一个个情况来逐步处理:

①、要删除的节点是叶子节点:比如:

这种情况处理比较简单,只要让父结点的子结点为null既可,如下:

先把整个框架条件定好,接下来先处理第一种情况,先来处理一下特殊情况:如果整棵树就只有一个结点:

其也满足叶子结点的条件,所以可以这样处理:

然后再来处理正常叶子的情况:

②、要删除的结点它只有左孩子,比如:

也就是再来处理这个条件分支:

其思路就是需要将要删除结点的左孩子往上顶替回来,也就是说:

所以看一下具体实现,所先判断是否删的是根结点:

接下来else的条件则是说明要删除的结点不是根结点了,但是此时还存在两种情况,一种是在父节点左边,另一种是在父节点右边,如下:

所以代码条件框架如下:

处理也比较简单,直接将要删除结点的左孩子接到父节点上既可,如下:

③、要删除的结点只有右孩子,跟情况2类似。所以直接依葫芦画瓢既可,只是将左孩子统一换成右孩子既可,如下:

/**
     * 删除节点
     */
    public void deleteNode(TreeNode node) {
        if (node == null) {
            throw new NoSuchElementException();
        } else {
            //先得到父亲,方便后面的操作
            TreeNode parent = node.parent;
            if (node.left == null && node.right == null) {
                //1、node是个叶子
                if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
                    root = null;
                else if (parent.right == node) {
                    parent.right = null;//直接将右结点置为null
                } else if (parent.left == node) {
                    parent.left = null;//直接将左结点置为null
                }
                node.parent = null;
            } else if (node.left != null && node.right == null) {
                //2、只有左孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.left.parent = null;//将它的左孩子作为根结点
                    root = node.left;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        parent.left = node.left;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        parent.right = node.left;
                    }
                    node.parent = null;
                }
            } else if (node.left == null && node.right != null) {
                //3、只有右孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.right.parent = null;//将它的左孩子作为根结点
                    root = node.right;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        parent.left = node.right;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        parent.right = node.right;
                    }
                    node.parent = null;
                }
            } else if (node.left != null && node.right != null) {
                //4、有左右孩子
                //TODO
            }
        }

    }

其实代码此时已经有bug了,修改如下:

/**
     * 删除节点
     */
    public void deleteNode(TreeNode node) {
        if (node == null) {
            throw new NoSuchElementException();
        } else {
            //先得到父亲,方便后面的操作
            TreeNode parent = node.parent;
            if (node.left == null && node.right == null) {
                //1、node是个叶子
                if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
                    root = null;
                else if (parent.right == node) {
                    parent.right = null;//直接将右结点置为null
                } else if (parent.left == node) {
                    parent.left = null;//直接将左结点置为null
                }
                node.parent = null;
            } else if (node.left != null && node.right == null) {
                //2、只有左孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.left.parent = null;//将它的左孩子作为根结点
                    root = node.left;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        node.left.parent = parent;
                        parent.left = node.left;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        node.left.parent = parent;
                        parent.right = node.left;
                    }
                    node.parent = null;
                }
            } else if (node.left == null && node.right != null) {
                //3、只有右孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.right.parent = null;//将它的左孩子作为根结点
                    root = node.right;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        node.right.parent = parent;
                        parent.left = node.right;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        node.right.parent = parent;
                        parent.right = node.right;
                    }
                    node.parent = null;
                }
            } else if (node.left != null && node.right != null) {
                //4、有左右孩子
                //TODO
            }
        }

    }

④、要删除的结点既有左孩子,又有右孩子。比如:

也就是对应这个条件的代码:

 

此时最复杂的操作来了,下面得留个心,很容易写错的,因为涉及到的情况也挺多的,下面一个个情况进行分析然后进行一一处理:

情况一:

那此时删掉之后,得由7往上补,因为都了右结点,都比左边的数要大,所以最终形态会是:

那下面用代码来处理这种情况:

 

这里还有一种情况如下:

也就是要删除的节点不是根节点,那此时则直接将7往上替换既可,如下:

所以这块逻辑的代码比较简单:

类似的,还有可能在根结点左边,如下:

像这种处理也是类似,直接将7顶替上来既可:

所以相似的处理代码:

情况二:

此时就要注意啦,就不能直接将7给被上去了啦,为啥,假如把7替上去,就不符合排序二叉树了?所以应该是遍历它的左孩子将其最小的4顶上去,如下:

所以下面来实现一下,先来封装一下找节点最小值的方法:

此时也就是找到了这个结点了:

接下来则需要将这个4放到5的位置,如何做呢?先这样做:

所以先处理这种情况,得一个个来,不然很容易绕晕的,而且必须要画图,具体代码如下:

此时的形态就变为:

好,接下来需要对最小节点做引用处理,因为有可能在它下面还有树,比如:

 

此时则需要将4这棵最小的值的右子树得先处理好了,才能将4作为根节点,所以代码处理如下:

所以此时的形态就变为:

注意需要将4的父结点的引用给去掉,以便将4顶上去,所以修改一下代码:

好,接下来再处理第三步,则应该是将7作为4的右子树,形态就会变为:

所以这一步的代码为:

同样的它也有一种特殊情况,比如要删的这个节点不是根结点,如下:

  

此时直接将4接到根结点既可,如下:

所以代码如下:

此时处理的是根结点的情况,也就是形态会成了这样了:

接下来再来处理有父节点的:

有父节点的就有两种情况,一个是被删除的子节点位于父节点左边,一个是位于父节点右边,下面以位于父节点右边为例来看一下目前的形态:

于是乎,整个删除节点的代码如下:

/**
     * 删除节点
     */
    public void deleteNode(TreeNode node) {
        if (node == null) {
            throw new NoSuchElementException();
        } else {
            //先得到父亲,方便后面的操作
            TreeNode parent = node.parent;
            if (node.left == null && node.right == null) {
                //1、node是个叶子
                if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
                    root = null;
                else if (parent.right == node) {
                    parent.right = null;//直接将右结点置为null
                } else if (parent.left == node) {
                    parent.left = null;//直接将左结点置为null
                }
                node.parent = null;
            } else if (node.left != null && node.right == null) {
                //2、只有左孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.left.parent = null;//将它的左孩子作为根结点
                    root = node.left;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        node.left.parent = parent;
                        parent.left = node.left;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        node.left.parent = parent;
                        parent.right = node.left;
                    }
                    node.parent = null;
                }
            } else if (node.left == null && node.right != null) {
                //3、只有右孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.right.parent = null;//将它的左孩子作为根结点
                    root = node.right;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        node.right.parent = parent;
                        parent.left = node.right;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        node.right.parent = parent;
                        parent.right = node.right;
                    }
                    node.parent = null;
                }
            } else if (node.left != null && node.right != null) {
                //4、有左右孩子
                if (node.right.left == null) {
                    //如果被删除的右子树的左子树为空,则直接补上右子树
                    node.right.left = node.left;
                    if (parent == null) {
                        //如果要删的根结点
                        root = node.right;
                    } else {
                        //如果不是根结点
                        if (parent.right == node) {
                            parent.right = node.right;
                        } else {
                            parent.left = node.right;
                        }
                    }
                    node.left = null;
                    node.right = null;
                    node.parent = null;
                } else {
                    //需要补上右子树的左子树上最小的一位
                    TreeNode treeNode = getMinLeftTreeNode(node.right);
                    treeNode.left = node.left;
                    //将最小左子树结点的右子树全部接上来
                    TreeNode leftNodeParent = treeNode.parent;
                    leftNodeParent.left = treeNode.right;
                    treeNode.right.parent = leftNodeParent;
                    treeNode.parent = null;
                    //将最小左子树结点顶到根位置
                    treeNode.right = node.right;
                    //处理根情况
                    if (parent == null) {
                        //要删除的节点既为根节点
                        node.left = null;
                        node.right = null;
                        root = leftNodeParent;
                    } else {
                        //要删除的节点不是根节点
                        if (parent.left == node) {
                            //左节点
                            parent.left = treeNode;
                        } else {
                            //右节点
                            parent.right = treeNode;
                        }
                        treeNode.parent = parent;
                        node.left = null;
                        node.right = null;
                        node.parent = null;
                    }
                }
            }
        }
    }

真的是。。晕菜了~~太TM复杂了,下面先来论证一下是否写得靠谱:

真是实力打脸,得找bug了。。

再运行:

嗯,对了,再来测:

继续找bug:

 

下面看一下为啥输出不对了,调一下:

另外,在删2时,发现它的parent还是5,不太对,所以得继续找原因,发现是这块的问题:

再运行:

正确了,可见这块删除只要有一个关系没处理好其结果就会有问题,面试要谁问到手写它,我觉得没个几个小时肯定是很难写得非常正确的,好下面继续再测试:

看一下结果:

哇塞,真不容易~~完美!!最后代码定格在:

package com.datastruct.test.tree;

import java.util.NoSuchElementException;

public class SearchBinaryTree {
    //根节点
    TreeNode root;

    public TreeNode put(int data) {
        if (root == null) {//如果没有根节点,则直接将其作为根节点
            TreeNode node = new TreeNode(data);
            root = node;
            return root;
        }
        TreeNode parent = null;
        TreeNode node = root;
        while (node != null) {
            parent = node;//再往下移之前需要记录一下父元素,以便在找到要插入的位置时将其进行连接上
            if (data < node.data) {
                //则往左找
                node = node.left;
            } else if (data > node.data) {
                //则往右找
                node = node.right;
            } else {
                //如果已经有相同的重复节点了,则直接返回,因为二叉排序树是不允许重复的
                return node;
            }
        }
        //生成一个新节点将其插入
        TreeNode newNode = new TreeNode(data);
        if (data < parent.data) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
        newNode.parent = parent;

        return newNode;
    }

    /**
     * 中序遍历
     */
    public void midOrderTraverse(TreeNode root) {
        if (root == null)
            return;
        midOrderTraverse(root.left);
        System.out.print(root.data + " ");
        midOrderTraverse(root.right);
    }

    /**
     * 查找接点
     */
    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.left;
            } else if (data > node.data) {
                //则往右找
                node = node.right;
            }
        }

        return null;
    }

    /**
     * 删除节点
     */
    public void deleteNode(TreeNode node) {
        if (node == null) {
            throw new NoSuchElementException();
        } else {
            //先得到父亲,方便后面的操作
            TreeNode parent = node.parent;
            if (node.left == null && node.right == null) {
                //1、node是个叶子
                if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
                    root = null;
                else if (parent.right == node) {
                    parent.right = null;//直接将右结点置为null
                } else if (parent.left == node) {
                    parent.left = null;//直接将左结点置为null
                }
                node.parent = null;
            } else if (node.left != null && node.right == null) {
                //2、只有左孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.left.parent = null;//将它的左孩子作为根结点
                    root = node.left;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        node.left.parent = parent;
                        parent.left = node.left;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        node.left.parent = parent;
                        parent.right = node.left;
                    }
                    node.parent = null;
                }
            } else if (node.left == null && node.right != null) {
                //3、只有右孩子
                if (parent == null) {
                    //说明删除的结点是根结点,直接将子结点接上来
                    node.parent = null;
                    node.right.parent = null;//将它的左孩子作为根结点
                    root = node.right;//重新更新根结点
                } else {
                    if (parent.left == node) {
                        //要删除的节点是在父亲的左边
                        node.right.parent = parent;
                        parent.left = node.right;
                    } else if (parent.right == node) {
                        //要删除的节点是在父亲的右边
                        node.right.parent = parent;
                        parent.right = node.right;
                    }
                    node.parent = null;
                }
            } else if (node.left != null && node.right != null) {
                //4、有左右孩子
                if (node.right.left == null) {
                    //如果被删除的右子树的左子树为空,则直接补上右子树
                    node.right.left = node.left;
                    if (parent == null) {
                        //如果要删的根结点
                        node.right.parent = null;
                        node.right.left.parent = node.right;
                        root = node.right;
                    } else {
                        //如果不是根结点
                        if (parent.right == node) {
                            parent.right = node.right;
                        } else {
                            parent.left = node.right;
                        }
                        node.right.parent = parent;
                        node.left.parent = node.right;
                    }
                    node.left = null;
                    node.right = null;
                    node.parent = null;
                } else {
                    //需要补上右子树的左子树上最小的一位
                    TreeNode treeNode = getMinLeftTreeNode(node.right);
                    treeNode.left = node.left;
                    node.left.parent = treeNode;
                    //将最小左子树结点的右子树全部接上来
                    TreeNode leftNodeParent = treeNode.parent;
                    leftNodeParent.left = treeNode.right;
                    if (treeNode.right != null)
                        treeNode.right.parent = leftNodeParent;
                    treeNode.parent = null;
                    //将最小左子树结点顶到根位置
                    treeNode.right = node.right;
                    //处理根情况
                    if (parent == null) {
                        //要删除的节点既为根节点
                        node.left = null;
                        node.right = null;
                        leftNodeParent.parent = treeNode;
                        root = treeNode;
                    } else {
                        //要删除的节点不是根节点
                        if (parent.left == node) {
                            //左节点
                            parent.left = treeNode;
                        } else {
                            //右节点
                            parent.right = treeNode;
                        }
                        treeNode.parent = parent;
                        node.left = null;
                        node.right = null;
                        node.parent = null;
                    }
                }
            }
        }
    }

    /**
     * 找左节点最小值
     */
    private TreeNode getMinLeftTreeNode(TreeNode node) {
        TreeNode curNode = null;
        if (node == null)
            return null;
        else {
            curNode = node;
            while (curNode.left != null) {
                curNode = curNode.left;
            }
        }
        return curNode;
    }

    public class TreeNode {
        int data;
        TreeNode left;
        TreeNode right;
        TreeNode parent;

        public TreeNode(int data) {
            this.data = data;
            this.left = null;
            this.right = null;
            this.parent = null;
        }
    }
}
原文地址:https://www.cnblogs.com/webor2006/p/11575167.html