数据结构与算法--KMP字符串匹配

335 阅读4分钟

KMP介绍

给定一个主串(以 S 代替)和模式串(以 P 代替),要求找出 P 在 S 中出现的位置,此即串的模式匹配问题。

Knuth-Morris-Pratt 算法(简称 KMP)是解决这一问题的常用算法之一,这个算法是由高德纳(Donald Ervin Knuth)和沃恩·普拉特在 1974 年构思,同年詹姆斯·H·莫里斯也独立地设计出该算法,最终三人于 1977 年联合发表。

next数组推导

在求解next数组的4种情况:

  1. 默认next[1] = 0;
  2. 当 i=0时,表示当前的比应该从头开始.则i++,j++,next[j] = i;
  3. 当 T[i] == T[j] 表示2个字符相等,则i++,j++.同时`next[j] = i;
  4. 当 T[i] != T[j]` 表示不相等,则需要将i 退回到合理的位置. 则 i = next[i]; 0123456 /011111 T[i] != T[j] 进行回退. [i,j]范围有没有前缀和后缀; [next[i],j] 范围有没有前缀和后缀; 最终直到[0,j]范围有没有前缀和后缀;

next优化nextval数组推导

在求解nextVal数组的5种情况:

  1. 默认next[1] = 0;
  2. T[i] == T[j] 且++i,++j 后 T[i] 依旧等于 T[j] 则 nextval[i] = nextval[j] 3. i = 0, 表示从头开始i++,j++后,且T[i] != T[j] 则nextVal = j;
  3. T[i] == T[j] 且++i,++j 后 T[i] != T[j] ,则nextVal = j;
  4. 当 T[i] != T[j]` 表示不相等,则需要将i 退回到合理的位置. 则 i = next[i];

代码实现

//----KMP 模式匹配算法---
//1.通过计算返回子串T的next数组;
//注意字符串T[0]中是存储的字符串长度; 真正的字符内容从T[1]开始;
void get_next(String T,int *next) {
    int i,j;
    j = 1;
    i = 0;
    next[1] = 0;
    //abcdex
    //遍历T模式串, 此时T[0]为模式串T的长度;
    //printf("length = %d\n",T[0]);
    while (j < T[0]) {
        //printf("i = %d j = %d\n",i,j);
        if(i ==0 || T[i] == T[j]){
            //T[i] 表示后缀的单个字符;
            //T[j] 表示前缀的单个字符;
            ++i;
            ++j;
            next[j] = i;
            //printf("next[%d]=%d\n",j,next[j]);
        }else
        {
            //如果字符不相同,则i值回溯;
            i = next[i];
        }
    }
}

//输出Next数组值
void NextPrint(int next[],int length) {
    int i;
    for(i=1;i<=length;i++)
        printf("%d",next[i]);
    printf("\n");
}

int count = 0;
//KMP 匹配算法(1)
//返回子串T在主串S中第pos个字符之后的位置, 如不存在则返回0;
int Index_KMP(String S,String T,int pos) {
    //i 是主串当前位置的下标准,j是模式串当前位置的下标准
    int i = pos;
    int j = 1;
    //定义一个空的next数组;
    int next[MAXSIZE];
    //对T串进行分析,得到next数组;
    get_next(T, next);
    count = 0;
    //注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
    //若i小于S长度并且j小于T的长度是循环继续;
    while (i <= S[0] && j <= T[0]) {
        
        //如果两字母相等则继续,并且j++,i++
        if(j == 0 || S[i] == T[j]){
            i++;
            j++;
        }else{
            //如果不匹配时,j回退到合适的位置,i值不变;
            j = next[j];
        }
    }
    if (j > T[0]) {
        return i-T[0];
    }else{
        return -1;
    }
}

//KMP 匹配算法(2)
//求模式串T的next函数值修正值并存入nextval数组中;
void get_nextVal(String T,int *nextVal) {
    int i,j;
    j = 1;
    i = 0;
    nextVal[1] = 0;
    while (j < T[0]) {
        if (i == 0 || T[i] == T[j]) {
            ++j;
            ++i;
            //如果当前字符与前缀不同,则当前的j为nextVal 在i的位置的值
            if(T[i] != T[j])
                nextVal[j] = i;
            else
            //如果当前字符与前缀相同,则将前缀的nextVal 值赋值给nextVal 在i的位置
                nextVal[j] = nextVal[i];
        }else{
            i = nextVal[i];
        }
    }
}

//KMP 匹配算法(2)
//返回子串T在主串S中第pos个字符之后的位置, 如不存在则返回0;
int Index_KMP2(String S,String T,int pos) {
    //i 是主串当前位置的下标准,j是模式串当前位置的下标准
    int i = pos;
    int j = 1;
    //定义一个空的next数组;
    int next[MAXSIZE];
    //对T串进行分析,得到next数组;
    get_nextVal(T, next);
    count = 0;
    //注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
    //若i小于S长度并且j小于T的长度是循环继续;
    while (i <= S[0] && j <= T[0]) {
        
        //如果两字母相等则继续,并且j++,i++
        if(j == 0 || S[i] == T[j]){
            i++;
            j++;
        }else{
            //如果不匹配时,j回退到合适的位置,i值不变;
            j = next[j];
        }
    }
    if (j > T[0]) {
        return i-T[0];
    }else{
        return -1;
    }
}

swift

//----KMP 模式匹配算法---
//1.通过计算返回子串T的next数组;
func next(_ s:String) -> Array<Int> {
    var next = Array<Int>(repeating: 0, count: s.count + 1)
    next[0] = -1;
    var i = -1, j = 0
    while j < s.count {
        if i == -1 || s[s.index(s.startIndex, offsetBy: i)] == s[s.index(s.startIndex, offsetBy: j)] {
            i += 1
            j += 1
            next[j] = i
        } else {
            i = next[i]
        }
    }
    return next
}

func nextval(_ s:String) -> Array<Int> {
    var nextval = Array<Int>(repeating: 0, count: s.count + 1)
    nextval[0] = -1;
    var i = -1, j = 0
    while j < s.count {
        if i == -1 || s[s.index(s.startIndex, offsetBy: i)] == s[s.index(s.startIndex, offsetBy: j)] {
            i += 1
            j += 1
            if j < s.count && s[s.index(s.startIndex, offsetBy: i)] == s[s.index(s.startIndex, offsetBy: j)] {
                nextval[j] = nextval[i]
            } else {
                nextval[j] = i
            }
        } else {
            i = nextval[i]
        }
    }
    return nextval
}

func KMP(_ s:String, t:String) -> Int {
    let nextArray = next(t);
//    let nextArray = nextval(t);
    var i = 0, j = 0;
    while i < s.count && j < t.count {
        if j == -1 || s[s.index(s.startIndex, offsetBy: i)] == t[t.index(t.startIndex, offsetBy: j)] {
            i += 1
            j += 1
        } else {
            j = nextArray[j]
        }
    }
    if j >= t.count {
        return i - t.count
    }
    return -1
}