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

夏驰和徐策带你从零开始学数据结构——哈希表


哈希表的概念:

哈希表是一种常用的数据结构,它可以在 O(1) 的时间复杂度内执行插入、查找和删除操作。哈希表的核心思想是使用哈希函数将键值对映射到数组中的一个位置上,从而实现快速的访问和修改。

哈希表由两个主要部分组成:哈希函数和数组。哈希函数将键映射到数组的下标,而数组则用来存储键值对。当需要访问或修改某个键值对时,只需要使用哈希函数将键转换为数组下标,然后访问或修改对应的位置即可。

使用哈希表的关键在于设计一个好的哈希函数,它应该满足以下几个要求:

  1. 一致性:同一个键总是映射到相同的数组下标。
  2. 均匀性:尽可能地使键被映射到不同的数组下标上,从而减少哈希冲突的概率。
  3. 高效性:计算哈希值的时间应该尽量短,以保证操作的高效性。

解决哈希冲突的方法有多种,常用的有链表法和开放地址法。链表法是在每个数组元素上维护一个链表,当哈希冲突发生时,将新的键值对插入到链表中。开放地址法则是尝试在其他空闲的位置上插入键值对,比如线性探测、二次探测和双重哈希等。

需要注意的是,哈希表的性能取决于哈希函数的设计和数组的大小。如果哈希函数不好,或者数组太小,就会导致哈希冲突增多,从而降低哈希表的效率。因此,在实际应用中,需要根据具体的场景来设计合适的哈希函数和数组大小,以达到最优的性能。

我的理解:

哈希表是一种用于快速查找和插入的数据结构,其核心思想是通过哈希函数将键映射到数组中的一个位置上。哈希函数将键映射到数组中的位置时,需要满足一致性、均匀性和高效性等要求。具体地说,一致性要求相同的键总是映射到相同的位置上,均匀性要求哈希函数能够尽可能地将键均匀地映射到数组中的位置上,高效性要求计算哈希值的时间尽量短。

哈希表的优点在于其插入、查找和删除的时间复杂度都为 O(1),即常数级别的时间复杂度,因此在需要快速进行这些操作的场合下,哈希表是一种非常有用的数据结构。常用的哈希表实现方式有链表法和开放地址法,其中链表法在哈希冲突时使用链表来存储冲突的键值对,而开放地址法则是尝试在其他空闲的位置上插入键值对。

总的来说,理解哈希表的概念需要掌握哈希函数的设计原理、数组的存储方式和解决哈希冲突的方法等基础知识,以及如何在实际应用中根据具体的场景来选择适合的哈希表实现方式。

例子:

假设我们有一个存储学生信息的数据集合,其中每个学生的信息包括学号、姓名、年龄等。我们需要能够快速地根据学号查找到对应的学生信息。这时,我们可以使用哈希表来实现这个功能。

首先,我们需要设计一个哈希函数,将学号映射到一个数组中的位置上。一种简单的哈希函数可以是取学号的最后几位作为数组下标,比如我们可以取学号的后两位作为下标,那么学号为"20230001"的学生会被映射到数组的第1个位置上,学号为"20230002"的学生会被映射到数组的第2个位置上,以此类推。

接下来,我们可以将每个学生的信息存储到对应的数组位置中。当需要查找某个学生信息时,只需要通过哈希函数计算出该学生信息所在的数组位置,然后访问该位置上的元素即可。

例如,如果我们需要查找学号为"20230001"的学生信息,就可以通过哈希函数将其映射到数组的第1个位置上,然后访问该位置上的元素,即可得到该学生的姓名、年龄等信息。由于哈希表的时间复杂度为 O(1),因此可以在常数级别的时间内完成这个操作,非常高效。

哈希表的实现:

C语言:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define TABLE_SIZE 100// 定义哈希表节点的结构体
typedef struct node {char *key; // 节点的键int value; // 节点的值struct node *next; // 指向下一个节点的指针
} Node;// 定义哈希表的结构体
typedef struct {Node *buckets[TABLE_SIZE]; // 存放哈希桶的数组
} HashTable;// 哈希函数,使用简单的取模法
int hash(char *key) {int hash = 0;for (int i = 0; i < strlen(key); i++) {hash += key[i];}return hash % TABLE_SIZE;
}// 创建一个新节点
Node *create_node(char *key, int value) {Node *node = (Node *) malloc(sizeof(Node));if (node == NULL) {fprintf(stderr, "Error: out of memory\n");exit(1);}node->key = strdup(key); // 使用strdup函数分配内存并复制字符串node->value = value;node->next = NULL;return node;
}// 向哈希表中插入一个节点
void hash_table_insert(HashTable *ht, char *key, int value) {int index = hash(key);Node *node = create_node(key, value);node->next = ht->buckets[index];ht->buckets[index] = node;
}// 在哈希表中查找一个节点
int hash_table_find(HashTable *ht, char *key) {int index = hash(key);Node *node = ht->buckets[index];while (node != NULL) {if (strcmp(node->key, key) == 0) {return node->value;}node = node->next;}return -1; // 表示未找到节点
}// 主函数,测试代码
int main() {HashTable ht;for (int i = 0; i < TABLE_SIZE; i++) {ht.buckets[i] = NULL;}hash_table_insert(&ht, "apple", 1);hash_table_insert(&ht, "banana", 2);hash_table_insert(&ht, "cherry", 3);printf("%d\n", hash_table_find(&ht, "apple")); // 输出 1printf("%d\n", hash_table_find(&ht, "banana")); // 输出 2printf("%d\n", hash_table_find(&ht, "cherry")); // 输出 3printf("%d\n", hash_table_find(&ht, "orange")); // 输出 -1,表示未找到return 0;
}

解释:

这个哈希表使用简单的取模法来计算键的哈希值,将节点插入到对应的哈希桶中,并使用链表解决冲突。在插入和查找节点时,分别使用哈希函数计算出键的哈希值,然后访问对应的哈希桶,遍历链表查找对应的节点。如果找到节点,则返回其值,否则返回-1。 

C++实现:
 

#include <iostream>
#include <string>using namespace std;const int TABLE_SIZE = 100;class HashNode {
public:int key;string value;HashNode* next;HashNode(int key, string value) {this->key = key;this->value = value;this->next = nullptr;}
};class HashMap {
private:HashNode** table;public:HashMap() {table = new HashNode*[TABLE_SIZE];for (int i = 0; i < TABLE_SIZE; i++) {table[i] = nullptr;}}~HashMap() {for (int i = 0; i < TABLE_SIZE; i++) {HashNode* entry = table[i];while (entry != nullptr) {HashNode* prev = entry;entry = entry->next;delete prev;}table[i] = nullptr;}delete[] table;}int getHashCode(int key) {return key % TABLE_SIZE;}void insert(int key, string value) {int hash = getHashCode(key);HashNode* entry = table[hash];if (entry == nullptr) {table[hash] = new HashNode(key, value);} else {while (entry->next != nullptr) {entry = entry->next;}entry->next = new HashNode(key, value);}}string search(int key) {int hash = getHashCode(key);HashNode* entry = table[hash];while (entry != nullptr) {if (entry->key == key) {return entry->value;}entry = entry->next;}return "";}void remove(int key) {int hash = getHashCode(key);HashNode* entry = table[hash];HashNode* prev = nullptr;while (entry != nullptr && entry->key != key) {prev = entry;entry = entry->next;}if (entry == nullptr) {return;}if (prev == nullptr) {table[hash] = entry->next;} else {prev->next = entry->next;}delete entry;}
};int main() {HashMap map;map.insert(1, "apple");map.insert(2, "banana");map.insert(3, "cherry");map.insert(4, "date");cout << map.search(2) << endl; // output: bananamap.remove(3);cout << map.search(3) << endl; // output: (empty string)return 0;
}

解释: 

这个示例中,我们定义了一个HashNode类来表示哈希表的节点,它包含了一个键值对和指向下一个节点的指针。我们还定义了一个HashMap类来实现哈希表,它包含了一个指向指针数组的指针,数组的长度为TABLE_SIZE。我们还实现了一些基本操作,如哈希函数的计算、插入、搜索和删除等。 

总结:

哈希表的重点:

  1. 哈希函数的设计:哈希函数需要将键映射到哈希表中的一个位置,使得每个位置都有均匀的分布,并且不同的键能够映射到不同的位置。

  2. 哈希冲突的处理:哈希冲突是指不同的键映射到了同一个位置,通常有两种处理方式:开放地址法和链表法。开放地址法会寻找哈希表中下一个空闲位置来存储键值对,而链表法会将冲突的键值对组织成一个链表,存储在同一个桶中。

哈希表的难点和易错点:

  1. 哈希函数的设计需要考虑多种因素,包括键的分布、哈希表的大小和性能等,因此需要具备较强的数学能力和经验。

  2. 哈希表的性能受到哈希冲突的影响,因此需要合理选择哈希函数和解决冲突的方法,避免出现过多的冲突,降低查询效率。

  3. 哈希表的空间占用和性能之间存在一定的权衡关系,需要根据具体应用场景和要求进行选择和优化。

  4. 哈希表的实现需要注意边界条件和特殊情况,比如哈希表为空、键不存在等情况的处理。此外,需要注意哈希函数的输出值需要在哈希表大小范围内。

  5. 在使用哈希表进行并发操作时,需要考虑线程安全的问题,避免出现竞争条件和数据损坏等情况。

相关文章:

夏驰和徐策带你从零开始学数据结构——哈希表

哈希表的概念&#xff1a; 哈希表是一种常用的数据结构&#xff0c;它可以在 O(1) 的时间复杂度内执行插入、查找和删除操作。哈希表的核心思想是使用哈希函数将键值对映射到数组中的一个位置上&#xff0c;从而实现快速的访问和修改。 哈希表由两个主要部分组成&#xff1a;…...

linux实现网络程序

1️⃣ 在linux下&#xff0c;通过套接字实现服务器和客户端的通信。 2️⃣ 实现单线程、多线程通信。或者实现线程池来通信。 3️⃣ 优化通信&#xff0c;增加守护进程。 有情提醒&#xff0c;类里面默认的函数是内联。内联函数在调用的地方展开&#xff0c;没有函数地址&…...

FreeRTOS 队列(二)

文章目录 一、向队列发送消息1. 函数原型&#xff08;1&#xff09;函数 xQueueOverwrite()&#xff08;2&#xff09;函数 xQueueGenericSend()&#xff08;3&#xff09;函数 xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()&#xff08;4&…...

用python获取当前目录下的创建时间超过3天的所有python文件

直接上代码&#xff1a; import os import datetime print(os.getcwd()) # 获取当前目录下所有的html文件 html_files [] for filename in os.listdir(): if filename.endswith(.py): html_files.append(os.path.join(., filename)) now date…...

第五章 Linux实际操作——用户管理

第五章 Linux实际操作——用户管理 5.1 基本介绍5.2 添加用户5.3 指定、修改密码5.4 删除用户5.5 查询用户信息指令5.6 切换用户5.7 查看当前用户、登录用户5.8 用户组5.9 用户和组相关文件8.9.1/etc/passwd 文件8.9.2/etc/shadow文件8.9.3/etc/group文件 5.1 基本介绍 Linux系…...

悲观锁和乐观锁详细

悲观锁和乐观锁详细 悲观锁 ​ 悲观锁就是悲观的思想&#xff0c;他认为数据每一次被访问的时候都会被上锁&#xff0c;所以每次获得锁的时候都会上锁&#xff0c;这样其他线程想要获取这个锁的时候就会被堵塞&#xff0c;要等待上一个线程锁的释放。也就是说这个线程只一次只…...

三谈ChatGPT(ChatGPT可以解决问题的90%)

这是我第三次谈ChatGPT&#xff0c;前两篇主要谈了ChatGPT的概念&#xff0c;之所以火的原因和对人们的影响&#xff0c;以及ChatGPT可能存在的安全风险和将面临的监管问题。这一篇主要讲讲ChatGPT的场景和处理问题的逻辑。 这一次我特意使用了ChatGPT中文网页版体验了一番。并…...

Qt QSet 详解:从底层原理到高级用法

目录标题 引言&#xff1a;QSet的重要性与简介QSet 的常用接口迭代器&#xff1a;遍历Qset 中的元素&#xff08;Iterators: Traversing Elements in Qset &#xff09;高级用法&#xff1a;QSet 中的算法与功能&#xff08;Advanced Usage: Algorithms and Functions in QList…...

Mac Doxygen的使用

Doxygen的使用 安装着Doxygen和Graphviz这两个东西 在源码目录先使用doxygen -g生成一个叫 ‘Doxyfile’ 的Doxygen的配置文件修改配置文件&#xff0c;里面都有介绍各个选项的功能&#xff0c;这里主要修改一下几个: HAVE_DOT YES EXTRACT_ALL YES EXTRACT_PRIVATE YES E…...

FPGA基础代码复用

一、verilog中有关代码复用的语法 1、连接符“{}” {4{1b1}} 或者 {5d6, 5d8} 2、参数(Parameter)型常量定义 parameter 参数名&#xff1d;表达式&#xff1b; 或者 localparam 参数名&#xff1d;表达式&#xff1b; parameter DATA_WIDTH 20; 3、function函数定义 …...

Hbase简介

HBase简介 一、HBase简介 1. HBase简介 (1) apache的顶级项目&#xff0c;hadoop的数据库&#xff0c;分布式、大规模的大数据存储。 HBase是Google的BigTable的开源java版本&#xff0c;建立在hdfs之上的&#xff0c;分布式、列存储、非关系&#xff08;nosql、key-value&a…...

科海思除COD树脂,大孔树脂,除COD专用树脂

一、产品介绍 Tulsimer A-722 MP具有控制孔径的大孔强碱性Ⅰ型阴离子交换树脂 Tulsimer A-722 MP 是一款具有便于颜色和有机物去除的控制孔径的&#xff0c;专门开发的大孔强碱性Ⅰ型阴离子交换树脂。 Tulsimer A-722 MP&#xff08;氯型&#xff09;专门应用于去除COD…...

Qt 多线程 QThread、QThreadPool使用场景

QThread 和 QRunnable 都是 Qt 框架中用于多线程编程的类&#xff0c;它们之间有以下不同点&#xff1a; 继承关系不同 QThread 继承自 QObject 类&#xff0c;而 QRunnable 没有父类。 实现方式不同 QThread 是一个完整的线程实现&#xff0c;包含了线程的创建、启动、停止、…...

如何一招搞定PCB阻焊过孔问题?

PCB阻焊油墨根据固化方式&#xff0c;阻焊油墨有感光显影型的油墨&#xff0c;有热固化的热固油墨&#xff0c;还有UV光固化的UV油墨。而根据板材分类&#xff0c;又有PCB硬板阻焊油墨&#xff0c;FPC软板阻焊油墨&#xff0c;还有铝基板阻焊油墨&#xff0c;铝基板油墨也可以用…...

【代码随想录】刷题Day2

1.左右指针比大小 977. 有序数组的平方 class Solution { public:vector<int> sortedSquares(vector<int>& nums) {vector<int> ret nums;int left 0;int right nums.size()-1;int end nums.size();while(left<right){if(abs(nums[left])>abs…...

Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能&#xff0c;这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…...

计及调度经济性的光热电站储热容量配置方法【IEEE30节点】(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …...

“不要放过这个春天”解锁品牌春日宣传新玩法

在万物复苏的春天&#xff0c;人们换新装、踏青等需求蓄势待发&#xff0c;出现了全民消费热情高涨的趋势&#xff0c;让品牌「贩卖春天」的宣传此起彼伏。 品牌洞察到用户的消费需求&#xff0c;打造具有品牌特色的浪漫宣传&#xff0c;如采用春日限定元素、创新春天宣传场景…...

利用GPT2 预测 福彩3d预测

使用GPT2预测福彩3D项目 个人总结彩票数据是随机的,可以预测到1-2个数字,但是有一两位总是随机的 该项目紧做模型学习用,通过该项目熟练模型训练调用生成过程. 福彩3D数据下载 https://www.17500.cn/getData/3d.TXT data数据格式 处理后数据格式 每行 2023 03 08 9 7 3 训…...

类加载过程

基本说明 反射机制是Java实现动态语言的关键&#xff0c;也就是通过反射实现类动态加载。 静态加载&#xff1a;编译时加载相关的类&#xff0c;如果没有则报错&#xff0c;依赖性太强动态加载&#xff1a;运行时加载需要的类&#xff0c;如果运行时不用该类&#xff0c;即使…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...