【重难点算法题】设计哈希集合、哈希映射

文章目录

    • Tag
    • 题目来源
    • 解题思路
      • 方法一:链地址法
    • 类似题目
      • 代码1
      • 代码2
    • 写在最后

Tag

【哈希集合】【哈希映射】【链地址法】【数据结构设计】


题目来源

705. 设计哈希集合

解题思路

在解题之前需要先明确两组概念:

  • 哈希表与散列表
  • 哈希函数与散列函数

上面的每组概念都是等同的,比如,有些资料中会将哈希表称作散列表,这两个概念是完全相同的,都是任意元素到另一个固定数值的映射。哈希表中的哈希函数对应的就是散列表中的散列函数。

为了实现哈希集合这一数据结构,有以下几个关键问题需要解决:

  • 哈希函数:能够将集合中任意元素映射到一个固定范围的整数值,并将该元素存储到整数值对应的的地址上。
  • 处理冲突:由于不同的元素可能映射到相同的整数值,因此需要再整数值出现冲突时处理冲突。通常有以下几种处理方法:
    • 连地址法:为每一个哈希值维护一个链表,将具有相同哈希值的元素都放入这一链表中;
    • 开放地址法:当发现哈希值 h 处发生冲突时,根据某种策略,从 h 出发找到下一个不冲突的位置。例如,一种简单的策略是,不断检查 h+1h+2,…,这些整数对应的位置,如果某一个地址没有发生冲突,则可以将元素映射到第一个没有发生冲突的地址。
    • 再哈希法:当发生哈希冲突后,使用另一个哈希函数产生一个新的哈希地址。
  • 扩容:当哈希元素过多时,冲突的概率就越来越大,有一个衡量的指标叫做填充因子, 填充因子 = 哈希表包含的元素数 位置总数 填充因子 = \frac{哈希表包含的元素数}{位置总数} 填充因子=位置总数哈希表包含的元素数。 一个不错的经验是:当填充因子大于 0.7,就需要调整哈希表的长度,以减少冲突。

方法一:链地址法

我们设置哈希表的大小为 base,接着设计一个简单的哈希函数:hash(x) = x mod basex 为当前需要映射的元素,hash(x) 表示对应的需要加入到哈希集合中的映射值。

我们开辟一个大小为 base 的数组,将每个 x 的映射值放入数组中,并利用「链地址法」解决哈希冲突,于是在实现中数组中的每一个位置实际放的一条链表。

由于我们使用整数除法作为哈希函数,为了尽可能避免冲突,应当将 base 取为一个质数。在这里,我们取 base=769

代码

class MyHashSet {
private:
    vector<list<int>> data;
    static const int base = 769;
    int hash(int key) {
        return key % base;
    }
public:
    MyHashSet(): data(base) {}  // 列表初始化,初始化 data 大小为 base
    
    void add(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it) == key) {
                return;
            }
        }
        data[h].push_back(key);
    }
    
    void remove(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it) == key) {
                data[h].erase(it);
                return;
            }
        }
    }
    
    bool contains(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it) == key) {
                return true;
            }
        }
        return false;
    }
};

/**
 * Your MyHashSet object will be instantiated and called as such:
 * MyHashSet* obj = new MyHashSet();
 * obj->add(key);
 * obj->remove(key);
 * bool param_3 = obj->contains(key);
 */

复杂度分析

时间复杂度: O ( n b ) O(\frac{n}{b}) O(bn)。其中 n n n 为哈希表中的元素数量, b b b 为链表的数量。假设哈希值是均匀分布的,则每个链表大概长度为 n b \frac{n}{b} bn

空间复杂度: O ( n + b ) O(n+b) O(n+b)


类似题目

706. 设计哈希映射


本题是设计一种哈希映射。设计思路和 设计哈希集合 思路基本一致。以下给出两个代码,代码1 是固定哈希表的大小,代码2 是根据哈希表的负载(填充)情况可以动态扩展哈希表的大小,更具一般性,建议仔细阅读并掌握。

关于哈希表的更多知识,可参考 【算法与数据结构】哈希表。

代码1

class MyHashMap {
private:
    vector<list<pair<int, int>>> data;
    static const int base = 769;
    static int hash(int key) {
        return key % base;
    }
public:
    MyHashMap():data(base) {}
    
    void put(int key, int value) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it).first == key) {
                (*it).second = value;
                return;
            }
        }
        data[h].push_back(make_pair(key, value));
    }
    
    int get(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it).first == key) {
                return (*it).second;
            }
        }
        return -1;
    }
    
    void remove(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it).first == key) {
                data[h].erase(it);
                return;
            }
        }
    }
};

/**
 * Your MyHashMap object will be instantiated and called as such:
 * MyHashMap* obj = new MyHashMap();
 * obj->put(key,value);
 * int param_2 = obj->get(key);
 * obj->remove(key);
 */

代码2

class MyHashMap {
private:
    int size;           // 键值对数量
    int capacity;       // 哈希表容量
    double loadThres;   // 负载因子阈值
    int extendRatio;    // 扩容倍数
    vector<list<pair<int, int>>> data;

    // hash 函数
    int hash(int key) {
        return key % capacity;
    }

    // 计算负载因子
    double loadFactor() {
        return double(size) / double(capacity);
    }

public:
    // 构造函数
    MyHashMap(): size(0), capacity(8), loadThres(0.7), extendRatio(2), data(capacity) {}

    // 析构函数
    ~MyHashMap() {}

    // 添加键值对
    void put(int key, int val) {
        ++size;
        if (loadFactor() > loadThres) {
            extend();
        }
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it).first == key) {
                (*it).second = val;
                return;
            }
        }
        data[h].push_back(make_pair(key, val));
    }

    // 查找
    int get(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it).first == key) {
                return (*it).second;
            }
        }
        return -1;
    }

    // 删除
    void remove(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); ++it) {
            if ((*it).first == key) {
                data[h].erase(it);
                --size;
                return;
            }
        }
    }

    // 扩容
    void extend() {
        vector<list<pair<int, int>>> dataTmp = data;
        capacity *= extendRatio;
        data.clear();
        data.resize(capacity);
        size = 0;
        for (auto& ele : dataTmp) {
            for (auto it = ele.begin(); it != ele.end(); ++it) {
                put((*it).first, (*it).second);
            }
        }
    }
};

写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于图形库

文章目录 1. 概念介绍2. 使用方法2.1 普通路由2.2 命名路由 3. 示例代码4. 内容总结 我们在上一章回中介绍了"使用get显示Dialog"相关的内容&#xff0c;本章回中将介绍使用get进行路由管理.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

分布式领域计算模型及SparkRay实现对比

目录 一、分布式计算领域概览 二、Spark计算模型分析 三、Ray计算模型分析 3.1 需求分析 3.2 系统设计 3.3 系统实现 四、总结 一、分布式计算领域概览 当前分布式计算模型主要分为以下4种&#xff1a; Bulk Synchronous Parallel Model&#xff08;块同步并行模型&…

视频下载器 UC网盘

老王导航 - 复杂问题找老王&#xff0c;简单问题百度搜 神器啊

入门2-分支结构

【深基2.习6】Apples Prologue / 苹果和虫子 题目描述 小 B 喜欢吃苹果。她现在有 m m m&#xff08; 1 ≤ m ≤ 100 1 \le m \le 100 1≤m≤100&#xff09;个苹果&#xff0c;吃完一个苹果需要花费 t t t&#xff08; 0 ≤ t ≤ 100 0 \le t \le 100 0≤t≤100&#xff0…

500行代码实现贪吃蛇(1)

文章目录 目录1. Win32 API 介绍1.1 Win32 API1.2 控制台程序&#xff08;Console&#xff09;1.3 控制台屏幕上的坐标COORD1.4 [GetStdHandle](https://learn.microsoft.com/zh-cn/windows/console/getstdhandle)1.5 [GetConsoleCursorInfo](https://learn.microsoft.com/zh-c…

LAME及 iOS 编译

文章目录 关于 LAME编译 for iOS 关于 LAME 官网&#xff1a;https://lame.sourceforge.io LAME是根据LGPL许可的高质量MPEG音频层III&#xff08;MP3&#xff09;编码器。 LAME的开发始于1998年年中左右。Mike Cheng 最开始将它作为针对8hz-MP3编码器源的补丁。在其他人提出…

python学习笔记----异常、模块与包(九)

一、异常 1.1 什么是异常 在Python中&#xff0c;异常是程序执行时发生的错误。当Python检测到一个错误时&#xff0c;它会引发一个异常&#xff0c;这可能是由于多种原因&#xff0c;如尝试除以零、访问不存在的文件&#xff0c;或者尝试从列表中获取不存在的索引等。异常处…

踏春正当时!VELO Prevail Ride带你探索多元骑行潮流体验~

嘿&#xff0c;朋友&#xff01;踏春正当时嘞&#xff01;在这个追求个性化与多元化的新时代&#xff0c;骑行爱好者们也开始寻找能适应各种骑行场景的理想坐垫。从悠闲自在的日常通勤&#xff0c;到热血沸腾的公路竞速&#xff0c;再到勇攀高峰的山地探险&#xff0c;维乐VELO…

【Linux—进程间通信】共享内存的原理、创建及使用

什么是共享内存 共享内存是一种计算机编程中的技术&#xff0c;它允许多个进程访问同一块内存区域&#xff0c;以此作为进程间通信&#xff08;IPC, Inter-Process Communication&#xff09;的一种方式。这种方式相对于管道、套接字等通信手段&#xff0c;具有更高的效率&…

论文辅助笔记:TimeLLM

1 __init__ 2 forward 3 FlattenHead 4 ReprogrammingLayer

总分420+专业140+哈工大哈尔滨工业大学803信号与系统和数字逻辑电路考研电子信息与通信工程,真题,大纲,参考书。

考研复习一路走来&#xff0c;成绩还是令人满意&#xff0c;专业803信号和数电140&#xff0c;总分420&#xff0c;顺利上岸&#xff0c;总结一下自己这一年复习经历&#xff0c;希望大家可以所有参考&#xff0c;这一年复习跌跌拌拌&#xff0c;有时面对压力也会焦虑&#xff…

【软件设计师】上午题

【软考】软件设计师plus 「软件设计师」 2022年下半年上午真题解析视频 计算机系统知识 22下 考点&#xff1a;指令系统之CISC vs RISC RISC指令系统整体特点是简单、精简 》指令种类少&#xff0c;但是指令功能强 考点&#xff1a;计算机系统组成 A属于运算器&#xff0c;…

第四节课《XTuner作业》

Tutorial/xtuner/personal_assistant_document.md at camp2 InternLM/Tutorial GitHub Tutorial/xtuner/personal_assistant_document.md at camp2 InternLM/Tutorial GitHub GitHub - InternLM/Tutorial at camp2 视频链接&#xff1a;https://b23.tv/BrTSfsl PDF链接&a…

【Delphi 爬虫库 3】使用封装好的 HTML 解析库对 HTML 数据进行解析

文章目录 解析HTML的意义1、简单解析HTML代码2、实战解析HTML代码 解析HTML的意义 HTML是Web页面的构建语言&#xff0c;每个Web开发者都需要了解HTML的基础知识。但是&#xff0c;通过手动阅读和解析需要极大的心智和时间投入。这时候&#xff0c;我们就需要使用HTML在线解析…

Mac 电脑安装 Raptor 流程图软件的方法

0. 安装逻辑 &#xff08;1&#xff09;运行 raptor&#xff0c;本质上需要 mac 能够运行 windows 程序&#xff0c;因此需要安装 .NET Runtime 7.0&#xff0c;这是微软程序运行必须的文件。 &#xff08;2&#xff09;运行 raptor 还需要安装依赖文件 mono-libgdiplus。 &am…

【C++】一篇文章带你熟练掌握<智能指针>及其模拟实现

目录 一、引入 二、智能指针的使用及原理 1、RAII 2、智能指针的原理 3、auto_ptr 4、unique_ptr 5、shared_ptr 6、weak_ptr 一、引入 我们先分析一下为什么需要智能指针&#xff1f; double Division(int a, int b) {// 当b 0时抛出异常if (b 0){throw invalid_a…

Day30:热帖排行、生成长图、将文件上传到云服务器、优化热门帖子列表、压力测试

热帖排行 不同的算分方式&#xff1a; 只存变化的帖子到redis中&#xff0c;每五分钟算一次分&#xff0c;定时任务 存redis 构建redis键 //统计帖子分数 //key:post:score -> value:postId public static String getPostScoreKey() {return PREFIX_POST SPLIT "…

【解决】docker一键部署报错

项目场景见&#xff1a;【记录】Springboot项目集成docker实现一键部署-CSDN博客 问题&#xff1a; 1.docker images 有tag为none的镜像存在。 2.有同事反馈&#xff0c;第一次启动docker-compose up -d 项目无法正常启动。后续正常。 原因&#xff1a; 1.服务中指定了镜像m…

mqtt上行数据传送

{"id": "123","version": "1.0","params": {"wendu": {"value": 25.0},"humi": {"value": 23.6}} } 不要time!!!!!!!!!!!!!!!!!!!!!!!!!!! 下面是官方文档的代码&#xff0c;我用…

自制RAG工具:docx文档读取工具

自制RAG工具&#xff1a;docx文档读取工具 1. 介绍2. 源码2.1 chunk2.2 DocReader 3. 使用方法3.1 文档格式设置3.2 代码使用方法 1. 介绍 在RAG相关的工作中&#xff0c;经常会用到读取docx文档的功能&#xff0c;为了更好地管理文档中的各个分块&#xff0c;以提供更高质量的…