[C国演义] 哈希的使用和开闭散列的模拟实现

哈希的使用和开闭散列的模拟实现

  • 1. 使用
    • 1.1 unordered_map的接口
    • 1.2 unordered_set的接口
  • 2. 哈希底层
    • 2.1 概念
    • 2.2 解决哈希冲突
  • 3. 实现
    • 3.1 开放寻址法
    • 3.2 拉链法

1. 使用

1.1 unordered_map的接口

  1. 构造
void test1()
{// 空的unordered_map对象unordered_map<int, int> m1(10);cout << "桶的实际个数->" << m1.bucket_count() << endl;// 用列表初始化来进行初始化unordered_map<int, int> m2{ {1,1}, {2,2}, {3,3} };cout << "列表初始化-> " << endl;for (const auto& e : m2){cout << e.first << " " << e.second << endl;}cout << endl;// 迭代器区间初始化unordered_map<int, int> m3(m2.begin(), m2.end());cout << "迭代器区间初始化-> " << endl;for (const auto& e : m2){cout << e.first << " " << e.second << endl;}cout << endl;
}int main()
{test1();return 0;
}

运行结果 :

桶的实际个数->16
列表初始化->
1 1
2 2
3 3迭代器区间初始化->
1 1
2 2
3 3
  1. 容量
  2. 元素访问
void test2()
{unordered_map<string, int> mat{ {"小呆呆", 1}, {"波比", 2} };cout << "初始化-> " << endl;for (const auto& e : mat){cout << e.first << " " << e.second << endl;}cout << endl;mat["猪猪侠"];cout << "插入功能-> " << endl;for (const auto& e : mat){cout << e.first << " " << e.second << endl;}cout << endl;mat["波比"] = 5;cout << "修改功能-> " << endl;for (const auto& e : mat){cout << e.first << " " << e.second << endl;}cout << endl;mat["超人强"] = 6;cout << "插入 + 修改功能-> " << endl;for (const auto& e : mat){cout << e.first << " " << e.second << endl;}cout << endl;
}int main()
{test2();return 0;
}

运行结果 :

初始化->
小呆呆 1
波比 2插入功能->
小呆呆 1
猪猪侠 0
波比 2修改功能->
小呆呆 1
猪猪侠 0
波比 5插入 + 修改功能->
小呆呆 1
猪猪侠 0
波比 5
超人强 6
  1. 查询
void test3()
{unordered_map<string, int> mat{ {"小呆呆", 1}, {"波比", 2} };mat["猪猪侠"] = 5;mat["猪猪侠"] = 7;size_t cnt = mat.count("猪猪侠");cout << cnt << endl;auto it = mat.find("小呆呆");if (it != mat.end()){cout << it->first << " is " << it->second << endl;}else{cout << "查无元素! " << endl;}cout << endl;auto git = mat.find("超人强");if (git != mat.end()){cout << git->first << " is " << git->second << endl;}else{cout << "查无元素! " << endl;}}int main()
{test3();return 0;
}

运行结果 :

1
小呆呆 is 1查无元素!
  1. 修改
void test4()
{unordered_map<string, int> mat1{ {"小呆呆", 1}, {"波比", 2} };// 插入mat1.insert({ "小呆呆", 5 });mat1.insert({ "超人强", 6 });for (const auto& e : mat1){cout << e.first << " " << e.second << endl;}cout << endl;// 删除mat1.erase("波比");mat1.erase("猪猪侠");for (const auto& e : mat1){cout << e.first << " " << e.second << endl;}cout << endl;// 交换unordered_map<string, int> mat2{ {"迪迦",1}, {"戴拿",2}};mat1.swap(mat2);cout << "交换后的mat1-> " << endl;for (const auto& e : mat1){cout << e.first << " is " << e.second << endl;}cout << endl;cout << "交换后的mat2-> " << endl;for (const auto& e : mat2){cout << e.first << " is " << e.second << endl;}
}int main()
{test4();return 0;
}

运行结果 :

小呆呆 1
波比 2
超人强 6小呆呆 1
超人强 6交换后的mat1->
迪迦 is 1
戴拿 is 2交换后的mat2->
小呆呆 is 1
超人强 is 6
  1. 桶操作
void test5()
{unordered_map<string, int> mat1{ {"小呆呆", 1}, {"波比", 2}, {"迪迦", 3} };// 桶的个数cout << mat1.bucket_count() << endl << endl;// 每个桶的有效个数for (int i = 0; i < mat1.bucket_count(); i++){printf("[%d] -> %d\n", i, mat1.bucket_size(i));}cout << endl;// 各个key所在的桶for (const auto& e : mat1){cout << mat1.bucket(e.first) << endl;}
}int main()
{test5();return 0;
}

运行结果 :

8[0] -> 1
[1] -> 0
[2] -> 1
[3] -> 0
[4] -> 0
[5] -> 0
[6] -> 1
[7] -> 02
6
0

1.2 unordered_set的接口

unordered_set 和 unordered_map的接口大致一样, 但是没有 operator [ ]

2. 哈希底层

2.1 概念

unordered_set 和 unordered_map的效率高的原因 ⇒ 底层是哈希结构

  • 理想中的搜索方法 :
    顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( l o g 2 N log_2 N log2N),搜索的效率取决于搜索过程中元素的比较次数。
    理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
    如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素.

使元素的存储位置和关键码建立一 一映射的关系, 这个方法称为 哈希(散列)方法,
通过一个函数使得存储位置和关键码建立一 一映射的关系, 这个函数称为 哈希函数,
最终形成的结构, 称为 哈希(散列)表, HashTable
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为 哈希冲突或哈希碰撞

  • 先浅浅地看一下哈希结构, 来理解一下概念

  • 常见的哈希函数
    哈希冲突的一个重要原因就是 哈希函数设置的不好
    那么, 我们来了解一下最常见的两个哈希函数

    1. 直接寻址法 : 一个key对应一个位置
      前提 : 知道数据集合的大小 和 分布情况
      适合场景: 数据量小且均匀

    2. 除留余数法 : 准备一个基准值去估计数据量的多少, 设为m, 采用 hash(key) = key % m的方法去建立元素和下标的一 一 映射关系

  • 采用直接寻址法 — — 数据量小 且 集中
    字符串中第一个唯一字符

class Solution {
public:int firstUniqChar(string s) {// <s中的每个字符, 个数>int hash[26] = {0};// 映射for(auto e : s){hash[e-'a']++;}// 查找for(int i = 0; i < s.size(); i++){if(hash[s[i] - 'a'] == 1){return i;}}return -1;}
};
  • 采用除留余数法 — — 任何场景下都可
    下面的 哈希冲突解决 和 实现 都是采用的除留余数法

  • 哈希函数设置的越巧妙, 哈希冲突就越低, 但是 哈希冲突无法避免

2.2 解决哈希冲突

解决哈希冲突主要有两种方法 : 闭散列 和 开散列

  1. 闭散列
    闭散列, 也叫 开放寻址法,
    思路是 : 当冲突发生时, 必然有空位置, 那么把冲突的元素放到 "下一个空位置" 即可!
  • 插入逻辑

  • 查找逻辑

    • 这从另一方面也体现了 哈希表的有效数据不应该占比太大 ⇒ 否则就是遍历这个哈希结构, O(N)
      但是 也不能占比太少 ⇒ 浪费空间
      一般, 控制 有效个数 / 哈希结构的大小 在 [0.7, 0.8]的范围内是比较合理的
  • 删除逻辑
    首先, 能确定的是不能直接把这个位置去掉

    那么该位置要进行保留, 那么 值该怎么处理呢 ?
    改为 0, -1 … … 等无意义的数值?
    其实这些都是不行的, 你怎么知道你修改后的数据是无意义的呢 ⇒ 能确定的是 该位置的值也要进行保留
    该位置要进行保留, 值也要进行保留 && 不能影响后面的查找逻辑 那么该怎么把它删掉呢? ⇒ 引入每个下标的状态 : 删除状态, 空白状态, 存在状态

  • 由于删除逻辑而导致新的插入逻辑

  • 由于删除逻辑而导致新的查找逻辑

  1. 开散列
    开散列, 又叫作 拉链法
    上面的 开放地址法 解决哈希冲突的办法是 将经过哈希函数处理过的 相同的key, "延后落座"
    拉链法的解决思路是 将经过哈希函数处理的 相同的key 放到一个单链表中, 然后将每一个单链表的头结点放到一个数组里面. 本质是一个 指针数组
  • 这里的插入删除, 查找逻辑就是在 key那个桶进行单链表操作

🗨️ 有同学就会说, 这不是单链表操作吗, 不过如此!

  • 我们可以控制 有效数据个数 / 桶的大小 = 1 ⇒ 平均下来就是一个桶一个数据

3. 实现

这里都先实现 数据位pair<K, V>类型的

3.1 开放寻址法

  1. STATE类型
enum STATE
{EXIT,DELETE,EMPTY
};
  1. HashData类
template<class K, class V>
struct HashData
{public:HashData(){}HashData(const pair<K, V>& kv):_data(kv){}public:pair<K, V> _data;STATE _st = EMPTY;
};
  1. Hash类
template<class K, class V, class Com = DEFAULT<K>>
class hash
{public:hash(){// 1. 先给4个空间// 2. size 和 capacity一样大_table.resize(4);}bool insert(const pair<K, V>& kv){// 扩容逻辑if ((double)_sz / _table.size() >= 0.7){size_t newsize = _table.size() * 2;hash<K, V> new_ht;new_ht._table.resize(newsize);// 挪动数据for (size_t i = 0; i < _table.size(); i++){// 不用挪动删除状态的值if (_table[i]._st == EXIT){new_ht.insert(_table[i]._data);}}std::swap(*this, new_ht);}// 线性探测for (const auto& e : _table){if (kv.first == e._data.first){return false;}}size_t hashi = com(kv.first) % _table.size();while (_table[hashi]._st == EXIT){++hashi;hashi %= _table.size();}_sz++;_table[hashi] = kv;_table[hashi]._st = EXIT;return true;}// 返回有key,// 不允许用户在外面更改key,// 所以返回<const K, V>*HashData<const K, V>* find(const K& key){size_t hashi = com(key) % _table.size();while (_table[hashi]._st != EMPTY){if (_table[hashi]._st == EXIT &&  _table[hashi]._data.first == key){return (HashData<const K, V>*)&_table[hashi];}hashi++;hashi %= _table.size();}return nullptr;}bool erase(const K& key){// 复用findHashData<const K, V>* res = find(key);if (res){res->_st = DELETE;_sz--;return true;}else{return false;}//for (auto e : _table)//{//	if (e._data.first == key)//	{//		e._st = DELETE;//		_sz--;//		return true;//	}//}//return false;}private:vector<HashData<K, V>> _table;size_t _sz = 0;Com com;
};
  1. DEFAULT — 通过仿函数来解决 字符串 不能进行 % 的问题
// 通过仿函数来解决 字符串 不能进行 % 
template<class K>
struct DEFAULT
{size_t operator()(const K& key){return (size_t)key;}
};// 模版的特化 -- 全特化
// 解决 字符串问题
template<>
struct DEFAULT<string>
{size_t operator()(const string& key){int res = 0;for (auto e : key){res += e * 131;}return res;}
};

3.2 拉链法

  1. HashData类
template<class K, class V>
struct HashData
{
public:
HashData(const pair<K, V>& kv):_data(kv)
{}public:
pair<K, V> _data;
HashData<K, V>* _next;
};
  1. Hash类
template<class K, class V, class Com = DEFAULT<K>>
class hash
{typedef HashData<const K, V> Node;
public:hash(){_table.resize(4, nullptr);}Node* find(const K& key){size_t hashi = com(key) % _table.size();Node* cur = _table[hashi];while (cur){if (com(cur->_data.first) == com(key)){return cur;}cur = cur->_next;}return nullptr;}bool insert(const pair<K, V>& kv){Node* res = find(kv.first);if (res){return false;}// 扩容逻辑if (_sz == _table.size()){vector<Node*> new_table;new_table.resize(_table.size() * 2, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];// 顺手牵走这个桶的内容while (cur){// 提前保存 next, 后面会改变的Node* next = cur->_next;size_t hashi = com(cur->_data.first) % new_table.size();// 先让cur链接上新表中该桶的内容cur->_next = new_table[hashi];// 再让cur成为新表中该桶的头节点new_table[hashi] = cur;cur = next;}}_table.swap(new_table);}// 插入逻辑size_t hashi = com(kv.first) % _table.size();Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_sz;return true;}bool erase(const K& key){Node* res = find(key);if (res == nullptr){return false;}else{size_t hashi = com(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){if (cur->_data.first == key){if (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}}prev = cur;cur = cur->_next;}--_sz;delete cur;}return true;}void print(){for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];printf("[%d]->", i);while (cur){printf("%d", cur->_data.first);cur = cur->_next;}cout << "NULL" << endl;}cout << endl;}private:vector<Node*> _table;size_t _sz = 0;Com com;
};
  1. DEFAUL — 通过仿函数来解决 字符串 不能 % 的问题
// 通过仿函数来解决 字符串 不能进行 % 
template<class K>
struct DEFAULT
{size_t operator()(const K& key){return (size_t)key;}
};// 模版的特化 -- 全特化
// 解决 字符串问题
template<>
struct DEFAULT<string>
{size_t operator()(const string& key){int res = 0;for (auto e : key){res += e * 131;}return res;}
};

无心买酒谒青春,对镜空嗟白发新。
花下少年应笑我,垂垂羸马访高人。
— — 岳飞 <过张溪赠张完>

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

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

相关文章

动态头像如何制作?这个方法请收藏

照片是记录生活的一种方式&#xff0c;但是静态图片有时候不能够完全表达我们的情感。而动态的图片能够让图片以更生动的方式来展示我们的想象力和内心情感。那么&#xff0c;大家知道动态图片制作的方法有哪些吗&#xff1f;使用gif动画制作&#xff08;https://www.gif.cn/&a…

React项目首页中用canvas实现星空

文章目录 前言代码使用后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端系列文章 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&#xff0c;感谢大家…

Element UI之Checkbox 多选框

Checkbox 多选框 在一组选项中进行多选 按需引入方式 如果是完整引入可跳过此步骤 import Vue from vue import { Checkbox, CheckboxGroup } from element-ui import element-ui/lib/theme-chalk/base.css import element-ui/lib/theme-chalk/checkbox.css import element…

两个macos命令

ldd替代: otool -L strace替代: sudo dtruss

机器学习笔记 - 隐马尔可夫模型的简述

隐马尔可夫模型是一个并不复杂的数学模型,到目前为止,它一直被认为是解决大多数自然语言处理问题最为快速、有效的方法。它成功地解决了复杂的语音识别、机器翻译等问题。看完这些复杂的问题是如何通过简单的模型得到描述和解决,我们会由衷地感叹数学模型之妙。 人类信息交流…

数据结构与算法设计分析——常用搜索算法

目录 一、穷举搜索二、图的遍历算法&#xff08;一&#xff09;深度优先搜索&#xff08;DFS&#xff09;&#xff08;二&#xff09;广度优先搜索&#xff08;BFS&#xff09; 三、回溯法&#xff08;一&#xff09;回溯法的定义&#xff08;二&#xff09;回溯法的应用 四、分…

Python学习笔记--进程

进程 Python 中的多线程其实并不是真正的多线程,如果想要充分地使用多核 CPU 的资源,在 Python 中大部分情况需要使用多进程。 Python 提供了非常好用的多进程包 multiprocessing,只需要定义一个函数,Python 会完成其他所有事情。 借助这个包,可以轻松完成从单进程到并…

EfficientNet:通过模型效率彻底改变深度学习

一、介绍 EfficientNet 是深度学习领域的里程碑&#xff0c;代表了神经网络架构方法的范式转变。EfficientNet 由 Google Research 的 Mingxing Tan 和 Quoc V. Le 开发&#xff0c;在不影响性能的情况下满足了对计算高效模型不断增长的需求。本文深入探讨了 EfficientNet 背后…

百度搜索智能化算力调控分配方法

作者 | 泰来 导读 随着近年深度学习技术的发展&#xff0c;搜索算法复杂度不断上升&#xff0c;算力供给需求出现了爆发式的增长。伴随着AI技术逐步走到深水区&#xff0c;算法红利在逐步消失&#xff0c;边际效应日益显著&#xff0c;算力效能的提升尤为重要&#xff0c;同时随…

每日一练 | 华为认证真题练习Day132

1、ACL本质上是一种报文过滤器&#xff0c;将ACL在业务模块中应用&#xff0c;ACL才能生效。 A. 对 B. 错 2、某个ACL规则如下&#xff1a;则下列哪些IP地址可以被permit规则匹配&#xff1f;&#xff08;多选&#xff09; rule 5 permit ip source 10.0.1.0 0.0.254.255 A…

视频修复软件 Aiseesoft Video Repair mac中文版功能

AIseesoft Video RepAIr mac是一款专业的视频修复软件&#xff0c;主要用于修复损坏或无法播放的视频文件。AIseesoft Video RepAIr是一个功能强大的程序,可以帮助恢复丢失或损坏的数据的视频。只要您以相同的格式提供示例视频,并在功能强大的技术的支持下,只需单击几下即可收获…

如何正确规划 JVM 性能调优

JVM性能调优涉及到很多方面的权衡&#xff0c;其中某一方面可能会极大地影响整体性能。因此&#xff0c;需要综合考虑所有可能的影响。理解并遵循一些基本原则和理论将使性能调优变得更加容易。为了更好地理解本文的内容&#xff0c;您必须满足以下先决条件&#xff1a; 了解 …

Docker 笔记(一)--安装

Docker 笔记&#xff08;一&#xff09;–安装 记录Docker 安装操作记录&#xff0c;便于查询。 参考 链接: Docker 入门到实战教程(二)安装Docker链接: docker入门(利用docker部署web应用)链接: 阿里云容器镜像服务/镜像加速器/操作文档链接: 网易镜像中心链接: 阿里云镜像…

智能配电系统解决方案

智能配电系统解决方案是一种集成了先进技术和智能化功能的配电系统&#xff0c;它能够提高电力系统的效率、可靠性和安全性。力安科技智能配电系统解决方案依托电易云-智慧电力物联网&#xff0c;具体实施的方案如下&#xff1a; 智能化设备和传感器&#xff1a;采用智能化的开…

「Java开发指南」如何在Spring中使用JAX-WS注释器?

本文将指导您如何使用JAX-WS注释器从Spring服务生成JAX-WS Web服务&#xff0c;在本教程中&#xff0c;您将学习如何&#xff1a; 为Spring服务启用JAX-WS部署应用程序并测试服务 所有与Spring scaffolding相关的任务都需要MyEclipse Spring或Bling授权。 MyEclipse v2023.1…

RT-DETR优化改进:轻量级Backbone改进 | VanillaNet极简神经网络模型 | 华为诺亚2023

🚀🚀🚀本文改进:一种极简的神经网络模型 VanillaNet,支持vanillanet_5, vanillanet_6, vanillanet_7, vanillanet_8, vanillanet_9, vanillanet_10, vanillanet_11等版本,相对于自带的rtdetr-l、rtdetr-x参数量如下: layersparametersgradientsvanillanet_5338277174…

MapApp 地图应用

1. 简述 1.1 重点 1&#xff09;更好地理解 MVVM 架构 2&#xff09;更轻松地使用 SwiftUI 框架、对齐、动画和转换 1.2 资源下载地址: Swiftful-Thinking:https://www.swiftful-thinking.com/downloads 1.3 项目结构图: 1.4 图片、颜色资源文件图: 1.5 启动图片配置图: 2. Mo…

前端JS 使用input完成文件上传操作,并对文件进行类型转换

使用input实现文件上传 // 定义一个用于文件上传的按钮<input type"file" name"upload1" />// accept属性用于定义允许上传的文件类型&#xff0c; onchange用于绑定文件上传之后的相应函数<input type"file" name"upload2"…

数据结构-链表的简单操作代码实现3-LinkedList【Java版】

写在前: 本篇博客主要介绍关于双向链表的一些简答操作实现&#xff0c;其中有有部分代码的实现和前两篇博客中的单向链表是相类似的。例如&#xff1a;查找链表中是否包含关键字key、求链表的长度等。 其余的涉及到prev指向的需要特别注意&#xff0c;区分和单向链表之间的差异…

uniapp heckbox-group实现多选

文章目录 html 代码JS 代码 混了业务逻辑&#xff0c;谨慎观看 html 代码 <view><!--可滚动视图区域。用于区域滚动 --><scroll-view :style"{ height: clientHeight px }" :scroll-top"scrollTop" scroll-y"true"scrolltouppe…