散列表(也叫哈希表)是一种查找算法,与链表、树等算法不同的是,散列表算法在查找时不需要进行一系列和关键字(关键字是数据元素中某个数据项的值,用以标识一个数据元素)的比较操作。
散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必须要在数据元素的存储位置和它的关键字(可用key表示)之间建立一个确定的对应关系,使每个关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定关键字在散列表中的位置即可。这种对应关系被称为散列函数(可用h(key)表示)。
根据设定的散列函数h(key)和处理冲突的方法将一组关键字key映像到一个有限的连续的地址区间上,并以关键字在地址区间中的像作为数据元素在表中的存储位置,这种表便被称为散列表,这一映像过程称为散列,所得存储位置称为散列地址。
关键字、散列函数以及散列表的关系如下图所示:
1、散列函数
散列函数是从关键字到地址区间的映像。
好的散列函数能够使得关键字经过散列后得到一个随机的地址,以便使一组关键字的散列地址均匀地分布在整个地址区间中,从而减少冲突。
常用的构造散列函数的方法有:
(1)、直接定址法
取关键字或关键字的某个线性函数值为散列地址,即:
h(key) = key 或 h(key) = a * key + b
其中a和b为常数。
(2)、数字分析法
(3)、平方取值法
取关键字平方后的中间几位为散列地址。
(4)、折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址。
(5)、除留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址,即:
h(key) = key MOD p p ≤ m
(6)、随机数法
选择一个随机函数,取关键字的随机函数值为它的散列地址,即:
h(key) = random(key)
其中random为随机函数。
2、处理冲突
对不同的关键字可能得到同一散列地址,即key1 ≠ key2,而h(key1)= h(key2),这种现象称为冲突。具有相同函数值的关键字对该散列函数来说称作同义词。
在一般情况下,散列函数是一个压缩映像,这就不可避免地会产生冲突,因此,在创建散列表时不仅要设定一个好的散列函数,而且还要设定一种处理冲突的方法。
常用的处理冲突的方法有:
(1)、开放定址法
hi =(h(key) + di) MOD m i =1,2,…,k(k ≤ m-1)
其中,h(key)为散列函数,m为散列表表长,di为增量序列,可有下列三种取法:
1)、di = 1,2,3,…,m-1,称线性探测再散列;
2)、di = 12,-12,22,-22,32,…,±k2 (k ≤m/2),称二次探测再散列;
3)、di = 伪随机数序列,称伪随机探测再散列。
(2)、再散列法
hi = rhi(key) i = 1,2,…,k
rhi均是不同的散列函数。
(3)、链地址法
将所有关键字为同义词的数据元素存储在同一线性链表中。假设某散列函数产生的散列地址在区间[0,m-1]上,则设立一个指针型向量void *vec[m],其每个分量的初始状态都是空指针。凡散列地址为i的数据元素都插入到头指针为vec[i]的链表中。在链表中的插入位置可以在表头或表尾,也可以在表的中间,以保持同义词在同一线性链表中按关键字有序排列。
(4)、建立一个公共溢出区
相关的C语言解释
hash.h
哈希表数据结构&&接口定义头文件
#ifndef HASH_H #define HASH_H #define HASH_TABLE_INIT_SIZE 7 #define SUCCESS 1 #define FAILED 0 /** * 哈希表槽的数据结构 */ typedef struct Bucket { char *key; void *value; struct Bucket *next; } Bucket; /** * 哈希表数据结构 */ typedef struct HashTable { int size; // 哈希表大小 int elem_num; // 哈希表已经保存的数据元素个数 Bucket **buckets; } HashTable; int hashIndex(HashTable *ht, char *key); int hashInit(HashTable *ht); int hashLookup(HashTable *ht, char *key, void **result); int hashInsert(HashTable *ht, char *key, void *value); int hashRemove(HashTable *ht, char *key); int hashDestory(HashTable *ht); #endif</div>
hash.c
哈希表操作函数具体实现
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "hash.h" /** * 初始化哈希表 * * T = O(1) * */ int hashInit(HashTable *ht) { ht->size = HASH_TABLE_INIT_SIZE; ht->elem_num = 0; ht->buckets = (Bucket **)calloc(ht->size, sizeof(Bucket *)); if (ht->buckets == NULL) return FAILED; else return SUCCESS; } /** * 散列函数 * * T = O(n) * */ int hashIndex(HashTable *ht, char *key) { int hash = 0; while (*key != '\0') { hash += (int)*key; key ++; } return hash % ht->size; } /** * 哈希查找函数 * * T = O(n) * */ int hashLookup(HashTable *ht, char *key, void **result) { int index = hashIndex(ht, key); Bucket *bucket = ht->buckets[index]; while (bucket) { if (strcmp(bucket->key, key) == 0) { *result = bucket->value; return SUCCESS; } bucket = bucket->next; } return FAILED; } /** * 哈希表插入操作 * * T = O(1) * */ int hashInsert(HashTable *ht, char *key, void *value) { int index = hashIndex(ht, key); Bucket *org_bucket, *tmp_bucket; org_bucket = tmp_bucket = ht->buckets[index]; // 检查key是否已经存在于hash表中 while (tmp_bucket) { if (strcmp(tmp_bucket->key, key) == 0) { tmp_bucket->value = value; return SUCCESS; } tmp_bucket = tmp_bucket->next; } Bucket *new = (Bucket *)malloc(sizeof(Bucket)); if (new == NULL) return FAILED; new->key = key; new->value = value; new->next = NULL; ht->elem_num += 1; // 头插法 if (org_bucket) { new->next = org_bucket; } ht->buckets[index] = new; return SUCCESS; } /** * 哈希删除函数 * * T = O(n) * */ int hashRemove(HashTable *ht, char *key) { int index = hashIndex(ht, key); Bucket *pre, *cur, *post; pre = NULL; cur = ht->buckets[index]; while (cur) { if (strcmp(cur->key, key) == 0) { post = cur->next; if (pre == NULL) { ht->buckets[index] = post; } else { pre->next = post; } free(cur); return SUCCESS; } pre = cur; cur = cur->next; } return FAILED; } /** * 哈希表销毁函数 * * T = O(n) */ int hashDestory(HashTable *ht) { int i; Bucket *cur, *tmp; cur = tmp = NULL; for (i = 0; i < ht->size; i ++) { cur = ht->buckets[i]; while (cur) {