由于多次查询有必要用哈希,所以今天来说说。
哈希表(c++里叫unordered_map),O(1)时间内完成,利用数组索引快速搜索的特性,哈希表依赖于数组。
再写哈希之前要解决这两个问题:
(1)定散列函数(按照什么方式分组):求整取余法index=value%M,M
是素数且>元素个数。
(2)哈希冲突(两个数据抢占同一个位置):开放定址法,拉链法(最常用的解法)。
开放定址法:线性探测:沿着冲突地方一个一个向后找,有位置就放。二次探测:沿着冲突位置,
,
,
......(不希望产生冲突就得组多点,装载因子
=元素个数/表长<0.8)。
拉链法:和已有的数据构成链表,用头增法(因为头增法O(1))。
线性探测优化(闭散列):趋近于0.8扩容,旧表向新表分批次迁移,所以可能同时存在,查的时候先查新表,再查旧表。
拉链法优化(开散列):趋近1扩容,把对应的节点迁移过来,原来的节点没开辟新空间,如果某一个位置产生冲突特别多,链表很长,就需要扩桶(M),所有数据按新的规则重新分桶。
开放定址法,拉链法优缺点:
1.拉链法中各链表的节点空间是动态申请的,可以用于造表前无法确定表长。
2.开放定址法在数据规模较大时浪费很多空间,拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,节省空间。
3.用拉链法构造的散列表中,删除操作容易实现,开放定址法删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填入的数据的查找路径,只能在删除结点上做标记,不能真正删除。
4.数据规模较小,开放定址法节省空间,拉链法指针需要额外空间。
解决完这两个问题,我们就可以创建哈希表了,我们用拉链法创建。
由于链表知道表头就能访问,所以我们要创建的是指针数组。
步骤:
(1)定义一个链表结构体。
(2)申请指针数组并赋空。
(3)元素入组:元素根据散列函数对应相应组里,申请节点,单向链表表头添加。
(4)搜索:找到当前元素的组,链表的遍历。
#include<stdio.h>
#include<string.h>
#define M 13
typedef struct Node
{
int val;
struct Node* next;
}List;
List** CreateHashTable(int arr[], int len)
{
if (arr == NULL || len == 0) return NULL;
//申请表头
List** phash = (List**)malloc(sizeof(List*) * M);
//按字节赋值,多字节只能0或-1
memset(phash, 0, sizeof(List*) * M);
//元素入表
int index;
for (int i = 0; i < len; i++)
{
//获取索引位置
index = arr[i] % M;
//节点空间申请
List* ptem = NULL;
ptem = (List*)malloc(sizeof(List));
ptem->val = arr[i];
//头添加
ptem->next = phash[index];
phash[index] = ptem;
}
return phash;
}
void Hashserch(List** phash,int target)
{
if (phash == NULL) return;
//索引
int index = target % M;
List* thash = phash[index];
while (thash)
{
if (thash->val == target)
{
printf("success\n"); return;
}
else
thash = thash->next;
}
printf("fail\n");
}
int main()
{
int arr[] = { 124,45,67,7,8,9,33,22,11,33,1};
List** phash = CreateHashTable(arr, sizeof(arr) / sizeof(arr[0]));
Hashserch(phash, 1);
return 0;
}