-
哈希概念
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。例如:给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
-
哈希冲突
对于两个数据元素的关键字Ki 和Kj(i != j),有Ki != Kj,但有:
Hash(Ki)== Hash(Kj)
即不同关键字通过相同哈希函数计算出相同地址,该现象称为哈希冲突或哈希碰撞。具有相同函数值的关键字对该散列函数来说称做同义词。
-
哈希函数
哈希函数设计原则:
- 哈希函数定义域必须包括需要存储的全部关键码,如果散列表允许有m个地址时,其值域必须在0~m-1之间
- 哈希函数计算出来的地址能均匀的分布在整个空间
- 哈希函数应该比较简单
常见哈希函数:
- 直接寻址法
取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
数字分析法
分析一组数据,比如一组员工的出生年月日,这时发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
取关键字平方后的中间几位作为散列地址。
- 折叠法
将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
- 随机数法
选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
- 除留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
-
处理哈希冲突
闭散列:开放地址法
- 线性探测
- 二次探测
- 哈希表
#pragma once
#include <stdlib.h>
#include <stdio.h>
typedef int Key;
typedef int(*HashFunc)(Key, int);
// 无法直接删除,需要利用状态标记
typedef enum State
{
EMPTY,
DELETED,
EXIST
} State;
typedef struct Element
{
Key key;
State state;
} Element;
typedef struct HashTable
{
Element *array;
int size;
int capacity;
HashFunc hashFunc;
} HashTable;
//初始化/销毁
void HashTableInit(HashTable* pHT, int capacity, HashFunc hashFunc)
{
pHT->array = (Element *)malloc(sizeof(Element)*capacity);
pHT->size = 0;
pHT->capacity = capacity;
pHT->hashFunc = hashFunc;
//所有状态置为空 EMPTY
for (int i = 0; i < capacity; i++)
{
pHT->array[i].state = EMPTY;
}
}
void HashTableDestroy(HashTable* pHT)
{
free(pHT->array);
pHT->capacity = pHT->size = 0;
pHT->hashFunc = NULL;
}
// 查找 / 删除 / 插入
// 找到,返回 0
// 没找返回 -1
int HashTableSearch(HashTable* pHT, Key key)
{
int index = pHT->hashFunc(key, pHT->capacity);
int i = index;
int c = 1;
while (pHT->array[i].state != EMPTY)
{
if (pHT->array[i].state == EXIST && key == pHT->array[i].key)
{
return 0;
}
//i = (index + 1) % pHT->capacity;
i = (index + (c * c)) % pHT->capacity;//二次探测
c++;
}
// TODO: 看起来,会死循环,因为不会把所有的位置都用掉
return -1;
}
// 找到,删除成功,返回 0
// 没找到,返回 -1
int HashTableRemove(HashTable* pHT, Key key)
{
int index = pHT->hashFunc(key, pHT->capacity);
int i = index;
int c = 1;
while (pHT->array[i].state != EMPTY)
{
if (pHT->array[i].state == EXIST && key == pHT->array[i].key)
{
pHT->array[i].state = DELETED;
pHT->size--;
return 0;
}
//i = (index + 1) % pHT->capacity;
i = (index + (c * c)) % pHT->capacity;//二次探测
c++;
}
// TODO: 看起来,会死循环,因为不会把所有的位置都用掉
return -1;
}
int HashTableInsert(HashTable *pHT, Key key);
void ExpandIfNeed(HashTable* pHT)
{
if (pHT->size * 10 / pHT->capacity < 7)
{
return;
}
//两倍扩容
// 申请新空间
// 搬移老数据
// 释放老空间
int newCapacity = pHT->capacity * 2;
HashTable tempHT;
HashTableInit(&tempHT, newCapacity, pHT->hashFunc);
for (int i = 0; i < pHT->capacity; i++)
{
if (pHT->array[i].state==EXIST)
{
HashTableInsert(&tempHT, pHT->array[i].key);
}
}
free(pHT->array);
pHT->array = tempHT.array;
pHT->capacity = tempHT.capacity;
}
// 插入 key 不允许重复,空间优先
// 先查找一次,如果找到,插入失败 返回 -1
// 没找到,再次遍历,遇到 EMPTY | DELETED 都可以插入了
int HashTableInsert(HashTable *pHT, Key key)
{
// 进行扩容判断
ExpandIfNeed(pHT);
if (HashTableSearch(pHT, key) == 0) {
return -1;
}
int index = pHT->hashFunc(key, pHT->capacity);
int i = index;
int c = 1;
while (pHT->array[i].state == EXIST)
{
i = (index + (c * c)) % pHT->capacity;
c++;
}
pHT->array[i].key = key;
pHT->array[i].state = EXIST;
pHT->size++;
return 0;
}
// 实际用到的 hash 函数
int Hash(Key key, int capacity)
{
return key % capacity;
}
void test()
{
HashTable ht;
HashTableInit(&ht, 7, Hash);
HashTableInsert(&ht, 0);
HashTableInsert(&ht, 15);
HashTableInsert(&ht, 1);
HashTableInsert(&ht, 3);
HashTableInsert(&ht, 9);
HashTableInsert(&ht, 10);
printf("%d\n", HashTableSearch(&ht, 0));
HashTableRemove(&ht, 0);
printf("%d\n", HashTableSearch(&ht, 0));
HashTableDestroy(&ht);
}
开散列:链地址
- 哈希桶
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int Key;
typedef int(*HashFunc)(Key, int);
typedef struct Node
{
Key key;
struct Node* next;
} Node;
typedef struct HashBucket
{
Node** array;
int size;
int capacity;
HashFunc hashFunc;
} HashBucket;
void HashBucketInit(HashBucket* pHB, int capacity, HashFunc hashFunc)
{
// 申请 array 的空间
pHB->array = (Node**)malloc(sizeof(Node)*capacity);
//初始化array的值
for (int i = 0; i < capacity; i++)
{
pHB->array[i] = NULL;//空链表
}
pHB->capacity = capacity;
pHB->size = 0;
pHB->hashFunc = hashFunc;
}
void HashBucketDestroy(HashBucket* pHB)
{
// 从堆上申请的空间的释放
for (int i = 0; i < pHB->capacity; i++)
{
Node*cur;
Node* next;
for (cur = pHB->array[i]; cur != NULL; cur=next)
{
next = cur->next;
free(cur);
}
}
free(pHB->array);
}
int HashBucketSearch(HashBucket* pHB, Key key)
{
int index = pHB->hashFunc(key, pHB->capacity);
// pHB->array[index] 链表的第一个结点
for (Node* cur = pHB->array[index]; cur != NULL; cur = cur->next)
{
if (key == cur->key)
{
return 0;
}
}
return -1;
}
int HashBucketRemove(HashBucket* pHB, Key key)
{
int index = pHB->hashFunc(key, pHB->capacity);
// pHB->array[index] 链表的第一个结点
Node* prev = NULL;
for (Node* cur = pHB->array[index]; cur != NULL; cur = cur->next)
{
if (key == cur->key)
{
if (prev == NULL)
{
pHB->array[index] = cur->next;
}
else
{
prev->next = cur->next;
}
free(cur);
pHB->size--;
return 0;
}
prev = cur;
}
return -1;
}
int HashBucketInsert(HashBucket* pHB, Key key);
void ExpandIfRequired(HashBucket* pHB)
{
if (pHB->size * 10 / pHB->capacity < 9)
{
return;
}
int newCapacity = pHB->capacity * 2;
HashBucket tempHB;
HashBucketInit(&tempHB, newCapacity, pHB->hashFunc);
for (int i = 0; i < pHB->capacity; i++)
{
for (Node* cur = pHB->array[i]; cur != NULL; cur = cur->next)
{
HashBucketInsert(&tempHB, cur->key);
}
}
HashBucketDestroy(pHB);
pHB->array = tempHB.array;
pHB->capacity = tempHB.capacity;
}
int HashBucketInsert(HashBucket* pHB, Key key)
{
// 扩容
ExpandIfRequired(pHB);
// 不允许重复
if (HashBucketSearch(pHB, key) == 0)
{
return -1;
}
int index = pHB->hashFunc(key, pHB->capacity);
// 头插,时间节省
Node *node = (Node *)malloc(sizeof(Node));
node->key = key;
node->next = pHB->array[index];
pHB->array[index] = node;
pHB->size++;
return 0;
}
int hash(Key key, int capacity)
{
return key % capacity;
}
void test2()
{
HashBucket hb;
HashBucketInit(&hb, 7, hash);
HashBucketInsert(&hb, 0);
HashBucketInsert(&hb, 7);
HashBucketInsert(&hb, 14);
HashBucketInsert(&hb, 21);
HashBucketInsert(&hb, 28);
HashBucketInsert(&hb, 1);
HashBucketInsert(&hb, 8);
HashBucketInsert(&hb, 5);
printf("%d\n", HashBucketSearch(&hb, 14));
HashBucketRemove(&hb, 14);
printf("%d\n", HashBucketSearch(&hb, 14));
printf("成功\n");
}
-
哈希变形位图
#pragma once
#include<stdio.h>
#include <stdlib.h>
// 32 个房间的宿舍楼
typedef unsigned int uint32_t;
typedef struct {
uint32_t *array;
int size; // 里面住了多少同学
int capacity; // 有多少楼
} BitMap;
void BitMapInit(BitMap *pBM, int capacity)
{
pBM->array = (uint32_t *)malloc(sizeof(uint32_t)*capacity);
pBM->size = 0;
pBM->capacity = capacity;
}
void BitMapDestroy(BitMap *pBM)
{
free(pBM);
}
void BitMapSetOne(BitMap* pBM, unsigned int n)
{
int index = n / 32;
int bit = n % 32;
pBM->size++;
pBM->array[index] = pBM->array[index] | (1 << bit);
}
void BitMapSetZero(BitMap *pBM, unsigned int n)
{
int index = n / 32;
int bit = n % 32;
pBM->size--;
pBM->array[index] = pBM->array[index] & ~(1 << bit);
}
int BitMapIsSetOne(BitMap* pBM, unsigned int n)
{
int index = n / 32;
int bit = n % 32;
return pBM->array[index] >> bit & 1;
}
void test3()
{
BitMap bm;
BitMapInit(&bm, 5);
BitMapSetOne(&bm, 1);
BitMapSetOne(&bm, 10);
BitMapSetOne(&bm, 100);
BitMapSetOne(&bm, 28);
BitMapSetOne(&bm, 66);
BitMapSetOne(&bm, 99);
BitMapSetOne(&bm, 101);
printf("%d\n", BitMapIsSetOne(&bm, 1));
BitMapSetZero(&bm, 1);
printf("%d\n", BitMapIsSetOne(&bm, 1));
printf("成功\n");
}