字符串的模式匹配中的算法

字符串的模式匹配是一个比较经典的问题:假设有一个字符串S,称其为主串,然后还有一个字符串T,称其为子串。

现在要做的是,从主串S当中查找子串T的位置,如果存在返回位置值,如果不存在返回-1。另外主串又称为目标串,

子串称为模式串。

暴力匹配算法

这是一个经典的串匹配问题,涉及的算法也比较多,先讨论第一种简单的暴力算法,思路如下

将主串S的第pos个字符 与 子串T的第一个字符比较, 若相同,继续比较子串和主串后面的字符。

若不相同,那么从主串S的第(pos + 1)个字符开始继续向后匹配,直到匹配到主串的(S.len - T.len)的位置为止

匹配成功返回索引值,匹配失败返回-1,下面是实现代码

#include <stdio.h>
#include <stdlib.h>

#define OK 1

typedef int Status;
typedef struct {
  char *data;
  int  len;
}String;

Status initString(String *T){
  T->data = NULL;
  T->len  = 0;
  return OK;
}

Status strAssign(String *T,char *str){
  if(T->data)free(T->data);

  int i=0,j;
  while(str[i]!=‘\0‘)i++;
  if(!i){T->data=NULL;T->len=0;}
  else{
    T->data=(char*)malloc(i*sizeof(char));
    for(j=0;j<i;j++){
      T->data[j]=str[j];
    }
    T->data[j]=‘\0‘;
    T->len=i;
  }
  return OK;
}

int Index_SimpleMode(String T,String S,int pos){
  if( S.len == 0 || T.len<S.len )return -1;

  int i=pos, j=0;
  while(i<T.len && j<S.len){
    if(T.data[i] == S.data[j]){i++,j++;}
    else{i=i-j+1;j=0;}
  }
  if(j==S.len)return i-S.len;
  else return -1;
}

int main(){
  int pos,idx;
  String S1,S2;
  initString(&S1);
  initString(&S2);

  char *main = (char*)malloc(100*sizeof(char));
  char *sub  = (char*)malloc(100*sizeof(char));

  printf("enter string:");
  scanf("%s",main);

  printf("enter string:");
  scanf("%s",sub);

  printf("enter index:");
  scanf("%d",&idx);

  strAssign(&S1,main);
  strAssign(&S2,sub);

  pos = Index_SimpleMode(S1,S2,idx);
  if(pos<0)printf("doesn‘t match\n");
  else printf("find it:%d\n",pos);

  return OK;
}

设主串S的长度是n  子串T的长度是m

最好的情况是:主串为aaaaaabc, 子串为bc,此时执行的次数是 ( m + n ) / 2

时间复杂度为O(n+m)

最坏的情况是:主串为000000001,子串为01,此时执行的次数是 m * ( n - m + 2 ) / 2

时间复杂度为O(m*n)

双向匹配算法

可以看到上面所示最坏的情况需要不断回滚,可以限制匹配成功的条件,也就是模式串的首尾同时匹配上

那么再继续进行匹配,这个算法我称其为双向匹配算法,先不管该算法有没有很牛的名字,其思路是在暴力

匹配的基础上加上 模式串的尾部也需要相同才算匹配成功,然后首尾两头向中间移动继续匹配字符,因此如果

模式串的一半长度匹配成功,那么另一半也就匹配成功,则返回成功匹配的索引值,否则返回-1

#include <stdio.h>
#include <stdlib.h>

#define OK 1

typedef int Status;
typedef struct {
  char *data;
  int  len;
}String;

Status initString(String *T){
  T->data = NULL;
  T->len  = 0;
  return OK;
}

Status strAssign(String *T,char *str){
  if(T->data)free(T->data);

  int i=0,j;
  while(str[i]!=‘\0‘)i++;
  if(!i){T->data=NULL;T->len=0;}
  else{
    T->data=(char*)malloc(i*sizeof(char));
    for(j=0;j<i;j++){
      T->data[j]=str[j];
    }
    T->data[j]=‘\0‘;
    T->len=i;
  }
  return OK;
}

int twoWayMode(String T,String S){
  if(S.len > T.len)return -1;
  int i=0, j=0, m=S.len-1, n=m/2;
  int count = 0;
  while(i<T.len && j<S.len){
    if(T.data[i]==S.data[j] && T.data[(i-j)+(m-j)]==S.data[m-j]){
      ++i;
      ++j;
      if(j>n){  //improve;
        printf("count is %d\n",++count);
        return i-j;
      }
    }else{
      i=i-j+1;  //rollback
      j=0;
    }
    count++;
  }
  printf("calc: %d\n",count);
  return -1;
}

int main(){

  char str1[] = "ABCABd ABdsadA ABCAdsaABddsadasdaABCDsadCaDdsaABCDEFGH";
  char str2[] = "ABCDEFGH";
  String S1,S2;
  initString(&S1);
  initString(&S2);
  strAssign(&S1,str1);
  strAssign(&S2,str2);

  int res = twoWayMode(S1,S2);
  if(res>=0)printf("find it: %d\n",res);
  else
    printf("doesn‘t match\n");
  return OK;
}

其时间复杂度与暴力匹配的时间复杂度基本相同,但是新的算法的复杂度更接近于最好的情况也就是O(m + n)

KMP匹配算法

还有一种改进的算法是KMP算法,这个算法不太好理解

因此这里找了一篇看过中讲的最好的文章:

http://wiki.jikexueyuan.com/project/kmp-algorithm/define.html

当然可以先看阮一峰的文章,方便理解:

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

下面是具体的实现代码

#include <stdio.h>
#include <stdlib.h>

#define OK 1

typedef int Status;
typedef struct {
  char *data;
  int  len;
}String;

Status initString(String *T){
  T->data = NULL;
  T->len  = 0;
  return OK;
}

Status strAssign(String *T,char *str){
  if(T->data)free(T->data);

  int i=0,j;
  while(str[i]!=‘\0‘)i++;
  if(!i){T->data=NULL;T->len=0;}
  else{
    T->data=(char*)malloc(i*sizeof(char));
    for(j=0;j<i;j++){
      T->data[j]=str[j];
    }
    T->data[j]=‘\0‘;
    T->len=i;
  }
  return OK;
}
//next数组算法
void getNext(String S,int *next){
  next[0] = -1;
  int k=-1, j=0;
  while(j<S.len-1){
    if(k==-1 || S.data[j]==S.data[k]){
      ++k;
      ++j;
      next[j]=k;
    }else{
      k=next[k];
    }
  }

  int i;
}
//优化后的next数组算法
void newNext(String S,int *next){
  next[0] = -1;
  int k=-1, j=0;
  while(j<S.len-1){
    if(k==-1 || S.data[j]==S.data[k]){
      ++k;
      ++j;
      if(S.data[j]!=S.data[k])
        next[j] = k;
      else
        next[j] = next[k];
    }
    else{
      k=next[k];
    }
  }
}

int KMPSearch(String T,String S){
  int i=0, j=0, next[S.len], c=0;
  //getNext(S,next);
  newNext(S,next);

  while(i<T.len && j<S.len){
    if(j==-1 || T.data[i]==S.data[j]){i++;j++;}
    else{
      j=next[j];
    }
    c++;
  }
  printf("[TEST]:newNext: %d\n",c);
  if(j==S.len)return i-j;
  else return -1;
}

int main(){

  char str1[] = "ABCABd ABdsadA ABCAdsaABddsadasdaABCDsadCaDdsaABCDEFGH";
  char str2[] = "ABCDEFGH";
  String S1,S2;
  initString(&S1);
  initString(&S2);
  strAssign(&S1,str1);
  strAssign(&S2,str2);
  int res = KMPSearch(S1,S2);
  printf("%s\n%s\n",str1,str2);
  if(res>0)printf("find it :%d\n",res);
  else printf("doesn‘t match\n");
  return OK;
}

事实上还有一种算法叫BM算法,这种算法的使用目前是多的,而且也比较容易理解,由于时间有限,下次在此补充,大家可以参考下面的连接

通常情况下,模式串的长度n 远小于 目标串的长度m ,因此KMP的算法时间复杂度为O(m)

http://wiki.jikexueyuan.com/project/kmp-algorithm/bm.html

欢迎各位转载,但请指明出处:Demon先生

时间: 2024-10-06 01:20:25

字符串的模式匹配中的算法的相关文章

lua中的字符串操作(模式匹配)

模式匹配函数 在string库中功能最强大的函数是: string.find(字符串查找)string.gsub(全局字符串替换)string.gfind(全局字符串查找)string.gmatch(返回查找到字符串的迭代器) 这些函数都是基于模式匹配的.与其他脚本语言不同的是,Lua并不使用POSIX规范的正则表达式[4](也写作regexp)来进行模式匹配.主要的原因出于程序大小方面的考虑:实现一个典型的符合POSIX标准的regexp大概需要4000行代码,这比整个Lua标准库加在一起都大

ACM中常用算法----字符串

ACM中常用算法--字符串 ACM中常用的字符串算法不多,主要有以下几种: Hash 字典树 KMP AC自动机 manacher 后缀数组 EX_KMP SAM(后缀自动机) 回文串自动机 下面来分别介绍一下: 0. Hash 字符串的hash是最简单也最常用的算法,通过某种hash函数将不同的字符串分别对应到不同的数字.进而配合其他数据结构或STL可以做到判重,统计,查询等操作. #### 字符串的hash函数: 一个很简单的hash函数代码如下: ull xp[maxn],hash[max

模式匹配的KMP 算法

常见的字符串匹配时,模式串长度为n,源串长度为m,则从头匹配,两个指针i指向源串,j指向模式串,如遇到不同则回溯使j=0,这样就要反复匹配会使效率变低. 因为在如今i之前 的模式串与匹配串的匹配是同样的,即回溯时,不用将模式串与源串进行匹配,而仅仅将模式串与自身匹配就可以得到其是否须要回溯以及回溯到何处.则我们能够在进行模式匹配之前,想对模式串进行自我匹配,来计算出对于i在模式串的任何位置匹配失败后回溯的位置. 而对于自身匹配的算法另一个优化的地方在于,模式串在b位置匹配到自身的a位置,然后推断

数据结构例程——串的模式匹配(KMP算法)

本文针对数据结构基础系列网络课程(4):串中第5课时串的模式匹配(KMP算法). 问题:串的模式匹配 KMP算法: #include <stdio.h> #include "sqString.h" void GetNext(SqString t,int next[]) /*由模式串t求出next值*/ { int j,k; j=0; k=-1; next[0]=-1; while (j<t.length-1) { if (k==-1 || t.data[j]==t.d

【华为OJ】【081-查找两个字符串a,b中的最长公共子串】

[华为OJ][算法总篇章] [华为OJ][081-查找两个字符串a,b中的最长公共子串] [工程下载] 题目描述 查找两个字符串a,b中的最长公共子串 输入描述 输入两个字符串 输出描述 返回重复出现的字符 输入例子 abcdefghijklmnop abcsafjklmnopqrstuvw 输出例子 jklmnop 算法实现 import java.util.Scanner; /** * Author: 王俊超 * Date: 2016-01-04 08:43 * Declaration: A

程序员如何快速准备面试中的算法 - 结构之法

准备面试.学习算法,特别推荐最新出版的我的新书<编程之法:面试和算法心得>,已经上架京东等各大网店 前言 我决定写篇短文,即为此文.之所以要写这篇文章,缘于微博上常有朋友询问,要毕业找工作了,如何备战算法.尽管在微博上简单梳理过,如下图所示: 但因字数限制,许多问题无法一次性说清楚,故特撰此文着重阐述下:程序员如何快速准备面试中的算法,继而推荐一些相关的书籍或资料.顺便也供节后跳槽.3月春季招聘小高潮.及6月毕业找工作的朋友参考. 备战面试中算法的五个步骤 对于立志进一线互联网公司,同时不满足

用Javascript方式实现LeetCode中的算法(更新中)

前一段时间抽空去参加面试,面试官一开始让我做一道题,他看完之后,让我回答一下这个题的时间复杂度并优化一下,当时的我虽然明白什么是时间复杂度,但不知道是怎么计算的,一开局出师不利,然后没然后了,有一次我逛博客园时看到有个博主的文章说到有LeetCode这玩意,于是就知道了LeetCode.忽然有一种疑问:前端学不学算法?我看过一篇博文:为什么我认为数据结构与算法对前端开发很重要? 我觉得,前端应该是要学一下算法的,不久后前端明朗化,要做的工作量不低于后端人员,到时候也会像优化页面一样去优化js,既

初识面试中的算法题

在面试过程中,常常被要求手撕代码,作者作为测试经理,也经常被手撕代码.手撕代码的内入无非是对字符串.数组.元组.字典进行操作.第一题:取值:1.从字符串及元组中取值str1 = "abcd"stra, strb, strc, strd=str1结果:"a","b","c","d"2.从数组中取值list1 = [a,b,c,d]lista = list1[0],以此类推,中括号中的数字是数组的下标,从0开始

推荐系统中常用算法 以及优点缺点对比

推荐系统中常用算法 以及优点缺点对比 在 推荐系统简介中,我们给出了推荐系统的一般框架.很明显,推荐方法是整个推荐系统中最核心.最关键的部分,很大程度上决定了推荐系统性能的优劣.目前,主要的推荐方法包括:基于内容推荐.协同过滤推荐.基于关联规则推荐.基于效用推荐.基于知识推荐和组合推荐. 一.基于内容推荐 基于内容的推荐(Content-based Recommendation)是信息过滤技术的延续与发展,它是建立在项目的内容信息上作出推荐的,而不需要依据用户对项目的评价意见,更多地需要用机 器