1、C實現哈希表的鏈地址法
(1)什麼是鏈地址法?
鏈地址法是用於處理哈希衝突的一種方法(什麼是哈希衝突,以及解決哈希衝突的開放地址法)
在鏈地址法的描述中,哈希表的結構更加複雜一點,但是查詢和存儲的性能都有進一步提升,尤其是面對超大量的數據時,將哈希表的每一個位置設計成鏈表,無疑加快了查詢速度,而且理解上也更加容易。
在開放地址法中,若是哈希表的位置已經被佔用,則向後尋找一個爲空的位置保存數據,在鏈地址法中,若是哈希表的位置被佔用也不需要向後尋找,因爲每個位置都是一個鏈表的節點,我們只需要將新節點插入就行(程序中採用的是尾插法)
2、頭文件
#pragma once
typedef struct HashTable HashTable;
HashTable* hash_table_new();
void hash_table_delete(HashTable *ht);
//大師的加強版,key用字符串類型表示更貼合實際應用,value給void*則可傳任意類型,同時增加value的釋放函數指針
int hash_table_put2(HashTable *ht, char *key, void* value, void(*free_value)(void*));
void* hash_table_get(HashTable *ht, char *key);
void hash_table_rm(HashTable *ht, char *key);
3、函數實現
#include "HashTable2.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define MAX_TABLE_SIZE 100
//哈希表中的鏈表的一個結點結構體
typedef struct kv
{
struct kv *next;
char *key;
void *value;
void(*free_value)(void*);
}KV;
struct HashTable
{
KV **table; //這裏就是別人高手的思維,其實這麼寫會好理解一點
//struct kv *table[MAX_TABLE_SIZE]
};
static void init_kv(KV *kv)
{
kv->key = NULL;
kv->next = NULL;
kv->value = NULL;
kv->free_value = NULL;
}
static void free_kv(KV *kv)
{
if (kv){
if (kv->free_value){
//由於value結構不同,其應該有對應的free函數,且在相應free函數中應將指針置爲NULL
kv->free_value(kv->value);
}
free(kv->key);
kv->key = NULL;
free(kv);
}
}
//這裏是號稱最快的哈希函數 TIME33 算法
static unsigned int hash_33(char *key)
{
unsigned int hash = 0;
while (*key)
{
hash = (hash << 5) + hash + *key++;//左移5位相當於*32,再+hash則相當於*33;
}
return hash;
}
HashTable* hast_table_new()
{
HashTable *ht = (HashTable *)malloc(sizeof(HashTable));
if (ht == NULL) {
hash_table_delete(ht);
return NULL;
}
//上面創建了一個指向哈希表的指針,但是關鍵還是哈希表內的用於指向鏈表的指針(地址)
ht->table = (KV **)malloc(sizeof(KV*) * MAX_TABLE_SIZE);
if (ht->table == NULL) {
hash_table_delete(ht);
return NULL;
}
memset(ht->table, 0, sizeof(KV*)*MAX_TABLE_SIZE);
return ht;
}
void hash_table_delete(HashTable *ht)
{
if (ht) {
if (ht->table) {
for (int i = 0; i < MAX_TABLE_SIZE; i++) {
KV *p = ht->table[i];
KV *q = NULL;
//將鏈表的節點一個一個free掉
while (p)
{
q = p->next;
free_kv(p);
p = q;
}
}
free(ht->table);
ht->table = NULL;
}
free(ht);
}
}
int hash_table_put2(HashTable *ht, char *key, void *value, void(*free_value)(void*))
{
int i = hash_33(key) % MAX_TABLE_SIZE;
KV *p = ht->table[i];
KV *prep = p;
while (p)
{
//key相等,則修改value的值,修改前先將原value內存釋放,也就是說key是唯一的
if (strcmp(p->key, key) == 0) {
if (p->free_value) {
p->free_value(p->value);
}
p->value = value;
p->free_value = free_value;
}
//prep指向最後一個節點
prep = p;
p = p->next;
}
//跳出while循環表示當前節點的next爲NULL,想保存數據需要建立新的鏈表節點
if (p == NULL)
{
char *kstr = (char *)malloc(strlen(key) + 1);
if (kstr == NULL) {
return -1;
}
KV *kv = (KV *)malloc(sizeof(KV));
if (kv == NULL) {
free(kstr);
kstr = NULL;
return -1;
}
init_kv(kv);
//kv->next = NULL;
strcpy(kstr, key);
kv->key = kstr;
kv->value = value;
kv->free_value = free_value;
if (prep == NULL) {
ht->table[i] = kv; //哈希表的這個位置第一次來數據節點
}
else
{
prep->next = kv;
}
}
return 0;
}
void * hash_table_get(HashTable *ht, char *key)
{
int i = hash_33(key) % MAX_TABLE_SIZE;
KV *p = ht->table[i];
while (p)
{
if (strcmp(key, p->key) == 0) {
return p->value;
}
p = p->next;
}
return NULL;
}
void hash_table_rm(HashTable *ht, char *key)
{
int i = hash_33(key) % MAX_TABLE_SIZE;
KV *p = ht->table[i];
KV *prep = p;
while (p)
{
if (strcmp(key, p->key) == 0) {
free_kv(p);
//要知道,prep指向的就是p的上一個節點,p == prep表明此鏈表只有一個節點
if (p == prep) {
ht->table[i] = NULL;
}
else
{
//相當於刪除了鏈表中的某個節點
prep->next = p->next;
}
}
prep = p;
p = p->next;
}
}
4、測試
暫時沒有去測試