编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。
许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。
例如将kitten一字转成sitting:
sitten (k→s)
sittin (e→i)
sitting (→g)
俄罗斯科学家@ Vladimir Levenshtein 在1965年提出这个概念。
package com.test;
/**
* 编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。
* 许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。
*
*
* 例如将kitten一字转成sitting:
* sitten (k→s)
* sittin (e→i)
* sitting (→g)
* 俄罗斯科学家@ Vladimir Levenshtein 在1965年提出这个概念。
*
*
* @author Lenovo
*
*/
public class SimilarityUtil {
/**
* 求三个数中最小的哪一个
* @param one
* @param two
* @param three
* @return
*/
private static int min(int one, int two, int three) {
int min = one;
if (two < min) {
min = two;
}
if (three < min) {
min = three;
}
return min;
}
/**
* 求编辑距离
* @param str1
* @param str2
* @return
*/
public static int ld(String str1, String str2) {
int d[][]; // 矩阵
int n = str1.length();
int m = str2.length();
int i; // 遍历str1的
int j; // 遍历str2的
char ch1; // str1的
char ch2; // str2的
int temp; // 记录相同字符,在某个矩阵位置值的增量,不是0就是1
if (n == 0) {
return m;
}
if (m == 0) {
return n;
}
d = new int[n + 1][m + 1];
for (i = 0; i <= n; i++) { // 初始化第一列
d[i][0] = i;
}
for (j = 0; j <= m; j++) { // 初始化第一行
d[0][j] = j;
}
for (i = 1; i <= n; i++) { // 遍历str1
ch1 = str1.charAt(i - 1);
// 去匹配str2
for (j = 1; j <= m; j++) {
ch2 = str2.charAt(j - 1);
if (ch1 == ch2) {
temp = 0;
} else {
temp = 1;
}
// 左边+1,上边+1, 左上角+temp取最小
d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1]
+ temp);
}
}
return d[n][m];
}
/**
* 计算相似度
* @param str
* @param target
* @return
*/
public static double sim(String str, String target) {
//去除空白字符、换行、标点符号
String regex = "[\\pP\\p{Punct}\\s]";
return 1 - (float) ld(str.replaceAll(regex, ""), target.replaceAll(regex, "")) / Math.max(str.length(), target.length());
//int ld = ld(str1, str2);
//return 1 - (double) ld / Math.max(str1.length(), str2.length());
}
/**
* 判断两个字符串相似度,可设置level
* @param strSource 原字符串
* @param strCompared 比较字符串
* @param level 评分阀值
* @param moreCount 比较字符串比原字符串多多少个限制
* @return
*/
public static Boolean isSimilar(String strSource,String strCompared,int level,int moreCount){
if(strCompared.length()-strSource.length()>moreCount){
return false;
}
int count=strSource.length();
int maxSameCount=0;
//遍历count次
for(int i=0;i<count;i++){
int nowSameCount=0;
int c=0;
int lastIndex=0;//记录上一次匹配的目标索引
//遍历每一次的原字符串所有字段
for(int j=i;j<strSource.length();j++){
char charSource=strSource.charAt(j);
for(;c<strCompared.length();c++){
char charCompare=strCompared.charAt(c);
if(charSource==charCompare){
nowSameCount++;
lastIndex=++c;//如果匹配,手动加1
break;
}
}
c=lastIndex;//遍历完目标字符串,记录当前匹配索引
}
if(nowSameCount>maxSameCount){
maxSameCount=nowSameCount;
}
}
//大于原字符串数量的情况
if(maxSameCount>count){
maxSameCount=count-(maxSameCount-count);
}
double dLv= (double)100*maxSameCount/count;
int iLv=10*maxSameCount/count*10;
int cha=(int)dLv-iLv;
int yu=cha>5?1:0;
iLv+=yu*10;
if(iLv/10>=level){
return true;
}else{
return false;
}
}
public static void main(String[] args) {
String str1 = "灵武市白土岗乡泾兴村防洪工程(一期)";
String str2 = "灵武市白土岗乡泾兴村防洪工程(二期)";
//System.out.println("ld=" + ld(str1, str2));
System.out.println("相似度" + sim(str1, str2));
System.out.println(isSimilar(str1, str2, 9, 4)?"字符串相似":"字符串不相似");
}
}