kmp寻找子串
kmp,最重要的一步就是计算lps数组(匹配数组)。
void get_lps()
{
int k=0;
for (int i=1;i<s.size();i++)
{
while (k > 0 && s[i] != s[k])
{
k = lps[k - 1];
}
if (s[i] == s[k])
{
k++;
}
lps[i + 1] = k;//保证数组的最前面有一个0
}
}
//这是另一种lsp[]数组的算法,对应的kmp函数也有一定的变化
void get_lps(int n)
{
int i = 0, j = -1;
lps[0] = -1;
while (i < n)
{
if (j == -1 || duan[j] == duan[i])
{
i++;
j++;
lps[i] = j;
}
else
{
j = lps[j];
}
}
}
lps[i + 1] = k;
在下面的主函数中调用这个数组的时候可以保证不越界;这个数组保存的信息,就是当某个字符不匹配的时候可以,直接跳转到搜索词的某一个自读上而不需要,再次从头开始遍历;
k
记录的是,某个字符(包括自身在内的)在搜索串的前缀的位置;
代码中的while循环,当出现了不相等元素的时候就可以向前遍历,就可以把k退回到离这个不同的位置最近的一个相同的位置上去,或者k为0,这样就可以让k
一直在搜索词的前端;手写下这个过程很容易理解
有了这个lps[]
一切都变得简单了起来;
int main ()
{
int cnt = 0;
string chang;
chang = "abbbcdcjababcababcababcababc";
s = "ababc";
get_lps();
for (int i = 0; i < s.size() + 1; i++)
{
cout << lps[i] << ' ';
}//输出lps[]验证一下
cout << endl;
return 0;
int j = 0;
for (int i=0;i<chang.size();)
{
//i是待搜索的字符串索引
if (s[j] == chang[i])//如果现在两个的字符相同就比较下一个
{
i++;j++;
}else
{
//不行同就根据lps[]找到对应位置 开始下一次的寻找
j = lps[j - 1];
i++;
}
if (j == s.size())
{
cnt ++;
j = 0;//这个代码我是想要求出子串的个数
//所以这里的j就需要每次初始化为0
}
}
cout << "cnt = " << cnt << endl;
}
这里就是个例子我就直接写死了
时间复杂度O(n+m)
;
lps[]
数组的另外一层含义
lps[]
记录了,搜索词中,对前端的匹配信息(其实是我不知到怎样描述比较好~手动狗头~)。假设在lps[]
数组中3出现了3次,就代表在这个搜索词中,前缀( n = 3 )出现了4次(因为lps[]
是没有前缀对自身的匹配信息的,所以要加上这没有统计的一次就是4次)
放个题吧 HDU3336 Count the string
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
typedef long long orz;
const orz N = 200005;
const int inf = 0x3ffffffe;
const int mod = 10007;
//祖传的开头
int n;
char s[N];
int lps[N];
int p[N];
void fun_lps()
{
int i = 0, j = -1;
lps[0] = -1;
while (i < n)
{
while (j != -1 && s[i] != s[j])
{
j = lps[j];
}
lps[++i] = ++j;
}
}
int main()
{
int t;
scanf("%d", &t);
int ans = 0;
while (t--)
{
scanf("%d%s", &n, s);
memset(p, 0, sizeof(p));
s[n] = 'z';
fun_lps();
for (int i = 1; i <= n; i++)
{
p[lps[i]]++;
}
for (int i = 1; i <= n; i++)
{
ans += p[i];
ans %= mod;
}
ans += n;//最后要加上前缀的那几个
cout << ans % 10007 << endl;
ans = 0;
}
return 0;
}
做字符串的匹配问题是需要注意的几点问题
1.lsp[]
数组,在计算这个数组的时候,一定要检查好各种条件(like me下午因为一个 !=
查了半个小时的bug~更多的还是我做的题少~)
2.kmp()
函数,查找的时候,需要的统计主串中最多可以含有多少个子串,cnt++
执行完后对j
做处理,把j = 0;
可以不重复的寻找子串;j = lps[j]
可以允许有重叠的寻找子串;(理解为,当前子串的前缀和后缀,有一部分重叠的时候,寻找下一个子串,就直接从与前缀相同的下一个开始,和kmp()
中两字符不相同是干的事情相同);
3.如果要返回的数值是字符串开始的位置,需要用确定匹配之后的i
的值,这时的i
指向的时匹配子串的最后字符的下一个位置,所以返回的值应该是i - strlen(short_string) + 1
.
sunday算法
语病忽略吧。想不到其他的例子了更多的是不想改了
首先从两个字符串的开始的位置开始开始比较,知道遇到第一个不同的字符,然后判断待搜索串的下一个位置;
就向在这个例子中,第一个自符就不相等,然后就比较单词is
后面的第一个自符,这里是一个空格,然后判断一下空格在这个搜索词中是否出现,如果没有出现,就直接把搜索词移动到下一个位置正这要就可以不用比较中间的几个字符,省时间
如果,下一个位置的字符在搜索词中出现了,就逆向搜索这个搜索词,找到这个相同的字符然后连接在一起,这个例子中就是这样
然后再开始比较;
开始的时候记录一个字符位置的数组,int pos[26]
(记录搜索串),如果,上面所说的下一个位置的字符在搜索词中出现了,就可以更具这个pos[]
直接找到搜索词中对应字符的位置(空间换时间)这样操作回避每次判断都逆向搜索要省时间(空间不是亲儿子);
在移动搜索词的时候要判断下,档期啊主串剩余的长度书否还大于搜索词的长度(防止越界),如果有搜索词中不存在的字符就要把这些不存在的字符在pos[]
数组中对应位置上的值修改为搜索词长度的加一这样就可以根据这个值直接到下个位置上,==注意在建立pos[]
==时一定要正向的遍历数组,这样可以保证pos[]
存储的位置信息一定是这个搜索词中同一字符最后一个出现的位置
int findstr(string strlong, string strshort)
{
int len_short = strshort.size();
int len_long = strlong.size();
int pos[26] = {0};
for (int i = 0; i < 26; i++)
{
pos[i] = len_short + 1;
}
for (int i = 0; i < len_short; i++)
{
pos[strshort[i] - 'a'] = len_short - i;
} //建立pos数组
int index = 0; //记录现在主串正在比较的字符位置
while (index <= (len_long - len_short))
{
int i = index;
int j = 0; //临时记录现在短串正在比较的位置
for (j = 0; j < len_short; i++, j++)
{
if (strlong[i] != strshort[j])
{
if (index + len_short >= len_long)
{
return -1; //防止字符越界
}
index += pos[strlong[index + len_short] - 'a'];
//根据提前准备好的pos[]数组 转换index的值
break;
}
}
if (j == len_short)
{
//表明整个串全部都匹配上
return index;
}
}
return -1;
}
/**
*代码在一定程度上对其他的博文有借鉴
https://www.cnblogs.com/mr-ghostaqi/p/4285868.html
**/
这里在解释一下这段代码应为我看的时候就很久没反应过来
if (index + len_short >= len_long)
{
return -1; //防止字符越界
}
这个if()
是在比较的两个字符时不相同才执行的,而在函数的开始构建pos[]
数组的时候,这个数组中的最大值就是,搜索词的长度,如果index
加上这个最大值还是在字符数组内的,那么,加上其他的值就肯定不会越界;
话说用Sunday怎么统计某个搜索词的出现的个数,明天研究下现在有点晚了 睡了 晚安