编程之美3.3
看完题后,毫无头绪
书上的解题思路很好,首先两个字符串的距离肯定是个可知数,必须小于两字符串之和。
可以通过删除操作将两个串都变成空串。
书上所示的递归方法,代码敲出来了,有点点不同
view plain copy to clipboard print ?
#include
#include
#include
[b]void[/b] calDistance([b]char[/b] *a, [b]int[/b] aBegin, [b]int[/b] aEnd, [b]char[/b] * b, [b]int[/b] bBegin, [b]int[/b] bEnd, [b]int[/b]& dis);
[b]int[/b] min([b]int[/b] a, [b]int[/b] b, [b]int[/b] c);
[b]int[/b] main()
{
[b]char[/b] a[50];
[b]char[/b] b[50];
[b]int[/b] dis = 0;
printf("First String :\n");
scanf("%s", a);
printf("Second String : \n");
scanf("%s", b);
[b]int[/b] aLen = strlen(a);
[b]int[/b] bLen = strlen(b);
calDistance(a,0,aLen-1, b, 0, bLen-1, dis);
printf("The two string distance is :%d\n", dis);
[b]return[/b] 0; }
[b]int[/b] min([b]int[/b] a, [b]int[/b] b, [b]int[/b] c) {
[b]if[/b](a aEnd)
{
[b]if[/b](bBegin > bEnd)
[b]return[/b] ;
[b]else[/b]
{
dis = dis + bEnd - bBegin + 1;
[b]return[/b] ;
}
}
[b]else[/b] [b]if[/b](bBegin > bEnd)
{
dis = dis + aEnd - aBegin + 1;
[b]return[/b] ; }
[b]if[/b](a[aBegin] == b[bBegin])
calDistance(a, aBegin + 1, aEnd, b, bBegin + 1, bEnd, dis);
[b]else[/b] {
[b]int[/b] num1, num2, num3;
num1 = num2 = num3 = dis + 1;
calDistance(a, aBegin + 1, aEnd, b, bBegin, bEnd, num1); //给b[bBegin]前加a[aBegin]
calDistance(a, aBegin, aEnd, b, bBegin + 1, bEnd, num2); //给a[aBegin]前加b[bBegin]
calDistance(a, aBegin + 1, aEnd, b, bBegin + 1, bEnd, num3); //将a[aBegin]置换为b[bBegin],或者将b[bBegin]置换为a[aBegin]
dis = min(num1, num2, num3);
} }
因为上述的代码存在重复子问题,可以优化,利用将子问题计算后的结构暂存,再次调用时可以直接查结果。
涉及到重复子问题,联想到动态规划的备忘录...具体实现,嗯,需要参考资料了...
参考文章:
http://tech.ddvip.com/2009-05/1243159182120785_3.h tml
先转DP解法代码
动态规划算法总是充分利用重叠子问题,即通过每个子问题只解一次,把解保存在一个需要时就可以查看的表中,而每次查表的时间为常数。
动态规划算法是有底向上的。
view plain copy to clipboard print ?
/*
* A loop method using dynamic programming.
* Calculate from bottom to top. */
[b]int[/b] calculateStringDistance([b]string[/b] strA, [b]string[/b] strB)
{
[b]int[/b] lenA = ([b]int[/b])strA.length();
[b]int[/b] lenB = ([b]int[/b])strB.length();
[b]int[/b] c[lenA+1][lenB+1]; // Record the distance of all begin points of each string
// i: begin point of strA
// j: begin point of strB
[b]for[/b]([b]int[/b] i = 0; i = 0; i--)
[b]for[/b]([b]int[/b] j = lenB-1; j >= 0; j--)
{
[b]if[/b](strB[j] == strA[i])
c[i][j] = c[i+1][j+1];
[b]else[/b]
c[i][j] = minValue(c[i][j+1], c[i+1][j], c[i+1][j+1]) + 1;
}
[b]return[/b] c[0][0];
}
再转备忘录做法:
备忘录是动态规划的一种变形,它既具有通常的动态规划方法的效率,又采用了一种自顶向下的策略。
加了备忘的递归算法为每一个子问题的解在表中记录一个表项。开始时,每个表项最初都包含一个特殊的值,以表示该表项有待填入。当在递归算法的执行中第一次遇到一个子问题时,就计算它的解并填入表中。以后每次遇到该子问题时,只要查看并返回先前填入的值即可。
下面是原文递归算法的做备忘录版本,并通过布尔变量memoize来控制是否使用备忘录,以及布尔变量debug来控制是否打印调用过程。有兴趣的读都可以通过这两个布尔变量的控制来对比一下备忘录版本与非备忘录版本的复杂度。
view plain copy to clipboard print ?
#include
#define M 100
[b]using[/b] [b]namespace[/b] std;
[b]const[/b] [b]bool[/b] debug = [b]false[/b]; // Whether to print debug info
[b]const[/b] [b]bool[/b] memoize = [b]true[/b]; // Whether to use memoization
unsigned [b]int[/b] cnt = 0; // Line number for the debug info
[b]int[/b] memoizedDistance[M][M]; // Matrix for memoiztion
[b]int[/b] minValue([b]int[/b] a, [b]int[/b] b, [b]int[/b] c) {
[b]if[/b](a = 0)
[b]return[/b] memoizedDistance[pABegin][pBBegin];
[b]if[/b](pABegin > pAEnd)
{
[b]if[/b](pBBegin > pBEnd)
{
[b]if[/b](memoize)
memoizedDistance[pABegin][pBBegin] = 0;
[b]if[/b](debug)
cout pBEnd)
{
[b]if[/b](pABegin > pAEnd)
{
[b]if[/b](memoize)
memoizedDistance[pABegin][pBBegin] = 0;
[b]if[/b](debug)
cout 字符串相似度计算方法有好多,现对基于编距的算法的相似度计算自己总结下。
简单介绍下Levenshtein Distance(LD):LD 可能衡量两字符串的相似性。它们的距离就是一个字符串转换成那一个字符串过程中的添加、删除、修改数值。
举例: 如果str1="test",str2="test",那么LD(str1,str2) = 0。没有经过转换。
如果str1="test",str2="tent",那么LD(str1,str2) = 1。str1的"s"转换"n",转换了一个字符,所以是1。
如果它们的距离越大,说明它们越是不同。
Levenshtein distance最先是由俄国科学家Vladimir Levenshtein在1965年发明,用他的名字命名。不会拼读,可以叫它edit distance(编辑距离)。
Levenshtein distance可以用来: Spell checking(拼写检查)
Speech recognition(语句识别)
DNA analysis(DNA分析)
Plagiarism detection(抄袭检测)
LD用m*n的矩阵存储距离值。算法大概过程: str1或str2的长度为0返回另一个字符串的长度。
初始化(n+1)*(m+1)的矩阵d,并让第一行和列的值从0开始增长。
扫描两字符串(n*m级的),如果:str1[i] == str2[j],用temp记录它,为0。否则temp记为1。然后在矩阵d[i][j]赋于d[i-1][j]+1 、d[i][j-1]+1、d[i-1][j-1]+temp三者的最小值。
扫描完后,返回矩阵的最后一个值即d[n][m]
最后返回的是它们的距离。怎么根据这个距离求出相似度呢?因为它们的最大距离就是两字符串长度的最大值。对字符串不是很敏感。现我把相似度计算公式定为1-它们的距离/字符串长度最大值。
源码: package com.chenlb.algorithm;
/**
* 编辑距离的两字符串相似度
*
* @author chenlb 2008-6-24 下午06:41:55
*/
public class Similarity {
private int min( int one, int two, int three) {
int min = one;
if (two < min) {
min = two;
}
if (three < min) {
min = three;
}
return min;
}
public 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];
}
public double sim(String str1, String str2) {
int ld = ld(str1, str2);
return 1 - ( double ) ld / Math.max(str1.length(), str2.length());
}
public static void main(String[] args) {
Similarity s = new Similarity();
String str1 = " chenlb.blogjava.net " ;
String str2 = " chenlb.javaeye.com " ;
System.out.println( " ld= " + s.ld(str1, str2));
System.out.println( " sim= " + s.sim(str1, str2));
}
}
不知sim方法中的公式是合理,个人认为差强人意思,^_^
看完题后,毫无头绪
书上的解题思路很好,首先两个字符串的距离肯定是个可知数,必须小于两字符串之和。
可以通过删除操作将两个串都变成空串。
书上所示的递归方法,代码敲出来了,有点点不同
view plain copy to clipboard print ?
#include
#include
#include
[b]void[/b] calDistance([b]char[/b] *a, [b]int[/b] aBegin, [b]int[/b] aEnd, [b]char[/b] * b, [b]int[/b] bBegin, [b]int[/b] bEnd, [b]int[/b]& dis);
[b]int[/b] min([b]int[/b] a, [b]int[/b] b, [b]int[/b] c);
[b]int[/b] main()
{
[b]char[/b] a[50];
[b]char[/b] b[50];
[b]int[/b] dis = 0;
printf("First String :\n");
scanf("%s", a);
printf("Second String : \n");
scanf("%s", b);
[b]int[/b] aLen = strlen(a);
[b]int[/b] bLen = strlen(b);
calDistance(a,0,aLen-1, b, 0, bLen-1, dis);
printf("The two string distance is :%d\n", dis);
[b]return[/b] 0; }
[b]int[/b] min([b]int[/b] a, [b]int[/b] b, [b]int[/b] c) {
[b]if[/b](a aEnd)
{
[b]if[/b](bBegin > bEnd)
[b]return[/b] ;
[b]else[/b]
{
dis = dis + bEnd - bBegin + 1;
[b]return[/b] ;
}
}
[b]else[/b] [b]if[/b](bBegin > bEnd)
{
dis = dis + aEnd - aBegin + 1;
[b]return[/b] ; }
[b]if[/b](a[aBegin] == b[bBegin])
calDistance(a, aBegin + 1, aEnd, b, bBegin + 1, bEnd, dis);
[b]else[/b] {
[b]int[/b] num1, num2, num3;
num1 = num2 = num3 = dis + 1;
calDistance(a, aBegin + 1, aEnd, b, bBegin, bEnd, num1); //给b[bBegin]前加a[aBegin]
calDistance(a, aBegin, aEnd, b, bBegin + 1, bEnd, num2); //给a[aBegin]前加b[bBegin]
calDistance(a, aBegin + 1, aEnd, b, bBegin + 1, bEnd, num3); //将a[aBegin]置换为b[bBegin],或者将b[bBegin]置换为a[aBegin]
dis = min(num1, num2, num3);
} }
因为上述的代码存在重复子问题,可以优化,利用将子问题计算后的结构暂存,再次调用时可以直接查结果。
涉及到重复子问题,联想到动态规划的备忘录...具体实现,嗯,需要参考资料了...
参考文章:
http://tech.ddvip.com/2009-05/1243159182120785_3.h tml
先转DP解法代码
动态规划算法总是充分利用重叠子问题,即通过每个子问题只解一次,把解保存在一个需要时就可以查看的表中,而每次查表的时间为常数。
动态规划算法是有底向上的。
view plain copy to clipboard print ?
/*
* A loop method using dynamic programming.
* Calculate from bottom to top. */
[b]int[/b] calculateStringDistance([b]string[/b] strA, [b]string[/b] strB)
{
[b]int[/b] lenA = ([b]int[/b])strA.length();
[b]int[/b] lenB = ([b]int[/b])strB.length();
[b]int[/b] c[lenA+1][lenB+1]; // Record the distance of all begin points of each string
// i: begin point of strA
// j: begin point of strB
[b]for[/b]([b]int[/b] i = 0; i = 0; i--)
[b]for[/b]([b]int[/b] j = lenB-1; j >= 0; j--)
{
[b]if[/b](strB[j] == strA[i])
c[i][j] = c[i+1][j+1];
[b]else[/b]
c[i][j] = minValue(c[i][j+1], c[i+1][j], c[i+1][j+1]) + 1;
}
[b]return[/b] c[0][0];
}
再转备忘录做法:
备忘录是动态规划的一种变形,它既具有通常的动态规划方法的效率,又采用了一种自顶向下的策略。
加了备忘的递归算法为每一个子问题的解在表中记录一个表项。开始时,每个表项最初都包含一个特殊的值,以表示该表项有待填入。当在递归算法的执行中第一次遇到一个子问题时,就计算它的解并填入表中。以后每次遇到该子问题时,只要查看并返回先前填入的值即可。
下面是原文递归算法的做备忘录版本,并通过布尔变量memoize来控制是否使用备忘录,以及布尔变量debug来控制是否打印调用过程。有兴趣的读都可以通过这两个布尔变量的控制来对比一下备忘录版本与非备忘录版本的复杂度。
view plain copy to clipboard print ?
#include
#define M 100
[b]using[/b] [b]namespace[/b] std;
[b]const[/b] [b]bool[/b] debug = [b]false[/b]; // Whether to print debug info
[b]const[/b] [b]bool[/b] memoize = [b]true[/b]; // Whether to use memoization
unsigned [b]int[/b] cnt = 0; // Line number for the debug info
[b]int[/b] memoizedDistance[M][M]; // Matrix for memoiztion
[b]int[/b] minValue([b]int[/b] a, [b]int[/b] b, [b]int[/b] c) {
[b]if[/b](a = 0)
[b]return[/b] memoizedDistance[pABegin][pBBegin];
[b]if[/b](pABegin > pAEnd)
{
[b]if[/b](pBBegin > pBEnd)
{
[b]if[/b](memoize)
memoizedDistance[pABegin][pBBegin] = 0;
[b]if[/b](debug)
cout pBEnd)
{
[b]if[/b](pABegin > pAEnd)
{
[b]if[/b](memoize)
memoizedDistance[pABegin][pBBegin] = 0;
[b]if[/b](debug)
cout 字符串相似度计算方法有好多,现对基于编距的算法的相似度计算自己总结下。
简单介绍下Levenshtein Distance(LD):LD 可能衡量两字符串的相似性。它们的距离就是一个字符串转换成那一个字符串过程中的添加、删除、修改数值。
举例: 如果str1="test",str2="test",那么LD(str1,str2) = 0。没有经过转换。
如果str1="test",str2="tent",那么LD(str1,str2) = 1。str1的"s"转换"n",转换了一个字符,所以是1。
如果它们的距离越大,说明它们越是不同。
Levenshtein distance最先是由俄国科学家Vladimir Levenshtein在1965年发明,用他的名字命名。不会拼读,可以叫它edit distance(编辑距离)。
Levenshtein distance可以用来: Spell checking(拼写检查)
Speech recognition(语句识别)
DNA analysis(DNA分析)
Plagiarism detection(抄袭检测)
LD用m*n的矩阵存储距离值。算法大概过程: str1或str2的长度为0返回另一个字符串的长度。
初始化(n+1)*(m+1)的矩阵d,并让第一行和列的值从0开始增长。
扫描两字符串(n*m级的),如果:str1[i] == str2[j],用temp记录它,为0。否则temp记为1。然后在矩阵d[i][j]赋于d[i-1][j]+1 、d[i][j-1]+1、d[i-1][j-1]+temp三者的最小值。
扫描完后,返回矩阵的最后一个值即d[n][m]
最后返回的是它们的距离。怎么根据这个距离求出相似度呢?因为它们的最大距离就是两字符串长度的最大值。对字符串不是很敏感。现我把相似度计算公式定为1-它们的距离/字符串长度最大值。
源码: package com.chenlb.algorithm;
/**
* 编辑距离的两字符串相似度
*
* @author chenlb 2008-6-24 下午06:41:55
*/
public class Similarity {
private int min( int one, int two, int three) {
int min = one;
if (two < min) {
min = two;
}
if (three < min) {
min = three;
}
return min;
}
public 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];
}
public double sim(String str1, String str2) {
int ld = ld(str1, str2);
return 1 - ( double ) ld / Math.max(str1.length(), str2.length());
}
public static void main(String[] args) {
Similarity s = new Similarity();
String str1 = " chenlb.blogjava.net " ;
String str2 = " chenlb.javaeye.com " ;
System.out.println( " ld= " + s.ld(str1, str2));
System.out.println( " sim= " + s.sim(str1, str2));
}
}
不知sim方法中的公式是合理,个人认为差强人意思,^_^