散列(hash)
散列(hash)是常用的算法思想之一,在很多程序中都会有意无意地使用到。
先来看一个简单的问题:给出N个正整数,再给出M个正整数,问这M个数中每个数分别是否在N个数中出现过。
例如N=5,M=3,N个正整数{8,3,7,6,2},欲查询的M个正整数为{7,4,2},于是后者只有7和2在N个正整数中出现过,
而4是没有出现过的。
对于这个问题,最直观的思路是:对每个欲查询的正整数x,遍历所有的N个数,看是否有一个数与x相等。这种
做法的时间复杂度为O(MN),当N和M都很大时,时间复杂度显然太大!
那么该如何做呢?--不妨用空间换时间,即设定一个bool型数组hashTable[100001],其中hashTable[x]=true;表示
整数x在N中出现过,而hashTable[x]=false表示正整数x没有在N中出现过。这样就可以在一开始读入N个正整数时就进
行预处理,即当读入x时,就令hashTable[x]=true;(说明:hashTable数组需要初始化为false,表示初始状态下所有数
都没有出现过)。于是,对M个欲查询的数,就能够直接通过hashTable数组判断每个数是否出现过。显然这种做法的
时间复杂度为(N+M),代码如下:
同样的,如果题目要求M个欲查询的数中每个数在N个数中出现的次数,那么可以把hashTable数组替换为int型,然后
在输入N个数时进行预处理,即当输入的数为x时,就令hashTable[x]++;这样就可以用O(N+M)的时间复杂度输出每个欲查询
的数出现的次数。代码如下:
上面的问题都有一个特点,那就是直接把输入的数作为数组的下标来对这个数的性质进行统计。这是一个很好的用空间
换时间的策略,因为它将查询的复杂度降到了O(1)级别。但是,这个策略暂时还有一个问题--上面题目中的每个数都不会超过
105,因此直接作为数组下标是可行的,但是如果输入可能是109大小的整数,或者甚至是一个字符串(例如"I love you“),就不能
将它们直接作为数组下标了。要是有一种做法,可以把这些乱七八糟的元素转化为一个在能接受范围内的整数,那该多么美好呀!
这样的做法当然是存在的,那就是散列(hash)。一般来说,散列可以浓缩成一句话”将元素通过一个函数转化为整数,使得
该整数可以尽量唯一地代表这个元素“。其中把这个转化函数称为散列函数H,也就是说,如果元素在转化前为key,那么转化后就
是一个整数H(key)。
那么key是整数的情况来说,有哪些常用的散列函数呢?一般来说,常用的有直接定址法,平方取中法,除留余数法等
未完待续……