哈希表与离散化

一、字符串哈希

1. 什么是哈希

        哈希算法是:通过哈希函数将字符串、较大的数等转换为能够用变量表示的或者是直接作为数组下标的数,通过哈希算法转换到的值,称之为哈希值。哈希值可以实现快速查找和匹配。

        比如:用数组下标计数法,统计一个字符串,每种字母出现的次数就是一个简单的哈希,将每个字母都映射为了对应的 A s c i i 码。


2. 如何构造哈希

        原理:将字符串中的每一个字母都看作是一个数字(例如:从 a - z,视为 1 - 26 );将字符串视为一个是 b 进制的数。(注意:不能映射为 0,因为如果a 为 0,那么 a、aa、aaa 的值将都为 0)

        比如:可以将字符串 s = " abcd "视为 26 进制的整数,则可以计算出:hash(s)= 1 * 26 的立方 + 2 * 26 的平方 + 3 * 26 的一次方 + 26 的 0 次方,如果字符串很长,h ( s )很容易超出 long long 范围内。

        选取两个合适的互质常数 b 和 h ( b < h ),其中h要尽可能的大一点,为了降低冲突(不同字符串计算到同一个哈希值)的概率。

        一般来说:b 取 131 或 13331,h 取 2^{64},最终产生的哈希值的冲突的概率极低。

        假设字符串 C=c1c2c3...cm,定义哈希函数:

        H(c)=(c_{1} * b^{m-1} + c_{2}*b^{m-2} + ... +c_{m} * b^{0}) mod(h)


3. 滚动哈希优化

        如果针对一个很长的字符串,判断其中两个长度为 len 的子串是否相同,如果采用O( len )的时间复杂度计算出对应的子串 hash,那和直接取出子串比较的时间复杂度并无差异,因此我们需要使用滚动哈希优化的技巧,可以在O( 1 )的时间复杂度下取出子串的 hash 值。

       (1)滚动计算到前缀哈希 h ( k )

          设:h ( k )为字符串的子串的哈希值,(先不考虑取 MOD ):

           h(k+1)=h(k)*b+c_{k+1}; 

          类比 10 进制理解该公式,比如 10 进制的 12345,取出前 3 个数123,如果要去前 4 个数,可以使用:123 * 10(进制)+ 4(c_{k+1})= 1234 的方法来取出。


      (2)利用前缀哈希 H ( k ) 计算区间哈希值。

          设:

          H(R)=c_{1}*b^{R-1}+...+c_{L-1}*b^{R-L+1}+c_{r}*b^{0}

          H(L-1)=c_{1}*b^{L-2}+...+c_{L-1}*b^{0}

          H(L,R)=H(R)-H(L-1)*b^{r-l+1}

          因此,求 L ~ R 之间的哈希值:h[R]-h[L-1]*b^{r-l+1}

          由上述公式可知,只需要预处理出 b^{m},就可以在O(1)的时间内求得任意子串的哈希值。


       (3)时间复杂度

           综上,如果在一个长度为 n 的字符串中,任意取长度为 m 的子串进行匹配,时间复杂度为O(n+m)。


二、哈希表

1. 哈希表原理

(1)使用数组下标直接标记元素

        哈希表(也叫散列表):是一种高效的、通过把关键码值映射到表中的一个位置来访问记录的数据结构。

        类似字符串,查找的时间复杂度是常数时间,缺点是:需要消耗更多的内存。

        现在要存储和使用下面的线性表:A(12,83,284,49,183,13491,58)

        为了用O(1) 的时间实现查找,可以开一个一维数组 A[ 1 ... 13491 ],使得 A[ key ] = key,但显然造成了空间上的很大浪费,尤其是数据范围很大时。


(2)除余法构造哈希值

        为了使空间开销减少,我们可以使用第二种方法加以优化,设计一个哈希函数 H(key)=key mod 17,然后令 A[ H ( key ) ] = key,这样一来定义一个一维数组 A[0 ... 16]就以足够,这种方法就是哈希表。

        但是这样会造成哈希冲突,比如H(2)和H(19)的值都是 2.

        这里与字符串 Hash 有所不同,可能不论我们怎样选用哈希函数,还是很难避免产生冲突。

        因此我们考虑对每一个哈希值记一个链表(其实也就相当于邻接表),把所有等于同一个哈希值的数字都存储下来,而查询只要遍历对应的链表即可,这样实际复杂度取决于查询的链表长度,也可以看做是常数级。

        例如:我们定义哈希函数H( x ) = x mod 16,对数组A(12,83,284,49,183,13491,58)进行哈希运算后,插入一些数据的效果如下图:

        


(3)哈希函数的构造

        取余法构造哈希:H(key)= key % b,为了避免冲突,一般为能够存储下来并且尽量大的素数(一般情况下我们根据空间取 10 ~ 6 左右的素数)。一般地说如果 b 的约数越多,那么冲突的几率也就越大。

2. 使用数组模拟邻接表

(1)插入关键操作:

v = hash(x);//计算 x 的哈希值
idx++;
e[idx]=x;
ne[idx]=h[v];
h[v]=idx;

(2)查找关键操作:

int v = hash(x);//计算 x 的哈希值
//循环链表
for(int i=h[v];i!=0;i=ne[i]){
    if(e[i]==x){
        return 1;
    }
}

例如:读入整数 2 5 6 8 3 11,假设 h[ x ] = x % 6。

则:每个元素的哈希值是 2 5 0 2 3 5。

存储数组如下:

element 数组:按读入的顺序,存储每个元素的值。

  next 数组:next [ i ]存储和element [ i ]哈希值相同的上一个数的编号。


三、离散化

1. 什么是离散化

        离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小,例如:

        原数据:1,999,100000,15:处理后:1,3,4,2;

        原数据:{100,200},{20,50000},{1,400};

        处理后:{3,4},{2,6},{1,5};

        离散化本质上可以看成是一种特殊的哈希,其保证数据在哈希以后仍然保持原来的全偏序关系,用于解决:影响最终结果的只有元素之间的相对大小关系,这一类的问题。

2. 离散化的步骤

        离散化存在的步骤:

        (1)数组中有重复的元素,因此需要去重复;

        (2)计算出数组中每个值a [ i ]离散化后的值是多少:二分:

离散化数组:

离散化 vector:


最后

制作不易,点个赞吧!!!

制作不易,点个赞吧!!!

制作不易,点个赞吧!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值