哈希表的简单应用

来源:互联网 发布:排队叫号软件 编辑:程序博客网 时间:2024/05/25 18:10

哈希表对数的处理

需要找到一个数在全部序列中的位置时,暴力方法就是把存储数的数组从头到尾find一遍,显然最差效率是O(n)。然而如果把该数作为新开一个数组的下标,而新数组中存储的是其在原数组中的位置,即a[n]存放n在原数组中是第几个(当然这种方法实际上很常用了,只是举例),那么这其实就是一个简单的hash表,简单的不像话。

其缺点就是,新数组要囊括扩所有可能的数,即0<n<=m,新数组就得开到m那么大。如果虽然n的范围是那么大,但是实际上元素的个数远小于m个,那么就造成了很大的空间浪费。为了把开的空间压缩一下,就需要对哈希进行优化(在这里再解释一下,哈希不过是建立了两类元素的映射关系,上面提到的映射关系就是位置;这个映射关系还可以是函数等,一般哈希就是个函数)。

       因为这个哈希表是要存数的,所以不能在一个位置有两个数,从数学的角度来说,就是函数需要保证有一对一的映射关系,否则就不叫函数了。所以说我们的任务就是建立那个映射的函数,以便在存储数据时压缩空间。

例:数据范围 0 <n < max ;

法一:直接定址法

以n本身作为映射的关键字,存储在数组 a[max+1]中(当然要是非要把n加上个数什么的也无所谓),这种方法基本没法用,虽然稳定均匀,但是极其耗费空间。

法二:除余法

取key = n % t 作为n这个数的关键字,将其存在 a[key]里,这样数组开a[k+1]即可。为了使数组a中重复的最少,一般 t 是选取小于 max 的最大质数(在此补充,既然是压缩了空间了,就不能保证将所有的情况都映射完了以后没有一个关键字对应两个乃至多个。我们所能做的,就是尽量避免)。这样的话,能最大程度地避免关键字对应多个值(这个很好理解,一个素数能对应跟它一样多的值,一个合数就不行)。当然这些数据比较大的时候,我们也不知道最接近的素数是什么,那就大胆的随手打一个数,一定要乱。

当然,这需要按情况而定。有时候一个素数不不够的,所以可以开两个素数,那么数组也就开成了a[ t_1 + 1 ][ t_2 + 1],自然t1 = n % t_1 作为第一位坐标,t2 = n % t_2 作为第二位坐标。这样更准确,但是也需要考虑空间问题,不能弄出来空间超范围。

法三:数字分析法

实际上,所谓的数字分析法更大的用处是在针对字符串上。数字分析法指的是对数据的逐位分析,然后选取其最多不同点的一个特点来进行映射。举个例子,对于一些很大的但是总量不多的数,取一些比较小的质数来一个个试,试出哪个重复的最少(针对全部值)。

       同样的思想应用在字符串上也很好。字符串当然不能去模一个数了,那么我们可以将字符串们的一端对其,然后寻找不同点比较少的那些位置(当然首要考虑的是位数,如果这个位数不会太大并且相似度比较低的话也要作为一维)(为什么不考虑相似度最低呢?因为寻找低相似度也是要降低时间效率的,这个方面要自己把握,因为多开一维是用空间换时间而已)。

法四:多次分析

所谓的多次分析就是在对一些数进行取模之前先平方或其他什么的,或对一些字符串比较之前先颠倒什么的。具体自己掌握,确实不用多说。

法五:折叠法

所谓折叠法,对于数字是这么样:我们的数组最大是p位,那么就将所给的数拆成没p个或p-1个小段(比如p=3 ,拆123456789成12,34,56,78 或者12,23,34,45,56,67,78,89,依情况而定),然后全部加起来,如果不够这个位数或多于这个位数就重复一遍这个步骤(在实现上这里需要提下,Pascal的同学比较沾光了,因为pascal可以直接定一个只有这么多位数的数组,比如a:array[1000..9999 ]of longint ,C++就有点麻烦了,就算用STL的map也是有让人无奈的插入效率)。这个折叠法对于字符串我实在是想不出来了。

法六:基数转换法

基数法,就是将现在10进制的数看成别的进制的,重新转化成10进制(这样显然数都会更大),或者说把10进制的数直接转化成别的进制的(本质上讲,和取模的区别不大);然后用数字分析法搞它。

 

写了这么多,其实这些方法都是有很大的共同点的:为了节约空间和查找时间,才有了Hash。因此我们需要节省空间(尽量少开)和节省时间(把hash函数不要做的超时),代价是降低准确性。上面都是些常用的方法,有时候针对某些题都会有某些特殊的方法,这有点像状态压缩了。正因为有重复的情况出现,所以精确度不是百分百。为了提高精确度保证AC,我们需要正确记录所有的量。一般有下面这么几种方式:

                     在实际编程中,为了数据的稳定,我们一般另外开辟一个区域来接收“溢出”的那些数据。

                     在一些小题中,我们可以把哈希表的每一位都设计成链表的形式(如果有时间还可以写成二叉查找树,不过旋转求平衡什么的就不用了把= = ),每当有第二个数就连上去,取数时找一找就好了,会快很多很多。(比如A[HASH] 表示hash值为HASH的原数据 你把A变成链表 就可以拉链了 所有hash值为HASH的数据都可以保存到A[HASH]里头了 比如要判断X是否出现过,如果hash(X)=HASH,那么就遍历A[HASH](这里头记录了原数据),看看是不是出现过X了。所以A[HASH]必须要保存的就是原来数据,然后根据需要添加其他要记录的数据。)

原创粉丝点击