zoukankan      html  css  js  c++  java
  • 算法分类整理+模板②:字符串处理

    本周训练赛出了一道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 <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #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<cnt;i++)
                cout<<no[i].start<<" "<<no[i].endn<<endl;*/
            for(int i=0;i<cnt-1;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
    ",len);
            else printf("%d
    ",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,暂时还没有太深的了解,等到有了时间和机会,会再来补上!

  • 相关阅读:
    页断裂(partial write)与doublewrite技术
    mysql大小写敏感与校对规则
    mysql自增列导致主键重复问题分析。。。
    一次进程hang住问题分析。。。
    教你手工mysql拆库
    mysql online ddl
    Yii2的深入学习--入口文件
    构建自己的PHP框架--抽象Controller的基类
    构建自己的PHP框架--抽象框架的内容
    构建自己的PHP框架--搭建基本结构
  • 原文地址:https://www.cnblogs.com/Torrance/p/5445946.html
Copyright © 2011-2022 走看看