【数据结构&&算法系列】KMP算法介绍及实现(c++ && java)

KMP算法如果理解原理的话,其实很简单。

KMP算法简介

这里根据自己的理解简单介绍下。

KMP算法的名称由三位发明者(Knuth、Morris、Pratt)的首字母组成,又称字符串查找算法。

个人觉得可以理解为最小回溯算法,即匹配失效的时候,尽量少回溯,从而缩短时间复杂度。

KMP算法有两个关键的地方,1)求解next数组,2)利用next数组进行最小回溯。

1)求解next数组

next数组的取值只与模式串有关,next数组用于失配时回溯使用。

在简单版本的KMP算法中,每个位置 j 的 next 值表示的是模式串的最长前缀的最后一个字符的位置(假设为 k ),其中最长前缀(长度为 k+1 )需要与模式串截至当前位置长度亦为 k+1 的后缀匹配,且 k 最大为 j-1 ,否则相当于没有回溯。当k=-1的时候,表示找不到这样的最长前缀。

用公式表示为

当k=-1的时候,表示空串。p表示模式串。

下面举一个计算next数组的例子,假设模式串是 “ abaabcaba ” 。

j 0 1 2 3 4 5 6 7 8
p a b a a b c a b a
next[j] -1 -1 0 0 1 -1 0 1 2

以 j = 8 为例,最长前缀为aba,最后一个字符位置为2,故 next[8] = 2 。

那么如何快速求解next数组呢?

这里有点动态规划的思想在里面,其中位置 j 等于 0 的 next 值为-1,表示找不到这样的最长前缀。 j > 0 时,next值可以通过 j - 1 位置的next值求得。

求解next[ j ]的步骤:

  1. t = next[ j - 1 ] + 1,t 指向可能等于 p[ j ] 的位置,即 p[ t ] 可能等于 p[ j ]。
  2. 如果 p[ t ]   =  p[ j ] , 那么 next[ j ] = next[ j - 1 ] + 1
  3. 如果 p[ t ]  !=  p[ j ] , 则令 t = next[ t - 1 ] + 1,继续第 2 步直到 t = 0 或者找到位置。
  4. 结束时判断p[ t ] 是否等于 p[ j ] ,如果等于则 next[ j ] = t , 否则等于 -1 。

下图表示了第一次不匹配,第二次匹配的过程,其它过程可以类推。其中     或     覆盖部分表示最长匹配串。   为待判定位置,   
为已判定位置。

0123                                                     j

×××××××××××××××××××××××××××××××××××××××××

×××××××××××××××××××××××××××××××××××××××××

2)利用next数组进行最小回溯

s ××××××××××××××××××××××××××××××××××××××××××××

p                                            ××××××××××××××

在j处不失配时,前面的有部分匹配,这时需要利用next数组信息进行最小回溯。

s ××××××××××××××××××××××××××××××××××××××××××××

p                                            ××××××××××××××

(这里 i 指向 s , j 指向 p。)

注意在 j = 0 的时候失配时,直接 i++ 即可。

当 j > 0 的时候,需要利用next数组最快找到 p[ j ] == s[ i ] 的位置。

如果 j 移动到了0还找不到,则 i++,然后继续匹配。

这里我们可以发现只有 j 回溯了,i没有回溯,但是由于普通版本的 KMP 算法 j 需要不停地回溯直到找到合适的回溯位置,因此速度不是特别快,还可以继续优化,感兴趣的读者可以想想如何事先求解好next数组从而不需要不停地回溯。

代码实现

strStr返回的是首次匹配的地址,如果不能匹配则返回NULL。

class Solution {
public:
    vector<int> getNext(char* &s){
        vector<int> next(strlen(s), -1);

        for(int i=1; i<strlen(s); i++){
            int j = next[i-1]; /* 前一个字符的最长匹配长度 */

            while(s[j+1] != s[i] && j>=0)
                j = next[j];

            if(s[j+1] == s[i])
                next[i] = j+1;
            // else 默认为-1
        }

        return next;
    }

    char *strStr(char *haystack, char *needle) {
        if(haystack==NULL || needle==NULL) return NULL;
        if(strlen(haystack) < strlen(needle)) return NULL;
        if(strlen(needle) == 0) return haystack;

        vector<int> next = getNext(needle);
        int i = 0;
        int j = 0;
        int haystackLen = strlen(haystack);
        int needleLen = strlen(needle);
        while(i<haystackLen && j<needleLen){
            if(haystack[i] == needle[j] ) {
                i++;
                j++;
                if(j == needleLen) return haystack + i - j;
            }else{
                if(j == 0) i++;
                else j = next[j-1]+1; /* 该步骤可以优化 */
            }
        }

        return NULL;
    }
};

由于有人问有没有java版本的,由于鄙人java比较挫,写java时部分还写成了scala的语法,不知道代码是否规范,有优化的地方还麻烦java方面的大神指点。

import java.util.*;

public class StrStrSolution {
    private List<Integer> getNext(String p){
        List<Integer> next = new ArrayList<Integer>();
        next.add(-1);

        for(int i=1; i<p.length(); i++){
            int j = next.get(i-1);

            while(p.charAt(j+1) != p.charAt(i) && j>=0)
                j = next.get(j);

            if(p.charAt(j+1) == p.charAt(i))
                next.add( j + 1 );
            else
                next.add( -1 );
        }

        return next;
    }

    public String strStr(String haystack, String needle) {
        if (haystack == null || needle == null) return null;
        if (needle.length() == 0) return haystack;
        if (needle.length() > haystack.length()) return null;

        List<Integer> next = getNext(needle);
        int i = 0;
        int j = 0;
        int haystackLen = haystack.length();
        int needleLen = needle.length();
        while(i < haystackLen && j < needleLen){
            if(haystack.charAt(i) == needle.charAt(j) ) {
                i++;
                j++;
                if(j == needleLen) return haystack.substring(i - j);
            }else{
                if(j==0) i++;
                else j = next.get(j-1)+1;
            }
        }  

        return null;
    }

    public static void main(String[] args) {
        String s = "babcabaabcacbac";
        String p = "abaabcac";
        StrStrSolution sol = new StrStrSolution();
        System.out.println(sol.strStr(s,p));
    }
}

【数据结构&&算法系列】KMP算法介绍及实现(c++ && java),布布扣,bubuko.com

时间: 2024-10-03 21:54:13

【数据结构&&算法系列】KMP算法介绍及实现(c++ && java)的相关文章

BF算法与KMP算法

BF(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符:若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果. BF算法实现: 1 int BF(char S[],char T[],int pos) 2 {//c从第pos位开始搜索匹配 3 int i=pos,j=0; 4 while(S[i+j]!='\0'&&T[j]!='\0')

字符串匹配——朴素算法、KMP算法

字符串匹配(string match)是在实际工程中经常会碰到的问题,通常其输入是原字符串(String)和子串(又称模式,Pattern)组成,输出为子串在原字符串中的首次出现的位置.通常精确的字符串搜索算法包括朴素搜索算法,KMP, BM(Boyer Moore), sunday, robin-karp 以及 bitap.下面分析朴素搜索算法和KMP这两种方法并给出其实现.假设原字符T串长度N,子串P长度为M. 1.NAIVE-STRING-MATCHING. 朴素算法,该方法又称暴力搜索,

数据结构20:KMP算法(快速模式匹配算法)详解

通过上一节的介绍,学习了串的普通模式匹配算法,大体思路是:模式串从主串的第一个字符开始匹配,每匹配失败,主串中记录匹配进度的指针 i 都要进行 i-j+1 的回退操作(这个过程称为“指针回溯”),同时模式串向后移动一个字符的位置.一次次的循环,直到匹配成功或者程序结束. "KMP"算法相比于"BF"算法,优势在于: 在保证指针 i 不回溯的前提下,当匹配失败时,让模式串向右移动最大的距离: 并且可以在O(n+m)的时间数量级上完成对串的模式匹配操作: 故,"

(原创)数据结构之利用KMP算法解决串的模式匹配问题

给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置. 输入格式: 输入有两行: 第一行是主串S: 第二行是模式T. 输出格式: 输出相匹配的子串中的第一个字符在主串S中出现的位置.若匹配失败,输出0. 输入样例: 在这里给出一组输入.例如: aaaaaba ba 输出样例: 在这里给出相应的输出.例如: 6 解题思路:串的模式匹配有两种:一种是BF算法,一种是KMP算法:基于这道题给的数

数据结构与算法之KMP算法

串的模式匹配算法 子串(模式串)的定位操作通常称为串的模式匹配. 这是串的一种重要操作,很多 软件,若有“编辑”菜单项的话, 则其中必有“查找”子菜单项. 串的顺序存储实现 #include<stdio.h> #include<string.h> #define MaxLen 256 /*定义能处理的最大的串长度*/ typedef struct { char str[MaxLen]; int curlen; /*定义当前实际串长度*/ }SString; BF算法设计思想: 将主

KMP算法及KMP算法的应用(POJ2406)

///KMP算法#include<bits/stdc++.h> using namespace std; int Next[1000]; void makeNext(const char P[],int next[]) { int q,k; int len=strlen(P); next[0]=0; for(q=1,k=0;q<len;q++) { while(k>0&&P[q]!=P[k]) { k=next[k-1]; } if(P[q]==P[k]) { k+

什么是KMP算法?KMP算法推导

花了大概3天时间,了解,理解,推理KMP算法,这里做一次总结!希望能给看到的人带来帮助!! 1.什么是KMP算法? 在主串Str中查找模式串Pattern的方法中,有一种方式叫KMP算法 KMP算法是在模式串字符与主串字符匹配失配时,利用已经匹配的模式串字符子集的最大块对称性,让模式串尽量后移的算法. 这里有3个概念:失配,已经匹配的模式串子集,块对称性 失配和隐含信息 在模式串的字符与主串字符比较的过程中,字符相等就是匹配,字符不等就是失配: 隐含信息是,失配之前,都是匹配. 在主串S[0,1

串、串的模式匹配算法(子串查找)BF算法、KMP算法

串的定长顺序存储#define MAXSTRLEN 255,//超出这个长度则超出部分被舍去,称为截断 串的模式匹配: 串的定义:0个或多个字符组成的有限序列S = 'a1a2a3--.an ' n = 0时为空串串的顺序存储结构:字符数组,串的长度就是数组末尾'\0'前面的字符个数数组需在定义时确定长度,有局限性数组的最大长度二:串的堆分配存储表示typedef struct { char *ch; //若是非空串,则按串长分配存储区 //否则ch为空 int length; //串长度}HS

优化算法系列-模拟退火算法(1)——0-1背包问题

优化算法系列之模拟退火算法(1)--0-1背包问题 1问题描述 有一个窃贼在偷窃一家商店时发现有N件商品:第i件物品价值vi元,重wi磅,其中vi.wi都是整数.他希望带走的东西越值钱越好,但他的背包小,最多只能装下W磅的东西(W为整数).如果每件物品或被带走或被留下,小偷应该带走哪几件东西? 2解空间 设xi表示第i件物品的取舍,1代表取,0代表舍,搜索空间为n元一维数组(x1,x2,x3,.....,xn).因而解空间的取值范围可表示为(0,0,0,....,0),(0,0,0,......

搞定面试算法系列 —— 分治算法三步走

主要思想 分治算法,即分而治之:把一个复杂问题分成两个或更多的相同或相似子问题,直到最后子问题可以简单地直接求解,最后将子问题的解合并为原问题的解. 归并排序就是一个典型的分治算法. 三步走 和把大象塞进冰箱一样,分治算法只要遵循三个步骤即可:分解 -> 解决 -> 合并. 分解:分解原问题为结构相同的子问题(即寻找子问题) 解决:当分解到容易求解的边界后,进行递归求解 合并:将子问题的解合并成原问题的解 这么一说似乎还是有点抽象?那我们通过经典的排序算法归并排序来体验一下分治算法的核心思想.