哈希(hash)——【C++实现】

在这里插入图片描述

本章gitee代码仓库:Hash

文章目录

    • 💐1. 哈希概念
    • 🌻2. 哈希冲突
    • 🌼3. 哈希函数
      • 🌸3.1 哈希函数设计原则
      • 🌸3.2 常见哈希函数
    • 🪴4. 哈希冲突解决方案
      • 🌱4.1 闭散列——开放定址法
        • 🌿4.11 负载因子
        • 🌿4.12 字符串哈希算法
        • 🌿4.13 代码实现
      • 🌱4.2 开散列——哈希桶
        • 🌿4.21 代码实现

💐1. 哈希概念

我们对元素进行搜索有几种方式:

  1. 暴力查找,直接遍历元素,时间复杂度为O(N)

    image-20230917101056152

  2. 二分查找,时间复杂度为O(logN)

    但二分查找有2个弊端:

    • 必须为有序
    • 增删查改不方便

    这两个弊端导致二分查找只是一个理想的查找方式,并不是很现实

    image-20230917101700713

  3. 平衡搜索树,增删查改的时间复杂度都是O(logN),总体性能都很不错

    image-20230917101710006

这些结构中的元素关键码和存储位置都没有对应的关系,而有一种方法名为哈希(也叫散列),它提供了一种与这些完全不同的存储和查找方式,即将存储的值和存储的位置建立出一个对应的函数关系。

有一种排序名为计数排序,将不同的值映射到对应的位置,这本质上就是哈希

不了解的可以看下这篇文章——非比较排序——计数排序

我们的通讯录,按照名字的首字母进行分类,本质也是哈希

image-20230917103230972

以数组{1,8,6,3}为例,假设hashi = key % 6 ,那则有如下对应关系

image-20230917104706750

这样就能通过取模直接定位到该元素的位置

但是如果进行插入元素,例如插入22%6=2,这就会导致和8的位置一样

🌻2. 哈希冲突

不同的关键字通过哈希函数计算出了相同的地址,值和位置出现了多对一的关系,这种线性称之为哈希冲突(哈希碰撞)。

解决方案:

  1. 选择合理的哈希函数
  2. 拟定冲突方案

🌼3. 哈希函数

出现哈希碰撞的原因之一可能就是哈希函数设计的不合理

🌸3.1 哈希函数设计原则

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

🌸3.2 常见哈希函数

  1. 直接定址法(值的范围集中)

    取某个线性函数作为散列的地址:Hash(key) = A*key + B

  2. 除留余数法(值的范围分散)

    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m质数p作为除数

    按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

🪴4. 哈希冲突解决方案

🌱4.1 闭散列——开放定址法

如果当前位置被占用,按照规则找到下一个位置(占用其他元素的位置)

image-20230917112017514

我们删除元素通常都不是直接删除,而采用覆盖的方式,而这里无法很好的覆盖

例如我们删除元素1,如果挪动后面的数据,这就导致映射关系全部乱了,如果不直接覆盖,那么之后又元素插入进来的时候,1这个位置还是有元素的,无法插入

所有这里采用状态的方式进行标记:

  • 存在——EXIST
  • 空——EMPTY
  • 删除——DELETE

🌿4.11 负载因子

哈希表定义了一个载荷因子α = 填入表中元素个数 / 哈希表的长度,这个是表示哈希表装满长度的标志因子

如果负载因子设计的大,那么哈希冲突的概率就越大(空间利用率高)

如果负载因子设计的小,那么哈希冲突的概率就越小(空间利用率低)

对于开放定址法,经过测算,负载因子应该控制在0.7 ~ 0.8,下面代码实现采用0.7

🌿4.12 字符串哈希算法

面对字符串的哈希函数,我们采用BKDRHash函数

有兴趣可查看此篇文章——各种字符串Hash函数

🌿4.13 代码实现

template<class K>
struct DefaultHashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
//模板特化
template<>
struct DefaultHashFunc<string>
{size_t operator()(const string& str){//BKDR hashsize_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return (size_t)str[0];}
};//开放定址法
namespace open_address
{enum STATE{EXIST,EMPTY,DELETE};template<class K, class V>struct HashDate{pair<K, V> _kv;STATE _state = EMPTY;};template<class K, class V, class HashFunc = DefaultHashFunc<K>>class HashTable{public:HashTable(){_table.resize(10);	//预先开好10个空间}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;//扩容if (_n * 10 / _table.size() >= 7)	//设负载因子为0.7{size_t newSize = _table.size() * 2;//扩容之后关系改变,需要重新映射HashTable<K, V> newHT;newHT._table.resize(newSize);//旧表数据插入到新标for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._kv);}}//新标和旧表交换_table.swap(newHT._table);}//不能取模capacity,虽然空间有,但访问还是要看size的大小,不然会发生越界HashFunc hf;size_t hashi = hf(kv.first) % _table.size();while (_table[hashi]._state == EXIST){//线性探测hashi++;hashi %= _table.size();}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}HashDate<const K, V>* Find(const K& key){HashFunc hf;size_t hashi = hf(key) % _table.size();while (_table[hashi]._state != EMPTY){if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key){return (HashDate<const K, V>*) & _table[hashi];}++hashi;hashi %= _table.size();}return nullptr;}bool Erase(const K& key){HashDate<const K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}return false;}private:vector<HashDate<K, V>> _table;	//哈希表size_t _n = 0;	//有效元素个数};
}

线性探测会导致一片拥堵,为此还有一种方法为二次探测

例如线性探测是:

hashi = key % n;
//如果有值了 i>=0
hashi+=i;

而二次探测则是:

hashi = key % n;
//如果有值 i>=0
hashi + i^2;

这样就能在一定程度上减少拥堵

🌱4.2 开散列——哈希桶

开放定址法的缺陷就是冲突会相互影响。而哈希桶的做法是,设置一个指针数组,如果发现冲突,则内部消化

image-20230917141822713

这里桶的结构其实就是链式结构,对每个桶的管理就相当于对于链表的管理,下面的代码采用的是单链表

这里也是需要进行扩容,如果不扩容,就会导致在某种情况下,桶越来越长,这样查找数据就变成了对链表数据的查找,时间复杂度为O(N),所以还是需要进行扩容。

这里的负载因子可以适当放大一点,一般负载因子控制在1,平均下来每个桶都有数据

🌿4.21 代码实现

这里的桶因为是自定义的链式结构,所以需要我们自己写拷贝构造和析构函数

//哈希桶
namespace hash_bucket
{template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode<K,V>* _next;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};template<class K,class V,class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_table.resize(10, nullptr);}~HashTable(){for (size_t i = 0; i <_table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}//拷贝构造HashTable(const HashTable& ht){_table.resize(ht._table.size(), nullptr);HashFunc hf;for (size_t i = 0; i < ht._table.size(); i++){Node* cur = ht._table[i];while (cur){Node* newNode = new Node(cur->_kv);size_t hashi = hf(cur->_kv.first) % ht._table.size();//头插newNode->_next = _table[hashi];_table[hashi] = newNode;cur = cur->_next;}}_n = ht._n;}void Print(){for (size_t i = 0; i < _table.size(); i++){printf("[%d]->", (int)i);Node* cur = _table[i];while (cur){cout << cur->_kv.first << "->";cur = cur->_next;}cout << "NULL" << endl;}cout << endl;}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;HashFunc hf;//扩容 -- 扩容的时候会稍微慢一点 ---^(扩容)-----^(扩容)----------^(扩容)-----.....//这里的扩容不能和开放定址法一样采用将旧表元素重新插入新表//因为这里涉及到开节点,新表开新节点,旧表释放旧节点,浪费if (_n == _table.size()){size_t newSize = _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize,nullptr);//遍历旧表,将节点牵过来for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;//头插到新表size_t newHashi = hf(cur->_kv.first) % newSize;cur->_next = newTable[newHashi];newTable[newHashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kv.first) % _table.size();//头插Node* newNode = new Node(kv);newNode->_next = _table[hashi];_table[hashi] = newNode;++_n;return true;}Node* Find(const K& key){HashFunc hf;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){HashFunc hf;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){//头删if (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _table;	//指针数组size_t _n = 0;	//有效元素};
}

本次参考了《数据结构(用面向对象方法与C++语言描述)》,详细的内容可以参考此书

那么本期的分享就到这里咯,我们下期再见,如果还有下期的话。

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

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

相关文章

【ROS】Ubuntu20.04+ROS Noetic 配置PX4-v1.12.2和Gazebo11联合仿真环境【教程】

【ROS】Ubuntu20.04ROS Noetic 配置PX4-v-v1.12.2和Gazebo11联合仿真环境【教程】 文章目录 【ROS】Ubuntu20.04ROS Noetic 配置PX4-v-v1.12.2和Gazebo11联合仿真环境【教程】0. 安装UbuntuROS1. 安装依赖2. 安装QGC地面站3. 配置PX4-v1.12.23.1 安装PX43.2 测试PX4是否成功安装…

Java常见面试题(含答案,持续更新中~~)

目录 1、JVM、JRE和JDK的关系 2、什么是字节码&#xff1f;采用字节码的最大好处是什么 3、Java和C的区别与联系 4、Java和GO的区别与联系 5、 和 equals 的区别是什么&#xff1f; 6、Oracle JDK 和 OpenJDK 的对比 7、String 属于基础的数据类型吗&#xff1f; 8、fi…

外星人入侵游戏-(创新版)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

【Tensorflow 2.12 电影推荐项目搭建】

Tensorflow 2.12 电影推荐项目搭建 学习笔记工具、环境创建项目项目配置安装相关python包召回模型实现排序模型实现实现电影推荐导入模块设置要推荐的用户召回推荐排序推荐推荐结果结尾学习笔记 Tensorflow 2.12 电影推荐项目搭建记录~ Tensorflow是谷歌开源的机器学习框架,可…

再次理解Android账号管理体系

目录 ✅ 0. 需求 &#x1f4c2; 1. 前言 &#x1f531; 2. 使用 2.1 账户体系前提 2.2 创建账户服务 2.3 操作账户-增删改查 &#x1f4a0; 3. 源码流程 ✅ 0. 需求 试想&#xff0c;自己去实现一个账号管理体系&#xff0c;该如何做呢&#xff1f; ——————————…

MySQL 学习笔记(基础)

首先解释数据库DataBase&#xff08;DB&#xff09;&#xff1a;即存储数据的仓库&#xff0c;数据经过有组织的存储 数据库管理系统DataBase Management System&#xff08;DBMS&#xff09;&#xff1a;管理数据库的软件 SQL&#xff08;Structured Query Language&#xf…

3D视觉到三维视觉之结构光

3D视觉是计算机视觉的终极体现形式 2D视觉技术主要在二维空间下完成工作&#xff0c;三维信息基本上没有得到任何利用&#xff0c;而三维信息才真正能够反映物体和环境的状态&#xff0c;也更接近人类的感知模式。近年来&#xff0c;学术界和工业界推出了一系列优秀的算法和产…

redis 哨兵(sentinel)机制

1. 前言 sentinel&#xff08;哨兵&#xff09;是Redis 的高可用性解决方案之一。通过哨兵可以创建一个当主服务器出现故障时自动将从服务器升级为主服务器的分布式系统&#xff0c;解决了主从复制出现故障时需要人为干预的问题。 redis 的主从复制的作用有数据预热、负载均衡…

【初阶数据结构】树(tree)的基本概念——C语言

目录 一、树&#xff08;tree&#xff09; 1.1树的概念及结构 1.2树的相关概念 1.3树的表示 1.4树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 二、二叉树的概念及结构 2.1二叉树的概念 2.2现实中真正的二叉树 2.3特殊的二叉树 2.4二叉树的性质…

使用 Feature Flags 实现数据库灰度迁移的监控与可观测性

作者&#xff1a;观测云与胡博 场景描述 很多企业会遇到数据库升级、或数据库迁移的情况&#xff0c;尤其是在自建数据库服务向云数据库服务、自建机房向云机房、旧数据库向新数据库迁移等场景。 然而&#xff0c;我们需要在整个移植过程中保证其稳定性、避免数据遗失、服务宕…

获取spring容器中的bean实例

在开发过程中&#xff0c;我们可能需要动态获取spring容器中的某个bean的实例&#xff0c;此时我们就会用到ApplicationContext spring应用上下文&#xff0c;这里做一下记录&#xff0c;网上很多类似的的工具类。 先写好工具类再测试一下是否好用 工具类&#xff1a; packag…

【pytest】生成测试报告

0. 脚本&#xff1a; fixture/test_fixtures_02.py # 功能函数 import pytestdef multiply(a, b):return a * bclass TestMultiply:# fixturesclassmethoddef setup_class(cls):print("setup_class>")classmethoddef teardown_class(cls):print("teardown_c…

最小二乘法

Least Square Method 1、相关的矩阵公式2、线性回归3、最小二乘法3.1、损失函数&#xff08;Loss Function&#xff09;3.2、多维空间的损失函数3.3、解析法求解3.4、梯度下降法求解 1、相关的矩阵公式 P r e c o n d i t i o n : ξ ∈ R n , A ∈ R n ∗ n i : σ A ξ σ ξ…

Linux驱动之INPUT设备驱动

目录 一、开发环境 二、编写按键input设备的注册与事件上报 2.1 修改设备树文件 1 添加 pinctrl 节点 2、添加 KEY 设备节点 3、检查 PIN 是否被其他外设使用 2.2 驱动程序编写 2.3 测试APP编写 2.4 运行测试 三、Linux内核自带按键input设备驱动 3.1 自带按键驱动程序源码简…

C#实现钉钉自定义机器人发送群消息帮助类

一、自定义机器人发送群消息使用场景 在企业中,针对一些关键指标内容(如每天的生产产量、每天的设备报警信息等信息),需要同时给多人分享,此时就可以将需要查看这些数据的人员都拉到一个群中,让群里的机器人将这些关键指标内容推送到群里即可【(目前已实现在钉钉群里创建…

Web 器学习笔记(基础)

Filter 过滤器 概念&#xff1a;表示过滤器&#xff0c;是 JavaWeb 三大组件&#xff08;Servlet、Filter、Listener&#xff09;之一 作用&#xff1a;顾名思义可以过滤资源的请求&#xff0c;并实现特殊的需求 Filter 接口及它核心的 doFilter() 方法&#xff08;执行前就是…

Excel 公式函数:学习基本示例

数据准备 对于本教程&#xff0c;我们将使用以下数据集。 家居用品预算 S / N项目数量价格小计价格适中吗&#xff1f;1芒果96002橘子312003番茄125004食用油565005汤力水133900 房屋建筑项目时间表 S/NITEM开始日期结束日期持续时间&#xff08;天&#xff09;1调查土地0…

000_差分信号

1.什么是差分信号 差分信号又叫做差模信号&#xff0c;使用差分信号传输时&#xff0c;需要2根信号线&#xff0c;这2根信号线的振幅相等&#xff0c;相位相反&#xff0c;通过2根信号线的电压差值来表示逻辑0和逻辑1。 差分信号表示逻辑值如下图&#xff1a; 2.差分信号的特…

IDEA2023.2.1中创建第一个Tomcat的web项目

首先&#xff0c;创建一个普通的java项目。点击【file】-【new】-【project】 创建一个TomcatDemo项目 创建如下图 添加web部门。点击【file】-【project structure】 选择【modules】-选中项目“TomcatDemo” 点击项目名上的加号【】&#xff0c;添加【web】模块 我们就会发现…

【Vue】快速入门案例与工作流程的讲解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue快速入门》。&#x1f…