HashTable
无论C++还是Java,都会提供哈希表,但是不同的语言实现不一样;
哈希表是无序的:插入数据并不进行排序(HashMap)
Map:是有序的,Map的底层是按照红黑数实现的,当用户把关键码插入进去,就会自动进行排序。因此Map的效率就没有HashMap高
一:简单哈希
(一):简单哈希表的定义:
typedef int KeyType;
struct ElemType
{
KeyType key;//关键码
void* ptr;//存放关键码所指向的数据的地址
};
typedef struct
{
ElemType data[m];
int cursize;//当前元素的个数
}HashTable;
HashTable ht;
哈希表的初始化:
等价于:
(二):简单哈希表存在的问题
问题:
- 哈希冲突
- 数据不连续
(三):解决问题的方法
线性探测
int Inc(int i)
{
return i;//线性探测
//return i * i;//平方探测
}
int Hash(KeyType kx)
{
return kx % m;
}
int Hash_Inc(KeyType kx, int i)
{
return (Hash(kx) + Inc(i)) % m;
}
(四):哈希查询
void* FindValue(HashTable* pt, KeyType kx)
{
assert(pt != nullptr);
for (int i = 0;i < m;++i)
{
int pos = Hash_Inc(kx, i);
if (pt->data[pos].key == kx)
{
return pt->data[pos].ptr;
}
if (pt->data[pos].key == NIL)
{
return nullptr;
}
}
return nullptr;
}
二:链式哈希解决哈希冲突
(一):链式哈希的定义
//链式哈希
#define m 13 //顺序表的大小
typedef int KeyType;
typedef struct
{
KeyType key;//关键码
void* ptr;//存放关键码所指向的数据的地址
}ElemType;
typedef struct
{
ElemType data;
struct HashNode* next;
}HashNode;
typedef struct
{
HashNode* table[m];
int cursize;
}HashTable;
(二):哈希函数
typedef int KeyType;
int Hash(KeyType kx)
{
return kx % m;
}
此次关键码kx是整型。若关键码为字符串呢?哈希函数该如何改进?
typedef char KeyType[20];
方法一:将字符串ASCLL码值相加取模;
但是对于“abc” “cba" 关键码一定不同,但是ASCLL相加却相同。所以不能简单的将ASCLL码相加,需要加工
(三):链式插入(头插)
//头插 时间复杂度O(1)
void Insert_Item(HashTable* pt, ElemType item)//没有去重,关键码相同依然会被插入
{
assert(pt != nullptr);
int index = Hash(item.key);
HashNode* s = (HashNode*)malloc(sizeof(HashNode));
if (nullptr == s)
{
exit(EXIT_FAILURE);
}
s->data = item;
s->next = pt->table[index];
pt->table[index] = s;
pt->cursize += 1;
}
HashNode* FindValue(HashTable* pt, KeyType kx)
{
int index = Hash(kx);
HashNode* p = pt->table[index];
while (p != nullptr && p->data.key != kx)
{
p = p->next;
}
return p;
}
void Insert_Item_New(HashTable* pt, ElemType item)//去重插入
{
assert(pt != nullptr);
int index = Hash(item.key);
HashNode* p = FindValue(pt,item.key);
if (p != nullptr) return ;
HashNode* s = (HashNode*)malloc(sizeof(HashNode));
if (nullptr == s)
{
exit(EXIT_FAILURE);
}
s->data = item;
s->next = pt->table[index];
pt->table[index] = s;
pt->cursize += 1;
}
(四):链式删除节点
bool Remove(HashTable* pt, KeyType kx)//删除节点
{
assert(pt != nullptr);
int index = Hash(kx);
HashNode* pr = nullptr;//p的前驱节点
HashNode* p = pt->table[index];
while (p != nullptr)
{
if (p->data.key == kx)
{
if (pr != nullptr)
{
pr->next = p->next;
}
else//pr为空删除第一个节点
{
pt->table[index] = p->next;
}
free(p);
pt->cursize -= 1;
return true;
}
pr = p;
p = p->next;
}
return false;
}
(五):链式清除
void ClearHash(HashTable* pt)//释放节点,哈希表置空
{
assert(pt != nullptr);
for (int i = 0;i < m;++i)
{
while (pt->table[i] != nullptr)
{
HashNode* q = pt->table[i];
pt->table[i] = q->next;
free(q);
}
}
pt->cursize = 0;
}
三:链式哈希解决数组重复赋值问题
问题1:
定义大小为100的整形数组,使用随机函数给数组元素赋值,
赋值范围1-100,并且不能重复
查表法:
问题2:
定义大小为100的整形数组,使用随机函数给数组元素赋值,
赋值范围1-10000,并且不能重复
若使用查表法,开辟空间较大,浪费严重效率底,所以使用链式哈希
#include<stdio.h>
#include<stdlib.h>
#include"HashTable.h"
void Print_Ar(const int* br, int n)
{
if (br == nullptr || n < 1) return;
for (int i = 0;i < n;++i)
{
printf("%10d", br[i]);
if ((i + 1) % 10 == 0)
{
printf("\n");
}
}
printf("\n");
}
int main()
{
int ar[100];
HashTable ht;
Init_Hash(&ht);
int i = 0;
while (i < 100)
{
int tmp = rand() % 100000 + 1;//1.....100
ElemType item = { tmp,nullptr };
if (Insert_Item_New(&ht, item))
{
ar[i] = tmp;
++i;
}
}
Print_Ar(ar, 100);
return 0;
}
四:哈希表的扩充(区别字典的步进式增容)
随着数据量的不断增多,链接的节点越来越多,链越来越长。最终的特点是哈希表退化:哈希表大小m=13,但是每一个链都有10000多个数据节点,查找数据时哈希到某个下标的时间复杂度虽然为O(1),但是节点数据查询时间复杂度由O(1)变成O(n)
慢的根源是链太长,因此就给出了哈希表扩容方案
结构定义:
//链式哈希
#define m 13 //顺序表的大小
#define INC 2 //哈希表增量为原来的2倍
typedef int KeyType;
//typedef char KeyType[20];
typedef struct
{
KeyType key;//关键码
void* ptr;//存放关键码所指向的数据的地址
}ElemType;
typedef struct HashNode
{
ElemType data;
HashNode* next;
}HashNode;
typedef struct
{
HashNode** table;
int capacity;//哈希表容量
int total;//节点总个数
}HashTable;
初始化:
void Init_Hash(HashTable* pt)
{
assert(pt != nullptr);
pt->table = (HashNode**)malloc(sizeof(HashNode*) * m);
if (pt->table == nullptr) exit(EXIT_FAILURE);
pt->capacity = m;
for (int i = 0;i < pt->capacity;++i)
{
pt->table[i] = nullptr;
}
pt->total = 0;
}
一步扩容
bool Inc(HashTable* pt)
{
int incsize = pt->capacity * INC;
int oldsize = pt->capacity;
HashNode** newdata = (HashNode**)malloc(sizeof(HashNode*) * incsize);
if (newdata == nullptr)return false;
for (int i = 0; i < incsize; ++i)
{
newdata[i] = nullptr;//newdata执行一块空间,空间里每个元素都是指针类型
}
pt->capacity = incsize;
for (int i = 0; i < oldsize; ++i)//旧表的内容一个个扫描并且哈希到新表中
{
while (pt->table[i] != nullptr)//后面链接有节点,需要取出来重新计算关键码放到新表中
{
HashNode* s = pt->table[i];
pt->table[i] = s->next;
int newindex = Hash(pt, s->data.key);
s->next = newdata[newindex];
newdata[newindex] = s;
}
}
free(pt->table);
pt->table = newdata;
return true;
}