【数据结构】哈希表

news2024/11/24 2:25:33

目录

一、哈希函数的引入

二、解决哈希冲突的思路

2.1基于闭散列的思路

2.2基于开散列的思路

 2.3负载因子

三、关于哈希函数的设计

四、基于拉链法实现哈希表

4.1哈希表的内部构造

4.2插入操作

4.3扩容操作

4.4搜索操作

4.5删除操作


哈希表其实就是基于数组衍生而来的,哈希表的查找时间复杂度近乎O(1)。用空间换时间的思想。

高效查找的奥秘就在于数组的随机访问特性(在数组中只要知道元素的下标-索引,可以立即在数组中取得该元素)。

一、哈希函数的引入

当有一组数据,要查询某个特定的元素是否在数组内,就将原数组的元素映射为新的布尔数组的下标,要查询某个元素是否存在,只需在对应的布尔数组中查看该索引即可。

当数组的元素跨度较大10w,100w等就得根据元素最大值来开辟数组,会浪费大量空间,当数组的元素存在负数,是无法直接进行映射的。这时引入了哈希函数。

哈希函数:将任意的key(所有数据类型)映射为数组下标。

哈希冲突:原本两个不同的key值,经过哈希函数运算后得到两个相同的int值(从数学角度一定存在)。

在处理整形映射为整形时,最常用的手段就是进行取模运算,一组很大的数组映射为一组很小的数组。例如100w和10w经过取模10之后都得到零(通过哈希冲突)。

二、解决哈希冲突的思路

2.1基于闭散列的思路

当发生哈希冲突时,找冲突位置的附近是都存在空闲位置,如果有就放入冲突元素。(好放难查更难删)。

线性探测:从不发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置即可。

 要在哈希表中查询某个元素是否存在:要查找x是否存在,首先对x%n = y。如果发现y未知的索引对应的值不是x,那么就继续向后遍历,若一直向后遍历走到下一个空闲位置还没找到待查找元素,说明该元素不存在。删除也需要先找到再删除。

2.2基于开散列的思路

如果出现哈希冲突,就在对应的位置上将该数组元素变为一个链表(拉链法)。哈希表的数组其实存储的是每个链表的头结点,整个哈希表就是数组+链表的结构。

当要查找某个元素x是否存在,将x取模拿到对应链表的头结点,只要遍历这个链表即可。

基于这种方案解决的哈希冲突就想整张表的冲突问题转为几个链表的冲突问题。

 这种思路简单实用,基本是各种哈希表解决冲突的方案首选。

若某个链表冲突非常严重,该立案表长度很长,查找元素又会为链表的遍历时间复杂度又为O(n)。

 =》1.针对整个哈希表进行扩容(对原数组扩容),冲突链表上的元素放到新数组上时会进行重新取模,降低冲突概率。(JDK中当整个哈希表元素个数<64采用这种方案)

 =》2.将某些冲突的链表再次拆拆解成子哈希表或树化(二叉搜索平衡树),原哈希表中其他位置不影响,不改动,只处理这个冲突严重的链表。(JDK中当整个哈希表元素个数>=64,且某个链表长度>=8,会将此链表转为BRTree,不进行第一种)

 2.3负载因子

负载因子是描述一个哈希表冲突的情况。LOAD_FACTOR = 哈希表中世纪存储的元素个数 / 数组长度。

负载因子越大说明当前哈希表中冲突概率越大,查找效率越低。比较节省空间。

负载因子越小说明当前哈希表中冲突概率越小,查找效率就越高,比较浪费数组空间。

触发扩容(树化)的机制为数组元素>=哈希表长度*负载因子。

负载因子如何确定需要根据现有的业务场景进行性大量的试验论证,在查找效率和占用空间上取平衡。JDK的HashMap的负载因子默认设置为0.75。

三、关于哈希函数的设计

一般用现成的方案即可(由离散数学家研究的话题)。

一般来说,如果是对整型做哈希运算,模一个素数冲突概率较低。

如果对字符串进行哈希运算,就可以利用md5算法(主要给字符换计算哈希值)。

md5算法的三大特点:(1)定长,无论输入多大的字符串,得到的md5值长度固定。(2)分散,原数据稍微变化一点点,得到的md5值差异很大。(3)不可逆,通过原数据得到md5值很容易,反之通过md5值倒退原数据内容非常难。若两个字符串的md5相同,在工程领域可以认为他们俩是相通的字符串。

四、基于拉链法实现哈希表

数组+链表的实现。

4.1哈希表的内部构造

首先需要创建一个Node类,然后创建key,value两个int变量,在创建一个next的Node变量。然后创建构造方法。

再在MyHashMap类中创建size变量用来存储存入的数据的数量,创建一个负载因子double 类型的LOAD_FACTOR为0.75。以及Node类型的数组hashTable和int类型的取模值M。然后创建两个构造方法,一个无参一个有参。在创建一个hash方法用来确定key值取模后的索引位置。

public class MyHashMap {
    private static class Node{
        int key;
        int value;
        Node next;
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    private int size;
    private static final double LOAD_FACTOR = 0.75;
    private Node[] hashTable;
    private int M;
    public MyHashMap() {
        this(10);
    }
    public MyHashMap(int capacity) {
        this.hashTable = new Node[capacity];
        this.M = capacity;
    }
    public int hash(int key){
        return Math.abs(key) % M;
    }
}

4.2插入操作

在插入操作中首先要存储index为key取模之后的值也就是相应的索引位置。然后遍历这个索引位置下的链表,如果在链表中存在相同的key值则直接将新得value值取代即可,然后返回原来的value值。

如果没有相同的key值,那么就直接头插在当前的链表上,然后size++。最后判断是否需要扩容,然后返回value值。

    public int put (int key,int value){
        int index = hash(key);
        for(Node x = hashTable[index];x!=null;x=x.next){
            if(x.key == key){
                int oldValue = x.value;
                x.value = value;
                return oldValue;
            }
        }
        Node newNode = new Node(key,value);
        newNode.next = hashTable[index];
        hashTable[index] = newNode;
        size++;
        if (size >= hashTable.length * LOAD_FACTOR) {
            resize();
        }
        return value;
    }

4.3扩容操作

首先需要创建一个新的Node数组,长度设置为原数组长度的二倍,然后将取模的数值更新为新的数组长度。然后遍历原先的数组,再遍历当前位置的链表,然后根据新的索引插入新的数组,然后将this.hashTable指向新的数组。

    private void resize() {
        Node[] newTable = new Node[hashTable.length<<1];
        this.M = newTable.length;
        for(int i = 0;i<hashTable.length;i++){
            Node x = hashTable[i];
            while(x!=null){
                Node next = x.next;
                int k = hash(x.key);
                newTable[k] = x;
                x = next;
            }
        }
        this.hashTable = newTable;
    }
    public static void main(String[] args) {
        MyHashMap hashMap = new MyHashMap(4);
        hashMap.put(1,11);
        hashMap.put(5,55);//断点1
        hashMap.put(2,22);//断点2
        hashMap.put(6,66);
        hashMap.put(3,33);
        hashMap.put(1,111);
    }

首先断点1过去后size=2,插入了两个元素,并没有进行扩容操作,M仍然为4。

断点2过去之后发现M变为8,插入了三个元素,成功进行了扩容操作。

4.4搜索操作

分为三种,查找对应key值或者value值是否存在,查找key值返回value值。

两个根据key值查找直接找到对应索引然后遍历链表即可。

根据value值查找则需要遍历整个数组+链表。

    public boolean containsKey(int key) {
        int index = hash(key);
        for (Node x = hashTable[index];x != null;x = x.next) {
            if (key == x.key) {
                return true;
            }
        }
        return false;
    }
    public int get(int key) {
        int index = hash(key);
        for (Node x = hashTable[index];x != null;x = x.next) {
            if (x.key == key) {
                return x.value;
            }
        }
        throw new NoSuchElementException("hashtable has not this key!");
    }
    public boolean containsValue(int value) {
        for (int i = 0; i < hashTable.length; i++) {
            for (Node x = hashTable[i];x != null;x = x.next) {
                if (x.value == value) {
                    return true;
                }
            }
        }
        return false;
    }
    public static void main(String[] args) {
        MyHashMap hashMap = new MyHashMap(4);
        hashMap.put(1,11);
        hashMap.put(5,55);
        hashMap.put(2,22);
        hashMap.put(6,66);
        hashMap.put(3,33);
        hashMap.put(1,111);
        System.out.println(hashMap.containsKey(6));
        System.out.println(hashMap.get(5));
        System.out.println(hashMap.containsValue(33));
    }

4.5删除操作

类似于链表的删除操作,首先需要排除当前key的所有为空时肯定不存在则直接返回false,若当前头结点就是要找到的结点则删除头结点,然后遍历后面的链表,找到即删除返回true,未找到返回false。

    public boolean remove (int key){
        int index = hash(key);
        Node head = hashTable[index];
        if (head == null) {
            return false;
        }
        if(head.key==key){
            hashTable[index] = head.next;
            head = head.next = null;
            size--;
            return true;
        }
        Node prev = head;
        while (prev.next!=null){
            if(prev.next.key==key){
                prev.next = prev.next.next;
                size--;
                return true;
            }
            prev = prev.next;
        }
        return false;
    }
public static void main(String[] args) {
        MyHashMap hashMap = new MyHashMap(4);
        hashMap.put(1,11);
        hashMap.put(5,55);
        hashMap.put(2,22);
        hashMap.put(6,66);
        hashMap.put(3,33);
        hashMap.put(1,111);
        System.out.println(hashMap.remove(1));
        System.out.println(hashMap.remove(6));
        System.out.println(hashMap.remove(9));
    }

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

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

相关文章

深度解读面试题:链表中环的入口结点(附代码,可过在线OJ)

在解读“链表中环的入口结点”前&#xff0c;我认为有必要明白关于它的一些用于打基础的问题&#xff08;相交链表、判断链表中是否存在环&#xff09; 相交链表 题目&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点…

快收藏!!整理了100个Python小技巧!!

下面&#xff0c;我就给大家分享100个Python小技巧&#xff0c;帮助大家更好的了解和学习Python&#xff0c;欢迎收藏、关注&#xff0c;点赞支持&#xff01; ▍1、for循环中的else条件 这是一个for-else方法&#xff0c;循环遍历列表时使用else语句。下面举个例子&#xff…

根据平均值列出记录

AANSI SQL包括几个聚合函数&#xff0c;使您可以对一组值进行计算以将其结果作为单个值返回。他们包括Count(), Min(), Max(), Sum() and AVG(),以及其他。默认情况下&#xff0c;聚合函数适用于所有行&#xff0c;但是您可以通过将WHERE子句应用于SELECT语句来缩小字段的范围。…

直冲云霄,阿里大牛耗时49天整理12W字面试手册,押题准确率直冲95%

很多人都想进字节做开发&#xff0c;不论是技术还是薪资、福利都算得上TOP级~ 7月底官方再次启动扩招&#xff0c;发布了1200&#xff0b;后端工程师岗位&#xff01; 那么本批有哪些优质岗位可选择&#xff1f;薪资待遇如何&#xff1f; 下面给大家列出几类具体的岗位要求&a…

软件项目管理指南:定义、5大过程、估算及进度管理方法等

本文将分享&#xff1a;1、软件项目管理的定义&#xff1b;2、软件项目管理的过程步骤&#xff1b;3、软件项目管理的内容&#xff1b;4、软件项目估算与进度管理方法&#xff1b;5、软件开发各生命周期阶段与文档、角色间的关系&#xff1b;6、软件开发项目中的各大角色职能&a…

深度学习-第P1周——实现mnist手写数字识别

深度学习-第P1周——实现mnist手写数字识别深度学习-第P1周——实现mnist手写数字识别一、前言二、我的环境三、前期工作1、导入依赖项并设置GPU2、导入数据集3、数据可视化四、构建简单的CNN网络五、训练模型1、设置超参数2、编写训练函数3、编写测试函数4、正式训练六、结果可…

ADSP-21489的图形化编程详解(7:延时、增益、分频、反馈、响度)

延时 21489 可以做延时&#xff0c;音频高手会运用此项算法来增强音效&#xff0c;我们做个最简单的&#xff0c;让大家知道怎么用它&#xff0c;至于怎么样嵌入到自己的系统里实现更好的效果&#xff0c;则需要各位调音师专业的耳朵来判断&#xff0c;调音无上限&#xff01;…

MySQL之索引及其背后的数据结构

✨博客主页: 荣 ✨系列专栏: MySQL ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. 索引的介绍1. 什么是索引2. 索引的使用二. 索引背后的数据结构1. 考虑使用哈希表2. 二叉搜索树3. N叉搜索树(B树, B树)4. 注意事项一. 索引的介绍 1. 什么是索引 索引 (Index) 是帮助…

[激光原理与应用-39]:《光电检测技术-6》- 光干涉的原理与基础

目录 第1章 概述 1.1 什么是光干涉 1.2 产生干涉的必要条件 1.3 非相干光 - 自发辐射无法产生干涉 1.4 相干光 - 受激辐射 1.5 时间相干性 1.6 空间相干性 它山之石 第1章 概述 1.1 什么是光干涉 它是指因两束光波相遇而引起光的强度重新分布的现象。 指两列或两列以上…

Verilog入门学习笔记:Verilog基础语法梳理

无论是学IC设计还是FPGA开发&#xff0c;Verilog都是最基本、最重要的必备技能。但任何一门编程语言的掌握都需要长期学习。并不是简简单单的随便读几本书&#xff0c;随便动动脑筋那么简单。Verilog是一门基于硬件的独特语言&#xff0c;由于它最终所实现的数字电路&#xff0…

基于AVDTP信令分析蓝牙音频启动流程

前言 公司项目edifier那边需要在原来音频SBC,AAC基础上增加LHDC5.0编码&#xff0c;在打通lhdc协议栈之前&#xff0c;学习记录一番AVDTP音频服务流程。 一、AVDTP音频流基础知识 分析音频流程首先应具备的最简单基础概念知识&#xff1a;AVDTP信令signal&#xff0c;流端点se…

【JVM】垃圾回收机制详解(GC)

目录一.GC的作用区域二.关于对象是否可回收1.可达性分析算法和引用计数算法2.四种引用类型三.垃圾收集算法1.标记-清除算法2.复制算法3.标记-整理算法4.分代收集算法四.轻GC(Minor GC)和重GC(Full GC)一.GC的作用区域 可以看jvm详解之后&#xff0c;再来理解这篇文章更好 堆和…

[附源码]计算机毕业设计农村人居环境治理监管系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

ASP.NET Core 3.1系列(18)——EFCore中执行原生SQL语句

1、前言 前一篇博客介绍了EFCore中常见的一些查询操作&#xff0c;使用Linq或Lambda结合实体类的操作相当方便。但在某些特殊情况下&#xff0c;我们仍旧需要使用原生SQL来获取数据。好在EFCore中提供了完整的方法支持原生SQL&#xff0c;下面开始介绍。 2、构建测试数据库 …

Radare2 框架介绍及使用

Radare2 框架介绍及使用 欢迎入群交流 radare2 这是整个框架的核心工具&#xff0c;它具有debugger和Hexeditor的核心功能&#xff0c;使您能够像打开普通的文件一样&#xff0c;打开许多输入/输出源&#xff0c;包括磁盘、网络连接、内核驱动和处于调试中的进程等。 它实现了…

旧版本金庸群侠传3D新Unity重置修复版入门-lua”脚本“

金庸3DUnity重置入门系列文章 金庸3dUnity重置入门 - lua 语法 金庸3dUnity重置入门 - UniTask插件 金庸3dUnity重置入门 - Cinemachine 动画 金庸3dUnity重置入门 - 大世界实现方案 金庸3dUnity重置入门 - 素材极限压缩 (部分可能放到付费博客&#xff09; 2022年底~20…

Apifox和Eolink两个测试工具谁最实用?

目前行业内有 postman、jmeter 为代表开源 Api 工具派系&#xff0c;我想对大家对这两个词并不陌生。虽然它们能解决基本的接口测试&#xff0c;但是无法解决接口链路上的所有问题&#xff0c;一个工具难以支持整个过程。在国内&#xff0c;我们可以看到有国产 API 管理工具&am…

Spring Cloud 微服务讲义

Spring Cloud 微服务讲义第一部分 微服务架构第 1 节 互联网应用架构演进第 2 节 微服务架构体现的思想及优缺点第 3 节 微服务架构中的核心概念第二部分 Spring Cloud 综述第 1 节 Spring Cloud 是什么第 2 节 Spring Cloud 解决什么问题第 3 节 Spring Cloud 架构3.1 Spring …

CCES软件做开发,如果仿真器连不进目标板怎么解决?(Failed to connect to processor)

ADI的DSP调试&#xff0c;我在Visual DSP软件下写过一个详细的帖子&#xff0c;来说明仿真器如果连不进目标板&#xff0c;可能存在的几种问题以及解决办法&#xff0c;现在在CCES软件下遇到了同样的问题&#xff0c;所以准备再写一个帖子说明一下。 我们都知道ADI的DSP&#…

智慧工地管理平台系统厂家哪家强|喜讯科技

喜讯科技针对施工现场涉及面广&#xff0c;多种元素交叉&#xff0c;状况较为复杂&#xff0c;如人员出入、机械运行、物料运输等工程项目管理在一定程度上存在着决策层看不清、管理层管不住、执行层做不好的问题。 围绕施工现场管理&#xff0c;构建全方位的智能监控防范体系弥…