字符串匹配的kmp算法_多字符串匹配

字符串匹配的kmp算法_多字符串匹配一、背景  给定一个主串(以S代替)和模式串(以P代替),要求找出P在S中出现的位置,此即串的模式匹配问题。  Knuth-Morris-Pratt算法(简称KMP)是解决这一问题的常用算法之一,这个算法是由高德纳(DonaldErvinKnuth)和沃恩·普拉特在1974年构思,同年詹姆斯·H·莫里斯也独立地设计出该算法,最终三人于1977年联合发表。  在继…


Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、背景

  给定一个主串(以 S 代替)和模式串(以 P 代替),要求找出 P 在 S 中出现的位置,此即串的模式匹配问题。

  Knuth-Morris-Pratt 算法(简称 KMP)是解决这一问题的常用算法之一,这个算法是由高德纳(Donald Ervin Knuth)和沃恩·普拉特在1974年构思,同年詹姆斯·H·莫里斯也独立地设计出该算法,最终三人于1977年联合发表。

  在继续下面的内容之前,有必要在这里介绍下两个概念:真前缀 和 真后缀。

这里写图片描述

  由上图所得, “真前缀”指除了自身以外,一个字符串的全部头部组合;”真后缀”指除了自身以外,一个字符串的全部尾部组合。

二、KMP字符串匹配算法

1、算法流程

(1)

  首先,主串”BBC ABCDAB ABCDABCDABDE”的第一个字符与模式串”ABCDABD”的第一个字符,进行比较。因为B与A不匹配,所以模式串后移一位。

这里写图片描述

(2)

  直到主串有一个字符,与模式串的第一个字符相同为止。

这里写图片描述

(3)

  接着比较主串和模式串的下一个字符,直到主串有一个字符,与模式串对应的字符不相同为止。

这里写图片描述

  一个基本事实是,当空格与D不匹配时,你其实是已经知道前面六个字符是”ABCDAB”。KMP算法的想法是,设法利用这个已知信息,不要把”搜索位置”移回已经比较过的位置,而是继续把它向后移,这样就提高了效率。

(4)

  怎么做到这一点呢?可以针对模式串,设置一个跳转数组int next[],这个数组是怎么计算出来的,后面再介绍,这里只要会用就可以了。

这里写图片描述

(5)

  已知空格与D不匹配时,前面六个字符”ABCDAB”是匹配的。根据跳转数组可知,不匹配处D的next值为2,因此接下来从模式串下标为2的位置开始匹配。

这里写图片描述

  因为空格与C不匹配,C处的next值为0,因此接下来模式串从下标为0处开始匹配。

(6)

这里写图片描述

  因为空格与A不匹配,此处next值为-1,表示模式串的第一个字符就不匹配,那么直接往后移一位。

(7)

这里写图片描述

  逐位比较,直到发现C与D不匹配。于是,下一步从下标为2的地方开始匹配。

(8)

  逐位比较,直到模式串的最后一位,发现完全匹配,于是搜索完成。

这里写图片描述

2、next数组是如何求出的

  next数组的求解基于“真前缀”和“真后缀”,即next[i]等于P[0]…P[i – 1]最长的相同真前后缀的长度(请暂时忽视i等于0时的情况,下面会有解释)。

这里写图片描述

这里写图片描述
这里写图片描述

  那么,为什么根据最长相同真前后缀的长度就可以实现在不匹配情况下的跳转呢?举个代表性的例子:假如i = 6时不匹配,此时我们是知道其位置前的字符串为ABCDAB,仔细观察这个字符串,首尾都有一个AB,既然在i = 6处的D不匹配,我们为何不直接把i = 2处的C拿过来继续比较呢,因为都有一个AB啊,而这个AB就是ABCDAB的最长相同真前后缀,其长度2正好是跳转的下标位置。

3、next数组的实现

void cal_next(string &str, vector<int> &next)
{
    const int len = str.size();
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < len - 1)
    {
        if (k == -1 || str[j] == str[k])
        {
            ++k;
            ++j;
            next[j] = k;//表示第j个字符有k个匹配(“最大长度值” 整体向右移动一位,然后初始值赋为-1)
        }
        else
            k = next[k];//往前回溯
    }
}

(1)i和j的作用

  i和j就像是两个”指针“,一前一后,通过移动它们来找到最长的相同真前后缀。

(2)if…else…语句里做了什么?

这里写图片描述

  假设i和j的位置如上图,由next[i] = j得,也就是对于位置i来说,区段[0, i – 1]的最长相同真前后缀分别是[0, j – 1]和[i – j, i – 1],即这两区段内容相同。

  按照算法流程,if (P[i] == P[j]),则i++; j++; next[i] = j;;若不等,则j = next[j],见下图:

这里写图片描述

  next[j]代表[0, j – 1]区段中最长相同真前后缀的长度。如图,用左侧两个椭圆来表示这个最长相同真前后缀,即这两个椭圆代表的区段内容相同;同理,右侧也有相同的两个椭圆。所以else语句就是利用第一个椭圆和第四个椭圆内容相同来加快得到[0, i – 1]区段的相同真前后缀的长度。

  j == -1意义就是为了特殊边界判断。

三、KMP算法demo

#include <iostream>
#include <string>
#include <vector>
using namespace std;

//部分匹配表
void cal_next(string &str, vector<int> &next)
{
    const int len = str.size();
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < len - 1)
    {
        if (k == -1 || str[j] == str[k])
        {
            ++k;
            ++j;
            next[j] = k;//表示第j个字符有k个匹配(“最大长度值” 整体向右移动一位,然后初始值赋为-1)
        }
        else
            k = next[k];//往前回溯
    }
}

vector<int> KMP(string &str1, string &str2, vector<int> &next)
{
    vector<int> vec;
    cal_next(str2, next);
    int i = 0;//i是str1的下标
    int j = 0;//j是str2的下标
    int str1_size = str1.size();
    int str2_size = str2.size();
    while (i < str1_size && j < str2_size)
    {
        //如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),
        //都令i++,j++. 注意:这里判断顺序不能调换!
        if (j == -1 || str1[i] == str2[j])
        {
            ++i;
            ++j;
        }
        else
            j = next[j];//当前字符匹配失败,直接从str[j]开始比较,i的位置不变
        if (j == str2_size)//匹配成功
        {
            vec.push_back(i - j);//记录下完全匹配最开始的位置
            j = -1;//重置
        }
    }
    return vec;
}

int main(int argc, char const *argv[])
{
    vector<int> vec(20, 0);
    vector<int> vec_test;
    string str1 = "bacbababadababacambabacaddababacasdsd";
    string str2 = "ababaca";
    vec_test = KMP(str1, str2, vec);
    for (const auto v : vec_test)
        cout << v << endl;
    return 0;
}

  KMP时间复杂度:O(m+n)。

四、KMP算法的应用

1、求其中出现重复的任意一个字符

  先求next数组,next[j]=k,k > 0 时,就返回j,p[j]就是出现重复的字符。

这里写图片描述

2、求最长的重复子串

  求最长的重复子串,就是求next[j]=k,求出k的最大值。

这里写图片描述

转自:https://segmentfault.com/a/1190000008575379
https://blog.csdn.net/willinux20130812/article/details/47133425

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/171707.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • OGNL简介

    OGNL简介

    2022年3月12日
  • 八种基本数据类型_uint16是什么数据类型

    八种基本数据类型_uint16是什么数据类型uint8_t/uint16_t/uint32_t/uint64_t 是什么数据类型在nesc的代码中,你会看到很多你不认识的数据类型,比如uint8_t等。咋一看,好像是个新的数据类型,不过C语言(nesc是C的扩展)里面好像没有这种数据类型啊!怎么

    2022年9月7日
  • 全局服务器负载均衡(GSLB)简介

    全局服务器负载均衡(GSLB)简介引言在过去的几年中,随着互联网的快速发展和企业应用WEB化,服务器负载均衡(SLB)技术已经不再陌生。服务器负载均衡根据用户数据请求中的4-7层信息将其智能转发到后端少则数台多则成百上千台应用服务器,并且确保根据事先定义的策略选择最佳的服务器进行转发,从而一定程度上解决了应用的可用性、扩展性等问题。但是,随着用户对应用可用性和扩展性需求的进一步增加,越来越多的用户不满足于在单一数据

    2022年6月6日
  • DNS服务器搭建与配置

    DNS服务器搭建与配置title:DNS服务器搭建与配置date:2018-10-1521:20:07tags:[Linux笔记,Linux服务]categories:LinuxDNS服务介绍DNS服务简介:DNS(DomainNameSystem–域名系统),是因特网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。是一个应用层的协议DNS使用…

    2022年6月4日
  • uniapp 复制粘贴_小程序封装键盘输入框

    uniapp 复制粘贴_小程序封装键盘输入框本篇没啥营养,就是告诉不熟悉uniapp的开发者怎么完成长按复制,懂得朋友别浪费时间1.给text组件设置对应平台的对应属性,在安卓手机上的效果2.直接设置剪切板的内容uni.setClipboardData(OBJECT)<textstyle=”@longpress=’copyText’>长按触发longpress事件</text>//对应事件copyText(){ uni.setClipboardData({ dat…

    2022年9月1日
  • 基于php教师档案管理系统毕业设计

    基于php教师档案管理系统毕业设计获取项目源文件,联系Q:1415736481,可指导毕设,课设随着经济的发展和社会的进步,高校的现代化管理逐渐被人们所重视,利用计算机对高校的管理在各个领域中都有广阔的发展空间和前景。高校教师档案管理系统的设计与实现对高等院校的决策者和管理者来说至关重要,对教育系统来说更是不可缺少的。本系统采用PHP语言和MySQL数据库等技术,基于B/S模式,根据管理员、教务处管理员、教师三种不同的用户权限…

    2022年10月29日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号