亲宝软件园·资讯

展开

C#利用KPM算法解决字符串匹配问题详解

黑夜中的潜行者 人气:0

什么是KPM算法

Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。

KMP方法算法就利用之前判断过信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间。

KPM的使用场景:模式串在文本串是否出现过,如果出现过,最早出现的位置

步骤

Ⅰ根据《最大长度表》部分匹配表(next)

对于P = p0 p1 ...pj-1 pj,寻找模式串P中长度最大且相等的前缀和后缀。如果存在p0 p1 ...pk-1 pk = pj- k pj-k+1...pj-1 pj,那么在包含pj的模式串中有最大长度为k+1的相同前缀后缀。

举个例子,如果给定的模式串为“abab”,那么它的各个子串的前缀后缀的公共元素的最大长度如下表格所示:

比如对于字符串aba来说,它有长度为1的相同前缀后缀a;而对于字符串abab来说,它有长度为2的相同前缀后缀ab(相同前缀后缀的长度为k + 1,k + 1 = 2)。

结论:最大前缀后缀元素长度所得到的数组就是我们所需要的 “部分匹配表”

寻找最长前缀后缀

如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

Ⅱ 根据 部分匹配表 进行匹配

匹配失配,j = next [j],模式串向右移动的位数为:j - next[j]。换言之,当模式串的后缀pj-k pj-k+1, ..., pj-1跟文本串si-k si-k+1, ..., si-1匹配成功,但pj跟si匹配失败时,因为next[j] = k,相当于在不包含pj的模式串中有最大长度为k 的相同前缀后缀,即p0 p1 ...pk-1 = pj-k pj-k+1...pj-1,故令j = next[j],从而让模式串右移j - next[j]位,使得模式串的前缀p0 p1, ..., pk-1对应着文本串si-k si-k+1, ..., si-1,而后让pk跟si继续匹配。如下图所示:

KMP的next数组相当于告诉我们:

当模式串中的某个字符跟文本串中的某个字符匹配失配时,模式串下一步应该跳到哪个位置。如模式串中在j处的字符跟文本串在i处的字符匹配失配时,下一步用next [j]处的字符继续跟文本串i 处的字符匹配,相当于模式串向右移动j - next[j]位。

代码实现

字符串匹配问题:

有一个字符串 str1=““上海自来水来自海上””,和一个子串 str2=“自来水”。

现在要判断str1是否含有str2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1

static void Main(string[] args)
{
    string str1 = "上海自来水来自海上";
    string str2 = "自来水";
	
	// 得出 部分匹配表
    int[] next = KPMNext(str2);
    // 根据 得出的 部分匹配表的 next 数组进行匹配,
    int index = KPMSearch(str1, str2, next);
    
    Console.WriteLine(index);
}

/// <summary>
/// 获取一个字符串的部分匹配值表
/// </summary>
/// <param name="dest"></param>
/// <returns></returns>
static int[] KPMNext(string dest)
{
    // 初始化 数组大小
    int[] next = new int[dest.Length];
    // 字符串长度为1,部分匹配值就为0
    next[0] = 0;
    for (int i = 1, j = 0; i < dest.Length; i++)
    {
        while (j > 0 && dest[i] != dest[j])
        {
            j = next[j - 1];
        }
        if (dest[i] == dest[j])
        {
            j++;
        }
        next[i] = j;
    }
    return next;
}

/// <summary>
/// kmp搜索
/// </summary>
/// <param name="str1">源字符串</param>
/// <param name="str2">子字符串</param>
/// <param name="next">部分匹配表</param>
/// <returns></returns>
static int KPMSearch(string str1, string str2, int[] next)
{
    for (int i = 0, j = 0; i < str1.Length; i++)
    {
        while (j > 0 && str1[i] != str2[j])
        {
            j = next[j - 1];
        }

        if (str1[i] == str2[j])
        {
            j++;
        }

        if (j == str2.Length)
        {
            return i - j + 1;
        }
    }
    return -1;
}

加载全部内容

相关教程
猜你喜欢
用户评论