龙空技术网

面试基础算法必备-KMP详解

老白说IT 288

前言:

现时各位老铁们对“kmp算法 partial match table”可能比较注重,同学们都需要了解一些“kmp算法 partial match table”的相关资讯。那么小编同时在网上汇集了一些对于“kmp算法 partial match table””的相关内容,希望各位老铁们能喜欢,我们一起来学习一下吧!

简介

现在的大厂面试,几乎都要涉及到算法面试,而‘字符串匹配’就是一个比较经典的算法问题。而且字符串匹配也是计算机程序中一个非常常用的功能或者任务。

举个简单的例子,我有一个字符串‘BC ABCDAB ABCDABCDABDE’。我想知道这个串里面是否包含了字符串‘ABCDABD’。

有很多种算法可以达成这个功能,而今天要介绍的KMP算法就是其中最常用的一种算法。

这个算法,对于很多第一次接触的朋友来说,都非常的晦涩难懂。老白第一次看到的时候也是云里雾里,直到自己通过一些例子,一步一步的推算了这个算法之后,才算比较清晰的理解了这个漂亮的算法。接下来,老白尝试用相对简单的方法来讲解些这个算法。

KMP这个算法主要的思路是通过部分匹配信息来跳过一些不可能匹配的字段,进而减少比较的次数。

部分匹配表

在开始讲这个算法之前,需要给大家普及一个概念‘部分匹配表’(partial match table),这个概念对于理解KMP算法很重要。 这个概念看起来好像很不好理解,其实说白了就是前后缀比较。

我们先来看看如果创建这个表吧:

0. 将搜索字符串拆分成,以第一个字符为起始每次递增一个字符的字符串集合。比如ABCDABD为搜索字符串,拆分的字符串集合就是[A, AB, ABC, ABCD, ABCDA, ABCDAB, ABCDABD]

1. 对字符串集合中的每个元素进行如下操作:

1.1. 生成元素的所有前缀字符串。前缀就是除了最后一个字符以外,所有可能开头的部分。比如ABCDABD的所有前缀是A, AB, ABC, ABCD, ABCDA, ABCDAB

1.2. 生成元素的所有后缀字符串。后缀跟前缀类似,就是除了第一个字符以外,所有可能开头的部分。比如ABCDABD的所有后缀是D, BD, ABD, DABD, CDABD, BCDABD

1.3. 计算1.1生成的前缀与1.2生成的后缀相同元素的长度 n

1.4. 将[length(元素)-1: n] 组合进行存储

还是以ABCDABD为例, 拆分的字符串组合是[A, AB, ABC, ABCD, ABCDA, ABCDAB, ABCDABD]

A 没有前后缀,共有元素n=0,length(A)=1,得到0:0

AB 前缀[A], 后缀[B],共有元素n=0, length(AB)=2,得到1:0

ABC 前缀[A, AB],后缀[C, BC],共有元素n=0,length(ABC)=3,得到2:0

ABCD 前缀[A, AB, ABC],后缀[D, CD, BCD],共有元素n=0,length(ABCD)=4,得到3:0

ABCDA 前缀[A, AB, ABC, ABCD],后缀[A, DA, CDA, BCDA],共有元素[A],n=1,length(ABCDA)=5,得到4:1

ABCDAB 前缀[A, AB, ABC, ABCD, ABCDA],后缀[B, AB, DAB, CDAB, BCDAB],共有元素[AB],n=2,length(ABCDAB)=6,得到5:2

ABCDABD 前缀[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀[D, BD, ABD, DABD, CDABD, BCDABD],共有元素n=0,length(ABCDABD)=7,得到6:0

因此就得到部分匹配表如下

索引(以0为基准) 0 1 2 3 4 5 6

对应元素 A B C D A B D

部分匹配值 0 0 0 0 1 2 0

至此,部分匹配表就创建好了。

KMP核心

接下来,将讲到KMP的核心匹配算法部分。

1. 搜索字符串‘BC ABCDAB ABCDABCDABDE’中第一个与‘ABCDABD’头字符相匹配的位置

2. 继续比较下一个字符,直到比较的字符不相等

3. 虽然空格和D不匹配,但是前面的六个字符ABCDAB是匹配的。通过查找部分匹配表尝试减少不必要的重复匹配。最后一个匹配字符是B,其对应的数值是2。因此移动的位数是6-2=4。移动4位。这里为什么移动4位呢?因为之前匹配上的字符ABCDAB长度是6。而部分匹配表中的2代表了前缀AB(CDAB)和后缀(ABCD)AB是相等的。也就意味着前缀的AB往后跳4个位,就又可以匹配上了。为什么不移动4位以上呢?比如5位?因为后面的数据比较出错了。没法判断是否可以跳过当前的数值。那为什么不是移动3位或者2位呢?因为即使移动2位也可能匹配上AB,但是4位是能够移动的最大量。而且即使移动更少的位数匹配上了AB,后面的数据也无法匹配。

4. 移动后的数据,空格和C依然不匹配。继续查看部分匹配表。已经匹配的是AB,匹配表数值是0。因此需要移动的是2-0位。由此我们可以知道移动的公式=匹配长度-最后一个匹配字符在部分匹配表中的数值。

5. 继续后移,进行匹配,一直匹配完ABCDAB,C和D不匹配。进行移动6-2=4位。匹配完成。

C#实现

using System;   class GFG {       void KMPSearch(string pat, string txt)     {         int M = pat.Length;         int N = txt.Length;           int[] lps = new int[M];         int j = 0;        computeLPSArray(pat, M, lps);           int i = 0;        while (i < N) {             if (pat[j] == txt[i]) {                 j++;                 i++;             }             if (j == M) {                 Console.Write("Found pattern "                              + "at index " + (i - j));                 j = lps[j - 1];             }               else if (i < N && pat[j] != txt[i]) {                 if (j != 0)                     j = lps[j - 1];                 else                    i = i + 1;             }         }     }       void computeLPSArray(string pat, int M, int[] lps)     {        int len = 0;         int i = 1;         lps[0] = 0; // lps[0] is always 0            while (i < M) {             if (pat[i] == pat[len]) {                 len++;                 lps[i] = len;                 i++;             }             else // (pat[i] != pat[len])             {                 if (len != 0) {                     len = lps[len - 1];                 }                 else // if (len == 0)                 {                     lps[i] = len;                     i++;                 }             }         }     }      public static void Main()     {         string txt = "BC ABCDAB ABCDABCDABDE";         string pat = "ABCDABD";         new GFG().KMPSearch(pat, txt);     } } 

标签: #kmp算法 partial match table