算法(2)KMP算法

1.0 問題描述

實現KMP算法查找字符串。算法

2.0 問題分析

  1. 「KMP算法」是對字符串查找「簡單算法」的優化。
  2. 字符串查找「簡單算法」是源字符串每一個字符分別使用匹配串進行匹配,一旦失配,模式串下標歸0,源字符串下標加1。
  3. 能夠很容易計算字符串查找「簡單算法」的時間複雜度爲O(m*n),其中n表示源字符串長度,m表示匹配串長度。
  4. KMP算法的匹配方式同簡單算法的匹配方式相同,只不過在失配的時候,模式串下標不歸零,反而會根據模式串自身的重複信息,迴歸到一個大於0的下標。從而減小匹配次數。
  5. 那麼失配時候,模式串下標迴歸的位置須要提早計算好。計算的方法是,匹配串中,每一個位置的前綴字符串「頭部」和「尾部」的重複字符數即爲失配下標迴歸位置。
    • 假設匹配串是:ababc
    • 下標0的字符是「a」,它的前綴是空字符串,因此迴歸位置爲0
    • 下標1的字符是「b」,它的前綴是「a」,數量不足2個,迴歸位置也是0
    • 下標2的字符是「a」,他的前綴是「ab」,沒有重複的頭部和尾部,迴歸位置是0
    • 下標3的字符是「b」,它的前綴是「aba」,有重複的頭部和尾部,重複子串是「a」,長度爲1,迴歸位置是1
    • 下標3的字符是「c」,它的前綴是「abab」,有重複的頭部和尾部,重複子串是「ab」,長度爲2,迴歸位置是2
  6. 根據上一條,咱們能夠計算出一個next數組用於保存失配時模式串下標應該回到哪裏的下標序列。
  7. 計算好next數組後,就能夠使用「簡單算法」的邏輯進行匹配了,只不過不一樣的是,一旦失配,匹配串下標不是迴歸到0,而是根據next數組決定。

3.0 代碼實現

3.1使用swift實現

///簡單查找
func simpleSearch(_ src: String, _ mode: String, _ start: Int) -> Int {
    var i = start;
    while i < src.count {
        var j = 0;
        while j < mode.count {
            if(i + j >= src.count || src[i + j] != mode[j]){
                break;
            }
            j += 1;
        }
        if j == mode.count{
            return i;
        }
        i += 1;
    }
    return -1;
}

///kmp字符串查找
func kmpSearch(_ src: String, _ mode: String, _ start: Int) -> Int {
    //計算next
    var next: [Int] = [0, 0];
    //自身匹配,i表示主下標,j表示匹配下標
    var i = 2, j = 0;
    while i < mode.count {
        if(mode[i - 1] == mode[j]){
            next[i] = j + 1;
            i += 1;
            j += 1;
        }else{
            if(j == 0){
                i += 1;
            }
            //已經匹配的部分有可能會有首尾相同的狀況
            j = next[j];
        }
    }
    
    //算法同自身匹配
    i = start;
    j = 0;
    while i < src.count {
        if(src[i] == mode[j]){
            i += 1;
            j += 1;
            
            if(j == mode.count){
                return i - mode.count;
            }
        }else{
            if(j == 0){
                i += 1;
            }
            j = next[j];
        }
    }
    
    return -1;
}
複製代碼

3.2使用js實現

function simpleSearch(src, mode, start){
    for(let i = start; i < src.length; i++){
        let miss = false;
        for(let j = 0; j < mode.length; j++){
            if(i + j >= src.length){
                miss = true;
                break;
            }else if(src.charAt(i + j) != mode.charAt(j)){
                miss = true;
                break;
            }
        }
        if(!miss){
            return i;
        }
    }
    return -1;
}

function kmpSearch(src, mode, start){
    let next = [0, 0];
    let i = 2; 
    let j = 0;
    while (i < mode.length) {
        if(mode.charAt(i - 1) == mode.charAt(j)){
            j++;
            i++;
            next[i-1] = j;
        }else{
            if(j == 0){
                i++;
            }
            j = next[j];
        }
    }

    i = start;
    j = 0;
    let found = false;
    while (i <= src.length) {
        if(src.charAt(i) == mode.charAt(j)){
            i++;
            j++;
            if(j == mode.length){
                found = true;
                break;
            }
        }else{
            if(j == 0){
                i++;
            }
            j = next[j];
        }
    }

    if(found){
        return i - mode.length;
    }else{
        return -1;
    }
}
複製代碼

4.0 複雜度分析

  1. 咱們選取複雜度最大的一種模型來分析,即:模式串全部字符都相同,源串和模式串老是在最後一位失配。
  2. 令源串爲 「aaaabaaaabaaaab」,匹配串爲 「aaaaa」。
  3. 匹配串的next數組爲:[0,0,1,2,3]
  4. 首次匹配會在第5位失配,比較次數爲5。
  5. 模式串回到3,進行一次比較即會失配,比較次數爲1。
  6. 模式串回到1,進行一次比較即會失配,比較次數爲1。
  7. 模式串回到0,同時源串下標加1。此時源串下標爲6,匹配串下標爲0。根據源串特色,此時會不斷重複4-7的過程。
  8. 根據上述分析,咱們推到通常狀況,長度爲n的源串,長度爲m的匹配串,會造成一個週期性匹配,週期次數爲n/m。
  9. 很容易看到一個週期內的複雜度小於O(2m),因此總體複雜度小於 O(2m*n/m)=O(2n),即複雜度爲O(n)。
  10. 計算next數組的算法和匹配算法相同,所以複雜度爲O(m)。
  11. 因此KMP算法總體複雜度爲O(m+n)。
相關文章
相關標籤/搜索