字符串hash与字典树
Posted 31415926535x
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字符串hash与字典树相关的知识,希望对你有一定的参考价值。
title: 字符串hash与字典树
date: 2018-08-01 22:05:29
tags:
- acm
- 算法
字符串
概述
这篇主要是关于字符串里的 字符串hash 和 字符串字典树,,两个都是简单的套模板的东西,,,理解基本思想就行了,,,对了,,还有一个字典树的的变形--01字典树;
字符串hash
如何求一个字符串的hash值
字符串hash的作用就是将 字符串有效的转化为一个整数 ,,这个转化过程利用的是一个 hash函数
例如,,我们选hash函数为 (hash[i]=(hash[i-1]*p+idx(s[i]))%mod)
其中的idx(s)为s的一个自定义索引,,,例如a = 1 , b = 2.....
这时,,取 p=13,mod=101,把abc映射成一个整数
hash[0]=1,表示 a 映射为1
hash[1]=(hash[0]p+idx(b))%mod=15,表示 ab 映射为 15
hash[2]=(hash[1]p+idx(c))%mod=97
所以abc就被映射成97。
同样对于其他的字符串也可以由此算出一个hash值,,,
当然这里的p , MOD要选取合适,,,否则可能会出现不同字符串有相同的hash值,,,
一般来说,,,p和mod一般取素数,p取一个较大的素数即可(6位到8位),mod取一个大素数,比如1e9+7,或者1e9+9....
一般的模板,,,
const unsigned long long p = 1e9 + 13;
const unsigned long long mod = 1e9 + 7;
unsigned long long hashStr(char *s){
unsigned long long h=0;
for(int i=0;i<len;i++){
unsigned long long value;
char c=s[i];
if(c>='0'&&c<='9') value=c-'0';
else if(c>='a'&&c<='z') value=c-'a'+10;
else value=c-'A'+36;
h=(h*p+value)%mod;
}
return h;
如何求一个字符串任意子串的hash值
最容易想到的方法就是不管原来的字符串,,,
单独对于子串从 l 到 r 直接用刚刚求hash的方法求就行了,,,
但是当数据量大时,,,时间复杂度就很高了,,,,
上面的那个公式中hash[i]求得时字符串第i个前缀的hash值,,,,相当于是一个hash的前缀和,,,,
要求的子串 (hash[l..r] == (hash[r] - hash[l - 1] * p^{r - l + 1}) \\% MOD)
**对了,,,这样直接求可能有负数,,,要判断+=mod一下。。。。。。
几个常用的求字符串hash算法
(hash[i] = (hash[i - 1] * p + idx(s[i])) \\% MOD)
常用,,,- unsigned long long hash[N];hash[i] = hash[i - 1] * p;这个自动取模,,,当数超过 (2^{64}-1)时就会溢出,,,相当于取模 (2^{64})的过程,,,
双hash,,,有些情况下一个hash可能会出现冲突,,,那就换两个,,,两个不行就换三个,,,,什么?!三个还不行???!!!那就四个或则换算法,,,逃,,,实现:取两个mod,,一般取1e9+7和1e9+9两个孪生素数,,,冲突概率已经很低了,,,,,
字典树
简单来说就是把n个字符串用树保存,,,这样查询的时候一层一层的找,,,,
构建节点
一般的字典树的结构体定义如下:
const int maxn = 26;
struct Trie
{
Trie *next[maxn];
int v; //根据需要变化
};
Trie *root;
插入
void Insert(char *str)
{
int len = strlen(str);
trie *p = root;
//insert
for (int i = 0; i < len; i++)
{
int t = str[i] - 'a';
//NULL
if (p->next[t] == NULL)
{
p->next[t] = new trie();
p = p->next[t];
}
else
{
p = p->next[t];
p->sum ++;
}
}
}
查询
int query(char *str)
{
int len = strlen(str);
trie *p = root;
for (int i = 0; i < len; i++)
{
int t = str[i] - 'a';
p = p->next[t];
//NULL
if (p == NULL) return 0;
}
return p->sum;
}
释放内存
不然可能有的题mle,,,,,
void free(trie *t)
{
if (t == NULL)
return;
for(int i = 0; i < maxn; i++)
if (t->next[i]) free(t->next[i]);
delete (t);
}
虽然是模板化的东西但是也要不死套模板,,,,,QAQ
习题
前三道都是模板题,,上午讲过,,,最后一题是01字典树问题,,,以后要再看一下,,,看到不同的好几种实现方法,,,没有来的及总结一下,,,,
Problem A: A
Time Limit: 1 Sec Memory Limit: 128 MB
Description
给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。
Input
输入,第一行一个N
接下来N行每行包含一个字符串
Output
输出不同字符串的个数
Sample Input
5
abc
aaaa
abc
abcc
12345
Sample Output
4
我的代码:
//#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const ull p = 1e9 + 13;
const ull mod = 1e9 + 7;
const int maxn = 1e6;
ull a[maxn];
ull hashstr(char *s)
{
ull h = 0;
for (int i = 0; i < strlen(s); i++)
{
ull idx;
if (s[i] >= '0' && s[i] <= '9') idx = s[i] - '0';
else if (s[i] >= 'a' && s[i] <= 'z') idx = s[i] - 'a' + 10;
else if (s[i] >= 'A' && s[i] <= 'Z') idx = s[i] - 'A' + 36;
h = (h * p + idx) % mod;
}
return h;
}
int main()
{
int n;scanf("%d" , &n);
char t[maxn];
for (int i = 1; i <= n; i++)
{
//gets(t);
scanf("%s" , &t);
a[i] = hashstr(t);
}
sort(a + 1 , a + 1 + n);
int ans = 0;
int cur = -1;
for (int i = 1; i <= n; i++)
if (a[i] != cur)
{
cur = a[i];
ans++;
}
cout << ans << endl;
return 0;
}
学长的,,,
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10010];
char s[1010];
ull Hash(char *s)
{
int len=strlen(s);
ull ans=0;
for(int i=0;i<len;i++)
ans=ans*base+(ull)s[i];
return ans;
}
int main()
{
/*srand(NULL);
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);*/
/*printf("1000
");
for(int i=1;i<=1000;i++)
{
int len=rand()%1000+1;
for(int i=0;i<len;i++)
{
int t=rand()%3;
if(t==0)
s[i]='0'+rand()%10;
else if(t==1)
s[i]='A'+rand()%26;
else
s[i]='a'+rand()%26;
}
s[len]='