c2java 动态规划之模糊匹配

字符串匹配
精确:
indexOf(String str); -- strstr(), O(mn)。
lastIndexOf(String str); -- continue 的别样用法。
matches(String regex); -- Regex.compile()/match()。
模糊:
 java package?
 Spell Checker -- 两个字符串的相似程度
 Fuzzy Finder -- 子列匹配
上面两个问题都可以用这个概念”编辑距离”来有效解决。
所谓这个距离是指从一个字符串变到另一个字符串所需操作序列的度量;
这些操作包括:替换 shot --> spot, 插入 ago --> agog, 删除 hour --> our。
对于子列匹配,设用户正在输入的s比目标字符串t短,比如Skina是Skienna的子列,
需要两个插入操作,距离为d=2。
问题: |s| + d = |t| 是否表明s是t的子列?

设P(i, j) 表示把s[0..i] 变成t[0..j]的问题,d[i][j]为所需操作的最短距离。
P(i,j)有optimal substructure性质,即子问题P(i-1,j-1), P(i-1,j),
P(i,j-1)构成P(i,j)。证明使用cut-and-paste反证:如果其组成的子问题解
不是最优的,比如d[i-1][j-1], 把它用一个更优的解替换掉,则P(i,j)更小,
与P(i,j)最优矛盾。

带记忆的递归写法的优点是有些子问题不用解。比如背包:
P(i,j) =
  P(i-1, j) if j < w[i];
  min{P(i-1,j), v[i] + P(i-1,j-w[i])} if j >= w[i]

而当前问题,采用递推写法,优点是可以滚动数组以节省内存。
(i,j) 依赖于(i-1,j-1), (i-1,j), (i,j-1),因此只要按i,j递增的顺序就可以。
滚动数组d[0][] 表示d[i-1][], d[1][] 表示d[i][]。

还有就是处理边界情形(i,0)和(0,j)。
对于子问题(s[0..i], t[0..0]),
 d[i][0] = 有三种情形 8(。
这里我们可以很巧妙的处理为:
把s[0]当成空字符\epsilon, 实际比较的字符都在s[1..m]。
则d[i][0] 表示把s[0..i]变成空串t[0..0],显然是把i个字符都删除。
注意上面的索引是虚拟的,实际引用字符时都要减去1。

下面我们假设插入和删除操作度量一样,记为indel()。


import java.util.Arrays;

public class EditDistance
{
	char[] s, t;
	int[][] d; 
	public EditDistance(char[] s, char[] t)
	{
		int i, sn = s.length, tn = t.length;
		d = new int[sn][tn];
		for(i = 0; i < sn; ++i)Arrays.fill(d[i], -1);
		this.s = s; this.t = t;	
	}

	int editDistance(int i, int j)
	{
		int rep, ins, del;
		
		if(i < 0)return (j+1) * indel(' ');
		if(j < 0)return (i+1) * indel(' ');
		if(d[i][j] >= 0)return d[i][j];
		
		rep = editDistance(i-1, j-1) + replace(s[i], t[j]);
		ins = editDistance(i,   j-1) + indel(t[j]);
		del = editDistance(i-1, j  ) + indel(s[i]);
		//System.out.printf("[%d,%d,%d]", rep, ins, del);
		
		if(rep > ins)rep = ins;
		if(rep > del)rep = del;
		//System.out.printf("(%d,%d,%d) ", i, j, rep);
		return d[i][j] = rep;
	}

	int distance(){return editDistance(s.length-1, t.length-1);}

	int distance3()
	{
		int i, j;
		int rep, ins, del;
		int m = s.length, n = t.length;
		int[][] d = new int[2][n+1];

		for(j = 0; j <= n; ++j){
			d[0][j] =  j * indel(' '); 
		}

		for(i = 1; i <= m; ++i){
			d[1][0] = i * indel(' '); 
			for(j = 1; j <= n; ++j){
				rep = d[0][j-1] + replace(s[i-1], t[j-1]); 
				ins = d[1][j-1] + indel(t[j-1]);
				del = d[0][j] + indel(s[i-1]);				
				if(rep > ins)rep = ins;
				if(rep > del)rep = del;
				d[1][j] = rep;
			}
			for(j = 0; j <= n; ++j)d[0][j] = d[1][j];
		}		
		return d[1][n];
	}

	int replace(char a, char b)
	{
		if(a == b)return 0;
		return 1;
	}

	int indel(char a)
	{
		return 1;
	}
	
	public static void main(String[] arg)
	{
		String s0 = "Skina";// "thou shalt not"; 
		String t0 = "Skienna";// "you should not"; 
		char[] s =s0.toCharArray();
		char[] t =t0.toCharArray();
		int ans, ans2;

		EditDistance ed = new EditDistance(s, t);	
		ans = ed.distance();
		ans2 = ed.distance3();
		System.out.printf("ans = %d ans2 = %d%n", ans, ans2);
	}
}

/*
$ javac -encoding UTF-8 EditDistance.java && java EditDistance
ans = 2 ans2 = 2
*/

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值