算法分类整理+模板②:字符串处理

本周训练赛出了一道kmp模板题,但是由于长时间没有复习字符串处理算法,而且学习时也并没有彻底理解,只是大概明白了思路,所以导致比赛时迟迟没有做出这一题,最后现场拿出学校整理的材料现场重新学习才ac的这一题。趁这个机会整理一下常用的字符串处理算法以及模板。

 

字符串处理在比赛中一般都不是特别难(至少我遇到的没有),有的字符串处理会和dp放在一起出题,加大一些难度,而单纯的字符串处理其实还是比较好写。

 

一、strstr

strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。据说strstr的效率和kmp差不多。

strstr没什么好说的,注意返回的是地址,如果要下标就用返回值减去数组首地址即可。

例题:http://acm.fzu.edu.cn/problem.php?pid=2128

分析:找出所有子串的位置,排序之后找到相邻两个子串的第二个字母与倒数第二个字母的距离,维护最大即可。

代码:

#include 
#include 
#include 
#include 
#define MAXN 1000010
using namespace std;
char str[MAXN],tmp[105];
struct node{
    int start,endn;
}no[MAXN];
int cnt=0;
bool cmp(node a,node b){
    return a.start<b.start;
}
int main(){
    while(~scanf("%s",str)){
        int n;
        scanf("%d",&n);
        cnt=0;
        int len=strlen(str);
        int res=-1;
        while(n--){
            scanf("%s",tmp);
            int pos=0;
            int ltmp=strlen(tmp);
            while(strstr(str+pos,tmp)!=NULL){
                int ans=strstr(str+pos,tmp)-str;
                no[cnt].start=ans;
                no[cnt].endn=ans+ltmp-1;
                pos=no[cnt].endn;
                cnt++;
            }
        }
        no[cnt].start=no[cnt].endn=len;
        cnt++;
        sort(no,no+cnt,cmp);
        /*for(int i=0;i*/
        for(int i=0;i1;i++)
            res=no[i+1].endn-no[i].start-1>res?no[i+1].endn-no[i].start-1:res;
        if(res==-1)
            printf("%d\n",len);
        else printf("%d\n",res);
    }
}
strstr应用

 

二、字符串hash

具体的哈希总结会另开一专题,这里只贴一下字符串哈希常用的模板:

1.SDBMHash

unsigned int SDBMHash(char *str){
    unsigned int hash = 0;
    while (*str){
        // equivalent to: hash = 65599*hash + (*str++);
        hash = (*str++) + (hash << 6) + (hash << 16) - hash;
    }
    return (hash & 0x7FFFFFFF);
}

2.BKDRHash

unsigned int BKDRHash(char *str){
    unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
    unsigned int hash = 0;
    while (*str){
        hash = hash * seed + (*str++);
    }
    return (hash & 0x7FFFFFFF);
}

3.APHash

unsigned int APHash(char *str){
    unsigned int hash = 0;
    int i;
    for (i=0; *str; i++){
        if ((i & 1) == 0){
            hash ^= ((hash << 7) ^ (*str++) ^ (hash >> 3));
        } else{
            hash ^= (~((hash << 11) ^ (*str++) ^ (hash >> 5)));
        }
    }
    return (hash & 0x7FFFFFFF);
}

3.kmp

kmp是acm中最常用的字符串处理算法,虽然其效率可能不如Sunday,BM等算法,但是其地位是不容质疑的。

kmp算法实质是在暴力寻找的基础上添加了next数组,其思想就是先对于模式串进行预处理,然后利用已有的匹配信息,优化在查找时候的模式串移动位数。

举个例子:

比如主串是:ASDFVAGBASDFGABSDFASDABCBDSFB,模式串是ABCABD。

我们首先从左向右比较,发现第二位没有匹配,此时如果是暴力的思想,我们应该以主串的第二位为首重新进行匹配,但是其实我们没有必要这样做,因为我们可以在主串中找到下一个A开始比较即可。

而我们要做的就是如何利用已知信息去寻找这个next数组,也就是如何对查询过程进行优化。

由此,我们引出了对于一个字符窜的前后缀概念。前缀是指一个字符串除去最后一个字母并且包含首字母的子串,后缀是指一个字符串除去首字母并且包含尾字母的子串。

举个例子:

  对于字符串:ABCDABD 来说:

  - "A"的前缀和后缀都为空集,共有元素的长度为0;

  - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

  - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

其实,我们也可以这样理解,next数组中存储的就是到这一位为止,有多少和从模式串的头算起相同的位数。

比如ABCDABD,next[6]==2,就是到第六位为止,对于这个子串存在一个两位的既存在于前缀中,也存在于后缀中,也就是在串中和模式串首重复的字串:AB。

而当我们在进行kmp时,模式串向后移动的距离就不简简单单是1位,而是“移动位数 = 已匹配的字符数 - 对应的部分匹配值”。

比如我们对于模式串ABCDABD来说,此时主串为ABCDABACDABCDABD,模式串的next数组前面已经得到,为0,0,0,0,1,2,0。首先我们匹配到第七位失配,就需要向后移动(已匹配的6位-最后一个匹配位所对应的next值2)=4位,这样就极大的提高了效率。

 

const int MAXN_T=1000010;
const int MAXN_P=10010;
char P[MAXN_P],T[MAXN_T];
int _next[MAXN_P];
void init_next(char *P){
    int m=strlen(P+1);        //数组下标从1开始
    _next[1]=0;             //next数组第一位是0
    for(int k=0,q=2;q<=m;q++){         //q是模板字符串下标,k是最大前后缀长度
        while(k>0 && P[k+1]!=P[q])        //递归求P串的各位最大相同前后缀长度
            k=_next[k];
        if(P[k+1]==P[q])        //如果两位相等,最大相同前后缀长度加1
            k++;
        _next[q]=k;
    }
}
求next数组模板
int kmp(char *P,char *T){
    int n=strlen(T+1),m=strlen(P+1);
    init_next(P);
    int sum=0;
    for(int i=1,q=0;i<=n;i++){        //i是T串下标,q是P串下标
        while(q>0&&P[q+1]!=T[i])      //根据最大前后缀长度找应该向后移动多少位
            q=_next[q];
        if(P[q+1]==T[i])
            q++;
        if(q==m){           //如果P串匹配到最后一位并且成功
            sum++;          //则计数加1
            q=_next[q];     //可以继续查询T串中共出现P串多少次
        }                   //如果只查询是否存在可以直接在这return
    }
    return sum;
}
kmp模板

 

其余关于字符串处理的算法比如自动机,后缀数组,BM,Sunday,暂时还没有太深的了解,等到有了时间和机会,会再来补上!

转载于:https://www.cnblogs.com/Torrance/p/5445946.html

你可能感兴趣的:(算法分类整理+模板②:字符串处理)