KMP算法学习

【算法理解】     

      这个程序的算法是相当“朴素”的:变量k是在字符串s中搜索t的起点,用指针i与j分别在s与t中扫描,逐一比较s[i]与t[j]是否相同。若遇到不相同的情况,则将起点k在s中后移一个字符,继续搜索。客观地讲,在两个字符串都不很长的情况下,这个算法的执行效率还是可以的。

       但在某些特殊情况下,这个算法的效率问题就会显现出来,它最大的问题就在于:当出现s[i]!=t[j]时,指针i要回到前面的字符重复比较。

       比如这种情况:  s: ababcabcacbab  t: abcac

 当起点k=2时,如表-1所示

 

 

 

k,i

 

 

 

 

 

 

 

 

 

 

s

a

b

a

b

c

a

b

c

a

c

b

a

b

t

 

 

a

b

c

a

c

 

 

 

 

 

 

 

 

 

j

 

 

 

 

 

 

 

 

 

 

       i从k(即2)开始,j从0开始比较,前四对字符都是比较成功的(表-1中的灰色部分),只在最后一个字符的比较时出现了问题,见表-2:

 

 

 

k

 

 

 

i

 

 

 

 

 

 

s

a

b

a

b

c

a

b

c

a

c

b

a

b

t

 

 

a

b

c

a

c

 

 

 

 

 

 

 

 

 

 

 

 

 

j

 

 

 

 

 

 

此时s[i]= =’b’,而t[j]= =’c’。比较失败后,内层循环结束,k移动到3,i也将回复到3,j回复到0,重新开始比较——尽管i最多时已经到6,它还是要回到3,这明显是有重复计算出现。

       KMP算法对前述算法的改进之处就是变量i不需回复。

       还从表-2分析,此时出现了s[i]!=t[j]的情况,我们的问题是:若变量i原地不动,它应该和t的哪一个字符继续进行比较呢(即变量j的值应修改为多少)?

       在挑选j值时需要保证:新的t[j]之前的部分应该是与字符串s匹配成功的,通过观察,发现当j=1时,可以满足要求。

 

 

 

 

 

 

 

i

 

 

 

 

 

 

s

a

b

a

b

c

a

b

c

a

c

b

a

b

t

 

 

 

 

 

a

b

c

a

c

 

 

 

 

 

 

 

 

 

 

j

 

 

 

 

 

 

       此时,t[j]前面的部分与s[i]前面的等长部分是完全相同的,因此可以“放心大胆”地从此时的s[i]与t[j]开始,继续比较下去。

       下面分析当s[i]!=t[j]时,求下一个j的算法。

       分析表-2与表-3的t一行,发现表-3中j之前的部分,恰好是表-2中j之前的部分的后缀,而t这一行标明的都是字符串t的内容,表-3不过是将t的位置后移了,因此我们也可以说:表-3中j之前的部分,也是表-2中j之前的部分的前缀。

       所以,若有s[i]!=t[j],找下一个j的方法是:在字符串t里j之前的部分中,找到即是前缀又是后缀的那个字符串(且要最长的那个)——假定它的长度为k,那么下一个j就取k(因为C++数组下标从0开始)。同时也得到另外一条结论:若有s[i]!=t[j],下一个j的选择只依赖于字符串t本身的性质,与字符串s无关,且这个值是应该固定的。

       定义整数数组n[],n[j]存储当s[i]!=t[j]时,j的下一取值。

       我们用递推的方法确定数组n[]。

       首先在n[0]处设置监视哨,规定n[0]= -1(即字符串最小可用下标0的前一个数字,在后面的讨论中将看到这一设置的好处:它可以将原本的三种情况,归结为两种情况)。

       然后用递推方法确定其它值,假定n[0]~n[j]的值都已经确定了,现由已知的这些数据来确定n[j+1]的值。

 

 

 

 

 

j

j+1

 

 

 

 

 

 

 

n

-1

0

0

0

1

 

 

 

 

 

 

 

 

t

a

b

c

a

b

d

d

 

 

 

 

 

 

t

 

 

 

a

b

c

a

b

d

d

 

 

 

 

 

 

 

 

k

 

 

 

 

 

 

 

 

    t=”abcabdd”,且我们已经确定n[0]~n[4]的值,现确定n[5]。当j=4时,记k=n[4]=1,由前面的讨论知道,在j之前有长度为k(即1)的子串即是前缀也是后缀(灰部分)。在表-4所示的情况下,又有t[j]==t[k],因此我们可以肯定:在j+1之前的部分中,最长的那个“即是前缀又是后缀的子串的长度一定是2,即k+1,因此有n[j+1]=k+1,即n[5]=2。

       这只是一种情况,下面讨论另一种t[j]!=t[k]的情况,看表-5。

 

 

 

 

 

 

j

 

 

 

 

 

 

 

n

-1

0

0

0

1

2

 

 

 

 

 

 

 

t

a

b

c

a

b

d

d

 

 

 

 

 

 

t

 

 

 

a

b

c

a

b

d

d

 

 

 

 

 

 

 

 

 

k

 

 

 

 

 

 

 

       此时已经确定n[0]~n[5]的值,现确定n[6]。已经有t[j]!=t[k],j+1之前的即是前缀又是后缀的最长子串已经不能由简单的由“延长”来得到了,但我们知道另一个有用的信息,即:若在t[k]处出现字符不相同,我们应该将k的值变为n[k],由于k<j,因此这个n[k]是已经确定的。通过这种方法不停缩小k值,直到出现两种情况:(1)若t[k]= =t[j],可以参考前一种方案;(2)若k= = -1,说明找不到合适的子串,要从t[0]开始比较,此时就可以用到监视哨了——n[j+1]=k+1。

       当有了数组n[]之后,在s中搜索t时,若有s[i]!=t[j],则取j=n[j]继续比较就是了,若出现j<0或s[i]= =t[j]的情况,i、j都后移。

【算法模板】

#include<bits/stdc++.h>
using namespace std;

char t[1001],s[1001];
int n[1001],m;

void next(){
	int i=0,k=-1;
	n[0]=k;
	while(i<m)
		if(k<0||t[k]==t[i]) n[i++]=++k;
		else k=n[k];
}

int match(){
	int i,j;
	while(i<int(strlen(s))&&j<int(strlen(t)))
		if(j<0||s[i]==t[j]){
			i++;
			j++;
		}
		else j=n[j];
	if(j==strlen(t)) return i-j+1;
	else return -1;
}

int main()
{
	cin>>s>>t;
	m=strlen(t);
	next();
	cout<<match()<<endl;
	return 0;
}

内容概要:该研究通过在黑龙江省某示范村进行24小时实地测试,比较了燃煤炉具与自动/手动进料生物质炉具的污染物排放特征。结果显示,生物质炉具相比燃煤炉具显著降低了PM2.5、CO和SO2的排放(自动进料分别降低41.2%、54.3%、40.0%;手动进料降低35.3%、22.1%、20.0%),但NOx排放未降低甚至有所增加。研究还发现,经济性和便利性是影响生物质炉具推广的重要因素。该研究不仅提供了实际排放数据支持,还通过Python代码详细复现了排放特征比较、减排效果计算和结果可视化,进一步探讨了燃料性质、动态排放特征、碳平衡计算以及政策建议。 适合人群:从事环境科学研究的学者、政府环保部门工作人员、能源政策制定者、关注农村能源转型的社会人士。 使用场景及目标:①评估生物质炉具在农村地区的推广潜力;②为政策制定者提供科学依据,优化补贴政策;③帮助研究人员深入了解生物质炉具的排放特征和技术改进方向;④为企业研发更高效的生物质炉具提供参考。 其他说明:该研究通过大量数据分析和模拟,揭示了生物质炉具在实际应用中的优点和挑战,特别是NOx排放增加的问题。研究还提出了多项具体的技术改进方向和政策建议,如优化进料方式、提高热效率、建设本地颗粒厂等,为生物质炉具的广泛推广提供了可行路径。此外,研究还开发了一个智能政策建议生成系统,可以根据不同地区的特征定制化生成政策建议,为农村能源转型提供了有力支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值