数据结构 哈希表

news2024/11/20 19:46:30

数据结构 哈希表

文章目录

  • 数据结构 哈希表
    • 1. 概念
    • 2. 冲突-概念
    • 3. 冲突-避免
      • 3.1 哈希函数设计
      • 3.2 负载因子调节
    • 4.冲突-解决
      • 4.1 闭散列
      • 4.2 开散列(哈希桶)
      • 4.3 哈希桶实现
    • 5. 性能分析
    • 6. 和java类集的关系

1. 概念

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

理想的搜索方法:

可以不经过任何比较,一次直接从表中得到要搜索的元素,如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

当向该结构中:

  • 插入元素

    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

  • 搜索元素

    对元素的关键码进行同样的计数,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法哈希方法中使用的转换函数称为哈希(散列)函数构造出来的结构称为哈希表(HashTable)(或者称散列表)

例如:数据集合{1, 7, 6, 4, 5, 9};

哈希函数设置为**:hash(key) = key % capacity**; (capacity为存储元素底层空间总的大小)

在这里插入图片描述

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较块

但是,如果按照上述哈希方法向集合中插入元素44,却会出现问题,为什么?往下看

2. 冲突-概念

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

我们把具有不同关键码而具有相同哈希地址的数据元素称为"同义词"

3. 冲突-避免

首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,我们能做的就是尽量降低冲突率

3.1 哈希函数设计

引起哈希冲突的一个元素可能是:哈希函数设计不够合理

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

常见哈希函数

  1. 直接定制法

    取关键字的某个线性函数为散列地址:Hash(Key) = A * Key + B

    优点:简单、均匀 缺点:需要事先知道关键字的分布情况

    使用场景:适合查找比较小且连续的情况

    代码示例:

    class Solution {
        public int firstUniqChar(String s) {
            int[] count = new int[26];
            for (int i = 0;i < s.length();i++) {
                count[s.charAt(i) - 97]++;
            }
            for (int i = 0;i < s.length();i++) {
                if (count[s.charAt(i) - 97] == 1) {
                    return i;
                }
            }
            return -1;
        }
       
    }
    

    在这里插入图片描述

  2. 除留余数法

    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数: Hash(Key) = Key % p(p <= m),将关键码转换成哈希地址

注:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

3.2 负载因子调节

在这里插入图片描述

负载因子和冲突率的关系粗略演示

在这里插入图片描述

所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率

已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组大小

4.冲突-解决

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

4.1 闭散列

闭散列:也交开发放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还要空位置,那么可以把key存放到冲突位置的"下一个"空位置中去

那么如何寻找下一个空位置呢?

比如上面的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,下标为4,因此44理论上应该插在该位置,但是该位置已放了值为4的元素,即发生哈希冲突

  1. 线性探测

    • 插入

      • 通过哈希函数获取待插入元素在哈希表中的位置

      • 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素

        在这里插入图片描述

      • 采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其它元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素

  2. 二次探测

    线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后去找,因此二次探测为了避免该问题,找下一个空位置的方法为:Hi = (H0 + i²) % m,或者:Hi = (H0 - i²) % m。其中:i = 1,2,3…,H0是通过散列函数Hash(x)对元素的关键码key进行计算得到的位置,m是表的大小。对于2.1中如果要插入44,产生冲突,使用解决后的情况为:

    在这里插入图片描述

    研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容

因此,闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷

4.2 开散列(哈希桶)

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

在这里插入图片描述

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

开散列(哈希桶),可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索

当冲突比较严重时,小集合的搜索性能解决效果也不高,这个时候

我们需要将这个所谓的小集合搜索问题继续进行转换,例如:

  1. 每个桶的背后是另一个哈希表
  2. 每个桶的背后是一棵搜索树

4.3 哈希桶实现

代码示例:

package demo1;

public class HashBuck {

    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; // 数组的每个元素都是一个桶,每个桶里都存放链表
    public int usedSize; // 实际存放数据

    public static final float DEFAULT_LOAD_FACTOR = 0.75f;

    public HashBuck() {
        array = new Node[10];
    }

    /*插入元素*/
    public void put(int key, int val) {
        int index = key % array.length;
        Node cur = array[index];
        while(cur != null) {
            if (cur.key == key) {
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        // 遍历完后说明没有找到key元素,利用头插法将元素加进桶中
        Node newNode = new Node(key, val);
        newNode.next = array[index];
        array[index] = newNode;
        usedSize++;

        //若负载因子超过0.75,扩容
        if (doLoadFact() > DEFAULT_LOAD_FACTOR) {
            resize();
        }
    }

    /*扩容*/
    private void resize() {
        Node[] newArray = new Node[array.length * 2];
        //遍历原来的数组
        for (int i = 0;i < array.length;i++) {
            Node cur = array[i];
            while(cur != null) {
                Node temp = cur.next;
                int newIndex = cur.key % newArray.length;
                //采用头插法,将节点插入新数组的newIndex下标中
                cur.next = newArray[newIndex];
                newArray[newIndex] = cur;
                cur = temp;
            }
        }
        array = newArray;
    }

    private float doLoadFact() {
        return usedSize*1.0f / array.length;
    }

    /*获取元素*/
    public int get(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;
    }
}

在这里插入图片描述

5. 性能分析

虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的,也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间的复杂度是O(1)

6. 和java类集的关系

  1. HashMapHashSet 是java中利用哈希表实现的Map 和 Set
  2. java中使用的是哈希桶方式解决冲突的
  3. java会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)
  4. java中计算哈希值实际上是调用的类的hashCode方法,进行key的相等性比较是调用key的equals方法。所以如果要同自定义类作为HashMap的key或者HashSet的值,必须覆写hashCode和equals方法,并且要做到equals相等的对象,hashCode一定是一致的。

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

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

相关文章

k8s----11、service

services 1、概述2、存在的意义2.1 服务发现2.2 负载均衡 3、pod与service的关系4、service 三种类型4.1 、 ClusterIP4.2 、NodePort4.3 、LoadBalancer 1、概述 Service 是 Kubernetes 最核心概念&#xff0c;通过创建 Service,可以为一组具有相同功能的容器应 用提供一个统…

说说腾讯云新用户那些事以及产品首单说明

腾讯云新用户是什么意思&#xff1f;新用户是指从未在腾讯云购买过云产品的用户&#xff0c;新用户可以购买特价服务器你不会不知道吧&#xff1f;一定要珍惜你的新用户资格&#xff0c;因为腾讯云很多优惠价格都是新用户专享的。腾讯云百科txybk.com来详细说下什么是腾讯云新用…

Windows安装cygwin + swoole,并配置crontab定时任务

一、Windows安装cygwin 安装包下载 自行搜索安装包进行下载&#xff0c;目前官网无法打开。官网地址&#xff1a;http://www.cygwin.com/ cygwin安装详细教程 1. 打开安装包&#xff1a;setup-x86_64.exe 2. 点击“下一页”&#xff0c;选择第一种方式后再点击“下一页” 3…

NFTScan | 10.16~10.22 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2023.10.16~ 2023.10.22 NFT Hot News 01/y00ts&#xff1a;迁移回以太坊的跨链桥已上线&#xff0c;将承担第一天所有 Gas 费 10 月 16 日&#xff0c;y00ts 发推称&#xff0c;将 y00…

Etsy店铺防防关联,自养号防关联环境搭建要点

Etsy是美国一个在线销售手工工艺品的网站&#xff0c;主要为客户提供定制化的手工制品和古董收藏品等。同时&#xff0c;该平台还为设计师、工匠和买家提供了一个可靠的交易平台。 在Etsy平台上&#xff0c;用户可以开设店铺&#xff0c;销售各类产品&#xff0c;包括摄影、艺…

halcon 多车牌识别

文章目录 车牌定位字符分割字符识别多张图片 车牌定位 dev_close_window() dev_open_window(0,0,512,400,black,WindowHandle) list_image_files(C:/Users/Augustine/Desktop/车牌照图片,jpg,[],ImageFiles) for i:0 to |ImageFiles|-1 by 1read_image(Image, ImageFiles[i])*…

GoLong的学习之路(一)语法之变量与常量

目录 GoLang变量批量声明变量的初始化类型推导短变量声明匿名变量 常量iota&#xff08;特殊&#xff09;&#xff08;需要重点记忆&#xff09; GoLang go的诞生为了解决在21世纪多核和网络化环境越来越复杂的变成问题而发明的Go语言。 go语言是从Ken Thomepson发明的B语言和…

RPA对财务报表的影响

在企业的财务管理中&#xff0c;财务报表分析能够帮助管理人员及时掌握企业的生产经营状况与财务状况&#xff0c;并为管理人员的科学决策提供财务数据支撑&#xff0c;规避企业经营风险。然而&#xff0c;传统的财务报表准备过程通常是繁琐、耗时和容易出错的&#xff0c;RPA技…

如何给R128在FreeRTOS下配置/data目录

在调试音频、usb等模块时&#xff0c;会发现SDK的根目录下没有/data该目录&#xff0c;导致无法存储所需要的文件&#xff0c;这就是因为/data目录没有配置好的原因。 1、选上配置 首先需要选上的配置&#xff1a; 运行mrtos_menuconfig&#xff0c;选上COMPONENT_LITTLEFS …

IDC报告:腾讯云领跑工业云市场,装备制造领域位列行业第一

10月23日&#xff0c;全球领先的IT市场研究和咨询公司IDC发布了《中国工业云IaaSPaaS市场份额&#xff0c;2022&#xff1a;分化初现》报告&#xff0c;腾讯云凭借领先的技术优势和助力实体经济数字化转型成果&#xff0c;成为头部厂商中增速最快的云计算厂商。在装备制造领域&…

服务器数据恢复-raid0硬盘故障导致服务器崩溃的数据恢复案例

服务器故障&分析&#xff1a; 某公司一台服务器&#xff0c;共2块磁盘组建了一组raid0磁盘阵列。RAID0阵列中有1块硬盘出现故障离线&#xff0c;导致服务器崩溃。 通过和用户方的沟通&#xff0c;北亚企安数据恢复工程师初步判断&#xff1a;故障服务器中出现故障离线的硬盘…

为什么学完了 C#觉得自己什么都干不了?

为什么学完了 C#觉得自己什么都干不了&#xff1f; 俺一向的观点&#xff1a;&#xff08;1&#xff09;学跟干是两码事。学&#xff0c;你要往外掏钱或时间或两个都得掏。干&#xff0c;是你从别人兜里掏钱。&#xff08;2&#xff09;如果没有干的需求&#xff0c;那么可以啥…

软件外包开发迭代管理工具

软件迭代的管理工具有助于团队有效地规划、跟踪和管理迭代开发过程&#xff0c;确保项目按时交付&#xff0c;并与团队成员之间进行协作。以下是一些常用的软件迭代管理工具&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#…

【触想智能】工控一体机与5G物联网技术结合是未来发展趋势

工控一体机也叫工业电脑一体机&#xff0c;是工业应用非常重要的一种产品。目前&#xff0c;工控一体机在工业领域的应用已经非常普及&#xff0c;在繁忙的生产车间、数字化机床、自助服务终端设备等场景中&#xff0c;我们都有看到它的身影。 工控一体机应用的普及已经潜移默化…

Java程序设计2023-第四次上机练习

8-1三子棋 编写程序&#xff0c;实现简单的三子棋游戏。在三子棋中&#xff0c;双方在33的棋盘中轮流下棋&#xff0c;一方用*示&#xff0c;另一方用O表示。如果一方的3个棋子占据了同一行&#xff0c;同一列或者对角线&#xff0c;则该方获胜。如果棋盘已被棋子占满&#xf…

LLM实战(二)| 使用ChatGPT API提取文本topic

在大模型前时代&#xff0c;构建机器学习模型通常需要标注数据&#xff0c;然后使用这些标注数据来训练机器学习模型&#xff0c;这个过程一般需要几个月的时间&#xff0c;然而&#xff0c;在大模型时代下&#xff0c;几个小时就可以完成&#xff0c;比如情感分类&#xff0c;…

Windows环境如何使用Zblog+cpoalr搭建个人网站并远程访问?

文章目录 1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册 3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕…

sql server 生成连续日期和数字

在sqlserver里&#xff0c;可以利用系统表master..spt_values里面存储的连续数字0到2047&#xff0c;结合dateadd&#xff08;&#xff09;函数生成连续的日期 select convert (varchar(10),dateadd(d, number, getdate()),23) as workday from master..spt_values where type…

深入篇【Linux】学习必备:进程环境变量/进程切换

深入篇【Linux】学习必备&#xff1a;进程环境变量/进程切换 Ⅰ.环境变量Ⅱ.深层意义Ⅲ.全局属性Ⅳ.进程切换 Ⅰ.环境变量 1.环境变量是什么&#xff1f;&#xff1a;环境变量是系统提供的一组name/value形式的变量&#xff0c;不同的环境变量有不同的用户。 一般是用来指定操作…

【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.9 右键菜单事件

本节对应的视频讲解&#xff1a;B_站_链_接 【QT开发笔记-基础篇】 第4章 事件 4.9 右键菜单事件 本章要实现的整体效果如下&#xff1a; QEvent::ContextMenu ​ 在窗口/控件上点击鼠标右键时&#xff0c;触发该事件&#xff0c;它对应的子类是 QContextMenuEvent 首先&…