当前位置: 首页 > news >正文

哈希表的底层实现(2)---C++版

目录

链地址法Separate Chaining——哈希桶的模拟实现

超大重点分析:

两种方法对比


由于在上次的哈希表的底层实现(1)---C++版已经详细的阐述了相关的结构和原理,哈希表的实现方法主要分为链地址法和开放定址法。开放定址法上次已经实现过了,这次我们实现一下链地址法

链地址法Separate Chaining——哈希桶的模拟实现

哈希桶的结构和链表是完全一样的,我们这边选择在每个vector里面装入单链表就可以了,比较简单嘛,所以每个结点和成员都是指针。

#include<iostream>
#include<vector>
using namespace std;

template<class K>
struct Hashfunc//仿函数
{
    int operator()(const K& key)
    {
        return (int)key;
    }
};

struct Hashfunc<string>//结构体名字必须一致才省略模板
{

    int operator()(const string& key)
    {
        int hashi = 0;
        for (auto e : key)
        {
            hashi = hashi * 31;
            hashi = hashi + e;
        }
        return hashi;
    }
};
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 hash = Hashfunc<K>>
class Hashtable
{
    typedef Hashnode<K, V> node;
public:
    Hashtable()
    {
        _tables.resize(10, nullptr);//先初始化存有10个空指针的数组
    }
    ~Hashtable()//需要自己写析构函数的
    {
        for (int i = 0; i < _tables.size(); i++)
        {
            node* cur = _tables[i];
            while (cur)
            {
                node* next = cur->_next;
                delete cur;
                cur = next;
            }
            _tables[i] = nullptr;

        }
    }

    bool Insert(const pair<K, V>& kv)
    {
        hash ha;
        // 负载因子==1扩容
        if (n == _tables[size])
        {
            /*Hashtable<K, V> newHT;
            newHT._tables.resize(_tables.size() * 2);
            for (size_t i = 0; i < _tables.size(); i++)
            {
                node* cur = _tables[i];
                while(cur)
                {
                    newHT.Insert(cur->_kv);//用以前复用的逻辑有点浪费空间了
                    cur = cur->_next;
                }
            }*/

            vector<node*>newht.resize(_tables.size() * 2, nullptr);
            for (int i = 0; i < _tables.size(); i++)
            {
                node* cur = _tables[i];
                while (cur)
                {
                    node* next = cur->_next;
                    // 旧表中节点,挪动新表重新映射的位置
                    size_t hashi = ha(cur->_kv.first) % newht.size();
                    // 头插到新表,当然使用尾插也可以
                    cur->_next = newht[hashi];//头插的逻辑
                    newht[hashi] = cur;
                    cur = next;
                }
                _tables[i] = nullptr;//置空了头结点后面的结点也就找不到了,其实感觉不置空也没什么问题
            }
            _tables.swap(newht);//再交换一下
        }
        size_t hashi = ha(kv.first) % _tables.size();
        //头插
        node* newnode = new(kv);//通过kv构造一个新结点,需要合适的构造函数
        newnode->_next = _tables[hashi];
        _tables[hashi] = newnode;
        n++;
    }

    Node* Find(const K& key)
    {
        hash he;
        size_t hashi = he(K) % _tables.size();
        node* cur = _table[hashi];
        while (cur)
        {
            if (cur->_kv.first == key)
            {
                return cur;
            }
            cur = cur->_next;
        }
        return nullptr;
    }

    bool Erase(const K& key)
    {
        hash ha;
        if (Find(key) == nullptr)
        {
            return false;
        }
        else
        {
            size_t hashi =ha(K) % _tables.size();
            node* cur = _table[hashi];
            node* prev = nullptr;
            while (cur)
            {
                if (cur->_kv.first == key)
                {
                    if (prev == nullptr)
                    {
                        _tables[hashi] = cur->_next;
                    }
                    else
                    {
                        prev->_next = cur->_next;
                    }
                    delete cur;
                    cur = nullptr;
                    --n;
                    return true;
                }
                prev = cur;
                cur = cur->_next
            }
        }
    }
private:
    vector<node*> _tables;// 使用指针数组
    size_t n = 0;//负载因子
};

超大重点分析:

为什么需要自己写析构函数呢?因为如果让系统调用默认构造的话,成员中负载因子属于内置类型编译器不处理,然后vector属于自定义类型,编译器会调用vector的默认构造,这样vector里面的单链表就没有办法析构了,就会照成内存泄漏。

为什么扩容不复用insert了呢,先说一下为什么会需要扩容,随着数据的不断大量的插入单链表,肯定在某种情况下会使得某个链表过于长,这样在查找哈希表的时候会使得时间复杂度过于大了,所以引入负载因子n进行控制,当n == size时就扩容,为什么在扩容时不建议复用呢,因为这样不断的创造新的结点而放着旧结点不直接拿来用的话会比较浪费空间,创造一个结点的消耗还是比较大的。

为什么这边需要写构造函数,因为insert传的是pair,那根据这个pair构造新结点的话需要自己写一个构造,默认构造用不上。

为什么这边哈希桶状的结构是单链表而不是直接使用list或者C++11新加入的forward_list呢,首先没说不可以,但是用单链表不是更简单吗,forward_list尽量少用。

vector<list<pair<K, V>>> _tables;  // 指针数组

像上面这种就是使用list的写法,但是到时候封装的iterator实现起来会比较困难

struct Bucket//联合体
{
       list<pair<K, V>> _lt;
       map<K, V> _m;
       size_t _bucketsize;   // >8 map  <=8 list

};
vector<Bucket> _tables;

但是呢就算是有扩容操作还是会有人故意使用一些很极端的数据使得即使多次扩容还是显得某个链表的插入数据很多,导致每个链表插入数据的数目不平衡。所以为了解决这种情况,有些人会选择当负载因子过大时转而使用搜索树map来代替list实现存储,如上:

最后一个问题,为什么使用头插呢,因为其实无论是头插还是尾插在Find还是erase都没什么显著差别的,但是在扩容时头插会比尾插更有优势,因为每个结点刚开始初始化时的_next结点都是空

这样当头插到开头时每次指向的都是空,这样就不会把多余的结点带出来了,如果是尾插就需要最后再手动将_next置空。

两种方法对比

应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a ,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

相关文章:

哈希表的底层实现(2)---C++版

目录 链地址法Separate Chaining——哈希桶的模拟实现 超大重点分析&#xff1a; 两种方法对比 由于在上次的哈希表的底层实现(1)---C版已经详细的阐述了相关的结构和原理&#xff0c;哈希表的实现方法主要分为链地址法和开放定址法。开放定址法上次已经实现过了&#xff0c…...

算法知识点————【LRU算法】

思想&#xff1a;淘汰最久没有使用的 应用场景&#xff1a;手机清后台的时候先清最久没有使用的应用 设计一种数据结构&#xff1a;接收一个 capacity 参数作为缓存的最大容量&#xff0c;然后实现两个 API&#xff0c;一个是 put(key, val) 方法存入键值对&#xff0c;另一个是…...

记一次MySQL视图查询优化的经验

背景&#xff1a;库房系统项目迁移&#xff0c;两个版本的结构发生了很大变化&#xff0c;新版本的库存系统在开发阶段由于数据量小&#xff0c;根据看不出查询的性能问题&#xff0c;还沾沾自喜的想新版本多好。但是在做同步之后&#xff08;规则变更&#xff0c;需要插入很多…...

Cloudways搭建WordPress外贸独立站完整教程(1)

验证邮件发送完成后&#xff0c;就等待Cloudways的回复邮件&#xff0c;一般24小时之内就会收到激活的邮件。 Cloudways账号升级 激活成功后还需要账户升级&#xff0c;Cloudways提供了为期3天的免费试用体验。如果在试用期结束之前未绑定信用卡以升级账户&#xff0c;试用期…...

Delphi5数据控制组件——查询

文章目录 效果图参考查询Free方法Close方法总结通俗理解 完整代码 效果图 参考 本文是在上一篇的基础上&#xff0c;将查询页面重新写一次。 查询 {点击查询} procedure TForm2.Button1Click(Sender: TObject); vartj,tj1,tj2,tj3,tj4,tj5,tj6,tj7:string; begin//按照工号查…...

git pull之后发现项目错误,如何回到之前的版本方法

目录 首先我们打开小程序的cmd的黑窗口&#xff0c;git reflog查看之前的版本 之后再git reset --hard main{1} 我这个就已经返回了之前的6daaa2e的版本了 首先我们打开小程序的cmd的黑窗口&#xff0c;git reflog查看之前的版本 之后再git reset --hard main{1} 我这个就已…...

防跌倒识别摄像机

防跌倒识别摄像机 是一种结合了人工智能技术和监控摄像技术的先进设备&#xff0c;旨在通过实时监测和分析监控画面中的行为动作&#xff0c;及时发现并预防跌倒事件的发生。这种摄像机在医疗、养老院、家庭等场所有着广泛的应用前景。 防跌倒识别摄像机在医疗领域具有重要意义…...

MyQql性能诊断与实践

获取更多免费资料&#xff0c;见下图...

有序序列判断

描述 输入一个整数序列&#xff0c;判断是否是有序序列&#xff0c;有序&#xff0c;指序列中的整数从小到大排序或者从大到小排序(相同元素也视为有序)。 数据范围&#xff1a;3 < n< 50 序列中的值都满足 1< val < 100 输入描述&#xff1a; 第一行输入一个整数N…...

【Kubernetes知识点问答题】健康检查

目录 1. Kubernetes 对集群 Pod 和容器健康状态如何进行监控和检测的。 2. 解释 LivenessProbes 探针的作用及其适用场景。 3. 解释 ReadinessProbe 探针的作用及其适用场景。 4. 解释 StartupProbe 探针的作用及其适用场景。 5. 说明 K8s 中 Pod 级别的 Graceful Shutdown…...

【Prometheus】PromQL数据类型以及常用的计算函数用法详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

STM32高级定时器生成互补PWM的原理与代码实现

文章目录 前言一 CubeMx配置1.1 TIM1 Mode and Configuration1.2 Paramter Settings 二 程序代码三 仿真分析总结 前言 互补 PWM&#xff08;Complementary PWM&#xff09;是指一对逻辑状态互为反相的 PWM&#xff08;脉冲宽度调制&#xff09;信号。这种信号配置常见于电机控…...

双指针题总结

双指针题总结 hot100移动零盛水最多的容器三数之和接雨水最小覆盖子串 hot100 移动零 题目链接&#xff1a; 283.移动零 代码&#xff1a; class Solution {public void moveZeroes(int[] nums) {int slow 0;for (int fast 0; fast < nums.length; fast ){if (nums[fas…...

[数据集][目标检测]人脸口罩佩戴目标检测数据集VOC+YOLO格式8068张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8068 标注数量(xml文件个数)&#xff1a;8068 标注数量(txt文件个数)&#xff1a;8068 标注…...

JVM3-双亲委派机制

目录 概述 作用 如何指定加载类的类加载器&#xff1f; 面试题 打破双亲委派机制 自定义类加载器 线程上下文类加载器 Osgi框架的类加载器 概述 由于Java虚拟机中有多个类加载器&#xff0c;双亲委派机制的核心是解决一个类到底由谁加载的问题 双亲委派机制&#xff…...

经典文献阅读之--DEviLOG(使用合成数据和真实世界数据的数据驱动占用网格映射基于Transformer的BEV方案量产方案)

0. 简介 在自动驾驶汽车&#xff08;AV&#xff09;的感知任务中&#xff0c;数据驱动的方法往往优于传统方法。这促使我们开发了一种基于数据的方法来从激光雷达测量中计算占用网格地图&#xff08;OGM&#xff09;。我们的方法扩展了之前的工作&#xff0c;使得估计的环境表…...

ssh之登录服务器后,自动进入目录(四十七)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…...

如何看待IBM中国研发部裁员?

背景&#xff1a; 近日&#xff0c;IBM中国宣布撤出在华两大研发中心&#xff0c;引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展&#xff0c;也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对这一突如其来的变化&…...

计算机毕业设计选题推荐-土地承包管理系统-Java/Python项目实战(亮点:数据可视化分析、账号锁定、智能推荐)

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…...

2024年高校辅导员考试题库及答案

一、判断题 121.高校学生身份权是基于高等教育的性质&#xff0c;学生应该获得的本体性权利。 答案&#xff1a;正确 122.学费占年生均教育培养成本的比例和标准由财政部制定。 答案&#xff1a;错误 123.享受国家专业奖学金的高校学生免缴学费。 答案&#xff1a;错误 124…...

Unity物理游戏开发:如何用FixedTimestep优化不同设备的性能表现

Unity物理游戏开发&#xff1a;动态调整FixedTimestep实现跨设备性能优化 移动端游戏开发者常面临一个核心矛盾&#xff1a;物理模拟精度与设备性能的平衡。当你的游戏在高端设备上流畅运行&#xff0c;却在低端机型出现卡顿时&#xff0c;问题往往出在Fixed Timestep的静态配置…...

告别Putty和串口助手:这款LVGL开发的LCOM,如何成为我的嵌入式开发调试新宠?

告别Putty和串口助手&#xff1a;这款LVGL开发的LCOM&#xff0c;如何成为我的嵌入式开发调试新宠&#xff1f; 作为一名嵌入式开发者&#xff0c;每天与各种开发板、单片机打交道是家常便饭。调试过程中&#xff0c;串口通信工具就像我们的"第三只手"&#xff0c;从…...

实战应用:用快马平台将dc=y103pc=参数转化为电商筛选功能

今天想和大家分享一个在电商项目中特别实用的功能开发经验——如何把URL参数&#xff08;比如dcy103&pchigh这种格式&#xff09;转化成用户友好的商品筛选面板。这个需求在实际业务中特别常见&#xff0c;比如用户分享一个筛选好的商品列表链接&#xff0c;其他人打开时能…...

春招已经过半,这一波再不动手,基本就没位置了

关注 霍格沃兹测试学院公众号&#xff0c;回复「资料」&#xff0c;领取人工智能测试开发技术合集导读3月底这个时间点&#xff0c;如果你还在纠结“要不要投”&#xff0c;那基本已经慢半拍了。现在的真实情况是&#xff1a;大厂已经进入筛选面试并行阶段一部分公司已经开始发…...

2026年华为云OpenClaw如何安装?配置百炼API零门槛10分钟步骤

2026年华为云OpenClaw如何安装&#xff1f;配置百炼API零门槛10分钟步骤。OpenClaw&#xff08;曾用名Clawdbot&#xff09;是一款轻量化、可扩展的开源AI智能体执行框架&#xff0c;支持自然语言指令驱动、多模型灵活切换与全场景任务自动化。对于新手而言&#xff0c;阿里云轻…...

忍者像素绘卷参数详解:CFG/Steps/画幅三要素调优指南

忍者像素绘卷参数详解&#xff1a;CFG/Steps/画幅三要素调优指南 1. 认识忍者像素绘卷 忍者像素绘卷是一款基于Z-Image-Turbo深度优化的图像生成工作站&#xff0c;它将忍者的热血意志与16-Bit复古游戏美学完美融合。这款工具采用明亮的"云端"视觉设计&#xff0c;…...

Hotkey Detective:Windows热键冲突终极诊断指南

Hotkey Detective&#xff1a;Windows热键冲突终极诊断指南 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detective 你是否曾经遇到…...

终极指南:如何用智能工具轻松突破内容访问限制

终极指南&#xff1a;如何用智能工具轻松突破内容访问限制 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 内容访问突破工具是现代数字工作者的必备利器&#xff0c;它能帮助研究人员…...

Pixel Language Portal快速上手:无需Python基础的Streamlit镜像开箱即用

Pixel Language Portal快速上手&#xff1a;无需Python基础的Streamlit镜像开箱即用 1. 什么是Pixel Language Portal&#xff1f; Pixel Language Portal&#xff08;像素语言跨维传送门&#xff09;是一款基于腾讯Hunyuan-MT-7B核心引擎构建的创新翻译工具。它最大的特点是…...

国行iPhone Siri功能意外上线又撤回,背后暗藏玄机

iPhone“Siri”变身“Apple智能与Siri”&#xff0c;意外功能短暂亮相3月31日凌晨&#xff0c;部分国行iPhone用户惊喜发现&#xff0c;手机设置中的“Siri”入口悄然变更为“Apple智能与Siri”&#xff0c;同时还短暂解锁了端侧模型下载及AI功能。不过&#xff0c;这一新鲜体验…...