字符串小结

本文介绍了字符串处理中的四个关键算法:最小表示法用于寻找字典序最小的字符串;字符串哈希将不同字符串映射为唯一数字;AC自动机结合了KMP和Trie树,用于高效模式匹配;Manacher算法能在O(n)时间内找出最长回文子串。

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

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一.最小表示法
  • 二.字符串哈希r
  • 三.AC自动机
  • 四.Manacher


前言

本篇文章对字符串的一些算法做了总结


一、最小表示法

https://www.luogu.com.cn/problem/P1368

给定一个字符串a,可以把字符串的第一个字符移动到最后,可以操作任意多次,经过n次的移动后,形成字典序最小的字符串是什么,可以明显地看出经过n次移动后所形成的字符串,就是在原有字符串的基础上选择一个位置,从该位置上输出到最后,再从头输出到该位置就是改变后的字符串,我们要找的最小表示就是去找这个位置。

如何去找?

可以设置两个指针l和r,让l的初值为0,也就是指向第一个位置,让r的初值为1,指向第二个位置同时设置一个k初值为0,依次比较a[(l+k)%n]和a[(r+k)%n],这里要取余,为了回到左边的位置否则会超过字符串长度,如果一样就让k++,若前面那个大就让l指向l+k+1的位置,否则就让让r指向r+k+1的位置,最后返回min(l,r),注意每次改变l和r的位置后都要让k=0。

总得来说就是不断更新l和r的位置,最后找到一个最小表示的开始地方,复杂度为O(n).

代码如下;

int zx(string a)
{
	 int l=0,r=1,k=0;
	 int n=a.length();
	 while(l<n&&r<n&&k<n)
	 {
	 	if(a[(l+k)%n]==a[(r+k)%n])k++;
	 	else 
	 	{
	 		if(a[(l+k)%n]>a[(r+k)%n])l+=k+1;
	 		else r+=k+1;
	        if(r==l)r++;
	        k=0;
		}
	 }
	 return min(l,r);//返回最小最小表示的开始位置
}

二、字符串哈希

所谓的字符串哈希,就是让每个不同的字符串对应一个固定的数字,至于如何实现,可以设置一个base和取余数mod,这里的base和mod最好用质数,这样可以让冲突最小,h数组表示一个字符串的前缀哈希值,用p数组表示base的幂次值,h数组可以递推来求,h[i]=h[i-1]*base+str[i]-'a'+1,在求每一端的哈希值时要注意,根据前缀和的求法,本来直接让h[r]-h[l-1]即可,但是h[r]和h[l-1]在前面的数的幂次是不一样的所以h[l-1]需要再乘上base的r-l+1次才可以,因为h[r]的最高幂次是r-1而h[l-1]的最高幂次是l-2。

这里用了unsigned long long 的自然溢出,否则会导致数值太大溢出

代码如下

typedef unsigned long long ull;
const int maxn=1e6+7;
const int seed=131;
char str[maxn];
ull h[maxn],p[maxn];//h数组是求前缀哈希值,p数组是求进制的i次幂
//求一段区间的哈希值
ull get(int l,int r){	return h[r]-h[l-1]*p[r-l+1];	}

int main(){
    scanf("%s",str+1);
    int n=strlen(str+1);
    p[0]=1;
    for(int i=1;i<=n;i++){
        h[i]=h[i-1]*seed+str[i]-'a'+1;
//注意:“+1”不能省,否则“agh”与“gh”表示用一个数,也可以写h[i]=h[i-1]*seed+str[i];  
        p[i]=p[i-1]*seed;
	    }
}

三.AC自动机

 https://www.luogu.com.cn/problem/P3808
上面是AC自动机的模板题

AC自动机感觉就是kmp和trie数的结合体,在字典树上的每一个节点都有一个fail值,我们称它为失配指针,具体的某个结点的fail值指的以这个结点为结尾的后缀和以fail值为结尾的字串(包括fail这个点)有最大公共部分,至于如何实现fail指针的创建,我们可以用BFS去实现,因为每个结点的fail总是指向比它深度更浅的结点,那到底怎么指呢,其实就是指向它父亲结点的fail的相同儿子处,感觉其实是一个递归的过程。

在这道题中,我们创建好trie树,并设置好fail指针,对模式串进行匹配时,要对每一个模式串的字符跳fail值,直到没有fail可跳后再去到下一个字符,就这样依次遍历每一个字符,对于每一个字符都去统计一下看出现了几个字串,记住每次统计好一个串后要标记一下,防止该字串在模式串中多次出现。当然有时候这样跳也会TLE,所以有时候我们记一下初始的位置,最后再统一走fail就好了。

代码如下:

int id,n;
struct tree{
	int vis[26];
	int fail;
	int cnt;
}trie[maxn];
void build(string a)
{
	int k=0,len=a.size();
	for(int i=0;i<len;i++)
	{
	   int x=a[i]-'a';
          if(trie[k].vis[x]==0)trie[k].vis[x]=++id;
	   k=trie[k].vis[x];		
	}
	trie[k].cnt++;
}
void get_fail()
{
	queue<int>q;
	for(int i=0;i<26;i++)
	{
		if(trie[0].vis[i]!=0)
		{
			trie[trie[0].vis[i]].fail=0;
			q.push(trie[0].vis[i]);
		}
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<26;i++)
		{
			if(trie[u].vis[i]!=0)
			{
				trie[trie[u].vis[i]].fail=trie[trie[u].fail].vis[i];//定义fail的值
				q.push(trie[u].vis[i]);
			}
			else trie[u].vis[i]=trie[trie[u].fail].vis[i];
		}
	}
}
int query(string a)
{
	int ans=0,len=a.length();
	int now=0;
	for(int i=0;i<len;i++)
	{
		now=trie[now].vis[a[i]-'a'];
		for(int t=now;t&&trie[t].cnt!=-1;t=trie[t].fail)
		{
			ans+=trie[t].cnt;
			trie[t].cnt=-1;
		}
	}
	return ans;
}

四.Manacher

马拉车算法的最大优势就是可以在O(n)的复杂度内求出最大的回文串长度

我们要先对字符串进行一个预处理,在每个字符的两边加上一个相同的符号,同时在开头和结尾加上一个都没有出现过的字符作为结束字符,这样可以让不管是奇数还是偶数的回文串都被处理出来。

我们先设置一个id用来表示当前能够达到的最右边回文串的中心,mx表示以id为中心的回文串最右边的位置,len[i]数组表示i位置的回文串半径,可以发现len[i]-1就是原来字符串的回文串长度,那我们只要求出所有的len数组即可

依次去遍历每一个字符去求len,当前位置为i若i<mx说明在id的左边有另一个和str[i]匹配的字符此时len[i]的长度至少是min(mx-i,len[2*id-i]),2*id-i就是i关于id的对应位置,但是大于mx位置的字符我们还不知道,所以这里要暴力去匹配,若i>mx,则len[i]就只能先是1,再去暴力匹配,更新len[i]的值,最后不要忘记更新id和mx

代码如下:

char   s[11000007];
char str[25000007];
int  len[25000005];
int initstr(char *s)
{
	int k=0,len=strlen(s);
	str[k++]='@';
	for(int i=0;i<len;i++){
		str[k++]='#';
		str[k++]=s[i];
	}
	str[k++]='#';
	str[k]='\0';
	return k;
}
int manacher(int n)
{
	int mx=0,id=0,maxx=0;
	for(int i=1;i<n;i++)
	{
		if(mx>i)
		    len[i]=min(mx-i,len[2*id-i]);
		else 
		    len[i]=1;
		while(str[i+len[i]]==str[i-len[i]])
		    len[i]++;
		if(len[i]+i>mx)
		{
			mx=i+len[i];
			id=i;
			maxx=max(maxx,len[i]);
		}
	}
	return maxx-1;
}

<think>好的,用户想了解字符串操作实验的总结或关键点。我需要先回顾一下相关的引用内容,尤其是引用[3]和引用[4],因为这些直接涉及字符串操作和性能比较。 首先,引用[4]中提到了在Java中使用不同的字符串拼接方法(如+运算符、concat()方法和StringBuilder)的性能差异。实验显示StringBuilder明显更快,这点非常重要,应该作为关键点之一。需要解释为什么会有这样的性能差异,比如String的不可变性导致每次拼接都生成新对象,而StringBuilder则是可变的对象,减少了内存分配和复制的开销。 然后引用[3]讨论了字符串拷贝函数,指出在拷贝指定长度时需要注意手动添加结束符'\0',否则可能导致问题。这可能涉及到C语言中的字符串处理,但用户的问题比较通用,可能需要总结不同编程语言中字符串处理的注意事项,比如不可变性、内存管理、结束符处理等。 另外,用户提供的引用中还有关于环境变量设置的问题(引用[2]),但可能和当前问题关联不大,暂时不考虑。引用[1]关于字符串比较的大小写问题,可能和字符串操作相关,比如比较函数的使用,但用户的问题更侧重于实验总结,所以可能不需要深入这点,除非实验中有涉及比较操作。 需要组织关键点,可能包括字符串不可变性、不同拼接方式的性能差异、内存管理、结束符处理、安全性和编码问题等。结论部分应强调在实际开发中选择合适的字符串操作方法,特别是循环体内使用StringBuilder的重要性。 还要注意用户要求回答结构清晰,分点说明,使用中文,并正确应用LaTeX格式。例如,在提到时间复杂度时,使用$O(n^2)$和$O(n)$这样的数学表达式。同时,生成相关问题,可能涉及优化方法、应用场景、不同语言差异等。 检查是否有遗漏的关键点,比如字符串函数的使用注意事项,比如拷贝时不自动添加结束符,需要手动添加,这也是实验中容易出错的地方。另外,可能要考虑字符串池的概念,或者编码问题如UTF-8处理,但根据引用内容,可能主要围绕性能和内存管理。 总结下来,关键点包括: 1. 字符串不可变性导致的性能问题。 2. 不同拼接方法的性能对比。 3. 字符串操作的内存管理(如结束符处理)。 4. 安全性问题(如缓冲区溢出)。 5. 编码和字符集的影响。 结论要强调实验中的性能差异,并推荐最佳实践,如使用StringBuilder处理大量拼接。</think>### 字符串操作实验总结 #### 关键点分析 1. **字符串拼接性能差异** 在循环体内频繁拼接字符串时,不同方法的性能差异显著: - **`+`运算符**:每次操作会创建新字符串对象,时间复杂度为$O(n^2)$。例如,拼接10,000次耗时约**500ms**。 - **`concat()`方法**:与`+`类似,仍涉及对象频繁创建,但略高效,耗时约**400ms**。 - **`StringBuilder`**:通过可变缓冲区直接追加字符,时间复杂度为$O(n)$,耗时仅约**1ms**,性能提升约**525倍**[^4]。 2. **字符串的不可变性** Java中`String`对象不可变,每次修改都会生成新对象,导致内存浪费和性能损耗。`StringBuilder`通过动态扩展缓冲区避免了这一问题[^4]。 3. **内存管理与结束符处理** 在C语言等底层操作中,手动拷贝字符串需注意**结束符`\0`的添加**,否则可能引发未定义行为。例如,`strncpy()`函数不会自动添加结束符,需显式赋值[^3]。 4. **安全性问题** 直接操作字符数组时,需防范缓冲区溢出风险(如`strcpy`不检查目标缓冲区大小)。应使用安全函数(如`strncpy`)或动态内存分配。 5. **字符集与编码影响** 字符串比较或处理时,字符集(如UTF-8、ASCII)可能影响结果。例如,二进制比较区分大小写,而某些字符集可能不区分[^1]。 #### 实验结论 - **高频拼接场景**:优先选择`StringBuilder`或`StringBuffer`(线程安全版本)。 - **短字符串操作**:`+`或`concat()`可读性更佳,性能影响可忽略。 - **底层操作规范**:手动处理字符串时需严格管理内存和结束符[^3]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值