哈希表就是一个元素有一一对应位置的一个表,如下图,哈希表也叫散列表,和函数的一个x对应一个y类似,不存在多个y对应一个x,当然哈希表可能有多个数对应一个下标,我们后面讲,这里暂且理解为和函数一样,是一种映射。
在图中,哈希表存的数据位整形,如果我们存手机号,可以将后四位作为key,或者是后四位经过一个算数处理,当作key也可以。
哈希表作为一个查找时间复杂度只有O(1)的数据接结构,可以说效率非常高。
对于二叉搜索树而言,二叉搜索树中的元素存储位置和其值没有直接对应关系,需要多次将关键码进行比较来查询。
相比二叉搜索树,哈希表有一对一对应的映射关系,不需要多次比较来查找。
我认为这是典型的空间换时间的数据结构,通过一对一映射的方式,必将有空间为空,说明其空间利用率其实不是100%,像java中,哈希表的存储利用率为0.75,超过0.75这个预设值,哈希表将被扩容,我在这篇文章中所用的开放定址法的代码将这个值控制在0.7,而拉链法的比例控制为1。
如上图一样,11和22在哈希表容量为11的时候,他们对应的都是0,这就有了冲突,解决冲突有两种办法,开散列和闭散列。
开散列又叫开放定址法,一个位置只存一个元素,如果新来的元素发现该位置有冲突,就向后寻找,直至找到一个空位为止,存入数据,但是很多时候这种方法很不好,容易造成过多的冲突,于是有了二次线性探测,和直接探测不同,二次探测是依次往后探测n的平方个,第一次往后一个,第二次往后4个,第三次往后9个。
闭散列又称拉链法,当有数冲突的时候,不是往后找,而是直接挂在这个位置的下面,像链表一样挂起来,这个方式一容量和元素个数的比例一般控制为1,多了会影响查找效率。
但是这种方法也有个坏处,假如有人恶意将其所有元素均存在一个位置,然后进行访问,就会出现访问速度过慢的问题,假如这被应用在网站的话,就会造成网站崩溃,无法访问。
负载因子我在上面也有带着提到过,这里详细说一下。
负载因子α = 表中元素个数 / 散列表的长度
由于表长是定值,所以α和填入表中的元素个数成正比,α越大,表明表中数据越多,冲突的可能性越大,反之冲突越小。
实际上,散列表的平均查找长度是载荷因子α 的函数,只是不同处理冲突的方法有不同的函数。
对于开放定址法,负载因子是特别中原的因素,应严格控制在0.7~0.8以下,超过0.8,查表时CPU的缓存不命中(cache missing)按照指数曲线上升,因此一些采用开放定址法的库比如java就是限制负载因子为0.75,超过则resize。
#pragma once
#include
#include
#include
#include
#include
typedef int HTDataType;
typedef enum Status
{
EMPTY,
EXIST,
DELETE
}Status;
typedef struct HashTableNode
{
HTDataType _data;
Status _status;
}HTNode;
typedef struct HashTable
{
HTNode* _table;
size_t _size;
size_t _capacity;
}Hash;
void HashInit(Hash* ht, size_t capacityy);//初始化
//功能辅助函数
static size_t GetPrime(Hash* ht);//STL里面的素数表,获取素数
static size_t HashFunc(HashTable* ht, HTDataType data); //哈希函数
static void CheckCapacity(HashTable* ht);//空间检查
static void HashPrint(HashTable* ht);//打印
//基本功能函数,增删查
bool HashInsert(Hash* ht, HTDataType data);
bool HashRemove(Hash* ht, HTDataType data);
HTNode* HashFind(Hash* ht, HTDataType data);
void TestHashTable();//测试用例
void HashInit(Hash* ht, size_t capacity)
{
assert(ht);
ht->_size = 0;
ht->_capacity = capacity;
ht->_capacity = GetPrime(ht);
ht->_table = (HTNode*)malloc(sizeof(HTNode) * ht->_capacity);
assert(ht->_table);
for (size_t i = 0; i < ht->_capacity; i++)
{
(*(ht->_table + i))._status = EMPTY;
}
}
static size_t GetPrime(Hash* ht)
{
const int _PrimeSize = 28;//仅可在c++下使用
//该数据都是经过许多人测试的素数,能够满足需求,冲突较小
static const unsigned long _PrimeList[_PrimeSize] = {
53ul,97ul,193ul,389ul,769ul,
1543ul,3079ul,6151ul,12289ul,
24593ul,49157ul,98317ul,196613ul,
393241ul,786433ul,1572869ul,3145739ul,
6291469ul,12582917ul,25165843ul,50331653ul,
100663319ul,201326611ul,402653189ul,805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
int index = 0;
while (index < _PrimeSize)
{
if (ht->_capacity < _PrimeList[index])
{
return _PrimeList[index];
}
index++;
}
return _PrimeList[_PrimeSize - 1];
}
static size_t HashFunc(HashTable* ht, HTDataType data)
{
//若需要存储其他类型的数据,需要修改此函数的寻址规则
return data % ht->_capacity;
}
static void CheckCapacity(HashTable* ht)
{
assert(ht);
//若空间不够,扩容
if (ht->_size * 10 / ht->_capacity >= 7)
{
HashTable newht;
HashInit(&newht, ht->_capacity);
for (int i = 0; i < ht->_capacity; i++)
{
if ((*(ht->_table + i))._status == EXIST)
{
HashInsert(&newht, (*(ht->_table + i))._data);//赋用Insert进行重新寻址插入
}
}
free(ht->_table);
ht->_table = newht._table;
ht->_size = newht._size;
ht->_capacity = newht._capacity;
}
}
bool HashInsert(Hash* ht, HTDataType data)
{
assert(ht);
CheckCapacity(ht);
size_t index = HashFunc(ht, data);
size_t i = 1;
while ((*(ht->_table + index))._status != EMPTY)
{
if ((*(ht->_table + index))._status == EXIST)
{
if ((*(ht->_table + index))._data == data)
{
return false;
}
}
//index++;
//if (index == ht->_capacity)
//{
// index = 0;
//}
index = index + i * i;//二次探测寻址
index = index % ht->_capacity;
i++;
}
(*(ht->_table + index))._data = data;
(*(ht->_table + index))._status = EXIST;
ht->_size++;
return true;
}
bool HashRemove(Hash* ht, HTDataType data)
{
HTNode* node = HashFind(ht, data);
if (node)
{
node->_status = DELETE;
return true;
}
return false;
}
HTNode* HashFind(Hash* ht, HTDataType data)
{
assert(ht);
size_t index = HashFunc(ht, data);
size_t i = 1;
while ((*(ht->_table + index))._status != EMPTY)
{
if ((*(ht->_table + index))._data == data)
{
if ((*(ht->_table + index))._status == EXIST)
{
return ht->_table + index;
}
else
{
return NULL;
}
}
//index++;//线性探测寻址法
index = index + i * i;//二次探测寻址
index = index % ht->_capacity;
i++;
}
return NULL;
}
static void HashDestroy(HashTable* ht)
{
assert(ht);
free(ht->_table);
ht->_table = NULL;
ht->_size = 0;
ht->_capacity = 0;
}
void HashPrint(HashTable* ht)
{
//为了测试显示用的打印函数
for (int i = 0; i < ht->_capacity; i++)
{
if (i % 10 == 0 && i != 0)
{
printf("\n");
}
if ((*(ht->_table + i))._status == EXIST)
{
printf("%2d ", (*(ht->_table + i))._data);
}
else if((*(ht->_table + i))._status == DELETE)
{
printf(" D ");
}
else
{
printf(" N ");
}
}
printf("\n");
}
void TestHashTable()
{
Hash ht;
HashInit(&ht, 0);
//插入测试
srand((unsigned)time(0));
for (int i = 0; i < 100; i++)
{
HashInsert(&ht, rand() % 100);
}
HashPrint(&ht);
printf("-------------------------------------------------\n");
//删除测试
for (int i = 0; i < 100; i++)
{
HashRemove(&ht, rand() % 100);
}
HashPrint(&ht);
HashDestroy(&ht);
}
#pragma once
#include
#include
#include
#include
#include
typedef int HashDataType;
typedef struct HashBucketNode
{
HashDataType _data;
struct HashBucketNode* _next;
}HashNode;
typedef struct HashBucket
{
HashNode** _tables;
size_t _size;
size_t _capacity;
}HashBucket;
void HashInit(HashBucket* hb, size_t capacity);//初始化哈希桶
static size_t HashFunc(HashBucket* hb, HashDataType data);//哈希函数
static HashNode* BuyNewHashNode(HashDataType data);//创建新链节点
static void CheckCapacity(HashBucket* hb);//检查空间,确保效率
static size_t GetPrime(HashBucket* hb);
static void HashPrint(HashBucket* hb);
bool HashInsert(HashBucket* hb, HashDataType data);//插入
HashNode* HashFind(HashBucket* hb, HashDataType data);//查找某个元素是否存在
bool HashRemove(HashBucket* hb, HashDataType data);//删除
size_t HashSize(HashBucket* hb);
void TestHashTable();//测试用例
void HashInit(HashBucket* hb, size_t capacity)
{
assert(hb);
hb->_capacity = capacity;
hb->_capacity = GetPrime(hb);
hb->_tables = (HashNode**)malloc(sizeof(HashNode*) * hb->_capacity);
assert(hb->_tables);
hb->_size = 0;
for (size_t i = 0; i < hb->_capacity; i++)
{
*(hb->_tables + i) = NULL;
}
}
static size_t HashFunc(HashBucket* hb, HashDataType data)
{
assert(hb);
return data % hb->_capacity;
}
static HashNode* BuyNewHashNode(HashDataType data)
{
HashNode* new_node = (HashNode*)malloc(sizeof(HashNode));
new_node->_data = data;
new_node->_next = NULL;
return new_node;
}
static void CheckCapacity(HashBucket* hb)
{
assert(hb);
if (hb->_size == hb->_capacity)
{
HashBucket new_hb;
HashNode* cur = NULL;
HashNode* next = NULL;
HashInit(&new_hb, hb->_capacity);
for (size_t i = 0; i < hb->_capacity; i++)
{
//将原数据插入新表中
cur = *(hb->_tables + i);
while (cur)
{
//之所以可以头插是因为数据没有重复
next = cur->_next;
size_t index = HashFunc(&new_hb, cur->_data);
cur->_next = *(new_hb._tables + index);
*(new_hb._tables + index) = cur;
cur = next;
}
}
hb->_capacity = new_hb._capacity;
free(hb->_tables);//释放旧表
hb->_tables = new_hb._tables;//链接新表
}
}
static size_t GetPrime(HashBucket* hb)
{
assert(hb);
const int _PrimeSize = 28;
//该数据都是经过许多人测试的素数,能够满足需求,冲突较小
static const unsigned long _PrimeList[_PrimeSize] = {
53ul,97ul,193ul,389ul,769ul,
1543ul,3079ul,6151ul,12289ul,
24593ul,49157ul,98317ul,196613ul,
393241ul,786433ul,1572869ul,3145739ul,
6291469ul,12582917ul,25165843ul,50331653ul,
100663319ul,201326611ul,402653189ul,805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
int index = 0;
while (index < _PrimeSize)
{
if (hb->_capacity < _PrimeList[index])
{
return _PrimeList[index];
}
index++;
}
return _PrimeList[_PrimeSize - 1];
}
static void HashPrint(HashBucket* hb)
{
//辅助打印函数
assert(hb);
HashNode* cur = NULL;
for (size_t i = 0; i < hb->_capacity; i++)
{
cur = *(hb->_tables + i);
printf("tables[%d]", i);
while (cur)
{
printf("->%d", cur->_data);
cur = cur->_next;
}
printf("->NULL\n");
}
}
bool HashInsert(HashBucket* hb, HashDataType data)
{
assert(hb);
CheckCapacity(hb);
size_t index = HashFunc(hb, data);
HashNode* cur = *(hb->_tables + index);
HashNode* prev = cur;
if (cur == NULL)
{
//该位置没有任何节点,直接插入
*(hb->_tables + index) = BuyNewHashNode(data);
}
else
{
//该位置已有节点,找寻合适位置插入
while (cur)
{
//不允许头插,因为可能有数据重复
if (cur->_data == data)
{
return false;
}
prev = cur;
cur = cur->_next;
}
prev->_next = BuyNewHashNode(data);
}
hb->_size++;
return true;
}
HashNode* HashFind(HashBucket* hb, HashDataType data)
{
assert(hb);
size_t index = HashFunc(hb, data);
HashNode* cur = *(hb->_tables + index);
while (cur)
{
if (cur->_data == data)
{
return cur;
}
cur = cur->_next;
}
return NULL;
}
bool HashRemove(HashBucket* hb, HashDataType data)
{
assert(hb);
size_t index = HashFunc(hb, data);
HashNode* cur = *(hb->_tables + index);
HashNode* prev = cur;
while (cur)
{
if (cur->_data == data)
{
break;
}
prev = cur;
cur = cur->_next;
}
if (cur == NULL)
{
//cur已经查到底或者该位置本就没有节点为空,未找到
return false;
}
else
{
//找到
if (cur == prev)
{
//节点为第一个结点,不能直接prev = cur,因为prev是临时变量
*(hb->_tables + index) = cur->_next;
}
else
{
//节点不是第一个节点
prev->_next = cur->_next;
}
free(cur);
return true;
}
}
size_t HashSize(HashBucket* hb)
{
assert(hb);
return hb->_size;
}
void TestHashTable()
{
HashBucket hb;
HashInit(&hb, 0);
srand((unsigned)time(0));
for (int i = 0; i < 50; i++)
{
HashInsert(&hb, rand() % 200);
}
HashPrint(&hb);
printf("\n");
for (int i = 0; i < 50; i++)
{
HashInsert(&hb, rand() % 200);
}
HashPrint(&hb);
printf("\n");
for (int i = 0; i < 200; i++)
{
HashRemove(&hb, rand() % 200);
}
HashPrint(&hb);
}