首页 > 编程语言 >c++哈希表hash_table的深度学习(hash_map,un和hash_set的底层实现)

c++哈希表hash_table的深度学习(hash_map,un和hash_set的底层实现)

时间:2024-06-12 22:29:27浏览次数:14  
标签:map set hash 哈希 链表 key 节点 size

什么是哈希表?

哈希表(Hash Table)是一种数据结构,它使用哈希函数将键(key)映射到桶(bucket)或槽(slot)中,可以直接通过相应的键值直接对数据进行访问,高效的插入,删除,查找


 哈希表的组成部分和特性

  1. 哈希函数:哈希函数接受一个键作为输入,并返回一个索引值(通常是一个整数),该索引值用于确定键在哈希表中的位置。一个好的哈希函数应该能够将键均匀地分布到哈希表的各个桶中,以减少冲突(collision)的可能性。

  2. 桶或槽:哈希表的存储空间被划分为若干个桶或槽,每个桶可以存储一个或多个键值对。桶的数量通常小于哈希表中可能存在的键的数量,因此多个键可能会映射到同一个桶中,这称为冲突。

  3. 冲突解决:当两个或多个键映射到同一个桶时(哈希冲突),需要使用冲突解决策略。常见的冲突解决策略包括:

  • 链地址法(链表法):在发生冲突的桶中维护一个链表,将具有相同哈希值的键值对链接在一起。
  • 开放地址法:当发生冲突时,通过某种探测方法(如线性探测、二次探测、双散列等)在哈希表中查找下一个可用的空槽。
  • 再哈希法:当发生冲突时,使用另一个哈希函数重新计算键的哈希值,直到找到一个空槽为止。

      4. 动态调整:为了保持哈希表的性能,当哈希表中的元素数量过多或过少时,可能需要动态地调整哈希表的大小。这通常涉及重新计算所有键的哈希值,并将它们重新分配到新的桶中。

      5. 性能:哈希表通常具有接近O(1)的平均时间复杂度,用于插入、删除和查找操作。然而,在最坏情况下(例如,当所有的键都映射到同一个桶中时),哈希表的时间复杂度可能会退化为O(n)。


声明:以下的都是我看侯捷的<<深度剖析stl>>总结的,可能最新的stl实现不是这样的,但是也是对的新的对旧的的一些完善,但是大体是正确的 

下面讲解哈希表,哈希碰撞是由链表来解决

teplate<class value>
struct _hashtable_node               //哈希表bukets的链表节点
{
    _hashtable_node *next;
    value  val;
}

template<class value,class key,class hashfuc,class extractkey,class equalkey>
class hashtable
{
   
private:
   //三个仿函数
   hashfcn hash;         //哈希分散的仿函数  插入元素时(刚创建并无key)根据数据类型(基类和对象等)返回key
   equalkey equals;      //比较key大小,以便实现对key的排序
   extractkey get_key;   //得到一个元素的key(这个元素已存在)


   _hashtable_node<value> node;     //哈希表bukets的链表节点
   vector<node*,alloc> bukets;      //哈希表bukets
   size_t     num_size;             //哈希表所含的元素个数
}

关于hashtable的学习我们可以从六个方面入手

容器,迭代器,算法,仿函数,适配器,分配器

我先对上述代码简要介绍,value可以是键值对或者就只是值,key则是键,hashfuc通过值返回一个key也就是编号,extractkey则是当你存储的是键值对时如何得到元素得key比如pair<key,value>返回first(标准库基本上没了),equalkey比较两个key是否相同,buckets(篮子)存放链表的篮子,num_size元素个数

容器:

从上面我们不难看出真正存储数据的是node(链表节点)可以看到有一个vetor<node*,alloc>,其中存储的就是链表,为什么用vector因为能扩充,元素过多时,或者单个链表过长影响性能我们能通过添加链表来添加元素


仿函数:

从上图可以看到每个bucket都有一个编号每个编号对应着一条链表

如何获得key(键)的呢?
template<class key> struct hash(){}
//偏特化
template<> struct hash(int)     //基础类型直接返回 int,unsigned int,char,const int
{
     size_t operater(int x){return x};
}  


template<> struct hash(char*)       //字符串类型 通过一个公式计算每个字符尽可能得到不相同的数据
{
     size_t operator(char* s)
     {
        unsiged long h;
        /*****begin******/      遍历字符串得到一个不同字符串能得到不同值
         
        /******end******/
        return (size_t)h;
     }
}

//当值是一个复杂类型,对象或者结构体你就得自己提供获取键的仿函数 

当我们拿到折射值还不行因为折射值可能很大,我们的编号有限所以我们还要将上面的到的折射值%buckets.size()

hash()%bucket.size()  //得到真正的键值  将这些元素散落在这个vector的链表,即使折射值不同仍然会出现相同的key我们则用链表连起来
为什么要判断键是否相同

对于具有相同哈希值但不相等的键,哈希表需要在插入、查找或删除操作时进行额外的等价检查(equality check)。这是因为在哈希表中,仅仅依赖哈希值来定位键是不够的,因为可能存在多个键具有相同的哈希值。因此,当哈希表在桶中找到了具有相同哈希值的键时,还需要通过等价检查来确定这些键是否真的相等。如果不相等,则需要继续搜索或采取其他策略来处理这个冲突 

其余仿函数大家可以自己去深入了解

迭代器

因为内容太多我就不讲述太多

我们知道迭代器需要能够遍历到整个哈希表

我们已经知道它的容器是vector<node*>存放的链表的节点所以有以下东西

template<class value,class key,class hashfuc,class extractkey,class equalkey>
class _hashtable_iterator 
{
    node * p;            //当前链表节点
    hash_table *ht       //当前篮子的指针
    _hashtable_iterator& operater*(){return this->p->val;} 对*,->等运算符重载
}

需要注意的是我们++或者--都要边界判定,为什么需要篮子指针,是因为每次++链表到尾了需要到下一个篮子, 每次--到头了需要到前一个篮子,以及+5,6,等等,

进行迭代器的减法时我们需要判断中间有几个元素也要有相应的算法逻辑


算法

插入算法,删除算法,查找算法,都是基于迭代器和容器上实现,算法是桥梁,这里我们给出插入的简单介绍

  void HashTable::insert(KeyType key, ValueType value) {  
    // 计算哈希值  
    int hashIndex = hash(key) % capacity; // 使用取模运算来确定桶的编号  
  
    // 遍历链表以查找是否存在具有相同键的节点  
    node* currentNode = buckets[hashIndex];  
    while (currentNode != nullptr) {  
        if (currentNode->key == key) {  
            // 如果键已经存在,可以选择更新值或执行其他操作(如抛出异常)  
            currentNode->value = value;  
            return; // 退出函数,因为键已经存在  
        }  
        currentNode = currentNode->next; // 移动到链表中的下一个节点  
    }  
  
    // 如果键不存在于链表中,则创建一个新节点并将其添加到链表的开头  
    node* newNode = new node(key, value); // 创建新节点  
    newNode->next = buckets[hashIndex]; // 将新节点设置为当前桶的头节点  
    buckets[hashIndex] = newNode; // 更新桶的头节点指针  
  
    // 增加哈希表中元素的数量  
    size++;  
  
    // 如果哈希表变得过满,考虑重新哈希(重新分配和重新组织元素)  
    if (size > capacity * LOAD_FACTOR) { // LOAD_FACTOR是一个预定义的阈值,如0.75  
        rehash(); // 重新哈希函数,会重新分配buckets数组并重新组织元素  
    }  
}

分配器:

分配内存控制整个hashtable内存等资源的分配

适配器:

hash_map,hash_set 以及c++11的unordered_set,unordered_map

标签:map,set,hash,哈希,链表,key,节点,size
From: https://blog.csdn.net/weixin_72492465/article/details/139638171

相关文章

  • 【异常】使用Dbeaver链接TDengine提示SQL错误[9684]:ERROR (2318): Connection reset
    一、异常内容使用Dbeaver链接TDengine提示SQL错误[9684]:ERROR(2318):Connectionreset,报错截图如下二、报错说明“ERROR(2318):Connectionreset”表示客户端与服务器之间的连接被意外地重置。这通常发生在一个应用程序试图读取或写入数据,但是连接的另一端已经关......
  • golang sync.Map 与使用普通的 map 的区别
     使用sync.Map与普通的Gomap主要有以下几点区别:1.并发安全性普通map:在没有外部同步的情况下,不是并发安全的。在多goroutine访问时,如果没有适当的锁或其他同步机制保护,可能会导致数据竞争和未定义行为。sync.Map:是并发安全的。它内部实现了必要的同步机制,允许多......
  • 创建entity模板,equals hashcode 方法模板
    创建entity模板,equalshashcode方法模板如下为FLINK官网实体类demoequalshashcode方法模板可以参考////Sourcecoderecreatedfroma.classfilebyIntelliJIDEA//(poweredbyFernFlowerdecompiler)//packageorg.apache.flink.walkthrough.common.entity;im......
  • 【K8s】专题五(1):Kubernetes 配置之 ConfigMap
    以下内容均来自个人笔记并重新梳理,如有错误欢迎指正!如果对您有帮助,烦请点赞、关注、转发!欢迎扫码关注个人公众号!目录一、基本介绍二、主要特性三、资源清单(示例)四、常用操作一、基本介绍在Kubernetes中,ConfigMap是一种用于存储非敏感信息的资源对象,提供了向Pod......
  • mybatis的mapper中的sql涉及嵌套且外部引用导致的问题:XML fragments parsed from prev
    假设xxx.xml中有类似下方的sql嵌套:<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.xx......
  • reason: '[<__NSDictionaryI setValue:forUndefinedKey this class is not key value
    崩溃之前的代码NSMutableDictionary*item=[NSMutableDictionarydictionaryWithDictionary:guaranteedModeDict];NSMutableDictionary*modelDict=item[MODEL];[modelDictsetValue:[[NSStringstringWithFormat:@"%f",minimoney]showShortPriceStri......
  • 【CMake系列】03-cmake 注释、常用指令 message、set、file、for_each、流程控制if
    本文给出了cmake中的一些常用的指令,可以快速了解,为后面的内容深入打点基础。本专栏的详细实践代码全部放在github上,欢迎star!!!如有问题,欢迎留言、或加群【392784757】交流注释#行注释#[[多行注释]]message(""#[[这里也可以注释]]"")message在学习时......
  • mybatis-plus加载多个module的mapper踩坑记录
    背景 有一个多模块的项目,每个模块中都有自己的mapper.xml文件。但是在执行一次SQL查询中,mybatis却报出了下面的异常 排查过程第一步,先检查mapper扫描是否正确 先找到这个方法的位置 可以看到包名是com.pinming.security.responsibility.mapper 检查SpringBoot......
  • YOLO 模型的评估指标——IOU、Precision、Recall、F1-score、AP、mAP、
    一、置信度是什么?置信度用于评估模型对检测结果的信心程度下图中,绿色框A表示GroundTruth,也称GT,GT就是正确的标注(人工)二、IOU与TP、FP、FNiou:表示预测的边界框(或分割区域)与真实边界框(或分割区域)之间的交集与并集之间的比值。阈值:根据实际情况可调节IOU=0.5如果预......
  • 前端菜鸡流水账日记 -- setTimeout定时器
    中午好哇,一上午的时间过的真快,这都快要吃午饭啦,突击询问有想好吃什么吗???当然,这不是重点,重点是我今天要说的这个定时器,以及和他搭配的取消定时器,话不多说,开始我们的新内容setTimeout都不陌生就是定时器,他可以这样用setTimeout(()=>{dealData.forEach(e=>{if(aw......