我所认识的Hash

作者: shawenlx | 来源:发表于2016-12-14 11:03 被阅读953次

前言

​ 关于Hash表,是我们经常会碰到的数据结构。大多数时候,它能高效地解决我们的一些实际问题。当然,多数情况下时间和空间正如鱼和熊掌,不可兼得。只有在特定的情况下,我们去选择适合当前需求的数据结构和算法。

​ Hash表本质是可以理解为键值对的集合。key-value一一对应。


字符Hash的简单应用

  • 先来看这样一个简单的例子,有一个字符串,仅有大写字母’A'~'Z'组成,我们需要对这个字符串中所有的字母进行统计。然后输出对应的表,如 ACAADEB, 则A->3, B->1, C->1, D->1, E->1。
  • 那么如何来做这个统计呢,这时候Hash表就可以派上用场了。字符‘A'对应的ASCII码为65,那么根据Hash的思想,我们可以把一个数组中的下标映射为字符,将下标对应的值中存储我们的统计结果,比如Hash['A'] = 3;
int hash[26];
string str = "ACAADEB";
void count_char_on_string(string &str) {
    for (int i = 0; i < str.length(); ++i) {
        ++hash[str[i] - 'A'];
    }
}

通过以上这个代码,我们就能将字符串中的字符一一统计下来。贯彻的思想就是将字符映射成数组下标


字符串Hash

  • 既然能够将字符映射成整数,那么肯定有办法将字符串也映射成整数。
  • 关于字符串hash算法,有很多种,可以通过这篇博客了解,不同的字符串hash算法的对比。各种字符串Hash函数比较
  • 此处,我仅仅以BKDRHash算法作为例子,核心的思想是通过一个选择器,我们也成为种子(seed),通常选择种子应该是一个质数,如31, 131,1313等,这样的好处在于减少不同字符串映射为相同整数的冲突。通过一个计算公式,将不同的字符串进行转化为数字。
// BKDR Hash Function
unsigned int BKDRHash(char *str) {
    unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
    unsigned int hash = 0;
    while (*str) {
        //每转化一位字符,用当前的hash值 * seed,在加上字符的ASCII码。
        //hash * seed, 此处我们可以理解为当前字符串在前一个字符串hash值的基础上,偏移了一个种子的数级距离。
        hash = hash * seed + (*str++);
    }
    return (hash & 0x7FFFFFFF); //最后用hash & Ox7FFFFFFF 确保hash值在unsigned int 范围中。
}

利用字符串Hash可以解决很多问题,可以做字符串匹配。效率也蛮高的,相比KMP算法或者后缀数组和AC自动机等数据结构,它也不失为一种巧妙的办法。我们可以通过将每一位计算得到的hash偏移值存储在一个数组里,然后通过计算偏移得到子串的hash值,在和匹配串的hash值进行对比,如果相同,则说明匹配成功。

typedef unsigned long long ull;
const int MOD = 0x7FFFFFFF;
ull hash[1000]; //假设字符串长度小于1000
ull char_hash[1000]; //存储对应字符的hash值
inline ull cal_hash(string &str, const int l, const int r) {
    if (l == 0) return cal_hash[r];
    ull seed = 131;
    hash[0] = 1;
    for (int i = 1; i < str.length(); ++i) {
      hash[i] = hash[i-1] * seed;   //计算偏移
    }
    //用当前r对应的char_hash值-减去当前子串前一部分多计算的偏移hash值。就能得到字串的hash值了。
    return (char_hash[r] - char_hash[l-1] * hash[r-l+1]) + MOD) % MOD;
}

解决字符串Hash冲突

  • 或多或少,BKDRHash算法在极小的几率下会出现Hash冲突,解决的办法有很多,多数时候,我们采用邻接表来解决这个问题。
  • 由于算法涉及&操作,会有导致冲突的可能。在多串匹配的情况下,我们需要将所有匹配字符串计算得到的hash值,存放到邻接表中,所谓的邻接表就是数组套链表。将计算到的匹配串hash值作为数组中的值,并加入到对应的链表中,如果我们计算到的hash值出现冲突的情况,就往对应链表中加入新的节点。在后续匹配的情况下,进行查找链表并做多一次简单的匹配即可。

关于Hash的一个有趣应用

  • 有这么一个4 * 4的棋盘,棋子有两种状态,黑色或白色。我们这个时候也可以通过Hash来记录当前棋盘的信息。

那么对应的棋盘,我们用1表示黑色,用0表示白色。那么就有:

imgimg

这个图转化成矩阵就是:

​ 1 0 1 0

​ 0 0 0 0

​ 1 1 0 1

​ 1 0 0 1

那么如何转化成整数来记录棋盘呢?

​ 2^0 2^1 2^2 2^3

​ 2^4 2^5 2^6 2^7

​ 2^8 2^9 2^10 2^11

​ 2^12 2^13 2^14 2^15

最后得到的棋盘我们可以通过判断当前棋子颜色,如果是黑色,则在对应位n上加上对应的2^n, n可以通过行列式计算出来。

int state[4][4]; //预处理记录棋子状态
unsigned int hash = 0;
for (int i = 0; i < 16; ++i) {
    hash += state[i/4][i%4] * (1 << i);
}

这样我们就把棋盘信息转化成一个整数了。


最后

​ 关于Hash表仍有很多的应用,还有一种应用是搜索领域,如何将互联网上大量的字符串存储,这里推荐大家了解一下字典树,它也是基于Hash算法的思想的数据结构。在iOS中,NSDictionary就是基于Hash的实现。以后有机会会深入CF框架看看实现源码,到时候再来分享。

相关文章

  • 我所认识的Hash

    前言 ​ 关于Hash表,是我们经常会碰到的数据结构。大多数时候,它能高效地解决我们的一些实际问题。当然,多数...

  • 我所认识的我

    我没读过几本书,没有坚持下来什么有意义的事,我就这样在这个世界里活着,没有什么存在感,也没有什么激情。每天跟自己罗...

  • 我所认识的张爱玲

    我喜欢的名人叫张爱玲,张出生于1920年卒于1995年9月8日出生在显赫家庭原名张英,在10岁时她改名为张爱玲,父...

  • 我所认识的自己

    时光飞逝,岁月如梭,一转眼,我已经20岁了,快要“奔三”啦。小的时候懵懵懂懂,做事全凭本能全看心情,很多事情做过了...

  • 我所认识的杨子

    昨天在玉泉路附近看了一个需要二次装修的房子,量完房子以后,跟业主初步达成要找我合作的意向,等我回去给她出好设计方案...

  • 我所认识的改变

    所以,改变无处不在,这是我所认识的改变的第一点! 让我们把焦点放在关于人的改变上,我很喜欢一段话:上帝,请赐予我平...

  • 我所认识的死亡

    人到中年,有种看透的感觉,经历得越多,越来越明白,这世上没有不“死”的,但没想到原来死还有那么多种。 ...

  • 我所认识的罗永浩

    那年我还在做培训班。有个学妹说有一个培训机构名字的逼格很高,叫作老罗和他的朋友们。那是我第一次听说罗永浩。 当时老...

  • 我所认识的网络

    TCP服务 TCP服务在网络应用中十分常见,目前大多数应用都是基于TCP搭建而成。 TCP的全名为传输控制...

  • 我所认识的所有

    那又怎么样,我找不到。

网友评论

    本文标题:我所认识的Hash

    本文链接:https://www.haomeiwen.com/subject/aehldttx.html