链地址法的基本思想是:将全部哈希地址为i 的元素构成一个称为同义词链的链表,并将链表的头指针存在哈希表的第i个单元中。因而查找、插入和删除主要在同义词链中进行。
该散列方法首先对关键码集合用某一个散列函数计算它们的存放位置。
若设散列表地址空间的全部位置是从0到m-1,则关键码集合中的全部关键码被划分为m个子集,具有同样地址的关键码归于同一子集。我们称同一子集中的关键码互为同义词。
每个子集称为一个桶。
通常各个桶中的表项通过一个链表链接起来,称之为同义词子表。全部桶号同样的表项都链接在同一个同义词子表中,各链表的表头结点组成一个向量。
进一步的分析:
1、通常,每个桶中的同义词子表都非常短。设有n个关键码通过某一个散列函数,存放到散列表中的 m 个桶中。那么每个桶中的同
义词子表的平均长度为 n / m。这样。以搜索平均长度为 n / m 的同义词子表取代了搜索长度为 n 的顺序表,搜索速度快得多(O(1))。
2、时间复杂度分析:应用链地址法处理溢出,须要增设链接指针,似乎添加了存储开销。
其实,因为开地址法必须保持大量的空暇空间以确保搜索
效率,如二次探查法要求装填因子。(a = n / m)而表项所占空间又比指针大得多,所以使用链地址法反而比开地址法节省存储空间。
以下给出链地址法的实现,包含建立哈希表,释放哈希表。在哈希表中依据key查找一项,依据key 插入一项,依据key 删除一项等。
使用双向链表实现。
hash.h
#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); /*查找keyword*/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);#endifhash.c
#include "hash.h"#include測试main.c#include "common.h"typedef struct hash_node{ void *key; //随意类型的keyword 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);}
#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结果: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 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 *,所以支持各种类型的keyword,比如測试中的字符串和整型。