文字列「学習アルゴリズム」はじめに

彼らはありませんブロガーアセンブリ文字列アルゴリズムを行うので、そうまだ一から書くつもり

まず、単純な文字列マッチングアルゴリズム(ナイーブ文字列マッチングアルゴリズム)

  • また、暴力マッチングアルゴリズムとして知られている(ブルートフォースアルゴリズム)

  • 前処理いいえ

  • 不一致をバックに移動した後、

  • 複雑さ: $ O(^ N-2)$

  • コード

    1 
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    無効 CHAR *、CHAR * B)
    { int型レナ= STRLEN(+ 1)、LENB = STRLEN(B + 1)。以下のためにINT iは= 1 ; I <=レナ; ++ I)のためにINT J = 1 ; J <= LENB; ++ j)は { 場合([I + J - 1!] = B [J])休憩 ; リターン1 ; } 戻り0}









第二に、ハッシュ(ハッシュ)

  • ハッシュ($ハッシュ$)は、アルゴリズム訓練されたガチョウの妹です

  • 前処理は、オブジェクトを見つけるために、$ O(1)$を行うことができた後に

  • Wordの原理:時間のためのスペース。-蒋介石

  • 特定の実装:

    栗:

    以下の入力および出力を達成するように設計されたプログラム:
    最初の行の2つの入力番号$ N、Mの$。
    第二列、N- $ $入力文字列。
    第三行、$ M $入力文字列。それは第二行N- $ $入力文字列、出力「YES」であれば、出力が「NO」である場合に存在する場合、各文字列を、尋ねます。

    データ説明:$ [1,10 ^ 5] $に入力された文字列の長さ。

    この質問は、男はそれぞれ番号が上登場するかどうか長い$ BOOL $配列$ VISの建設などとして、[10 ^ 5] $の記録を行いますので、[1leqslant A leqslant10 ^ 5] $番号$の文字列に変換されていた場合行わ。

    今も行うことができ、文字列になって、

    $地図$と呼ばれる事がありますので、

    しかし、この事定数が行う方法に大きなああですか?

    すべての権利、あなたは手書きを所有することができます。

    私たちは、あなたが130233666または何かを取ることができ、文字セットのサイズより数の大きいを表し、$ M $ $ lenは$ 16進数の$ M $、各文字列を呼び出します。

    回避オーバーフローのために、我々は多数のモジュロを行い、$ MOD $は、品質の同様の$ 99 $ 991を取ることができます。

    コード

    1 
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CONST  INT M = 131
    const int型モード= 99 991 ;

    int型 のハッシュCHAR *)
    { int型のハッシュ= 0、N = STRLEN(+ 1)。以下のためにINT iが= 1 ; I <= LEN; ++ I) ハッシュ=(ハッシュ* M + [I])%のMOD。戻り値のハッシュ。}





    質問があるので、どのように$ハッシュ$値が競合している場合?

    図1は、定義されたスペースがある、すなわちVIS $ $ $ MOD $配列サイズおよびサイズ(ハッシュの数は、デュアルモードであってもよい)、見かけ上の欠陥を上げ、ハッシュ衝突プールサイズのハッシュ値を増加させることによって問題を軽減します。

    他の手段による2、競合マッチングの場合に元の文字列としてレコードの各列のハッシュ値(または$ $ $ NSMA KMP

    $)。

  • いくつかの特別な処理なしハッシュは、それは私があなたの振る舞いを願って、簡単なカードです。

三、クヌース - モリス - プラット文字列照合アルゴリズム(KMP)

  • $ NSMA $アルゴリズムで見てみましょう、各パターン文字列の不一致が第1のリセットモードにテキスト文字列と文字列をバックジャンプします後に発見されました。

  • 実際には、我々はすべての点にパターン文字列をリセットする必要はありません

  • 提供されたテキスト文字列が$ S_1 $であり、パターン文字列は、$ S_2 $、$ S_1 $最初$ I $場所、$ S_2 $ $ J $場所の、$ S_1 [I] NEQ S_2 [jへの電流整合であります] $

  • $は、$ J $はもはや、最初のものにリセットされ、$ K $を介して直接リセットすることができる[1 - - k個のcdots jをj]は$ K $、そう$ S_2 [1 cdotsのK] = S_2がある場合位置、文字の前が同じであるため。

  • ここでの焦点:どのように$ K $を取得するにはどのくらいですか?

  • 因为在模式串的每一个位置都有可能发生失配,所以我们要对每一个$j$求出它对应的$k$,用$next$数组来存,$next[j] = k$,表示当$s_2[j] != s_1[i]$时,$j$的指针的下一个位置。

  • 当$j = 1$时,显然失配后只能重新开始匹配,所以$next[1] = 0$,木得问题。

  • 当$j > 1$时呢?如图。

  • 我们可以发现:当$p[k] = p[j]$时,有$next[j + 1] = next[j] + 1$

  • 看图感性理解,易证,留给读者当作业(滑稽.jpg)

  • 如果$p[k] ne p[j]$怎么办呢?

  • 如图:

  • 此时,我们应将$k$的值赋为$next[k]$,因为我们已经找不到一个最长的前缀串满足$s_2[1 cdots k] = s_2[j - k cdots j - 1]$,所以我们只能找次小的前缀串满足该条件,显然,这个次小的前缀串就是$next[k]$。

  • 小优化:

    按上述方法找到的$next$大致上已经没什么问题了,但是遇到部分情况还是会多出一切不必要的比较。

    如下图:

    显然,在上述匹配过程中,当前位置产生失配时,$j$会跳到$next[j]$,也就是$2$,但是,这一步是完全没有意义的,因为我们已经知道这一位用$B$去匹配是会失配的,在跳了一下$next$数组后,我们用来匹配的字符却还是$B$,显然还是失配的。

    解决:在求$next$时判一个相等就好了。

  • Code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    using namespace std;
    #define rep(i,a,b) for(int i = (a);i <= (b); ++i)
    #define ll long long
    const ll maxn = 1e6 + 5 ;

    ll Next[maxn],ln,lm;

    char n[maxn],m[maxn];

    void kmp()
    {
    ll k = 0;
    Next[1] = k;
    rep(i,2,lm)
    {
    while(k && m[i - 1] != m[k]) k = Next[k];
    if(m[i] == m[++k]) Next[i] = Next[k];
    else Next[i] = k;
    }
    }

    int main()
    {
    scanf("%s %s",n + 1,m + 1);
    ln = strlen(n + 1);
    lm = strlen(m + 1);
    kmp();
    ll cnt = 1,i = 1;
    while(i <= ln)
    {
    if(n[i] == m[cnt] || !cnt) ++cnt,++i;
    else cnt = Next[cnt];
    if(cnt == lm)
    {
    printf("%lld",i - lm + 1);
    return 0;
    }
    }
    puts("impossible");
    return 0;
    }

四、Aho-Corasick automation多字符串匹配算法(AC自动机)

  • 对于多字符串的匹配问题,我们一般会用$Hash$或者$Trie$储存。

  • 此处不讲$Hash$($hash$大法好啊)

  • $AC$自动机是什么?在$trie$上搞一个类似$KMP$的那个$next$数组的一个失配指针。我们用$fail$来表示这个东西。

  • 我们先对题中给出若干个模式串建出一颗$trie$树,不过我们对这个数的每一个节点都添加一个信息,就是该节点的$fail$指针。

  • 那我们怎么求这个$fail$指针呢?首先,深度为$2$的节点(即根节点的儿子)的$fail$指针都指向根节点,然后对于每一个节点$v$,它的$fail$指向:沿着它的父亲节点的失配指针,一直向上,直到我们找到拥有当前字母的子节点的节点的那个子节点

  • 具体实现可以看yyb大佬的代码

  • Code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    大专栏  「算法学习」字符串入门ss="line">28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<algorithm>
    using namespace std;
    struct Tree//字典树
    {
    int fail;
    int vis[26];//子节点的位置
    int end;//标记以这个节点结尾的单词编号
    }AC[100000];//Trie树
    int cnt=0;//Trie的指针
    struct Result
    {
    int num;
    int pos;
    }Ans[100000];//所有单词的出现次数
    bool operator <(Result a,Result b)
    {
    if(a.num!=b.num)
    return a.num>b.num;
    else
    return a.pos<b.pos;
    }
    string s[100000];
    inline void Clean(int x)
    {
    memset(AC[x].vis,0,sizeof(AC[x].vis));
    AC[x].fail=0;
    AC[x].end=0;
    }
    inline void Build(string s,int Num)
    {
    int l=s.length();
    int now=0;//字典树的当前指针
    for(int i=0;i<l;++i)//构造Trie树
    {
    if(AC[now].vis[s[i]-'a']==0)//Trie树没有这个子节点
    {
    AC[now].vis[s[i]-'a']=++cnt;//构造出来
    Clean(cnt);
    }
    now=AC[now].vis[s[i]-'a'];//向下构造
    }
    AC[now].end=Num;//标记单词结尾
    }
    void Get_fail()//构造fail指针
    {
    queue<int> Q;//队列
    for(int i=0;i<26;++i)//第二层的fail指针提前处理一下
    {
    if(AC[0].vis[i]!=0)
    {
    AC[AC[0].vis[i]].fail=0;//指向根节点
    Q.push(AC[0].vis[i]);//压入队列
    }
    }
    while(!Q.empty())//BFS求fail指针
    {
    int u=Q.front();
    Q.pop();
    for(int i=0;i<26;++i)//枚举所有子节点
    {
    if(AC[u].vis[i]!=0)//存在这个子节点
    {
    AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
    //子节点的fail指针指向当前节点的
    //fail指针所指向的节点的相同子节点
    Q.push(AC[u].vis[i]);//压入队列
    }
    else//不存在这个子节点
    AC[u].vis[i]=AC[AC[u].fail].vis[i];
    //当前节点的这个子节点指向当
    //前节点fail指针的这个子节点
    }
    }
    }
    int AC_Query(string s)//AC自动机匹配
    {
    int l=s.length();
    int now=0,ans=0;
    for(int i=0;i<l;++i)
    {
    now=AC[now].vis[s[i]-'a'];//向下一层
    for(int t=now;t;t=AC[t].fail)//循环求解
    Ans[AC[t].end].num++;
    }
    return ans;
    }
    int main()
    {
    int n;
    while(233)
    {
    cin>>n;
    if(n==0)break;
    cnt=0;
    Clean(0);
    for(int i=1;i<=n;++i)
    {
    cin>>s[i];
    Ans[i].num=0;
    Ans[i].pos=i;
    Build(s[i],i);
    }
    AC[0].fail=0;//结束标志
    Get_fail();//求出失配指针
    cin>>s[0];//文本串
    AC_Query(s[0]);
    sort(&Ans[1],&Ans[n+1]);
    cout<<Ans[1].num<<endl;
    cout<<s[Ans[1].pos]<<endl;
    for(int i=2;i<=n;++i)
    {
    if(Ans[i].num==Ans[i-1].num)
    cout<<s[Ans[i].pos]<<endl;
    else
    break;
    }
    }
    return 0 ;
    }

おすすめ

転載: www.cnblogs.com/lijianming180/p/12370814.html