【数据结构】散列表(哈希表)的学习知识总结
目录
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 配置…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...