Kmp算法
最近在学算法,发现一个更好的kmp算法的模板,在保留之前那篇关于kmp
算法1.0的博客的基础下决定整理分析kmp2.0版
这一段专门提出来作为模板记录:
for (int i = 2, j = 0; i <= n; i++)
{
while (j && pat[j + 1] != pat[i]) j = Next[j];
if (pat[j + 1] == pat[i]) j++;
Next[i] = j;
}
for (int i = 1, j = 0; i <= m; i++)
{
while (j && pat[j + 1] != str[i]) j = Next[j];
if (pat[j + 1] == str[i]) j++;
if (j == n)
{
cout << i - n << " ";
j = Next[j];
}
}
题目链接–KMP字符串
完整代码
#include<iostream>
using namespace std;
const int N = 1e5 + 5, M = 1e6 + 5;
char str[M], pat[N];
int Next[N];
int main()
{
int n, m;
cin >> n >> pat+1 >> m >> str + 1;//字符串都从第1位开始存储
//构造Next数组
for (int i = 2, j = 0; i <= n; i++)
//关于为什么i从2开始,因为字符串的前缀后缀匹配最长长度的比较要从比总字符串小一除
//开始比较,原字符串aba,aba就是本身,不分前缀后缀,匹配过程要求至少要比原串短一
//个字符,所以i要从第二个字符开始枚举。
{
while (j && pat[j + 1] != pat[i]) j = Next[j];
if (pat[j + 1] == pat[i]) j++;
//Next[i]记录的是以i为终点与以1为起点匹配的前缀和后缀的最长长度
//j指向的是前缀的重点,i指向的是后缀的终点(这一句是关键)
//这一段自己模拟走一步就懂了
Next[i] = j;
}
for (int i = 1, j = 0; i <= m; i++)
{
while (j && pat[j + 1] != str[i]) j = Next[j];
if (pat[j + 1] == str[i]) j++;
if (j == n)
{
cout << i - n << " ";
j = Next[j];//网上回溯
}
}
return 0;
}
Tire树(字典树)
一般来说字典树的创建都是通过指针,但当面对数据范围不是很大的时候,可以考虑用数组存储,本题的数组存储法更为巧妙
例题链接–Trie字符串统计
int son[N][26], cnt[N], idx;
先解释一下这两个数组和idx的含义:
son是用来存储当前节点的孩子节点的位置的,cnt是用来存储以当前节点为结尾的字符串的个数,idx是用来更新孩子节点位置的
这里的idx用的最为巧妙:
仔细观察数组发现存储Trie树的数组只是一个二维数组,但按照一般Trie树数组存储的方法,每一个节点都有一个长度为26的数组指向该节点的子节点,比如“abc”,根节点的孩子数组长度为26,其中a,b,c是已经存储的,接着到a节点,a节点有一个孩子数组长度也是26,b,c相同,所以对于一个两层的Trie树需要的空间大小是26+26*26
但这题的idx却是一直递增的,if (!son[p][u]) son[p][u] = ++idx;表示要存储的这个新的字符在树里面是不存在的,son[p][u] = ++idx;就表示对son[p][u]这个节点分配新的空间,++idx表示son[p][u]的孩子节点的指向。
比如:idx初始为0,插入 “abcde” “abcef”,两个字符串前三个字符存储的位置是一样的,在第0,1,2层,a,b的idx(孩子节点的位置)都为1 2,u=str[i]-‘a’,所以第一个字符串‘a’对应为第0层,idx++,那么a的孩子就在第一层
/son[0][0]=1,表示第0层的’a’字符对应的孩子节点在第一层
其他两个同理,这时候第一个字符串到d了,开始时son[[3][‘d’-‘a’]==0,idx++,开辟新的空间,表示该节点的孩子节点的层数为4。这是第一个字符串的操作,那么对于第二个字符串呢,当字符指向e时(str[3]),e字符在第三层并没有被存储,经过第一个字符串的存储,idx已经为5,这个5是第一个字符串结尾字符e的孩子数组指向的位置,当面对第二个字符串的e,idx++,所以e的孩子数组的指向在第6层,与第一个字符串中字符d(str[3])孩子数组的指向位置不是同一个。
cnt[N]存储的以当前节点为结尾字符串的个数,这个理解了idx的作用和整个结构的存储方式就不难理解。比如"abc",就是cnt[3]++,在存储”abe“,就是cnt[4]++.
模板:
void Insert(char str[])
{
int p = 0;
for (int i = 0; str[i]; i++)
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
cnt[p]++;
}
int Query(char str[])
{
int p = 0;
for (int i = 0; str[i]; i++)
{
int u = str[i]-'a';
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
完整代码:
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int son[N][26], cnt[N], idx;
void Insert(char str[]);
int Query(char str[]);
int main()
{
int n;
cin >> n;
while (n--) {
char c, str[N];
cin >> c >> str;
if (c == 'I') Insert(str);
else cout << Query(str) << endl;
}
return 0;
}
void Insert(char str[])
{
int p = 0;
for (int i = 0; str[i]; i++)
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
cnt[p]++;
}
int Query(char str[])
{
int p = 0;
for (int i = 0; str[i]; i++)
{
int u = str[i]-'a';
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}