字符串相关算法 RabinKarp KMP 前缀树 后缀数组 处理回文(哈希,Malacher)

本文深入探讨了字符串匹配问题,介绍了Rabin-Karp算法及其优化版本——滚动哈希法,并对比了KMP算法与后缀数组在解决匹配问题上的应用。通过实例展示了不同算法的时间复杂度及实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符串匹配问题:在源串中找模板串的下标。

RabinKarp:

哈希法。

一般法:PabinKarp

1,对模板字符串x按d进制求值,可以再mod h作为其的hash,
2,在对源串s,依次求出s.length-x.length个hash,
3,匹配两者的hash
时间复杂度:O(mn)

static List<Integer> ans=new ArrayList<>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String a="abababababa";
		String b="aba";
		long hashb=hash(b);
		match(a,b,hashb);
		for(int i:ans)System.out.print(i+" ");
		

	}
	public static void match(String a,String b,long hashb)
	{
		for(int i=0;i+b.length()<=a.length();i++)
		{
			long hasha=hash(a.substring(i,i+b.length()));
			if(hasha==hashb)
			{
				ans.add(i);
			}
		}
	}
	public static long hash(String b)
	{
		long ans=0;
		for(int i=0;i<b.length();i++)
		{
			ans=ans*31+b.charAt(i);
		}
		return ans;
	}

优化,RabinKarp(滚动哈希)

在原理的基础上,优化求哈希值。
时间复杂度为O(m+n)


public class 哈希求字符匹配滚动法 {

	static long mod=Long.MAX_VALUE;
	static long seed=31;//进制
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String a="abababababa";
		String b="aba";
		int lenb=b.length();
		long hashb=hash(b);
		
		
		long res[]=new long[a.length()-lenb+1];//0-4  0-1    0-3      5-2+1=4
		for(int i=0;i<lenb;i++)
		{
			res[i]=hash(a.substring(i,i+lenb));//存放每一位对应的hash
		}
		for(int i=lenb;i<res.length;i++)
		{
			char newchar=a.charAt(i);
			char oldchar=a.charAt(i-lenb);
			long nexthash=(long) (res[i-lenb]*seed-(a.charAt(i-lenb)*Math.pow(seed, lenb))+a.charAt(i))%mod;
			res[i]=nexthash;
		}
		List<Long> ans=new ArrayList<>();
		for(int i=0;i<res.length;i++)
		{
			if(res[i]==hashb)
				ans.add((long) i);
		}
		for(long i:ans)
			System.out.print(i+" ");
		
	}
	
	public static long hash(String b)
	{
		long ans=0;
		for(int i=0;i<b.length();i++)
		{
			ans=(ans*seed+b.charAt(i))%mod;
		}
		return ans;
	}

}

KMP

后缀数组

定义:
所有后缀子串按字典排序后,在数组中记录排名和在子串中的下标
在这里插入图片描述

处理回文

哈希 O(n)

#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define fir(i,a,b) for(int i=a;i<=b;i++)
#define fic(i,a,b) for(int i=a;i>=b;i--)
#define Mod 131 //P进制
const int N=1000007;
char s[N];
ull f1[N],f2[N],p[N];
int ans,t,l,r,mid;
ull Hash1(int i,int j)//正字符串的哈希值
{
    return (f1[j]-f1[i-1]*p[j-i+1]);
}
ull Hash2(int i,int j)//反字符串的哈希值
{
    return (f2[i]-f2[j+1]*p[j-i+1]);
}
void init()
{
    p[0]=1;//p^0为1
    fir(i,1,N-1)
        p[i]=p[i-1]*131;//P进制的位值
}
int main()
{
    init();
    while (++t)
    {
        ans=0;
        scanf("%s",s+1);
        int len=strlen(s+1);
        if (strcmp(s+1,"END")==0) //结束读入
            return 0;
        f2[len+1]=0;//初始化要注意,不然的话容易GG
        fir(i,1,len) 
            f1[i]=f1[i-1]*Mod+(s[i]-'a'+1);//前缀和
        fic(i,len,1)
            f2[i]=f2[i+1]*Mod+(s[i]-'a'+1);//后缀和
        fir(i,1,len)
        {
            l=0,r=min(i-1,len-i);//二分枚举长度为奇数的字符串 记住这里l一定要为0,不然的话,你会发现最后一个数据会卡死你.
            while(l<r)
            {
                mid=l+r+1>>1;
                if (Hash1(i-mid,i-1)==Hash2(i+1,i+mid))//如果这是一个回文串的话
                    l=mid;
                else
                    r=mid-1;
            }
            ans=max(l<<1 | 1,ans);//算出最大长度
            l=0,r=min(i-1,len-i+1);//偶数字符串
            while (l<r)
            {
                mid=l+r+1>>1;
                if (Hash1(i-mid,i-1)==Hash2(i,i+mid-1))//check判断
                    l=mid;
                else
                    r=mid-1;
            }
            ans=max(l<<1,ans);//偶数字符串只需要*2
        }
        printf("Case %d: %d\n",t,ans);
    }
    return 0;
}

Malacher O(n)

char a[N];

char b[N];
int p[N];

void init()  // a[]为原串,b[]为插入'#'后的新串
{
    int k = 0;
    b[k ++ ] = '$', b[k ++ ] = '#';
    for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = '#';
    b[k ++ ] = '^';
    n = k;
}

void manacher()  // 马拉车算法,b[]为插入'#'后的新串
{
    int rt = 0, mid;
    int ans=0;
    for (int i = 1; i < n; i ++ )
    {
        if (i < rt) p[i] = min(p[mid * 2 - i], rt - i);
        else p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
        if (i + p[i] > rt)
        {
            rt = i + p[i];
            mid = i;
        }
        ans = max(ans,p[i]-1);
    }
    cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值