【挑战程序设计竞赛】后缀数组 实现字符串匹配

时间:2020-12-04 05:59:59

字符串后缀Suffix 指的是从字符串的某个位置开始到其末尾的字符串字串

后缀数组 Suffix Array 指的是将某个字符串的所有后缀按字典序排序之后得到的数组,不过数组中不直接保存所有

的后缀子串,只要记录相应的位置就好了。

下面的代码使用倍增法来构造后缀数组,该算法的复杂度是 O(n log n)常数因子比较大。

基于后缀数组的字符串匹配,我们可以通过二分搜索来完成,算法复杂度是 O(|T|log|S|) 其中 S 是主串,T是模式串

具体的后缀数组的原理可以详细去看看国家队集训的论文,罗穗骞《后缀数组——处理字符串的有力工具》:

 

#include <iostream>
#include <cstring>
#include <cstddef>
#include <cstdio>
#include <string>
#include <algorithm> 

using namespace std;
const int MAXN = 10001;
int n,k;
int rank[MAXN+1],tmp[MAXN+1];

bool comp_sa(int i, int j)
{
	if(rank[i] != rank[j]) 
		return rank[i] < rank[j];
	int ri = i+k <= n? rank[i+k] : -1;
	int rj = j+k <= n? rank[j+k] : -1;
	return ri < rj; 
}

void calc_sa(string &S, int *sa) //计算字符串S的后缀数组 
{
	n = S.size();
	
	//初始长度为1 
	for(int i = 0; i <= n; i++)
	{
		sa[i] = i;
		rank[i] = i < n ? S[i] : -1;
	}
	
	for( k =1; k <= n; k *= 2)
	{
		sort(sa,sa+n+1,comp_sa);
		
		//先在tmp中临时存储新计算的rank,再转存回rank中 
		tmp[sa[0]] = 0;
		for(int i = 1; i <= n; i++)
		{
			tmp[sa[i]] = tmp[sa[i-1]] + (comp_sa(sa[i-1],sa[i]) ? 1: 0);
		}
		for(int i = 0; i <= n; i++)
		{
			rank[i] = tmp[i];
		}
	}
}

void SuffixArrayMatch(string &S, int *sa, string T)
{
	calc_sa(S,sa);
	int lhs = 0, rhs = S.size();
	
	//使用二分查找 
	while(rhs - lhs > 1)
	{
		int mid = (lhs + rhs)>>1;
		int res = S.compare(sa[mid],T.size(),T); 
		if(res < 0)
			lhs = mid;
		else if(res == 0) 
		{
			cout<<"match at: "<<sa[mid]<<endl;
			lhs = mid;
		}
		else rhs = mid;
	}
}
int main()
{
	string S = "abracadabra";
	string T = "ab";
	int *sa = new int[S.size()+1];
 	SuffixArrayMatch(S,sa,T);
 	delete [] sa;
 	sa = NULL;
}