【数据结构】散列表(哈希表)的学习知识总结
目录
1、散列表
2、散列函数
2.1 定义
2.2 散列函数的构造
2.2.1 除留余数法
2.2.2 直接定址法
2.2.3 数字分析法
2.2.4 平方取中法
3、冲突(碰撞)
4、处理冲突的方法
4.1 拉链法(链接法)
4.2 开放定址法
5、C语言实现散列表(哈希表)
方式一:数组
方式二:指针和动态内存分配
1、散列表
散列表(Hash Table)是一种数据结构,它通过使用散列函数将关键字映射到表中的一个位置,从而实现快速查找。散列表通常被用于实现字典、数据库索引等数据结构,它可以提供关键字的高效插入、查找和删除操作。散列表的实现基于数组,每个元素包含一个键值对(key-value pair)。其中,键通常是一个字符串或整数,值可以是任何类型的数据,例如字符串、整数、对象等。在查找元素时,散列表使用散列函数将关键字转换为数组索引,并在此位置上查找键值对。如果存在,则返回其对应的值;如果不存在,则返回空值。

2、散列函数
2.1 定义
散列函数是一种将输入数据映射到固定大小输出数据的函数。它将不同大小的输入数据转换成相同长度的散列值(也称为哈希值、摘要或指纹),通常用作数据加密、数字签名、消息认证码(MAC)等领域的基础。
散列函数应该满足以下要求:
1. 均匀性:散列函数应该将不同输入数据均匀地映射到输出空间的不同位置,以减少散列冲突的发生。
2. 独立性:输入数据的微小变化应该能够引起输出散列值的大幅度变化,以增强散列函数的安全性。
3. 不可逆性:根据输出散列值无法推导出输入数据,即难以通过散列值反向计算出输入数据。
常见的散列函数算法包括MD5、SHA-1、SHA-2、SHA-3等。但是,由于计算机计算能力的提高和攻击技术的发展,目前常用的算法已不够安全,需要不断更新和升级。
2.2 散列函数的构造
2.2.1 除留余数法
将关键字除以某个数m,取余数作为散列地址,即h(k)=k%m。但是如果m的取值不合适,可能会导致散列地址分布不均匀。m的取值一般是不大于散列表表长的最大质数。
2.2.2 直接定址法
将关键字直接映射为地址,即h(k)=a*k+b(a,b是常数)。但是如果关键字取值范围较大,则需要大量的内存空间。
2.2.3 数字分析法
选取数码分布较为均匀的若干位最为散列地址。
2.2.4 平方取中法
先将关键字平方,然后取中间几位作为散列地址,即h(k)=取中间几位( k^2 )。这种方法能够减小散列地址的值的尺寸,但是也可能出现分布不均匀的问题。

3、冲突(碰撞)
散列表的冲突指的是将两个或多个不同的键映射到了散列表的同一个位置上。这种情况称为冲突,因为两个不同的键无法在同一个位置上存储。
散列表的冲突可以通过散列函数的优化和冲突解决方法来减少或避免。以下是几种常见的冲突解决方法:
- 链接法 (Chaining):将相同位置上的键值对通过链表等数据结构链接在一起。
- 开放地址法 (Open Addressing):在发生冲突时,按照一定规则去寻找数组内的下一个空闲位置存储。
- 双散列法 (Double Hashing):当发生冲突时,使用另一个散列函数对键进行再次散列,再根据新的散列值去找到空位置存储。
4、处理冲突的方法
4.1 拉链法(链接法)
散列表解决冲突的链接法又被称为“拉链法”,其主要思想是将散列到同一个位置的元素存储在一个链表中。当散列到同一个位置的元素出现冲突时,只需要将新的元素插入到链表的末尾即可。
具体实现方式如下:
1. 创建一个散列表,其中每个位置都是一个链表的头结点。
2. 对于要插入的元素,先计算它的散列值,并找到对应的链表头结点。
3. 遍历该链表,查找是否有相同的元素,如果有,则更新它的值;否则,在链表末尾插入新元素。
4. 如果插入操作导致链表长度超过一定阈值,可以考虑进行动态扩容或者重新散列操作。
5. 对于查询和删除操作,也需要先计算元素的散列值,找到对应的链表,然后再进行操作。
在实际使用中,为了避免链表过长影响性能,可以设置一个阈值,当链表长度超过该阈值时,可以考虑进行扩容或者重新散列操作。
4.2 开放定址法
开放定址法是一种常用的解决冲突的方法。具体来说,开放定址法的思想是当发现散列表中的某个位置已经被占用时,就去寻找下一个可用的位置,直到找到一个空槽或者遍历完整个散列表。
开放定址法中,有四种基本的探测方法:
1. 线性探测:逐个查看表中的下一个空单元,直到找到一个空的单元为止。
2. 平方探测:访问下一个位置的时候,是按照下一个位置的平方进行访问的。
3. 双重散列:访问下一个位置的时候,是根据第二个哈希函数得到的值来访问的。
4.伪随机序列法:访问下一个位置的时候,是根据伪随机序列数得到的值来访问的。
利用开放定址法解决冲突的散列表的具体实现步骤如下:
1. 对于给定的键值,计算其散列值。
2. 如果在散列表中的该位置为空,则将该键值存储在该位置上。
3. 如果在散列表中的该位置已经被占用,则使用开放定址法寻找下一个可用的位置,直到找到一个空槽或者遍历完整个散列表。
4. 在寻找到的空槽上存储该键值。
5. 当需要查找某个键值时,首先计算该键值的散列值,并在散列表中查找该键值。如果已经找到,则返回该键值的值。如果未找到,则表示该键值不存在于散列表中。
开放定址法可能会造成散列表中某些位置被长时间占用,导致散列表性能下降。因此,在实现散列表时需要进行合理的设计和优化。
5、C语言实现散列表(哈希表)
方式一:数组
哈希表是一种常用的数据结构,它可以在平均情况下实现常数时间的插入、删除和查找操作。下面是用C语言数组实现哈希表的代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>#define MAX_SIZE 100 //哈希表的最大长度//定义哈希表结构体
typedef struct HashTable{int size;int* keys;char** values;
}HashTable;//哈希函数
int hashFunction(int key, int size){return key % size;
}//创建哈希表
HashTable* createHashTable(int size){HashTable* hashtable = (HashTable*)malloc(sizeof(HashTable));hashtable->size = size;hashtable->keys = (int*)calloc(size, sizeof(int));hashtable->values = (char**)calloc(size, sizeof(char*));for(int i=0; i<size; i++){hashtable->keys[i] = -1;}return hashtable;
}//插入键值对
void insert(HashTable* hashtable, int key, char* value){int index = hashFunction(key, hashtable->size);while(hashtable->keys[index] != -1){ //如果该位置已经被占用,则线性探测到下一个位置index++;index %= MAX_SIZE;}hashtable->keys[index] = key;hashtable->values[index] = (char*)malloc(sizeof(char) * (strlen(value)+1));strcpy(hashtable->values[index], value);
}//获取键对应的值
char* get(HashTable* hashtable, int key){int index = hashFunction(key, hashtable->size);while(hashtable->keys[index] != key){index++;index %= MAX_SIZE;}return hashtable->values[index];
}//删除键值对
void delete(HashTable* hashtable, int key){int index = hashFunction(key, hashtable->size);while(hashtable->keys[index] != key){index++;index %= MAX_SIZE;}hashtable->keys[index] = -1;free(hashtable->values[index]);
}//测试代码
int main(){HashTable* hashtable = createHashTable(MAX_SIZE);insert(hashtable, 1, "张三");insert(hashtable, 2, "李四");insert(hashtable, 3, "王五");printf("%s\n", get(hashtable, 2)); //输出"李四"delete(hashtable, 1);printf("%s\n", get(hashtable, 1)); //输出空字符串return 0;
}
在该实现中,我们使用了线性探测解决了哈希冲突的问题。对于哈希冲突,我们首先根据哈希函数计算键的索引,如果该位置已经被占用,则继续向下线性探测,直到找到空闲位置为止。当我们要查找或删除键值对时,我们也需要进行线性探测,直到找到对应的键为止。
方式二:指针和动态内存分配
哈希表是一种常见的数据结构,它可以支持快速的查询和插入操作。在C语言中,我们可以使用指针和动态内存分配来实现一个哈希表。
下面是一个简单的哈希表实现示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define TABLE_SIZE 10000// 哈希表节点结构体
typedef struct HashNode {int key;int value;struct HashNode* next;
} HashNode;// 哈希表结构体
typedef struct HashTable {HashNode* table[TABLE_SIZE];
} HashTable;// 哈希函数,将key映射成一个索引
int hash(int key) {return key % TABLE_SIZE;
}// 初始化一个哈希表
HashTable* initHashTable() {HashTable* ht = (HashTable*)malloc(sizeof(HashTable));memset(ht->table, 0, sizeof(ht->table));return ht;
}// 插入一个键值对到哈希表中
void insert(HashTable* ht, int key, int value) {int index = hash(key);HashNode* node = (HashNode*)malloc(sizeof(HashNode));node->key = key;node->value = value;node->next = NULL;if (ht->table[index] == NULL) {ht->table[index] = node;} else {HashNode* cur = ht->table[index];while (cur->next != NULL) {cur = cur->next;}cur->next = node;}
}// 查找一个key对应的值
int find(HashTable* ht, int key) {int index = hash(key);HashNode* cur = ht->table[index];while (cur != NULL) {if (cur->key == key) {return cur->value;}cur = cur->next;}return -1;
}// 从哈希表中删除一个键值对
void delete(HashTable* ht, int key) {int index = hash(key);HashNode* cur = ht->table[index];if (cur == NULL) {return;}if (cur->key == key) {ht->table[index] = cur->next;free(cur);return;}HashNode* prev = cur;cur = cur->next;while (cur != NULL) {if (cur->key == key) {prev->next = cur->next;free(cur);return;}prev = cur;cur = cur->next;}
}int main() {HashTable* ht = initHashTable();insert(ht, 1, 10);insert(ht, 2, 20);insert(ht, 3, 30);printf("%d\n", find(ht, 1));printf("%d\n", find(ht, 2));printf("%d\n", find(ht, 3));delete(ht, 2);printf("%d\n", find(ht, 2));return 0;
}
在上面的代码中,我们定义了一个哈希表节点结构体HashNode,包含了一个键值对和指向下一个节点的指针;还定义了一个哈希表结构体HashTable,包含了一个指针数组,每个指针指向一个节点链表。
哈希函数hash将键映射成一个索引,使用求余运算实现。初始化函数initHashTable动态分配内存,并将指针数组初始化为空。插入函数insert根据哈希函数计算出索引,再将节点插入到指针数组对应位置的链表中。查找函数find根据哈希函数计算出索引,再在对应链表中遍历查找键值对。删除函数delete根据哈希函数计算出索引,再在对应链表中遍历查找要删除的节点,并释放对应的内存。
在主函数中,我们创建一个哈希表并插入三个键值对。然后分别查找这三个键,并删除第二个键。最后再次查找第二个键,由于已经被删除,输出-1。
相关文章:
【数据结构】散列表(哈希表)的学习知识总结
目录 1、散列表 2、散列函数 2.1 定义 2.2 散列函数的构造 2.2.1 除留余数法 2.2.2 直接定址法 2.2.3 数字分析法 2.2.4 平方取中法 3、冲突(碰撞) 4、处理冲突的方法 4.1 拉链法(链接法) 4.2 开放定址法 5、C语言…...
2023智慧云打印小程序源码多店铺开源版 +前端
智慧自助云打印系统/智慧云打印小程序源码 前端 这是一款全新的基于Thinkphp的最新自助打印系统,最新UI界面设计的云打印小程序源码...
利用亚马逊 云服务器 EC2 和S3免费套餐搭建私人网盘
网盘是一种在线存储服务,提供文件存储,访问,备份,贡献等功能,是我们日常中不可或缺的一种服务。很多互联网公司都为个人和企业提供免费的网盘服务。但这些免费服务都有一些限制,比如限制下载速度࿰…...
数据分析技能点-数据的种类
在日常生活中,数据无处不在。当你去超市购物时,你可能会注意到商品的价格、重量、口味等;当你在社交媒体上浏览时,你可能会注意到好友的点赞数、评论等。这些都是数据的一种形式,而了解这些数据的种类和特点有助于我们更好地理解和使用它们。 数据的基本分类 数据大致可…...
解读:ISO 14644-21:2023《洁净室及相关受控环境:悬浮粒子采样》发布指导粒子采样!
药品洁净实验室环境监测结果是否满足微生物检测需求,直接决定检测结果的有效性准确性,进行药品微生物检测,必须对实验环境进行日常和定期监测,其内容包括非生物活性的空气悬浮粒子数及有生物活性的微生物监测。 悬浮粒子监测是保证…...
Java --- MySQL8之索引优化与查询优化
目录 一、索引失效场景 1.1、全值匹配 1.2、最佳左前缀规则 1.3、主键插入顺序 1.4、计算、函数、类型转换(自动或手动)导致索引失效 1.5、类型转换导致索引失效 1.6、范围条件右边的列索引失效 1.7、不等于(! 或者<>)索引失效 1.8、is null可以使用索引&…...
澳大利亚新版《2023年消费品(36个月以下儿童玩具) 安全标准》发布 旨在降低危险小零件的伤害
2023年9月4日,澳大利亚政府发布了新的儿童玩具强制性安全标准《2023年消费品(36个月以下儿童玩具)安全标准》(Consumer Goods (Toys for Children up to and including 36 Months of Age) Safety Standard 2023)。该强制性标准旨在尽可能地降…...
表格内日期比较计算
需求:在表格中新增数据,计算开始日期中最早的和结束日期中最晚的,回显到下方。 <el-formref"formRef":model"ruleForm":rules"rules"style"margin-top: 20px;"label-position"top">…...
Linux内核启动流程-第二阶段start_kernel 函数
一. Linux内核启动 上一篇文章简单介绍了 Linux内核启动的第一阶段,即执行汇编流程。 本文简单了解一下,Linux内核启动的第二阶段:start_kernel函数,这是一个 C 函数。 本文续上一篇文章的学习,地址如下:…...
Disruptor:无锁队列设计的背后原理
简介 在高并发场景下,队列的速度和效率是关键。而Disruptor,一种高性能的并发队列,通过独特的设计,解决了传统队列在处理高并发时可能遇到的性能瓶颈。本文将深入分析Disruptor如何通过环形数组结构、元素位置定位以及无锁设计&a…...
网络编程-UDP协议(发送数据和接收数据)
需要了解TCP协议的,可以看往期文章 https://blog.csdn.net/weixin_43860634/article/details/133274701 TCP/IP参考模型 通过此图,可以了解UDP所在哪一层级中 代码案例 发送数据 package com.hidata.devops.paas.udp;import java.io.IOException; …...
AI绘画普及课【一】绘画入门
文章目录 一、AI 绘画入门1、Stable Diffusion VS. MidJourney2、Stable Diffusion 介绍3、Stable Diffusion 环境搭建4、文生图与图生图 一、AI 绘画入门 1、Stable Diffusion VS. MidJourney Midjourney 优点: 操作简单、出图绚丽多彩 缺点: 订阅付费充钱 内容有限制&a…...
Selenium和Requests搭配使用
Selenium和Requests搭配使用 前要1. CDP2. 通过requests控制浏览器2. 1 代码一2. 2 代码2 3. 通过selenium获取cookie, requests携带cookie请求 前要 之前有提过, 用selenium控制本地浏览器, 提高拟人化,但是效率比较低,今天说一种selenium和requests搭配使用的方法 注意: 一定…...
【JDK 8-函数式编程】4.4 Supplier
一、Supplier 接口 二、实战 Stage 1: 创建 Student 类 Stage 2: 创建方法 Stage 3: 调用方法 Stage 4: 执行结果 一、Supplier 接口 供给型 接口: 无入参,有返回值(T : 出参类型) 调用方法: T get(); 用途: 如 无参的工厂方法&#x…...
后端大厂面试-16道面试题
1 java集合类有哪些? List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。 ArrayList是容量…...
产品经理认证(UCPM)备考心得
UCPM是联合国训练所CIFAL中心颁发的产品经理证书。如今,ESG是推动企业可持续发展的新潮流。UCPM作为一种可持续发展证书,为我们带来了一套先进科学、系统全面的产品管理模式,是产品管理领域公认的权威证书。那么,如何准备这张证书…...
E : A DS顺序表_删除有序表中的重复元素
Description 给定一个按升序排列的顺序表,请删除所有重复的元素,使得每个元素只出现一次,并输出处理后的顺序表。 Input 第一行输入t,表示有t个测试样例。 第二行起,每一行首先输入n,表示有n个元素&…...
前端教程-vite
官网 Vite中文网 视频教程 Vite世界指南(带你从0到1深入学习 vite)...
Java笔记三
包机制: 为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。 包语句的语法格式为:pack pkg1[. pkg2[. pkg3...]]; 般利用公司域名倒置作为包名;如com.baidu.com,如图 导包: 为了能够…...
ElementUI之首页导航与左侧菜单
目录 一、Mock 1.1 什么是Mock.js 1.2 安装与配置 1.2.1 安装mock.js 1.2.2 引入mock.js 1.3 mock.js使用 1.3.1 定义测试数据文件 1.3.2 mock拦截Ajax请求 1.3.3 界面代码优化 二、总线 2.1 定义 2.2 类型分类 2.3 前期准备 2.4 配置组件与路由关系 2.4.1 配置…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
