Hash
哈希表(hash table)的思路是根据关键码值直接访问。
哈希表的思路很简单——根据关键码值直接计算出信息的存储地址。
通常会为hash table 分配一个一维数组的存储空间,二维数组或多维数组用得比较少
但是多维数组应用到hash table上面有一些好处:
在多关键字的情况下 hash函数的设计 显得自然
设hash 函数是 h(K)
Hash表一般支持以下几个操作。
1. 插入:计算h(K)并插入
2. 查找:计算h(K)并访问
3. 删除:增加一些删除标记,以后统一删除(较少使用)
Hash的使用有两个比较需要注意的地方:
1. Hash函数的设计
2. 冲突的解决方法
常用的hash函数:直接取余法,平方取中法,折叠法,ELFhash函数(字符串)
在hash函数方面前人做过很多研究,想要在这方面做突破需要对其有很深刻的理解和很细致入微的研究,所以我们现在只需要直接拿来用就好了^^.
冲突解决方法,一般是开散列法,不赘述。
到目前为止,实现hash表所需要知道的最基本的东西已经差不多了,但是还有一个很关键的问题没有解决——hash是用来干嘛的?(虽然笼统一点可以说 hash是用来解决查询次数多且数据分布相对随机的应用的,但是太过抽象,不够具体)
有一道例题:
例一:促销
促销活动遵循以下原则:“参与本活动的顾客,应将自己的个人信息写在所付账单背面,并将账单投入指定的箱子。在每天的销售结束后,箱子中消费金额最大和最小的两个顾客奖杯选出。消费最多的顾客将得到一笔奖金,奖金的数目等于两张账单金额之差。为了避免因为一次消费而得到多笔奖金,依照以上原则选出的两张账单将不被放回到箱子中,但箱子里剩下的账单可以继续参加第二天的活动。”
你的任务是根据每天投入箱子的所有账单,计算出整个促销活动中超市要付出的奖金总额。本题中约定:
1. 整个促销活动持续了n天,n<=5000
2. 第i天放入的钞票有a[i]张,a[i]<=100000,且s=∑a[i]<=1000000
3. 第i天放入的钞票面值分别是bi[1],bi[2],。。。bi[a[i]],bi[j]<=1000000
分析:这个应用下的数据规模是很大的,采用普通的线性表的话,复杂度差不多是O(ns),也就是 差不多要计算 5000X1000000次!!!!,这么做的话是会慢到爆的。。。
但是用分段hash表的话,可以把复杂度降到O(s+(nm)^0.5)(其中m是总长度,在分段hash表中,每一段的长度是 (m)^1/2),也就是差不多计算 1000000+100000次,比第一种方法快了将近5000倍。。。。。。
上面一道例子反应了hash表的一类应用(我不太会描述)。~~
hash有一种应用是判重,我们知道解决字符串匹配有一些高效算法,例如基于自动机的kmp算法,除了kmp算法之外,还有一个平均性能不错的算法:rabin-karp 算法,某种意义上其实可以把它算作是hash表的应用。(rabin-karp友情链接:http://www.cnblogs.com/feature/articles/1813967.html)
如果熟悉了rabin-karp,那么有一个二维匹配的应用可以用近乎一样的思想解决:
例2:equal squares
在一个N*M的字符矩阵中找到两个相同的子正方形矩阵(可以相交),并使找到的两个子正方形的矩阵边长尽量大。
分析:比较快的解法有两个:二维的KMP和hash表。。。。而且由于这是找子矩阵,所以二维KMP的效果还不如hash好,hash解法的复杂度是 O(mnlog(mn))
Hash判重还有一些其他的应用方式,例如判循环等等,判有根树的同构,判定无根树的同构,判定有向图的同构(这些都能通过hash得到很好的解决,这三个判定图的同构问题的解法复杂度都是O(NlogN))等等。。不一一列举了
写到这里,越发觉得hash的神奇了,因为我很难像并查集那样概括出它的用途,它似乎并没有太明显的针对性,这就表示,它的应用可能会很巧妙(比如和编码联系在一起的时候的hash函数 的设计,而编码本身就是一个很奇妙的东西。。。)。。。
好吧,才太疏学太浅了。。。。。。希望能在在以后的学习中对hash有更深入的理解。