数据结构 -- 哈希表
哈希表(Hash Table)核心概念
定义:一种支持高效存储与查找的数据结构,目标查找复杂度为 O(1)∼O(lgN) ,通过哈希函数映射数据到存储位置,平衡存储与查询效率。
核心要素:
- 哈希函数(Hash Fun):
- 作用:将输入的
key
(要存储的数据)转换为哈希表的下标,确定数据存储位置。 - 要求:计算快捷(降低时间开销)、地址分布均匀(减少冲突概率)。
- 示例(数字场景):常用求余运算(如
key % 哈希表长度
)。
- 作用:将输入的
- 哈希表(Hash Table):存储数据的连续空间,通过哈希函数映射的下标访问,是哈希逻辑的载体。
- 哈希函数(Hash Fun):
冲突(Collision):不同
key
经哈希函数计算得到相同下标(即fun(key1) == fun(key2)
),需通过探测策略解决。常见探测方式:探测策略 描述 示例探测序列(假设基础下标为 i
)线性探测 按顺序遍历后续下标 i+1, i+2, i+3, ...
(或循环回表首)二次探测 按 “平方级偏移” 遍历下标 i+1, i-1, i+2, i-2, ...
(避免连续扎堆)随机探测 用随机数生成偏移量 i + rand()
(依赖随机算法,分布更灵活)
关键逻辑梳理
- 存储流程:
数据 → 哈希函数计算下标 → 若下标位置空闲,直接存储;若冲突,按探测策略找新位置存储。 - 查找流程:
目标key
→ 哈希函数计算下标 → 比对数据,若匹配则命中;若不匹配,按探测策略遍历查找(或判定不存在)。 - 设计权衡:
- 哈希函数越简单、分布越均匀,存储 / 查询效率越高,但冲突仍难完全避免。
探测策略影响冲突解决效率:线性探测实现简单但易引发 “聚集”;二次 / 随机探测可分散冲突,却增加实现复杂度。
实现函数
#include <stdio.h> // 标准输入输出库,用于printf等函数
#include <stdlib.h> // 标准库,用于malloc、free等内存管理函数
#include <string.h> // 字符串处理库,用于memcpy等内存拷贝函数// 定义哈希表存储的数据类型为int
typedef int DATATYPE;// 哈希表结构体定义
typedef struct
{DATATYPE* head; // 指向哈希表数组的首地址int tlen; // 哈希表的长度(数组大小)
} HS_Table;/*** @brief 创建哈希表* * @param len 哈希表的长度* @return HS_Table* 成功返回哈希表指针,失败返回NULL*/
HS_Table* CreateHsTable(int len)
{// 为哈希表结构体分配内存HS_Table* hs = malloc(sizeof(HS_Table));if (NULL == hs) // 内存分配失败检查{perror("CreateHsTable malloc1"); // 打印错误信息return NULL;}// 为哈希表数组分配内存hs->head = malloc(sizeof(DATATYPE) * len);if (NULL == hs->head) // 内存分配失败检查{perror("CreateHsTable malloc2"); // 打印错误信息free(hs); // 释放已分配的结构体内存,防止内存泄漏return NULL;}hs->tlen = len; // 设置哈希表长度// 初始化哈希表,将所有元素设为-1(表示该位置为空)int i = 0;for (i = 0; i < len; i++){hs->head[i] = -1;}return hs; // 返回创建好的哈希表
}/*** @brief 哈希函数:根据需要存储的数据计算下标* 设计原则:1.计算过程尽可能简单 2.使下标均匀分布* * @param h 哈希表指针* @param data 需要计算下标的数据* @return int 计算得到的下标*/
int HS_Fun(HS_Table* h, DATATYPE* data)
{// 使用取模运算作为哈希函数,data对哈希表长度取模return *data % h->tlen;
}/*** @brief 向哈希表中插入数据* * @param h 哈希表指针* @param data 要插入的数据* @return int 0表示成功*/
int HS_Insert(HS_Table* h, DATATYPE* data)
{// 计算数据应插入的初始位置int ind = HS_Fun(h, data);// 处理哈希冲突:当计算的位置已有数据时,使用线性探测法寻找下一个空位置// 线性探测法:如果当前位置被占用,则检查下一个位置,直到找到空位置while (-1 != h->head[ind]) // -1表示位置为空{printf("data:%d ind:%d 发生冲突\n", *data, ind); // 打印冲突信息ind = (ind + 1) % h->tlen; // 计算下一个位置,循环到表的开始}// 将数据插入到找到的空位置memcpy(&h->head[ind], data, sizeof(DATATYPE));return 0; // 插入成功
}/*** @brief 在哈希表中查找数据* * @param h 哈希表指针* @param data 要查找的数据* @return int 找到返回数据所在下标,未找到返回-1*/
int HS_Search(HS_Table* h, DATATYPE* data)
{// 计算数据可能存在的初始位置int ind = HS_Fun(h, data);int old_ind = ind; // 保存初始位置,用于判断是否遍历完整个表//由于哈希表是 “循环数组”(通过% h->tlen实现循环),需要一个 “起点标记” 来判断是否已经遍历了整个哈希表。old_ind就是这个起点,当后续查找绕回old_ind时,说明所有位置都已检查过// 查找数据,处理可能的哈希冲突while (*data != h->head[ind]) //// 若当前位置不是目标数据,继续查找{ind = (ind + 1) % h->tlen; // 检查下一个位置// 如果回到初始位置,说明整个表已遍历完毕且未找到数据if (ind == old_ind){return -1; // 未找到}}return ind; // 找到数据,返回下标
}// 哈希表销毁函数声明(未实现)
int HS_Destroy(HS_Table* h);/*** @brief 主函数:演示哈希表的创建、插入和查找操作*/
int main(int argc, char** argv)
{// 创建长度为12的哈希表HS_Table* hs = CreateHsTable(12);// 准备要插入的数据int array[12] = {12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34};// 将数据插入哈希表int i = 0;for (i = 0; i < 12; i++){HS_Insert(hs, &array[i]);}// 在哈希表中查找数据12int want_num = 12;int ret = HS_Search(hs, &want_num);if (-1 == ret){printf("未找到 %d\n", want_num);}else{printf("找到 %d,位于下标 %d\n", want_num, ret);}// 注意:此处缺少哈希表的销毁操作,实际应用中应调用HS_Destroy释放内存return 0;
}