Java集合系列源码分析(六)-- 二叉查找树

之前讲了部分集合源码,有涉及到红黑树,一下就讲红黑树有点不好讲,先从简单点的树开始讲,以下就从二叉查找树开始讲

一、 二叉查找树

二叉查找树(Binar Search Tree),又称二叉搜索树,二叉排序树

二叉查找树特性:

  1. 若左子树非空,则左子树上所有的节点关键字值均小于根节点的关键字值
  2. 若右子树非空,则右子树上所有节点关键字值均大于根节点的关键字值
  3. 左右子树本身也分别是一颗二叉查找树

简单来说就是左子树节点值<根节点值<右子树节点值。

如下图所示就是一个二叉查找树:
在这里插入图片描述

二、二叉查找树的增删改查

二叉搜索树的节点定义

public class Node {

    public int data;
    public Node left;
    public Node right;

    public Node(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                '}';
    }
}

2.1 二叉查找树的插入

  • 若二叉查找树为空,则直接插入节点,即根节点
  • 若插入的节点值小于根节点值,则插入左子树
  • 若插入的节点值大于根节点值,则插入右子树
  • 新插入的节点一定是一个叶节点
public class BinarySearchTree {

    private Node root;

    public BinarySearchTree() {
        root = null;
    }

    public boolean insert(int val){
        Node newNode = new Node(val);
        if(root == null){
            root = newNode;
        }else {
            Node temp = root;
            Node parent;
            while(true){
                parent = temp;
                if(val < temp.data){
                    temp = temp.left;
                    if(temp == null){
                        parent.left = newNode;
                        return true;
                    }
                }else if (val > temp.data){
                    temp = temp.right;
                    if(temp == null){
                        parent.right = newNode;
                        return true;
                    }
                }else{
                    return false;
                }
            }
        }
        return false;
    }
}

2.2 二叉查找树的查找

二叉查找树的查找先从根节点开始,进行比较向下查找。根据二叉查找树的特性,最小值必然在左子树的最左侧节点,最大值必然在右子树的最右侧节点。

而其他节点的查找则根据查找数与根节点大小的比较,比根节点小的则查找根节点的左子树,比根节点大的则查找根节点的右子树

   public Node search(int val) {
        Node temp = root;
        while (val != temp.data) {
            //判断查询方向
            if (val < temp.data) {
                temp = temp.left;
                if(temp == null){
                    return null;
                }
            }
            if (val > temp.data) {
                temp = temp.right;
                if(temp == null){
                    return null;
                }
            }
        }
        return temp;
    }

比较简单,看下就懂了,不多说了

2.3 二叉查找树的删除

删除过程比较复杂

在删除时,只能删除需要删除的节点,而该节点下的子树不能删除,删除后有可能又破坏了二叉查找树的构成条件,需要重新构造一下

因此删除分为以下几种情况:

  1. 删除叶节点:直接删除即可,不会破坏二叉查找树的构成
  2. 删除节点只有一颗左子树或右子树:直接让删除节点的子树替代删除节点的位置
  3. 删除节点有左右两颗子树:使用删除节点后,中序第一个子女填补

情况1:删除叶节点

  public void delete(int val) {
        Node temp = root;
        Node parent = null;
        String position = null;
        while (val != temp.data) {
            if (val < temp.data) {
                parent = temp;
                position = "left";
                temp = temp.left;
                if (temp == null) {
                    return;
                }
            }
            if (val > temp.data) {
                parent = temp;
                position = "right";
                temp = temp.right;
                if (temp == null) {
                    return;
                }
            }
        }

        //情况1:检查删除的节点是否是叶子节点
        if (isLoof(temp)) {
            if (position.equals("left")) {
                parent.left = null;
            } else {
                parent.right = null;
            }

            return ;
        }
        }

删除需要先查找再删除,暂时想到这个比较笨的code,有好的可以分享出来。逻辑比较简单,不多解释了。

情况2:删除节点只有一颗子树

删除节点只有1颗子树只有以下图示得四种情况
在这里插入图片描述

根据上面的四种类型,在删除方法里增加以下代码

        //判断是否是情况2
        Node next = isLoofParent(temp);
        if (next != null) {
            if (position.equals("left")) {
                parent.left = next;
            } else {
                parent.right = next;
            }
            temp.left = null;
            temp.right = null;

            return ;
        }

isLoofParent(Node temp)的代码

    private Node isLoofParent(Node temp) {
        if (temp.left == null && temp.right != null) {
            temp = temp.right;
            return temp;
        }
        if (temp.left != null && temp.right == null) {
            temp = temp.left;
            return temp;
        }
        return null;
    }

代码还是比较low的,逻辑也简单,不解释了。

情况3:左右都有子树

按中序找删除节点后面节点的第一个子女,填补删除节点,然后删除删除节点后面节点的第一个子女。说的有点绕,看下面的图:
在这里插入图片描述
以上方法,填补后转换为情况1或情况2

附上完整的删除方法,都比较简单,因为情况有限,每种情况做了if就找到了,就是感觉有点low

    public void delete(int val) {
        Node temp = root;
        Node parent = null;
        String position = null;
        while (val != temp.data) {
            if (val < temp.data) {
                parent = temp;
                position = "left";
                temp = temp.left;
                if (temp == null) {
                    return;
                }
            }
            if (val > temp.data) {
                parent = temp;
                position = "right";
                temp = temp.right;
                if (temp == null) {
                    return;
                }
            }
        }

        //情况1:检查删除的节点是否是叶子节点
        if (isLoof(temp)) {
            if (position.equals("left")) {
                parent.left = null;
            } else {
                parent.right = null;
            }

            return ;
        }

        //判断是否是情况2
        Node next = isLoofParent(temp);
        if (next != null) {
            if (position.equals("left")) {
                parent.left = next;
            } else {
                parent.right = next;
            }
            temp.left = null;
            temp.right = null;

            return ;
        }


        //检查是否是情况3
        if (temp.left != null && temp.right != null) {
            parent = temp;
            position = "right";
            Node rNode = temp.right;
            while (rNode.left != null) {
                parent = rNode;
                position = "left";
                rNode = rNode.left;
            }
            int tempData = rNode.data;
            delete(rNode.data);
            temp.data = tempData;
        }
    }

2.4 二叉查找树的遍历

遍历树可以使用递归,很简单方便,但是可能执行效率稍低。

中序遍历的代码如下,前序和后序只需要换下输出语句的位置即可

public void inOrder() {
    this.inOrder(root);
}

private void inOrder(Node root) {
    if (root == null) {
        return;
    }
    inOrder(root.left);
    System.out.println(root.data + " ");
    inOrder(root.right);
}

三、二叉查找树的查找效率分析

对一个高度为h的二叉查找树来说,查找、删除和插入的时间都是O(h)。效率主要取决于树的高度,相当于2分查找,平均查找长度为O(log2n)(不知道怎么把n上标,理解就好)

因此二叉查找树的查找效率也是比较高的,与2分法还有点不同。二分法的查找树是唯一的,但是二叉查找树会根据插入的不同顺序生成不同的树。

有的时候二叉查找树会生成一种极端情况,如下所示:

在这里插入图片描述
在这种情况下,相当于是一个链表,查找效率就会受到严重影响。

为了解决这个问题,又出现了平衡二叉树,下一节说一下平衡二叉树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值