结合字符串相似度算法改进变位词相似度算法

前一篇博文:
[url]http://zhuyufufu.iteye.com/blog/1989482[/url]
实现了我的一个相似度简单算法,但是其缺陷十分明显。这两天查找了一些资料,找到了应用编辑距离计算字符串相似度的算法。

俄罗斯科学家Vladimir Levenshtein在1965年提出这个编辑距离概念。

现在我就用这个算法实现变位词的相似度计算。也算是站在巨人的肩膀上了吧!

参考资料:
[b]levenshtein距离[/b]
[url]http://baike.baidu.com/view/4123766.htm[/url]
[b]编辑距离[/b]
[url]http://baike.baidu.com/view/2020247.htm[/url]
[b]字符串相似度算法[/b]
[url]http://wdhdmx.iteye.com/blog/1343856#bc2305994[/url]

对于字符串相似度算法:第二个和第三个链接的内容解释的非常清楚,我就不做赘述了。

变位词的相似度计算算法简要设计:
1.对两个单词按字母排序
2.计算排序后的编辑距离
3计算得到相似度


[b]重构了第三篇博文中的字符串相似度算法程序[/b]

package com.zas.anagram;
/**
* @className:Levenshtein.java
* @classDescription:Levenshtein Distance 算法实现
* 可以使用的地方:DNA分析   拼字检查   语音辨识   抄袭侦测
* @author:donghai.wan
* @createTime:2012-1-12
*/
public class Levenshtein {

public static void main(String[] args) {
//要比较的两个字符串
String str1 = "今天星期四";
String str2 = "今天是星期五";
System.out.println("相似度为: " + getSimilarity(str1, str2));
}

/**
* 求字符串相似度
* @param str1
* @param str2
* @return
*/
public static Float getSimilarity(String str1,String str2) {
int levenshteinLength = levenshtein(str1,str2);
//计算相似度
float similarity =1 - (float) levenshteinLength / Math.max(str1.length(), str2.length());
return similarity;
}

/**
* 获取编辑距离
* @param str1
* @param str2
* @return
*/
public static int 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);
}
}
// 取数组右下角的值,同样不同位置代表不同字符串的比较
return dif[len1][len2];
}

/**
* 得到最小值
* @param is
* @return
*/
private static int min(int... is) {
int min = Integer.MAX_VALUE;
for (int i : is) {
if (min > i) {
min = i;
}
}
return min;
}

}




[b]按照设计的算法计算变位词相似度[/b]

package com.zas.anagram;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* 变位词算法设计
* @author zas
*
*/
public class Anagram {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(Anagram.getSimilarity(null, null));
System.out.println(Anagram.getSimilarity("", ""));
System.out.println(Anagram.getSimilarity("", null));
System.out.println(Anagram.getSimilarity(null, ""));
System.out.println(Anagram.getSimilarity(null, "cba"));
System.out.println(Anagram.getSimilarity("cba", null));
System.out.println(Anagram.getSimilarity("abc", "cba"));
System.out.println(Anagram.getSimilarity("abc", "cbaa"));
System.out.println(Anagram.getSimilarity("abc", "cbc"));
System.out.println(Anagram.getSimilarity("", "cbc"));
System.out.println(Anagram.getSimilarity("abc", ""));
System.out.println(Anagram.getSimilarity("abc", "acff"));
}

/**
* 给出一个两个字符串互为变位词的相似度算法。
* 当他们为变位词的时候输出1.0;
* 当他们长度不同且没有相同字母时输出0;
* 其他情况给出一个规则输出一个0到1之间的浮点数。
* @param wordA
* @param wordB
* @return
*/
public static Float getSimilarity(String wordA, String wordB) {
/**
* 算法设计说明:
* 一.是变位词与彻底不是变位词都有明确的定义
* 二.其余情况处理如下:
* 1. 取排序后的单词
* 2. 计算其字符串相似度
*/
//是变位词,返回1
if(isAnagram(wordA, wordB)){
return 1f;
}
if(isNotAnagram(wordA, wordB)){
return 0f;
}

//return getSimilarityByMap(wordA,wordB);
return getSimilarityByLevenshtein(wordA,wordB);
}



private static Float getSimilarityByLevenshtein(String wordA, String wordB) {
//获取两个单词的小写复本
wordA = wordA.toLowerCase();
wordB = wordB.toLowerCase();
//对两个单词按字母大小顺序排序
wordA = sort(wordA);
wordB = sort(wordB);
return Levenshtein.getSimilarity(wordA, wordB);
}

/**
* 给出一个两个字符串互为变位词的相似度算法。
* @param wordA
* @param wordB
* @return
*/
private static Float getSimilarityByMap(String wordA, String wordB) {
/**
* 算法设计说明:
* 一.是变位词与彻底不是变位词都有明确的定义
* 二.其余情况处理如下:
* 1. 取两个单词长度较大的作为基准单词,如:abc与acff 则取acff作为基准单词。
* 2. 计算要增删多少个单词才能使长度小的单词达到长度大的那样,以 abc、acff为例:abc要删除b增加ff,则需要操作3个字母
* 3. 相似度公式 1 - 操作的字母数/基准单词长度 ,上例则为:1 - 3/4 = 0.25
*/
//基准单词
String word = wordA;
String otherWord = wordB;
if(wordA.length() < wordB.length()){
word = wordB;
otherWord = wordA;
}
Map<Character, Integer> mapWord = getWordMap(word);
Map<Character, Integer> mapForOtherWord = getWordMap(otherWord);
int count = getOperateCount(mapWord, mapForOtherWord);
float result = 1f - (float)count/word.length();
return result;
}

/**
* @param mapWord 基准单词 长单词
* @param mapForOtherWord 短单词
* @return
*/
private static int getOperateCount(Map<Character, Integer> mapWord,
Map<Character, Integer> mapForOtherWord) {
// 字母操作计数器
int count = 0;
Set<Character> key = mapWord.keySet();
for (Iterator<Character> it = key.iterator(); it.hasNext();) {
Character c = (Character) it.next();
Integer charCount = mapWord.get(c);
Integer charCountOther = mapForOtherWord.get(c);
// 短单词中没有字母时字母操作数加上字母数
if (null == charCountOther) {
count = count + charCount;
} else {
// 否则加上字母个数差值的绝对值
count = count + Math.abs(charCount - charCountOther);
}
}
Set<Character> keyOther = mapForOtherWord.keySet();
for (Iterator<Character> it = keyOther.iterator(); it.hasNext();) {
Character c = (Character) it.next();
Integer charCount = mapWord.get(c);
Integer charCountOther = mapForOtherWord.get(c);
// 短单词中没有字母时字母操作数加上字母数
if (null == charCount) {
count = count + charCountOther;
}
}
return count;
}

/**
* 判断是否不为变位词
* @param wordA
* @param wordB
* @return
*/
private static boolean isNotAnagram(String wordA, String wordB) {
//当为变位词时,返回false
if(isAnagram(wordA, wordB)){
return false;
}
//处理null
if(null == wordA && null != wordB){
return true;
}
if(null != wordA && null == wordB){
return true;
}
//当他们长度不同且没有相同字母时不是变位词
if(wordA.length() != wordB.length()){
for (int i = 0; i < wordA.length(); i++) {
if(wordB.contains(String.valueOf(wordA.charAt(i)))){
return false;
}
}
return true;
}
return false;
}

/**
* 判断两个单词是否互为变位词
* @param string
* @param string2
* @return true/false
*/
public static boolean isAnagram(String wordA, String wordB) {
//异常情况处理
if(null == wordA && null == wordB){
return true;
}
if(false == handleNull(wordA, wordB)){
return false;
}
return isAnagramBySort(wordA, wordB);
//return isAnagramByMap(wordA, wordB);
}


/**
* 处理异常情况 返回 true表示要继续处理 false表示不为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean handleNull(String wordA, String wordB) {
//一个为空,另一个不为空不是变位词
if(null == wordA && null != wordB){
return false;
}
if(null == wordB && null != wordA){
return false;
}
//长度不同不为变位词
if(wordA.length() != wordB.length()){
return false;
}
return true;
}

/**
* 通过排序后比较其是否相同判断是否为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean isAnagramBySort(String wordA, String wordB) {
//获取两个单词的小写复本
wordA = wordA.toLowerCase();
wordB = wordB.toLowerCase();
//对两个单词按字母大小顺序排序
wordA = sort(wordA);
wordB = sort(wordB);

if(wordA.equals(wordB)){
return true;
}
return false;
}

/**
* 按字母顺序排序字符串
* @param wordA
* @return
*/
private static String sort(String word) {
char[] charArray = word.toCharArray();
//排序基本为小数据量的,因此采用冒泡、选择、插入中的一种,这里选择选择排序
for (int i = 0; i < charArray.length; i++) {
//内层循环找到未排序的最小字母
int selectedIndex = i;
for (int j = 0; j < charArray.length; j++) {
if(charArray[selectedIndex] > charArray[j]){
selectedIndex = j;
}
}
if(selectedIndex != i){
char tempForSwap = charArray[selectedIndex];
charArray[selectedIndex] = charArray[i];
charArray[i] = tempForSwap;
}
}
return String.valueOf(charArray);
}

/**
* 通过 字母-字母个数 键值对来判断变位词
* @param wordA
* @param wordB
* @return true false;
*/
private static boolean isAnagramByMap(String wordA, String wordB) {
Map<Character, Integer> mapForWordA = getWordMap(wordA);
Map<Character, Integer> mapForWordB = getWordMap(wordB);
//字母的个数不同肯定不是变位词
if(mapForWordA.size() != mapForWordB.size()){
return false;
}
//迭代mapForWordA的字母 并在mapForWordB中获得对应的字母个数 若不同则不是变位词
Set<Character> key = mapForWordA.keySet();
for (Iterator<Character> it = key.iterator(); it.hasNext();) {
Character c = (Character) it.next();
Integer charCountA = mapForWordA.get(c);
Integer charCountB = mapForWordB.get(c);
if(charCountA != charCountB){
return false;
}
}
return true;
}

/**
* 获得一个字符串的字母-字母个数键值对
* @param wordA
* @return
*/
private static Map<Character, Integer> getWordMap(String word) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
char[] charArray = word.toCharArray();
for (int i = 0; i < charArray.length; i++) {
Character c = charArray[i];
Integer charCount = map.get(c);
if(null == charCount){
charCount = 1;
}else{
charCount = charCount + 1;
}
map.put(c, charCount);
}
return map;
}

/**
* 从文件中获取词典列表
* @param path
* @return List<String>
*/
private static List<String> getWordsListFromFile(String path) {
List<String> wordList = new ArrayList<String>();
File file = new File(path);
FileReader fr = null;
BufferedReader br = null;
try{
fr = new FileReader(file);
br = new BufferedReader(fr);
String s;
while((s = br.readLine()) != null){
//去首尾空白
s = s.trim();
wordList.add(s);
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally{
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

return wordList;
}

/**
* 获取所有变位词集合列表
* @param wordList
* @return
*/
private static Map<String, List<String>> getAnagramCollectionMap(List<String> wordList) {
Map<String, List<String>> anagramCollectionMap = new HashMap<String, List<String>>();
while(wordList.size() > 0){
String word = wordList.remove(0);
//将单词存入变位词集合map中
//这里有两种算法,一种是把单词排序之后放入map这样就不需要遍历map
//另一种是遍历map的key判断它是否和该单词互为变位词
//这里采用第一种
String sortedWord = sort(new String(word).toLowerCase());
List<String> list = anagramCollectionMap.get(sortedWord);
if(list == null){
list = new ArrayList<String>();
}
list.add(word);
anagramCollectionMap.put(sortedWord, list);
}
return anagramCollectionMap;
}

/**
* 根据某种条件从map集中获取符合条件的列表 可以考虑实现一个说明模式
* 为了演示简便,给出获取特定大小变位词集合的实现
* @param anagramCollectionMap
* @return
*/
private static Map<String, List<String>> getAnagramCollectionMapByCondition(Map<String, List<String>> anagramCollectionMap, int size) {
Map<String, List<String>> resultMap = new HashMap<String, List<String>>();
Set<String> key = anagramCollectionMap.keySet();
for (Iterator<String> it = key.iterator(); it.hasNext();) {
String str = (String) it.next();
List<String> list= anagramCollectionMap.get(str);
if(list.size() == size){
resultMap.put(str, list);
}
}
return resultMap;
}

/**
* 向文件中输出变位词集合列表
* @param path
* @return
*/
private static void writeWordsListToFile(String path, Map<String, List<String>> anagramCollectionMap) {
File file = new File(path);
FileWriter fw = null;
BufferedWriter bw = null;
try{
fw = new FileWriter(file);
bw = new BufferedWriter(fw);
Set<String> key = anagramCollectionMap.keySet();
for (Iterator<String> it = key.iterator(); it.hasNext();) {
String str = (String) it.next();
List<String> list= anagramCollectionMap.get(str);
bw.write(str + list.toString());
bw.newLine();
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally{
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}




[b]修改后的算法仍然没有解决原来的问题:[/b]
算法的缺陷:举例说明 abc 与 cbc 在本算法中相似度为 1/3;正常人看应该为 2/3。

看来有必要重新想一个算法了。

[b]希望本博客的朋友们能提供一些想法。[/b]

非常感谢!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值