关于java:我所知道的十大常用算法之字符串匹配问题KMP暴力匹配

7次阅读

共计 4084 个字符,预计需要花费 11 分钟才能阅读完成。

前言需要


后面解说了动静布局的思维,明天咱们要说的是波及字符串匹配问题(KMP)

当初前来看咱们的问题

1. 字符串 str1 = “BBC ABCDAB ABCDABCDABDE”

2. 字符串 str2 = “ABCDABD”

当初要判断 str1 是否含有 str2, 如果存在,就返回第一次呈现的地位, 如果没有,则返回 -1

那么你感觉你会是怎么做的呢?

一、小明同学的思路想法

小明同学:我先找到合乎 str2 字符串的第一个 ’A’ 的地位

小明同学:将 str2 的字符串一个一个的进行匹配否相等

那么小明同学的这种思路呢,咱们称为:暴力匹配

咱们假如当初 str1 匹配到 i 地位,子串 str2 匹配到 j 地位,则会:

1. 匹配胜利(即 str1[i] == str2[j]),则 i ++,j++,持续匹配下一个

2. 匹配失配(即 str1[i]! = str2[j]),令 i = i – (j – 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为 0

public static  int violenceMatch(String str1,String str2){char[] s1 = str1.toCharArray();
     char[] s2 = str2.toCharArray();
     int s1Len = s1.length;
     int s2Len = s2.length;
     int i = 0;// i 索引指向 s1
     int j = 0;// j 索引指向 s2
     while(i<s1Len && j<s2Len){if(s1[i] == s2[j]){
            i++;
            j++;
         }else{
             // 如果匹配失败
             i = i - (j - 1);
             j = 0;
         }
     }
            // 判断是否匹配胜利
     if(j == s2Len){return i - j;}else{return -1;}   
}

咱们能够应用 demo 测试一下这种办法

public static void main(String[] args) {
     String str1 = "BBC ABCDAB ABCDABCDABDE";
     String str2 = "ABCDABD";
     int index = violenceMatch(str1,str2);
     System.out.println(index);
}

运行后果如下:15

二、小王同学的思路想法

小王同学:小明同学的办法尽管是能够实现找到,然而也有不好的中央

1. 当匹配失败时候,就回溯从新进行匹配,这样的办法很简单同时效率很慢






2. 当匹配胜利时候,才间接把 i 的地位 – j 的地位

小王同学:我晓得一个算法,它比拟适宜做这样的事件:KMP 算法

三、什么是 KMP 算法

Knuth-Morris-Pratt 字符串查找算法,简称为“KMP 算法”,罕用于在一个文本串 S 内查找一个模式串 P 的呈现地位

KMP 办法算法就利用

1.之前判断过信息,通过一个 next 数组 保留模式串中前后最长公共子序列的长度

2.每次回溯时,通过 next 数组找到,后面匹配过的地位,省去了大量的计算工夫

ok,什么意思?不懂没关系,咱们一步一步来剖析。

图解剖析暴力匹配引入 KMP

依照小明同学的思路,字符串 Str1 与 字符串 Str2 匹配,不满足则下一个匹配

始终反复匹配,直到 Str1 与 Str2 有一个字符匹配合乎为止

接着比拟下一个字符串,还是匹配合乎

直到遇到 Str1 有一个字符串与 Str2 对应的字符串(D 与空格)不合乎

这个时候就回溯,又进行从新的匹配,从 B 与 A 匹配

其实这是很不明智的,因为此时 BCD 已比拟过了,没有必要做反复的工作,当空格与 D 不匹配的时候,其实你已晓得后面六个字符是“ABCDAB”

KMP 算法的想法是:设法利用这个已知信息,不要把”搜寻地位”移回曾经比拟过的地位,持续把它向后移,这样就进步了效率

步骤图解剖析 KMP 的想法

这里怎么了解呢,一起画图来解释看看,当咱们回溯从新匹配的时候

发现 B 与 A 不合乎 那么进行下一个,C 与 A 匹配

发现 C 与 A 不合乎 那么进行下一个,D 与 A 匹配 …. 以此类推

当 A 与 A 匹配的时候,那么又执行了 B 与 B 比,空格与 C 比,这样的反复

咱们发现没有,BCD 就已比拟过了,所以咱们 不要把”搜寻地位”移回曾经比拟过的地位



步骤图解剖析的问题发现

如果咱们晓得这个 A 和前面的 BCD 是不雷同的,那么这样的比拟就没有意义的

那么有个问题须要思考一下:咱们怎么晓得这些比拟会不雷同呢?

这里就波及到一个局部搜索词:字符串前缀与后缀公共的局部

咱们能够创立一个搜索词(局部匹配表)

在此之前,咱们晓得 0000120 是什么?怎么来的?为什么是这样的

这就要先说说字符串的前缀与后缀了

KMP 的搜索词(局部匹配表)剖析

咱们采纳一个字符串:bread 来剖析前缀、后缀

接下里咱们在以刚刚的式子:ABCDABD 拆开来剖析看看吧

当初你晓得 0000120 是怎么来的吗?每个匹配值你应该晓得对应的是什么

ok,回到刚刚咱们提出的那个问题:咱们怎么晓得这些比拟会不雷同呢?

当初依据部门匹配值 你是有点思路的想法嘛?

四、通过一坨代码来验证思维 KMP

首先咱们先将字符串的局部匹配表弄出来,一起来看看代码是怎么做的?

public static  int[] kmpNext(String dest){

    // 创立一个 next 数组保留部门匹配值
    int[] next = new int[dest.length()];
    
    //dest 的字符个数只有一个,则间接 = 0;next[0] = 0;
    
    for(int i = 1,j = 0;i < dest.length() ; i++){        
        //kmp 算法的思维 相当于是回溯,始终往前比拟
        while(j>0 && dest.charAt(i) != dest.charAt(j)){j = next[j-1];
        }
        if(dest.charAt(i) == dest.charAt(j)){j++;}
        next[i] = j;
    }
    return next;
}

好,这代码是什么状况?为什么 dest.charAt(i) == dest.charAt(j)?

咱们刚刚表格是从字符串的头开始剖析的,还记得吗?代入进去咱们验证一下

这个时候,有的小伙伴就有点懵逼了,为什么要执行 while 循环多一层判断呢?

这里的目标是要回溯,不然的话 j ++ 会造成以下的成果

通过例子来领会 KMP 思维

咱们应用一个例子来 demo 与查找办法领会一下

public static int kmpSearch(String str1,String str2){for(int i = 0,j = 0;i<str1.length();i++){if(str1.charAt(i) == str2.charAt(j)){j++;}
        if(j == str2.length()){return i - j +1;}
    }
    return -1;
}
public static void main(String[] args) {

    String str1 = "BBC ABCDAB ABCDABCDABDE";
    String str2 = "BBC";
    System.out.println("index ="+ kmpSearch(str1,str2));
}

运行后果如下:index = 0

1. 当咱们进行匹配 BBC 的时候,charAt(i) == charAt(j) 是满足的,j++

2. 当 j =3 时,也等于 BBC 字符串的长度,这时咱们代表找到了,这时 i = 2

3. 因为 charAt(2) = C,只须要应用 i – j + 1 则代表开始 index 坐标

问题发现

public static void main(String[] args) {

    String str1 = "BBC ABCDAB ABCDABCDABDE";
    String str2 = "ABCDABD";
    System.out.println("index ="+ kmpSearch(str1,str2));
}

运行后果如下:index = 8

当咱们将 BBC 换成 ABCDABD 的时候,后果就呈现问题了 index = 8

为什么会这样呢?

那是因为咱们没有思考不相等的状况,只思考的相等的状况 ++

1. 当咱们一直的判断满足的时候,str2 字符串只剩下 D 没匹配

2.str1 对应的 i 字符:空格、A、B、C 都不满足,始终到 D 这时 j ++

3. 这时的条件满足了,然而不是咱们想要的后果,所以咱们须要思考不相等

public static int kmpSearch(String str1,String str2,int next[]){for(int i = 0,j = 0;i<str1.length();i++){while(j>0 && str1.charAt(i) != str2.charAt(j)){j = next[j-1];
        }
        if(str1.charAt(i) == str2.charAt(j)){j++;}
        if(j == str2.length()){return i - j +1;}
    }
    return -1;
}
public static void main(String[] args) {
    String str1 = "BBC ABCDAB ABCDABCDABDE";
    String str2 = "ABCDABD";
    int [] next = kmpNext(str2);
    System.out.println("index ="+ kmpSearch(str1,str2,next));
}

五、步骤图演示 KMP 算法

那么接下来咱们应用 KMP 算法图解的形式走一遍

首先先求出 Str2 的局部匹配值

这时,咱们从这里开始解说,一步一步的来,A 与 A 匹配,此时 j ++

下一个匹配,B 与 B 匹配,此时 j ++.. 以此类推..

直到遇到 Str1 有一个字符串与 Str2 对应的字符串(D 与空格)不合乎

此时(D 与空格)不合乎,那么 J 回溯到对应的匹配值,持续匹配

此时(C 与空格)不合乎,那么 J 回溯到对应的匹配值,持续匹配

此时(A 与空格)不合乎,所以接着 Str1 字符串 i++ 进行下一个匹配,匹配上就 j ++ 以此类推


因为此时 BCD 已比拟过了,没有必要做反复的工作,当空格与 D 不匹配的时候,其实已晓得后面六个字符是“ABCDAB”

直到遇到 Str1 有一个字符串与 Str2 对应的字符串(D 与 C)不合乎

此时(D 与 C)不合乎,那么 J 回溯到对应的匹配值,持续匹配

下一个匹配,B 与 B 匹配,此时 j ++.. 以此类推..

此时咱们的回溯是在局部匹配表里进行的,而不是从 Str1 进行回溯,依据图解你能看出小明同学的思路与小王同学的思路区别么?

正文完
 0