字符串

字符串

定义:

串(string)是由零个或多个字符组成的有限序列,又名叫字符串。

  • 一般地,由n个字符串构成的串记作: S="a0a1......an-1"(n≥0),其中a_i(1≤i≤n)
    • n是一个有限的数值
    • 串一般记为S是串的名称,用双引号或单引号括起来的字符序列是串的值(引号不属于串的内容)。
    • 可以是字母、数字或其他字符,i就是该字符在串中的位置。串中的字符数目n称为串的长度,n是一个有限的数值。

特征:

  • 结构简单,(以二进制字符为例,仅 0,1两种字符构成)
  • 规模庞大,
  • 元素(字符)重复率高

子串

在对字符串S做处理时,经常需要取出其中某一连续的片段,称为S的子串(substring)

  • 具体地,由串S中起始于位置i的连续k个字符组成的子串记作
    substr(S,i,k) = "aiai+1...ai+k-1",0≤i 〈 n,0≤k
  • 前缀 prefix(S,k) = substr(S,0,k);
  • 后缀 suffix(S,k) = substr(S,n-k,k)
  • 空格串:只包含空格的串。

结论

  1. 空串是任何字符串的子串,也是任何字符串的前缀和后缀
  2. 任何字符串都是自己的子串,也是自己的前缀和后缀。此类子串,前缀和后缀分别称为平凡子串,平凡前缀,平凡后缀
  3. 字符串本身之外的所以非空子串,前缀,后缀,分别称为真子串,真前缀,真后缀

判定

一对字符串S="a0a1......an-1" 和 T="b0b1......bm-1",当且仅当二者长度相等(n=m),
且对应的字符分别相等(对任何0≤i 〈n,都有ai = bi)

模式匹配算法

朴素的模式匹配算法(BF(Brute-Force)算法)

  • 基本思想是:
    1. 从主串的第一个字符起与子串的第一个字符进行比较,若相等,则继续逐对字符进行后续的比较;
    2. 若不相等,则从主串第二个字符起与子串的第一个字符重新比较,以此类推,
      直到子串中每个字符依次和主串中的一个连续的字符序列相等为止,此时称为匹配成功。
    3. 如果不能在主串中找到与子串相同的字符序列,则匹配失败。
  • BF算法是最原始、最暴力的求解过程,但也是其他匹配算法的基础

public static void bruteForce(String s, String p) {
        int index = -1;// 成功匹配的位置
        int sLength = s.length();// 主串长度
        int pLength = p.length();// 子串长度
        if (sLength < pLength) {
            System.out.println("Error.The main string is greater than the sub string length.");
            return;
        }
        int i = 0;
        int j = 0;
        while (i < sLength && j < pLength) {
            if (s.charAt(i) == p.charAt(j)) {// 判断对应位置的字符是否相等
                i++;// 若相等,主串、子串继续依次比较
                j++;
            } else {// 若不相等
                i = i - j + 1;// 主串回溯到上次开始匹配的下一个字符
                j = 0;// 子串从头开始重新匹配
            }
        }
        if (j >= pLength) {// 匹配成功
            index = i - j;
            System.out.println("Successful match,index is:" + index);
        } else {// 匹配失败
            System.out.println("Match failed.");
        }
    }

KMP

P模式匹配算法,是一个效率非常高的字符串匹配算法。其全称是Knuth–Morris–Pratt string searching algorithm

  • 其核心思想就是主串不回溯,模式串尽量多地往右移动
  • 即: N(P, j) = {t | prefix(prefix(P, j), t) = suffix(prefix(P, j), t), 0≤ t 〈 j } shift = j -t shift就是本次移动
    地最大步长

构建next表

  1. 因为空串是任何非空串的真字串,真前缀,真后缀,故只要 j > 0,则必有 0 ∈ N(P, j)
    此时N(P, j) 必非空,从而保证“在其中取最大值”这一操作可行。反之。若j=0,则前缀prefix(P, j)
    本身就是空串,它没有真子串,于是必有集合N(P, j) = φ。此种情况下,next[0] 该如何定义呢?
  2. 按照串匹配算法的构思,倘若某轮迭代中第一对字符即失配,则应该将模式串P直接右移一位,然后从其首字符继续下一轮对比
    就实际效果而言,这一处理方法完全等价于“令next[0] = -1”。
  3. 下面以模式串 "ABCDABD" 为例来详细说明是如何构建next表
P = ABCDABD
j = 0, prefix(P, 0) = φ
next[0] = -1;//规定如此

P = ABCDABD
j = 1, prefix(P, 1) = A
真前缀: φ
真后缀: φ
next[1] = 0;

P = ABCDABD
j = 2, prefix(P, 2) = AB
真前缀: A
真后缀: B
next[2] = 0;

P = ABCDABD
j = 3, prefix(P, 3) = ABC
真前缀: A,AB
真后缀: BC,C
next[3] = 0;

P = ABCDABD
j = 4, prefix(P, 4) = ABCD
真前缀: A,AB,ABC
真后缀: BCD,CD,D
next[4] = 0;

P = ABCDABD
j = 5, prefix(P, 5) = ABCDA
真前缀: A,AB,ABC,ABCD
真后缀: BCDA,CDA,DA,A
next[5] = 1;

P = ABCDABD
j = 6, prefix(P, 6) = ABCDAB
真前缀: A,AB,ABC,ABCD,ABCDA
真后缀: BCDAB,CDAB,DAB,AB,B
next[6] = 2;

得出next表为:
[-1, 0, 0, 0, 0, 1, 2]

接下来就是代码实现:

//第一步构造next表
public static int[] buildNext(String p){
        //构建next表就是查找真前缀 == 真后缀的最大长度,以获取模式串尽量多地往右移动
        int[] N = new int[p.length()];
        int m = p.length(),j = 0;//主串位置
        int t = N[0] = -1;//字串位置
        while(j < m -1){
            if(t < 0 || p.charAt(j) == p.charAt(t)){
                j++;t++;
                N[j] = t;
            }else{//失配
                t = N[t];
            }
        }
        return N;
    }
//第二步利用next表尽量多地往右移动
public static void kmp(String s, String p) {
        int[] next = buildNext(p);// 调用next(String p)方法
        int index = -1;// 成功匹配的位置
        int sLength = s.length();// 主串长度
        int pLength = p.length();// 子串长度
        if (sLength < pLength) {
            System.out.println("Error.The main string is greater than the sub string length.");
            return;
        }
        int i = 0;
        int j = 0;
        while (i < sLength && j < pLength) {
            /*
             * 如果j = -1, 或者当前字符匹配成功(即s.charAt(i) == p.charAt(j)), 都令i++,j++
             * 这两个条件能否交换次序?
             */
            if (j == -1 || s.charAt(i) == p.charAt(j)) {
                i++;
                j++;
            } else {
                /*
                 * 如果j != -1,且当前字符匹配失败, 则令 i 不变,j = next[j], next[j]即为j所对应的next值
                 */
                j = next[j];
            }
        }
        if (j >= pLength) {// 匹配成功
            index = i - j;
            System.out.println("Successful match,index is:" + index);
        } else {// 匹配失败
            System.out.println("Match failed.");
        }
    }
posted @ 2021-03-12 23:36  AronJudge  阅读(740)  评论(0编辑  收藏  举报