zoukankan      html  css  js  c++  java
  • 字符串搜索算法

    1.单模式匹配

    就是在一些文本中查找某一个子字符串的算法,效率较高的有以下几种。

    KMP算法:全称Knuth-Morris-Pratt算法 预处理时间Θ(m) 匹配搜索时间 Θ(n)

    代码
    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <string.h>
    4
    5  const int * get_prefix(const char * P)
    6 {
    7 int * pi = (int *)malloc(sizeof(int) * strlen(P));
    8 pi[0] = -1;
    9 int i = 1;
    10 int j = -1;
    11 while (P[i])
    12 {
    13 while (j >= 0 && P[j + 1] != P[i])
    14 {
    15 j = pi[j];
    16 }
    17 if (P[j + 1] == P[i])
    18 {
    19 ++j;
    20 }
    21 pi[i] = j;
    22 ++i;
    23 }
    24 return pi;
    25 }
    26
    27  void kmp_match(const char * T, const char * P)
    28 {
    29 const int * pi = get_prefix(P);
    30 int i = 0;
    31 int j = -1;
    32 while (T[i])
    33 {
    34 while (j >= 0 && P[j + 1] != T[i])
    35 {
    36 j = pi[j];
    37 }
    38 if (P[j + 1] == T[i])
    39 {
    40 ++j;
    41 }
    42 if (0 == P[j + 1])
    43 {
    44 printf("%s\n", T + i - j);
    45 j = pi[j];
    46 }
    47 ++i;
    48 }
    49 free(pi);
    50 }
    51
    52  int main(int argc, char * argv[])
    53 {
    54 kmp_match("abcdabcdabcdabcd", "abc");
    55
    56 return 0;
    57 }
    58
    59
    60 参考:《算法导论》
    61
    62  ---------------------------------------------------------------------------
    63
    64  /*
    65 * Knuth-Morris-Pratt 字符串匹配算法的三种实现。
    66 * 匹配部分都一样,差异只在求 next 数组。:)
    67 */
    68
    69 #include <stdio.h>
    70 #include <stdlib.h>
    71 #include <string.h>
    72
    73  /*
    74 * 实现一
    75 */
    76  char * kmp1(char * content, char * pattern)
    77 {
    78 int i;
    79 int j;
    80 int len;
    81 int * next;
    82
    83 if (NULL == content || NULL == pattern)
    84 {
    85 return NULL;
    86 }
    87
    88 len = strlen(pattern);
    89 next = (int *)malloc(len * sizeof(int));
    90
    91 /* Get the "next" array. */
    92 next[0] = -1;
    93 for (i = 1; pattern[i] != 0; ++i)
    94 {
    95 j = next[i - 1];
    96 while (pattern[i - 1] != pattern[j] && j >= 0)
    97 {
    98 j = next[j];
    99 }
    100 next[i] = j + 1;
    101 }
    102
    103 /* Match. */
    104 i = 0;
    105 j = 0;
    106 while (content[i] && pattern[j])
    107 {
    108 if (content[i] == pattern[j])
    109 {
    110 ++i;
    111 ++j;
    112 }
    113 else
    114 {
    115 j = next[j];
    116 if (-1 == j)
    117 {
    118 ++i;
    119 ++j;
    120 }
    121 }
    122 }
    123
    124 free(next);
    125
    126 if (pattern[j])
    127 {
    128 return NULL;
    129 }
    130 else
    131 {
    132 return &content[i - j];
    133 }
    134 }
    135
    136  /*
    137 * 实现二
    138 */
    139  char * kmp2(char * content, char * pattern)
    140 {
    141 int i;
    142 int j;
    143 int len;
    144 int * next;
    145
    146 if (NULL == content || NULL == pattern)
    147 {
    148 return NULL;
    149 }
    150
    151 len = strlen(pattern);
    152 next = (int *)malloc(len * sizeof(int));
    153
    154 /* Get the "next" array. */
    155 next[0] = -1;
    156 i = 0;
    157 j = -1;
    158 while (pattern[i])
    159 {
    160 if (-1 == j || pattern[i] == pattern[j])
    161 {
    162 ++i;
    163 ++j;
    164 next[i] = j;
    165 }
    166 else
    167 {
    168 j = next[j];
    169 }
    170 }
    171
    172 /* Match. */
    173 i = 0;
    174 j = 0;
    175 while (content[i] && pattern[j])
    176 {
    177 if (content[i] == pattern[j])
    178 {
    179 ++i;
    180 ++j;
    181 }
    182 else
    183 {
    184 j = next[j];
    185 if (-1 == j)
    186 {
    187 ++i;
    188 ++j;
    189 }
    190 }
    191 }
    192
    193 free(next);
    194
    195 if (pattern[j])
    196 {
    197 return NULL;
    198 }
    199 else
    200 {
    201 return &content[i - j];
    202 }
    203 }
    204
    205 /*
    206 * 实现三
    207 *
    208 * 实现二的改进,改进处见注释。
    209 */
    210 char * kmp3(char * content, char * pattern)
    211 {
    212 int i;
    213 int j;
    214 int len;
    215 int * next;
    216
    217 if (NULL == content || NULL == pattern)
    218 {
    219 return NULL;
    220 }
    221
    222 len = strlen(pattern);
    223 next = (int *)malloc(len * sizeof(int));
    224
    225 /* Get the "next" array. */
    226 next[0] = -1;
    227 i = 0;
    228 j = -1;
    229 while (pattern[i])
    230 {
    231 if (-1 == j || pattern[i] == pattern[j])
    232 {
    233 ++i;
    234 ++j;
    235
    236 /* 此处是对实现二的改进。 */
    237 if (pattern[i] == pattern[j])
    238 {
    239 next[i] = next[j];
    240 }
    241 else
    242 {
    243 next[i] = j;
    244 }
    245 }
    246 else
    247 {
    248 j = next[j];
    249 }
    250 }
    251
    252 /* Match. */
    253 i = 0;
    254 j = 0;
    255 while (content[i] && pattern[j])
    256 {
    257 if (content[i] == pattern[j])
    258 {
    259 ++i;
    260 ++j;
    261 }
    262 else
    263 {
    264 j = next[j];
    265 if (-1 == j)
    266 {
    267 ++i;
    268 ++j;
    269 }
    270 }
    271 }
    272
    273 free(next);
    274
    275 if (pattern[j])
    276 {
    277 return NULL;
    278 }
    279 else
    280 {
    281 return &content[i - j];
    282 }
    283 }
    284
    285 int main(int argc, char * argv[])
    286 {
    287 printf("%s\n", kmp1(argv[1], argv[2]));
    288 printf("%s\n", kmp2(argv[1], argv[2]));
    289 printf("%s\n", kmp3(argv[1], argv[2]));
    290
    291 return 0;
    292 }

    BM算法:全称Boyer-Moore string search algorithm 预处理时间Θ(m + |Σ|) 匹配搜索时间Ω(n/m), O(n)

    代码
    1 using System;
    2 namespace stringsearch
    3 {
    4 /// <summary>
    5 /// 字符串搜索的基本抽象类
    6 /// </summary>
    7 public abstract class StringSearchTool
    8 {
    9 public enum Search
    10 {
    11 NOT_FOUND,
    12 SEARCH_EXACT,
    13 SEARCH_CASELESS
    14 }
    15
    16 protected Search search;
    17 protected String pattern;
    18
    19 public string Pattern
    20 {
    21 get
    22 {
    23 return pattern;
    24 }
    25 set
    26 {
    27 //大小写暂时无用处
    28 if (search == Search.SEARCH_CASELESS)
    29 {
    30 pattern = value;
    31 pattern.ToUpper();
    32 }
    33 else
    34 {
    35 pattern = value;
    36
    37 }
    38 }
    39 }
    40
    41 public StringSearchTool()
    42 {
    43 search = Search.SEARCH_CASELESS;
    44 pattern = null;
    45 }
    46 public StringSearchTool(string p)
    47 {
    48 search = Search.SEARCH_CASELESS;
    49 pattern = p;
    50
    51 }
    52
    53
    54 public StringSearchTool(string p, Search type)
    55 {
    56 search = type;
    57 pattern = p;
    58 }
    59 public int getPatternLength()
    60 {
    61 return pattern.Length;
    62 }
    63 public Search getSearchType()
    64 {
    65 return search;
    66 }
    67 public int find(string target)
    68 {
    69 return find(target, 0);
    70 }
    71 public abstract int find(string target, int start);
    72 }
    73 }
    74 // BoyerMoore算法
    75 using System;
    76 namespace stringsearch
    77 {
    78 /// <summary>
    79 ///
    80 /// </summary>
    81 public class BoyerMoore : stringsearch.StringSearchTool
    82 {
    83 protected int[] delta;
    84 private static readonly int DELTA_SIZE = 65536;
    85 public BoyerMoore()
    86 : base()
    87 {
    88
    89 }
    90 public BoyerMoore(string p)
    91 : base(p)
    92 {
    93
    94 }
    95 public BoyerMoore(string p, Search type)
    96 : base(p, type)
    97 {
    98
    99 }
    100
    101 public override int find(string target, int start)
    102 {
    103 if ((pattern == null) || (start < 0))
    104 return (int)Search.NOT_FOUND;
    105 String target2;
    106 //if(search==Search.SEARCH_CASELESS)
    107 // target2=target.ToUpper();
    108 //else
    109 target2 = target;
    110 int t = start + pattern.Length;
    111 while (t <= target2.Length)
    112 {
    113 int p = pattern.Length;
    114 while (pattern[p - 1] == target2[t - 1])
    115 {
    116 if (p > 1)
    117 {
    118 --p;
    119 --t;
    120 }
    121 else
    122 {
    123 return t - 1;
    124 }
    125 }
    126 t += delta[(int)target2[t - 1]];
    127 }
    128 return (int)Search.NOT_FOUND;
    129 }
    130 public new string Pattern
    131 {
    132 get
    133 {
    134 return base.Pattern;
    135 }
    136 set
    137 {
    138 base.Pattern = value;
    139 int n;
    140 delta = new int[DELTA_SIZE];
    141 for (n = 0; n < DELTA_SIZE; ++n)
    142 delta[n] = pattern.Length;
    143 for (n = 1; n < pattern.Length; ++n)
    144 delta[(int)pattern[n - 1]] = pattern.Length - n;
    145 delta[(int)pattern[pattern.Length - 1]] = 1;
    146 }
    147 }
    148 }
    149 }
    150 // 测试代码(部分):
    151 private void button1_Click(object sender, System.EventArgs e)
    152 {
    153 String terget = label1.Text;
    154 String pattern=textBox1.Text;
    155 BoyerMoore bm=new BoyerMoore();
    156 bm.Pattern=pattern;
    157 if (bm.find(terget,0)>0)
    158 MessageBox.Show(this,"你是不是在找 "+pattern+" ?"+"恭喜你找到了!");
    159 else
    160 MessageBox.Show(this,"下面的这段话中没有找到你所查找的文字!");
    161 }

    2. 有限模式集合匹配

    就是在字符串中查找多个子字符串的算法,常用于查找字典中的单词和一些脏字匹配算法

    Aho-Corasick算法:这是一种字典匹配算法,它用于在输入文本中查找字典中的字符串。时间复杂度是线性的。

    基本原理:该算法利用类似后缀树的方法构造一个trie结构,匹配时利用该结构来搜索。

    Aho-Corasick:可以认为是KMP算法在多串查找的扩展。先把所有要查找的串放在一起建一棵trie树。然后从源串的各个位置开始的串到trie树里查找。这样每次查找的复杂度为O(m),m为最长的子串。总共要查n次,所以复杂度为O(nm)。这只能说是brute force算法在多串下的扩展。所以还要在trie树里添加一些转移,使得源串在匹配过程中不出现回退。假设当前节点为i,源串匹配到字符c,如果节点i不存在c字母对应的转移。这时候应该跳转到一个节点j,从trie树根节点到这个节点的字母组成的字符串,应该是从根节点组成的字符串的后缀,如果有多个这样的节点,则跳转到最短的一个。分析一下这个自动机在匹配时的复杂度。要匹配过程中,源串不回退,所以遍历源串的复杂度为O(n)。但是在匹配失败的时候,会出现跳转,可能要跳转很多次才可以匹配成功。但是注意一点,每次跳转使得深度至少减1,而深度至多跟源串匹配的长度相等,所以可以跳转的次数不会超过源串的长度,所以总的复杂度是O(n)。

    要注意一个地方,后缀关系是可以传递的,a是b的后缀,b是c的后缀,则a是c的后缀。所以上面的表达式,最终是把一个串的后有后缀,从长到短串成了一个链表。

    难的地方就是自动机的构造。为了便于分析,把建trie树跟增加跳转分成两个过程。假设现在已经建好trie树。要为一个节点增加跳转,最简单的方法是,把根结点到它一路上的字符串组成的字符串,求后缀,再到trie树里查找,因为是求最长的后缀,所以从长到短,逐一去查找,直至找到为止,找到的节点就是要跳转的节点。这种方法的复杂度很高。观察一下,假设在trie树里,i节点是j节点的父结点,j的跳转结点的父结点表示的串,也是i的后缀。反过来说,根据一个节点的父结点的跳转可以快速地找到这个结点的跳转。假设当前节点的父结点的跳转结点为k,下一个要匹配的字符为c,看k是否有c的跳转,如果没有,则k继续跳转。会否出现没有找到合法的跳转,而k又不能再跳转的情况呢?初始的时候,把所有节点的跳转都指向根结点,可以避免这种情况。

    在实现的时候,因为跳转总是从深度大的结点跳到深度小的结点,所以对trie树作广度遍历。现在来分析一下构造的复杂度。trie树中结点的总数不会超过待查找的子串的长度和。但是每个结点的查找会进行多次跳转。分析一个子串它对应的一系列节点,每交跳转会使得深度减1,而这些节点的深点至多为子典的长度,所以一个子串对应的所有结点的构造费用之和,不会超过字串的长度,所以总的构造复杂度是跟字符长度和成正比的。


    Commentz-Walter 算法:是BM算法的自然扩展,它的速度并不快

    Rabin-Karp string search算法:该算法最差复杂度不好,因此运用的并不广泛。

    1. 问题描述

          给定目标字符串 T[0..n-1] (基于 0 的数组,数组长度为 n ),和模式串 P[0..m-1] ,问 P 可否匹配 T 中的任意子串,如果可以,返回匹配位置。

    2. 问题分析

    直观分析

          brute-force 的蛮力法,适用于较小规模的字符串匹配。

     优化

          主要介绍 3 种优化办法,分别具体为: Rabin-Karp 算法,有限自动机和 KMP 算法。将分为 3 篇博文分别讨论。本小节主要介绍 Rabin-Karp 算法。

     得出算法

          Rabin-Karp 算法(以下简称为 RK 算法),是基于这样的思路:即把串看作是字符集长度进制的数,由数的比较得出字符串的比较结果。例如,给定字符集为∑ ={0,1,2,3,4,5,6,7,8,9} ,∑长度为 d=10 ,那么任何以∑为字符集的串都可看作 d (此处为 10 )进制的数。

    记模式串 P[0..n-1] 对应的数值为 P , T[0..n-1] 所有长度为 m 的子串对应的数值为 ts ,设 P 和 T 都是基于字符集长度为 | ∑ |=d 的字符串。

    那么, ts 即为 T[s..s+m] 对应的数值,这里 0<=s<=n-m-1 。

    P = P[m]+d*(P[m-1]+d*(P[m-2]+..)))

    同样 t0 也可类似求得。

    最重要的是如何从 ts 求出 ts+1

    ts+1 =T[s+m]+d*(ts +dm-1 *T[s])

    注:此处是该算法的关键,即在常数时间内能够计算出下一个 m 长度的字串对应的数值。初看比较抽象,举个例子就比较明白了,设 x=12345 ,现在是已知长度为 3 的数值 234 ,现在要求 345 对应的数值,可以这样来得到: 345 = 5 + 10*(234-102 *2)

    3. 算法描述

          求出所有 m 长度子串所对应的数值,对数值进行比较,继而得出子串是否匹配。当模式串长度很大时,这时对应的数值会很大,比较起来比较麻烦,可使用对一个大奇数取模后进行比较。

    4. 具体实现

          这里实现的只是m值较小时的情形,大整数需要特定的类的支持(如可自定义大整数类),选取10进制的数是为了方便起见,当然字母也是OK的。

    代码
    #include "iostream"
    #include
    "string"
    #include
    "cmath"
    using namespace std;

    // get the value of the character in the set
    int getV(char p, string set)
    {
    for(int i=0; i<set.length(); i++)
    {
    if (p==set[i])
    return i;
    }
    return -1;
    }
    // d is the size of the character set
    int RK(string T, string P,string set)
    {
    int d = int(set.length());
    int n = T.length();
    int m = P.length();
    int h = pow(double(d), m-1);
    int p=0;
    int t = 0;
    for(int i=0; i<m; i++)
    {
    p
    = d*p + getV(P[i],set);
    t
    = d*t + getV(T[i], set);
    }
    for (int s=0; s<=n-m; s++)
    {
    cout
    <<"p,t is "<<p<<","<<t<<endl;
    if (p==t)
    return s;
    if (s<n-m)
    t
    = getV(T[s+m],set)+d*(t-h*getV(T[s],set));
    }
    return -1;
    }
    int main()
    {
    // set is the character set
    string set= "0123456789";
    // pattern P
    string P = "2365";
    // T is the string to match
    string T = "258569236589780";
    int i = RK(T, P, set);
    cout
    <<"the postition is:"<<i<<endl;
    return 0;
    }

  • 相关阅读:
    用 Python 带你看各国 GDP 变迁
    Fluent Interface(流式接口)
    probing privatePath如何作用于ASP.NET MVC View
    Word插入htm文件导致文本域动态增加的一个问题
    Visual Studio 2013附加进程调试IE加载的ActiveX Control无效解决方法
    Ubuntu下Chrome运行Silverlight程序
    Windows Phone Bing lock screen doesn't change解决方法
    SPClaimsUtility.AuthenticateFormsUser的证书验证问题
    Web Service Client使用Microsoft WSE 2.0
    Visual Studio 2013安装Update 3启动crash的解决方法
  • 原文地址:https://www.cnblogs.com/myphoebe/p/1794252.html
Copyright © 2011-2022 走看看