字串相似度演算法
字串相似度演算法
一、百度百科
Levenshtein 距離,又稱編輯距離,指的是兩個字串之間,由一個轉換成另一個所需的最少編輯操作次數。
許可的編輯操作包括將一個字元替換成另一個字元,插入一個字元,刪除一個字元。
編輯距離的演算法是首先由俄國科學家Levenshtein提出的,故又叫Levenshtein Distance。
二、用途
字串的模糊匹配查詢
三、實現過程
- 首先是有兩個字串,這裡寫一個簡單的 abc和abe
- 將字串想象成下面的結構
A處 是一個標記,為了方便講解,不是這個表的內容
abc | a | b | c | |
---|---|---|---|---|
abe | 0 | 1 | 2 | 3 |
a | 1 | A處 | ||
b | 2 | |||
e | 3 |
-
計算 A處 出得值
它的值取決於:左邊的1、上邊的1、左上角的0,
按照Levenshtein distance的意思:
上面的值和左面的值都要求加1,這樣得到1+1=2;
A處 由於是兩個a相同,左上角的值加0,這樣得到0+0=0;
這是後有三個值,左邊的計算後為2,上邊的計算後為2,左上角的計算為0,所以 A處 取他們裡面最小的值,即0。
-
新增A處資料後結果如下
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處 的值。
- 新增B處資料後結果如下
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處 的值。
- 以此類推,最終的結果如下表所示
abc | a | b | c | |
---|---|---|---|---|
abe | 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個需要編輯的操作,這個是需要計算出來的,同時,也獲得一些額外的資訊
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個操作
-
相似度計算
先取兩個字串長度的最大值maxLen,用1-(需要運算元除maxLen),得到相似度。
例如abc 和abe 一個操作,長度為3,所以相似度為1-1/3=0.666。
四、JAVA程式碼實現
複製後即可執行
package code;
/**
* @className:MyLevenshtein.java
* @classDescription:Levenshtein Distance 演算法實現
* 可以使用的地方:DNA分析 拼字檢查 語音辨識 抄襲偵測
* @author:ylp
* @createTime:2018-10-18
*/
public class MyLevenshtein {
public static void main(String[] args) {
//要比較的兩個字串
String str1 = "今天星期四";
String str2 = "今天是星期五";
levenshtein(str1,str2);
}
/**
* DNA分析 拼字檢查 語音辨識 抄襲偵測
*
* @createTime 2018-10-18
*/
public static void levenshtein(String str1,String str2) {
//計算兩個字串的長度。
int len1 = str1.length();
int len2 = str2.length();
//建立上面說的陣列,比字元長度大一個空間
int[][] dif = new int[len1 + 1][len2 + 1];
//賦初值,步驟B。
for (int a = 0; a <= len1; a++) {
dif[a][0] = a;
}
for (int a = 0; a <= len2; a++) {
dif[0][a] = a;
}
//計算兩個字元是否一樣,計算左上的值
int temp;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
temp = 0;
} else {
temp = 1;
}
//取三個值中最小的
dif[i][j] = min(dif[i - 1][j - 1] + temp, dif[i][j - 1] + 1,
dif[i - 1][j] + 1);
}
}
System.out.println("字串\""+str1+"\"與\""+str2+"\"的比較");
//取陣列右下角的值,同樣不同位置代表不同字串的比較
System.out.println("差非同步驟:"+dif[len1][len2]);
//計算相似度
float similarity =1 - (float) dif[len1][len2] / Math.max(str1.length(), str2.length());
System.out.println("相似度:"+similarity);
}
//得到最小值
private static int min(int... is) {
int min = Integer.MAX_VALUE;
for (int i : is) {
if (min > i) {
min = i;
}
}
return min;
}
}
五、原理
為什麼這樣就能算出相似度了?
下面給兩個例子來解釋一下,其中,紅色是取值的順序。
- "今天週一" "天週一"
天 | 周 | 一 | ||
---|---|---|---|---|
0 | 1 | 2 | 3 | |
今 | 1 | 1 | 2 | 3 |
天 | 2 | 1 | 2 | 3 |
周 | 3 | 2 | 1 | 3 |
一 | 4 | 3 | 3 | 1 |
這兩個字串轉換最快的步驟就是:
實現是去掉 “今” ,一步完成。
- "聽說馬上就要放假了" "你聽說要放假了"
你 | 聽 | 說 | 要 | 放 | 假 | 了 | ||
---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
聽 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 6 |
說 | 2 | 2 | 2 | 1 | 2 | 3 | 4 | 5 |
馬 | 3 | 3 | 3 | 2 | 2 | 3 | 4 | 5 |
上 | 4 | 4 | 4 | 3 | 3 | 3 | 4 | 5 |
就 | 5 | 5 | 5 | 4 | 4 | 4 | 4 | 5 |
要 | 6 | 6 | 6 | 5 | 4 | 5 | 5 | 5 |
放 | 7 | 7 | 7 | 6 | 5 | 4 | 5 | 6 |
假 | 8 | 8 | 8 | 7 | 6 | 5 | 4 | 6 |
了 | 9 | 9 | 9 | 8 | 7 | 6 | 6 | 4 |
這兩個轉換最快的步驟就是:
去掉“你”,加上“馬上就”,總共四步操作。
總結來說就是,先根據二維的計算方式找到兩個字串相互轉換的最少步驟,即編輯距離,也可以理解為字串之間的差異或者不同,然後除以最長的字串的長度,即差異值佔最大字串長度的比例,可以理解為差異度(個人定義,非官方),然後用1減去差異度就可以得到相似度了。
六、結束語
此演算法的優化空間還有很大,歡迎有興趣的同學一起交流,在程式猿的道路上一起進步。
參考連結:計算字串相似度演算法——Levenshtein.