数据结构与算法学习笔记(八)二叉查找树

1.二叉查找树(Binary Search Tree)

概念:是一棵空树,或者是具有下列性质的二叉树
  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值
  • 任意节点的左、右子树也分别为二叉查找树
  • 没有键值相等的节点
示例图:

在这里插入图片描述

查找操作:

在二叉查找树中查找一个给定的值 k ,与二分查找很类似
首先拿 k 与树根的值进行比较,如果 k 比根的值大,则在根的右子树中查找,否则在根的左子树中查找,重复此过程,直到找到 k 和遇到空节点为止

查找的过程:

  1. 若二叉树是空树,则查找失败
  2. 若 k 等于根节点的数据,则查找成功
  3. 若 k 小于根节点的数据,则递归查找其左子树
  4. 若 k 大于根节点的数据,则递归查找其右子树

注意:
查找最小值:较小的值总是位于左节点上,只需要遍历左子树查找最后一个节点即可

查找最大值:较大的值总是位于右节点上,只需要遍历右子树查找最后一个节点即可

删除操作:
  • 如果删除的是叶节点,可以直接删除
  • 如果被删除的元素有一个子节点,可以将子节点直接移到被删除元素的位置
  • 如果有两个子节点,这时候就采用中序遍历,找到待删除的节点的后继节点,将其与待删除的节点互换,此时待删除节点的位置已经是叶子节点,可以直接删除

在这里插入图片描述

插入操作:

当向树中插入一个新的节点时,该节点将总是作为叶子节点。将这个新的节点称为节点 n,而遍历的当前节点称为节点 c。开始时,节点 c 为 BST 的根节点。
则定位节点 n 父节点的步骤如下:

  1. 如果节点 c 为空,则节点 c 的父节点将作为节点 n 的父节点。如果节点 n 的值小于该父节点的值,则节点 n 将作为该父节点的左孩子;否则节点 n 将作为该父节点的右孩子
  2. 比较节点 c 与节点 n 的值,如果节点 c 的值与节点 n 的值相等,则说明用户在试图插入一个重复的节点。解决办法可以是直接丢弃节点 n,或者可以抛出异常
  3. 如果节点 n 的值小于节点 c 的值,则说明节点 n 一定是在节点 c 的左子树中。则将父节点设置为节点 c,并将节点 c 设置为节点 c 的左孩子,然后返回至第 1 步
  4. 如果节点 n 的值大于节点 c 的值,则说明节点 n 一定是在节点 c 的右子树中。则将父节点设置为节点 c,并将节点 c 设置为节点 c 的右孩子,然后返回至第 1 步

在这里插入图片描述
在这里插入图片描述

2. 代码实现

package com.example.data_structure;

import java.util.ArrayList;
import java.util.List;

/**
 * 描述:
 * 二叉查找树
 *
 * @author XueGuCheng
 * @create 2022-10-26 21:18
 */
public class BinarySearchTree<E extends Comparable<E>> {

    /**
     * 根节点
     */
    private Node<E> root;

    /**
     * 树的节点数量
     */
    private int size;



    private static class Node<E> {
        /**
         * 节点值
         */
        E data;

        /**
         * 左节点
         */
        Node<E> left;

        /**
         * 右节点
         */
        Node<E> right;

        /**
         * 父节点
         */
        Node<E> parent;

        public Node(E data) {
            this.data = data;
        }

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

    /**
     * 查询容量
     * @return 容量
     */
    public int size(){
        return size;
    }

    /**
     * 是否为空
     * @return true:空;false:非空
     */
    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 添加元素
     * @param e 添加的元素值
     */
    public void add(E e){
        root = add(root,e);
        size++;
    }

    /**
     * 添加元素
     * @param root 当前跟节点
     * @param e 添加的元素值
     * @return 添加元素后的树
     */
    private Node<E> add(Node<E> root,E e){
        if (root != null){
            if (e.compareTo(root.data) > 0){
                root.right = add(root.right, e);
                root.right.parent = root;
            }else if (e.compareTo(root.data) < 0){
                root.left = add(root.left, e);
                root.left.parent = root;
            }
            return root;
        }else {
            return new Node<>(e);
        }
    }

    /**
     * 前序遍历
     */
    public List<E> preorderTraversal() {
        ArrayList<E> list = new ArrayList<>();
        preTraversal(root,list);
        return list;
    }

    private void preTraversal(Node<E> root, List<E> list) {
        if (root != null){
            // 前序遍历
            list.add(root.data);
            preTraversal(root.left,list);
//            // 中序遍历
//            list.add(root.data);
            preTraversal(root.right,list);
            // 后序遍历
//            list.add(root.data);
        }
    }

    /**
     * 删除指定元素
     * @param e 删除的元素值
     * @return 是否删除成功
     */
    public boolean remove(E e){
        Node<E> node = find(root, e);
        if (node == null){
            return false;
        }
        fastRemove(node);
        size--;
        return true;
    }



    /**
     * 在以root为根节点的树中查找值为e的节点
     * @param root 当前跟节点
     * @param e 查找的元素值
     * @return 查找到对应的节点
     */
    private Node<E> find(Node<E> root,E e){
        if (root != null){
            if (e.compareTo(root.data) > 0){
                return find(root.right,e);
            }else if (e.compareTo(root.data) < 0){
                return find(root.left,e);
            }else {
                return root;
            }
        }
        return root;
    }

    /**
     * 快速删除
     * @param node 待删除节点
     */
    private void fastRemove(Node<E> node) {
        if (node.left != null && node.right != null){
            // 使用后继节点代替待删除节点
            Node<E> minimum = minimum(node.right);
            node.data = minimum.data;
            node = minimum;
        }
        Node<E> replacement = node.left != null ? node.left : node.right;
        if (replacement != null){
            replacement.parent = node.parent;
        }
        // 待删除节点没有父节点
        if (node.parent == null){
            root = replacement;
        }else {
            if (node == node.parent.left){
                node.parent.left = replacement;
            }else {
                node.parent.right = replacement;
            }
        }

        node.left = node.right = node.parent = null;
    }

    /**
     * 找到当前节点下的最小子节点
     * @param root 当前节点
     * @return 最小子节点
     */
    private Node<E> minimum(Node<E> root){
        Node<E> cur = root;
        while (cur.left != null){
            cur = cur.left;
        }
        return cur;
    }
    
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值