哈希(C语言)
本文介绍一个常用的算法——哈希算法,哈希算法依赖于哈希表来实现,首先我会介绍一下哈希表,并在哈希表的基础上衍生出哈希算法。
1.数据结构——哈希表
哈希表(hash table),又称散列表,它通过建立键key 与值value 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键key ,则可以在𝑂(1) 时间内获取对应的值value 。
除哈希表外,数组和链表也可以实现查询功能,它们的效率对比如表所示。
- 添加元素:仅需将元素添加至数组(链表)的尾部即可,使用𝑂(1) 时间。
- 查询元素:由于数组(链表)是乱序的,因此需要遍历其中的所有元素,使用𝑂(𝑛) 时间。
- 删除元素:需要先查询到元素,再从数组(链表)中删除,使用𝑂(𝑛) 时间。
数组 | 链表 | 哈希表 | |
---|---|---|---|
查找元素 | O(n) | O(n) | O(1) |
添加元素 | O(1) | O(1) | O(1) |
删除元素 | O(n) | O(1) | O(1) |
观察发现,在哈希表中进行增删查改的时间复杂度都是𝑂(1) ,非常高效。 |
1.1哈希表的工作原理
我们先考虑最简单的情况,仅用一个数组来实现哈希表。在哈希表中,我们将数组中的每个空位称为桶(bucket),每个桶可存储一个键值对。因此,查询操作就是找到key 对应的桶,并在桶中获取value 。那么,如何基于key 定位对应的桶呢?这是通过哈希函数(hash function)实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有key ,输出空间是所有桶(数组索引)。换句话说,输入一个key ,我们可以通过哈希函数得到该key 对应的键值对在数组中的存储位置。
输入一个key ,哈希函数的计算过程分为以下两步。
- 通过某种哈希算法hash() 计算得到哈希值。
- 将哈希值对桶数量(数组长度)capacity 取模,从而获取该key 对应的数组索引index 。
1.2哈希表的代码实现
typedef struct {
int key;
char *val;
} Pair;
/* 基于数组实现的哈希表*/
typedef struct {
Pair *buckets[MAX_SIZE];
} ArrayHashMap;
/* 构造函数*/
ArrayHashMap *newArrayHashMap() {
ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));
for (int i=0; i < MAX_SIZE; i++) {
hmap->buckets[i] = NULL;
}
return hmap;
}
/* 析构函数*/
void delArrayHashMap(ArrayHashMap *hmap) {
for (int i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
free(hmap->buckets[i]->val);
free(hmap->buckets[i]);
}
}
free(hmap);
}
/* 添加操作*/
void put(ArrayHashMap *hmap, const int key, const char *val) {
Pair *Pair = malloc(sizeof(Pair));
Pair->key = key;
Pair->val = malloc(strlen(val) + 1);
strcpy(Pair->val, val);
int index = hashFunc(key);
hmap->buckets[index] = Pair;
}
/* 删除操作*/
void removeItem(ArrayHashMap *hmap, const int key) {
int index = hashFunc(key);
free(hmap->buckets[index]->val);
free(hmap->buckets[index]);
hmap->buckets[index] = NULL;
}
/* 获取所有键值对*/
void pairSet(ArrayHashMap *hmap, MapSet *set) {
Pair *entries;
int i = 0, index = 0;
int total = 0;
/* 统计有效键值对数量*/
for (i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
total++;
}
}
entries = malloc(sizeof(Pair) * total);
for (i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
entries[index].key = hmap->buckets[i]->key;
entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1);
strcpy(entries[index].val, hmap->buckets[i]->val);
index++;
}
}
set->set = entries;
set->len = total;
}
/* 获取所有键*/
void keySet(ArrayHashMap *hmap, MapSet *set) {
int *keys;
int i = 0, index = 0;
int total = 0;
/* 统计有效键值对数量*/
for (i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
total++;
}
}
keys = malloc(total * sizeof(int));
for (i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
keys[index] = hmap->buckets[i]->key;
index++;
}
}
set->set = keys;
set->len = total;
}
/* 获取所有值*/
void valueSet(ArrayHashMap *hmap, MapSet *set) {
char **vals;
int i = 0, index = 0;
int total = 0;
/* 统计有效键值对数量*/
for (i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
total++;
}
}
vals = malloc(total * sizeof(char *));
for (i = 0; i < MAX_SIZE; i++) {
if (hmap->buckets[i] != NULL) {
vals[index] = hmap->buckets[i]->val;
index++;
}
}
set->set = vals;
set->len = total;
}
/* 打印哈希表*/
void print(ArrayHashMap *hmap) {
int i;
MapSet set;
pairSet(hmap, &set);
Pair *entries = (Pair *)set.set;
for (i = 0; i < set.len; i++) {
printf("%d -> %s\n", entries[i].key, entries[i].val);
}
free(set.set);
}
2.哈希算法
2.1 哈希算法介绍
哈希算法是一种将任意长度的数据转换为固定长度的输出的算法。这种输出通常被称为哈希值或消息摘要。哈希算法具有以下特性:
- 确定性:相同的输入总是产生相同的输出。
- 快速计算:计算哈希值的速度通常很快。
- 雪崩效应:输入的微小变化会导致输出的显著变化。
- 抗碰撞性:找到两个不同的输入产生相同输出的情况非常困难。
常见的哈希算法
- MD5
- SHA-1
- SHA-256
- SHA-3
2.2C语言实现示例
#include <stdio.h>
#include <string.h>
#define HASH_TABLE_SIZE 256
// 一个简单的哈希函数
unsigned int simple_hash(const unsigned char *str) {
unsigned int hash = 0;
for (int i = 0; str[i] != '\0'; i++) {
hash += str[i]; // 这里使用了字符的ASCII值
}
return hash % HASH_TABLE_SIZE; // 取模运算以适应哈希表大小
}
int main() {
const char *input = "Hello, World!";
unsigned int hash_value = simple_hash((const unsigned char *)input);
printf("The hash value of '%s' is: %u\n", input, hash_value);
return 0;
}