字符串算法

Trie树
字典树,是一种存储字符串的方法,最核心的思想就是把前缀一样的字符串的前缀共用。然后把每一个串的结尾标记一下。
模板题
我们来看一下代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,m;
struct node{
	int cnt;
	int son[26];
	bool have;
	node(){
	cnt=0;
	memset(son,0,sizeof(son));
	have=false;	
	}
}trie[800000];

int num=0;
void insert(char *s){
	int v,len=strlen(s);
	int u=0;
	for(int i=0;i<len;i++){
		v=s[i]-'a';
		if(!trie[u].son[v])
			trie[u].son[v]=++num;
			u=trie[u].son[v];
	}
	trie[u].have=1;
}

int find(char *s){
	int v,u=0,len=strlen(s);
	for(int i=0;i<len;i++){
		v=s[i]-'a';
		if(!trie[u].son[v]){
			return 3;
		}
		u=trie[u].son[v];
	}
	if(!trie[u].have) return 3;
	if(!trie[u].cnt){
		trie[u].cnt++;
		return 1;
	} 
	return 2;
}
//int n,m;
int main(){
	char ch[100];
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>ch;
		insert(ch);
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>ch;
		int k=find(ch);
		if(k==3){
			cout<<"WRONG"<<endl;
		}
		else if(k==1){
			cout<<"OK"<<endl;
		}
		else if(k==2){
			cout<<"REPEAT"<<endl;
		}
	}
}

其实它的核心思想在于,对于每一个串,如果当前的部分有公共的前缀,就直接跑trie上的前缀直到不相同了为止,然后开始添加新的。

KMP
update:2019.8.11
Kmp算法用于单模式串匹配。即在一个文本串中找一个模式串出现了几次和出现的位置。
我们可以暴力思考一下。复杂度是O(n*m)的。
我们优化在于每一个字符重新开始匹配的位置。在暴力中,我们只是向后移一位,而在kmp中,我们通过预处理,来优化这个过程。
我们定义prefix[i]为模式串(1-i)中最长公共前缀后缀。我们在每次失去匹配后就可以直接跳到prefix[i]的位置上。
我们在预处理的时候是模式串的自匹配。
我们每次记录的当前位匹配的最大公共前后缀len,如果当前位匹配成功,那么len++,prefix[i]=len;
如果不匹配len=prefix[len-1],但是还是要注意两个问题,在i=1,len=0的时候会陷入死循环。所以我们要特判一下。

我们习惯于将数组向后移一位,将第一位置为-1。

我们将文本串的长度设为m,模式串长度n。
我们匹配到n-1位时,我们记录一下,然后还要匹配后面的文本串,所以j=prefix[j].

#include<bits/stdc++.h>
using namespace std;
int prefix[1000010];
char text[1000010];
char pattern[1000010];
int n;
void prefix_table(){
	prefix[0]=0;
	int len=0;
	int i=1;
	while(i<n){
		if(pattern[i]==pattern[len]){
			len++;
			prefix[i]=len;
			i++;
		}
		else{
			if(len>0){
				len=prefix[len-1];
			}
			else{
				prefix[i]=len;
				i++;
			}
		}
	}	
}

void move_prefix(){
	int i;
	for(int i=n;i>0;i--){
		prefix[i]=prefix[i-1];
	}
	prefix[0]=-1;
}

void kmp(){
	int n=strlen(pattern);
	int m=strlen(text);
	int i=0;
	int j=0;
	while(i<m){
		if(j==n-1 && text[i]==pattern[j]){
			printf("%d\n",i-j+1);
			j=prefix[j];
		}
		if(text[i]==pattern[j]){
			i++;j++;
		}
		else{
			j=prefix[j];
			if(j==-1){
				i++;j++;
			}
		}
	}
}


int main(){
	cin>>text;
	cin>>pattern;
	n=strlen(pattern);
	prefix_table();
	move_prefix();
	kmp();
	for(int i=1;i<=n;i++){
		printf("%d ",prefix[i]);
	}
}

AC自动机

  • 概述
    AC自动机就是把trie和kmp放在一起。也就是在trie上跑kmp,所以AC自动机又叫tr
    ie图。它和kmp
    的不同是,kmp是处理一个字串然后用处理的子串匹配主串,就是说你要匹配的串只有一个。但是有多个你就要用ac自动机了。

  • 算法过程
    你首先要把trie树建出来。然后根据题目不同的要去开不同的结构体。建图的过程就是从根节点开始,找到和这个串前缀相同最长的那个,然后把它的后缀插在它的后面。
    然后你就要处理失败指针。(next数组)
    这个处理的过程由于是在树上,所以有点类似bfs的感觉。开个队列,手写、STL区别不大。然后第一件事就是处理第一层。把所有点的fail指针指向根节点,然后把它们加入队列。其实就是bfs。核心代码就是下面两行吧。

	while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            if(ac[u].vis[i]){
                ac[ac[u].vis[i]].fail=ac[ac[u].fail].vis[i];
                q.push(ac[u].vis[i]);
            }
            else
                ac[u].vis[i]=ac[ac[u].fail].vis[i];
        }
    }		

如果你有这个节点,那么它的失败指针就是它父节点失败指针的这个儿子。
否则它自己指向,它父节点失败指针的这个儿子。
查询的过程就比较简单了。

int ac_query(string s){
    int l=s.length();
    int now=0,ans=0;
    for(int i=0;i<l;i++){
        now=ac[now].vis[s[i]-'a'];
        for(int j=now;j&&ac[j].end!=-1;j=ac[j].fail){
            ans+=ac[j].end;
            ac[j].end=-1;
        } 
    }
    return ans;
}

这样字符串部分的主流算法(提高范围内)就是有哈希了。

  • 哈希
    其实哈希就是一个加密的过程,就是把一个字符串通过一些方式把它变成一个数。下面来看 最简单最简单的哈希题
    这个题可以用最基础最基础的进制哈希。
    你为了尽量避免哈希冲突,所以你这个基数就要是个质数(想一想为什么),而且从理论上说这个数的设置不一定跟它的大小有直接关系,所以你要好好思考这个东西取什么。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[100010];
char s[100010];
int n,ans=1;
ull hash(char s[])
{
   int len=strlen(s);
   ull ans=0;
   for (int i=0;i<len;i++)
       ans=ans*base+(ull)s[i];
   return ans&0x7fffffff;
}
int main()
{
   scanf("%d",&n);
   for (int i=1;i<=n;i++)
   {
       scanf("%s",s);
       a[i]=hash(s);
   }
   sort(a+1,a+n+1);
   for (int i=2;i<=n;i++)
       if (a[i]!=a[i-1])
           ans++;
   printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值