本博客所有源代码:Github托管项目地址
链式哈希表介绍
链式哈希表从根本上来说是由一组链表构成。每个链表都可以看作一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。插入元素时,首先将其键传入一个哈希函数(此过程称为哈希键),函数通过散列的方式告知元素属于哪一个“桶”,然后再相应的链表头插入元素,直到我们发现我们想要查找的元素。
解决冲突的方法
1. 开放定址法
这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:
l 线性探测再散列
dii=1,2,3,…,m-1
这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
l 二次探测再散列
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
l 伪随机探测再散列
di=伪随机数序列。
具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。
例如,已知哈希表长度m=11,哈希函数为:H(key)= key % 11,则H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为69,则H(69)=3,与47冲突。如果用线性探测再散列处理冲突,下一个哈希地址为H1=(3 + 1)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 + 2)% 11 = 5,还是冲突,继续找下一个哈希地址为H3=(3 + 3)% 11 = 6,此时不再冲突,将69填入5号单元,参图8.26 (a)。如果用二次探测再散列处理冲突,下一个哈希地址为H1=(3 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时不再冲突,将69填入2号单元,参图8.26 (b)。如果用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,……..,则下一个哈希地址为H1=(3 + 2)% 11 = 5,仍然冲突,再找下一个哈希地址为H2=(3 + 5)% 11 = 8,此时不再冲突,将69填入8号单元。
2. 再哈希法
这种方法是同时构造多个不同的哈希函数:
Hi=RH1(key) i=1,2,…,k
当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
3. 链地址法
这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
4、建立公共溢出区
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
哈希表的构造方法
直接定址法
数字分析法
平方取中法
折叠法
除留余数法
随机数法
一个简单处理字符串的哈希函数hashpjw.c
/*hashpjw.c*/ unsigned int hashpjw(const void *key) { const char *ptr; unsigned int val; val = 0; ptr = key; while (*ptr != '\0'){ unsigned int tmp; val = (val << 4) +(*ptr); if (tmp = (val & 0xf0000000)){ val = val ^ (tmp >> 24); val = val ^ tmp; } ptr++; } return val%PRIME_TBLSIZ }链式哈希表头文件chtbl.h
/*chtbl.h*/ #ifndef CHTBL_H #define CHTBL_H #include <stdlib.h> /*单链表的头文件*/ #include "list.h" /* 定义一个链式哈希表的结构体*/ typedef struct Chtbl_ { int buckets; /*哈希表分配桶的个数*/ int (*h)(const void *key);/*哈希函数指针h */ int (*match)(const void *key1,const void *key2);/*判断2个键是否匹配*/ void (*destroy)(void *data);/*释放内存空间*/ int size;/*成员个数*/ List *table;/*哈希表中链表桶的头指针*/ }Chtbl; /* 函数接口*/ int chtbl_init (Chtbl *htbl,int buckets,int (*h)(const void *key),int (*match)(const void *key1,const void *key2),void (*destroy)(void *data)); int chtbl_destroy (Chtbl *htbl); int chtbl_insert(Chtbl *htbl,const void *data); int chtbl_remove (Chtbl *htbl,void **data); int chtbl_lookup(const Chtbl *htbl,void **data); #define chtbl_size(Chtbl *htbl) ((htbl)->size) #endif链式哈希表主文件chtbl.c
/*chtbl_c*/ #include <stdlib.h> #include <string.h> #include "list.h" #include "chtbl.h" /* 初始化哈希表结构体*/ int chtbl_init(Chtbl * htbl, int buckets, int(* h)(const void * key), int(* match)(const void * key1, const void * key2), void(* destroy)(void * data)) { int i; /* 申请空间为桶*/ if ((htbl->table = (List *)malloc(buckets * sizeof(List))) == NULL){ return -1; } /* 初始化哈希表*/ htbl->buckets = buckets; for (i = 0; i < buckets; i++){ list_init(&htbl->table[i], destroy); } htbl->h = h; htbl->match = match; htbl->destroy = destroy; htbl->size = 0; return 0; } /*哈希表销毁*/ void chtbl_destroy(Chtbl * htbl) { int i; if (htbl == NULL) return; /*销毁各个桶*/ for (i = 0; i < htbl->buckets; i++){ list_destory(htbl->table[i]); } /* 释放hash table 空间*/ free(htbl->table); /*清空htbl结构体*/ memset(htbl,0,sizeof(Chtbl)); return ; } /* 插入数据 * return 成功返回0;失败返回-1;存在返回1; * */ int chtbl_insert(Chtbl * htbl, const void * data) { void *temp; int bucket; int retval; /*如何data已经在哈希表中返回1*/ temp = (void *)data; if (chtbl_lookup(htbl, &temp) == 0) return 1; /*获得哈希键*/ bucket = htbl ->h(data) % htbl->buckets; /*插入哈希表*/ if ((retval = list_ins_next(htbl->table[bucket], NULL, data)) == 0) htbl ->size ++; return retval; } /*删除数据 *return 成功返回0;失败返回-1; */ int chtbl_remove(Chtbl * htbl, void * * data) { ListElmt *element = NULL; ListElmt *prev = NULL; int bucket = -1; /*获得哈希键*/ bucket = htbl ->h(*data)%htbl -> buckets; /*查找在哈希表中的data*/ for (element = list_head(&htbl->table [bucket]);element != NULL;element = list_next(element)){ if (htbl->match(*data,list_data(element))){ if (list_rem_next(&htbl->table[bucket], prev, data) == 0){ htbl->size --; return 0; } else { return -1; } } prev = element; } return -1; } /* 在哈希表中查找元素 *return :如果找到元素返回0;否则返回-1. */ int chtbl_lookup(const Chtbl * htbl, void * * data) { ListElmt *element; int bucket; /*获得哈希键*/ bucket = htbl -> h(*data)%htbl->buckets; /*在哈希表中查找元素*/ for (element = list_head(&htbl->table [bucket]);element != NULL;element = list_next(element)){ if (htbl->match(*data,list_data(element))){ *data = list_data(element); return 0; } } return -1; }
/*ohtbl.h*/ #ifndef OHTBL_H #define OHTBL_H #include <stdlib.h> /*定义开地址哈希表结构体*/ typedef struct Ohtbl_{ int positions;/*槽位的个数*/ void *vacated;/*指针将被初始化来指向一个特殊的地址空间, 来证明这个特殊地址上曾经删除过一个元素*/ int (*h1)(const void * key); int (*h2)(const void *key); int (*match)(const void *key1,const void *key2); void (*destroy)(void *data); int size;/*元素的个数*/ void **table;/*存储元素的数组(malloc申请)*/ }Ohtbl; /*函数接口*/ int ohtbl_init(Ohtbl *htbl,int positions,int (*h1)(const void *key),int (*h2)(const void *key),int (*match)(const void *key1,const void *key2),void (*destroy)(void *data)); void ohtbl_destroy(Ohtbl *htbl); int ohtbl_insert(Ohtbl *htbl,const void *data); int ohtbl_remove(Ohtbl *htbl,void **data); int ohtbl_lookup(const Ohtbl *htbl,void **data); #define ohtbl_size(htbl) ((htbl) ->size) #endif
/*ohtbl.c*/ #include <stdlib.h> #include <string.h> #include "ohtbl.h" /*预订一个内存地位为腾出的元素*/ static char vacated; /*结构体初始化 *return 初始化成功: 0, 失败:-1. */ int ohtbl_init(Ohtbl * htbl, int positions, int(* h1)(const void * key), int(* h2)(const void * key), int(* match)(const void * key1, const void * key2), void(* destroy)(void * data)) { int i; /*申请空间为哈希表*/ if ((htbl ->table = (void **)malloc(positions *sizeof(void *))) == NULL) return -1; /*初始化每个positions*/ htbl ->positions = positions; for (i = 0; i < htbl->positions; i++) htbl -> table[i] = NULL; /*设置腾出成员的内存地址*/ htbl->vacated = &vacated; /*初始化封装的函数*/ htbl -> h1 = h1; htbl -> h2 = h2; htbl ->match = match; htbl ->destroy = destroy; /*初始化元素个数*/ htbl ->size = 0; return 0; } /*开地址哈希表销毁 * * */ int ohtbl_destroy(Ohtbl * htbl) { int i; if (htbl ->destroy !=NULL){ /*使用用户定义的函数去释放存放数据的内存*/ for (i = 0; i < htbl ->positions; i++){ if (htbl ->table[i] != NULL && htbl ->table[i] != htbl ->vacated) htbl ->destroy(htbl ->table[i]); } } /*释放table 内存*/ free(htbl ->table); /*清空哈希表结构体*/ memest(htbl,0,sizeof(Ohtbl)); return; } /* 哈希表中插入元素 *return 初始化成功: 0, 失败:-1. * 数据已经在哈希表内返回:1 */ int ohtbl_insert(Ohtbl * htbl, const void * data) { void *temp; int position = 0; int i = 0; /*但哈希表已经满的话返回-1*/ if (htbl ->size == htbl ->positions) return -1; /*如果数据已经在哈希表内,返回1*/ temp = (void *)data; if (ohtbl_lookup(htbl, &temp) == 0) return 1; /*找到哈希编码值存在数据*/ for (i = 0; i <htbl -> positions; i++){ /*获得哈希编码值*/ position = (htbl->h1(data) + i * htbl ->h2(data)) %htbl ->positions; if (htbl ->table[position] == NULL || htbl ->table[position] ==htbl ->vacated){ /*插入数据*/ htbl ->table[position] = (void *)data; htbl ->size ++; return 0; } } return -1; } /* 删除哈希表数值 *return 初始化成功: 0, 失败:-1. */ int ohtbl_remove(Ohtbl * htbl, void * * data) { int i = 0; int position = 0; if (htbl ->size == 0) return -1; for (i = 0; i <htbl -> positions; i++){ /*获得哈希编码值*/ position = (htbl->h1(data) + i * htbl ->h2(data)) %htbl ->positions; if (htbl ->table[position] == NULL){/*表明元素未存放到哈希表内*/ return -1; } else if (htbl ->table[position] == htbl ->vacated){ /*搜索vacated 之外*/ continue; } else if(htbl -> match(htbl->table[position],*data)){ *data = htbl->table[position]; htbl->table[position] = htbl ->vacated; htbl->size --; return 0; } } return -1 } /*查找数据 *return 初始化成功: 0, 失败:-1. */ int ohtbl_lookup(const Ohtbl * htbl, void * * data) { int position = 0; int i = 0; for (i = 0; i <htbl -> positions; i++){ /*获得哈希编码值*/ position = (htbl->h1(data) + i * htbl ->h2(data)) %htbl ->positions; if (htbl ->table[position] == NULL){/*表明元素未存放到哈希表内*/ return -1; } else if(htbl -> match(htbl->table[position],*data)){ *data = htbl->table[position]; return 0; } } return -1; }