字符串 模式匹配

要点


模式匹配是数据结构中字符串的一种基本运算,给定一个子串,要求在某个字符串中找出与该子串相同的所有子串,这就是模式匹配

假设P是给定的子串,T是待查找的字符串,要求从T中找出与P相同的所有子串,这个问题成为模式匹配问题。P称为模式,T称为目标。如果T中存在一个或多个模式为P的子串,就给出该子串在T中的位置,称为匹配成功;否则匹配失败。

文中代码是本人自己写的,实测有效,含JAVA和C++两种代码。干货充足吧。

蛮力算法 (BF算法)


蛮力算法(Brute-Force),简称BF算法。(男朋友算法,简单粗暴—_—!)

算法思想

BF算法的算法思想是:

目标串T的的第一个字符起与模式串P的第一个字符比较。

若相等,则继续对字符进行后续的比较;否则目标串从第二个字符起与模式串的第一个字符重新比较。

直至模式串中的每个字符依次和目标串中的一个连续的字符序列相等为止,此时称为匹配成功,否则匹配失败。

通过下图示例,可一目了然:

算法性能

假设模式串的长度是m,目标串的长度是n。

最坏的情况是每遍比较都在最后出现不等,即没变最多比较m次,最多比较n-m+1遍。

总的比较次数最多为m(n-m+1),因此BF算法的时间复杂度为O(mn)。

BF算法中存在回溯,这影响到效率,因而在实际应用中很少采用。

代码

JAVA版本

1 public class BFMatch {
 2 
 3     static int bfMatch(String target, String pattern) {
 4         int pos = -1;
 5         int i = 0, j = 0, k = 0;
 6 
 7         // 在没找到匹配pattern的子串前,遍历整个target
 8         while (-1 == pos && i < target.length()) {
 9 
10             // 将目标串和模式串逐一比对,如果有不同的则退出
11             while (j < pattern.length() && target.charAt(i) == pattern.charAt(j)) {
12                 i++;
13                 j++;
14             }
15 
16             if (j >= pattern.length()) { // 如果模式串扫描完,说明目标串中含有这个子串
17                 pos = k;
18             } else { // 反之,没有扫描完,则从目标串的下一个字符开始重新逐一比对
19                 j = 0;
20                 k++;
21                 i = k;
22             }
23         }
24 
25         return pos;
26     }
27 
28     public static void print(String target, String pattern, int index) {
29         if (-1 != index) {
30             System.out.format("[%s] is in the Pos = %d of [%s]\n", pattern, index, target);
31         } else {
32             System.out.format("[%s] is not in the [%s]\n", pattern, target);
33         }
34     }
35 
36     public static void main(String[] args) {
37         String target = "Hello World";
38         String pattern = "llo";
39         String pattern2 = "Woe";
40 
41         int index = bfMatch(target, pattern);
42         int index2 = bfMatch(target, pattern2);
43         print(target, pattern, index);
44         print(target, pattern2, index2);
45 
46     }
47 
48 }

BF算法之JAVA实现

C++版本

1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 int bfMatch(string target, string pattern) {
 7     int pos = -1;
 8     int i = 0, j = 0, k = 0;
 9 
10     // 在没找到匹配pattern的子串前,遍历整个target
11     while (-1 == pos && i < (int)target.length()) {
12 
13         // 将目标串和模式串逐一比对,如果有不同的则退出
14         while (j < (int)pattern.length() && target[i] == pattern[j]) {
15             i++;
16             j++;
17         }
18 
19         if (j >= (int)pattern.length()) { // 如果模式串扫描完,说明目标串中含有这个子串
20             pos = k;
21         } else { // 反之,没有扫描完,则从目标串的下一个字符开始重新逐一比对
22             j = 0;
23             k++;
24             i = k;
25         }
26     }
27 
28     return pos;
29 }
30 
31 void print(string target, string pattern, int index) {
32     if (-1 != index) {
33         cout << "[" << pattern << "] is in the Pos = " << index << " of [" << target << "]" << endl;
34     } else {
35         cout << "[" << pattern << "] is not in the [" << target << "]" << endl;
36     }
37 }
38 
39 int main()
40 {
41     string target = "Hello World";
42     string pattern = "llo";
43     string pattern2 = "Woe";
44 
45     int index = bfMatch(target, pattern);
46     int index2 = bfMatch(target, pattern2);
47     print(target, pattern, index);
48     print(target, pattern2, index2);
49     return 0;
50 }

BF算法之C++实现

运行结果

[llo] is in the Pos = 2 of [Hello World]
[Woe] is not in the [Hello World]

KMP算法


Knuth-Morris-Pratt算法(简称KMP),是由D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的一个改进算法,消除了BF算法中回溯问题,完成串的模式匹配。

算法思想

在BF算法中,用模式串去和目标串的某个子串比较时,如果不全部匹配,就要回溯到起始位置,然后后移。

显然,移回到前面已经比较过的位置,还是不能完全匹配。

KMP算法的思想是,设法利用这个已知信息,跳过前面已经比较过的位置,继续把它向后移,这样就提高了效率。

由此可知,KMP算法其实有两大要点:

(1) 计算跳转位置信息,这里我们称之为部分匹配表。

(2) 后移到指定位置,重新开始匹配。

首先,来看如何获得部分匹配表

为了确定匹配不成功时,下次匹配时 j的位置,引入了next[]数组,next[j]的值表示模式串P[0...j-1]中最长后缀的长度等于相同字符序列的前缀。

这个next 数组叫做部分匹配表

对于next[]数组的定义如下:

对于BF算法中的例子,模式串P=“abcac”,根剧next[j]的定义,可得到下表:

j 0 1 2 3 4
t[j]  a b c a c
next[j] -1 0 0 0 1

有了部分匹配表,就可以后移到指定位置

在匹配过程中,若发生不匹配的情况。

如果next[j] >= 0,则目标串的指针 i 不变,将模式串的指针 j 移动到 next[j] 的位置继续进行匹配;

若next[j] = -1,则将 i 右移1位,并将 j 置0,继续进行比较。

以上要点配合下面的示意图理解,效果会更好哦。

算法性能

假设模式串的长度是m,目标串的长度是n。

在KMP算法中求next数组的时间复杂度为O(m),在后面的匹配中因目标串T的下标不用回溯,所以比较次数可记为n。

由此,得出KMP算法的总的时间复杂度O(n+m)

代码

JAVA版本

1 public class KMPMatch {
 2 
 3     // 计算部分匹配表
 4     public static int[] getNext(String pattern) {
 5         int j = 0, k = -1;
 6         int[] next = new int[pattern.length()];
 7         next[0] = -1;
 8         while (j < pattern.length() - 1) {
 9             if (-1 == k || pattern.charAt(j) == pattern.charAt(k)) {
10                 j++;
11                 k++;
12                 next[j] = k;
13             } else {
14                 k = next[k];
15             }
16         }
17 
18         return next;
19     }
20 
21     // KMP算法
22     static int kmpMatch(String target, String pattern) {
23         int i = 0, j = 0, index = 0;
24         int[] next = getNext(pattern); // 计算部分匹配表
25 
26         while (i < target.length() && j < pattern.length()) {
27             if (-1 == j || target.charAt(i) == pattern.charAt(j)) {
28                 i++;
29                 j++;
30             } else {
31                 j = next[j]; // 如果出现部分不匹配,获取跳过的位置
32             }
33         }
34 
35         if (j >= pattern.length())
36             index = i - pattern.length(); // 匹配成功,返回匹配子串的首字符下标
37         else
38             index = -1; // 匹配失败
39 
40         return index;
41 
42     }
43 
44     // 打印完整序列
45     public static void printAll(int[] list) {
46         for (int value : list) {
47             System.out.print(value + "\t");
48         }
49         System.out.println();
50     }
51 
52     public static void main(String[] args) {
53         String target = "ababcabcacbab";
54         String pattern = "abcac";
55         int index = kmpMatch(target, pattern);
56         System.out.format("[%s] is in the pos = %d of [%s]", pattern, index, target);
57     }
58 
59 }

KMP算法之JAVA实现

C++版本

1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 const int MAX = 100;
 7 int next[MAX] = {0};
 8 
 9 // 计算部分匹配表
10 void getNext(string pattern) {
11     int j = 0, k = -1;
12     next[0] = -1;
13     while (j < (int)pattern.length() - 1) {
14         if (-1 == k || pattern[j] == pattern[k]) {
15             j++;
16             k++;
17             next[j] = k;
18         } else {
19             k = next[k];
20         }
21     }
22     return;
23 }
24 
25 // KMP算法
26 int kmpMatch(string target, string pattern) {
27     int i = 0, j = 0, index = 0;
28     getNext(pattern); // 计算部分匹配表
29 
30     while (i < (int)target.length() && j < (int)pattern.length()) {
31         if (-1 == j || target[i] == pattern[j]) {
32             i++;
33             j++;
34         } else {
35             j = next[j]; // 如果出现部分不匹配,获取跳过的位置
36         }
37     }
38 
39     if (j >= (int)pattern.length())
40         index = i - pattern.length(); // 匹配成功,返回匹配子串的首字符下标
41     else
42         index = -1; // 匹配失败
43 
44     return index;
45 
46 }
47 
48 void print(string target, string pattern, int index) {
49     if (-1 != index) {
50         cout << "[" << pattern << "] is in the Pos = " << index << " of [" << target << "]" << endl;
51     } else {
52         cout << "[" << pattern << "] is not in the [" << target << "]" << endl;
53     }
54 }
55 
56 int main()
57 {
58     string target = "ababcabcacbab";
59     string pattern = "abcac";
60     int index = kmpMatch(target, pattern);
61     print(target, pattern, index);
62     return 0;
63 }

KMP算法之C++实现

运行结果

[abcac] is in the pos = 5 of [ababcabcacbab]

参考资料


《数据结构习题与解析》(B级第3版)

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

http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html

相关阅读


欢迎阅读 程序员的内功——算法 系列

时间: 2024-08-08 17:53:45

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

LeetCode:Word Pattern - 字符串模式匹配

1.题目名称 Word Pattern(字符串模式匹配) 2.题目地址 https://leetcode.com/problems/word-pattern/ 3.题目内容 英文:Given a pattern and a string str, find if str follows the same pattern. 中文:给出一组模式(pattern)和一个字符串(str),查看字符串是否与模式匹配 例如: pattern = "abba",str = "dog cat

字符串模式匹配sunday算法

文字部分转自:http://www.cnblogs.com/mr-ghostaqi/p/4285868.html 代码是我自己写的 今天在做LeetCode的时候,碰到一个写字符串匹配的题目: https://oj.leetcode.com/problems/implement-strstr/ 我一看就懵了,字符串模式匹配我记得当时在上数据结构的时候,书上只写了BF和KMP算法,老师说考试“只可能会考BF”,KMP不要求掌握. 然后出于一颗探求的心,我还是看了一下KMP,这算法好难理解,于是就没

MySQL学习笔记之九 字符串模式匹配

我们在使用查询的时候,经常会碰到模糊条件查询,而模糊查询就涉及到了字符串模式匹配. 在这里,主要讲两个:标准的SQL模式匹配.扩展正则表达式模式匹配.     一.标准的SQL模式匹配 SQL的模式匹配允许你使用"_"匹配任何单个字符,而"%"匹配任意数目字符(包括零个字符).在MySQL中,SQL的模式缺省是忽略大小写的.下面显示一些例子.注意在你 使用SQL模式时,你不能使用=或!=:而使用LIKE或NOT LIKE比较操作符. 为了找出包含正好5个字符的名字,

KMP字符串模式匹配详解

KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法.简单匹配算法的时间复杂度为O(m*n);KMP匹配算法.可以证明它的时间复杂度为O(m+n).. 一.简单匹配算法 先来看一个简单匹配算法的函数: int Index_BF ( char S [ ], char T [ ], int pos ) { /* 若串 S 中从第pos(S 的下标0≤pos个字符 起存在和串 T 相同的子串,则称匹配成功,返回第一个 这样的子串在串 S 中的下标,否则返回 -1    */ int

(转)KMP字符串模式匹配详解

(转)KMP字符串模式匹配详解 个人觉得这篇文章是网上的介绍有关KMP算法更让人容易理解的文章了,确实说得很“详细”,耐心地把它看完肯定会有所收获的--,另外有关模式函数值next[i]确实有很多版本啊,在另外一些面向对象的算法描述书中也有失效函数 f(j)的说法,其实是一个意思,即next[j]=f(j-1)+1,不过还是next[j]这种表示法好理解啊: KMP字符串模式匹配详解 KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法.简单匹配算法的时间复杂度为O(m*n)

【算法视频】字符串模式匹配--布鲁特.福斯算法

2.4.字符串模式匹配 资讯网址:www.qghkt.com 腾讯课堂:https://qghkt.ke.qq.com/20个常用算法 模式串(或子串)在主串中的定位操作通常称为串的模式匹配,它是各种串处理系统中最重要的运算之一. 2.4.1.布鲁特-福斯算法 [基本思想] 从主串的第一个字符起与模式串的第一个字符比较,若相等,则继续逐个字符进行后续比较,否则从主串的第二个字符起与模式串的第一个字符重新开始比较,直至模式串中每个字符依次与主串中的一个连续的字符序列相等时为止,此时称为匹配成功:如

字符串模式匹配KMP算法中的next数组算法及C++实现

一.问题描述: 对于两个字符串S.T,找到T在S中第一次出现的起始位置,若T未在S中出现,则返回-1. 二.输入描述: 两个字符串S.T. 三.输出描述: 字符串T在S中第一次出现的起始位置,若未出现,则返回-1. 四.输入例子: ababaababcbababc 五.输出例子: 5 六.KMP算法解析: KMP算法分为两步,第一步是计算next数组,第二步是根据next数组通过较节省的方式回溯来比较两个字符串. 网络上不同文章关于next数组的角标含义略有差别,这里取参考文献中王红梅<数据结构

字符串模式匹配之KMP算法图解与 next 数组原理和实现方案

之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 i指针,而是利用已经得到的“部分匹配”的结果将模式子串向右“滑动”尽可能远的一段距离后,继续进行比较.如果 ok,那么主串的指示指针不回溯!算法的时间复杂度只和子串有关!很好. KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的,很自然的,需要一个函数来存储匹

13.字符串-模式匹配

一般提起字符串的相关算法,就是几个基本的算法:赋值strcpy.求长strlen.联接strcat.比较strcmp和求子串substr.这5个操作相对来说都比较简单,构成了字符串的最小操作集,其他的算法都可以由这几个算法来实现.但是实际应用中,模式匹配index是应用非常广泛的字符串操作,我们倾向于不依赖其他的操作来实现它. 一般匹配 如下图,在目标字符串S中查找模式字符串T的最直白的做法就是: 1.分别用i.j指向字符串 2.依次遍历,S[i]==T[j]则i++.j++ 3.S[i]!=T