# 哈希表(散列表)#
1、哈希表的特征
是根据关键码值(Key value)而直接进行访问的数据结构。
它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
这个映射函数叫做散列函数,存放记录的数组叫做散列表。
这里的对应关系 f 称为散列函数,又称为哈希(Hash函数),
散列将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。
哈希表是把Key通过一个固定的算法函数,即所谓的哈希函数转换成一个整型数字,
然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,
将value存储在以该数字为下标的数组空间里。
(或:把任意长度的输入(又叫做预映射),通过散列算法,变换成固定长度的输出,
该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,
不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。)
而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,
并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。
2、哈希表的实现方法:拉链法
数组的特点是:寻址容易,插入和删除困难。
而链表的特点是:寻址困难,插入和删除容易。
一种寻址容易,且插入删除容易的数据结构:哈希表。
哈希表最常用的实现方法——拉链法,我们可以理解为“链表的数组”,如图:
左边很明显是数组,数组的每个成员包括一个指针,指向一个链表的头,
根据元素的一些特征把元素分配到不同的链表中去,再根据这些特征,找到正确的链表和元素。
3.Hash的应用
1、Hash主要用于信息安全领域中加密算法,它把一些不同长度的信息转化为128位编码,
这些编码值叫做Hash值。Hash即找到一种数据内容和数据存放地址之间的映射关系。
2、查找:哈希表,又称为散列,是一种更加快捷的查找技术。
我们之前的查找思路:集合中拿出来一个元素,比较,不等就缩小范围继续查找。
而哈希表是完全另外一种思路:知道key值可以直接计算出这个元素在集合中的位置。
3、Hash表在海量数据处理中有着广泛应用。
Hash Table的查询速度非常的快,几乎是O(1)的时间复杂度。
Q : 如果两个字符串在哈希表中对应的位置相同怎么办?
可以转化成链表来解决,只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK了。
4.散列法
元素特征转变为数组下标的方法就是散列法。下面列出三种比较常用的:
1,除法散列法
最直观的一种,上图使用的就是这种散列法,公式形如: index = value % 16
2,平方散列法
求index是非常频繁的操作,而乘法的运算要比除法来得省时,
所以我们考虑把除法换成乘法和一个位移操作。公式: index = (value * value) >> 28
(右移,除以2^28。记法:左移变大,是乘。右移变小,是除。)
3,斐波那契(Fibonacci)散列法
找出一个理想的乘数,而不拿value本身当作乘数呢。
1)对于16位整数而言,这个乘数是40503
2)对于32位整数而言,这个乘数是2654435769
3)对于64位整数而言,这个乘数是11400714819323198485
这几个“理想乘数”是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,
而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列,
即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377, 610…。
对我们常见的32位整数而言,公式: index = (value * 2654435769) >> 28
如果用这种斐波那契散列法的话,那上面的图就变成这样了:
注:用斐波那契散列法调整之后会比原来的取模散列法好很多。
// 改编自https://blog.youkuaiyun.com/duan19920101/article/details/51579136/
5.例题
Snowflake Snow Snowflakes [ poj 3349 ]
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
/*【Snow Snowflakes】poj 3349
给出雪花六个边的长度,不按顺序,判断是否有重复的雪花
【分析】把每个雪花存入数组,再把每个雪花哈希,如果遇到哈希值相等的雪花,
再一一比较。由于不按顺序,只用判断同构,所以可以用哈希。 */
const int SIZE=100010;
int n,tot,p=99991; //p为Hash的数
int snow[SIZE][6],head[SIZE],next[SIZE];
int H(int *a){ //用每个数据的sum和mul之和作为hash值
int sum=0,mul=1;
for(int i=0;i<6;i++){
sum=(sum+a[i])%p;
mul=(long long)mul*a[i] %p;
}
return (sum+mul)%p;
}
bool equal(int *a,int *b){
for(int i=0;i<6;i++)
for(int j=0;j<6;j++){
bool eq=1;
for(int k=0;k<6;k++) //正序旋转
if(a[(i+k)%6]!=b[(j+k)%6]) eq=0;
if(eq) return 1;
eq=1;
for(int k=0;k<6;k++) //逆序旋转
if(a[(i+k)%6]!=b[(j-k+6)%6]) eq=0;
if(eq) return 1;
}
return 0;
}
bool insert(int *a){
int val=H(a); //得到a的Hash值
for(int i=head[val];i;i=next[i]){
//遍历表头head[val]指向的链表,寻找相同的雪花
if(equal(snow[i],a)) return 1;
}
tot++; //未找到形状相同的,插入这组数据
memcpy(snow[tot],a,6*sizeof(int));
next[tot]=head[val]; //从表头位置插入
head[val]=tot;
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int a[10];
for(int j=0;j<6;j++) scanf("%d",&a[j]);
if(insert(a)){ //用邻接表插入元素
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");
return 0;
}