链地址法的基本思想是:将所有哈希地址为i 的元素构成一个称为同义词链的链表,并将链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。
该散列方法首先对关键码集合用某一个散列函数计算它们的存放位置。
若设散列表地址空间的所有位置是从0到m-1,则关键码集合中的所有关键码被划分为m个子集,具有相同地址的关键码归于同一子集。我们称同一子集中的关键码互为同义词。每一个子集称为一个桶。
通常各个桶中的表项通过一个链表链接起来,称之为同义词子表。所有桶号相同的表项都链接在同一个同义词子表中,各链表的表头结点组成一个向量。
进一步的分析:
1、通常,每个桶中的同义词子表都很短,设有n个关键码通过某一个散列函数,存放到散列表中的 m 个桶中。那么每一个桶中的同
义词子表的平均长度为 n / m。这样,以搜索平均长度为 n / m 的同义词子表代替了搜索长度为 n 的顺序表,搜索速度快得多(O(1))。
2、时间复杂度分析:应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上,由于开地址法必须保持大量的空闲空间以确保搜索
效率,如二次探查法要求装填因子,(a = n / m)而表项所占空间又比指针大得多,所以使用链地址法反而比开地址法节省存储空间。
下面给出链地址法的实现,包括建立哈希表,释放哈希表,在哈希表中根据key查找一项,根据key 插入一项,根据key 删除一项等。
使用双向链表实现。
#ifndef _HASH_H_
#define _HASH_H_
//双向链表的哈希
typedef struct hash hash_t;
typedef unsigned int (*hashfunc_t)(unsigned int,void*);//哈希函数
/*建立哈希表*/
hash_t* hash_alloc(unsigned int buckets,hashfunc_t hash_func);
/*查找关键字*/
void* hash_lookup_entry(hash_t *hash,void* key,unsigned int key_size);
/*哈希表中添加记录*/
void hash_add_entry(hash_t *hash,void *key,unsigned int key_size,
void *value,unsigned int value_size);
/*释放哈希表*/
void hash_free_entry(hash_t *hash,void *key,unsigned int key_size);
#endif
hash.c
#include "hash.h"
#include <assert.h>
#include "common.h"
typedef struct hash_node
{
void *key; //任意类型的关键字
void *value;//信息
struct hash_node *prev;//前驱指针
struct hash_node *next;//后继指针
}hash_node_t;
struct hash
{
unsigned int buckets;
hashfunc_t hash_func;
hash_node_t **nodes;
};
//得到桶号
hash_node_t **hash_get_bucket(hash_t *hash,void *key)
{
unsigned int bucket=hash->hash_func(hash->buckets,key);
if(bucket >= hash->buckets)
{
fprintf(stderr,"bad buckets lookup\n");
exit(EXIT_FAILURE);
}
return &(hash->nodes[bucket]);
}
hash_node_t *hash_get_node_by_key(hash_t *hash,void *key,unsigned int key_size)
{
hash_node_t **bucket=hash_get_bucket(hash,key);
hash_node_t *node=*bucket;
if(node==NULL)
return NULL;
while(node!=NULL && memcmp(node->key,key,key_size)!=0)
{
node=node->next;
}
return node;
}
hash_t* hash_alloc(unsigned int buckets,hashfunc_t hash_func)
{
hash_t *hash=(hash_t *)malloc(sizeof(hash_t));
hash->buckets=buckets;
hash->hash_func=hash_func;
int size=buckets * sizeof(hash_node_t*);
hash->nodes=(hash_node_t**)malloc(size);
memset(hash->nodes,0,size);
return hash;
}
void* hash_lookup_entry(hash_t *hash,void* key,unsigned int key_size)
{
hash_node_t *node=hash_get_node_by_key(hash,key,key_size);
if(node==NULL)
return NULL;
return node->value;
}
void hash_add_entry(hash_t *hash,void *key,unsigned int key_size,
void *value,unsigned int value_size)
{
//已经存在
if(hash_lookup_entry(hash,key,key_size))
{
fprintf(stderr,"duplicate hash key\n");
return ;
}
hash_node_t *node=malloc(sizeof(hash_node_t));
node->prev=NULL;
node->next=NULL;
node->key=malloc(key_size);
memcpy(node->key,key,key_size);
node->value=malloc(value_size);
memcpy(node->value,value,value_size);
hash_node_t **bucket=hash_get_bucket(hash,key);
if(*bucket == NULL)
{
*bucket=node;
}
else
{
//将新的节点插入到头部
node->next=*bucket;
(*bucket)->prev=node;
*bucket=node;
}
}
//删除操作
void hash_free_entry(hash_t *hash,void *key,unsigned int key_size)
{
hash_node_t *node=hash_get_node_by_key(hash,key,key_size);
if(node==NULL)
return ;
free(node->key);
free(node->value);
if(node->prev)
node->prev->next=node->next;
else
{
hash_node_t **bucket=hash_get_bucket(hash,key);
*bucket=node->next;
}
if(node->next)
node->next->prev=node->prev;
free(node);
}
测试main.c
#include "hash.h"
#include "common.h"
typedef struct stu
{
char sno[5];
char name[32];
int age;
}stu_t;
typedef struct stu2
{
int sno;
char name[32];
int age;
}stu2_t;
//字符串哈希函数
unsigned int hash_str(unsigned int buckets,void *key)
{
char *sno=(char *)key;
unsigned int index=0;
while(*sno)
{
index=*sno+4*index;
sno++;
}
return index % buckets;
}
unsigned int hash_int(unsigned int buckets,void *key)
{
int *sno=(int *)key;
return (*sno) % buckets;
}
int main()
{
/*
stu_t stu_arr[]=
{
{"1234","AAAA",20},
{"4568","AAAA",23},
{"6729","AAAA",19}
};
hash_t *hash=hash_alloc(256,hash_str);
int size=sizeof(stu_arr)/sizeof(stu_arr[0]);
int i;
for(i=0;i<size;i++)
{//插入地址,关键码,关键码的长度,数据项
hash_add_entry(hash,stu_arr[i].sno,strlen(stu_arr[i].sno),
&stu_arr[i],sizeof(stu_arr[i]));
}
//查找
stu_t *s=(stu_t *)hash_lookup_entry(hash,"4568",strlen("4568"));
if(s)
{
printf("%s %s %d\n",s->sno,s->name,s->age);
}
else
printf("Not found\n");
hash_free_entry(hash,"1234",strlen("1234"));
s=(stu_t *)hash_lookup_entry(hash,"1234",strlen("1234"));
if(s)
{
printf("%s %s %d\n",s->sno,s->name,s->age);
}
else
printf("Not found\n");
*/
stu2_t stu_arr[]=
{
{1234,"AAAA",20},
{4568,"AAAA",23},
{6729,"AAAA",19}
};
hash_t *hash=hash_alloc(256,hash_int);
int size=sizeof(stu_arr)/sizeof(stu_arr[0]);
int i;
for(i=0;i<size;i++)
{//插入地址,关键码,关键码的长度,数据项
hash_add_entry(hash,&(stu_arr[i].sno),sizeof(stu_arr[i].sno),
&stu_arr[i],sizeof(stu_arr[i]));
}
//查找
int sno=4568;
stu2_t *s=(stu2_t *)hash_lookup_entry(hash,&sno,sizeof(sno));
if(s)
{
printf("%d %s %d\n",s->sno,s->name,s->age);
}
else
printf("Not found\n");
sno=1234;
hash_free_entry(hash,&sno,sizeof(sno));
s=(stu2_t *)hash_lookup_entry(hash,&sno,sizeof(sno));
if(s)
{
printf("%d %s %d\n",s->sno,s->name,s->age);
}
else
printf("Not found\n");
return 0;
}
结果:
可以看到因为我们封装的时候使用的是void *,所以支持各种类型的关键字,例如测试中的字符串和整型。