哈希表(二)—— 开散列 / 拉链法 / 哈希桶的模拟实现

news2024/11/15 6:45:32

哈希表的基本思路是通过某种方式将某个值映射到对应的位置,这里的采取的方式是除留余数法,即将原本的值取模以后再存入到数组的对应下标,即便存入的值是一个字符串,也可以根据字符串哈希算法将字符串转换成对应的ASCII码值,然后再取模。

如果某个位置已经存了其他数据,相互冲突的数据拉成一个链表,哈希表中存放第一个结点的地址。我们把这种方法称为 开散列(或者哈希桶)


目录

1、基本思路

2、极端情况的处理

3、数据存储的结构

4、 查找实现

5、插入实现

6、移除实现


1、基本思路

如果某个位置已经存了其他数据,直接头插当前位置对应的链表,之所以选择头插,是因为哈希表中只保存头结点的地址,尾插的话需要从头遍历当前链表。

2、极端情况的处理

开散列法存在一些极端情况,比如:

  • 1、存了50个值,有40个值是冲突的,挂在一个桶下面
  • 2、存了10000个值,平均每个桶长度是100,极端场景有些桶可能有上千个结点,此时的查找效率没有特别明显的提升

解决这种极端情况的关键就是扩容。有两种情况需要考虑扩容:

  • 哈希表的负载因子大于0.75,就扩容。(负载因子  = 有效数据个数 / 哈希表容量 ) —— 减少冲突
  • 当一个桶下的结点个数超过 10 个时,就扩容。(最大结点数可以自己决定)—— 避免桶过长

拓展:JDK8以后采用了一种更新的方式,当一个桶长度超过一定值以后,转换成红黑树(JAVA中每个桶下面超过8个就转换成红黑树)

 

3、数据存储的结构

哈希表中每个位置保存链表头结点的地址,数据结构的定义如下:

template <class T>
struct HashNode
{
    T _data;                // 保存的数据
    HashNode<T> *_next;     // 下一个结点的地址

    HashNode(const T &data)
        : _data(data), _next(nullptr)
    {
    }
};

 

4、 查找实现

首先通过 key 值算出保存到哈希表的哪个桶下,即保存到数组中的哪个下标位置,然后去遍历该位置的链表。

Node *Find(const K &key)
{
    if (_tables.empty())
    {
        return nullptr;
    }

    HashFunc hf;        // hf 是为了将字符串类型或者自定义类型转换成无符号整型的仿函数
    size_t index = hf(key) % _tables.size();
    Node *cur = _tables[index];
    KeyOfT kot;        // kot 是为了兼容键值对 和 单一数据的存储
    while (cur)
    {
        if (kot(cur->_data) == key)
        {
            return cur;
        }
        else
        {
            cur = cur->_next;
        }
    }

    return nullptr;
}

 

5、插入实现

第一步,检查插入的数据在哈希表中是否存在。目的是为了去重。

第二步,检查是否需要扩容。如果需要扩容,遍历原本哈希表中的每一个结点,重新计算映射下标,复用原来的结点,直接挂载到对应的桶下面。

第三步,插入新的结点。

注意:扩容时不推荐使用递归。递归时默认会重新创建新的结点,明明有原本的结点可以用,还要去创建新的结点,就会造成空间浪费。

bool Insert(const T &data)
{
    KeyOfT kot;
    Node *ret = Find(kot(data));        // 先判断要插入的数据是否存在,目的是为了去重
    if (ret)
        return false;

    HashFunc hf;
    // 负载因子 == 1时扩容
    if (_n == _tables.size())
    {
        size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
        vector<Node *> newTables;
        newTables.resize(newSize);
        // 遍历之前的哈希表,根据新的容量大小重新每个数据的映射(这里复用原来的结点)
        for (size_t i = 0; i < _tables.size(); ++i)    
        {
            Node *cur = _tables[i];
            while (cur)
            {
                Node *next = cur->_next;

                size_t index = hf(kot(cur->_data)) % newTables.size();
                // 头插
                cur->_next = newTables[index];        
                newTables[index] = cur;

                cur = next;
            }

            _tables[i] = nullptr;
        }

        _tables.swap(newTables);
    }

    size_t index = hf(kot(data)) % _tables.size();
    Node *newnode = new Node(data);
    // 头插
    newnode->_next = _tables[index];
    _tables[index] = newnode;

    ++_n;        // 有效数据个数自增
    return true;
}

 

6、移除实现

先根据 key 值确定要删除的结点在哪个桶下面,然后再开始遍历该桶下的链表结点。移除时需要考虑被删除的结点在当前链表中的位置,头删 or 中间删除

bool Erase(const K &key)
{
    if (_tables.empty())
    {
        return false;
    }

    HashFunc hf;
    size_t index = hf(key) % _tables.size();
    Node *prev = nullptr;
    Node *cur = _tables[index];
    KeyOfT kot;
    while (cur)
    {
        if (kot(cur->_data) == key)    
        {
            if (prev == nullptr) // 头删
            {
                _tables[index] = cur->_next;
            }
            else // 中间删除
            {
                prev->_next = cur->_next;
            }

            --_n;

            delete cur;

            return true;
        }
        else        
        {
            prev = cur;
            cur = cur->_next;
        }
    }

    return false;
}

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

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

相关文章

Week 6 hw3-1 全连接网络反向传播推导

Week 6 hw3-1 全连接网络反向传播推导 折腾了半天&#xff0c;记录一下。 作业中网络由若干全连接层ReLU组成&#xff0c;输出层的函数为softmax&#xff0c;损失函数为交叉熵。 一、记号 设网络有nnn层。如图&#xff0c;当i<ni<ni<n时&#xff0c;我们有如下几条…

机器学习/人工智能 实验一:典型监督学习方法分类实践与比较分析

一、实验目的与要求 (1)利用所学习的监督学习方法完成目标识别实验方案的设计。 (2)编程并利用相关软件完成实验测试&#xff0c;得到实验结果。 (3)通过对实验数据的分析﹑整理&#xff0c;方法的对比&#xff0c;得出实验结论&#xff0c;培养学生创新思维和编写实验报告的能…

【PyTorch深度学习实践】09_卷积神经网络基础

文章目录1.卷积操作1.1 卷积操作1.2 padding-填充1.3 stride-步长1.4 pooling-池化1.5 基础版CNN代码示例1.6 完整CNN代码示例1.卷积操作 卷积神经网络概览 1.1 卷积操作 输入通道数卷积核通道数&#xff0c;卷积核个数输出通道数 1.2 padding-填充 padding是为了让源图像最…

FPGA图像处理HLS实现三种图像缩放算法,线性插值、双线性插值、双三次插值,提供HLS工程和vivado工程源码

目录一、三种图像缩放算法介绍线性插值双线性插值双三次插值二、HLS实现线性插值图像缩放三、HLS实现双线性插值图像缩放四、HLS实现双三次插值图像缩放五、HLS在线仿真并导出IP六、其他FPGA型号HLS在线仿真并导出IP七、zynq7100开发板vivado工程八、上板调试验证九、福利&…

纪念QT可直接安装的离线版最后版本5.14.2

为什么说纪念呢&#xff1f;因为&#xff0c;这个版本之后再也没有可下载下来安装的版本了&#xff0c;因为我们以后再也没有这么方便了。为是很么说纪念呢&#xff1f;因为我们从QT还很柔弱的时候开始就是使用的离线版。 以前用c#来做组态&#xff0c;自定义控件开发起来也还…

基础知识一览2

这里写目录标题1.XML2.1 XML中的转义字符2.2 CDATA区2.3 如何去约束XMl:DTD2.3.1 xml文件内部引用DTD约束2.3.2 xml文件引用外部DTD约束2.3.3 xml文件引用公共DTD约束1.XML xml的文件后缀名是.xmlxml有且只有一个根标签xml的标签是尖括号包裹关键字成对出现的&#xff0c;有开…

如何做好banner设计(banner设计要点包括哪些)

网页设计的Banner作为表达网站价值或者传达广告信息的视觉主体&#xff0c;一直在根据网络环境的变化而变化着&#xff0c;从表现形式到尺寸大小&#xff0c;再到创意的多元化&#xff0c;因此更需要我们网页设计师们对其设计创意进行丰富和完善&#xff0c;才能真正达到宣传的…

Elasticsearch入门——Elasticsearch7.8.0版本和Kibana7.8.0版本的下载、安装(win10环境)

目录一、Elasticsearch7.8.0版本下载、安装1.1、官网下载地址1.2、下载步骤1.3、安装步骤(需要jdk11及以上版本支持)1.4、启动后&#xff0c;控制台中文乱码问题解决二、Node下载、安装&#xff08;安装Kibana之前需要先安装Node&#xff09;2.1、Node官网下载地址2.2、Node下载…

Linux文字处理和文件编辑(三)

1、Linux里的配置文件&#xff1a; /etc/bashrc文件&#xff1a;该配置文件在root用户下&#xff0c;权限很高。~/.bashrc文件&#xff1a;只有当前用户登录时才会执行该配置文件。每次打开终端&#xff0c;都会自动执行配置文件里的代码。比如&#xff0c;alias md‘mkdir’就…

《2022年终总结》

2022年终总结 笔者成为社畜的一年&#xff0c;整整打了一年工&#xff01; 之前都说每年都有点变化&#xff0c;今年的变化可能就是更加懒散了&#xff0c;玩了更多的手机 就是运动的坚持更加多了&#xff0c;收入也增加了&#xff0c;哈哈&#xff01; 其实今年的变化不大&am…

41. 【农产品溯源项目前后端Demo】后端目录结构

本节介绍下后端代码的目录结构。 1. 实现用户管理、菜单管理、角色管理、代码自动生成等服务,归结为系统管理,是若依框架提供的能力。 2. ruoyi-traces实现农产品溯源应用的代码,如果要引入其他Java包,修改本模块的pom.xml文件。 1)config包加载配置文件数据,配置文件路…

FPGA:IIC验证镁光EEPROM仿真模型(纯Verilog)

目录日常唠嗑一、程序设计二、镁光模型仿真验证三、testbench文件四、完整工程下载日常唠嗑 IIC协议这里就不赘述了&#xff0c;网上很多&#xff0c;这里推荐两个&#xff0c;可以看看【接口时序】6、IIC总线的原理与Verilog实现 &#xff0c;还有IIC协议原理以及主机、从机Ve…

基于SpringBoot的车牌识别系统(附项目地址)

yx-image-recognition: 基于spring boot maven opencv 实现的图像深度学习Demo项目&#xff0c;包含车牌识别、人脸识别、证件识别等功能&#xff0c;贯穿样本处理、模型训练、图像处理、对象检测、对象识别等技术点 介绍 spring boot maven 实现的车牌识别及训练系统 基于…

3-1存储系统-存储器概述主存储器

文章目录一.存储器概述&#xff08;一&#xff09;存储器分类1.按在计算机中的作用&#xff08;层次&#xff09;分类2.按存储介质分类3.按存取方式分类4.按信息的可保存性分类&#xff08;二&#xff09;存储器的性能指标二.主存储器&#xff08;一&#xff09;基本组成1.译码…

6 个必知必会高效 Python 编程技巧

编写更好的Python 代码需要遵循Python 社区制定的最佳实践和指南。遵守这些标准可以使您的代码更具可读性、可维护性和效率。 本文将展示一些技巧&#xff0c;帮助您编写更好的 Python 代码 文章目录遵循 PEP 8 风格指南1.遵守 PEP 8 命名约定2. 使用描述性的和有意义的变量名…

读书笔记--- ggplot2:数据分析与图形艺术

最近看了这本书《ggplot2&#xff1a;数据分析与图形艺术》&#xff08;第2版&#xff09;&#xff0c;实际上网页在线版本已经更新到第3版了&#xff08;https://ggplot2-book.org/&#xff09;。 这本书页数不多&#xff0c;但是整体还是值得阅读&#xff0c;不愧是Hadley W…

【Proteus仿真】【STM32单片机】酒精浓度检测系统设计

文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1602显示模块、按键模块、LED和蜂鸣器、MQ-3酒精传感器模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示酒精浓度值和阈值&…

插入排序.

根据找插入位置的方法分为&#xff1a; ①、顺序法定位插入位置——直接插入排序 ②、二分法定位插入位置——二分插入排序 ③、缩小增量多遍插入排序——希尔排序 一、直接插入排序&#xff08;以升序为例&#xff09; 先背模板&#xff01; void insert_sort(int *a,int le…

远程服务器(恒源云)上使用NNI进行训练调参的详细流程

远程服务器&#xff08;恒源云&#xff09;上使用NNI进行训练调参的详细流程 一、环境配置 pip下载安装nni&#xff0c;&#xff08;可使用豆瓣源&#xff0c;可快速下载&#xff0c;在安装命令后加 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com&#x…