哈希表
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INIT_CAP 8
#define LOAD_FACTOR 0.75
typedef struct Entry {
char *key; // 拥有所有权(strdup 复制)
int value;
struct Entry *next; // 链地址法
} Entry;
typedef struct {
Entry **buckets;
size_t cap; // 桶数量
size_t size; // 键值对数量
} Dict;
/* ---------- 哈希函数:djb2 ---------- */
static unsigned long hash_str(const char *s) {
unsigned long h = 5381;
int c;
while ((c = *s++))
h = ((h << 5) + h) + c; // h * 33 + c
return h;
}
/* ---------- 创建/销毁 ---------- */
Dict *dict_new(void) {
Dict *d = malloc(sizeof(Dict));
if (!d) return NULL;
d->cap = INIT_CAP;
d->size = 0;
d->buckets = calloc(d->cap, sizeof(Entry *));
if (!d->buckets) { free(d); return NULL; }
return d;
}
void dict_free(Dict *d) {
if (!d) return;
for (size_t i = 0; i < d->cap; i++) {
Entry *e = d->buckets[i];
while (e) {
Entry *next = e->next;
free(e->key);
free(e);
e = next;
}
}
free(d->buckets);
free(d);
}
/* ---------- 扩容 ---------- */
static void dict_resize(Dict *d, size_t new_cap) {
Entry **new_buckets = calloc(new_cap, sizeof(Entry *));
if (!new_buckets) return; // 扩容失败则保持原样
for (size_t i = 0; i < d->cap; i++) {
Entry *e = d->buckets[i];
while (e) {
Entry *next = e->next;
size_t idx = hash_str(e->key) % new_cap;
e->next = new_buckets[idx];
new_buckets[idx] = e;
e = next;
}
}
free(d->buckets);
d->buckets = new_buckets;
d->cap = new_cap;
}
/* ---------- 插入/更新 ---------- */
void dict_put(Dict *d, const char *key, int value) {
size_t idx = hash_str(key) % d->cap;
// 已存在则更新
for (Entry *e = d->buckets[idx]; e; e = e->next) {
if (strcmp(e->key, key) == 0) {
e->value = value;
return;
}
}
// 头插新节点
Entry *e = malloc(sizeof(Entry));
if (!e) return;
e->key = strdup(key); // 复制键(POSIX;C23 也标准化了)
if (!e->key) { free(e); return; }
e->value = value;
e->next = d->buckets[idx];
d->buckets[idx] = e;
d->size++;
// 负载因子检查
if ((double)d->size / d->cap > LOAD_FACTOR)
dict_resize(d, d->cap * 2);
}
/* ---------- 查找 ---------- */
int dict_get(Dict *d, const char *key, int *out) {
size_t idx = hash_str(key) % d->cap;
for (Entry *e = d->buckets[idx]; e; e = e->next) {
if (strcmp(e->key, key) == 0) {
*out = e->value;
return 1; // 找到
}
}
return 0; // 没找到
}
/* ---------- 删除 ---------- */
int dict_remove(Dict *d, const char *key) {
size_t idx = hash_str(key) % d->cap;
Entry **pp = &d->buckets[idx];
while (*pp) {
Entry *e = *pp;
if (strcmp(e->key, key) == 0) {
*pp = e->next;
free(e->key);
free(e);
d->size--;
return 1;
}
pp = &e->next;
}
return 0;
}
/* ---------- 测试 ---------- */
int main(void) {
Dict *d = dict_new();
dict_put(d, "apple", 10);
dict_put(d, "banana", 20);
dict_put(d, "cherry", 30);
dict_put(d, "apple", 99); // 更新
int v;
if (dict_get(d, "apple", &v)) printf("apple = %d\n", v); // 99
if (dict_get(d, "banana", &v)) printf("banana = %d\n", v); // 20
if (!dict_get(d, "grape", &v)) printf("grape 不存在\n");
dict_remove(d, "banana");
if (!dict_get(d, "banana", &v)) printf("banana 已删除\n");
printf("size = %zu, cap = %zu\n", d->size, d->cap);
dict_free(d);
return 0;
}
malloc 只分配、不清零;calloc 分配并清零,还带乘法溢出检查。const char *s 表示"s 指向的字符不可通过 s 修改"。加它是为了:① 承诺函数只读不写;② 能接收字符串字面量和 const 变量(否则传 "apple" 会警告/报错);③ 编译期防止误改;④ 自文档化。
static + const char* 组合是 C 里工具函数的经典写法:私有 + 只读输入。
while ((c = *s++))
h = ((h << 5) + h) + c; // h * 33 + c
// 等价的"笨"写法,帮你理解
while (1) {
c = *s; // 读当前字符
s++; // 指针后移
if (c == 0) // 遇到 '\0'
break;
h = ((h << 5) + h) + c; // << 5 是左移 5 位,等价于 h * 2^5 = h * 32。
}
/*5381 是 djb2 的作者 Daniel J. Bernstein 选的魔数。选它的原因:
-
是质数
-
二进制
1010100000101,位模式"分散",减少碰撞 -
经验上分布效果好
没有严格数学证明,就是实践中表现优秀的经验值。*/
for (Entry *e = d->buckets[idx]; e; e = e->next)
等价于
Entry *e = d->buckets[idx];
while (e != NULL) {
// 循环体
e = e->next;
}
for 版的优点:
e 作用域仅限循环,不污染函数其余部分
三要素(初始化/条件/步进)一眼看全,遍历意图明确
浙公网安备 33010602011771号