1. 程式人生 > 實用技巧 >C# 資料庫查詢返回連續行

C# 資料庫查詢返回連續行

KMP演算法

1.應用場景

1.1 介紹:

KMP 演算法(Knuth-Morris-Pratt 演算法)是一個著名的字串匹配演算法,效率很高,但是確實有點複雜。

1.2 應用:

在實際生活中,字串查詢是非常常見的,在一段資訊中匹配到你需要尋找的資訊(就是在一段字串找到需要匹配的字串),那麼怎樣去尋找呢?

1.2.1 與尋常的暴力匹配尋找,它有怎樣的優化

我們運用暴力匹配的時候,每次都匹配了很多此無意義的字元,什麼意思呢? 請看:


我們從這裡開始匹配

因為主串【i】和匹配串【j】一直不相等,所以主串右移

現在主串【i】和匹配串【j】相等開始匹配,i和j一起右移

現在它們又不相等了,所以

按照暴力匹配的演算法思想: 此時應該將i回溯,將i回到原來開始的位置的下一個位置,j歸零。程式碼中的表現就是 i-(j-1),j=0,但這樣效率實在太差,我們已經知道關於字串一部分資訊了,我們怎樣運用這部分加速匹配資訊呢?這裡就要用到
KMP演算法

2 演算法思想介紹

演算法簡介

主要是消除了主串指標的回溯,之後效率就提高

加速匹配資訊的介紹和提取

KMP演算法主要是通過消除主串指標的回溯來提高匹配的效率的,那麼,它是則呢樣來消除回溯的呢?就是因為它提取並運用了加速匹配的資訊
  這種資訊就是對於每模式串t的每個元素tj,都存在一個實數k,使得模式串t開頭的k個字元(t0t1…tk-1)依次與tj前面的k(tj-ktj-k+1…tj-1,這裡第一個字元tj-k最多從t1開始,所以k<j)個字元相同

如果這樣的k有多個,則取最大的一個模式串t中每個位置j的字元都有這種資訊採用next陣列表示,即next[j]=MAX{k}。

加速資訊就是next陣列

next陣列

怎樣優化

遇到像上圖的情況怎樣優化

如果我們自己用人腦去思考,用眼睛去看:

到這裡我們就可以得到了基本思路了,又該怎樣去實現它呢?

next陣列實現(語言層面)

實在不好描述,懂得都懂,因為我們現實生活比就是這樣比,如果硬要描述,請看下面:

到了這裡我們可以大概看出一點東西,當匹配失敗時,j要移動的下一個位置k。存在著這樣的性質:最前面的k個字元和j之前的最後k個字元是一樣的。

如果用數學公式來表示是這樣的

P[0 ~ k-1] == P[j-k ~ j-1]

數學描述

當T[i]!=P[j]時
有T[i-j~i-1]==P[0~j-1]
由P[0~k-1]==P[j-k~j-1]
必然:T[i-k~i-1]==P[0~k-1]

字首表描述

0 A
0 AB
0 ABC
1 ABCA(這裡就是最大字首A和最大字尾B)
2 ABCAB(類似:AB)
0 ABCABA(AB)

最大字首和最大字尾的解釋:比如ABCABA的字首有{A,AB,ABC,ABCA,ABCAB}
最大字尾:{A,BA,ABA,CABA,BCABA}
這裡解釋下,因為自己最開始也理解不對

next陣列的演算法

在匹配串的每個位置都有可能發生不匹配,所以要計算沒一個k(其實最後一個可以不算,但也可以,這裡我們算,其實感覺不算更好我們首先要知道next【j】=k,表示當它們不匹配時,j指標的下一個位置 (非常重要),還有就是,因為字串是從下標0開始的,所以j位置之前的最大重複子串(最大相同前後綴)注意是j位置之前

public int[] getNext(char[]parm){ 
      int next[]=new int[parm.length]; 
      next[0]=0; 
      int i=0; 
      int k=1; 
      while(k<parm.length){
           if(parm[i]==parm[k]){
           next[k++]=++i; 
            }else{ if(i>0){
            i=next[i-1];
            }else {
            next[k++]=i;
            }
              } 
              return next;}
              } 


記住最重要的東西:next陣列中儲存的是當它們不匹配時,j指標要移動的位置

2種情況(匹配和不匹配)

匹配

當它們匹配的時候怎麼求next陣列呢?這裡我們可以發現用字首表看出來

比如:ABAB
next【3】=1
next【4】=2
所以當它們匹配的時候就有:next【j+1】=next【j】+1

證明:
因為在P[j]之前已經有P[0~k-1]==p[j-k~j-1]。(next[j]==k)
這時候現有P[k]==P[j],我們是不是可以得到P[0~k-1]+P[k]==p[j-k~j-1]+P[j]。
即:P[0~k]==P[j-k~j],即next[j+1]==k+1==next[j]+1。
p為匹配串

不匹配

這裡也是next陣列最重要的部分

k=next【k-1】(我這裡用的是k=next【k-1】,很多部落格時用到的是k=next【k】,
因為我們求得是k位置之前的最大重複子串(最大相同前後綴,所以儲存的位置可能不同,這和後面的一個解釋也相互驗證)

其實就是當k和j不相同的時候,就把k之前的字串看做要匹配的串,後面的字串看做主串,所以當它們不匹配的時候,就要和kmp演算法的處理階段一樣,把它們移動到可能的地方.

其實可以用數學歸納法證明出來,
但解釋語言比較長,現在天色已晚,後面有時間再補

KMP演算法的實現

程式碼實現

class Solution {
    public int strStr(String haystack, String needle) {
                if("".equals(needle)){
                        return 0;
                }
           char[] noodleArr=haystack.toCharArray();
           char[] parmsArr=needle.toCharArray();
           int i=0;
           int j=0;
           int[] next=getNext(parmsArr);
           while(i<noodleArr.length && j<parmsArr.length){
                   if(noodleArr[i]==parmsArr[j]){
                           i++;
                           j++;
                   }else{ 
                           if(j>0){
                           j=next[j-1];
                           }else{
                                   i++;
                           }
                   }
           }
           if(j==needle.length()){
                   return i-j;
           }else{
                   return -1;
           }
    }
    public int[] getNext(char []parm){
          int[] next=new int[parm.length];
          next[0]=0;
          int k=1;
          int j=0;
          while(k<parm.length){
                  if(parm[k]==parm[j]){
                          next[k++]=++j;
                  }else{
                          if(j>0){
                                  j=next[j-1];
                          }else{
                                  next[k++]=j;
                          }
                  }
          }
          return next;
  }
}

一些對程式碼的補充說明

因為一些小細節的實現可能和其他部落格不一樣,所以特此進行說明

  1. 首先我並沒有把next【0】=-1

造成的結果是:
我的next陣列每個位置儲存的是當前位置的最大重複子串。但每次不匹配的時候,
要移動到的位置就是不匹配位置k之前的最大子串長度 (注意是k之前)
所以我的是k=next[k-1]

2.下面這段程式碼的一些小細節:

最開始我是這樣寫的(注意劃紅線的地方)
if(j>0){
j=next[j-1];
}else if(j==0){
i++;
}

最開始真的沒仔細思考,沒想到當j=next[j-1]=0,之後造成了又進去了一次,其實這次完全是沒必要的,這裡就是當j=0時,i++,就是當它們一開始(i=0,j=0)就不匹配的時候,

下面是leetcode不符合的情況

一些知道情況的錯誤

這就是上面程式碼說明中的第2種情況

這是沒考慮當要匹配的字串長度為0的情況

一些不知道情況的錯誤

自己也一臉矇蔽,當時也沒處理(debug)因為其他事耽擱了

KMP時間複雜度

建設字元主串的長度為m,模式串為n

有了程式碼過程就很容易得到時間複雜度 我們得到結果的條件是,字元主串或者模式串讀完(注意是讀完,不是讀到)結尾,所以很容易就知道是O(N+M)

leetcode題目

思考和問題

為什麼我用kmp演算法表現還差些?是它用測試用例很極端嗎?

暴力演算法:

KMP演算法:

這個題目還有個捷徑:直接用Java的內建函式(indexof())

問題:

1. 發現自己對Java基礎知識(比如集合)有點遺忘啊(過度依賴idea的提示功能了)
2. 自己應該以計算機的方式去思考