ACM暑期集训11

今天主要学习了KMP算法,trie树和AC自动机

1.KMP算法

KMP算法是用来解决字符串匹配问题,时间复杂度O(n+m) (其中n,m分别为长、短字符串的长度)

直接上模板,后面有注释

1)求fail数组

void getFail(int n) {					//n表示短字符串的长度
  int i = 0, j = -1;						//i表示短字符串当前位置,j表示当前位置之前的部分前缀和后缀相同的个数-1
	fail[0] = -1;							//fail数组表示当前位置匹配失败后从短字符串之前哪个位置继续匹配
	while(i < n) {
		if(j==-1 || temp[i]==temp[j])		//temp数组用来存储短字符串
		{
			++i, ++j;
			if(temp[i] != temp[j]) fail[i] = j;
			else fail[i] = fail[j];
		}
		else j = fail[j];
	}
}

2)KMP

int kmp(int n, int m) {				//n表示长字符串的长度,m表示短字符串的长度
	int res = 0;						//res表示长字符串中有多少个短字符串
	int i = 0, j = 0;					//i表示长字符串当前位置,j表示短字符串当前位置
	while(i < n) {
		if(j==-1 || str[i]==temp[j]) {	//temp存储短字符串,str存储长字符串
			++i, ++j;
			if(j == m) {				//当发现长字符串中有一个短字符串时
				++res;
				j = 0;
			}
		}
		else j = fail[j];
	}
	return res;
}

如果考虑字符串可以覆盖,eg.  temp=aza,  str=azazaza 考虑覆盖,str中有三个temp,这是只需要修改while循环中的一小步就行

int kmp(int n, int m) {				//n表示长字符串的长度,m表示短字符串的长度
	int res = 0;						//res表示长字符串中有多少个短字符串
	int i = 0, j = 0;					//i表示长字符串当前位置,j表示短字符串当前位置
	while(i < n) {
		if(j==-1 || str[i]==temp[j]) {	//temp存储短字符串,str存储长字符串
			++i, ++j;
		}
		else j = fail[j];
		if(j == m) {				//当发现长字符串中有一个短字符串时
				++res;
			}
	}
	return res;
}

 

2.trie树(也叫字典树)

引例:

已知n扇门,每扇门对应一个密码(用一个字符串表示,密码已知)

扫描二维码关注公众号,回复: 2520662 查看本文章

例如:0对应say,1对应she,2对应shr,3对应her

每次询问时,输入一个密码,求它对应几号门(0,1, …, n-1)

如果把输入的密码和每扇门的密码一一比较,时间复杂度约为O(n*k),k为密码的长度

如果询问q次,时间复杂度为O(q*n*k) 多次询问可能会超时

分析:

构建一棵树如下图 在树的叶子节点存储对应的几号门 可以将时间复杂度降为O(q*k)

 

建树时间复杂度:O(k*m)  查询时间复杂度:O(m)

其中k为需要插入字符串的个数(插入一个为O(m)),m为字符串的长度

1)Trie树----需要的数据结构

const int INDEX_N = 26;			//一个字符串中最多有多少种字符
struct Node {					//树的节点
	int num;						//num表示是几号门
	Node *next[INDEX_N];			//next数组指向子节点

	Node(): num(-1) {				//构造函数
		for(int i=0; i<INDEX_N; i++)
			next[i] = NULL;
	}
};
Node *root = new Node;					//Trie树的根节点

 

2)Trie树----插入字符串

void insert(string str, int num) {		//str表示要插入的密码,num表示对应的几号门												
    Node *ptr = root;
    for(unsigned int i=0; i<str.size(); i++) {
		int pos = str[i]-'A';
		if(!ptr->next[pos]) ptr->next[pos] = new Node;
		ptr = ptr->next[pos];
	}
	ptr->num = num;
}

3)查找

void search(string str) {
    Node *ptr = root;
    for(unsigned int i=0; i<str.size(); i++) {
		int pos = str[i]-'A';
		if(!ptr->next[pos]) return;
		ptr = ptr->next[pos];
	}
	cout << ptr->num << endl;
}

3.AC自动机

思路:

Trie树结合KMP,就成了AC自动机 也就是加上fail指针(如下图)

但是AC自动机的fail指针算法有所不同

重要性质:任何一个点的fail指针,一定是root 或着它父亲fail指针形成的链上的某个点的儿子 且这个儿子对应的字符与该节点字符相同

1)需要用到的数据结构

struct Node {
	int index;						//index与num类似,某些节点需要保存的信息
	Node *fail;					//当前节点匹配失败后指向下一个匹配节点
	Node *next[INDEX_N];

	Node(): index(0), fail(NULL) {		//构造函数
		for(int i=0; i<INDEX_N; i++)
			next[i] = NULL;
	}
};
Node *root = new Node;			//根节点

2)插入短字符串

void Insert(string str, int index) {					//str表示插入的短字符串
    Node *ptr = root;
    for(unsigned int i=0; i<str.size(); i++) {
		int pos = str[i]-'A';
		if(!ptr->next[pos]) ptr->next[pos] = new Node;
		ptr = ptr->next[pos];
	}
	ptr->index = index;
}

3)fail指针

void GetFail() {
	queue<Node*> que;
	que.push(root);
	while(!que.empty()) {
		Node *ptr = que.front();
		que.pop();
		for(int i=0; i<INDEX_N; i++)
			if(ptr->next[i]) {
				Node *temp = ptr->fail;
				while(temp && !temp->next[i])
					temp = temp->fail;
				if(temp) ptr->next[i]->fail = temp->next[i];
				else ptr->next[i]->fail = root;
				que.push(ptr->next[i]);
			}
	}
}

4)AC自动机

void Solve(string str)
{
	Node *ptr = root;
	for(unsigned int i=0; i<str.size(); i++)
	{
		if(str[i]<'A' || str[i]>'Z')
		{
			ptr = root;
			continue;
		}

		int pos = str[i]-'A';
		while(ptr!=root && !ptr->next[pos]) ptr = ptr->fail;
		ptr = ptr->next[pos];

		if(!ptr)
		{
			ptr = root;
			continue;
		}

		for(Node *temp=ptr; temp!=root; temp=temp->fail)
			if(temp->index)
				num[temp->index]++;
	}
}

构造时间复杂度:与Trie树类似 查询时间复杂度:O(n + k*m)

其中n、m分别为长字符串和短字符串的长度,k为短字符串的个数

4.KMP----fail数组另一个作用

n-fail[n]表示短字符串最小循环节的长度

其中n表示短字符串的长度

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_41383801/article/details/81362780
今日推荐