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

news2024/11/25 7:54:58

目录

链地址法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 ,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

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

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

相关文章

MySQL record 04 part

高级查询&#xff1a; order by 对查询结果排序&#xff0c; 注意&#xff0c;使用order by的时候&#xff0c;如果某条记录的order by 指定的字段值是 null&#xff0c;那么包含 null 的这条记录会排在第一位&#xff0c;因为 null 被认为是最小值。 group by 对字段的值进行…

【系统分析师】-软件设计

目录 1、概要设计 1&#xff09;层次图&#xff08;H图&#xff09; 2&#xff09;HIPO图 2、详细设计 1&#xff09;流程图 2&#xff09;盒图&#xff08;N-S图&#xff09; 3&#xff09;PAD 问题分析图 4&#xff09;PDL伪代码图 3、软件设计过程 4、软件设计活动…

MQTT工业网关的工作原理及其在实际生产中的重要作用

在智能制造与工业4.0的浪潮中&#xff0c;MQTT工业网关作为连接传统工业设备与现代物联网技术的桥梁&#xff0c;正发挥着不可或缺的作用。MQTT协议以其轻量级、开放性和可靠性&#xff0c;在工业物联网领域得到了广泛应用。本文将通过一个实际应用案例&#xff0c;解析MQTT工业…

网络安全工程师填补人才缺口

近年来&#xff0c;新兴技术如人工智能、5G和量子信息技术等的迅猛发展&#xff0c;极大地推动了互联网技术的革新。 然而&#xff0c;随之而来的网络安全威胁也日益增多&#xff0c;对国家、企业及个人安全构成了严重挑战。 网络安全问题就在我们身边&#xff0c;因此&#…

关于电影票api接口你了解多少?

电影票API接口是连接第三方平台与电影院票务系统的一种技术手段&#xff0c;它允许第三方应用程序如网站、移动应用或小程序集成电影票购买服务。通过API&#xff0c;用户可以在第三方平台上查询电影信息、影院排期、选择座位并完成购票支付。 电影票API接口的主要功能通常包括…

智能头盔语音识别声控芯片,AI离线语音识别ic方案,NRK3301

头盔是交通事故中保护电动车车主安全的最后一道屏障。为了增加骑行用户的安全保护&#xff0c;改善骑行用户的出行体验&#xff0c;让用户从被动使用头盔到主动佩戴头盔&#xff0c;头盔厂家与九芯电子合作&#xff0c;推出了语音智能头盔&#xff0c;它具备首家骑行专用的智能…

关于SpringBoot项目yml配置数据库、redis、mq等中间件的用户密码敏感信息加密问题的解决方案

一、问题描述 一般情况下,yml里边的配置信息 都是在项目部署时动态管理的,一般不存在泄密或者不安全的情况,但是,不凡有一些脑袋有泡的客户,要对你项目源码进行安全性检测。故提供如下解决方案: 二、关于中间件Redis、MQ等对用户名或密码进行加密。 一般可在其对应的配…

【.NET 8 实战--孢子记账--从单体到微服务】--用户(登录/注册/Token)

从这篇文章开始&#xff0c;我们就进入到了项目开发阶段。我们的项目是面向用户的&#xff0c;因此我们首先要做的是和用户相关的逻辑代码。 一、需求 首先&#xff0c;我们来看一下服务端的需求&#xff1a; 编号需求标题需求内容1登录传入参数用户名、密码和验证码&#x…

国内PMP可以在线考试?

1. PMP考试的两种类型 PMP考试分为两种类型&#xff1a;一种是在线机考&#xff0c;另一种是线下笔试。国外采用机考形式&#xff0c;可以随时参加考试&#xff0c;除了节假日&#xff1b;而国内由中国国际人才交流基金会和PMI共同组织&#xff0c;因此是线下笔试。 虽然线上…

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合&#xff0c;内部除了存储元素外&#xff0c;还会存储一个score&#xff0c;存储在zset中的元素会按照score的大小升序排列&#xff0c;不同元素的score可以重复&#xff0c;score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd zadd key [NX |…

【计算机方向】中科院二区宝刊!国人发文友好,晋升从此不是梦!

期刊解析 &#x1f6a9;本 期 期 刊 看 点 &#x1f6a9; 国人发文占比第一&#xff0c;审稿友好 审稿速度快 自引率5.7% 今天小编带来计算机领域SCI快刊的解读&#xff01; 如有相关领域作者有意投稿&#xff0c;可作为重点关注&#xff01; 01 期刊信息✦ 期刊名称&…

Centos7安装JDK1.8保姆版

工欲善其事&#xff0c;必先利其器。这句话同样适用于学习Java编程。在开始Java的学习旅程之前&#xff0c;我们必须首先配置好适合的开发环境。 通过事先准备好这些工具和配置&#xff0c;我们可以避免在学习过程中遇到因环境问题导致的代码异常或错误。一个稳定、高效的开发环…

python binning data openAI gym

题意&#xff1a;Python 数据分箱 OpenAI Gym 问题背景&#xff1a; I am attempting to create a custom environment for reinforcement learning with openAI gym. I need to represent all possible values that the environment will see in a variable called observati…

11.Java基础概念-ArrayList

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 Facts speak louder than words&#xff01; ArrayList是Java中的一…

kubectl的安装使用

1. Windows下载kubectl 2.将kucectl的所在目录添加到PATH环境变量下 3.运行 kubectl version --client 命令来测试kubectl是否正确安装并显示其版本信息。这个命令会显示kubectl客户端的版本信息&#xff0c;如果一切正常&#xff0c;这将确认kubectl已经成功安装在你的Windo…

DC-DC升降压芯片(MC34063A/33063)典型电路与元件参数在线计算

MC34063包含DC/DC变换器所需的主要功能的单片控制电路&#xff0c;多用于升压变换器、降压变换器、反向器的控制核心部分。 MC34063的基本结构及引脚图功能&#xff1a; 1脚&#xff1a;开关管T1集电极引出端&#xff1b; 2脚&#xff1a;开关管T1发射极引出端&#xff1b; …

一次耗时的安全测试

简介 接到一个安全测试任务&#xff0c;数据包使用安全控件进行了加密。通过开发插件&#xff0c;实现明文测试&#xff0c;最终发现了2个越权。 加解密过程分析 访问网站首页&#xff0c;需要先安装一个控件。安装完成后&#xff0c;访问网站发现数据包加密处理。按照以前的…

基于springboot+vue实现的在线商城系统

系统主要功能&#xff1a; &#xff08;1&#xff09;商品管理模块&#xff1a;实现了商品的基本信息录入、图片上传、状态管理等相关功能。 &#xff08;2&#xff09;商品分类模块&#xff1a;实现了分类的增删改查、分类层级管理、商品分类的关联等功能。 &#xff08;3&…

不懂编程的都觉得AI要取代程序员了,程序员自己却在偷着乐?真相是…

最近&#xff0c;AI写代码的新闻满天飞&#xff0c;不懂编程的人都觉得AI要逆天了&#xff0c;程序员马上就要失业了&#xff01;但奇怪的是&#xff0c;程序员群体——这帮最懂代码、最常使用AI编程工具的人&#xff0c;怎么反而觉得AI取代不了他们呢&#xff1f;&#x1f914…

TD综合教程——噪波球和正交线性脉冲(附思路和工程文件)

一、噪波球 整体思路&#xff1a; 渲染三件套&#xff1a;Geometry COMP、Camera COMP、Render TOP 在此基础上进行pbr MAT材质和environment COMP环境光渲染 使sphere TOP&#xff08;球体&#xff09;更加柔和&#xff0c;将类型改为NURBS Twist TOP&#xff08;扭曲&#x…