首页 > 编程语言 >KMP 字符串搜索算法

KMP 字符串搜索算法

时间:2024-02-24 09:01:11浏览次数:23  
标签:pat charAt int DFA 搜索算法 KMP 字符串 匹配 dfa

KMP 字符串搜索算法是 Knuth、Morris、Pratt 三位在类似的时间段内一起发明的一种字符串搜索算法,该算法的主要原理是利用待查找子串中的某些信息,在匹配失败时能够减少回退的步数

算法原理

假设现在有一个待搜索的字符串 ABABAC,如何利用现有的字符串实现在字符不匹配时尽可能向后调整搜索的开始位置。

目前主要存在两种处理方式:DFA 和部分匹配表

DFA[1]

假设通过原有的搜索字符串已经构建了一个 DFA,他能够帮助我们在不匹配的情况下如何移动匹配的开始位置指针。比如,将 ABABAC 构造对应的 DFA,如下图所示:

313419147.jpg

很直观的形式,当匹配过程中遇到某个不匹配的字符时,可以通过这个不匹配的字符重新定位待搜索字符串的开始位置。比如,如果查找的原字符串内容为 BCBAABACAABABAC,那么可能搜索情况如下图所示:

168078869.jpg

如果存在这样的 DFA,那么在搜索时对应的代码实现如下所示:

public int search(String txt) {
    // 模拟 DFA 处理文本时进行的操作
    int i, j, N = txt.length(), M = pat.length();
    for (int i = 0, j = 0; i < N && j < M; ++i) {
        j = dfa[txt.charAt(i)][j];
    }
    
    if (j == M) return i - M;
    return N;
}

DFA 的使用比较简单,但是如果需要从头开始研究如何构造对应的 DFA,这具有很大的难度。因此,这里仅仅给出构造 DFA 的代码:

public void initDFA() {
    dfa[pat.charAt(0)][0] = 1;
    for (int x = 0, j = 0; j < M; ++j) {
        for (int c = 0; c < R; ++c) {
            dfa[c][j] = dfa[c][x];
        }
        dfa[pat.charAt(j)][j] = j + 1;
        x = dfa[pat.charAt(j)][x];
    }
}

具体构造过程如下图所示:
390738585.jpg

部分匹配表[2]

首先,了解两个概念:前缀和后缀。“前缀” 是指除了字符串的最后一个字符外,子串的全部头部组合;“后缀” 是指除了首字符外,子字符串的所有尾部组合。

部分匹配表的元素是当前位置的 “前缀” 和 “后缀” 最长共有的元素的长度,比如,对于字符串 ABCDABD 来讲,它的 “部分匹配表” 的产生过程如下:

  1. “A” 的前缀和后缀都为空集,公共元素数量为 \(0\)
  2. “AB” 的前缀为 “A”,后缀为 “B”,公共元素数量为 \(0\)
  3. “ABC” 的前缀为 \([A, AB]\),后缀为 \([BC, C]\),公共元素数量为 \(0\)
  4. “ABCD” 的前缀为 \([A, AB, ABC]\),后缀为 \([BCD, CD, D]\),公共元素的数量为 \(0\)
  5. “ABCDA” 的前缀为 \([A, AB, ABC, ABCD]\),后缀为 \([BCDA, BCD, CD, A]\),公共元素为 \([A]\),长度为 \(1\)
  6. “ABCDAB” 的前缀为 \([A, AB, ABC, ABCD, ABCDA]\),后缀为 \([BCDAB, CDBAB, DAB, AB, B]\),共有的公共元素为 \([AB]\),长度为 \(2\)
  7. “ABCDABD‘ 的前缀为 \([A, AB, ABC, ABCD, ABCDA, ABCDAB]\),后缀为 \([BCDABD, CDABD, DABD, ABD, BD, D]\) 公有长度为 \(0\)

因此,最后产生的部分匹配表如下图所示:

image.png

当搜索时,通过

\[ 移动位数 = 已匹配字符数 - 对应的部分匹配值 \]

移动相应的匹配搜索开始位置

实现

  • DFA 的实现如下:

    public class KMP {
        private final String pat;
        private final int[][] dfa;
        private final int R;
        private final int m;
    
        public KMP(String pat) {
            this.pat = pat;
            R = 256;
            m = pat.length();
            dfa = new int[R][m];
            
            dfa[pat.charAt(0)][0] = 1;
            for (int x = 0, j = 1; j < m; ++j) {
                // 复制不匹配的情况下的状态
                for (int c = 0; c < R; ++c) {
                    dfa[c][j] = dfa[c][x];
                }
    
                dfa[pat.charAt(j)][j] = j + 1; // 设置匹配成功时的状态
                x = dfa[pat.charAt(j)][x]; // 更新重启状态
            }
        }
    
        public int search(String txt) {
            int n = txt.length();
            int i, j;
            for (i = 0, j = 0; i < n && j < m; ++i)
                j = dfa[txt.charAt(i)][j];
    
            if (j == m) return i - m;
            return n;
        }
    }
    
  • “部分匹配表“ 的实现如下:

    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.Set;
    
    public class KmpPartTable {
        private final String pat;
        private final int[] next;
    
        public KmpPartTable(String pat) {
            this.pat = pat;
            int m = pat.length();
    
            next = new int[m];
            next[0] = 0;
            for (int i = 1; i < m; ++i) {
                Set<String> set = new HashSet<>();
                // 计算前缀集合
                for (int j = 0; j < i; ++j) {
                    set.add(pat.substring(0, j));
                }
    
                // 计算后缀集合
                for (int j = 1; j <= i; ++j) {
                    String str = pat.substring(j, i + 1);
                    if (set.contains(str) && str.length() > next[i])
                        next[i] = str.length();
                }
            }
    
            System.out.println(Arrays.toString(next));
        }
    
        public int search(String txt) {
            int m = pat.length(), n = txt.length();
            int i, j;
            for (i = 0, j = 0; i < n && j < m;) {
                int k = i, cnt = 0;
                while (k < n && j < m) {
                    if (txt.charAt(k) == pat.charAt(j)) {
                        k++;
                        j++;
                        continue;
                    }
    
                    cnt = (j + 1) - next[j];
                    break;
                }
    
                if (j >= m) break;
                i += cnt;
            }
    
            if (j == m) return i - m;
            return n;
        }
    }
    

参考:

[1] 《算法(第四版)》

[2] https://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html

标签:pat,charAt,int,DFA,搜索算法,KMP,字符串,匹配,dfa
From: https://www.cnblogs.com/FatalFlower/p/18030706

相关文章

  • 【字符串】
    首先创建字符串可以使用单引号、双引号、三单引号和三双引号,其中三引号可以多行定义字符串,Python不支持单字符类型,单字符在Python中也是作为一个字符串使用。我们定义一个变量str='python'语句,它在计算机中的执行顺序是先在内存中创建一个字符串Python,在程序栈寄存器中......
  • 综合练习字符串2
    思路2......
  • 代码随想录 day59 两个字符串的删除操作 编辑距离
    两个字符串的删除操作两种思路如果是以最长公共子序列去理解求出这个子序列长度然后原长减一下就行如果是直接正面求解就是如下解法递推式很好理解初始化意思是当一个串为0长度时需要操作另一个字符串长度次也就是直接赋予下标编辑距离dp[i-1][j-1]+1意......
  • 综合练习字符串1
    ALT+回车:将一句代码分割成两句代码charAt()方法的结果是字符串类型‘1’、‘2’···,需要变成数字1、2···,利用ASCii表(字符串‘0’对应ascii为48,所以数字0=字符串‘0’-48=48-48=0)思路2jdk12后,switch简化......
  • KMP字符串匹配算法
    什么是KMPKMP算法主要应用在字符串匹配问题。因为是由这三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP 核心思想KMP的主要思想是:「当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。」(可......
  • POJ--1961 Period(KMP)
    记录18:262024-2-22http://poj.org/problem?id=1961利用KMP构造next数组,其实next数组就是方便于找到下一个应该比较的字符,或者说是不动目标字符,移动查找字符,这里面利用next数组就可以很快捷的移动这道题是利用next字符,给出证明的结果是当i-next[i]能整除i时,S[1~i-......
  • 24 - 格式化字符串
    格式化字符串笔者认为格式化字符串(formattedstring)在任何语言里都值得单独拿出来做个笔记,因为它是编程中控制输出的重要一环。FormattedStringLiterals(f-string)官网的翻译为“格式化字符串字面值”。比较常用的格式化方法。在字符串前加上前缀f或F,通过{expres......
  • 字符串
    字符串KMP\(p_i\)表示\(s_{1...i}\)的最长真前缀,真后缀(“真”即是不包括原串)相等处理就很简单,每个i就判断能否更新i-1的答案,如不行就i变成\(p_{i-1}\)再处理Fu(i,2,m+n+1){ intj=p[i-1]; while(j>0&&a[i]!=a[j+1])j=p[j]; if(a[i]==a[j+1])p[i]=j+1;}EXKMP\(p_i\)表......
  • linux统计字符串出现次数(linux查询关键字出现的个数了解)
     使用脚本统计字符串出现次数#!/bin/bash#获取要监控的本地服务器IP地址IP=`ifconfig|grepinet|grep-vE'inet6|127.0.0.1'|awk'{print$2}'`echo"IP地址:"$IP#获取cpu总核数cpu_num=`grep-c"modelname"/proc/cpuinfo`echo"cpu总核数:&q......
  • 如何在python中判断一个字符串是否可以转换为数字
    方法一:isdigit()不可识别汉字小数类型str1='1'str2='2.1'str3='三'str4='3.3.3.3'print(str1.isdigit())print(str2.isdigit())print(str3.isdigit())print(str4.isdigit())结果:TrueFalseFalseFalse方法二:isdecim......