问题标题: 奇妙的字符串算法

2
0
已解决
许金夫
许金夫
初级天翼
初级天翼

字符串算法

字符串算法主要包括KMP,AC自动机,字典树.......

- 1 KMP算法

KMP:进行字符串的匹配

字符串算法的基**

在一个主串s中查找一个子串t,返回下标,否则-1

为了方便,所有的字符串下标从1开始(cin>>s+1),字符串用char[]表示

-暴力算法

用i遍历s,用j遍历t。最初ij指向t的第一个字符

ij一起向后挪,一个一个字符得比对,如果发现不同,ij回到t的开头,同时i++表示t向后一个一位

-核心函数

令s=ababcababa,t=ababa

核心思路:

用一个i指向s,用j指向t
一开始i,j所指的都是t的头,如果s[i]==t[j],i,j++
当s[i]!=t[j],也就是i,j=5时,我们会希望j不要回到头(会浪费)
我们发现ababa中前三个aba等于后三个
因为此时我们已经匹配了不少,已知1-4都是相同的,要是移动过后前面还是有一部分是相同的就会省时间
所以我们我们把ababa向后挪3格(j=3)
这样的话就是这样的:
ababcababa
  ababa
可以看到此时j=3,i=5,但ij所指的位置一样
前面2个ab是相同的(省时间),但是s[i]还是不等于t[j]
因为j=3,前三个字符aba中相同的是a和a,所以我们把j更改为1
ababcababa
    ababa
这次开头不一样,再往后挪一下
ababcababa
     ababa
匹配成功

在上面的过程中可以发现当j在快速变化的时候,数值很特殊(最大真前缀后缀相同的长度)

这个特殊的数组就是p[t]

-求数组p[t]

数组p[t]表示t[1-t]中所有满足t[1,k]==t[j-k+1,j]中k的最大值,也就是所有前缀==后缀中最长的那个的长度k

p[j]数组是我们在减少暴力算法中j的遍历次数

void pre(){//填p数组 
    p[1]=0;//初始化
    int j=0;//要求的最大前缀后缀相同的长度 
    for(int i=1;i<m;i++){//自己和自己匹配 
        while(j>0&&b[j+1]!=b[i+1])//如果j不等于0代表正在向后匹配,如果发现不同,就后退 
            j=p[j];//后退
        if(b[j+1]==b[i+1])j++;//自己和自己匹配,如果相同就增加
        p[i+1]=j;//为了继续往后做 
    }
} 

-函数实现

因为求p[t]的过程就是t和自己匹配,而kmp主函数的过程就是s和t匹配,所以函数很相近,具体见完整代码

-完整代码

#include<bits/stdc++.h>
using namespace std;
int p[10010],n,m;
char s[10010],t[10010];
void pre(){
    p[1]=0;//边界
    int j=0;//要求的最大真前后缀长度
    for(int i=1;i<m;i++){
        while(j>0&&t[i+1]!=t[j+1])//连续前移直到不能退回前面 
            j=p[j]; 
        if(t[i+1]==t[j+1])j++;
        p[i+1]=j;
    } 
}
void kmp(){
    int j=0;//这个j是指遍历t的下标 
    for(int i=1;i<n;i++){
        while(j>0&&s[i+1]!=t[j+1])j=p[j];//如果匹配没成功就退回前面
        if(s[i+1]==t[j+1])j++;
        if(j==m){
            cout<<i+1-j+1<<endl;//匹配成功,输出头的下标
            j=p[j];//解可能不止一个,继续找 
        }
    }
}
int main(){
    cin>>s+1;//下标从1开始
    cin>>t+1;
    n=strlen(s+1);
    m=strlen(t+1);
    pre();kmp();
    return 0;
}

所以kmp十分的简介,我们可以把它背掉

许金夫在2022-03-20 22:44:37追加了内容

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

在博客内查看更优~~~~~~~~~~~~~~~~

在博客内查看更优~~~~~~~~~~~~~~~~

在博客内查看更优~~~~~~~~~~~~~~~~

 

许金夫在2022-04-17 12:43:41追加了内容

更新啦:

 

字典树

用字母表示的树,字母在边上,不在顶点上

利用公共前缀来节约内存

字典树是一个26叉树

题目中可以用来判断前缀(书P84)

思路:..................................

代码:

struct node{//trie结构 
    bool end;//这个点是否是一个字符串的结尾
    int vis[30];//接下来一个节点的位置 
}trie[10010];
int n,cnt;//cnt:点的下标 
void build(string s){//插入s 
    int sp=0;//当前节点位置 
    int l=s.length();
    for(int i=0;i<n;i++){
        if(trie[sp].vis[s[i]]==0)//没有这个节点 
            trie[sp].vis[s[i]]==++cnt;//构建出这个节点 
        sp=cnt; //移到这个节点上 
    } 
}

WA自动机

kmp:单个字符串匹配单个字符串

ac自动机:一堆字符串匹配单个字符串(出现位置或出现次数)

1:构建一个trie树

代码见上

2:构建失配指针next[],类似kmp里的那个

- 一个节点的指针指向:沿着其父节点的失配指针,一直向上,直到找到拥有“当前这个字母的子节点”的节点,指针指向被找到的节点的子节点

- 第二层的所有指针指向根节点

- 底下的找不到也指向根节点

举两个例子:
s的儿子h节点的父节点在第二层,指向根节点,根节点有一个子节点也是h,所以s底下的h指向跟底下的h
s的孙子e节点的父节点h指向根的儿子h,那个h有一个儿子也是e,所以s的孙子e指向根的孙子e
类似kmp中的next

代码需要使用BFS(用到队列),总代码在后面

3:匹配字符串

#include<bits/stdc++.h>
using namespace std;
struct node{
    int fail;//失配指针
    bool end;//字符串结尾标记
    int vis[30];//接下来一个节点的位置 
}trie[10010];//树 
int n,cnt;//cnt:点下标
void build(string s){
    int sp=0;//当前节点位置(根)
    int l=s.length();
    for(int i=0;i<l;i++){
        if(trie[sp].vis[s[i]-'a']==0)
            trie[sp].vis[s[i]-'a']=++cnt;
        sp=cnt;
    }
} 
void faill(){//求fail 
    queue<int> q;
    for(int i=0;i<26;i++){//预处理第二层的指针指向根节点 
        if(trie[0].vis[i]!=0){//如果存在这个节点 
            trie[0].fail=0;//指向根节点
            q.push(trie[0].vis[i]);//把这个节点入队 
        } 
    } 
    while(!q.empty()){//队不空 
        int u=q.front();//出队首,开始bfs
        q.pop();
        for(int i=0;i<26;i++){//找u的所有子节点 
            if(trie[u].vis[i]!=0){
                trie[trie[u].vis[i]].fail=trie[trie[u].fail].vis[i];
                //找到拥有“当前这个字母的子节点”的节点,指针指向被找到的节点的子节点
                q.push(trie[u].vis[i]); 
            }
            else {
                trie[u].vis[i]=trie[trie[u].fail].vis[i];
                //当前节点的这个子节点指向当前节点fail指针的这个子节点 
            }
            //这个if-else要画图 
        } 
    } 
}
int check(string s){
    int l=s.length();
    int sp=0;//当前节点ac树上坐标
    int ans=0;
    for(int i=0;i<l;i++){//遍历 
        sp=trie[sp].vis[s[i]-'a'];//向下移动
        for(int i=sp;i&&trie[i].end!=-1;i=trie[i].fail){
            ans+=trie[i].end;
            trie[i].end=-1;
        } 
    }
    return ans;
} 
int main(){

    return 0;
}
许金夫在2022-04-17 12:44:43追加了内容

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

在博客内查看更优:https://www.luogu.com.cn/blog/Luogu-Blog-Org/zi-fu-chuan-suan-fa

在博客内查看更优:https://www.luogu.com.cn/blog/Luogu-Blog-Org/zi-fu-chuan-suan-fa

在博客内查看更优:https://www.luogu.com.cn/blog/Luogu-Blog-Org/zi-fu-chuan-suan-fa

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


0
0
许金夫
许金夫
初级天翼
初级天翼

许金夫在2022-05-19 22:28:39追加了内容

DING

我要回答