散列表介绍
散列表是非比较查找表。散列表通过关键字直接查找数据结构,理论上的时间复杂度为O(1)。
- 散列函数是一个将关键字映射成对应地址的函数。
- 冲突 散列函数将不同的关键字映射到了相同的地址,这就是冲突。
散列函数构造方法
- 直接定址法 H(key) = key或H(key) = a * key + b;适合关键字分布基本连续的场景。
- 除留余数法 H(key) = key % p,p为质数
- 数字分析法
- 平方取中法 取关键字的平方的中间几位数作为散列地址
冲突解决方法
链接法不能有效利用CPU缓存,开放定址法能有效利用CPU缓存。
开放定址法
如果应用场景设计hash表数据的序列化,使用开放定址法可能更佳。
Hi = (H(key) + di) % m(表长) i = 1...k(k <= m - 1)
- 线性探测法 di = ...m-1
- 平方探测法 di = 0^2,1^2,...,(m-1)^2
- 再散列法 di = H2(key)
- 伪随机序列法 di = 伪随机数序列
链接法
线性探测法解决冲突的代码实现
散列函数使用除留余数法。
#include <stdio.h>
#include <stdlib.h>
#define NULLKEY -1
#define FOUND -2
#define NOTFOUND -3
#define FULL -4
#define INSERTED -5
typedef struct{
int del;
int key;
}Elem;
typedef struct{
Elem *elem;
int num;
int size;
}HashTable;
//构造hash表
void createHashTable(HashTable *ht){
int size = (*ht).size;
ht->elem = (Elem*)malloc(sizeof(Elem) * size);
if(ht->elem == NULL) exit(0);
for(int i = 0; i < size; i ++){
ht->elem[i].key = NULLKEY;
ht->elem[i].del = 0;
}
}
//散列函数 除留余数法
int hashFunc(HashTable ht, int key){
return key % ht.size;
}
// 冲突处理函数 -- 线性探测法
void collison(HashTable ht, int *pos){
*pos = (*pos + 1) % ht.size;
}
//搜索关键字
int search(HashTable ht, int key, int *pos){
int i = 0;
*pos = hashFunc(ht, key);
while(i < ht.size){
if(ht.elem[*pos].key == NULLKEY){ //该元素不在散列表中
return NOTFOUND;
}else if(ht.elem[*pos].key == key && ht.elem[*pos].del == 0){//找到该元素
return FOUND;
}else{ //继续查找
collison(ht, pos);
}
i ++;
}
return NOTFOUND;
}
//插入元素
int insert(HashTable *ht, int key){
int i = 0;
int pos;
int ret = search(*ht, key, &pos);
if(ret == FOUND) return FOUND;
pos = hashFunc(*ht, key);
while(i < (*ht).size){
if((*ht).elem[pos].key == NULLKEY || (*ht).elem[pos].del == 1){ //找到空位,直接插入
(*ht).elem[pos].key = key;
(*ht).elem[pos].del = 0;
return INSERTED;
}else{
collison(*ht, &pos);
}
i ++;
}
return FULL;
}
//删除元素。前面在查找元素时,有一段逻辑,如果key=NULLKEY则散列表中没有该元素。如果在删除元素时将key置为NULLKEY,将导致上述逻辑失效,因此这里使用伪删除法,不真正删除元素,而是通过del字段控制是否删除。
void delete(HashTable *ht, int key){
int pos;
int ret = search(*ht, key, &pos);
if(ret == NOTFOUND) return;
(*ht).elem[pos].del = 1;
}
void print(HashTable ht){
for(int i = 0; i < ht.size; i ++){
if(ht.elem[i].key != NULLKEY && ht.elem[i].del != 1){
printf("%d %d\n", i, ht.elem[i].key);
}
}
}
int main(int argc, const char * argv[]) {
HashTable ht;
ht.size = 17;
ht.num = 0;
ht.elem = NULL;
createHashTable(&ht);
insert(&ht, 8);
insert(&ht, 5);
delete(&ht, 8);
insert(&ht, 8);
insert(&ht, 22);
print(ht);
return 0;
}
链接法解决冲突代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FOUND -1
#define NOTFOUND -2
#define INSERTED -3
typedef struct keyType {
int key;
struct keyType* next;
}keyType;
typedef struct {
keyType** elem;
int size;
int num;
}HashTable;
void createHashTable(HashTable** ht, int size) {
*ht = (HashTable*)malloc(sizeof(HashTable));
if (*ht == NULL) {
exit(0);
}
(*ht)->elem = (keyType**)malloc(sizeof(keyType*) * size);
if ((*ht)->elem == NULL) {
exit(0);
}
memset((*ht)->elem, 0, sizeof(keyType*) * size);
(*ht)->size = size;
(*ht)->num = 0;
}
int htFunc(HashTable ht, int k) {
return k % ht.size;
}
int search(HashTable ht, int k, keyType* ptr) {
int pos = htFunc(ht, k);
ptr = ht.elem[pos];
while (ptr != NULL) {
if (ptr->key == k) {
return FOUND;
}
ptr = ptr->next;
}
return NOTFOUND;
}
//按升序插入
int insert(HashTable* ht, int k) {
int pos = htFunc(*ht, k);
keyType* p = ht->elem[pos], *q = NULL, * node;
if (p == NULL) { //没有结点
node = (keyType*)malloc(sizeof(keyType));
if (node == NULL) exit(0);
node->key = k;
node->next = NULL;
ht->elem[pos] = node;
ht->num++;
return INSERTED;
}
if (k < p->key && p->next == NULL) { //有一个结点且关键字大于k
node = (keyType*)malloc(sizeof(keyType));
if (node == NULL) exit(0);
node->key = k;
node->next = p;
ht->elem[pos] = node;
ht->num++;
return INSERTED;
}
//查找结点的插入位置
while (p != NULL) {
if (k == p->key) {
return FOUND;
}
else if (k > p->key) {
q = p;
p = p->next;
}
else { //找到第一个大于k的结点的前驱
break;
}
}
node = (keyType*)malloc(sizeof(keyType));
if (node == NULL) exit(0);
node->key = k;
node->next = p;
q->next = node;
ht->num++;
return INSERTED;
}
void printHt(HashTable ht) {
keyType* p;
for (int i = 0; i < ht.size; i++) {
p = ht.elem[i];
while (p != NULL) {
printf("%d\t", p->key);
p = p->next;
}
}
}
int main() {
HashTable *ht;
createHashTable(&ht, 17);
insert(ht, 8);
insert(ht, 25);
insert(ht, 9);
keyType key;
int ret = search(*ht, 9, &key);
if (ret == FOUND) {
printf("OK");
printf("\n");
}
ret = search(*ht, 10, &key);
if (ret == NOTFOUND) {
printf("NOTFOUND \n");
}
printHt(*ht);
return 0;
}(keyType));
if(node == NULL) exit(0);
node->key = k;
node->next = p;
ht->elem[pos] = node;
ht->num ++;
return INSERTED;
}
//查找结点的插入位置
while(p != NULL){
if(k == p->key){
return FOUND;
}else if(k > p->key){
q = p;
p = p->next;
}else{ //找到第一个大于k的结点的前驱
break;
}
}
node = (keyType*)malloc(sizeof(keyType));
if(node == NULL) exit(0);
node->key = k;
node->next = p;
q->next = node;
ht->num ++;
return INSERTED;
}