哈希表的设计

2023-11-20

概念

顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键 码的多次比较 顺序查找时间复杂度为 O(N) ,平衡树中为树的高度,即 O( ) ,搜索的效率取决于搜索过程中 元素的比较次数。
理想的搜索方法:可以 不经过任何比较,一次直接从表中得到要搜索的元素 如果构造一种存储结构,通过某种函 (hashFunc) 使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快 找到该元素
向该结构中插入元素:
  根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放 。
查询元素:
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若 关键码相等,则搜索成功。
该方式即为哈希 ( 散列 ) 方法, 哈希方法中使用的转换函数称为哈希 ( 散列 ) 函数,构造出来的结构称为哈希表 (Hash Table)( 或者称散列表 )

哈希冲突

什么是哈希冲突

对于两个数据元素的关键字 和 (i != j) ,有 != ,但有: Hash( ) == Hash( ) ,即: 不同关键字通过相同哈 希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

怎样解决哈希冲突

1、闭散列(线性探测):当冲突发生时,找到冲突旁边的空闲位置放入冲突元素

缺点:通过这种方式解决哈希冲突,当被冲突元素所占位置需要插入元素时,该位置已经不能被插入,那就需要继续查找空闲位置,这样的话,不仅插入的效率会降低,查询的效率也会降低,而且还会影响其他元素的插入与查询。

2、开散列:当冲突发生时将冲突位置变为一个链表。

缺点:当某一个为值发生的冲突过多,会导致链表长度过长,由此带来插入和查询效率降低。

哈希函数

常用的哈希函数

1. 直接定制法
取关键字的某个线性函数为散列地址: Hash Key = A*Key + B 优点:简单、均匀 缺点:需要事先知道关 键字的分布情况 使用场景:适合查找比较小且连续的情况 。
2. 除留余数法
设散列表中允许的 地址数为 m ,取一个不大于 m ,但最接近或者等于 m 的质数 p 作为除数,按照哈希函数: Hash(key) = key% p(p<=m), 将关键码转换成哈希地址。
3. 平方取中法
假设关键字为 1234 ,对它平方就是 1522756 ,抽取中间的 3 227 作为哈希地址; 再比如关键字为 4321 ,对 它平方就是18671041 ,抽取中间的 3 671( 710) 作为哈希地址 平方取中法比较适合:不知道关键字的分 布,而位数又不是很大的情况。
4. 折叠法
折叠法是将关键字从左到右分割成位数相等的几部分 ( 最后一部分位数可以短些 ) ,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。 折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况。
5. 随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random(key), 其中 random 为随机数函数。 通常应用于关键字长度不等时采用此法
6. 数学分析法
设有 n d 位数,每一位可能有 r 种不同的符号,这 r 种不同的符号在各位上出现的频率不一定相同,可能在某 些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

设计哈希表

在哈希表的设计中我采用开散列来解决哈希冲突,而哈希函数采用的是除留余数法。

哈希表模块设计

1、哈希函数

private int hash(int key) {
        return key % this.M;
    }

哈希函数是哈希表中最重要的一部分,一个好的哈希函数可以大大降低哈希冲突产生的概率,我采用的是哈希函数是对哈希桶的长度进行取余操作。

2、添加元素

public int put(int key,int value) {
        // 1.首先计算出当前新元素的下标
        int index = hash(key);
        // 2.在当前的子链表中判断key值是否存在,若存在,只需要更新value即可
        for (Node x = data[index];x != null;x = x.next) {
            if (x.key == key) {
                // 存在,只需要更新value
                int oldValue = x.value;
                x.value = value;
                return oldValue;
            }
        }
        // 3.此时key第一次出现,头插到当前的子链表中
        Node node = new Node(key,value);
        node.next = data[index];
        data[index] = node;
        size ++;
        // 4.判断当前哈希表的冲突情况,是否需要扩容
        if (size >= this.data.length * LOAD_FACTOR) {
            resize();
        }
        return -1;
    }

先计算出元素hash后的值,作为索引下标,在判断当前索引链表是否存在key值,若不存在直接插入(头插到链表中),若存在返回旧的value值,并更新value值。并判断是否需要对哈希表进行扩容。

3.扩容

private void resize() {
        this.M = data.length << 1;
        Node[] newData = new Node[data.length << 1];
        // 搬移原数组的所有节点
        for (int i = 0; i < data.length; i++) {
            for (Node x = data[i];x != null;) {
                Node next = x.next;
                // 当前x搬移到新数组的对应位置
                int newIndex = hash(x.key);
                // 头插到新数组的对应位置
                x.next = newData[newIndex];
                newData[newIndex] = x;
                // 继续搬移原数组的下一个节点
                x = next;
            }
        }
        // 更新data的指向
        data = newData;
    }

当哈希表需要扩容时,先将哈希桶扩容为原来的二倍,之后因为原来哈希表中的所以是根据哈希桶的长度取余所得,所以需要对之前的元素进行搬移。

哈希表的实现

public class MyHashMap {
    private class Node{
        int key;
        int value;
        Node next;

        public Node(int key,int value) {
            this.key = key;
            this.value=value;
        }
    }
    private int size;
    private int m;
    private Node[] data;
    private static final double LOACL_FACTOR = 0.75;

    public MyHashMap() {
        this(16);
    }

    public MyHashMap(int capacity){
        this.data=new Node[capacity];
        this.m=capacity;
    }

    public int put(int key,int value){
        int index=hash(key);
        for(Node x=data[index];x!=null;x=x.next){
            if(x.key==key){
                int oldval=x.value;
                x.value=value;
                return oldval;
            }
        }
        Node node=new Node(key,value);
        node.next=data[index];
        data[index]=node;
        size++;
        if(size>=data.length*LOACL_FACTOR){
            resize();
        }
        return -1;
    }

    private void resize(){
        this.m= data.length<<1;
        Node[] newData=new Node[data.length<<1];
        for(int i=0;i<data.length;i++){
            for(Node x=data[i];x!=null;){
                Node next=x.next;
                int newIndex=hash(x.key);
                x.next=newData[newIndex];
                newData[newIndex]=x;
                x=next;
            }
        }
        data=newData;
    }

    public boolean removeKey(int key){
        int index=hash(key);
        if(data[index]==null){
            return false;
        }
        if (data[index].key == key) {
            data[index] = data[index].next;
            size --;
            return true;
        }
        Node prev=data[index];
        while(prev.next!=null){
            if(prev.next.key==key){
               prev.next=prev.next.next;
               size--;
               return true;
            }
        }
        return false;
    }


    public boolean cntainsKey(int key){
        int index=hash(key);
        for(Node x=data[index];x!=null;x=x.next){
            if(x.key==key){
                return true;
            }
        }
        return false;
    }

    public boolean containsValue(int value){
        for(int i=0;i<data.length;i++){
            for(Node x=data[i];x!=null;x=x.next){
                if(x.value==value){
                    return  true;
                }
            }
        }
        return false;
    }




    private int hash(int k){
        return k%m;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

哈希表的设计 的相关文章

  • 用于解析和构建逻辑表达式的 Java 库

    我正在寻找一个 Java 开源库来解析和构建类似 SQL 的表达式 例如评估表达式的有效性 例如 a x or y and b z 另外我想要一个用于构建或扩展表达式的 API 就像是 Expression exp new Expressi
  • 将构造函数作为参数传递给方法

    我是java新手 开始研究构造函数 我看到一些构造函数作为参数传递给方法的示例 请告诉我当构造函数作为参数传递给方法时会发生什么 或者建议我一些链接 我可以在其中获得有关使用构造函数的足够知识 根据您需要传递构造函数的目的 您可以考虑传递供
  • 将 MouseListener 添加到面板

    我正在尝试将鼠标操作添加到我的面板中 这就是程序应该做的事情 编写一个程序 允许用户通过按三下鼠标来指定一个三角形 第一次按下鼠标后 画一个小点 第二次按下鼠标后 绘制一条连接前两个点的线 第三次按下鼠标后 绘制整个三角形 第四次按下鼠标会
  • 在 Java 中使用 Batik 检查和删除 SVG 中的属性

    这个问题基本上说明了一切 如何检查 SVG 是否具有 viewBox 属性 我正在使用蜡染库 我需要这个 因为我需要 至少 通知用户有一个 viewBox 属性 我可以删除它吗 使用 org w3c dom 类 您可以按照以下方式做一些事情
  • 重写 getPreferredSize() 会破坏 LSP

    我总是在这个压倒一切的网站上看到建议getPreferredSize 而不是使用setPreferredSize 例如 如前面的线程所示 对于固定大小的组件 使用重写 getPreferredSize 而不是使用 setPreferredS
  • Java 卡布局。多张卡中的一个组件

    一个组件 例如JLabel 在多张卡中使用CardLayout 目前看来该组件仅出现在它添加到的最后一张卡上 如果有办法做到这一点 我应该吗 这是不好的做法吗 或者有其他选择吗 你是对的 它只出现在 添加到的最后一张卡 中 但这与CardL
  • Spring中的ProxyFactoryBean

    有人可以解释一下吗代理工厂Bean http static springsource org spring docs current javadoc api org springframework aop framework ProxyFa
  • 以有效的方式从 Map 中删除多个键?

    我有一个Map
  • JavaFX使节点覆盖父节点边框颜色

    我有一个如下所示的节点 仅使用 css 我希望标签覆盖其父边框颜色 因此标签下方的边框颜色部分变得不可见 我用来制作这个边框的CSS代码 fx border color black fx border width 3 fx border r
  • 如何在 HandlerInterceptorAdapter 中添加 HttpServletRequest 标头?

    我正在尝试将授权标头添加到我的请求中 作为我们切换环境时的临时解决方法 我试图在扩展 HandlerInterceptorAdapter 的拦截器中处理它 我使用 MutableHttpServletRequest 类制作here http
  • 具有 JPA 持久性的 Spring 状态机 - 存储库使用

    我试图弄清楚如何轻松使用 Spring 状态机 包括使用 JPA 进行持久化 这是我正在处理的问题 不兼容的数据类型 工厂和持久性 在程序的某个时刻 我想使用连接到用户的状态机 有用于此目的的存储库 项目spring statemachin
  • Struts 1 到 Spring 迁移 - 策略

    我有一个legacy银行应用程序编码为Struts 1 JSP现在的要求是迁移后端 目前为 MVC to Springboot MVC 后续UI JSP 将迁移到angular Caveats 1 后端不是无状态的 2 会话对象中存储了大量
  • 纱线上的火花,连接到资源管理器 /0.0.0.0:8032

    我正在我的开发机器 Mac 上编写 Spark 程序 hadoop的版本是2 6 spark的版本是1 6 2 hadoop集群有3个节点 当然都在linux机器上 我在idea IDE中以spark独立模式运行spark程序 它运行成功
  • 在 Java 中创建 XML 文件的最佳方法是什么?

    我们目前使用 dom4j 来创建 XML 文件 不过 我猜现在有更好的东西了 如果我们使用的是 Java 1 6 或更高版本 那么在编写 XML 文件时最好使用什么类 运行速度最快 使用简单 我不需要构建一个 DOM 然后编写整个 DOM
  • 使用单独的线程在java中读取和写入文件

    我创建了两个线程并修改了 run 函数 以便一个线程读取一行 另一个线程将同一行写入新文件 这种情况会发生直到整个文件被复制为止 我遇到的问题是 即使我使用变量来控制线程一一执行 但线程的执行仍然不均匀 即一个线程执行多次 然后控制权转移
  • 使用 PC/SC 读卡器验证 Ultralight EV1

    我在尝试使用 Java 中的 PC SC 读卡器 特别是 ACR1222L 验证 Ultralight EV1 卡时遇到问题 我能够使用 ISO 14443 3 标签的相应 APDU 在不受保护的标签上进行写入和读取 但是 我找不到运行 P
  • 如何从 JavaFX 中的另一个控制器类访问 UI 元素?

    我有一个使用 NetBeans 8 编写的 JavaFX Java 8 应用程序 没有SceneBuilder 我的应用程序有一个主窗口 该窗口有自己的 FXML 文件 primary fxml 和自己的控制器类 FXMLPrimaryCo
  • 在实现使用原始类型的接口时如何避免警告?

    我正在实施流程工厂 http help eclipse org ganymede index jsp topic org eclipse platform doc isv reference api org eclipse debug co
  • 如何建立与 FileZilla Server 1.2.0 的 FTPS 数据连接

    使用 Apache commons net 的 Java FTPSClient 进行会话恢复是一个已知问题 会话恢复是 FTPS 服务器数据连接所需的一项安全功能 Apache FTPSClient 不支持会话恢复 并且 JDK API 使
  • 决策树和规则引擎 (Drools)

    In the application that I m working on right now I need to periodically check eligibility of tens of thousands of object

随机推荐

  • RT-Thread记录(七、IPC机制之邮箱、消息队列)

    讲完了线程同步的机制 我们要开始线程通讯的学习 线程通讯中的邮箱消息队列也属于 RT Thread 的IPC机制 目录 前言 一 邮箱 1 1 邮箱控制块 1 2 邮箱操作 1 2 1 创建和删除 1 2 2 初始化和脱离 1 2 3 发送
  • 骑马与砍杀服务器修复,《骑马与砍杀2》新热修补丁:联机社交系统更新、修复单机崩溃...

    目前 骑马与砍杀中文站官博公开了 骑马与砍杀2 7月1日Beta e1 4 1测试版公共版热修补丁 此次热修补丁会更新联机社交系统 同时还会修复单机崩溃问题 以下为官方原文 官博截图 公共版 版本 Native e1 4 1 Sandbox
  • qt 获取父类指针

    QWidget QWidget parentWidget const Returns the parent of this widget or 0 if it does not have any parent widget
  • 单向散列函数的性质

    一 根据任意长度计算出固定长度的散列值 首先 单向散列函数的输入必须能够是任意长度的消息 其次 无论输入多长的消息 单向散列函数必须都能够生成长度很短的散列值 如果消息越长生成的散列值越长的话就不好用了 从使用方便的角度 散列值的长度最好是
  • 运行Chrome中的app

    chrome apps
  • python中and与or的计算规则

    1 在纯and语句中 如果每一个表达式都不是假的话 那么返回最后一个 因为需要一直匹配直到最后一个 如果有一个是假 那么返回假 2 在纯or语句中 只要有一个表达式不是假的话 那么就返回这个表达式的值 只有所有都是假 才返回假 3 在or和
  • git 报错did not match any file(s) known to git

    前言 在使用gitLab中时遇到一个问题 就是我在gitLab新建分支后 在本地切换分支不成功 遇到了这个问题 在大佬的博客的指点下 顺利解决这个问题 记录下我一步一步解决问题的过程 最后面是我参考大佬的地址 有兴趣的朋友可以去看一下 问题
  • 使用花生壳将自己的Linux主机配置为服务器

    1 服务端花生壳配置 http service oray com question 11630 html 如果在客户端连接失败 在这里点击诊断 如果局域网服务器连接成功才行 不成功可能的原因有两个 1 配置不对 内网主机要写Linux主机的
  • matlab产生高斯噪声

    randn randn random normal distribution 是一种产生标准 正态分布的 随机数或 矩阵的函数 属于MATLAB函数 返回一个n n的随机项的矩阵 如果n不是个数量 将返回错误信息 MATLAB函数randn
  • 编程杂感两篇

    一 Null是个巨大的错误吗 为null正名 null可以表示未初始化的引用 为什么不强迫初始化 因为初始化时可能抛异常 变量声明放进try块 又可能有跨作用域的需求 一种常见的做法是大改语法引入maybe关键字支持代数类型 并且函数做模式
  • 攻防世界—file_include

    打开之后发现是一段php代码 可以看出这是段代码有文件包含漏洞 下面是学习部分 着急看题解继续往下滑 谢谢 文件包含漏洞 File Inclusion Vulnerability 是一种Web应用程序常见的安全漏洞 也是攻击者常用的攻击手段
  • 自激振荡现象

    理论上说 自激振荡是指当放大器加电后 还没有加载输入信号 输出端就出现了高频的类似于正弦波一样的波形 实际中 还有另外一种情况 也属于自激振荡 当输入某些信号时 输出是正常的 一旦改变输入信号幅度或者频率到某些特定值 输出波形在原基础上会叠
  • redis概述、不同系统安装以及redis配置文件redis.conf各参数详细介绍

    目录 redis简介 简介 特点 优势 redis与其他key value数据库的区别 redis的安装 windows linux ubuntu下安装 redis配置 redis数据类型 String 字符串 Hash 哈希 List 列
  • 【总结】LinkedBlockingQueue为什么可以实现双锁算法

    LinkedBlockingQueue 双锁算法 putLock takeLock 为什么可以实现 put操作是将新节点放在队尾 take操作是将队首的节点取出 故两操作是满足FIFO 这在队列不为空时 避免了put和take会操作同一个元
  • JavaScript 节流和防抖

    前言 本文主要记录了JavaScript 节流和防抖 节流和防抖本质上是优化执行高频率代码的一种手段 例如 浏览器的 mousemove resize scroll 等事件在触发时 会不断地调用绑定的事件函数极大地降低了前端的性能 为了性能
  • C#、js如何实现文件上传功能

    上传文件 今天我来讲讲在MVC中如何进行文件的上传 我们逐步深入 一起来看看 我们在默认创建的项目中的控制器下添加如下 第一步创建一个接受文件的实体 创建好后判断一下接受文件的是什么文件类型如txt 然后就是文件名称建好后检查目录文件是否存
  • CVPR2017-如何在无标签数据集上训练模型

    论文 Fine tuning Convolutional Neural Networks for Biomedical Image Analysis Actively and Incrementally 论文链接 http openacce
  • 深入解析Spring Boot中最常用注解的使用方式(下篇)

    摘要 本文是 深入解析Spring Boot中最常用注解的使用方式 的下篇内容 将继续介绍Spring Boot中其他常用的注解的使用方式 并通过代码示例进行说明 帮助读者更好地理解和运用Spring Boot框架 目录 第二部分 常见的容
  • 全国职业技能大赛云计算--高职组赛题卷④(容器云)

    全国职业技能大赛云计算 高职组赛题卷 容器云 第二场次题目 容器云平台部署与运维 任务1 Docker CE及私有仓库安装任务 5分 任务2 基于容器的web应用系统部署任务 15分 任务3 基于容器的持续集成部署任务 15分 任务4 Ku
  • 哈希表的设计

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