二叉查找树

二叉排序树(Binary Sort Tree)又称二叉查找树(Binary Search Tree),亦称二叉搜索树。本文主要介绍二叉查找树的基本操作。

1. 二叉查找树的性质

1.1 定义

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
这里写图片描述

1.2 二叉查找树的性质
  1. 在由N个随机键构造的二叉查找树中,查找命中平均所需的比较次数为~2lnN(约为1.39lgN);
  2. 在由N个随机键构造的二叉查找树中插入操作和查找未命中平均所需的比较次数为~2lnN(约为1.39lgN);
  3. 在一棵二叉查找树中,所有操作在最坏情况下所需的时间都和树的高度成正比。
1.3 二叉查找树的优点

二叉排序树是一种比较有用的折衷方案。
数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦。
链表与之相反,删除和插入元素很快,但查找很慢。
二叉排序树就既有链表的好处,也有数组的好处。
在处理大批量的动态的数据是比较有用。

2. 二叉查找树的实现

二叉查找树的节点类:

private class Node{
        private Key key;//键
        private Value value;// 值
        private Node left,right;//指向子树的链接
        private Node parent; //指向父节点
        private int N;//以该节点为根的子树的节点总数
        public Node(Key key, Value value, int n) {
            this.key = key;
            this.value = value;
            this.N = n;
        }
    }

获取指定节点为根节点的二叉查找树的节点总数:

/**
     * 获取该查找二叉树的节点总数
     * @return 返回节点总数
     */
    public int size(){
        return size(root);
    }

    /**
     * 获取以node为根节点的子树的节点总数
     * @param node 子树的根节点
     * @return 返回节点总数
     */
    private int size(Node node) {
        if(node == null){
            return 0;
        }
        else return node.N;
    }

2.1 查找操作

这里写图片描述
2.1.1 查找的递归操作

/**
     * 递归查找
     * 在二叉树中查找键key所对应的值
     * @param key 键
     * @return 返回键所对应的值
     */
    public Value get(Key key){
        return get(root,key);
    }

    /**
     * 在以node为根节点的子树中获取键为key的值
     * @param node 要查找的子树的根节点
     * @param key 要查找的键
     * @return 返回键所对应的值
     */
    private Value get(Node node, Key key) {
        //如果node为空即所查找的子树不存在,则返回null
        if(node == null){
            return null;
        }
        //将要查找的键和子树的根节点比较
        int cmp = key.compareTo(node.key);
        //如果要查找的键小于子树的根节点的键,则递归查找左子树
        if(cmp < 0){
            return get(node.left,key);
        }
        //如果要查找的键大于子树的根节点的键,则递归查找右子树
        else if(cmp > 0){
            return get(node.right,key);
        }else
            return node.value;//键和根节点的键相同,返回根节点的值
    }

2.1.2 查找的非递归操作

/**
     * 非递归法查找
     * @param key
     * @return
     */
    public Node search(Key key){
        if(root == null){
            return null;
        }
        //从根节点开始搜索
        Node current = root;
        while(current != null){
            int cmp = key.compareTo(current.key);
            if(cmp < 0){
                //向左子树搜索
                current = current.left;
            }
            else if(cmp > 0){
                //向右子树搜索
                current = current.right;
            }else{
                return current;
            }
        }
        return null;
    }

2.2 插入操作

这里写图片描述
2.2.1 插入的递归操作

/**
     * 递归法添加新节点
     * 查找key,找到则更新它,否则为他创建一个新节点
     * @param key 要添加的节点的键
     * @param val 要添加节点的值
     */
    public void put(Key key,Value val){
        root = put(root,key,val);
    }

    /**
     * 在以node为跟的子树中添加节点,如果key存在于以node为根的子树中,则更新该节点,
     * 否则在该子树 中添加节点
     * @param node
     * @param key
     * @param val
     * @return 返回添加的节点的父节点
     */
    private Node put(Node node, Key key, Value val) {
        if(node == null){
            return new Node(key,val,1);
        }
        int cmp = key.compareTo(node.key);
        if(cmp < 0){
            node.left = put(node.left, key, val);
        }
        else if(cmp > 0){
            node.right = put(node.right, key, val);
        }
        else node.value = val;
        node.N = size(node.left) + size(node.right) + 1;
        return node;
    }

2.2.2 插入的非递归操作

/**
     * 非递归法在二叉排序树中添加新节点
     * @param key
     * @param val
     */
    public void add(Key key, Value val){
        if(root == null){
            root = new Node(key, val, 1);
        }else{
            Node current = root;
            Node parent = null;
            int cmp;
            //搜索合适的叶子节点添加新节点
            do{
                parent = current;
                cmp = key.compareTo(current.key);
                if(cmp > 0){
                    //以右子节点为当前节点
                    current = current.right;
                }
                else if(cmp < 0){
                    current = current.left;
                }
                else
                    break;
            }while(current != null);
            //创建新节点
            Node newNode = new Node(key, val, 1);
            if(cmp > 0){
                //新节点为父节点的右子节点
                parent.right = newNode;
            }
            else if(cmp < 0){
                //新节点为父节点的左孩子
                parent.left = newNode;
            }else//跟新父节点的值
                current.value = val;
            current.N = size(current.left) + size(current.right) + 1;
        }
    }

2.3 获取最大最小值操作

/**
     * 获取二叉树中的最小键
     * @return 返回最小键
     */
    public Key min(){
        return min(root).key;
    }
    /**
     * 获取以node为根节点的自述中的最小键
     * @param node 要查找的子树
     * @return 返回最小键所对应的节点
     */
    private Node min(Node node) {
        if(node.left == null){
            return node;
        }else
            return min(node.left);
    }
    /**
     * 获取二叉树中的最大键
     * @return 返回最大键
     */
    public Key max(){
        return max(root).key;
    }
    /**
     * 获取以node为根节点的自述中的最大键
     * @param node 要查找的子树
     * @return 返回最大键所对应的节点
     */
    private Node max(Node node) {
        if(node.right == null){
            return node;
        }else
            return min(node.right);
    }

2.4 删除操作

2.4.1 递归法删除

这里写图片描述

/**
     * 删除最小节点
     */
    public void deletMin(){
        root = deleteMin(root);
    }
    /**
     * 删除node为根的子树中的最小节点
     * @param node
     * @return 返回删除节点的父节点的左孩子节点
     */
    private Node deleteMin(Node node) {
        if(node.left == null){
            return node.right;
        }
        node.left = deleteMin(node.left);
        node.N = size(node.left) + size(node.right) + 1;
        return node;
    }
    /**
     * 递归法删除键key所对应的节点
     * @param key
     */
    public void delete(Key key){
        root = delete(root,key);
    }

    /**
     * 递归法删除节点
     * @param node
     * @param key
     * @return
     */
    private Node delete(Node node, Key key) {
        if(node == null){
            return null;
        }
        int cmp = key.compareTo(node.key);
        if(cmp < 0){
            node.left = delete(node.left, key);
        }
        else if(cmp > 0){
            node.right = delete(node.right, key);
        }
        else{
            if(node.right == null){
                return node.left;
            }
            if(node.left == null){
                return node.right;
            }
            Node t = node;
            //获取要删除节点所在子树的最小值
            node = min(t.right);
            node.right =t.right;
            node.left = t.left;
        }
        node.N = size(node.left) + size(node.right) + 1;
        return node;
    }

2.4.2 非递归法删除

对于二叉查找树的删除操作分三种情况:

1、如果结点为叶子结点(没有左、右子树),此时删除该结点不会改变树的结构,直接删除即可,并修改其父结点指向它的引用为null.
这里写图片描述
2、如果被删除的节点只有左孩子或者只有右孩子,那么删除该节点后,树的整体结构并不会变化,只需要将被删除节点的左孩子或者右孩子连接到被删除节点的父节点即可。

这里写图片描述

3、被删除的节点既有左孩子又有右孩子,那么通常有两种方法来保持树的有序性:

  • 以被删除节点为根节点,在它的右子树中查找最小节点,然后将此最小节点从原来的子树中删除,将被删除节点用此最小节点置换;
  • 以被删除节点为根节点,在它的左子树中查找最大节点,然后将此最大节点从原来的子树中删除,将被删除节点用此最大节点置换。

这里写图片描述

/**
     * 非递归法删除指定键所对应的节点
     * @param key
     */
    public void remove(Key key){
        //获取要删除的节点
        Node node = (Node) search(key);
        //如果要删除的节点不存在,直接返回
        if(node == null){
            return;
        }
        //如果要删除节点的左右子树都为空
        if(node.left == null && node.right == null){
            //如果要删除的节点是根节点
            if(node == root){
                root = null;
            }else{
                //删除的节点是父节点的左孩子
                if(node.parent.left == node){
                    //将父节点的左孩子置为空
                    node.parent.left = null;
                }else{
                    //删除的节点是父节点的右孩子
                    node.parent.right = null;
                }
                node.parent = null;
            }
        }
        //如果要删除的节点的左子树为空,右子树不为空
        if(node.left == null && node.right != null){
            //被删除节点是根节点
            if(node == root){
                root = node.right;
            }else{
                //删除的节点是父节点的左孩子
                if(node.parent.left == node){
                    //将父节点的左孩子置为被删除节点的右孩子
                    node.parent.left = node.right;
                }else{
                    //删除的节点是父节点的右孩子,则父节点的右孩子指向被删除节点的右孩子
                    node.parent.right = node.right;
                }
                //让被删除节点的右孩子的父节点指向被删除节点的父节点
                node.right.parent = node.parent;
            }
        }
        //如果要删除的节点的右子树为空,左子树不为空
        if(node.left != null && node.right == null){
            //要删除的是根节点
            if(node == root){
                root = node.left;
            }else{
                //要删除的节点是父节点的左孩子
                if(node.parent.left == node){
                    //将父节点的左孩子置为被删除节点的zuo孩子
                    node.parent.left = node.left;
                }else{
                    //要删除的节点是父节点的右孩子
                    node.parent.right = node.left;
                }
                //让被删除节点的左孩子的父节点指向被删除节点的父节点
                node.left.parent = node.parent;
            }
        }
        //被删粗节点的左右子树都不为空
        else{
            //leftMax保存左子树中最大键的节点
            Node leftMax = node.left;
            //搜索左子树的最大键
            while(leftMax.right != null){
                leftMax = leftMax.right;
            }
            //从原来的子树中删除leftMax
            leftMax.parent.right = null;
            //让leftMax的父节点指向node的父节点
            leftMax.parent = node.parent;
            //被删除的节点是父节点的左子节点
            if(node.parent.left == null){
                //让父节点的左孩子指向leftMax
                node.parent.left = leftMax;
            }else{
                //被删除的节点是父节点的右子节点,让父节点的右孩子指向leftMax
                node.parent.right = leftMax;
            }
            leftMax.left = node.left;
            leftMax.right = node.right;
            node.left = node.right = node.parent = null;
        }
    }

2.5 广度优先遍历

二叉树的遍历参考线索二叉树(上)。

广度优先遍历的代码如下:

public List<Node> breadthFirst(){
        Queue<Node> queue = new ArrayDeque();
        List<Node> list = new ArrayList();
        if(root != null){
            //将根节点加入队列
            queue.offer(root);
        }
        while(!queue.isEmpty()){
            //访问根节点
            list.add(queue.peek());
            //队列的头元素出队
            Node p = queue.poll();
            //如果左子树不为空,访问之
            if(p.left != null){
                queue.offer(p.left);
            }
            //如果右子树不为空,访问之
            if(p.right != null){
                queue.offer(p.right);
            }
        }
        return list;
    }

参考文献:

  1. 《java 算法》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二叉查找树是一种具有特定性质和规则的二叉树。它要么是一棵空树,要么满足以下条件:如果它的左子树不为空,则左子树上所有节点的值都小于根节点的值;如果它的右子树不为空,则右子树上所有节点的值都大于根节点的值;同时,它的左子树和右子树也都是二叉查找树。通过这种特性,二叉查找树可以用来进行高效的查找操作。 在二叉查找树中,查询成绩的操作可以通过比较目标成绩与根节点的值来进行。如果目标成绩小于根节点的值,则继续在左子树中进行查找;如果目标成绩大于根节点的值,则继续在右子树中进行查找。通过不断比较和移动,最终可以找到目标成绩所对应的节点。 需要注意的是,二叉查找树的查询效率取决于树的结构。当二叉查找树退化成链状时,查询效率将下降到与顺序查找相同的量级。因此,在构建二叉查找树时,需要尽量保持树的平衡,以提高查询效率。 引用\[1\]提到了二叉排序树的查询步数不会超过树的最大深度,而引用\[2\]则介绍了二叉查找树相对于链表和数组的高效查找方式。因此,通过构建合适的二叉查找树,可以实现对成绩的高效查询操作。 #### 引用[.reference_title] - *1* [(十三)数据结构动态查找之二叉排序树查找](https://blog.csdn.net/qq_45849888/article/details/104301071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [java实现二叉查找树](https://blog.csdn.net/blue_x2/article/details/120681747)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值