如果让你从一个长字符串中匹配另一个字符串,你会怎么去查?大多数人的第一反应是从第一位开始匹配,如果匹配不成功,接着从第二位开始重新匹配,以此类推,直至完全匹配为止。但是匹配的字符有些部分前缀实际上是相同的,那么从这点考虑,有没有一些别的算法可以精简这步重复匹配操作呢。本文将介绍一种算法,KMP(Knuth-Morris-Pratt)。
要学习KMP算法,首先要理解字符串前缀后缀的含义,打个比方,"KnuthKn",它的前缀有K,Kn,Knu,Knut,Knuth,KnuthK,后缀有n,Kh,hKn,thKn,uthKn,nuthKn。然后我们定义一个部分匹配表,它会记录字符串每一位的前缀后缀相同字符串的长度。比如字符串前五位是Knuth,它们的前后缀没有匹配字符串,所以在部分匹配表里第五位是0;又比如前七位是KnuthKn,它们前后缀匹配的字符串是Kn,字符串长度为2,所以部分匹配表里第五位就是2。由此可得,"KnuthKn"的部分匹配表为table=[0000012]。
那么,可以跳过前面字符串位数的公式为
partial_match_length - table[partial_match_length - 1]
partial_match_length为已匹配字符长度,table为部分匹配表,下标从0开始。
如果这个时间,拿"KnuthKn"去匹配一个任意长度的字符串"KnuKnuthKzKnuthKn",当匹配到第三位的时候,发现不匹配,这个时间,执行上述公式,3-0=3,所以,跳过的长度为3,于是,下一次比对不需要从第三位开始,而是从第四位开始。当匹配到第十位的时候,又发现不匹配,则需要跳过6-1=5位,下一次从第9位开始。
但是在实际应用中,多数公司并没有采用KMP这种算法,甚至JDK内部的String.contains方法也是使用的是暴力匹配的方法,因为优化足以支持正常的使用。
算法的思想越简单,实际应用的效果越好