散列(hash)知识总结

散列是常用的算法思想之一

应用如,给出N个正整数,再给出M个正整数

问:M个数中的每个数是否在N个数中出现过

如果N,M<=10^5,可以用遍历来做,但时间复杂度很大

这时就可以用空间换时间

即设定一个bool型数组hash Table[100010],

这样在一开始读入N个正整数时就进行预处理

代码:

#include<cstdio>
#define maxn 100010
bool hashTable[maxn]={false};
int main()
{
    int n,m,x;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&x);
        hashTable[x]=true;
    }
    for(int i=0;i<m;i++)
    {
        scanf("%d",&x);
        if(hashTable[x]==true)
        {
            printf("YES\n");
        }
        else
        {
            printf("NO\n");
        }
    }
    return 0;
}

如果要求M个欲查询的书中每个数在N个数中出现的次数

那么可以把hashTable换成int型

代码:

#include<cstdio>
#define maxn 100010
int hashTable[maxn]={0};
int main()
{
    int n,m,x;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&x);
        hashTable[x]++;
    }
    for(int i=0;i<m;i++)
    {
        scanf("%d",&x);
        printf("%d\n",hashTable[x]);
    }
    return 0;
}

以上两个问题都有一个特点

就是直接把输入的数作为数组下标来对这个数的性质 进行统计

这是一个很好的用空间换时间的策略

还有以上题目的每个数都不会超过10^5

因此直接作为数组下标是可行的

但是如果输入的可能是10^9大小的整数

或者甚至是一个字符串

就不能直接将他们作为下标了

此时就要用到hash

即为“将元素通过一个函数转化为整数,使得该整数可以尽量唯一地代表这个元素”

元素在转换前为key,转换后就是一个整数H(key)

对于key是整数的情况,常用的散列函数

①直接地址法

A恒等变换

即H(key)=key(开头两个应用,数组下标)

B线性变换

H(key)=a*key+b

②平方取中法

指取key的平方的中间若干位作为hash值(很少用)

③除留余数法

把key除以一个数mod得到的余数作为hash的方法

H(key)=key%mod

通过这个散列函数,可以将很大的数转换为不超过mod的整数

这样就可以将它作为可行的数组下标

(注意:表长TSize必须不小于mod,不然会产生越界)

显然,当mod是一个素数时

H(key)能尽可能覆盖[0,mod)范围内的每一个数

因此一般为了方便起见

下文中取TSize是一个素数

而mod直接取成与TSize相等

但是,通过除留余数法可能会有两个不同的数key1,key2

它们的hash值H(key 1)与H(key 2)是相同的

这样当key1已经把表中位置为H(key 1)的单位占据时

key2便不能再使用这个位置了

我们把这种情况叫做冲突

以下三种解决冲突的方法:

(其中第一种和第二种都计算了新的hash值,又称为开放定址法)

①线性探查法

当得到key的hash值H(key)

但是表中下标为H(key)的位置已经被某个其他元素使用了

那么就检查下一个位置H(key)+1是否被占

如果没有就使用这个位置

否则就继续检查下一个位置(也就是hash值不断+1)

如果检查过程中发现超过了表长

那么就回到表的首位继续循环

直到找到一个可以使用的位置

或者发现表中的所有位置都已被使用

显然,这个方法容易导致扎堆

即表中连续若干个位置都被使用

这在一定程度上会降低效率

②平方探测法

为了尽可能避免扎堆现象

当表中下标为H(key)的位置被占时

将按下面的顺序检查表中的位置

H(key)+1^2、H(key)-1^2、H(key)+2^2、H(key)-2^2、H(key)+3^2……

如果检查过程中H(key)+k^2超过了表长TSize

那么就把H(key)+k^2对表长TSize取模

如果检查过程中出现H(key)-k^2<0的情况(假设表的首位是0)

那么将((H(key)-k^2)%TSize+TSize)%TSize作为结果

等价于将H(key)-k^2不断加上TSize直到出现第一个非负数

如果想要避免负数的麻烦

可能只进行正向的平方探查

可以证明

如果k在[0,TSize)范围内都无法找到位置

那么当T>=TSize时,也一定无法找到位置

③链地址法

此方法不和以上两种方法一样

不计算新的hash值

而是把所有H(key)相同的key连接成一条单链表

这样可以设定一个数组Link

范围是Link[0]~Link[mod]

其中Link[h]存放H(key)=h的一条单链表

于是当多个关键字key的hash值都是h时

就可以直接把这些冲突的key直接用单链表连接起来

此时就可以遍历这条单链表来寻找所有H(key)=h的key

一般来说,直接使用map使用hash的功能

因此除非必须模拟这些方法或是对算法的效率要求比较高

一般不需要自己实现上面解决冲突的方法

知识点来自于《算法笔记》

猜你喜欢

转载自blog.csdn.net/qq_42232118/article/details/81624590