算法 - kmp匹配算法


问题描述

  • 实现kmp匹配算法
  • 利于软件 y-cruncher 生成百万位的 PI 的值,在其中使用kmp匹配算法匹配特定的数字串

算法描述

  • kmp匹配算法实则是利用next数组优化匹配过程的匹配方法,要理解kmp匹配要了解 如何生成next数组 以及 如何使用next数组 这两个核心问题

如何生成next数组

  • next数组的第 i 位其实就是从 0 到第 (i-1) 位字串的最大相同前后缀的长度值,第 0 位默认为 0
/**
 * 计算next数组
 * next数组的目标是求得从开始到当前位置的字串相同最大前后缀长度为多少
 * 该值的作用是在当前位置的字符匹配失败时,匹配字符串应该从那里开始继续匹配,而不用回到开头
 * 时间复杂的(O(m))
 */
void calNextArray(const string &str, int *next)
{
	//head代表前缀的索引,tail代表后缀的索引
	int head = 0;
	int tail = 1;
	int len = str.length();
	//无论如何首字母的next值均为0
	next[0] = 0;

	//tail等于len代表计算完了整个字符串的next值
	while (tail <  len)
	{
		//如果当前首尾字符串匹配
		if (str[head] == str[tail])
		{
			//此处的最大相同前后缀长度为 head+1 (head从0开始,所以长度需要加1)
			next[tail] = head + 1;
			//head于tail均移动到下一个字符,继续匹配
			++head;
			++tail;
		}
		else
		{
			//在首尾不匹配且head为0的情况下代表此字符串最大相同前后缀长度为0
			if (head == 0)
			{
				next[tail] = 0;
				//head不变,tail移动到下一个字符
				++tail;
			}
			//当head不为0时就代表之前的字符串最大相同前后缀长度不为0
			//即索引[0 ~ (head-1)]的字串(记为A)与索引[(tail-head) ~ (tail-1)]的字串(记为B)相等
			//由于A = B,故A的最大前缀等于B的最大后缀
			//所以此时head直接回退到A最大前缀后的第一个字符处即可,也即此时(head-1)位置处的next值
			//以上head回退方式是保证求得的是最大相同前后缀
			else
				head = next[head - 1];
		}
	}
}

如何使用next数组

/**
 * kmp匹配过程
 * 每次匹配失败后无需返回初始位置,而是利用next数组和已有匹配结果继续在当前位置匹配
 * 时间复杂度(O(n))
 */
int kmp(const string &search_str, const string &match_str)
{
	//计算next数组
	int *next = new int[match_str.length()];
	calNextArray(match_str, next);

	//定义被检索字符串和匹配字符串索引
	int searchIndex = 0;
	int matchIndex = 0;
	int searchLen = search_str.length();
	int matchLen = match_str.length();

	//直到匹配字符串完全匹配或者被检索字符串被全部检索为止
	while (matchIndex != matchLen && searchIndex != searchLen)
	{
		//如果当前位置两者不匹配
		if (search_str[searchIndex] != match_str[matchIndex])
		{
			//如果匹配字符串索引不在首字符则回退到上一个字符对应的next值的位置
			if (matchIndex != 0)
				matchIndex = next[matchIndex - 1];
			//如果匹配字符串索引在首字符,则被检索字符到下一个位置即可
			else
				++searchIndex;
		}
		//匹配成功同时到下一个字符
		else
		{
			++searchIndex;
			++matchIndex;
		}
	}

	delete[] next;
	if (matchIndex != matchLen)
		return -1;
	else
		return (searchIndex - matchIndex);
}

运行结果

  • surprising!你会发现在你生成的百万位的 PI 的数字串中,如果你用6位的模式串去匹配你的支付密码,你的生日统统都在其中……(如果用人用它做字典破解你的支付密码)
    result

心得收获

  • kmp算法的实质是利用模式字符串自身的特点:最大相同前后缀的值,来省去传统的 strstr() 方法匹配失败之后主串和模式串的回溯,大大加快匹配效率
  • 要理解kmp算法首先要理解next数组,会求next数组

完整代码

/**
 * KMP算法
 * 时间复杂度 O(m*n)
 * 2019-05-06 by sunhaojie
 */

#include <iostream>
#include <String>
#include <cstdio>
#include <cstdlib>

using namespace ::std;

/**
 * 计算next数组
 * next数组的目标是求得从开始到当前位置的字串相同最大前后缀长度为多少
 * 该值的作用是在当前位置的字符匹配失败时,匹配字符串应该从那里开始继续匹配,而不用回到开头
 * 时间复杂的(O(m))
 */
void calNextArray(const string &str, int *next)
{
	//head代表前缀的索引,tail代表后缀的索引
	int head = 0;
	int tail = 1;
	int len = str.length();
	//无论如何首字母的next值均为0
	next[0] = 0;

	//tail等于len代表计算完了整个字符串的next值
	while (tail <  len)
	{
		//如果当前首尾字符串匹配
		if (str[head] == str[tail])
		{
			//此处的最大相同前后缀长度为 head+1 (head从0开始,所以长度需要加1)
			next[tail] = head + 1;
			//head于tail均移动到下一个字符,继续匹配
			++head;
			++tail;
		}
		else
		{
			//在首尾不匹配且head为0的情况下代表此字符串最大相同前后缀长度为0
			if (head == 0)
			{
				next[tail] = 0;
				//head不变,tail移动到下一个字符
				++tail;
			}
			//当head不为0时就代表之前的字符串最大相同前后缀长度不为0
			//即索引[0 ~ (head-1)]的字串(记为A)与索引[(tail-head) ~ (tail-1)]的字串(记为B)相等
			//由于A = B,故A的最大前缀等于B的最大后缀
			//所以此时head直接回退到A最大前缀后的第一个字符处即可,也即此时(head-1)位置处的next值
			//以上head回退方式是保证求得的是最大相同前后缀
			else
				head = next[head - 1];
		}
	}
}

/**
 * kmp匹配过程
 * 每次匹配失败后无需返回初始位置,而是利用next数组和已有匹配结果继续在当前位置匹配
 * 时间复杂度(O(n))
 */
int kmp(const string &search_str, const string &match_str)
{
	//计算next数组
	int *next = new int[match_str.length()];
	calNextArray(match_str, next);

	//定义被检索字符串和匹配字符串索引
	int searchIndex = 0;
	int matchIndex = 0;
	int searchLen = search_str.length();
	int matchLen = match_str.length();

	//直到匹配字符串完全匹配或者被检索字符串被全部检索为止
	while (matchIndex != matchLen && searchIndex != searchLen)
	{
		//如果当前位置两者不匹配
		if (search_str[searchIndex] != match_str[matchIndex])
		{
			//如果匹配字符串索引不在首字符则回退到上一个字符对应的next值的位置
			if (matchIndex != 0)
				matchIndex = next[matchIndex - 1];
			//如果匹配字符串索引在首字符,则被检索字符到下一个位置即可
			else
				++searchIndex;
		}
		//匹配成功同时到下一个字符
		else
		{
			++searchIndex;
			++matchIndex;
		}
	}

	delete[] next;
	if (matchIndex != matchLen)
		return -1;
	else
		return (searchIndex - matchIndex);
}

int main()
{
	cout << "************************************" << endl;
	cout << "**********  KMP Algorithm ************" << endl;
	cout << "************************************" << endl;
	cout << "Input match string:";
	string match_str;
	cin >> match_str;

	freopen("PI.txt", "r", stdin);
	string search_str;
	cin >> search_str;
	cout << search_str;

	int location = kmp(search_str, match_str);
	cout << "Match result:";
	if (location == -1)
		cout << "No match!" << endl;
	else
		cout << "Matched(" << location << ')' << endl;
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值