C语言数据结构串的KMP

关于串的BF算法和KMP算法(王道)

BF匹配算法(朴素模式匹配算法)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

BF算法核心就是用模式串T和主串S对比,如果从主串S的第一个字符出发,往后m个字符都与模式串对比(m为模式串的长度)即,模式串从主串的第一个字符开始匹配,如果都匹配就返回k(匹配的字符的第一个位置),如果不匹配,就把主串后移一位,模式串回到第一个字符,开始重新匹配,如上图所示。

BF缺点
BF 算法的缺点,如果匹配成功往前匹配了m个字符,然后遇到不匹配的就会倒退m-1个字符。主串和模式串中,只有部分能匹配成功,很明显在匹配成功的“g o o g l ”中第一个g的后面都无法做到与主串有较理想的匹配(第二个g会在KMP的优化中提到)即g要给第一个“o”比然后在和第二个“o”,而这些比较是完全没有必要的,所以完全可以跳过这些已经得到匹配的字符,即kmp算法

在这里插入图片描述

KMP算法

书上概念理论性过强太抽象,这里只讲通俗的方便理解的kmp求next[ ]数组。next[ ]数组到底是用来干嘛的?在字符串与模式串匹配的算法实现时,你要把所分析的kmp的方法转化为程序指令,在计算机上运行,这里就需要用next来求解到底把模式串向后移多少或者模式串回溯几个才是最优的和主串进行匹配(j=next[j];)
kmp算法,就是如果从第一个字符开始匹配,遇到不匹配的字符时,不用回溯到第二个字符(BF是这么干的)主串i不回溯一直往后走,模式串根据next[ ]数组决定回溯到哪,然后再继续匹配。
即遇到不匹配,i不回溯 j 回溯到第一个
在这里插入图片描述
在这里插入图片描述

next[ ]数组的求法
KMP算法中的next函数值之和模式串有关,而和相匹配的主串无关(即前面提到的,为提高BF算法效率,遇到匹配不上的字符,不让主串回溯,只模式串回溯,这里next[ ]数组就是表示模式串回溯多少的)
法一:
先根据字符串从第一个字符开始找最大前后缀(前缀时包括第一个字符不包括最后一个字符的子串,后缀时包括最后一个字符不包括第一个字符的子串 )
模式串 :a b a a b c a c
在这里插入图片描述
图中maxL是指遍历到每一个字符时,它的最大的相等的前后缀。从第一个开始 a没有前后缀所以maxL为0 。 a b 前缀是a 后缀是b 前后缀不相等,所以maxL为0 ,a b a 最大相等前后缀为a 所以maxL为1 …到子串5的位置时 a b a a b 时,最大相等前后缀为 a b 所以maxL为2 按照此法依次求出maxL
next第一个直接写零,从第2个开始,next的值等于第前一个maxL+1 这样就求出了next[ ]数组

法二:
不用写maxL的方法直接写next[ ] (秒杀法)
在这里插入图片描述
无论哪种方法第一位都直接写0 ,。从第2个开始,写哪一个的next捂住谁,比如第2 个b ,捂住b 前面只有一个a 则a的相等的前后缀为0 ,则 0+1=next 即next写1 第3个字符a 捂住,前面是a b 最大相等的前后缀还是0,则next= 0+1=1 , 到第4个字符a 捂住a 前面是a b a 最大相等的前后缀有一个 a 则next= 1+1=2 …依次类推就可以直接根据模式串算出next

两种方法的区别
第一种方法需要先求出最大前后缀再根据最大前后缀错位+1算得next,而第二种方法直接就可以根据模式串写出next。第一种方法,到第几个字符,判断最大前后缀时,把第几个字符也算上,但第二种方法是,到第几个字符就捂住第几个字符只根据它前面的字符来算最大相等的前后缀再+1得next (文字太生硬,其实很容易理解,耐心点!)在这里插入图片描述

KMP算法的优化

前面讲到主串和模式串在++往后遍历时遇到不匹配的字符时,主串不回溯,模式串根据next数字决定回溯到第几个字符,在开始匹配,但在以下特殊情况中,如下图,
在这里插入图片描述
在第四个字符时遇到了不匹配,按照KMP算法,主串i不回溯,模式串j根据next回溯到3,可以发现第3个字符和第4个字符一样,所以第3个字符去匹配也是个第4个字符一样,接着回溯,一直到第一个字符的next 0 当next[j]为0时,i++ , j++ i加一到第5个字符 next[j] =0 加一到第1个字符 ,然后在进行匹配。这样很明显前三个a的回溯完全没有必要,所以对KMP算法进行了优化,引入 nextval 即在第四个字符,直接把nextval=0,也就是跳过原来kmp算法中从next=3开始回溯到next=0,
关于nextval的求法
在这里插入图片描述
第一个直接写0 之后拿图中maxL和nextval对比,相等的话就取等于的那个值的nextval,如果不相等,其next值就等于nextval。如第2个maxL 为0 而next为1 不相等 则nextval取next的值 1 ,第3个maxL与next相等为1,则nextval就取序号为1 的字符的nextval值 所以第3个nextval=0 。

还可以根据秒杀发写出next后倒退出maxL 在根据next和maxL 写出相应的nextval。

广义表

head()操作可以取出单元素,而tail()得到的有括号例如广义表M (a,b) head(M)=a tail(M)=(b) 想要得到b元素还要把(b)的括号去掉 即head(tail(M))

已知广义表T=((a,b,c),(d,e,f)),取出e。

tail(T) = ((d,e,f)) //相当于把表头(a,b,c)删掉剩余的部分当然也包括 括号

head(tail(T)) = (d,e,f) //取表头去掉一层括号

tail(head(tail(T))) = (e,f)

head(tail(head(tail(T)))) = e

H()取表头,可以去掉一层括号,T()取表尾,括号跟着走。
广义表越深括号越多,也越容易迷。 要保持人间清醒啊!

严蔚敏版数据结构P109
请将香蕉banana用工具H ( ) — Head ( ) , T ( ) — Tail ( )从L中取出。

L=(apple ,(orange ,(strawberry ,(banana)) ,peach) ,pear)
第一步banana在表尾,那就用T()取表尾,相当于把表头“apple ,”删掉留下apple外的括号

T(L)=(apple , (orange ,(strawberry ,(banana)) ,peach) ,pear) 注意apple后的逗号也一块带走

T(L)=((orange ,(strawberry ,(banana)) ,peach) ,pear)
然后取表头相当于取标黄的一段
T(L) =((orange ,(strawberry ,(banana)) ,peach) ,pear)
H(T(L)) = (orange ,(strawberry ,(banana)) ,peach)
再取表尾 相当于删了 'orange , ’
H(T(L)) = (orange ,(strawberry ,(banana)) ,peach)
T(H(T(L))) = ((strawberry ,(banana)) ,peach)
再取表头,也就是取黄色部分
T(H(T(L))) = ((strawberry ,(banana)) ,peach)
H(T(H(T(L)))) = (strawberry ,(banana))
再取表尾相当于把“strawberry ,”删掉
H(T(H(T(L)))) = (strawberry ,(banana))
T(H(T(H(T(L))))) = ((banana))
前面讲到过,取表头有去括号的作用把 ((banana)) 当做 ((banana),/0) ‘/0’ 也就是空
取表头
H(T(H(T(H(T(L)))))) = (banana)
再取表头
H(H(T(H(T(H(T(L))))))) = banana

举个方便理解的栗子

广义表L=(a,((b),c),d)
取表头Head(L)= a
取表尾 Tail(L)=(((b),c),d)
取表尾括号留着

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值