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

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...