字符串哈希(BKDR_Hash)

  BKDR_Hash函数把一个任意长度的字符串映射成一个非负整数,并且其冲突 [ 1 ] ^{[1]} [1]概率几乎为零。

  取一固定值 P P P,把字符串看作 P P P进制数,并分配一个大于 0 0 0的数值,代表每种字符一般来说,我们分配的数值都远小于 P P P。例如对于小写字母构成的字符串,可以令 a = 1 , b = 2 , … … , z = 26 a = 1, b = 2,……,z = 26 a=1,b=2,……,z=26。取一固定值 M M M,求出该P进制数对 M M M的余数,作为该字符串的 H a s h Hash Hash值。

  一般来说,我们取 P = 131 P = 131 P=131或 P = 13331 P = 13331 P=13331,此时 H a s h Hash Hash值产生冲突 [ 1 ] ^{[1]} [1]的概率极低,只要 H a s h Hash Hash值相同,我们就可以认为原字符串是相等的。通常我们取 M = 2 64 M = 2^{64} M=264,即直接使用 u n s i g n e d l o n g l o n g unsigned\,\,\,long\,\,\, long unsignedlonglong类型存储这个 H a s h Hash Hash值,在计算时不处理算术溢出问题,产生溢出时相当于自动对 2 64 2^{64} 264取模,这样可以避免低效的取模( m o d mod mod)运算。

  除了在极特殊构造的数据上,上述 H a s h Hash Hash算法很难产生冲突 [ 1 ] ^{[1]} [1],一般情况下上述 H a s h Hash Hash算法完全可以出现在题目的标准解答中。我们还可以多取一下恰当的 P P P和 M M M的值(例如大质数),多进行几组 H a s h Hash Hash运算,当结果都相同时才认为原字符串相等,就更加难以构造出使这个 H a s h Hash Hash产生错误的数据。

  对字符串的各种操作,都可以直接对 P P P进制数进行算术运算反映到 H a s h Hash Hash值上。

[1] : 冲突指的是两个不同字符串通过上述哈希函数计算出来的哈希值几乎不会相等(冲突)。并不是说映射到哈希表上时,键(位置)不会冲突。因为哈希值在映射时还要进行取模运算,故有概率发生哈希冲突。
  
哈希冲突不可避免

  
  

BKDR_HASH的计算过程

  • 理解 ( P P P取一个质数)
s t r str str NULL a b c d e f
H a s h Hash Hash 0
下标 0 1 2 3 4 5 6
Hash[0] = 0;
Hash[1] = Hash[0] * P + str[1];
Hash[2] = Hash[1] * P + str[2];
Hash[3] = Hash[2] * P + str[3];
…………………………
Hash[6] = Hash[5] * P + str[6];最终hash[6]存放的就是整个字符串的哈希值。
  • 辅助理解 ( P P P取10,看成十进制数)

将字符串“654321”===>十进制

s t r str str NULL 6 5 4 3 2 1
H a s h Hash Hash 0
下标 0 1 2 3 4 5 6
Hash[0] = 0;
Hash[1] = Hash[0] * 10 + str[1] = 6
Hash[2] = Hash[1] * 10 + str[2] = 65
Hash[3] = Hash[2] * 10 + str[3] = 654
…………………………
Hash[6] = Hash[5] * 10 + str[6] = 654321最终hash[6]存放的就是整个字符串的哈希值(前缀和方式计算)。

  
  

子串的hash值

   子串区间 [ l , r ] 子串区间[l, r] 子串区间[l,r]
   H a s h [ r ] − H a s h [ l − 1 ] ∗ p ( r − l + 1 ) Hash[r] - Hash[l - 1] * p ^{(r - l + 1)} Hash[r]−Hash[l−1]∗p(r−l+1)
  
  如果我们已知字符串 S S S的 H a s h Hash Hash值为 H ( S ) H(S) H(S), 那么在 S S S后添加一个字符 c c c构成的新字符串 S + c S+c S+c的 H a s h Hash Hash值就是 H ( S + c ) = ( H ( S ) ∗ P + v a l u e [ c ] ) m o d M H(S +c)= (H(S)*P + value[c]) \,\,mod\,\,M H(S+c)=(H(S)∗P+value[c])modM。其中乘 P P P就相当于 P P P进制下的左移运算, v a l u e [ c ] value[c] value[c] 是我们为 c c c选定的代表数值。

  如果我们已知字符串 S S S的 H a s h Hash Hash值为 H ( S ) H(S) H(S), 字符串 S + T S+T S+T的 H a s h Hash Hash值为 H ( S + T ) H(S+T) H(S+T),那么字符串 T T T的 H a s h Hash Hash值 H ( T ) = ( H ( S + T ) − H ( S ) ∗ p l e n g t h ( T ) ) m o d M H(T)= (H(S + T)- H(S)* p^{length(T)})\,\, mod\,\, M H(T)=(H(S+T)−H(S)∗plength(T))modM。这就相当于通过 P P P进制下在 S S S后边补 0 0 0的方式,把 S S S左移到与 S + T S+T S+T的左端对齐,然后二者相减就得到了 H ( T ) H(T) H(T)。

  

例如,S = “abc”, c = “d”, T = “xyz”,则:

S S S表示为 P P P进制数: 1 2 3 1\,2\,3 123

H ( S ) = 1 ∗ p 2 + 2 ∗ P + 3 H(S)= 1*p^2+ 2*P+3 H(S)=1∗p2+2∗P+3

H ( S + c ) = 1 ∗ p 3 + 2 ∗ p 2 + 3 ∗ P + 4 = H ( S ) ∗ P + 4 H(S+c)= 1*p^3+2*p^2+3*P+4= H(S)*P+4 H(S+c)=1∗p3+2∗p2+3∗P+4=H(S)∗P+4

S + T S+T S+T表示为 P P P进制数: 1 2 3 24 25 26 1\,2\,3\,24\,25\,26 123242526​

H ( S + T ) = 1 ∗ p 5 + 2 ∗ p 4 + 3 ∗ P 3 + 24 ∗ p 2 + 25 ∗ P + 26 H(S +T)= 1*p^5+2*p^4+3*P^3+ 24*p^2+ 25*P+ 26 H(S+T)=1∗p5+2∗p4+3∗P3+24∗p2+25∗P+26

S S S在 P P P进制下左移 l e n g t h ( T ) length(T) length(T) 位: 1 2 3 0 0 0 1\,2\,3\,0\,0\,0 123000

二者相减就是 T T T表示为 P P P进制数: 24 25 26 24 \,25 \,26 242526

H ( T ) = H ( S + T ) − ( 1 ∗ P 2 + 2 ∗ P + 3 ) ∗ P 3 = 24 ∗ P 2 + 25 ∗ P + 26 H(T)=H(S+T)-(1*P^2 +2*P+3)*P^3= 24*P^2 +25*P+26 H(T)=H(S+T)−(1∗P2+2∗P+3)∗P3=24∗P2+25∗P+26

  
  

代码实现

散列函数构造方法:BKDR_Hash

冲突解决方法:链式地址法

/*************************************************************************> File Name: hash_table.c> Author: Luzelin************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct Node {     //表结点定义char *str;struct Node *next;
} Node;typedef struct HashTable {   //哈希表定义Node **data;int size;
} HashTable;Node *init_node(char *str, Node *head) {     //实例化新结点Node *p = (Node *)malloc(sizeof(Node));p->str = strdup(str);     //strdup()在内部调用了malloc()为变量分配内存,不需要使用返回的字符串时,需要用free()释放相应的内存空间,否则会造成内存泄漏。返回一个指针,指向为复制字符串分配的空间;如果分配空间失败,则返回NULL值。p->next = head;     return p;        //配合接收返回值的对象完成链表头插
}HashTable *init_hashtable(int n) {     //哈希表初始化HashTable *h = (HashTable *)malloc(sizeof(HashTable));h->size = n << 1;         //将哈希表长度设置为给定上限的两倍防溢出h->data = (Node **)calloc(h->size, sizeof(Node *));   //实例化Node*类型数组(指针数组)return h;
}int BKDRHash(char *str) {   //计算字符串的hash值// 也可以直接把hash定义成unsigned类型int seed = 31, hash = 0;   //31优质乘子(前提必是质数)for(int i = 0; str[i]; ++i)  hash = hash * seed + str[i];   //前缀和方法计算return hash & 0x7fffffff;    //处理负数情况    0x7fffffff-->0 + 31个1
}int insert(HashTable *h, char *str) {   //在哈希表h中插入一个字符串strint hash = BKDRHash(str);          //计算出该字符串的哈希值int ind = hash % h->size;          //用除留余数法确定在哈希表中的位置h->data[ind] = init_node(str, h->data[ind]);    //在目标位置进行头插return 1;
}int search(HashTable *h, char *str) {         //在哈希表h中进行查找strint hash = BKDRHash(str);                 //计算出该字符串的哈希值int ind = hash % h->size;               //用除留余数法确定在哈希表中的位置Node *p = h->data[ind];                   //取出目标位置的头结点while(p && strcmp(p->str, str)) p = p->next;     //寻找return p != NULL;
}void clear_node(Node *node) {        //释放结点if(node == NULL)  return ;Node *p = node, *q;while(p) {q = p->next;free(p->str);free(p);p = q;}
}void clear_hashtable(HashTable *h) {   //释放哈希表内存if(h == NULL)  return ;for(int i = 0; i < h->size; ++i) {clear_node(h->data[i]);}free(h->data);free(h);return ;
}int main() {int op;#define max_n 100char str[max_n + 5] = {0};HashTable *h = init_hashtable(max_n + 5);   //初始化哈希表while(~scanf("%d%s", &op, str)) {           //0 插入     1 查找switch(op) {case 0:printf("insert %s to HashTable\n", str);insert(h, str);break;case 1:printf("search %s from HashTable result = %d\n", str, search(h, str));break;}}#undef max_nclear_hashtable(h);return 0;
}

字符串哈希(BKDR_Hash)相关推荐

  1. ELFhash - 优秀的字符串哈希算法

    原 ELFhash - 优秀的字符串哈希算法 分类:算法杂论算法精讲数据结构 (1424)  (2) 1.字符串哈希: 我们先从字符串哈希说起 在很多的情况下,我们有可能会获得大量的字符串,每个字符串 ...

  2. 【CodeForces】961 F. k-substrings 字符串哈希+二分

    [题目]F. k-substrings [题意]给定长度为n的串S,对于S的每个k-子串$s_ks_{k+1}...s_{n-k+1},k\in[1,\left \lceil \frac{n}{2} ...

  3. 138. 兔子与兔子【字符串哈希】

    很基础的字符串哈希 #include<bits/stdc++.h> using namespace std; typedef unsigned long long int ull; con ...

  4. Singing Superstar 字符串哈希-map操作

    题意 : 给一长度 < 1e5 的字符串s,q < 1e5次询问,每次问一个长 < 30 的串 t 在s中出现的次数,且t不可重叠. 例 : "abababa"中 ...

  5. 中石油训练赛 - DNA(字符串哈希)

    题目链接:点击查看 题目大意:给出一串只由A,C,G,T组成的字符串,再给出一个数字k,问每个长度为k的连续子串,出现的次数最多是多少次 题目分析:O(n)哈希一下,O(n)更新一下用无序map维护的 ...

  6. HDU - 3613 Best Reward(字符串哈希)

    题目链接:点击查看 题目大意:给出一个字符串,每个字母都有一个贡献值,现在要将这个字符串拆成两个子串,如果子串是回文串的话,贡献就是其中每个字母的贡献和,现在问贡献最大为多少 题目分析:很简单的一道回 ...

  7. 怎么把字符串变成数组_字符串哈希:从零开始的十分钟包会教程

    大家吼啊!这是我下定决心写专栏以来的第二篇文章,请大家多多资瓷!!同样我们先以上次的话起头吧! 恭喜你找到了这篇博客!虽然这个标题看起来非常像是nc营销号的标题但是!请相信我一次这是真的!如果不行请随 ...

  8. [Leetcode][程序员面试金典][面试题17.13][JAVA][恢复空格][动态规划][Trie][字符串哈希]

    [问题描述][中等] [解答思路] 1. 动态规划 动态规划流程 第 1 步:设计状态 dp[i] 表示字符串的前 i 个字符的最少未匹配数. 第 2 步:状态转移方程 假设当前我们已经考虑完了前 i ...

  9. 【牛客 -2A】矩阵(二分,字符串哈希)

    题干: 给出一个n * m的矩阵.让你从中发现一个最大的正方形.使得这样子的正方形在矩阵中出现了至少两次.输出最大正方形的边长. 输入描述: 第一行两个整数n, m代表矩阵的长和宽: 接下来n行,每行 ...

最新文章

  1. spring boot 学习(二)spring boot 框架整合 thymeleaf
  2. Keil uVision5 下载程序 add flash programming algorithm选项缺少需要的下载算法的解决办法
  3. 被低估的css滤镜,你所不知道的 CSS 滤镜技巧与细节
  4. Android网络通信的六种方式示例代码
  5. android封装oauth2,Android AccountAuthenticator和OAuth2
  6. 转债---Pregel: A System for Large-Scale Graph Processing(译)
  7. 【转】ELK 日志分析系统
  8. sphinx php mysql_Sphinx+MySQL+PHP 12亿DNS数据秒查
  9. linux下怎么解压tar.xz,Linux下解压.tar.xz格式文件的方法
  10. zb_system login.php,zblog后台登录地址怎么修改?
  11. c语言 取结构体地址,结构体赋值,对用不用取地址符有些困惑?
  12. 位运算实现加减乘除运算——超详细C语言描述
  13. 6 生僻字_抖音《生僻字》的字词成语解释完整版
  14. 初中数学知识点总结_初中数学知识点总结
  15. 大数据内涵-“岂止于大”
  16. 电网设备股集体上涨,国家电网称将推进电网数字化转型
  17. jquery省市县三级联动
  18. Elastic认证考试:备考环境完全指南
  19. sublime使用简介
  20. 程序员的节日1024

热门文章

  1. fio 2种画图方法 fio_generate_plots 和 gfio
  2. CPU究竟跑得有多快?
  3. 【无标题】C语言闭包学习编程
  4. 高新技术企业认定的指标要求
  5. Ominibus F4V3 Pro飞控原理图和接线图
  6. IntelliJ IDEA 206 个快捷键大全,动图演示!搬砖杠杠的!
  7. [AHK]虚拟数字小键盘(NumPad) v3.0 绿色免费版(ahk实现作品,作者小恐龙)
  8. 面向对象程序设计(OOP设计模式)-结构型模式之装饰器模式的应用与实现
  9. 开发自己的网上支付案例代码(易宝支付php)
  10. 程序员如何打造自己的个人IP?