最长公共子序列与字符串的相似度问题

字符串相似度与最长公共子序列

     字符串的相似性:如果将一个串转换成为另一个串所需的操作数最少,那么可以说这两个串是相似的。另外一种权衡的方法是,寻换第三个串s3,如果s3都出现在s1和s2中,且出现的顺序相同,但不要求在s1和s2中连续,那么s3的长度越大,就说明相似度越高。

         后一种对相似度概念命名为最长公共子序列。

         1、最长公共子序列的特征

         如果用暴力搜索的方法求解LCS问题,就要穷举X的所有子序列,对每个子序列进行检查,看它是否是Y的子序列,记录找到的最长的子序列。X对应下标人格集合{1,2,3……m}的一个子集,那么X的子序列就有2^m个。

         但其实LCS是具有最优子结构的:

         令X={x1,x2,x3……xm}, Y={y1,y2,y3……yn}。Z={z1,z2,z3……zk}为X和Y的任意LCS.则有:

         (1)如果xm=yn, 则zk = xm = yn,且Zk-1是Xm-1与Yn-1的LCS.

           (2)如果xm!=yn,则zk!=xm意味着Zk-1是Xm-1与Y的LCS

           (3)如果xm!=yn,则zk!=yn意味着Zk-1是Yn-1与X的LCS

      

      2、递归解

         用dp[i][j]表示Xi与Yj的LCS的长度。

         dp[i][j] = 0   if i = 0  or j =0

         dp[i][j] = dp[i-1][j-1] +1   if  xi = yj

         dp[i][j] = max(dp[i-1][j], dp[i][j-1])  if x1!=yj

 

     代码如下:

// LCS.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <string.h>
#include <iostream>

using namespace std;

#define MaxLength 100

int max(int a,int b)
{
	return a>=b?a:b;
}

//既保存了长度值,又记录了最长公共子序列
void LCS_Length(char x[], char y[], int m, int n, int c[][MaxLength], int b[][MaxLength]) 
{
	for(int i=0;i<m;i++)
		c[i][0] = 0;
	for(int j=0;j<n;j++)
		c[0][j] = 0;
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(x[i-1] == y[j-1])
			{
				c[i][j] = c[i-1][j-1] + 1;
				b[i][j] = 0;
			}
			else if(c[i-1][j]>=c[i][j-1])
			{
				c[i][j] = c[i-1][j];
				b[i][j] = 1;
			}
			else
			{
				c[i][j] = c[i][j-1];
				b[i][j] = -1;
			}
				
		}
	}
	cout<<"LCS = "<<c[m][n]<<endl;
}

int LCS(char x[],char y[],int m,int n, int c[][MaxLength])
{
	int i = 0, j =0;
	for(i = 0;i<m;i++)
		c[i][0] = 0;
	for(j = 0;j<n;j++)
		c[0][j] = 0;
	for(i = 1 ;i<=m;i++)
	{
		for(j = 1;j<=n;j++)
		{
			if(x[i-1]==y[j-1])
				c[i][j] = c[i-1][j-1]+1;
			else
				c[i][j] = max(c[i-1][j],c[i][j-1]);
		}
	}
	return c[m][n];
}
void Print_LCS(int b[][MaxLength], char x[], int m, int n)
{
	if(m==0||n==0)
		return;
	if(b[m][n] == 0)
	{
		Print_LCS(b,x,m-1,n-1);
		printf("%c  ",x[m-1]);
	}
	else if(b[m][n] == 1)
		Print_LCS(b,x,m-1,n);
	else
		Print_LCS(b,x,m,n-1);
}
int _tmain(int argc, _TCHAR* argv[])
{
	char x[8] = "abcbdab";
    char y[7] = "bdcaba";
	int c[MaxLength][MaxLength], b[MaxLength][MaxLength];
	LCS_Length(x,y,7,6,c,b);
	Print_LCS(b,x,7,6);
	cout<<"LCS = "<<LCS(x,y,7,6,c);
	return 0;
}

     那么对于字符串相似度的前一种解释,延伸出这样一道算法题目:

    计算字符串的相似度,定义了一套操作方法,把两个不同的字符串变得相同,具体操作方法:

    (1) 修改一个字符;(2) 增加一个字符;(3) 删除一个字符。

    如果所进行的操作数越少,相似度就越高,那么这道题目就转换成了,将两个字符串变为相同的字符串,所需的最小操作数。

    对于两个字符:A=xabcdae, B=xfdfa。它们的第一个字符是相同的,那么只需要计算A[2……7]和B[2……5]的距离就可。如果第一个字符不同,那么可以进行这样的操作:

    (1)删除A串的第一个字符,然后计算A[2->lenA]与B[1->lenB]。

    (2)删除B串的第一个字符,然后计算A[1->lenA]与B[2->lebB]。

    (3)修改A串的第一个字符为B串的第一个字符,然后计算A[2……lenA]与B[2……lenB]

    (4)修改B串的第一个字符为A串的第一个字符,然后计算A[2……lenA]与B[2……lenB]

    (5)增加A串的第一个字符到B串的第一个字符前,然后计算A[1……lenA]与B[2……lenB]

    (6)增加B串的第一个字符到A串的第一个字符前,然后计算A[2……lenA]与B[1……lenB]

   即,经过一步操作后,将A[1->lenA]与B[2->lenB]变为相同的字符串;将A[2->lenA]与B[1->lenB]变为相同的字符串;将A[2->lenA]与B[2->lenB]变为相同的字符串。这样求最小的操作数,就是求这三种情况的最小值。

   令dp[i][j]表示A从第i个字符开始,B从第j个字符开始,所需要的操作数。

    那么if  A[i]!=B[j] ,  dp[i][j] = min(dp[i+1][j] , dp[i][j+1], dp[i+1][j+1]) +1

          else dp[i][j] = dp[i+1][j+1]

   
部分代码如下:

int DP(int n, int m)
{
	int i = 0,j=0;
	//初始化dp,
	for(i=0;i<n;i++)
		dp[i][m] = MaxLength;
	for(j=0;j<m;j++)
		dp[n][j] = MaxLength;

	for(i = n-1;i>0;i--)
	{
		for(j = m-1;j>0;j--)
		{
			if(X[i]==Y[j])
				dp[i][j] = dp[i+1][j+1];
			else
				dp[i][j] = min(dp[i+1][j],dp[i][j+1],dp[i+1][j+1])+1;
		}
	}

	return dp[0][0];
	
}


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值