开散列哈希桶

news2024/11/25 15:48:32

通过上面这幅图,读者应该能较为直观地理解何为开散列,以及闭散列与开散列的区别在哪里 —— 数据的存储形式不同,至于其他的,如确定每个元素的哈希地址等一概相同。

与闭散列相比,开散列能够更好地处理发生冲突的元素 —— 假使我们要在上述闭散列中再插入 5 ,会因为 24 的先插入而导致 5 必须往后寻找空位置,进而影响 6 的插入等。

1. 什么是桶?

通过 HashFunc 计算每个元素的哈希地址,哈希地址相同的元素所组成的子集称为 哈希桶 ,这些元素通过单链表链接在一起。

如:4 % 10 == 24 % 10 == 34 % 10 == 44 % 10 == 4

开散列的每个桶中存的都是发生哈希冲突的元素。

2. 开散列框架搭建
  • HashFunc
template<class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        size_t ret = key;
        return ret;
    }
};

// 为 string 写一个特化版本
template<>
struct HashFunc<string>
{
    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (auto& e : s)
        {
            hash = hash * 131 + e; // 131 是前辈用大量数据测试得到的值,可以尽大程度避免哈希冲突
        }
        return hash;
    }
};
  • HashNode
	template<class K, class V>
    struct HashNode
    {
        HashNode* _next;
        pair<K, V> _kv;
        
        HashNode(const pair<K, V>& kv)
            :_next(nullptr)
            ,_kv(kv)
        {}
    };
  • HashTable
	template<class K, class V, class Hash = HashFunc<K>>
    class HashTable
    {
        typedef HashNode<K, V> Node;
    public:
        HashTable()
        {
            _tables.resize(10);
        }
        
    private:
        vector<Node*> _tables;
        size_t _n = 0;
    };
3. Insert()
	bool Insert(const pair<K, V>& kv)
    {
        if (Find(kv.first)) // 未实现的 Find,已存在则返回该元素哈希位置的指针,不存在则返回空
            return false;
        Hash hs;
        // 扩容 
        if (_n == _tables.size()) // STL 库中,开散列的负载因子设为 1
        {
            // ...
        }
        
        // 插入
        
        size_t hashi = hs(kv.first) % _tables.size();
        Node* newNode = new Node(kv);
        
        newNode->_next = _tables[hashi];
        _tables[hashi] = newNode;// 头插
        
        ++_n;
        return true;
    }

再来聊一聊扩容逻辑。

与闭散列不同,我们不准备复用 Insert() 完成数据的拷贝 —— 假设哈希桶中已经存在 1000, 000 个元素,需要重新拷贝 1000, 000 个元素,再将原表中的元素一一释放。

更好的办法是,直接将原表中的节点 挂到 新表对应的哈希位置上

	// 扩容部分
	if (_n == _tables.size())
    {
        vector<Node*> newTable(2 * _tables.size(), nullptr);
        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i];
            while (cur)
            {
                Node* next = cur->_next;
                
                size_t hashi = hs(cur->_kv.first) % newTable.size();
                cur->_next = newTable[hashi];
                newTable[hashi] = cur;
                
                cur = next;
            }
            _tables[i] = nullptr;// 将原表置空
        }
        _tables.swap(newTable);
        // 不需要手动将 newTable delete[],编译器会自动调用 vector 的析构函数,
        // 且 swap 后,newTable 里全为空,不需要担心内存泄露的问题
    }
4. Find()Erase()
  • Find()
	Node* Find(const K& key)
    {
        Hash hs;
        size_t hashi = hs(key) % _tables.size();
        Node* cur = _tables[hashi];
        while (cur)
        {
            if (cur->_kv.first == key)
            {
                break;
            }
            cur = cur->_next;
        }
        if (cur && cur->_kv.first == key)
            return cur;
        else 
            return nullptr;
    }
  • Erase()

开散列的 Erase() 不能像闭散列那样,Find() 后直接删除。

调用 Find() 能得到 key 对应的 HashData 的指针,但无法得到前一个节点的指针,会造成一系列问题。

	bool Erase(const K& key)
    {
        Hash hs;
        size_t hashi = hs(key) % _tables.size();
        Node* cur = _tables[hashi];
        Node* prev = nullptr; // prev 为前一个节点指针
        while (cur)
        {
            if (cur->_kv.fisrt == key) // 找到了
            {
                if (prev) // prev 不为空,说明 cur 为中间节点
                {
                    prev->_next = cur->_next;
                }
                else // prev 为空,说明 cur 为 _tables[hashi]
                {
                    _tables[hashi] = cur->_next;
                }
                delete cur;
                --_n;
                return true;
            }
            
            prev = cur;
            cur = cur->_next;
        }
        return false;
    }

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

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

相关文章

知识付费行业数字化转型:转的是什么?你知道吗!

在知识付费的浪潮中&#xff0c;数字化转型正悄然改变着这个行业的格局&#xff01;那么&#xff0c;知识付费行业数字化转型到底转的是什么呢&#xff1f;这是一个值得我们深入探讨的问题。 1.转的是商业模式&#xff1a;从传统的销售模式转向多元化的盈利模式。从简单的买卖关…

数据结构(二) 线性表

2024年5月13日一稿 线性表的定义与基本操作 数据类型相同(各个元素占用空间相同) 是有限序列 基操

Netty源码分析二NioEventLoop 剖析

剖析方向 NioEventLoop是一个重量级的类&#xff0c;其中涉及到的方法都有很复杂的继承关系&#xff0c;调用链&#xff0c;要想把源码全部过一遍工作量实在是太大了&#xff0c;于是小编就基于下面的这些常见的问题来对NioEventLoop的源码来进行剖析 1.Seletor何时创建 1.1Se…

前端Vue架构

1 理解&#xff1a; 创建视图的函数&#xff08;render&#xff09;和数据之间的关联&#xff1b; 当数据发生变化的时候&#xff0c;希望render重新执行&#xff1b; 监听数据的读取和修改&#xff1b; defineProperty&#xff1a;监听范围比较窄&#xff0c;只能通过属性描…

基于SSM的计算机课程实验管理系统的设计与实现(源码)

| 博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f44…

架构每日一学 5:拼多多如何通过洞察人性脱颖而出?

本文首发于公众平台&#xff1a;腐烂的橘子 上一篇文章&#xff0c;我们讲到架构活动一定要顺应人性&#xff0c;今天我们就来聊一聊&#xff0c;拼多多如何通过洞察人性在电商行业脱颖而出。 拼多多从诞生到现在&#xff0c;可以说是颠覆了整个互联网的认知。 2015 年&#…

JSON 转为json串后出现 “$ref“

问题描述 转为JSON 串时出现 "$ref":"$.RequestParam.list[0]" $ref&#xff1a; fastjson数据重复的部分会用引用代替&#xff0c;当一个对象包含另一个对象时&#xff0c;fastjson就会把该对象解析成引用 “$ref”:”..” 上一级 “$ref”:”” 当前对…

SpringBoot自动配置源码解析+自定义Spring Boot Starter

SpringBootApplication Spring Boot应用标注 SpringBootApplication 注解的类说明该类是Spring Boot 的主配置类&#xff0c;需要运行该类的main方法进行启动 Spring Boot 应用 SpringBootConfiguration 该注解标注表示标注的类是个配置类 EnableAutoConfiguration 直译&#…

日本率先研发成功6G设备,刺痛了谁?为何日本能率先突破?

日本率先研发成功6G设备&#xff0c;无线数据速率是5G的百倍&#xff0c;这让日本方面兴奋莫名&#xff0c;毕竟日本在科技方面从1990年代以来太缺少突破的创新了&#xff0c;那么日本为何如今在6G技术上能率先突破呢&#xff1f; 日本在1980年代末期达到顶峰&#xff0c;它的科…

华为OD机试 - 求幸存数之和(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

python将图片以及标注信息按类别分开

目录 需求&#xff1a; 思路&#xff1a; 原数据集结构&#xff1a; 代码1&#xff08;效率低&#xff0c;但不用提前知道需要分多少个类别&#xff09;&#xff1a; 代码2&#xff08;效率相对高点&#xff0c;但类别数量如果超过设定的11个&#xff0c;则需要改下代码&a…

MySQL·索引

目录 索引的意义 索引的理解 为何IO交互要是 Page 理解Page 其他数据结构为何不行&#xff1f; 聚簇索引 VS 非聚簇索引 索引操作 主键索引操作 唯一键索引操作 普通索引的创建 总结 全文索引 索引的意义 索引&#xff1a;提高数据库的性能&#xff0c;索引是物美…

[AIGC] 压缩列表了解吗?快速列表 quicklist 了解吗?

文章目录 压缩列表了解吗&#xff1f;快速列表 quicklist 了解吗&#xff1f; 压缩列表了解吗&#xff1f; 压缩列表是 Redis 为了节约内存 而使用的一种数据结构&#xff0c;是由一系列特殊编码的连续内存快组成的顺序型数据结构。 一个压缩列表可以包含任意多个节点&#xf…

ae如何导出mp4格式?图文教程,手把手教您搞定

在创作精彩的视频内容后&#xff0c;将其成功导出为通用的MP4格式是确保作品在不同平台上流畅播放的重要一环。Adobe After Effects作为一款专业的视频后期制作工具&#xff0c;提供了丰富的功能来实现这一目标。在本文中&#xff0c;我们将通过图文教程&#xff0c;手把手地向…

人生是旷野,不是轨道

最近看到一句话&#xff0c;很喜欢&#xff0c;分享一下。"人生是旷野&#xff0c;不是轨道"。人生不是固定的方程式&#xff0c;也没有唯一答案&#xff0c;没有谁生来就应该是什么样。别太被太多世俗观念束缚住手脚&#xff0c;每个人都有权利自由生长&#xff0c;…

【Ubuntu】apt命令安装最新版本Nginx

目录 环境前言添加Nginx仓库步骤1、仓库公钥2、文本公钥转二进制GPG公钥&#xff08;可选&#xff09;3、添加apt软件源4、安装新版Nginx 参阅 环境 Ubuntu 22.04 前言 ubuntu官方apt软件仓库&#xff08;或者叫软件源&#xff09;的软件版本可能会比较旧&#xff0c;导致无…

Unity 性能优化之GPU Instancing(五)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、GPU Instancing使用方法二、使用GPU Instancing的条件三、GPU Instancing弊端四、注意五、检查是否成功总结 前言 GPU Instancing也是一种Draw call…

xCode升级后: Library ‘iconv2.4.0’ not found

报错信息&#xff1a; targets 选中 xxxNotification: Build Phases ——> Link Binary With Libraries 中&#xff0c;移除 libiconv.2.4.0.tbd libiconv.2.4.0.dylib 这两个库&#xff08;只有一个的移除一个就好&#xff09;。 然后重新添加 libiconv.tbd 修改完…

如何将Git仓库中的文件打包成zip文件?

要将Git仓库中的文件打包成zip文件&#xff0c;您可以使用git archive命令。这个命令允许您将任何git可访问的树或提交导出成一个归档文件。以下是一些基本的步骤&#xff1a; 打开命令行或终端。切换到您的Git仓库的目录。执行git archive命令。 git archive --formatzip --o…

【ARMv8/v9 系统寄存器 5 -- CPU ID 判断寄存器 MPIDR_EL1 使用详细介绍】

文章目录 寄存器名称: MPIDR_EL1寄存器结构:主要功能和用途亲和级别&#xff08;Affinity Levels&#xff09;简介CORE ID 获取函数 在ARMv8-A架构中&#xff0c; MPIDR_EL1寄存器是一个非常重要的系统寄存器&#xff0c;它提供了关于处理器在其物理和逻辑配置中的位置的信息。…