字符串匹配算法之Sunday算法

背景

我们第一次接触字符串匹配,想到的肯定是直接用2个循环来遍历,这样代码虽然简单,但时间复杂度却是Ω(m*n),也就是达到了字符串匹配效率的下限。于是后来人经过研究,构造出了著名的KMP算法(Knuth-Morris-Pratt算法),让我们的时间复杂度降低到了O(m+n),但现代文字处理器中,却很少使用KMP算法来做字符串匹配,因为还是太慢了。现在主流的算法是BM算法(Boyer-Moore算法),成功让平均时间复杂度降低到了O(m/n),而Sunday算法,则是对BM算法的进一步小幅优化。

KMP算法很多人看了一遍遍以后,对next[n]数组的理解还是有点困难(包括笔者),写代码的时候总是容易变成这种情况(/捂脸.jpg):

(切到网页):马冬梅

(切到编译器):马什么梅

(切到网页):马冬梅

(切到编译器):马冬什么

(切到网页):马冬梅

(切到编译器):什么冬梅

而Sunday算法,理解起来则是非常容易,同时极低的时间复杂度,让Sunday算法成为了笔者目前最常使用的字符串匹配算法

算法解释

Sunday算法和BM算法稍有不同的是,Sunday算法是从前往后匹配,在匹配失败时关注的是主串中参加匹配的最末位字符的下一位字符。

  • 如果该字符没有在模式串中出现则直接跳过,即移动位数 = 模式串长度 + 1;
  • 否则,其移动位数 = 模式串长度 - 该字符最右出现的位置(以0开始) = 模式串中该字符最右出现的位置到尾部的距离 + 1。

下面举个例子说明下Sunday算法。假定现在要在主串”substring searching”中查找模式串”search”。

  • 刚开始时,把模式串与文主串左边对齐:

     
    Sunday算法1
  • 结果发现在第2个字符处发现不匹配,不匹配时关注主串中参加匹配的最末位字符的下一位字符,即标粗的字符 i,因为模式串search中并不存在i,所以模式串直接跳过一大片,向右移动位数 = 匹配串长度 + 1 = 6 + 1 = 7,从 i 之后的那个字符(即字符n)开始下一步的匹配,如下图:

     
    Sunday2
  • 结果第一个字符就不匹配,再看主串中参加匹配的最末位字符的下一位字符,是’r’,它出现在模式串中的倒数第3位,于是把模式串向右移动3位(m - 3 = 6 - 3 = r 到模式串末尾的距离 + 1 = 2 + 1 =3),使两个’r’对齐,如下:

     
    Sunday3
  • 匹配成功。

详细代码(Java版)

static final int ASCII_SIZE = 126;   

int sunday(char[] total,char[] part){
int tSize = total.length;
int pSize = part.length;
int[] move = new int[ASCII_SIZE];
//主串参与匹配最末位字符移动到该位需要移动的位数
for (int i= 0;i<ASCII_SIZE;i++){
move[i] = pSize+1;
} for (int i = 0;i<pSize;i++){
move[part[i]] = pSize-i;
} int s = 0;//模式串头部在字符串位置 int j;//模式串已经匹配了的长度 while(s<=tSize-pSize){//到达末尾之前
j = 0;
while(total[s+j]==part[j]){
j++;
if (j>=pSize){
return s;
}
}
s+=move[total[s+pSize]];
}
return -1;
}

我们来一步一步解释

int sunday(char[] total,char[] part){
int tSize = total.length;
int pSize = part.length;
...
}

其中total为主串,part为模式串

        int[] move = new int[ASCII_SIZE];
//主串参与匹配最末位字符移动到该位需要移动的位数
for (int i= 0;i<ASCII_SIZE;i++){
move[i] = pSize+1;
} for (int i = 0;i<pSize;i++){
move[part[i]] = pSize-i;
}

定义一个长为ASCII码长度大小的数组,用于存放存入匹配失败时模式串需要移动的长度。这里看到,除了part中不存在的字符,移动长度都直接是模式串长度+1;而part中存在的字符,则需要移动的长度则依次减小。这也很好理解,因为我们匹配的是模式串首部位置+模式串长度+1位置的字母存在于模式串中的位置,这个位置越靠后,则整个模式串需要移动的距离就越短

        int s = 0;//模式串头部在字符串位置

        int j;//模式串已经匹配了的长度

s为模式串首部在字符串的位置,一开始为0;j是模式串已经匹配了的长度,一开始也是0

        while(s<=tSize-pSize){ // 1
j = 0; // 2
while(total[s+j]==part[j]){// 3
j++;// 4
if (j>=pSize){
return s;// 5
}
}
s+=move[total[s+pSize]]; // 6
}

这里是最关键的代码了,咱们讲细一点

  1. 首先循环继续的判定条件为s<=tSize-pSizes作为模式串首部在字符串的位置,加上pSize肯定要比tSize小,不然就越界了

  2. j是模式串已经匹配了的长度,匹配开始或者匹配失败后都要给j赋值为0,重新开始计数

  3. 接下就是一个字符一个字符的比较的循环

  4. 已经比较成功,则j加1

  5. 如果j已经大于等于pSize,就返回模式串首部在字符串当前的位置

  6. 这是最关键的一句,涉及到Sunday算法的核心,也就是模式串在主串中的“跳跃”,我们把这句代码分解一下就好理解的多

    int nextCompare = s+pSize; //跳到s+pSize,也就是模式串后的一个字符的位置
    int ascii_number = total[nextCompare];//获取转跳后位置的字符的ascii码值
    int moveLength = move[ascii_number];//根据ascii码值在move数组中查找模式串需要跳跃的长度
    s += moveLength; //让模式串首部在字符串的位置加上跳跃的长度,完成跳跃

一个例子

 String str1 = "searching substring";
String str2 = "substr";
sunday(str1.toCharArray(),str2.toCharArray());

其实最关键的,就是要计算move[]数组中的各个值,我们来手动算一下

pSize = 6;
i = 0 : part[i] = s; move[s] = 6;
i = 1 : part[i] = u; move[u] = 5;
i = 2 : part[i] = b; move[b] = 4;
i = 3 : part[i] = s; move[s] = 3;
i = 4 : part[i] = t; move[t] = 2;
i = 5 : part[i] = r; move[r] = 1; final:
move[s] = 3,
move[u] = 5,
move[b] = 4,
move[s] = 3,
move[t] = 2,
move[r] = 1 ,
move[其他] = 7

然后进行匹配

  1. s = 0, j = 1时,匹配失败

    total[s+pSize] = total[6] = i

    move[i] = 7

    s+=7

    待匹配串为ing substring

  2. s = 7 , j = 0 时,匹配失败

    total[s+pSize] = total[13] = u

    move[u] = 5

    s+=5

    待匹配串为substring

  3. 匹配成功

Sunday算法的缺点

看上去简单高效非常美好的Sunday算法,也有一些缺点。因为Sunday算法的核心依赖于move数组,而move数组的值则取决于模式串,那么就可能存在模式串构造出很差的move数组。例如下面一个例子

主串:baaaabaaaabaaaabaaaa

模式串:aaaaa

这个模式串使得move[a]的值为1,即每次匹配失败时,只让模式串向后移动一位再进行匹配。这样就让Sunday算法的时间复杂度飙升到了O(m*n),也就是字符串匹配的最坏情况

总结

当然,也不能因为存在最坏的情况就直接否定Sunday算法,大多数情况下,Sunday依然是一个简单高效的算法,值得我们熟练学习掌握。


 

字符串匹配算法之Sunday算法(转)的更多相关文章

  1. 字符串匹配算法之Sunday算法

    字符串匹配查找算法中,最着名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简 ...

  2. 字符串匹配算法:Sunday算法

    背景 我们第一次接触字符串匹配,想到的肯定是直接用2个循环来遍历,这样代码虽然简单,但时间复杂度却是\(Ω(m*n)\),也就是达到了字符串匹配效率的下限.于是后来人经过研究,构造出了著名的KMP算法 ...

  3. 动画演示Sunday字符串匹配算法——比KMP算法快七倍!极易理解!

    前言 上一篇我用动画的方式向大家详细说明了KMP算法(没看过的同学可以回去看看). 这次我依旧采用动画的方式向大家介绍另一个你用一次就会爱上的字符串匹配算法:Sunday算法,希望能收获你的点赞关注收 ...

  4. 字符串匹配算法之 kmp算法 (python版)

    字符串匹配算法之 kmp算法 (python版) 1.什么是KMP算法 KMP是三位大牛:D.E.Knuth.J.H.MorriT和V.R.Pratt同时发现的.其中第一位就是<计算机程序设计艺 ...

  5. 字符串匹配算法之BM算法

    BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法. BM算法定义了两个规则: ...

  6. Python 细聊从暴力(BF)字符串匹配算法到 KMP 算法之间的精妙变化

    1. 字符串匹配算法 所谓字符串匹配算法,简单地说就是在一个目标字符串中查找是否存在另一个模式字符串.如在字符串 "ABCDEFG" 中查找是否存在 "EF" ...

  7. 字符串匹配算法之————KMP算法

    上一篇中讲到暴力法字符串匹配算法,但是暴力法明显存在这样一个问题:一次只移动一个字符.但实际上,针对不同的匹配情况,每次移动的间隔可以更大,没有必要每次只是移动一位: 关于KMP算法的描述,推荐一篇博 ...

  8. 字符串匹配算法之kmp算法

    kmp算法是一种效率非常高的字符串匹配算法,是由Knuth,Morris,Pratt共同提出的模式匹配算法,所以简称KMP算法 算法思想 在一个字符串中查找另一个字符串时,会遇到如下图的情况 我们通常 ...

  9. 字符串匹配算法(二)-BM算法详解

    我们在字符串匹配算法(一)学习了BF算法和RK算法,那有没更加高效的字符串匹配算法呢.我们今天就来聊一聊BM算法. BM算法 我们把模式串和主串的匹配过程,可以看做是固定主串,然后模式串不断在往后滑动 ...

随机推荐

  1. MeteoInfoLab脚本示例:OMI Swath HDF数据

    这个例子读取OMI卫星Swath数据中的CloudFaction变量并绘图.脚本程序: #Add data file folder = 'D:/Temp/hdf/' fns = 'OMI-Aura_L ...

  2. day49 Pyhton 数据库Mysql 06

    多表查询 连表查询 要进行连接,那一定涉及两个表,两个表中要有关联条件才能进行连接 内连接 只有表一和表二中的连接条件都满足的时候才能显示出来 inner join on /where 条件 sele ...

  3. 【C语言C++编程学习笔记】一种很酷的 C 语言技巧,灵活运用编程技巧让你写代码事半功倍!

    C语言常常让人觉得它所能表达的东西非常有限.它不具有类似第一级函数和模式匹配这样的高级功能.但是C非常简单,并且仍然有一些非常有用的语法技巧和功能,只是没有多少人知道罢了. ☆ 指定的初始化 很多人都 ...

  4. kafka-manage管理工具

    1 github地址   https://github.com/sheepkiller/kafka-manager-docker   2 启动   将参数传递给kafka-manager   对于版本 ...

  5. centos8平台基于iftop监控网络流量

    一,iftop的作用: 基于ip统计外部机器与本机之间的网络流量, 可以方便的查看各客户端是否有非正常的到本机的访问 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnbl ...

  6. Vue留言 checked框案列

    在命令行窗口输入vue create "工程名"命令 来创建vue脚手架

  7. Python包安装及使用指南

    这里长期更新一些Python第三方包的安装教程,以及使用教程... Pygame 安装教程: Windows: 首先,查看已安装的Python版本:访问https://www.lfd.uci.edu/ ...

  8. vivo 基于原生 RabbitMQ 的高可用架构实践

    一.背景说明 vivo 在 2016 年引入 RabbitMQ,基于开源 RabbitMQ 进行扩展,向业务提供消息中间件服务. 2016~2018年,所有业务均使用一个集群,随着业务规模的增长,集群 ...

  9. 手把手教你使用 cert-manager 签发免费证书

    概述 随着 HTTPS 不断普及,越来越多的网站都在从 HTTP 升级到 HTTPS,使用 HTTPS 就需要向权威机构申请证书,需要付出一定的成本,如果需求数量多,也是一笔不小的开支.cert-ma ...

  10. Djano之数据库--ORM

    一.建立数据库模型类 1.在model里创建模型类.(继承models.Model) 1 class Order(models.Model): 2 TYPE_CHOICE = ( 3 (0, u&qu ...