參考:
目錄:
問題
字符串s1 和 字符串s2 的比較算法 ==> 相似度 or 差異性。
主流的算法有兩大類:
- 基於編輯距離 ( Edit Distance),例如:LD算法;
- 基於最長公共子串 ( Longest Common Subsequence),例如:Needleman/Wunsch算法等。
LD算法
LD算法(Levenshtein Distance)又稱為編輯距離算法(Edit Distance):以字符串A通過插入字符、刪除字符、替換字符變成另一個字符串B,其中,這三類操作的總次數可表示兩個字符串的差異。
例如:字符串A:kitten如何變成字符串B:sitting。
第一步:kitten——》sitten。k替換成s
第二步:sitten——》sittin。e替換成i
第三步:sittin——》sitting。在末尾插入g
故kitten和sitting的編輯距離為3。
定義說明:
LD(A,B)表示字符串A和字符串B的編輯距離。若LD(A, B)=0表示字符串A和字符串B完全相同;
Rev(A)表示反轉字符串A;
Len(A)表示字符串A的長度;
A+B表示連接字符串A和字符串B;
A=a1a2……aN,表示A是由a1a2……aN這N個字符組成,Len(A)=N
B=b1b2……bM,表示B是由b1b2……bM這M個字符組成,Len(B)=M
定義LD(i, j) = LD(a1a2……ai, b1b2……bj),其中0≤i≤N,0≤j≤M, ===> LD(N,M)=LD(A,B)
對於1≤i≤N,1≤j≤M,有如下公式(動態規划的思想):
若ai=bj,則LD(i,j) = LD(i-1,j-1) [無需替換 刪除 插入]
若ai≠bj,則LD(i,j) = Min(LD(i-1,j-1)[可以在此基礎上經過替換變成另一字符串], LD(i-1,j),LD(i,j-1)[可以在此基礎上經過插入or刪除變成另一字符串])+1
舉例說明:
A=GGATCGA,B=GAATTCAGTTA,計算LD(A,B)
第一步:初始化LD矩陣
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | |||||||||||
G | 2 | |||||||||||
A | 3 | |||||||||||
T | 4 | |||||||||||
C | 5 | |||||||||||
G | 6 | |||||||||||
A | 7 |
第二步:利用上述的公式,計算第一行
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
G | 2 | |||||||||||
A | 3 | |||||||||||
T | 4 | |||||||||||
C | 5 | |||||||||||
G | 6 | |||||||||||
A | 7 |
第三步,利用上述的公式,計算其余各行
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
G | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 7 | 8 | 9 |
A | 3 | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 8 |
T | 4 | 3 | 2 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
C | 5 | 4 | 3 | 3 | 2 | 2 | 2 | 3 | 4 | 5 | 6 | 7 |
G | 6 | 5 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 4 | 5 | 6 |
A | 7 | 6 | 5 | 4 | 4 | 4 | 4 | 3 | 4 | 4 | 5 | 5 |
則LD(A,B) = LD(7,11) = 5
代碼實現(C++):
class Solution { public: int minDistance(string word1, string word2) { int n = word1.length(); int m = word2.length(); // 有一個字符串為空串 if (n * m == 0) return n + m; // DP 數組 int D[n + 1][m + 1]; // 邊界狀態初始化 for (int i = 0; i < n + 1; i++) { D[i][0] = i; } for (int j = 0; j < m + 1; j++) { D[0][j] = j; } // 計算所有 DP 值 for (int i = 1; i < n + 1; i++) { for (int j = 1; j < m + 1; j++) { int left = D[i - 1][j] + 1; int down = D[i][j - 1] + 1; int left_down = D[i - 1][j - 1]; if (word1[i - 1] != word2[j - 1]) left_down += 1; D[i][j] = min(left, min(down, left_down)); } } return D[n][m]; } };
我們往往不僅僅是計算出字符串A和字符串B的編輯距離,還要能得出它們的匹配結果。
以上面為例A=GGATCGA,B=GAATTCAGTTA,LD(A,B)=5
他們的匹配為:
A:GGA_TC_G__A
B:GAATTCAGTTA
如上面所示,藍色表示完全匹配,黑色表示編輯操作,_表示插入字符或者是刪除字符操作。如上面所示,黑色字符有5個,表示編輯距離為5。
利用上面的LD矩陣,通過回溯,能找到匹配字串。
第一步:定位在矩陣的右下角
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
G | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 7 | 8 | 9 |
A | 3 | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 8 |
T | 4 | 3 | 2 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
C | 5 | 4 | 3 | 3 | 2 | 2 | 2 | 3 | 4 | 5 | 6 | 7 |
G | 6 | 5 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 4 | 5 | 6 |
A | 7 | 6 | 5 | 4 | 4 | 4 | 4 | 3 | 4 | 4 | 5 | 5 |
第二步:回溯單元格,至矩陣的左上角
若ai=bj,則回溯到左上角單元格
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
G | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 7 | 8 | 9 |
A | 3 | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 8 |
T | 4 | 3 | 2 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
C | 5 | 4 | 3 | 3 | 2 | 2 | 2 | 3 | 4 | 5 | 6 | 7 |
G | 6 | 5 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 4 | 5 | 6 |
A | 7 | 6 | 5 | 4 | 4 | 4 | 4 | 3 | 4 | 4 | 5 | 5 |
若ai≠bj,回溯到 (左上角、上邊、左邊) 里值最小的單元格,若有相同最小值的單元格,優先級按照左上角、上邊、左邊的順序
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
G | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 7 | 8 | 9 |
A | 3 | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 8 |
T | 4 | 3 | 2 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
C | 5 | 4 | 3 | 3 | 2 | 2 | 2 | 3 | 4 | 5 | 6 | 7 |
G | 6 | 5 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 4 | 5 | 6 |
A | 7 | 6 | 5 | 4 | 4 | 4 | 4 | 3 | 4 | 4 | 5 | 5 |
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
G | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
G | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 7 | 8 | 9 |
A | 3 | 2 | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 8 |
T | 4 | 3 | 2 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
C | 5 | 4 | 3 | 3 | 2 | 2 | 2 | 3 | 4 | 5 | 6 | 7 |
G | 6 | 5 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 4 | 5 | 6 |
A | 7 | 6 | 5 | 4 | 4 | 4 | 4 | 3 | 4 | 4 | 5 | 5 |
依照上面的回溯法則,回溯到矩陣的左上角
第三步:根據三個不同方向的回溯路徑,寫出匹配字串
若回溯到左上角單元格,將ai添加到匹配字串A,將bj添加到匹配字串B
若回溯到上邊單元格,將ai添加到匹配字串A,將_添加到匹配字串B
若回溯到左邊單元格,將_添加到匹配字串A,將bj添加到匹配字串B
搜索晚整個匹配路徑,匹配字串也就完成了
從上面可以看出,LD算法在不需要計算出匹配字串的話,時間復雜度為O(MN),空間復雜度經優化后為O(M)
不過,如果要計算匹配字符串的話,時間復雜度為O(MN),空間復雜度由於需要利用LD矩陣計算匹配路徑,故空間復雜度仍然為O(MN)。這個在兩個字符串都比較短小的情況下,能獲得不錯的性能。不過,如果字符串比較長的情況下,就需要極大的空間存放矩陣。例如:兩個字符串都是20000字符,則LD矩陣的大小為20000*20000*2=800000000Byte=800MB。在比較長字符串的時候,還有其他性能更好的算法。
Needleman/Wunsch算法
該算法是基於最長公共子串(不一定連續出現)的文本比較算法。
實例說明:
對於字符串A=kitten,字符串B=sitting
最長公共子串為ittn(注:最長公共子串不需要連續出現,但一定是出現的順序一致),最長公共子串長度為4。
定義:
LCS(A,B)表示字符串A和字符串B的最長公共子串的長度,LSC(A,B)=0表示兩個字符串沒有公共部分。
Rev(A)表示反轉字符串A
Len(A)表示字符串A的長度
A+B表示連接字符串A和字符串B
A=a1a2……aN,表示A是由a1a2……aN這N個字符組成,Len(A)=N
B=b1b2……bM,表示B是由b1b2……bM這M個字符組成,Len(B)=M
定義LCS(i,j)=LCS(a1a2……ai,b1b2……bj),其中0≤i≤N,0≤j≤M
對於1≤i≤N,1≤j≤M,有:
若ai=bj,則LCS(i,j)=LCS(i-1,j-1)+1
若ai≠bj,則LCS(i,j)=Max(LCS(i-1,j-1), LCS(i-1,j), LCS(i,j-1)) ==> 類似編輯距離
計算LCS(A,B)的算法有很多,下面介紹的Needleman/Wunsch算法是其中的一種。和LD算法類似,Needleman/Wunsch算法用的都是動態規划的思想。在Needleman/Wunsch算法中還設定了一個權值,用以區分三種操作(插入、刪除、更改)的優先級。在下面的算法中,認為三種操作的優先級都一樣。故權值默認為1。
舉例說明:
A=GGATCGA,B=GAATTCAGTTA,計算LCS(A,B)
第一步:初始化LCS矩陣
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
G | 0 | |||||||||||
G | 0 | |||||||||||
A | 0 | |||||||||||
T | 0 | |||||||||||
C | 0 | |||||||||||
G | 0 | |||||||||||
A | 0 |
第二步:計算矩陣的第一行
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | |||||||||||
A | 0 | |||||||||||
T | 0 | |||||||||||
C | 0 | |||||||||||
G | 0 | |||||||||||
A | 0 |
第三步:計算矩陣的其余各行
G | A | A | T | T | C | A | G | T | T | A | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
T | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
C | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
G | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 5 |
A | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 6 |
則,LCS(A,B)=LCS(7,11)=6
可以看出,Needleman/Wunsch算法實際上和LD算法是非常接近的。故他們的時間復雜度和空間復雜度也一樣。時間復雜度為O(MN),空間復雜度為O(MN)。空間復雜度經過優化,可以優化到O(M),但是一旦優化就喪失了計算匹配字串的機會了。代碼和LD算法幾乎一樣。
代碼(c++):
int needleman(char s1[], char s2[]){ int len1 = s1.size(); int len2 = s2.size(); int i,j; vector<vector<int>> count(len1+1,vector<int>(len2+1, 0) ); for(i=1;i<len1+1;i++){ for(j=1;j<len2+1;j++){ if(s1[i] == s2[j]){ count[i][j] = count[i-1][j-1]+1; } else { count[i][j] = max(max(count[i-1][j-1],count[i][j-1]),count[i-1][j]); } } } return count[len1][len2]; }
LD算法和Needleman/Wunsch算法的回溯路徑是一樣的。這樣找到的匹配字串也是一樣的。
不過,Needleman/Wunsch算法和LD算法一樣,若要找出匹配字串,空間的復雜度就一定是O(MN),在文本比較長的時候,是極為耗用存儲空間的。故若要計算出匹配字串,還得用其他的算法。
計算文本相似度
在給定的字符串A和字符串B,LD(A,B)表示編輯距離,LCS(A,B)表示最長公共子串的長度。
如何來度量它們之間的相似度呢?
不妨設S(A,B)來表示字符串A和字符串B的相似度。那么,比較合理的相似度應該滿足下列性質。
性質一:0≤S(A,B)≤100%,0表示完全不相似,100%表示完全相等
性質二:S(A,B)=S(B,A)
目前,網上介紹的各種相似度的計算,都有各自的不盡合理的地方。
計算公式一:S(A,B)=1/(LD(A,B)+1)
能完美的滿足性質二。
當LD(A,B)=0時,S(A,B)=100%,不過無論LD(A,B)取任何值,S(A,B)>0,不能滿足性質一。
計算公式二:S(A,B)=1-LD(A,B)/Len(A)
當Len(B)>Len(A)時,S(A,B)<0。不滿足性質一。
有人會說,當S(A,B)<0時,強制指定S(A,B)=0就解決問題了。
問題是,S(A,B)=1-LD(A,B)/Len(A),而S(B,A)=1-LD(B,A)/Len(B)。當Len(A)≠Len(B)時,S(A,B)≠S(B,A)。不滿足性質二
還有一個例子可以說明問題
A="BC",B="CD",C="EF"
S(A,B)=1-LD(A,B)/Len(A)=1-2/2=0
S(A,C)=1-LD(A,C)/Len(A)=1-2/2=0
A和B的相似度與A和C的相似度是一樣的。不過很明顯的是B比C更接近A
計算公式三:S(A,B)=LCS(A,B)/Len(A)
這個公式能完美的滿足的性質一
不過當Len(A)≠Len(B)時,S(A,B)≠S(B,A)。不滿足性質二
用一個例子說明問題
A="BC",B="BCD",C="BCEF"
S(A,B)=LCS(A,B)/Len(A)=2/2=100%
S(A,C)=LCS(A,C)/Len(A)=2/2=100%
A和B的相似度與A和C的相似度是一樣的。不過很明顯的是B比C更接近A
上面是網上能找到的三個計算公式,從上面的分析來看都有各自的局限性。
我們看例子:
A=GGATCGA,B=GAATTCAGTTA,LD(A,B)=5,LCS(A,B)=6
他們的匹配為:
A:GGA_TC_G__A
B:GAATTCAGTTA
給出一個新的公式
S(A,B)=LCS(A,B)/(LD(A,B)+LCS(A,B))
這個公式能解決上述三個公式的種種不足。
而LD(A,B)+LCS(A,B)表示兩個字符串A、B的最佳匹配字串的長度。這個是唯一的。
還有注意的是LD(A,B)+LCS(A,B)和Max(Len(A),Len(B))這兩個並不完全相等。
Nakatsu算法
LD算法和LCS算法都是基於動態規划的。它們的時間復雜度O(MN)、空間復雜度O(MN)(在基於計算匹配字符串情況下,是不可優化的。如果只是計算LD和LCS,空間占用可以優化到O(M))。
Nakatsu算法在計算匹配字符串的情況下,有着良好的時間復雜度O(N(M-P))和空間復雜度O(N2),而且在采取適當的優化手段時,可以將空間復雜度優化到O(N)。
定義說明:
1. 設M=Len(A),N=Len(B),不失一般性,假設M≤N。
2. A=a1a2……aM,表示A是由a1a2……aM這M個字符組成
B=b1b2……bN,表示B是由b1b2……bN這N個字符組成
LCS(i,j)=LCS(a1a2……ai,b1b2……bj),其中1≤i≤M,1≤j≤N
3. L(k,i)=Min{j} Where LCS(i,j)=k 表示,所有與字符串a1a2……ai有長度為k的LCS的字符串b1b2……bj中j的最小值(這個可以看后面的例子好好理解)
為了推導L的計算,有下面幾個定理。
定理一:任意的i,1≤i≤M。有L(1,i)<L(2,i)<L(3,i)……
定理二:任意的i,1≤i≤M-1。任意的k,1≤k≤M。有L(k,i+1)≤L(k,i)
定理三:任意的i,1≤i≤M-1。任意的k,1≤k≤M-1。有L(k,i)<L(k+1,i+1)
定理四:如果L(k,i+1)存在,則L(k,i+1)的計算公式為
L(k,i+1)=Min{Min{j},L(k,i)} Where {ai+1=bj And j>L(k-1,i)}
上面四個定理證明從略。可以從上面四個定理推導出L的計算。
故,L的計算公式為
L(1,1)=Min{j} Where {a1=bj}
L(1,i)=Min{Min{j} Where {ai=bj},L(1,i-1)} 此時,i>1
L(k,i)=Min{Min{j} Where {ai=bj And j>L(k-1,i-1)},L(k,i-1)} 此時,i>1,k>1
注:以上公式中,若找不到滿足Where后面條件的j,則j=MaxValue
當i<k時,則L(k,i)=MaxValue
MaxValue是一個常量,表示“不存在”
舉例說明:A=GGATCGA,B=GAATTCAGTTA,計算LCS(A,B)
第一步:初始化L矩陣,表格中V=MaxValue。
i=1 | i=2 | i=3 | i=4 | i=5 | i=6 | i=7 | |
---|---|---|---|---|---|---|---|
k=1 | |||||||
k=2 | V | ||||||
k=3 | V | V | |||||
k=4 | V | V | V | ||||
k=5 | V | V | V | V | |||
k=6 | V | V | V | V | V | ||
k=7 | V | V | V | V | V | V |
第二步:依據上面的計算公式,計算表格的其余單元格
i=1 | i=2 | i=3 | i=4 | i=5 | i=6 | i=7 | |
---|---|---|---|---|---|---|---|
k=1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
k=2 | V | 8 | 2 | 2 | 2 | 2 | 2 |
k=3 | V | V | 11 | 4 | 4 | 4 | 3 |
k=4 | V | V | V | V | 6 | 6 | 6 |
k=5 | V | V | V | V | V | 8 | 7 |
k=6 | V | V | V | V | V | V | 11 |
k=7 | V | V | V | V | V | V | V |
第三步:在矩陣中找尋對角線
1、先找如下的對角線,對角線中有四個單元格的值是V(MaxValue)。不是本算法的合適答案
i=1 | i=2 | i=3 | i=4 | i=5 | i=6 | i=7 | |
---|---|---|---|---|---|---|---|
k=1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
k=2 | V | 8 | 2 | 2 | 2 | 2 | 2 |
k=3 | V | V | 11 | 4 | 4 | 4 | 3 |
k=4 | V | V | V | V | 6 | 6 | 6 |
k=5 | V | V | V | V | V | 8 | 7 |
k=6 | V | V | V | V | V | V | 11 |
k=7 | V | V | V | V | V | V | V |
2、再找右邊的一條對角線。
i=1 | i=2 | i=3 | i=4 | i=5 | i=6 | i=7 | |
---|---|---|---|---|---|---|---|
k=1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
k=2 | V | 8 | 2 | 2 | 2 | 2 | 2 |
k=3 | V | V | 11 | 4 | 4 | 4 | 3 |
k=4 | V | V | V | V | 6 | 6 | 6 |
k=5 | V | V | V | V | V | 8 | 7 |
k=6 | V | V | V | V | V | V | 11 |
k=7 | V | V | V | V | V | V | V |
對角線上的所有單元格的值都不是V(MaxValue)。故本對角線就是算法的求解。
LCS(A,B)就是對角線的長度。故LCS(A,B)=6。
本算法的精妙之處就在於這六個單元格的值所對應的字符串B的字符就是最長公共子串。
最長公共子串:b1b2b4b6b8b11=GATCGA
再將最長公共子串在兩個字符串中搜索一遍,能得出字符串的匹配字串。
A:GGA_TC_G__A
B:GAATTCAGTTA
注:原本以為能很容易得出匹配字符串。不過現在看來還需費一番周折,也是考慮不周。不過已經有大概的解決方案,留待后文介紹。
Nakatsu算法關鍵就是找尋滿足條件對角線(對角線的值沒有MaxValue),故計算的過程可以沿着對角線進行,先計算第一條對角線,看是否滿足對角線條件,滿足則退出,不滿足則繼續計算下一條對角線,直到計算出滿足條件的對角線。
假設LCS(A,B)=P,則一共需要計算M-P+1條對角線,每條對角線的比較次數為N,則Nakatsu算法的時間復雜度為O((M-P+1)N),空間復雜度為O(M2),但由於計算順序的優化,可以將空間復雜度降為O(M),這應該是令人滿意的了。