19、HashTable(哈希)、位图的实现和布隆过滤器的介绍

news2025/7/17 19:48:19

一、了解哈希【散列表】

1、哈希的结构

在这里插入图片描述

  • 在STL中,HashTable是一个重要的底层数据结构, 无序关联容器包括unordered_set, unordered_map内部都是基于哈希表实现
    • 哈希表又称散列表,一种以「key-value」形式存储数据的数据结构。
    • 哈希函数:负责将任意大小的输入映射到固定大小的输出,即哈希值
      • 这个哈希值作用:是放在在数组中存储键值对的索引
    • 哈希冲突:由于哈希函数的映射不是一对一的,可能会出现两个不同的键映射到相同的索引,即冲突 。
  • 解决冲突的方法:
    • 链地址法
    • 开发寻址法
    • 双重哈希

2、哈希函数

  • 定义:将键(任意类型)映射为固定大小的整数(哈希值),决定数据在哈希桶中的存储位置。

可能出现的情况
冲突情况 :将两个或两个以上的不同key映射到同一地址

3、hash操作

  • 插入
  • 查找

4、哈希的负载因子【重点】

  • 负载因子 = 存储的元素个数/数组长度
  • 用来形容散列表的存储密度
  • 负载因子越小,冲突越小,负载因子越大,冲突越大
  • 描述冲突的程度

5、哈希冲突的解决方法

5.1、链地址法

在这里插入图片描述
方法一:拉链法 (链表法) 将具有相同的addr的key,可以用链表连接。但是负载因子要在合理范围内。

5.2、开发寻址法

方法二:开发寻址法

  • 将所有的数据直接存储在哈希数组中。如果冲突就采用某种算法来改变位置。
    • 算法有多种思路:
    • 比如 i+1,i+2,i+3等等 或者 i^1, i ^ 2 , i ^3等等。但是他们会出现hash聚集,也就是近似值的hash值很接近,那么位置也接近。聚集的话,就会在一片区域内,查找,这片区域数据太多了,时间复杂从O(1)变O(n)。所以可以使用双重哈希解决。 但是负载因子要在合理范围内
      在这里插入图片描述

6、分布式一致性哈希

6.1、了解

  • 一致性哈希通过环形哈希空间(Hash Ring) 和 虚拟节点(Virtual Nodes) 优化数据分布 。
  • 解决传统哈希表在节点数量变化时导致的全局数据迁移问题(例如模运算哈希的 hash(key) % N,当 N 变化时所有数据需重新分布)。
  • 一致性哈希,当节点增删时,仅影响环上相邻节点的数据,避免全局数据迁移。

6.2、哈希环

  • 将节点和数据通过哈希函数映射到一个环形空间(通常范围为 0 ~ 2^32 - 1)
  • 节点和数据的位置由哈希值决定。

每个数据项沿环顺时针查找最近的节点作为存储位置。

6.3、基本原理

在这里插入图片描述

  • 第一步:
    • 创建哈希环 。
    • 将节点和数据通过哈希函数映射到一个环形空间
  • 第二步:
    • 将数据 a 、b 、c 通过哈希函数确定环上的位置,放上去 。

在这里插入图片描述

  • 第三步
    • 确定a、b、c映射到哪一个节点上 。
    • 按顺时针顺序,将a、b、c映射到离它们最近的节点。

在这里插入图片描述

  • 第四步
    • 新增节点4,放在 1和2之间:仅需将环上相邻节点的部分数据迁移到新节点。

在这里插入图片描述

  • 第五步
    • 删除节点 4 ,把节点4上的数据 a 迁移到 节点2上
      • 移除节点:该节点的数据顺时针迁移到下一个相邻节点。
        在这里插入图片描述

6.4、虚拟节点

在这里插入图片描述

  • 问题:
    • 若物理节点较少,数据可能分布不均【哈希偏移】, 如上图。
    • 哈希偏移:
      • 在一致性哈希中,如果节点数量较少,可能会导致数据分布不均匀,某些节点负载过高,而其他节点负载较低。
  • 解决:
    • 每个物理节点映射多个虚拟节点
    • 数据最终存储在虚拟节点对应的物理节点

虚拟节点的优点

  • 数据分布均匀
    • 虚拟节点将物理节点的负载分散到哈希环的多个位置,避免数据倾斜。
  • 动态扩容
    • 增加物理节点时,只需为其分配虚拟节点,数据迁移量较少
  • 容错性
    • 删除物理节点时,其虚拟节点对应的数据会迁移到其他物理节点,系统仍然保持平衡。

7、哈希的代码

#include<cstddef>
#include<cstdio>
#include<cstdlib>
#include<sstream>
#include<vector>
#include<functional>
#include<utility>
#include<list>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
template<class Key,class Value,class Hash=hash<Key>>
class HashTable{
    class HashNode{
    public:
        Key key;
        Value value;
        //从key构造节点,Value使用默认构造
        explicit HashNode(const Key& key):key(key),value(){}
        //从key和value构造节点
        HashNode(const Key&key,const Value& value):key(key),value(value){}
        //比较运算符,只按key来比较
        bool operator==(const HashNode& other)const{
            return key==other.key;
        }
        bool operator!=(const HashNode& other)const{
            return key!=other.key;
        }
        bool operator<(const HashNode& other)const{
            return key<other.key;
        }
        bool operator>(const HashNode& other)const{
            return key>other.key;
        }
        bool operator==(const Key& key)const{
            return this->key==key;
        }
        //打印
        void print ()const{
            cout<<key<<" "<<value<<" ";
        }
    };

private:
    using Bucket = list<HashNode>;//定义桶的类型为存储键的链表
    vector<Bucket> buckets;       //存储所有桶的动态数组
    size_t tableSize;             //哈希表的大小,即桶的数量
    size_t numElements;           //哈希中的元素的数量
    float maxLoadFator = 0.75;    //默认最大负载因子
    Hash hashFunction;            //哈希函数对象
    //计算哈希值,并将其映射到桶的索引
    size_t hash(const Key& key)const{
      return hashFunction(key) % tableSize;
    }
    //当元素数量超过最大负载因子定义的容量时,增加桶的数量并重新分配所有键
    void rehash(size_t newSize){
        vector<Bucket> newBucket(newSize);//创建新的桶组
        for(Bucket& bucket:buckets){//遍历旧桶
            //遍历桶中的每一个键
            for(HashNode& hashNode:bucket){
                //因为这里是新的newSize计算,所以不能用hash(key)来求
                size_t newIndex=hashFunction(hashNode.key)%newSize;
                newBucket[newIndex].push_back(hashNode);//将键重新放入桶中
            }
        }
        buckets = move(newBucket);//使用移动语义更新桶数组
        tableSize = newSize;
    }
public:
    //构造函数初始化哈希表
    HashTable(size_t size = 10,const Hash& hashFunc = Hash())
    :buckets(size),hashFunction(hashFunc),tableSize(size),numElements(0){

    }
    //插入键到哈希表中
    void insert(const Key&key,const Value& value){
        if((numElements+1)>maxLoadFator*tableSize){//检查是否需要重哈希
            //处理clear后再次插入元素时,tableSize = 0 的情况
            if(tableSize==0)tableSize = 1;
            rehash(tableSize*2);// 重哈希,桶数量翻倍
        }
        size_t index = hash(key);    //计算键的索引
        list<HashNode>& bucket = buckets[index];//获取对应的桶
        //如果不在桶中,则添加到桶中
        if(find(bucket.begin(),bucket.end(),key)==bucket.end()){
            bucket.push_back(HashNode(key,value));
            ++numElements;
        }
    }
    void insertKey(const Key&key){
        insert(key,Value{});
    }
    //从哈希表中移除键
    void erase(const Key& key){
        size_t index = hash(key);//计算键的索引
        auto & bucket = buckets[index];//获取对应的桶
        auto it = find(bucket.begin(),bucket.end(),key);//查找键
        if(it!=bucket.end()){
            bucket.erase(it);//删除键
            numElements--;//减少元素的数量

        }
    }
    //查找键是否在哈希表中
    Value* findKey(const Key& key){
        
        size_t index = hash(key);//计算键的索引
        auto & bucket = buckets[index];//获取对应的桶
        auto it = find(bucket.begin(),bucket.end(),key);//查找键
        if(it!=bucket.end()){
            return &it->value;
        }
        return nullptr;
    }
    //获取哈希表中的元素数量
    size_t size()const {return numElements;}
    //打印哈希表中的所有元素
    void print()const{
        for(size_t i = 0;i<buckets.size();++i){
            for(const HashNode& element:buckets[i]){
                element.print();
            }
        }
        cout<<endl;
    }
    void clear(){
        this->buckets.clear();
        this->numElements=0;
        this->tableSize = 0;
    }
};
int main(int argc, char const *argv[])
{
    //创建一个哈希表实例
    HashTable<int, int> hashTable;
    int n;
    cin>>n;
    getchar();
    string line;
    for(int i = 0;i<n;i++){
        getline(cin,line);
        istringstream iss(line);
        string command;
        iss>>command;
        int key;
        int value;
        if(command=="insert"){
            iss>>key>>value;
            hashTable.insert(key,value);
        }
        if(command == "erase"){
            if(hashTable.size()==0){
                continue;
            }
            iss>>key;
            hashTable.erase(key);
        }
        if(command=="find"){
            if(hashTable.size()==0){
                cout<<"not exist"<<endl;
                continue;
            }
            iss>>key;
            int* res = hashTable.findKey(key);
            if(res!=nullptr){
                cout<<*res<<endl;
            }else{
                cout<<"not exist"<<endl;
            }
        }
        if(command=="print"){
            if(hashTable.size()==0){
                cout<<"empty"<<endl;
            }else{
                hashTable.print();
            }
        }
        if(command=="size"){
            cout<<hashTable.size()<<endl;
        }
        if(command=="clear"){
            hashTable.clear();
        }
    }
    return 0;
}

二、位图

推荐文章

1、问题一

腾讯问题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在40亿个数中。【哈希表:每个无符号整数占4字节。40亿占的是16G

  • 1 B是 8 bit 。
  • 1GB = 1024MB。
  • 1MB = 1024 KB 。
  • 1KB = 1024B 。
  • 40 亿个 无符号整数是 每个数 4字节。 就有 160 亿字节
  • 160亿字节/1024/1024/1024 ≈ 14.9 GB

2、介绍

  • 创建一段数组空间,用比特 1 和 0 来表示存在和不存在(1是存在,0是不能存在)。
    在这里插入图片描述

3、实现位图的计算

void set(size_t x) {
    size_t index = x / 32;  // 定位到哪个 int
    size_t pos = x % 32;    // 定位到 int 的哪一位
    bits[index] |= (1 << pos);//更新pos位置的 1
}
  • 例如 x = 37:
    • index = 37 / 32 = 1(第 2 个 int)
      • 假设 bit[index]= 0000 0100
    • pos = 37 % 32 = 5(第 5 位)
      • 得pos = 0001 0000
    • bits[1] |= (1 << 5) 将第 37 位设为 1
    • 0000 0100 | 0001 000 = 0001 0100 。保持了原来的位的1,并更新了现在要改的位为 1

3、操作

3.1、了解运算符

在这里插入图片描述

3.2、位图操作

在这里插入图片描述

4、位图的优缺点

  • 查找很快
  • 但是只能用于整型

5、代码实现

#include <cstddef>
#include<vector>
#include<iostream>
using namespace std;
namespace bit{
    class bitset{
    public:
        explicit bitset(size_t N){
            bits.resize(N/32+1,0);
            //如果是32的倍数,会多分配一个int
        }
        //设置位图
        void set(size_t x){
            size_t index = x/32;//算出x映射的位在第几个整型
            size_t pos = x%32;//算出x在这个整型的第几个位置
            bits[index]|= (1<<pos);//保留原来的1 ,设置现在需要 位 的1
            ++num;
        }
        第pos个位置设置为0
        void reset(size_t x){
            size_t index = x/32;//算出x映射的位在第几个整型
            size_t pos = x%32;//算出x在这个整型的第几个位置
            bits[index] &= ~(1<<pos);//第pos个位置设置为0
        }
        //判断x在不在

        bool test(size_t x){
            size_t index = x/32;
            size_t pos = x%32;
            return bits[index]&(1<<pos);
        }
    private:
        vector<int> bits;
        size_t num;//个数
    };
};
void test_bitset(){
    using namespace bit;
    bitset bs(100);
    bs.set(99);
    bs.set(98);
    bs.set(97);
    for(size_t i =0;i<100;++i){
        cout<<"[%d]:%d\n"<<i<<static_cast<int>(bs.test(i))<<endl;
    }
}
int main(int argc, char const *argv[])
{
    test_bitset();
    return 0;
}

三、布隆过滤器(Bloom Filter)

1、了解

  • 用于:只想知道key存在不存在,不想知道内容。(适合去重场景)
    • 支持任意数据类型
  • 布隆过滤器将元素进行多个Hash算法计算,都存入位图中,查询时使用同样的Hash算法计算,对应当所有值都为true时,表示存在。这样就可以极大的提升位图的存储效率。

布隆过滤器也有致命的缺陷,即存在误判率,也称为假阳性率
当数据量不断增大,位图中非true位置越来越少,很可能会出现未插入的数据,查询结果为true

2、构成

  • 哈希+位图

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

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

相关文章

mysql中int(1) 和 int(10) 有什么区别?

困惑 最近遇到个问题&#xff0c;有个表的要加个user_id字段&#xff0c;user_id字段可能很大&#xff0c;于是我提mysql工单​​alter table xxx ADD user_id int(1)​​。领导看到我的sql工单&#xff0c;于是说&#xff1a;这int(1)怕是不够用吧&#xff0c;接下来是一通解…

FreeRTOS如何实现100%的硬实时性?

实时系统在嵌入式应用中至关重要&#xff0c;其核心在于确保任务在指定时间内完成。根据截止时间满足的严格程度&#xff0c;实时系统分为硬实时和软实时。硬实时系统要求任务100%满足截止时间&#xff0c;否则可能导致灾难性后果&#xff0c;例如汽车安全系统或医疗设备。软实…

element-ui日期时间选择器禁止输入日期

需求解释&#xff1a;时间日期选择器&#xff0c;下方日期有禁止选择范围&#xff0c;所以上面的日期输入框要求禁止输入&#xff0c;但时间输入框可以输入&#xff0c;也就是下图效果&#xff0c;其中日历中的禁止选择可以通过【picker-options】这个属性实现&#xff0c;此属…

[论文阅读]Deeply-Supervised Nets

摘要 我们提出的深度监督网络&#xff08;DSN&#xff09;方法在最小化分类误差的同时&#xff0c;使隐藏层的学习过程更加直接和透明。我们尝试通过研究深度网络中的新公式来提升分类性能。我们关注卷积神经网络&#xff08;CNN&#xff09;架构中的三个方面&#xff1a;&…

多模态大语言模型arxiv论文略读(六十二)

MileBench: Benchmarking MLLMs in Long Context ➡️ 论文标题&#xff1a;MileBench: Benchmarking MLLMs in Long Context ➡️ 论文作者&#xff1a;Dingjie Song, Shunian Chen, Guiming Hardy Chen, Fei Yu, Xiang Wan, Benyou Wang ➡️ 研究机构: The Chinese Univers…

现代框架对SEO的深度影响

第8章&#xff1a;现代框架对SEO的深度影响 1. 引言 Next 和 Nuxt 是两个 &#x1f525;热度和使用度都最高 的现代 Web 开发框架&#xff0c;它们分别基于 ⚛️React 和 &#x1f596;Vue 构建&#xff0c;也代表了这两个生态的 &#x1f310;全栈框架。 Next 是由 Vercel 公司…

密码学--RSA

一、实验目的 1.随机生成明文和加密密钥 2.利用C语言实现素数选择&#xff08;素性判断&#xff09;的算法 3.利用C语言实现快速模幂运算的算法&#xff08;模重复平方法&#xff09; 4.利用孙子定理实现解密程序 5.利用C语言实现RSA算法 6.利用RSA算法进行数据加/解密 …

如何选择自己喜欢的cms

选择内容管理系统cms what is cms1.whatcms.org2.IsItWP.com4.Wappalyzer5.https://builtwith.com/6.https://w3techs.com/7. https://www.netcraft.com/8.onewebtool.com如何在不使用 CMS 检测器的情况下手动检测 CMS 结论 在开始构建自己的数字足迹之前&#xff0c;大多数人会…

BUUCTF——杂项渗透之赛博朋克

下载附件&#xff0c;是一个txt。打开查看&#xff0c;数据如下&#xff1a; 感觉这个像是用十六进制编辑器打开后的图片数据。为了验证此想法&#xff0c;我用010editor打开&#xff0c;发现文件头的确是png图片的文件头。 把txt文件后缀改成png格式&#xff0c;再双击打开&am…

React 中集成 Ant Design 组件库:提升开发效率与用户体验

React 中集成 Ant Design 组件库:提升开发效率与用户体验 一、为什么选择 Ant Design 组件库?二、基础引入方式三、按需引入(优化性能)四、Ant Design Charts无缝接入图标前面提到了利用Redux提供全局维护,但如果在开发时再自己手动封装组件,不仅效率不高,可能开发的组件…

编译原理实验 之 语法分析程序自动生成工具Yacc实验

文章目录 实验环境准备复现实验例子分析总的文件架构实验任务 什么是Yacc Yacc(Yet Another Compiler Compiler)是一个语法分析程序自动生成工具&#xff0c;Yacc实验通常是在编译原理相关课程中进行的实践项目&#xff0c;旨在让学生深入理解编译器的语法分析阶段以及掌握Yac…

从“山谷论坛”看AI七剑下天山

始于2023年的美国山谷论坛(Hill and Valley Forum)峰会,以“国会山与硅谷”命名,寓意连接科技界与国家安全战略。以人工智能为代表的高科技,在逆全球化时代已成为大国的致胜高点。 论坛创办者Jacob Helberg,现在是华府的副国务卿,具体负责经济、环境和能源事务。早先曾任…

C——数组和函数实践:扫雷

此篇博客介绍用C语言写一个扫雷小游戏&#xff0c;所需要用到的知识有&#xff1a;函数、数组、选择结构、循环结构语句等。 所使用的编译器为:VS2022。 一、扫雷游戏是什么样的&#xff0c;如何玩扫雷游戏&#xff1f; 如图&#xff0c;是一个标准的扫雷游戏初始阶段。由此…

sui在windows虚拟化子系统Ubuntu和纯windows下的安装和使用

一、sui在windows虚拟化子系统Ubuntu下的安装使用&#xff08;WindowsWsl2Ubuntu24.04&#xff09; 前言&#xff1a;解释一下WSL、Ubuntu的关系 WSL&#xff08;Windows Subsystem for Linux&#xff09;是微软推出的一项功能&#xff0c;允许用户在 Windows 系统中原生运行…

智能合约在去中心化金融(DeFi)中的核心地位与挑战

近年来&#xff0c;区块链技术凭借其去中心化、不可篡改等特性&#xff0c;在全球范围内掀起了技术革新浪潮。去中心化金融&#xff08;DeFi&#xff09;作为区块链技术在金融领域的重要应用&#xff0c;自 2018 年以来呈现出爆发式增长态势。据 DeFi Pulse 数据显示&#xff0…

有关SOA和SpringCloud的区别

目录 1. 定义 2. 架构风格 3. 技术栈 4. 服务交互 5. 适用场景 前言 面向服务架构&#xff08;SOA&#xff09;是一种软件设计风格&#xff0c;它将应用程序的功能划分为一系列松散耦合的服务。这些服务可以通过标准的通信协议进行交互&#xff0c;通常是HTTP或其他消息传…

学习搭子,秘塔AI搜索

什么是秘塔AI搜索 《秘塔AI搜索》的网址&#xff1a;https://metaso.cn/ 功能&#xff1a;AI搜索和知识学习&#xff0c;其中学习部分是亮点&#xff0c;也是主要推荐理由。对应的入口&#xff1a;https://metaso.cn/study 推荐理由 界面细节做工精良《今天学点啥》板块的知…

IBM BAW(原BPM升级版)使用教程第六讲

续前篇&#xff01; 一、事件&#xff1a;Undercover Agent 在 IBM Business Automation Workflow (BAW) 中&#xff0c;Undercover Agent (UCA) 是一个非常独特和强大的概念&#xff0c;旨在实现跨流程或系统的事件处理和触发机制。Undercover Agent 主要用于 事件驱动的流程…

高并发PHP部署演进:从虚拟机到K8S的DevOps实践优化

一、虚拟机环境下的部署演进 1. 低并发场景&#xff08;QPS<10&#xff09;的简单模式 # 典型部署脚本示例 ssh userproduction "cd /var/www && git pull origin master" 技术痛点&#xff1a; 文件替换期间导致Nginx返回502错误&#xff08;统计显示…

VBA高级应用30例应用4:利用屏蔽事件来阻止自动运行事件

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…