C# KMP

本文不介绍KMP具体思想: 只用代码注释自己的实现思路

next数组的核心思想在于, 当主串字符与子串字符失配时
判断主串指针字符前有多少字符 是 可以跳过的.
/// 
假设主串为ad abcad abcab 模式串为 abcab, 字符指针范围为[0, str.Length - 1]
当主串与模式串匹配时 主串中abcad部分的末尾d 与 模式串abcab末尾的b不符
此时根据模式串b的前一个字符a的next可得知 abca 的最长公共前后缀 为 a = a 即1
也就说明 abcad 中 有1个字符a 即b左侧字符 是可以跳过匹配的.
其主要原因在于: abcad 与 abcab 匹配进行到了 d 与 b, 前四个字符 abca 由于是判断相同的, 故模式串可跳跃最长公共前(后) 缀重新匹配.
///
此时模式串的指针 从abcab的末尾b跳回夹中b 进行重新匹配(4->1(next[4 - 1]))
///    
同理 由于夹中b仍与主串字符d不符 则根据夹中b前一个字符a的next进行指针跳回
a的next = 0 故lesser = (1->0(next[1 - 1])) 再重新匹配
///
假设子串第一个字符仍与主串不符 则主串指针跳跃+1
 

/// </summary>
/// <param name="haystack">参与匹配的主串</param>
/// <param name="needle">需要匹配的模式串</param>
/// <returns> </returns>
public static int KMP_(string haystack, string needle)
{
	// 最长公共前后缀数组
	int[] next = GetKMP_Next(needle);

	// 用于指向主串匹配位置的指针
	int main = 0;
	// 用于指向模式串匹配位置的指针
	int lesser = 0;

	/*
		理论上 当lesser = str.Length - 1时 匹配成功 应该退出返回起始坐标
		反之返回 -1 表示未匹配成功
	 */

	// 当子串完全匹配或主串遍历完毕后跳出循环
	while(main < haystack.Length && lesser < needle.Length)
	{
		// 当字符匹配相同时 同时跳跃指针
		if (haystack[main] == needle[lesser])
		{
			main++;
			lesser++;
		}
		else
		{
			// 字符失配 且 子串指针指向第一位时 则主串跳跃
            if (lesser == 0) main++;
            else
            {
				// lesser指向的是当前匹配的子串字符 但要根据该字符的前一个字符的next进行重新指向
				lesser = next[lesser - 1];
            }
		}
	}

	if (lesser == needle.Length)
		return main - needle.Length;
	else return -1;

}

/// <summary>
/// KMP的最大难点在于如何寻找 模式串的 最长公共前后缀
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static int[] GetKMP_Next(string str)
{
	int[] next = new int[str.Length];

	// 前缀指针
	int main = 0;
	// 后缀指针
	int lesser = 1;

	/*
	 * main < lesser <= str.Length - 1
	 * main 始终小于 lesser
	 */

	next[main] = 0; // 第一个字符的最长公共前后缀必定为0

	while (lesser < str.Length)
	{
		// 当两个字符相同时
		// lesser 指向当前匹配字符
		if (str[main] == str[lesser])
		{
			next[lesser++] = ++main;
			// 为什么要加++main 因为在本代码里 next表示的是可跳跃字符的长度 此轮比较已经成功
			// 需要注意的是 指针是从0开始的 所以跳跃一个字符长度 本质上是将指针跳跃到1
		}
		else if(main == 0)
		{
			// 当 main == 0 且 字符失配时
			next[lesser++] = main;
		}
		else
		{
			/* 当字符不相同时 要跳回指针
			 * 跳回位置原理与KMP_相同 都是因为有最长公共前后缀
			 * 假设 str 为 bbccdabbd 其 next 应该为 010000120
			 * 理论上 当l指向最后一个字符d时 main应该等于2 即指向c
			 * 由于d!=c 所以m应该根据 next[main-1] 即1 跳回至1
			 * 在跳回前会发现 bbc与bbd的前两个字符是相同的 这也侧面说明原理与KMP_思想一样
			*/ 
			main = next[--main];
		}
	}

	return next;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值