Levenshtein Distance 算法,又叫 Edit Distance 算法,是指兩個字符串之間,由一個轉成另一個所需的最少編輯操作次數。許可的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符。一般來說,編輯距離越小,兩個串的相似度越大。
算法實現原理圖解:
a.首先是有兩個字符串,這里寫一個簡單的 abc 和 abe
b.將字符串想象成下面的結構。
A 處 是一個標記,為了方便講解,不是這個表的內容。
abc | a | b | c | |
abe | 0 | 1 | 2 | 3 |
a | 1 | A處 | ||
b | 2 | |||
e | 3 |
c.來計算 A 處 出得值
它的值取決於:左邊的 1、上邊的 1、左上角的 0。
按照 Levenshtein distance 的意思:
上面的值加 1 ,得到 1+1=2 ,
左面的值加 1 ,得到 1+1=2 ,
左上角的值根據字符是否相同,相同加 0 ,不同加 1 。A 處由於是兩個 a 相同,左上角的值加 0 ,得到 0+0=0 。
然后從我們上面計算出來的 2,2,0 三個值中選取最小值,所以 A 處的值為 0 。
d.於是表成為下面的樣子
abc | a | b | c | |
abe | 0 | 1 | 2 | 3 |
a | 1 | 0 | ||
b | 2 | B處 | ||
e | 3 |
在 B 處 會同樣得到三個值,左邊計算后為 3 ,上邊計算后為 1 ,在 B 處 由於對應的字符為 a、b ,不相等,所以左上角應該在當前值的基礎上加 1 ,這樣得到 1+1=2 ,在(3,1,2)中選出最小的為 B 處的值。
e.於是表就更新了
abc | a | b | c | |
abe | 0 | 1 | 2 | 3 |
a | 1 | 0 | ||
b | 2 | 1 | ||
e | 3 | C處 |
C 處 計算后:上面的值為 2 ,左邊的值為 4 ,左上角的:a 和 e 不相同,所以加 1 ,即 2+1 ,左上角的為 3 。
在(2,4,3)中取最小的為 C 處的值。
f.於是依次推得到
a | b | c | ||
0 | 1 | 2 | 3 | |
a | 1 | A處 0 | D處 1 | G處 2 |
b | 2 | B處 1 | E處 0 | H處 1 |
e | 3 | C處 2 | F處 1 | I處 1 |
I 處: 表示 abc 和 abe 有1個需要編輯的操作( c 替換成 e )。這個是需要計算出來的。
同時,也獲得一些額外的信息:
A處: 表示a 和a 需要有0個操作。字符串一樣
B處: 表示ab 和a 需要有1個操作。
C處: 表示abe 和a 需要有2個操作。
D處: 表示a 和ab 需要有1個操作。
E處: 表示ab 和ab 需要有0個操作。字符串一樣
F處: 表示abe 和ab 需要有1個操作。
G處: 表示a 和abc 需要有2個操作。
H處: 表示ab 和abc 需要有1個操作。
I處: 表示abe 和abc 需要有1個操作。
g.計算相似度
先取兩個字符串長度的最大值 maxLen,用 1-(需要操作數 除 maxLen),得到相似度。
例如 abc 和 abe 一個操作,長度為 3 ,所以相似度為 1-1/3=0.666 。
以上就是整個算法的推導過程,但是至於為什么能算出相似度,還是不太懂。而且我發現這個算法有個很坑的地方,就是有時候根據算法推算出的結果,會和我們想象中的不一樣:
例如:字符串 abcd 和字符串 def ,根據算法算出來的相似度為 0,可是明明是由一個相同字符 d 的,至少應該是有一定相似度,即使很相似度很低,但是也不應該為0才對。但是字符串 abcd 和字符串 aert ,同樣是只有一個相同字符 a ,但是根據算法算出來的相似度卻為 0.25 。
根據最開始對算法的介紹,使用替換、刪除、插入這三種操作方式,字符串 abcd 和字符串 aert 最少需要 3 步就能完成轉變,而字符串 abcd 和字符串 def 卻最少需要4步才能完成轉變,兩個字符串的最大長度才為 4 ,而完成轉換卻至少需要 4 步,相似度為 0 好像也能說的通,但是從表面來看,明明有一個相同的字符d,相似度為0,讓我覺得很怪異,所以我認為再使用的時候需要慎重。
下面是我的 C# 代碼實現:
1 using UnityEngine; 2 using System.Collections; 3 using System; 4 5 public class EditorDistance 6 { 7 /// <summary> 8 /// 比較兩個字符串的相似度,並返回相似率。 9 /// </summary> 10 /// <param name="str1"></param> 11 /// <param name="str2"></param> 12 /// <returns></returns> 13 public static float Levenshtein(string str1, string str2) 14 { 15 char[] char1 = str1.ToCharArray(); 16 char[] char2 = str2.ToCharArray(); 17 //計算兩個字符串的長度。 18 int len1 = char1.Length; 19 int len2 = char2.Length; 20 //建二維數組,比字符長度大一個空間 21 int[,] dif = new int[len1 + 1, len2 + 1]; 22 //賦初值 23 for (int a = 0; a <= len1; a++) 24 { 25 dif[a, 0] = a; 26 } 27 for (int a = 0; a <= len2; a++) 28 { 29 dif[0, a] = a; 30 } 31 //計算兩個字符是否一樣,計算左上的值 32 int temp; 33 for (int i = 1; i <= len1; i++) 34 { 35 for (int j = 1; j <= len2; j++) 36 { 37 if (char1[i - 1] == char2[j - 1]) 38 { 39 temp = 0; 40 } 41 else 42 { 43 temp = 1; 44 } 45 //取三個值中最小的 46 dif[i, j] = Min(dif[i - 1, j - 1] + temp, dif[i, j - 1] + 1, dif[i - 1, j] + 1); 47 } 48 } 49 //計算相似度 50 float similarity = 1 - (float)dif[len1, len2] / Math.Max(len1, len2); 51 return similarity; 52 } 53 54 /// <summary> 55 /// 求最小值 56 /// </summary> 57 /// <param name="nums"></param> 58 /// <returns></returns> 59 private static int Min(params int[] nums) 60 { 61 int min = int.MaxValue; 62 foreach (int item in nums) 63 { 64 if (min > item) 65 { 66 min = item; 67 } 68 } 69 return min; 70 } 71 }