sayhitrue

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

1. Brute force.

该方法又称暴力搜索,也是最容易想到的方法。也叫朴素字符串匹配算法,NAIVE-STRING-MATCHER

预处理时间 O(0)

匹配时间复杂度O(N*M)

主要过程:从原字符串开始搜索,若出现不能匹配,则从原搜索位置+1继续。

//无视我的库文件
#include<iostream>
#include<string>
#include <math.h>
#include <algorithm>
#include <vector>
#include <functional>
using namespace std;
int bf(string text, string find)
{
	auto m = text.size(), n = find.size();
	if (0 == m*n || m < n) return -1;
	string::iterator p, q, s;
	s = text.begin();
	p = text.begin();
	q = find.begin();
	while (p != text.end() && (text.end() - s) >= n)
	{
		if (*p == *q)
		{
			p++;
			q++;
		}
		else
		{
			s++;
			p = s;
			q = find.begin();
		}
		if (q == find.end()) return s - text.begin();
	}
	return -1;
}
int main()
{
	const string text{ "introduction to algorithms" };
	const string find{ "algorithm" };
	cout << bf(text, find) << endl;
	system("pause");
}

假设模式P中的所有字符都是不同的。试说明如何对一段n个字符的文本T加速朴素字符串匹配的执行速度,使其运行时间达O(n).——算法导论32.1-2

主要过程:这里所有字符不同说明对T遍历的时候不需要回过来,只需要把模式P中的下标记为起始就可以了。

int bf_unique(string text, string find)
{
	auto m = text.size(), n = find.size();
	if (0 == m*n || m < n) return -1;
	for (auto p = text.begin(), q = find.begin(); p != text.end(); ++p)
	{
		if (*p == *q&&q != find.end()) ++q;
		else
		{
			if (q!=find.begin())
			{
				q = find.begin();
				--p;
				continue;
			}	
		}
		if (find.end() == q) return p - text.begin() - find.size() + 1;//从0开始
	}
	return -1;
}

RabinKarp算法

算法思想是将字符串映射到一个数字,然后比较字串的数字是否相等,如果相等则继续进一步判断确认正确性,如果不相等,则可以判断字符串不匹配。

预处理时间O(0)

最坏匹配时间复杂度O(N*M)

int RabinKarpMatcher(string st, string sp, int d, int q)
{
	int n = st.length(), m = sp.length(), h = ((int)pow(d, m - 1)) % q;
	int p = 0, t = 0;
	//计算p和t0
	for (int i = 0; i < m; ++i)
	{
		p = (p*d + sp[i]) % q;
		t = (t*d + st[i]) % q;
	}

	for (int s = 0; s < n - m + 1; ++s)
	{
		if (p == t&&sp == st.substr(s, m))
		{
			return s;//如果找多个应该先存起来,或者这边输出但是别return
		}
		t = (d*(t - st[s] * h) + st[s + m]);
		t = ((t < 0) ? q : 0) + t%q;//如果上式直接取余数的话会遇到负号的问题,这里判断下。
	}
	return -1;
}

KMP

KMP是经典的字符串匹配算法。

预处理时间:O(M)

匹配时间复杂度:O(N)

主要过程:通过对字串进行预处理,当发现不能匹配时,可以不进行回溯。

void computePrefixFunction(string sp, int* a)
{
	int m = sp.length(), k = 0;
	a[0] = 0;
	for (int q = 1; q < m; ++q)
	{
		while (k>0 && sp[k] != sp[q])//加到一定程度和不匹对的情况;
			k = a[k-1];//这边也是从0开始的,所以还是要减1。。
		if (sp[k] == sp[q])
			++k;
		a[q] = k;
	}
//	return;
}
void kmpMatcher(string st, string sp)
{
	int n = st.length(), m = sp.length(), q = 0;
	int* p = new int[m];
	computePrefixFunction(sp, p);
	for (int i = 0; i < n; ++i)
	{
		while (q>0 && sp[q] != st[i])
			q = p[q - 1];
		if (sp[q] == st[i])
			q++;
		if (q == m)//因为上面提前+1了;
		{
			cout << i - m + 1 << endl;//从0开始计算。
			q = p[q - 1];//注意数组不要越界。
		}
			
	}
	delete []p;
//	return -1;//如果以返回值的方式那就只能输出第一个了。要输出多个要么提前保存,要么直接输出。
}

未完待续

续一秒更健康

posted on 2016-03-31 19:51  sayhitrue  阅读(389)  评论(0编辑  收藏  举报