【数据结构】二分搜索树

news2024/9/21 14:27:39

集合java.util包下的常用子类,集合无非就是各种数据结构的应用。集合存在的目的就是为了将数据高校进行读写,无论哪种具体的集合无外乎CURD。

Collection->单个元素保存的父接口。

List->可以保存重复的单个元素。

Set->保存单个不重复元素。

Queue->队列接口,操作受限的线性表。

Deque->双端队列,使用此接口来表示栈或者队列。

一、JDK中的Map和Set

核心的应用场景:高效的搜索。

Set集合只能保存单个元素。集合中所有元素是唯一的不重复的。

Map集合一次保存一个键值对对象(key=vlaue)这样映射对象,用于统计某个元素以及其出现的次数。数学中的映射其实就是Java中的Map集合,存储的都是一个key对应一个value的映射关系。在Map映射关系中,key是唯一的,value的值可以重复。

1.1Map接口的使用

HashMap底层基于哈希表(数组+链表)的实现。

TreeMap底层基于二分搜索平衡树的实现(BeeTree)

(1)元素的添加操作

put(K key, V value): 将键值对key和value保存到当前的Map

在Map集合中,key值不能重复,若put时,发现key重复,则会将当前map中的key对应的value更新为此刻put的value值。value是可以重复的。并且在Map中键值对是无序的,元素的保存顺序和添加顺序无关。

    public static void main(String[] args) {
        Map<String,String> namesMap = new HashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨","宋江");
        namesMap.put("浪子","燕青");
        namesMap.put("及时雨","燕青");
        System.out.println(namesMap);
    }

在HashMap中key和value都可以为null,并且key值只能有一个null。

    public static void main(String[] args) {
        Map<String,String> namesMap = new HashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put(null,null);
        namesMap.put("及时雨",null);
        namesMap.put(null,"哈哈");
        System.out.println(namesMap);
    }

 在TreeMap中key值不能为空,value值可以为空,并且key必须事先Comparable接口或者通过TreeMap的构造方法传入比较器对象,TreeMap的Key值必须是可以比较的。

    public static void main(String[] args) {
        Map<String,String> namesMap = new TreeMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨",null);
        System.out.println(namesMap);
        namesMap.put(null,null);

    }

LinkedHashMap:给普通的HashMap加了个链表,这个链表就保存了元素的添加顺序。

    public static void main(String[] args) {
        Map<String,String> namesMap = new LinkedHashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨",null);
        namesMap.put(null,null);
        System.out.println(namesMap);
    }

(2)在Map集合中查询特定的值 

get(K key):根据Key值搜索对应的value值,若没有对应的key值则返回null。

getOrDefault(K key,V defaultVal):根据key值搜索map中对应的value值,若没找到,返回默认值defaultValue。

    public static void main(String[] args) {
        Map<String,String> namesMap = new LinkedHashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨","宋江");
        namesMap.put("浪子","燕青");
        namesMap.put("小乙哥","燕青");
        System.out.println(namesMap.get("浪子"));
        System.out.println(namesMap.getOrDefault("及时雨","0"));
        System.out.println(namesMap.getOrDefault("毛毛雨","0"));
    }

 boolean contains(K key)在当前Map集合中是否包含指定的key值。

boolean contains(V value)在当前Map集合中是否包含指定的value值。

    public static void main(String[] args) {
        Map<String,String> namesMap = new LinkedHashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨","宋江");
        namesMap.put("浪子","燕青");
        namesMap.put("小乙哥","燕青");
        System.out.println(namesMap.containsKey("黑旋风"));
        System.out.println(namesMap.containsValue("江哥"));
    }

(3)删除Map中指定的value和key

remove(K key)删除key值对应的键值对对象,返回删除之前的value值。

boolean remove(K key,V value)删除key值对应的value对象,返回布尔值返回是否删除成功。

clear()清空Map表。

    public static void main(String[] args) {
        Map<String,String> namesMap = new LinkedHashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨","宋江");
        namesMap.put("浪子","燕青");
        namesMap.put("小乙哥","燕青");
        System.out.println(namesMap.remove("黑旋风"));
        System.out.println(namesMap.remove("智多星","李逵"));
        System.out.println(namesMap.remove("智多星","吴用"));
        System.out.println(namesMap);
        namesMap.clear();
        System.out.println(namesMap);
    }

 (4)Map集合的遍历

Map集合的遍历是比较低效的。

for-each循环只能用于lterable接口以及其子类,Map接口和它毫无关系。

要想进行Map集合的遍历,必须先将Map转为Set集合。

Map接口在存储键值对对象时,内部存储的每一个都是Map.Entry。

    public static void main(String[] args) {
        Map<String,String> namesMap = new LinkedHashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨","宋江");
        namesMap.put("浪子","燕青");
        namesMap.put("小乙哥","燕青");
        Set<Map.Entry<String,String>> set = namesMap.entrySet();
        for(Map.Entry<String,String> entry: set) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }

也可以分别输出key和value,这时重复地value会重复输出。 

   public static void main(String[] args) {
        Map<String,String> namesMap = new LinkedHashMap<>();
        namesMap.put("黑旋风","李逵");
        namesMap.put("智多星","吴用");
        namesMap.put("及时雨","宋江");
        namesMap.put("浪子","燕青");
        namesMap.put("小乙哥","燕青"); Set<String> keys = namesMap.keySet();
        Collection<String> values = namesMap.values();
        System.out.println("Key值为 : ");
        for (String s : keys) {
            System.out.print(s +" ");
        }
        System.out.println();
        System.out.println("Values为 : ");
        for (String s : values) {
            System.out.print(s +" ");
        }
    }

1.2Set集合的应用 

最常用的操作就是去重处理

添加方法add(E e),向当前Set集合中添加一个新的元素e,如果集合中没有该元素则成功添加返回true,若存在,添加失败返回false。

删除方法remove(O o),删除指定元素O。

boolean contains(O o)查询当前Set集合是否包含元素o。

1.3 Set集合和Map集合的关系

Set是保存单个不重复元素的集合,Map是保存一对键值对对象的集合。

Set的常用子类如HashSet,TreeSet内部就使用的是对应的Map对象,HashSet就在HashMap的key上保存,TreeSet就在TreeMap的key上保存。

二、二分搜索树

2.1什么是二分搜索树

右基础BST没有结构上的特点,他是一个二叉树,每个结点的值大于其左右左子树的结点值,小于其右子树的结点值,左子树<树根<右子树。

存储的元素必须具备可比较性。

(1)二分搜索树的中序遍历:能得到一个完全升序的数组(有序数组)

(2)在BST查找一个元素是否存在,其实就是一个二分查找,时间复杂度为O(logn)走到空树还没找到,说明该元素不存在。BST在实际的生产工作中有着广泛应用。

2.2创建一个二分搜索树

(1)二分搜索树的内部构建

和正常的二叉树是相通的,创建一个size来记录长度,root作为根节点。

然后TreeNode类中有val表示该节点的值,left表示左子树,right表示右子树。

public class MyBinarySearchTree {
    private int size;
    private TreeNode root;
    private static class TreeNode{
        int val;
        TreeNode left ;
        TreeNode right;
        public TreeNode(int val) {
            this.val = val;
        }
    }
}

(2)插入操作

插入之后的新节点一定是叶子结点,不断比较当要插入的值和树根的大小关系,不断走左右子树,如果值比左子树小就走左子树,值比右子树大就走右子树。直到走到null,就构造新节点放入带插入元素的值。

    public void add(int val){
        root = add(root,val);
    }
    private TreeNode add(TreeNode root, int val) {
        if(root==null){
            size++;
            return new TreeNode(val);
        }
        if(val>root.val){
            root.right = add(root.right,val);
        }
        if(val<root.val){
            root.left = add(root.left,val);
        }
        return root;
    }

(3)判断一个val值是否存在

 判断则创建一个递归,两个终止条件当走到最后root==null,则证明这个二叉搜索树中不包含该val值,则返回false。另一种是当root.val == val,则找到了这个该元素返回true即可。

然后如果值比左子树小就递归左子树,值比右子树大就递归右子树。

    public Boolean contains(int val){
        return contains(root,val);
    }
    private Boolean contains(TreeNode root, int val) {
        if(root==null){
            return false;
        }
        if(root.val == val){
            return true;
        }
        if(val<root.val){
            return contains(root.left,val);
        }else{
            return contains(root.right,val);
        }
    }
    public static void main(String[] args) {
        MyBinarySearchTree bst = new MyBinarySearchTree();
        bst.add(41);
        bst.add(28);
        bst.add(58);
        bst.add(15);
        System.out.println(bst.contains(58));
        System.out.println(bst.contains(22));
    }

 (4)按照节点的深度先序遍历打印BST

主要就是运用二叉树的先序遍历,然后再根据深度加上--。

    public String toString() {
        StringBuilder sb = new StringBuilder();
        generateBSTSting(root,0,sb);
        return sb.toString();
    }
    private void generateBSTSting(TreeNode root, int depth, StringBuilder sb) {
        if(root==null){
            sb.append(generateHeightString(depth)).append("NULL\n");
            return ;
        }
        sb.append(generateHeightString(depth)).append(root.val).append("\n");
        generateBSTSting(root.left,depth+1,sb);
        generateBSTSting(root.right,depth+1,sb);
    }
    private String generateHeightString(int depth){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("--");
        }
        return sb.toString();
    }
    public static void main(String[] args) {
        MyBinarySearchTree bst = new MyBinarySearchTree();
        bst.add(41);
        bst.add(28);
        bst.add(58);
        bst.add(15);
        bst.add(33);
        bst.add(50);
        System.out.println(bst.toString());
    }

 (5)关于最小值

最小值位于左子树的最左侧,一路向左走,碰到第一个root.left == null的结点,root即为最小值结点。输出最小值时直接返回该root即可。

如果是删除最小值,那么就需要创建一个TreeNode类型的right存储root.right然后将root.left = root = null,再将树的结点个数size--最后返回right。

    public int min(){
        if (size == 0) {
            throw new NoSuchElementException("bst is empty!no min");
        }
        TreeNode min = findMinNode(root);
        return min.val;
    }
    public  int removeMin(){
        if (size == 0) {
            throw new NoSuchElementException("bst is empty!cannot removeMin");
        }
        TreeNode minNode = findMinNode(root);
        root = removeMin(root);
        return minNode.val;
    }
    private TreeNode removeMin(TreeNode root) {
        if(root.left==null){
            TreeNode right = root.right;
            root.left = root.right = root = null;
            size--;
            return right;
        }
        root.left = removeMin(root.left);
        return root;
    }
    private TreeNode findMinNode(TreeNode root) {
        if(root.left==null){
            return root;
        }
        return findMinNode(root.left);
    }
    public static void main(String[] args) {
        MyBinarySearchTree bst = new MyBinarySearchTree();
        bst.add(41);
        bst.add(28);
        bst.add(58);
        bst.add(15);
        bst.add(33);
        bst.add(50);
        System.out.println(bst.toString());
        System.out.println(bst.removeMin());
        System.out.println(bst.min());
        System.out.println(bst.toString());
    }

(6)关于最大值

最大值位于右子树的最右侧,一路向右走,碰到第一个root.right == null的结点,root即为最大值结点。

输出最大值和删除最大值和最小值几乎是相同的。

public int max() {
        if (size == 0) {
            throw new NoSuchElementException("bst is empty!no max!");
        }
        TreeNode maxNode = findMaxNode(root);
        return maxNode.val;
    }
    private TreeNode findMaxNode(TreeNode root) {
        if(root.right==null){
            return root;
        }
        return findMaxNode(root.right);
    }
    public int removeMax() {
        if (size == 0) {
            throw new NoSuchElementException("bst is empty!cannot remove max!");
        }
        TreeNode maxNode = findMaxNode(root);
        root = removeMax(root);
        return maxNode.val;
    }
    private TreeNode removeMax(TreeNode root) {
        if(root.right==null){
            TreeNode left = root.left;
            root.left = root = null;
            size--;
            return left;
        }
        root.right = removeMax(root.right);
        return root;
    }
    public static void main(String[] args) {
        MyBinarySearchTree bst = new MyBinarySearchTree();
        bst.add(41);
        bst.add(28);
        bst.add(58);
        bst.add(15);
        bst.add(33);
        bst.add(50);
        System.out.println(bst.toString());
        System.out.println(bst.removeMax());
        System.out.println(bst.toString());
        System.out.println(bst.max());
    }

(7)删除任意结点

首先在public的remove方法中判断一下这个key值是否存在,存在则能删除则调用remove(root,key),不存在则直接返回false即可。

在private的remove方法中有四种情况,第一种是root==null,则直接返回null值。第二种是root.val==key,也就是我们找到了需要删除的结点,这时又分为三种情况,如果他的左节点为空,那么直接将右节点补上即可。如果右节点为空那么直接将左节点补上即可。剩下一种情况便是左右节点都不为空,即选择右子树中最小的结点进行填补,在下面的画图中会讲解。

然后当root.val<key,我们调用root.right = remove(root.right,key)即可。剩下的情况即root.val>key调用root.left = remove(root.left,key)即可。

当我们进行一次一个的遍历,root.val == key时,开始进行右子树中最小节点填补的操作。

 我们需要先行创建一个TreeNode类型的successor变量来存储findMinNode(root.right)中找到的最小值节点。

 然后使successor.right = removeMin(root.right),即删除最小节点后的root的右子树(在这里为空。)

 然后让successor.left = root.left。让successor左节点连接上root的左节点。

 再使root的左右节点以及本身置空 root.left = root.right = root = null。最后返回return successor即可,让他与前面的节点连接上。

    public boolean remove(int key) {
        if(!contains(key)){
            return false;
        }else{
            root = remove(root,key);
            return true;
        }
    }
    private TreeNode remove(TreeNode root, int key) {
        if(root==null){
            return null;
        }
        if(root.val==key){
            if(root.left==null){
                TreeNode right = root.right;
                root.right = root = null;
                size--;
                return right;
            }else if(root.right==null){
                TreeNode left = root.left;
                root.left = root = null;
                size--;
                return left;
            }else{
                TreeNode successor = findMinNode(root.right);
                successor.right = removeMin(root.right);
                successor.left = root.left;
                root.left = root.right = root = null;
                return successor;
            }
        }else if(root.val<key){
            root.right = remove(root.right,key);
        }else {
            root.left = remove(root.left,key);
        }
        return root;
    }
    public static void main(String[] args) {
        MyBinarySearchTree bst = new MyBinarySearchTree();
        bst.add(41);
        bst.add(28);
        bst.add(58);
        bst.add(15);
        bst.add(33);
        bst.add(50);
        System.out.println(bst.toString());
        System.out.println(bst.remove(28));
        System.out.println(bst.toString());
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python在centos7.x下建立虚拟环境

(327条消息) python在centos下安装以及配置_雨师的博客-CSDN博客 https://blog.csdn.net/wtt234/article/details/128172281 python离线环境下安装第三方模块的方法&#xff1a; https://blog.csdn.net/wtt234/article/details/128162292 上篇已经把python在centos下的安装以及…

红蓝对抗--sliver 搭建

使用sliver的优点: 1 支持macos、win、linux上线 2 支持丰富的插件加载扩展,功能选择多,CS已经很普遍了,可以尝试sliver做C2 sliver相关构成: Implant:生成植入的木马 sliver-client :C2的控制端 Sliver Server:C2的控制端,客户端通过gRPC接口与server交互 架构: […

【 java 集合】Collection 接口中的常用方法

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

LeetCode刷题复盘笔记—一文搞懂纯完全背包问题(动态规划系列第十一篇)

今日主要总结一下动态规划背包问题的基础——纯完全背包问题 在Leetcode题库中主要都是0-1背包和完全背包的应用问题&#xff0c;所以主要掌握这两个背包问题 题目&#xff1a;纯完全背包问题 题目描述&#xff1a; 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是w…

List——顺序表与链表(二)

文章目录前言一、链表概念及结构二、LinkedList与链表1.什么是LinkedList2.LinkedList的常用方法3.链表的遍历三.实现自己的LinkedList四.ArrayList和LinkedList的区别与优缺点总结前言 上一篇文章中&#xff0c;介绍了List接口以及ArrayList的使用&#xff0c;并且进行了简单…

ByteTrack多目标追踪论文阅读

paper:ByteTrack: Multi-Object Tracking by Associating Every Detection Box code:ByteTrack 一.摘要 多目标追踪的目的是识别视频中物体或对象的位置和身份&#xff0c;也就是说&#xff0c;不同于目标检测的是&#xff0c;追踪问题可以分为两个任务&#xff1a;1&#x…

(附源码)springboot平衡膳食小程序 毕业设计 250859

基于springboot平衡膳食小程序 摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;平衡膳食小程序被用户普遍使…

物联网设备WIFI模块实现

问题 如何在设备上进行 Wifi 编程&#xff1f; LwIp (Light Weight IP) 简介 LwIp 是轻量化的 TCP/IP&#xff0c;是一个小型开源的 TCP/IP 协议栈 LwIp 的设计目标是用较少的资源实现较完整的 TCP/IP 协议栈 LwIp 能在操作系统中运行&#xff0c;也能在无操作系统的情况下…

Java—异常体系

文章目录异常和错误java异常的分类&#xff1a;非运行时异常运行时异常受检异常&#xff08;非运行时异常&#xff09;如何处理&#xff1f;1、try catch finally为什么要用try catch finally2、throwsThrow和Throws的区别JVM是如何处理异常的try-catch-finally中哪个部分可以省…

项目管理逻辑:项目经理如何掌控项目生命周期, 才能避免身心俱疲?

目录 1.项目生命周期 2.预测型项目周期 3.迭代型项目周期 3.1.初始阶段 3.2.精化阶段 3.3.构建阶段 3.4.交付阶段 4.增量型生命周期 5.敏捷开发 5.根据具体项目使用合理的开发方式 1.项目生命周期 2.预测型项目周期 预测型项目周期就是软件开发领域的瀑布流模型&…

【Python自然语言处理】概率上下文无关文法(PCFG)及神经网络句法分析讲解(图文解释 超详细)

觉得有帮助或有疑问麻烦点赞关注收藏后评论区私信留言~~~ 一、句法分析 句法分析&#xff08;syntactic parsing或者parsing&#xff09;是识别句子包含的句法成分要素以及成分之间的内在关系&#xff0c;一般以句法树来表示句法分析的结果。实现该过程的应用称作句法分析器&a…

三维模型的简化算法研究(任务书+lunwen+外文翻译+源码+查重报告)

目 录 第1章 绪论 1 1.1 研究背景 1 1.2 内存网格简化算法 1 1.2.1 顶点聚类 1 1.2.2 区域合并 2 1.2.3 迭代式消除 4 1.2.4 随机重采样 5 1.3 三维模型简化算法 6 1.3.1 分片简化 6 1.3.2 使用外部数据结构 7 1.3.3 网格批处理 9 1.3.4 流式简化 10 1.3.5 小结 11 1.4 自适应等…

【前沿技术RPA】 一文了解UiPath Orchestrator的触发器和监听器

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;UiPath &#x1f980;专栏简介&#xff1a;UiPath在传统的RPA&#xff08;Robotic…

公众号接口免费调用

公众号接口免费调用 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xff09;…

Express:CORS 跨域资源共享

CORS 跨域资源共享 Staticfile CDN 1. 接口的跨域问题 刚才编写的 GET 和 POST接口&#xff0c;存在一个很严重的问题&#xff1a;不支持跨域请求。 解决接口跨域问题的方案主要有两种&#xff1a; 1.CORS&#xff08;主流的解决方案&#xff0c;推荐使用&#xff09; 2.J…

Excel - 选择性粘贴和单元格引用规则

最基本的功能&#xff0c;才是最重要的功能&#xff0c;一定好好好理解。 最常用的复制、粘贴功能&#xff0c;在Excel里赋予了更多的选项&#xff0c;也变得更加强大。Excel里一般可复制的内容都是只单元格区域&#xff0c;其组成包括数据(文本或数值)、格式、公式、有效性验证…

FileZilla Server.xml 如何配置

要从xp.cn说起&#xff0c;因为它自带了一个ftp服务器。我点击配置后&#xff0c;就会直接用记事本打开FileZilla Server.xml让配置。我就很懵。不知道如何下手。 弹出的配置界面如下&#xff1a; 如何配置FileZilla Server.xml 我一开始想到去xp.cn找文档&#xff0c;可惜…

初探基因组组装——生信原理第四次实验报告

初探基因组组装——生信原理第四次实验报告 文章目录初探基因组组装——生信原理第四次实验报告实验目的实验内容实验题目第一题题目用SOAPdenovo 进行基因组组装评估组装质量第二题题目Canu组装Hifiasm组装基于nucmer的基因组比对过滤比对结果转换为可读性强的tab键分隔的文件…

期末论文LaTeX模板

简介 这学期的其中一门课程结束了&#xff0c;考核形式是写一篇中文的课程论文。于是&#xff0c;我使用了Elegant LaTeX 系列的模板。 小编已经把最新版本的三份模板放到公众号&#xff0c;后台回复[课程论文模板]即可获取。也欢迎大家去 GitHub 给贡献者点 star&#xff01;…

【从零开始玩量化13】quantstats:分析你的量化策略

背景 之前总结了一些获取量化数据的途径&#xff0c;数据是一个量化策略的“原材料”&#xff0c;接下来要考虑的问题就是如何使用这些数据。 本文&#xff0c;介绍一个量化指标分析工具quantstats&#xff0c;利用它可以很方便的分析你的策略。 Github地址&#xff1a;https…