当前位置: 首页 > 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…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...