java学习笔记——KMP字符串匹配算法

字符串匹配暴力解法

  • 暴力解法的思想很简单,规定两个指针,一个指向目标字符串(target)规定为指针j,一个指向待匹配字符串(source)规定为指针i,需要在source中找到target字符串并返回开头索引。
    在这里插入图片描述

  • 如果指针i和j指向的字符相同,就同时向后移动
    在这里插入图片描述

  • 一旦两个指针所指的字符串不相同,指针i就重新指向target的开头,而指针j则指向source开始匹配的下一个字符,重复以上步骤。
    在这里插入图片描述

  • 该解法虽然能够解决字符串的匹配问题,但是当进行回溯过程时,浪费了大量无意义的过程。如果以我们正常思想来看,完全没必要回溯至source中的第二个元素。因为它明显与target的开头匹配不上。

  • 如果要解决以上浪费的问题,我们可以当匹配不上时,回溯到最近的,与target开头能匹配的位置上去,如下图所示。
    在这里插入图片描述
    在这里插入图片描述

  • 现在的问题就是如何找到这个位置,而KMP算法就是解决这个问题的。

KMP算法匹配字符串

算法核心思想

  • 当进行如下图所示的字符串匹配时,此时指针i和j所指的字符匹配不一致。但是我们可以看到,在前面匹配一致的内容中,开头黄色部分和当前所指元素前方的蓝色部分内容是一样的。
    在这里插入图片描述

  • 由于指针i之前的部分已经匹配成功,因此i前方的蓝色部分也和target前方黄色部分的内容相同,接下来我们应该保持指针i不动,而指针j指向target黄色部分后方的元素,与指针i所指的元素进行比较,继续进行匹配过程。
    在这里插入图片描述

  • 因此,在匹配过程中,如果指针ij所指的内容不一致,而指针i前方紧连着的部分和target开头部分一致的话,我们就保持指针i不变,而指针j则指向开头内容一致部分的下一个元素,继续进行匹配。

  • 而如果不是指针i紧连着的部分与target开头相匹配(即中间隔得有元素),那么即使有相同部分也毫无意义,因为黄色部分和蓝色部分后面的元素不一样,所以无论指针怎么回退,一定会在红框部分出现不匹配的情况。
    在这里插入图片描述
    在这里插入图片描述

  • 所以,除了指针i前方紧连着的部分和target开头部分能匹配上,其他情况的处理方法均一致:指针 i 位置不变,指针 j 回到target的开头继续匹配
    在这里插入图片描述

next数组

  • 讲明白了KMP算法的思想,剩下就只需要解决一个问题:当两个指针所指的元素不匹配时,如何确定指针 j 回退的位置
  • 根据之前的分析我们可以发现,当指针 i 和 j 所指元素不匹配时,指针 i 前方的 j 个字符是和target中指针 j 前方的字符完全一样的。因此指针 j 回退的位置只和target中的字符有关。
    在这里插入图片描述
  • 因此我们可以设定两个指针 k 和 j 分别指向0和1的位置。k 所指的位置即为 j 所指位置在匹配不一致时应该回退的位置。
    在这里插入图片描述
  • k保持不动,而 j 依次向后移动,当两个指针所指的元素一样时,两个元素一起向后移动。
    在这里插入图片描述
  • 当两个指针所指元素再次不一样时,此时 k 所指位置依然是 j 所指元素回退时的位置。因为 j 前方紧挨着的部分仍然和taget开头部分的内容相同。
    在这里插入图片描述
  • 当下次 j 移动至下一位时,k 回到target的开头的位置。
    在这里插入图片描述
  • 以上面的过程直到 j 遍历完整个target为止,而 j 每移动一次,就把 k 的位置记录下来,并将它们储存至一个数组中,该数组就叫做next数组
  • 获取next数组的代码
    /**
     * 获得next数组
     * @param target    目标字符串
     * @return  next数组
     */
    public static int[] getNext(String target){
        int[] next = new int[target.length()];
        int k = -1; //k先指向next数组的-1位置
        int j = 0;
        next[0] = -1;   //默认k在0位置时的回退位置为-1
        while (j<target.length()-1){
            //当j指向元素与k指向元素不匹配,并且j前面的元素与target开头内容匹配时,其回退位置为target开头已匹配内容的下一个元素(即k的位置),因此需要先移动指针再赋值
            if (k==-1 || target.charAt(k)==target.charAt(j)){
                k++;
                j++;
                next[j] = k;
            }else { //如果不匹配时,k指针回退。如果k在0位置,则回退至-1位置。
                k = next[k];
            }
        }
        return next;
    }
  • 代码中next[0]位置为什么回退位置为-1,这点可能有些难以理解,下面将用图解的方式来解释该流程。
  • 初始状态 k 指向-1,j 指向0。除0位置的回退位置为-1,其他所有位置默认初始化位置都为0
    在这里插入图片描述
  • k==-1时,两个指针同时向后移动一位
    在这里插入图片描述
  • 两个指针所指的元素不匹配,k回退至-1(next[0])。
    在这里插入图片描述
  • k==-1条件又成立,两个指针再次同时向后移动一位
    在这里插入图片描述
  • 重复以上步骤,直到 j 指向A
    在这里插入图片描述
  • 此时满足target.charAt(k)==target.charAt(j)条件,两个指针同时向后移动一位,此时 j 指向位置的回退位置为 k 的位置
    在这里插入图片描述
  • 仍然满足target.charAt(k)==target.charAt(j)条件,两个指针同时向后移动一位,此时虽然两个指针指向的元素不匹配,但是根据之前的分析,j 前方紧挨着的内容与开头的内容匹配,其回退位置为开头匹配内容的后一位(即 k 的位置),所以逻辑才是先移动再赋值。
    在这里插入图片描述
  • 下次循环时,既不满足k==-1也不满足target.charAt(k)==target.charAt(j),k回退至next[k](即0位置)
    在这里插入图片描述
  • 下次循环依然两个条件都不满足,k回退next[0](即-1)
    在这里插入图片描述
  • 重复以上步骤直到 j 指向最后一个元素。这里注意!循环条件必须是j<target.length()-1,因为当 j==target.length() 进入循环后,j++以后next[j]会产生数组越界异常
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值