博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
KMP算法
阅读量:6257 次
发布时间:2019-06-22

本文共 6273 字,大约阅读时间需要 20 分钟。

KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。

next[j]是什么呢?

即求算模式串每个位置处的最长后缀与前缀相同的长度

就是求一个字符串中索引为j的字符前面的字符串中找出一个以0开始,一个以j-1结束的相等的子字符串。

就是在一个小字符串中找出长度为k的一个最大子字符串,要求字符串p中p[0,k-1]==p[j-k,j-1],这里的最大子字符串不能等于自身,我们就称一下为靠边大子字符串。

例:字符串 ababacd

 子字符串                    next[j]/k(第一个定义)               j
ababacd          /ababacd                     “”                            -1                           0
ababacd          a/babacd                     “”                             0                           1
ababacd          ab/abacd                     “”                             0                           2
ababacd          aba/bacd                     “a"                            1                           3
ababacd          abab/acd                     "ab"                           2                           4
ababacd          ababa/cd                     "aba“                          3                           5
ababacd          ababac/d                     ”“                             0                           6

 就是像这样,当j=5时,前面的字符串为ababa,在ababa中前3个aba与后3个aba就是上面说的靠边大子字符串,这里k=3,j=5.

这里的前面三个都没有子字符串,k应该都为0,但在下面的计算中为了方便把第一个设为-1,这也表明了第一个k特别。

怎么算出一个字符串中的相对应的next[j]数组呢?

static int[] GetNextVal2(string smallstr)        {            int[] next = new int[smallstr.Length];            //遍历整个字符串            for (int i = 0; i < smallstr.Length; i++)            {                if (i == 0)                    next[i] = -1;                if (i == 1)                    next[i] = 0;                //遍历字符前的字符串p[0,j-1]                for (int k = i - 1; k >= 0; k--)  //k==0时,next[i]=0;                {                    string left = smallstr.Substring(0, k); //前子字符串                    string right = smallstr.Substring(i - k, k); //后子字符串                    if (left == right)                    {                        next[i] = k;  //把最大的子字符串长度存储在next[i]中                        break;  //跳出里面的这个循环                    }                }            }            return next;        }

上面的方法循环太多了,还有一种是按照递推的思想

///         /// p0,p1....pk-1         (前缀串)        /// pj-k,pj-k+1....pj-1   (后缀串)        ///         ///         /// 
static int[] GetNextVal(string smallstr) { //前缀串起始位置("-1"是方便计算) int k = -1; //后缀串起始位置("-1"是方便计算) int j = 0; int[] next = new int[smallstr.Length]; //根据公式: j=0时,next[j]=-1 next[j] = -1; while (j < smallstr.Length - 1) { if (k == -1 || smallstr[k] == smallstr[j]) { //pk=pj的情况: next[j+1]=k+1 => next[j+1]=next[j]+1 next[++j] = ++k; } else { //pk != pj 的情况:我们递推 k=next[k]; //要么找到,要么k=-1中止 k = next[k]; } } return next; }

大体意思为:就上面的字符串ababacd,查询出来的next[j]的数组为-1,0,0,1,2,3,0

p[5]时,它的可能最大的靠边大子字符串就是p[4]的靠边大子字符串加上p[4],

所以在p[4]中查找k时,k=2,j=4,查找后验证p[4],即p[j]是否与k值为next[4]元素p[k]相等,p[2]==p[4],相等的话就是找到了p[5]的靠边大子字符串,k++;

 

如果不相等呢?有什么捷径找到p[5]的靠边大子字符串?

在上面的这个例子中p[k]==p[j],所以换一下,求字符串p”ababaad"的p[6]的靠边大子字符串

p[6]的靠边大子字符串,可能的靠边大子字符串就是p[5]的靠边大子字符串+p[5],p[5]中k=3,j=5,在这里发现p[5]!=p[3],就是上面讲的不等的情况,该怎么找它的

从p[5]的靠边大子字符串中查找靠边大子字符串a,再与p[6]进行匹配,不行的话从找到的"a“中再找靠边大子字符串,当然这里是没有了,也匹配了,next[6]=1

 

全部代码

public class Program    {        static void Main(string[] args)        {            string zstr = "ababcabababdc";            string mstr = "ababaab";            var index = KMP(zstr, mstr);            if (index == -1)                Console.WriteLine("没有匹配的字符串!");            else                Console.WriteLine("哈哈,找到字符啦,位置为:" + index);            Console.Read();        }        static int KMP(string bigstr, string smallstr)        {            int i = 0;            int j = 0;            //计算“前缀串”和“后缀串“的next            int[] next = GetNextVal2(smallstr);            for (int x = 0; x < next.Length; x++)            {                Console.WriteLine(next[x]);            }            while (i < bigstr.Length && j < smallstr.Length)            {                if (j == -1 || bigstr[i] == smallstr[j])                {                    i++;                    j++;                }                else                {                    j = next[j];                }            }            if (j == smallstr.Length)                return i - smallstr.Length;            return -1;        }        ///         /// p0,p1....pk-1         (前缀串)        /// pj-k,pj-k+1....pj-1   (后缀串)        ///         ///         /// 
static int[] GetNextVal(string smallstr) { //前缀串起始位置("-1"是方便计算) int k = -1; //后缀串起始位置("-1"是方便计算) int j = 0; int[] next = new int[smallstr.Length]; //根据公式: j=0时,next[j]=-1 next[j] = -1; while (j < smallstr.Length - 1) { if (k == -1 || smallstr[k] == smallstr[j]) { //pk=pj的情况: next[j+1]=k+1 => next[j+1]=next[j]+1 next[++j] = ++k; } else { //pk != pj 的情况:我们递推 k=next[k]; //要么找到,要么k=-1中止 k = next[k]; } } return next; } static int[] GetNextVal2(string smallstr) { int[] next = new int[smallstr.Length]; //遍历整个字符串 for (int i = 0; i < smallstr.Length; i++) { //前字符串为空时,k=-1; if (i == 0) next[i] = -1; if (i == 1) //前字符串只有一个字母时,k=0; next[i] = 0; //遍历字符前的字符串p[0,j-1] for (int k = i - 1; k >= 0; k--) //k==0时,next[i]=0; { string left = smallstr.Substring(0, k); //前子字符串 string right = smallstr.Substring(i - k, k); //后子字符串 if (left == right) { next[i] = k; //把最大的子字符串长度存储在next[i]中 break; //跳出里面的这个循环 } } } return next; } }

 

转载地址:http://rntsa.baihongyu.com/

你可能感兴趣的文章
SVN与TortoiseSVN实战:补丁详解
查看>>
java一些面试题
查看>>
干货型up主
查看>>
获取页面中所有dropdownlist类型控件
查看>>
读《淘宝数据魔方技术架构解析》有感
查看>>
[转载]如何破解Excel VBA密码
查看>>
【BZOJ】2563: 阿狸和桃子的游戏
查看>>
redis 中文字符显示
查看>>
国内外MD5在线解密网站
查看>>
【OC语法要闻速览】一、方法调用
查看>>
Git-命令行-删除本地和远程分支
查看>>
本文将介绍“数据计算”环节中常用的三种分布式计算组件——Hadoop、Storm以及Spark。...
查看>>
顺序图【6】--☆☆
查看>>
Docker Swarm 让你事半功倍
查看>>
string.Format字符串格式说明
查看>>
[转]IC行业的牛人
查看>>
javaScript事件(四)event的公共成员(属性和方法)
查看>>
linux系统常用命令
查看>>
在 Word 中的受支持的区域设置标识符的列表
查看>>
Caffe + Ubuntu 14.04 64bit + CUDA 6.5 配置说明2
查看>>