求取最长公共子序列

前言

LCS可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。另一方面,对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。

算法介绍

子序列:一个序列S任意删除若干个字符得到新序列T。

公共子序列:两个序列X和Y中都存在的项组成的序列。

最长公共子序列:两个序列X和Y的公共子序列中最长的序列。

例如:

字符串13455与245576的最长公共子序列为455

字符串acdfg与adfc的最长公共子序列为adf

 求取算法详解

第一种:自定义暴力求取方式

假定字符串X,Y的长度分别为m,n

X的一个子序列即下标序列{1, 2, …, m}的严格递增子序列,因此,X共有2m个不同子序列;同理,Y有2n个不同子序列,从而穷举搜索法需要指数时间O(2m*2n);

对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列;

显然,不可取。

针对该种算法进行改良,只对第一组数据进行依次列出,然后与第二组进行比较。其时间复杂度虽然也很高,但与2m*2n相比还是要少一些。

void lscChangeViolence(int *X, int *Y, int m) {
	int *AllX, *AllFlag, *result;
	AllX = (int*)malloc(sizeof(int)*m);
	AllFlag = (int*)malloc(sizeof(int)*m);
	result = (int*)malloc(sizeof(int)*m);
	for (int i = 0; i < m; i++) {
		AllX[i] = 0;
		AllFlag[i] = 0;
		result[i] = -1000;
	}

	int count = 1;
	
	while (count < pow(2, m)) {
		int num = 0;
        // 按照取某一位的值和不取某一位的值,将X中的某些值赋值给AllX
		for (int i = 0; i < m; i++) {			
			if (((count >> i) & 1) == 1) {
				AllX[num] = X[i];
				num++;
			}
		}
		
        // 给定比较数组
		int *compare;
		compare = (int*)malloc(sizeof(int)*(num));
		for (int i = 0; i < num; i++) {
			compare[i] = AllX[i];
			
		}
		int comNum = 0;
		int jump = 0;
		int maxNum = -1;
		int start = 0;
        // 如果子数组与第二组匹配上,则记录长度,并记下数据
		for (int i = 0; i < num; i++) {
			if (start > n) {
				break;
			}
			for (int j = start; j < n; j++) {
				if (compare[i] == Y[j]) {
					start = j + 1;
					comNum++;
					break;
				}
				
			}
			if (comNum == i) {
				break;
			}
		}
		if (comNum > maxNum) {
			maxNum = comNum;
			for (int k = 0; k < maxNum; k++) {
				result[k] = compare[k];
			}
		}
		count++;
	}

	for (int i = 0; i < 5; i++) {
		if (result[i] != -1000) {
			printf("result[%d]=%d\n", i, result[i]);
		}
	}
}

第二种:动态规划法

字符串X,长度为m,从1开始数;

字符串Y,长度为n ,从1开始数;

LCS(X , Y) 为字符串X和Y的最长公共子序列。

若xm=yn(最后一个字符相同),则:Xm与Yn的最长公共子序列Zk的最后一个字符必定为xm或yn。

LCS(Xm , Yn) = LCS(Xm-1 , Yn-1) + xm

若xm≠yn,则:要么LCS(Xm,Yn)=LCS(Xm-1, Yn),要么LCS(Xm,Yn)=LCS(Xm, Yn-1)。

则:

LCS(X_{m},Y_{n})=\left\{\begin{matrix}LCS(X_{m-1},Y_{n-1}) \\ max\left \{ LCS(X_{m-1},Y_{n}),LCS(X_{m},Y_{n-1}) \right \} \end{matrix}\right.\begin{matrix} x_{m}=y_{n} \\ x_{m}\neq y_{n} \end{matrix}   

使用二维数组C[m,n]。

c[i,j]记录序列Xi和Yj的最长公共子序列的长度。

当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。

c(i,j)=\left\{\begin{matrix} 0\\ c(i-1,j-1)\\ max\left \{ c(i-1,j),c(i,j-1)) \right \} \end{matrix}\right.\begin{matrix} i=0,j=0\\ i>0,j>0,x_{i}=y_{j}\\ i>0,j>=0,x_{i}\neq y_{j} \end{matrix}

使用二维数据B[m,n],其中,b[i,j]标记c[i,j]的值是由哪一个子问题的解达到的。即c[i,j]是由c[i-1,j-1]+1或者c[i-1,j]或者c[i,j-1]的哪一个得到的。取值范围为Left,Top,LeftTop三种情况。
 

// 根据规则将c[i][j]进行赋值,获取方向数组b[i][j]
void lcs_length(char *X, char *Y, int mLength, int nLength,int **C,char **B) {

	int num = 0;
	for (int i = 1; i < mLength + 1; i++) {
		for (int j = 1; j < nLength + 1; j++) {
			if (X[i-1] == Y[j-1]) {
				C[i][j] = C[i - 1][j - 1] + 1;
				B[i][j] = 'x';
			}
			else if (C[i - 1][j] >= C[i][j - 1]) {
				C[i][j] = C[i - 1][j];
				B[i][j] = 's';
			}
			else {
				C[i][j] = C[i][j - 1];
				B[i][j] = 'z';
			}
		}
	}

	for (int i = 0; i < mLength + 1; i++) {
		for (int j = 0; j < nLength + 1; j++) {
			printf("C[%d][%d]=%d  ", i, j, C[i][j]);
		}
		printf("\n");
	}

}

下面打印最长公共子序列

// 从最后的位置进行索引,按照方向依次寻找数据,将标记为x的数据打印即为最长子数组
void lcsPrint(char **B, char *X, int i, int j) {
	if (i == 0 || j == 0) {
		return;
	}
	if (B[i][j] == 'x') {
		lcsPrint(B, X, i - 1, j - 1);
		printf("X[%d]=%c\n", i - 1, X[i - 1]);
	}
	else if (B[i][j] == 's') {
		lcsPrint(B, X, i - 1, j);
	}
	else {
		lcsPrint(B, X, i, j - 1);
	}
}

参考资料:

七月算法: https://www.julyedu.com/

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 答案:下面是最长公共子序列c的代码:int lcs(char *X, char *Y, int m, int n) { int L[m+1][n+1]; int i, j; /* 下面的循环填充二维数组L[][]。 */ for (i=0; i<=m; i++) { for (j=0; j<=n; j++) { if (i == 0 || j == 0) L[i][j] = 0; else if (X[i-1] == Y[j-1]) L[i][j] = L[i-1][j-1] + 1; else L[i][j] = max(L[i-1][j], L[i][j-1]); } } /* 下面的代码用于最长公共子序列的长度 */ return L[m][n]; } ### 回答2: 最长公共子序列(Longest Common Subsequence)是指两个序列中都能找到的最长的子序列。以下是一个用动态规划解决最长公共子序列问题的代码。 假设有两个序列A和B,长度分别为m和n。我们用一个二维数组dp来保存最长公共子序列的长度。dp[i][j]表示序列A的前i个元素和序列B的前j个元素的最长公共子序列的长度。 具体步骤如下: 1. 首先,初始化dp数组的第一行和第一列为0,即dp[0][j] = dp[i][0] = 0。 2. 然后,遍历整个二维数组dp,根据状态转移方程来计算dp[i][j]的值。如果A的第i个元素和B的第j个元素相等,则dp[i][j] = dp[i-1][j-1] + 1;否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。 3. 最后,dp[m][n]即为序列A和序列B的最长公共子序列的长度。 下面是伪代码实现: function longestCommonSubsequence(A, B): m = length(A) n = length(B) dp = array(m+1, n+1) for i from 0 to m: dp[i][0] = 0 for j from 0 to n: dp[0][j] = 0 for i from 1 to m: for j from 1 to n: if A[i] == B[j]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) return dp[m][n] 注意,上述伪代码还需要对序列A和B进行索引的调整,因为数组的索引通常是从1开始的,而不是从0开始的。 最终的结果就是两个序列的最长公共子序列的长度。 ### 回答3: 最长公共子序列(Longest Common Subsequence)是一种常见的动态规划问题,可以采用动态规划的方法解决。下面给出一个用Python语言实现的最长公共子序列的代码: ```python def longest_common_subsequence(s1, s2): m, n = len(s1), len(s2) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if s1[i - 1] == s2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) lcs_length = dp[m][n] lcs = [''] * lcs_length i, j = m, n while i > 0 and j > 0: if s1[i - 1] == s2[j - 1]: lcs[lcs_length - 1] = s1[i - 1] i -= 1 j -= 1 lcs_length -= 1 elif dp[i - 1][j] > dp[i][j - 1]: i -= 1 else: j -= 1 return ''.join(lcs) s1 = "ABCD" s2 = "ACDF" lcs = longest_common_subsequence(s1, s2) print(lcs) ``` 以上代码中,首先定义一个二维数组dp来存储最长公共子序列的长度。然后利用两个嵌套的循环遍历字符串s1和s2,当两个字符相等时,可以从当前字符向前移动,并将dp[i][j]更新为dp[i-1][j-1]+1,否则,分别向上或向左移动,取dp[i-1][j]和dp[i][j-1]中的较大值。最后,根据dp数组的结果,通过回溯可以最长公共子序列。输出结果为"ACD"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值