【数据结构】散列表(哈希表)的学习知识总结

news2024/12/23 20:31:31

目录

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、冲突(碰撞)

        散列表的冲突指的是将两个或多个不同的键映射到了散列表的同一个位置上。这种情况称为冲突,因为两个不同的键无法在同一个位置上存储。

        散列表的冲突可以通过散列函数的优化和冲突解决方法来减少或避免。以下是几种常见的冲突解决方法:

  1. 链接法 (Chaining):将相同位置上的键值对通过链表等数据结构链接在一起。
  2. 开放地址法 (Open Addressing):在发生冲突时,按照一定规则去寻找数组内的下一个空闲位置存储。
  3. 双散列法 (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。

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电缆直埋、电缆沟、电缆井大样图

一、图纸下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1_SUnhFHMUY8Q_kkhgzscDQ?pwd8888 提取码&#xff1a;8888 二、部分图纸预览

GoogLeNet网络

目录 1. 创新点 1.1 引入Inception结构 1.2 11卷积降维 1.3 两个辅助分类器 1.4 丢弃全连接层&#xff0c;使用平均池化层 2. 网络结构 3. 知识点 3.1 torch.cat 3.2 关于self.training 3.3 关于load_state_dict中的strict 4. 代码 4.1 model.py 4.2 train.py …

MQ - 30 基础功能:死信队列的设计

文章目录 导图概述什么是死信队列死信队列实现的技术方案死信队列的存储目标死信队列的方案设计生产死信队列消费死信队列Broker 的死信队列主流消息队列的死信功能RocketMQRabbitMQ总结导图 概述 在日常业务的消费数据过程中,如果遇到数据无法被正确处理,就需要先手动把消息…

202309解决新版Animate Diff报错问题

在安装最新版的Animate Diff的时候发现报错了 ImportError: cannot import name images_tensor_to_samples from modules.sd_samplers_common (W:\A1111\stable-diffusion-webui\modules\sd_samplers_common.py)原因很简单&#xff0c;Animate diff已经适配了新版webui1.6 所以…

基于LQR算法的一阶倒立摆控制

1. 一阶倒立摆建模 2. 数学模型 倒立摆的受力分析网上有很多&#xff0c;这里就不再叙述。直接放线性化后的方程&#xff1a; F (Mm)x″-mLφ″ (ImL)φ″ mLx″ mgLφ&#xff08;F为外力&#xff0c;x为物块位移&#xff0c;M&#xff0c;m为物块和摆杆的质量&#xff0c;…

计算机是怎么跑起来的(2)?程序如何驱动硬件工作的?

上文计算机是怎么跑起来的?从零开始手动组装微型计算机我们说了&#xff0c;如何手动从来组装一台计算机&#xff0c;那组装完后的计算机上是怎么跑起来程序的呢&#xff1f;程序是如何驱动硬件工作的&#xff1f; 前面我们通过DMA将代码输入到内存的指定位置&#xff0c;然后…

JavaWeb 学习

1. 基本概念 1.1 Web web&#xff1a;网络&#xff0c;网页 静态 web html&#xff0c;css提供给所有人看的数据始终不会变化 动态 web 淘宝提供给每个人看的数据会有所不同技术栈&#xff1a;Servlet/JSP&#xff0c;ASP&#xff0c;PHP Java 中&#xff0c;动态 web 资…

如何通过优化Read-Retry机制降低SSD读延迟?

近日,小编发现发表于2021论文中,有关于优化Read-Retry机制降低SSD读延迟的研究,小编这里给大家分享一下这篇论文的核心的思路,感兴趣的同学可以,可以在【存储随笔】VX公号后台回复“Optimizing Read-Retry”获取下载链接。 本文中主要基于Charge Trap NAND架构分析。NAND基…

[C++网络协议] 优于select的epoll

1.epoll函数为什么优于select函数 select函数的缺点&#xff1a; 调用select函数后&#xff0c;要针对所有文件描述符进行循环处理。每次调用select函数&#xff0c;都需要向该函数传递监视对象信息。 对于缺点2&#xff0c;是提高性能的最大障碍。因为&#xff0c;套接字是…

python+vue驾校驾驶理论考试模拟系统

管理员的主要功能有&#xff1a; 1.管理员输入账户登陆后台 2.个人中心&#xff1a;管理员修改密码和账户信息 3.用户管理&#xff1a;管理员可以对用户信息进行添加&#xff0c;修改&#xff0c;删除&#xff0c;查询 4.添加选择题&#xff1a;管理员可以添加选择题目&#xf…

读高性能MySQL(第4版)笔记15_备份与恢复(下)

1. 二进制日志 1.1. 服务器的二进制日志是需要备份的最重要元素之一 1.2. 对于基于时间点的恢复是必需的&#xff0c;并且通常比数据要小&#xff0c;所以更容易被进行频繁的备份 1.3. 如果有某个时间点的数据备份和所有从那时以后的二进制日志&#xff0c;就可以重放从上次…

MySQL数据库入门到精通8--进阶篇( MySQL管理)

7. MySQL管理 7.1 系统数据库 Mysql数据库安装完成后&#xff0c;自带了一下四个数据库&#xff0c;具体作用如下&#xff1a; 7.2 常用工具 7.2.1 mysql 该mysql不是指mysql服务&#xff0c;而是指mysql的客户端工具。 语法 &#xff1a; mysql [options] [database] 选…

NumPy数值计算

1、Numpy概念 1.1Numpy是什么&#xff1f; Numpy是&#xff08;Numerical Python的缩写&#xff09;&#xff1a;一个开源的Python科学计算库使用NumPy可以方便的使数组、矩阵进行计算包含线性代数、傅里叶变换、随机数生成等大量函数 1.2为什么使用Numpy 对于同样的数值计…

Windows打开:控制面板\网络和 Internet\网络连接 显示空白怎么办?

Windows打开&#xff1a;控制面板\网络和 Internet\网络连接 显示空白怎么办&#xff1f; 最近有用户反馈遇到这个问题&#xff0c;问题产生原因&#xff1a;在卸载某个软件的时候&#xff0c;系统提示需要重新启动计算机&#xff0c;但是&#xff0c;启动之后&#xff0c;就…

前端项目练习(练习-002-NodeJS项目初始化)

首先&#xff0c;创建一个web-002项目&#xff0c;内容和web-001一样。 下一步&#xff0c;规范一下项目结构&#xff0c;将html&#xff0c;js&#xff0c;css三个文件放到 src/view目录下面&#xff1a; 由于html引入css和js时&#xff0c;使用的是相对路径&#xff0c;所以…

从聚水潭到金蝶云星空通过接口配置打通数据

从聚水潭到金蝶云星空通过接口配置打通数据 数据源平台:聚水潭 聚水潭SaaSERP于2014年4月上线&#xff0c;目前累计超过2.5万商家注册使用&#xff0c;成为淘宝应用服务市场ERP类目商家数和商家月订单增速最快的ERP。2014年及2015年“双十一”当天&#xff0c;聚水潭SaaSERP平稳…

python的讲解和总结V2.0

python的讲解和总结V2.0 一、Python的历史二、Python的特点三、Python的语法四、Python的应用领域五、Python的优缺点优点a. 简单易学&#xff1a;b. 可读性强&#xff1a;c. 库和框架丰富&#xff1a;d. 可移植性强&#xff1a;e. 开源&#xff1a; 缺点a. 运行速度较慢&#…

C 语言基础题:PTA L1-027 出租

下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救声&#xff0c;急问这个怎么破。其实这段代码很简单&#xff0c;index数组就是arr数组的下标&#xff0c;index[0]2 对应 arr[2]1&#xff0c;index[1]0 对应 arr[0]8&#xff0c;index[2]3 对应 arr[3]0&…

springboot 捕获数据库唯一索引导致的异常

在一些业务场景中,需要保证数据的唯一性,一般情况下,我们会先到数据库中去查询是否存在,再去判断是否可以插入新的数据.如果是在高并发的情况下,可能还是会出现重复的情况.这时候可能就需要用到锁.也可以在数据库中设置唯一索引. 如果使用唯一索引,在插入相同数据的情况下会抛出…

【postgresql】ERROR: column “xxxx.id“ must appear in the GROUP BY

org.postgresql.util.PSQLException: ERROR: column "xxx.id" must appear in the GROUP BY clause or be used in an aggregate function 错误&#xff1a;列“XXXX.id”必须出现在GROUP BY子句中或在聚合函数中使用 在mysql中是正常使用的&#xff0c;在postgre…