//算法原理参考http://blog.jobbole.com/75496/ /* * 步骤 * 说明 * 1 * 设置n为字符串s的长度。(“GUMBO”) * 设置m为字符串t的长度。(“GAMBOL”) * 如果n等于0,返回m并退出。 * 如果m等于0,返回n并退出。 * 构造两个向量v0[m+1] 和v1[m+1],串联0..m之间所有的元素。 * 2 * 初始化 v0 to 0..m。字符从1开始,初始化的值是从空字符串到生成字符需要的编辑距离。同理每次v1[0]初始化的值也是符合这个原理的,只是 * 3 * 检查 s (i from 1 to n) 中的每个字符。 * 4 * 检查 t (j from 1 to m) 中的每个字符 * 5 * 如果 s[i] 等于 t[j],则编辑代价为 0; * 如果 s[i] 不等于 t[j],则编辑代价为1。 * 6 * 设置单元v1[j]为下面的最小值之一: * a、紧邻该单元上方+1:v1[j-1] + 1 * b、紧邻该单元左侧+1:v0[j] + 1 * c、该单元对角线上方和左侧+cost:v0[j-1] + cost * 7 * 在完成迭代 (3, 4, 5, 6) 之后,v1[m]便是编辑距离的值。 */ //这是求编辑距离,这里用两个列向量替代了原来的矩阵,并以短的长度作为列向量的大小。 public static int distance(string src, string tgt) { src = StringTool.blankomit(src); tgt = StringTool.blankomit(tgt); int row = 0; int col = 0; //我们把字符串调整为长字符串在上端比较,短字符放侧边比较。这样可以较小开辟空间的大小。 if (src.Length < tgt.Length) { string tmp = src; src = tgt; tgt = tmp; } row = tgt.Length; col = src.Length; if (col == 0) { return(row); } if (row == 0) { return(col); } int[] v0 = new int[row + 1]; int[] v1 = new int[row + 1]; for (int i = 0; i <= row; i++) { v0[i] = i; } int cost = 0; for (int i = 1; i <= col; i++) { v1[0] = i; for (int j = 1; j <= row; j++) { if (src[i - 1].Equals(tgt[j - 1])) { cost = 0; } else { cost = 1; } //这里三个分别对应,插入,删除和替换。 v1[j] = Math.Min(Math.Min(v1[j - 1] + 1, v0[j] + 1), v0[j - 1] + cost); } v0 = (int[])v1.Clone(); } return(v1[row]); }
private static readonly int mNumChars = 4;//我觉得可以根据句子的长度来设置这个值。 public static double Jaro_Winkler(string aString1, string aString2) { aString1 = StringTool.blankomit(aString1); aString2 = StringTool.blankomit(aString2); string minStr = null; string maxStr = null; StringTool.MinFirst(aString1, aString2, out minStr, out maxStr); int lLen1 = minStr.Length; int lLen2 = maxStr.Length; if (lLen1 == 0) { return(lLen2 == 0 ? 1.0 : 0.0);//两个字符串长度都为0的时候判断为完全相似1,否则只有一个为0,则判断为完全不相似0 } //这一段是求匹配字符个数的,其中这里有个窗口大小来求匹配字符个数,超过窗口大小的,就算存在相同的字符也不计数。 int lSearchRange = Math.Max(0, Math.Max(lLen1, lLen2) / 2 - 1); // default initialized to false bool[] lMatched1 = new bool[lLen1]; bool[] lMatched2 = new bool[lLen2]; int lNumCommon = 0; //对第一个字符依次后移,匹配另一个字符的字符段里面的字符,如果之前匹配成功过,则跳过,直到字符段窗口结束或者遇见相同字符。 for (int i = 0; i < lLen1; ++i)//因为我调整过字符串的长度,所以len1是短,len2是长。 { int lStart = Math.Max(0, i - lSearchRange); int lEnd = Math.Min(i + lSearchRange + 1, lLen2); for (int j = lStart; j < lEnd; ++j) { if (lMatched2[j]) { continue; } if (minStr[i] != maxStr[j]) { continue; } lMatched1[i] = true; lMatched2[j] = true; ++lNumCommon; break; } } // if (lNumCommon == 0) { return(0.0);//在窗口范围内的匹配字符为零则返回相似度为0; } //求换位的数目,即匹配字符是否是按相同顺序,如果顺序相同,则换位数目为0,如果顺序不同则需要计算换位数目。 //先位移到第一个字符串的第一个匹配字符哪里,对第二个字符串进行匹配,不匹配就后移。这时候,两个标签都在匹配字符那里,这时候,我们比较这两个匹配字符是否相同 //如果不等则增加换位数。然后再对第一个字符串的第二个匹配字符进行匹配。 //因为换位是字符对,所以要把结果除以2. //即若在字符串的第i位出现了a,b,在第j位又出现了b,a,则表示两者出现了换位。 int lNumHalfTransposed = 0; int k = 0; for (int i = 0; i < lLen1; ++i) { if (!lMatched1[i]) { continue; } while (!lMatched2[k]) { ++k; } if (minStr[i] != maxStr[k]) { ++lNumHalfTransposed; } ++k; } // System.Diagnostics.Debug.WriteLine("numHalfTransposed=" + numHalfTransposed); int lNumTransposed = lNumHalfTransposed / 2; // System.Diagnostics.Debug.WriteLine("numCommon=" + numCommon + " numTransposed=" + numTransposed); double lNumCommonD = lNumCommon; double lWeight = (lNumCommonD / lLen1 + lNumCommonD / lLen2 + (lNumCommon - lNumTransposed) / lNumCommonD) / 3.0; if (lWeight <= mWeightThreshold) { return(lWeight); } //这是求最大前缀匹配长度 int lMax = Math.Min(mNumChars, Math.Min(minStr.Length, maxStr.Length)); int lPos = 0; while (lPos < lMax && minStr[lPos] == maxStr[lPos]) { ++lPos; } // if (lPos == 0) { return(lWeight); } return(lWeight + 0.1 * lPos * (1.0 - lWeight)); }