Map和Set及哈希--的奥秘(详解)

news2024/11/24 1:13:10

目录:

一 搜索树:

二. 搜索相关概念 

三.Map 的说明 
四. Set 的说明
五.哈希表:

一 搜索树:

1.概念:

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

(1). 若它的左子树不为空,则 左子树上所有节点的值都小于根节点的值
(2). 若它的右子树不为空,则 右子树上所有节点的值都大于根节点的值
(3). 它的左右子树也分别为二叉搜索树

2.操作:

(1).查找:

你要插入,删除都要先找到要操作的节点;要找到要操作的位置简单来说就是,要查找的key大就往右边找,key小就往左边找,要满足搜索二叉树的概念。

时间复杂度:最坏情况:O(N)

                      最好情况: O (logN)

图解:

  

代码:

/**
     * 搜索:
     * 时间复杂度:最坏情况 O(N),  最好情况:O(logN)
     * @param key
     * @return
     */
    public TreeNode search(int key) {

        TreeNode cur = root;
        if (cur == null) {
            return null;
        }

        while (cur != null) {
            if (cur.val > key) {
                cur = cur.left;
            }else if (cur.val < key) {
                cur = cur.right;
            }else {
                return cur;
            }
        }
        return null;
    }
(2).插入:
插入,为空就直接插入不为空就找到要插入的位置然后插入,这个时候定义一个变量parent来记录插入的前一个位置
图解:
代码:
 //插入
    public void insert(int key) {
        if (root == null) {
            root = new TreeNode(key);
            return;
        }

        TreeNode cur = root;
        TreeNode node = new TreeNode(key);
        TreeNode parent = null;

        //找到要插入的位置
        while (cur != null) {
            if (cur.val > key) {
                parent = cur;
                cur = cur.left;
            }else if (cur.val < key) {
                parent = cur;
                cur = cur.right;
            }else {
                //搜索二叉树,不会插入相同的节点
                return;
            }
        }
        //插入
        if (parent.val > key) {
            parent.left = node;
        }else {
            parent.right = node;
        }
    }

(3) 删除(难点,情况比较多):
分为三种情况:
要删除节点左边为空:
(1). cur 是 root,则 root = cur.right
(2). cur 不是 rootcur 是 parent.left,则 parent.left = cur.right
(3). cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
要删除节点右边为空:(情况二和情况一差不多)
(1).cur 是 root,则 root = cur.left
(2).cur 不是 rootcur 是 parent.left,则 parent.left = cur.left
(3).cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
要删除节点左右边都为空:这个我们采用替换删除,就是 左树找到最大值,或者右树找到最小值,来替换,并删除替换节点的原位置。
下图用的是左树找到最大值: (而我写的代码是,t往右边走找最小值)
  /**删除有三种情况:
     *   1.cur的左边为空
     *   2.cur的右边为空
     *   1.cur的左右两边都不为空
     */
    private void removeNode(TreeNode parent, TreeNode cur) {
        if (cur.left == null) {
            if (cur == root) {
                root = cur.right;
            }else if (cur == parent.left) {
                parent.left = cur.right;
            }else {
                parent.right = cur.right;
            }

        }else if (cur.right == null) {
            if (cur == root) {
                root = cur.left;
            }else if (cur == parent.left) {
                parent.left = cur.left;
            }else {
                parent.right = cur.left;
            }
        }else {
            TreeNode tp = cur;
            TreeNode t = cur.right;
            while (t.left != null) {
                tp = t;
                t = t.left;
            }

            cur.val = t.val;//交换

            //删除
            if (tp.left == t) {
                tp.left = t.right;
            }else {
                tp.right = t.right;
            }
        }

    }
}

3.性能分析:

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n 个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即 结点越深,则比较次数越多

4.搜索树 java 类集的关系:

1.TreeMap TreeSet java 中利用搜索树实现的 Map 和 Set实际上用的是红黑树,而红黑树是一棵近似平衡的二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证



二. 搜索相关概念 :

1.概念及场景

Mapset是一种专门用来进行搜索的容器或者数据结构其搜索的效率与其具体的实例化子类有关

2.模型:

一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以模型会有两种:

1. key 模型

2.Key-Value 模型




三.Map 的说明 
1 关于 Map 的说明:
Map是一个接口类 ,该类 没有继承自Collection ,该类中 存储的是<K,V>结构的键值对 ,并且 K 定是 唯一的,不能重复
2.关于 Map.Entry<K, V> 的说明:
Map.Entry<K, V> 是Map内部实现的用来 存放<key, value>键值 对映射关系的 内部类 该内部类中主要提供了 <key, value>的获取,value的设置以及Key的比较方式。
注意:Map.Entry<K,V>并没有提供设置Key的方法
3 Map 的常用方法说明
总结:
1. Map是一个接口,不能直接实例化对象,如果实例化对象只能实例化其实现类TreeMap或者HashMap
2. Map中存放键值对的Key是唯一的value是可以重复的
3. 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,
value可以为空。 是HashMap的key和value都可以为空。
4. Map中的Key可以全部分离出来,存储到Set来进行访问(因为Key不能重复)
5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)
6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行 重新插入。
7. TreeMapHashMap的区别:
4 TreeMap 的使用:
  public static void main(String[] args) {
        Map<String, String> m = new TreeMap<>();
// put(key, value):插入key-value的键值对
// 如果key不存在,会将key-value的键值对插入到map中,返回null
        m.put("林冲", "豹子头");
        m.put("鲁智深", "花和尚");
        m.put("武松", "行者");
        m.put("宋江", "及时雨");
        String str = m.put("李逵", "黑旋风");
        System.out.println(m.size());
        System.out.println(m);

// put(key,value): 注意key不能为空,但是value可以为空
// key如果为空,会抛出空指针异常
//m.put(null, "花名");

        str = m.put("无名", null);
        System.out.println(m.size());
// put(key, value):
// 如果key存在,会使用value替换原来key所对应的value,返回旧value
        str = m.put("李逵", "铁牛");


// get(key): 返回key所对应的value
// 如果key存在,返回key所对应的value
// 如果key不存在,返回null

        System.out.println(m.get("鲁智深"));
        System.out.println(m.get("史进"));

//GetOrDefault(): 如果key存在,返回与key所对应的value,如果key不存在,返回一个默认值
        System.out.println(m.getOrDefault("李逵", "铁牛"));
        System.out.println(m.getOrDefault("史进", "九纹龙"));
        System.out.println(m.size());
    }

结果:




四. Set 的说明:

1 常见方法说明
注意:
(1). Set是继承自Collection的一个接口类
(2). Set中只存储了key,并且要求key一定要唯一
(3). TreeSet的底层是使用Map来实现的,其使用keyObject的一个默认对象作为键值对插入到Map中的
(4) . Set最大的功能就是对集合中的元素进行去重
(5). 实现Set接口的常用类有TreeSetHashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序。
(6). Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
(7). TreeSet中不能插入null的key,HashSet可以
2 TreeSet 的使用案例:
这里注意: TreeSet可以使用Interator遍历。
 public static void main(String[] args) {

        Set<String> s = new TreeSet<>();
// add(key): 如果key不存在,则插入,返回ture
// 如果key存在,返回false
        boolean isIn = s.add("apple");
        s.add("orange");
        s.add("peach");
        s.add("banana");
        System.out.println(s.size());
        System.out.println(s);


        isIn = s.add("apple");
// add(key): key如果是空,抛出空指针异常
//s.add(null);
// contains(key): 如果key存在,返回true,否则返回false
        System.out.println(s.contains("apple"));
        System.out.println(s.contains("watermelen"));
// remove(key): key存在,删除成功返回true
// key不存在,删除失败返回false
// key为空,抛出空指针异常
        s.remove("apple");
        System.out.println(s);
        s.remove("watermelen");
        System.out.println(s);

        System.out.println();
        Iterator<String> it = s.iterator();
        while(it.hasNext()){
            System.out.print(it.next() + " ");
        }
        System.out.println();
    }




五.哈希表:
1 概念:
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,
即O(logN)搜索的效率取决于搜索过程中元素的比较次数。

 如果构造一种存储结构,通过某种函使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(或者称散列表)
下面是利用key和散列表长度构建哈希:
公式就是 (你要的放的) key % capcity (列表长度)
方法:根据待插入元素的关键码, 以此公式计算出该元素的存储位置并按此位置进行存放
2 冲突的 概念
对于两个数据元素的关键字ki,kj 和 (i != j),有 != ,但有:Hash(ki) == Hash(kj),即: 不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞
简单来说就是不同的值通过哈希函数计数出,相同的位置。
3.冲突的 避免
通过调节负载因子,和设计合理的哈希函数。

4.通过负载因子调节避免冲突:

解决哈希冲突两种常见的方法是:闭散列和开散列,这里重点说说开散列(哈希桶)

(1). 散列表的负载因子定义:等于 填入表中的元素个数 / 散列表的长度

已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小由散列表的负载因子定义可知,我们通过增加散列表的长度来调节冲突(下面代码会呈现)

5. 解决哈希冲突两种常见的方法是:闭散列和开散列

闭散列: 也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然 还有空位置,那么可以把 key存放到冲突位置中的“下一个” 空位置 中去。
在这个过程中会发生, 线性探测和二次线性探测( 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止 )。

6.开散列/哈希桶(重点):

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
如图:

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素

7.哈希桶的实现:

注意:

key:关键码通过散列函数计算出的散列地址

val:节点对应的值

next:向后连接相同散列地址的节点地址

节点和散列表的创建:

public class MyHashBuck {
    static class Node{
        public int key;
        public int val;
        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }

    }

    public Node[] array = new Node[10];
    public int usedSize;
    public static final double DEFAULT_LOAD_FACTOR = 0.75f;//负载因子

负载因子的判断:

//负载因子 == 表中元素个数个数 / 表长
    private double doLoadFactor() {
        return usedSize*1.0 / array.length;
    }

扩容(降低哈希冲突):

这里注意不能直接,简单扩容数组,一定要重新哈希,这样才可以把,原来的哈希位置放到正确新对应的散列地址处,。不可以使用cur = cur.next ,因为每一个节点都有自己的cur,插入的节点也有cur要分清。

 private void resize() {
        Node[] newArray = new Node[2*array.length];
        for (int i = 0; i < array.length; i++) {
            //扩容后要遍历链表的每一个节点
            //重新哈希
            Node cur = array[i];
            while (cur != null) {
                int newIndex = cur.key % newArray.length;
                //头插:
                //记录一下,原来链表的cur,之后返回(注意这个有一个外来的cur,和本身的cur)
                Node curN = cur.next;
                cur.next = newArray[newIndex];
                newArray[newIndex] = cur;
                cur = curN;
            }
        }
        array = newArray;
    }

获取key:

public int getVal(int key) {
        int index = key % array.length;
        Node cur = array[index];

        while (cur != null) {

            //不能有相同的节点
            if (cur.key == key) {
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }
8.性能分析
虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的, 也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1) 。
9. 哈希表 和 java 类集的关系:
1. HashMap 和 HashSet java 中利用哈希表实现的 Map 和 Set
2. java 中使用的是哈希桶方式解决冲突的
3. java 会在冲突链表长度大于一定阈值后(在链表长度超过8,数组长度超过64),将链表转变为搜索树(红黑树)
4. java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key equals 方法。所以如果要用自定义类作为 HashMap key 或者 HashSet 的值,必须覆写
hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode 一定是一致的

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

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

相关文章

常见中间件漏洞复现之【Jboss】!

Jboss介绍 JBoss是⼀个基于J2EE的开发源代码的应⽤服务器。JBoss代码遵循LGPL许可&#xff0c;可以在任何商业应⽤中免费使⽤。JBoss是⼀个管理EJB的容器和服务器&#xff0c;⽀持EJB1.1、EJB 2.0和EJB3的规范。但JBoss核⼼服务不包括⽀持servlet/JSP的WEB容器&#xff0c;⼀般…

61 函数参数——可变长度参数

可变长度参数在定义函数时主要有两种形式&#xff1a;*parameter 和 **parameter&#xff0c;前者主要用来接收任意多个实参并将其放在一个元组中&#xff0c;后者接收类似于关键参数一样显示赋值形式的多个实参并将其放入字典中。 # 无论调用该函数时传递了多少实参&#xff…

鸿蒙Harmony开发:onFrame逐帧回调规范

通过返回应用onFrame逐帧回调的方式&#xff0c;让开发者在应用侧的每一帧都可以设置属性值&#xff0c;从而实现设置了该属性值对应组件的动画效果。 使用animator实现动画效果 使用如下步骤可以创建一个简单的animator&#xff0c;并且在每个帧回调中打印当前插值。 引入相…

萌新的Java入门日记18

一、mybatis范围筛选 1.第一种表示方法 <!--resultType 查出来的结果自贡每一行都要映射到该类型的对象--><select id"getStaff" resultType"com.easy.bean.Staff">select * from staff<!--根据参数不同组合出不同的SQL语句 动态SQL语句…

java之IO篇——工具包Commons-io和Hutool

前言 结束了IO篇的File、基本流和高级流。还要认识IO流的一些工具包Commons-io和hutool&#xff0c;不算是框架&#xff0c;但是非常实用。 目录 前言 一、Commons-io 1.来历及作用 2.使用 二、Hutool 1.简介 2.使用 一、Commons-io 1.来历及作用 Commons-io是apache…

C++第七篇 模板初阶和STL简介

目录 一&#xff0c;模板初阶 1.泛型编程 2.函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3.类模板(模板类&#xff0c;模板函数) 3.1 类模板定义格式 二&#xff0c;STL简介 1. 什么是STL 2. ST…

【JUC】并发编程与源码分析 1-7章

1 线程基础知识复习 1把锁&#xff1a;synchronized&#xff08;后面细讲&#xff09; 2个并&#xff1a; 并发&#xff08;concurrent&#xff09;&#xff1a;是在同一实体上的多个事件&#xff0c;是在一台机器上“同时”处理多个任务&#xff0c;同一时刻&#xff0c;其…

【学习笔记】A2X通信的协议(三)- A2X PC5通信(一)

目录 6. A2X通信 6.1 A2X PC5通信 6.1.1 一般说明 6.1.2 通过NR-PC5的单播模式A2X通信 6.1.2.1 概述 6.1.2.2 A2X PC5单播链路建立程序 6.1.2.2.1 一般说明 6.1.2.2.2 发起UE启动A2X PC5单播链路建立程序 6.1.2.2.3 目标UE接受的A2X PC5单播链路建立程序 6.1.2.2.5 目…

学单片机怎么在3-5个月内找到工作?

每个初学者&#xff0c;都如履薄冰&#xff0c;10几年前&#xff0c;我自学单片机时&#xff0c;也一样。 想通过学习&#xff0c;找一份体面点的工作&#xff0c;又害怕辛辛苦苦学出来&#xff0c;找不到工作。 好在&#xff0c;当初执行力&#xff0c;还算可以&#xff0c;自…

WebLogic

二、WebLogic 2.1 后台弱口令GetShell 漏洞描述 通过弱口令进入后台界面&#xff0c;上传部署war包&#xff0c;getshell 影响范围 全版本(前提后台存在弱口令) 漏洞复现 默认账号密码:weblogic/Oracle123weblogic常用弱口令: Default Passwords | CIRT.net这里注意&am…

设计模式--结构型

类适配器 #include <queue> #include <iostream> #include <algorithm> #include <iterator>using namespace std;// 目标接口 class Target {public:virtual ~Target() {}virtual void method() 0; };// 适配者类 class Adaptee {public:void spec_…

CHIESI凯西医药:外企入职测评综合能力及性格测试SHL题库测评真题解析

CHIESI凯西医药是一家意大利国际制药集团&#xff0c;以研发为核心&#xff0c;专注于呼吸道健康、罕见疾病和专科治疗的创新治疗方案。集团总部位于意大利帕尔马市&#xff0c;拥有超过85年的历史&#xff0c;业务遍及全球31个国家和地区&#xff0c;拥有7,000多名员工。2023年…

day22(mysql数据库主从搭建)

上午&#xff1a; 1、为mysql添加开机启动chkconfig 2、编辑配置文件my.cnf 3、修改环境变量 4、mysql角色授权 角色不生效 在配置文件中不添加activate_all_roles_on_loginon glibc安装&#xff0c;my.cnf在项目目录之下 rpm安装&#xff0c;my.cnf文件在/etc/my.cnf 5、自…

函数实例讲解(五)

文章目录 提取字符串必学的函数&#xff08;LEFT、MID、RIGHT、LEN、LENB&#xff09;1、LEFT2、RIGHT3、MID4、LEN5、LENB 提取实战套路知多少1、FIND2、ISNUMBER 利用随机函数来抽奖&#xff08;RAND、RANDBETWEEN&#xff09;1、RAND2、RANDBETWEEN 排名的几种套路&#xff…

解决nacos疯狂报错“user nacos not found”

nacos疯狂报错“user nacos not found” 参考博客&#xff1a;https://blog.csdn.net/cnskylee/article/details/137640113 背景&#xff1a;项目启动后一直刷“user nacos not found”报错信息&#xff0c;但是不影响接口调用 解决&#xff1a; 1、将nacos版本切换为2.2.2&am…

ET实现游戏中聊天系统逻辑思路(服务端)

目录 一、准备工作 1.1 前言 1.2 完善聊天服务器 1.3 自定义聊天服消息 二、玩家登录聊天服 2.1 Gate网关与聊天服通讯 2.2 保存玩家实体映射实例ID 三、处理聊天逻辑 3.1 发送聊天消息的定义 3.2 定义聊天记录组件 3.3 编写发送聊天消息处理类 ET是一个游戏框架&…

Linux--shell脚本语言—/—终章

一、shell函数 1、shell函数定义格式 参数说明&#xff1a; 1、可以带function fun() 定义&#xff0c;也可以直接fun() 定义,不带任何参数。 2、参数返回&#xff0c;可以显示加&#xff1a;return 返回&#xff0c;如果不加&#xff0c;将以最后一条命令运行结果&#xff…

(20)SSM-MyBatis关系映射

MyBatis关联映射 概述 在实际开发的工程中&#xff0c;经常对出现多表操作&#xff0c;如果常见的根据某条数据的ID去检索数据&#xff08;根据用户查询订单信息&#xff09;&#xff0c;这个时候我们数据库设计的时候就需要使用外键进行关联&#xff0c;那么mybatis在操作这…

推荐4款2024年热门的win10 录屏软件。

如果只是偶尔需要简单地录制一下电脑屏幕&#xff0c;一般大家都会选免费且操作简单的软件&#xff1b;但如果是专业的视频制作&#xff0c;就需要功能强大、支持后期编辑的软件。而下面的这4款软件却能满足以上两种需求&#xff0c;并且能够兼容的系统也很多。 1、福昕专业录屏…

如何提升你的广告创意?

当你有一个好的产品时&#xff0c;你一定会想到用广告投放来推广它。但简单的广告投放是不足以帮助你建立起品牌知名度的。要从众多产品中脱颖而出&#xff0c;还需要独特又有效的广告创意。以下是尤里改为你总结的提升广告创意的办法&#xff0c;电子商务品牌可以着重了解下~ …