问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

java怎么写求最长的公共子序列

发布网友 发布时间:2022-05-13 20:23

我来回答

2个回答

懂视网 时间:2022-05-15 02:35

最长公共子序列(longest common sequence)和最长公共子串(longest common substring)不是一回事儿,下面这篇文章主要给大家介绍了关于javascript实现最长公共子序列的相关资料,需要的朋友可以参考下。

介绍

最长公共子序列(Longest Common Subsequence LCS)是从给定的两个序列X和Y中取出尽可能多的一部分字符,按照它们在原序列排列的先后次序排列得到。LCS问题的算法用途广泛,如在软件不同版本的管理中,用LCS算法找到新旧版本的异同处;在软件测试中,用LCS算法对录制和回放的序列进行比较,在基因工程领域,用LCS算法检查患者DNA连与键康DNA链的异同;在防抄袭系统中,用LCS算法检查论文的抄袭率。LCS算法也可以用于程序代码相似度度量,人体运行的序列检索,视频段匹配等方面,所以对LCS算法进行研究具有很高的应用价值。

基本概念

子序列(subsequence): 一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序)。例如序列<A,B,C,B,D,A,B>的子序列有:<A,B>、<B,C,A>、<A,B,C,D,A>等。

公共子序列(common subsequence): 给定序列X和Y,序列Z是X的子序列,也是Y的子序列,则Z是X和Y的公共子序列。例如X=[A,B,C,B,D,A,B],Y=[B,D,C,A,B,A[,那么序列Z=[B,C,A]为X和Y的公共子序列,其长度为3。但Z不是X和Y的最长公共子序列,而序列[B,C,B,A]和[B,D,A,B]也均为X和Y的最长公共子序列,长度为4,而X和Y不存在长度大于等于5的公共子序列。对于序列[A,B,C]和序列[E,F,G]的公共子序列只有空序列[]。

最长公共子序列:给定序列X和Y,从它们的所有公共子序列中选出长度最长的那一个或几个。
子串: 将一个序列从最前或最后或同时删掉零个或几个字符构成的新系列。区别与子序列,子序列是可以从中间抠掉字符的。cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列

给一个图再解释一下:

我们可以看出子序列不见得一定是连续的,连续的是子串。

问题分析

我们还是从一个矩阵开始分析,自己推导出状态迁移方程。

首先,我们把问题转换成前端够为熟悉的概念,不要序列序列地叫了,可以认为是数组或字符串。一切从简,我们就估且认定是两个字符串做比较吧。

我们重点留意”子序列“的概念,它可以删掉多个或零个,也可以全部干掉。这时我们的第一个子序列为 空字符串 (如果我们的序列不是字符串,我们还可以 )!这个真是千万要注意到!许多人就是看不懂《算法导论》的那个图表,还有许多博客的作者不懂装懂。我们总是从左到右比较,当然了第一个字符串,由于作为矩阵的高,就垂直放置了。

x""BDCABA
""
A
B
C
D
A
B

假令X = "ABCDAB", Y="BDCABA",各自取出最短的序列,也就是空字符串与空字符串比较。LCS的方程解为一个数字,那么这个表格也只能填数字。两个空字符串的公同区域的长度为0.

x""BDCABA
""0
A
B
C
D
A
B

然后我们X不动,继续让空字符串出阵,Y让“B”出阵,很显然,它们的公共区域的长度为0. Y换成其他字符, D啊,C啊, 或者, 它们的连续组合DC、 DDC, 情况没有变, 依然为0. 因此第一行都为0. 然后我们Y不动,Y只出空字任串,那么与上面的分析一样,都为0,第一列都是0.

x""BDCABA
""0000000
A0
B0
C0
D0
A0
B0

LCS问题与背包问题有点不一样,背包问题还可以设置-1行,而最长公共子序列因为有空子序列的出现,一开始就把左边与上边固定死了。

然后我们再将问题放大些,这次双方都出一个字符,显然只有两都相同时,才有存在不为空字符串的公共子序列,长度也理解数然为1。

A为"X", Y为"BDCA"的子序列的任意一个

x""BDCABA
""0000000
A00001
B0
C0
D0
A0
B0

继续往右填空,该怎么填?显然,LCS不能大于X的长度,Y的从A字符串开始的子序列与B的A序列相比,怎么也能等于1。

x""BDCABA
""0000000
A0000111
B0
C0
D0
A0
B0

如果X只从派出前面个字符A,B吧,亦即是“”,“A”, "B", "AB"这四种组合,前两个已经解说过了。那我们先看B,${X_1} == ${Y_0}, 我们得到一个新的公共子串了,应该加1。为什么呢?因为我们这个矩阵是一个状态表,从左到右,从上到下描述状态的迁移过程,并且这些状态是基于已有状态 累加 出来的。 现在我们需要确认的是,现在我们要填的这个格子的值与它周围已经填好的格子的值是存在何种关系 。目前,信息太少,就是一个孤点,直接填1。

x""BDCABA
""0000000
A0000111
B01
C0
D0
A0
B0

然后我们让Y多出一个D做帮手,{"",A,B,AB} vs {"",B,D,BD},显然,继续填1. 一直填到Y的第二个B之前,都是1。 因为到BDCAB时,它们有另一个公共子序列,AB。

x""BDCABA
""0000000
A0000111
B011112
C0
D0
A0
B0

到这一步,我们可以总结一些规则了,之后就是通过计算验证我们的想法,加入新的规则或限定条件来完善。

Y将所有字符派上去,X依然是2个字符,经仔细观察,还是填2.

看五行,X再多派一个C,ABC的子序列集合比AB的子序列集合大一些,那么它与Y的B子序列集合大一些,就算不大,就不能比原来的小。显然新增的C不能成为战力,不是两者的公共字符,因此值应该等于AB的子序列集合。

×""BDCABA
""0000000
A0000111
B0111122
C01
D0
A0
B0

并且我们可以确定,如果两个字符串要比较的字符不一样,那么要填的格子是与其左边或上边有关,那边大就取那个。

如果比较的字符一样呢,稍安毋躁,刚好X的C要与Y的C进行比较,即ABC的子序列集合{"",A,B,C,AB,BC,ABC}与BDC的子序列集合{"",B,D,C,BD,DC,BDC}比较,得到公共子串有“”,B,D 。这时还是与之前的结论一样,当字符相等时,它对应的格子值等于左边与右边与左上角的值,并且左边,上边,左上边总是相等的。这些奥秘需要更严格的数学知识来论证。

假设有两个数组,A和B。A[i]为A的第i个元素,A(i)为由A的第一个元素到第i个元素所组成的前缀。m(i, j)为A(i)和B(j)的最长公共子序列长度。

由于算法本身的递推性质,其实只要证明,对于某个i和j:

m(i, j) = m(i-1, j-1) + 1 (当A[i] = B[j]时)

m(i, j) = max( m(i-1, j), m(i, j-1) ) (当A[i] != B[j]时)

第一个式子很好证明,即当A[i] = B[j]时。可以用反证,假设m(i, j) > m(i-1, j-1) + 1 (m(i, j)不可能小于m(i-1, j-1) + 1,原因很明显),那么可以推出m(i-1, j-1)不是最长的这一矛盾结果。

第二个有些trick。当A[i] != B[j]时,还是反证,假设m(i, j) > max( m(i-1, j), m(i, j-1) )。

由反证假设,可得m(i, j) > m(i-1, j)。这个可以推出A[i]一定在m(i, j)对应的LCS序列中(反证可得)。而由于A[i] != B[j],故B[j]一定不在m(i, j)对应的LCS序列中。所以可推出m(i, j) = m(i, j-1)。这就推出了与反正假设矛盾的结果。

得证。

我们现在用下面的方程来继续填表了。

程序实现

//by 司徒正美
function LCS(str1, str2){
 var rows = str1.split("")
 rows.unshift("")
 var cols = str2.split("")
 cols.unshift("")
 var m = rows.length 
 var n = cols.length 
 var dp = []
 for(var i = 0; i < m; i++){ 
 dp[i] = []
 for(var j = 0; j < n; j++){ 
 if(i === 0 || j === 0){
 dp[i][j] = 0
 continue
 }
 
 if(rows[i] === cols[j]){ 
 dp[i][j] = dp[i-1][j-1] + 1 //对角+1
 }else{
 dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1]) //对左边,上边取最大
 }
 }
 console.log(dp[i].join(""))//调试
 } 
 return dp[i-1][j-1]
 }

LCS可以进一步简化,只要通过挪位置,省去新数组的生成

//by司徒正美
function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 var dp = [new Array(n+1).fill(0)] //第一行全是0
 for(var i = 1; i <= m; i++){ //一共有m+1行
 dp[i] = [0] //第一列全是0
 for(var j = 1; j <= n; j++){//一共有n+1列
 if(str1[i-1] === str2[j-1]){ 
 //注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理
 dp[i][j] = dp[i-1][j-1] + 1 //对角+1
 } else {
 dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1]) 
 }
 }
 } 
 return dp[m][n];
}

打印一个LCS

我们再给出打印函数,先看如何打印一个。我们从右下角开始寻找,一直找到最上一行终止。因此目标字符串的构建是倒序。为了避免使用stringBuffer这样麻烦的中间量,我们可以通过递归实现,每次执行程序时,只返回一个字符串,没有则返回一个空字符串, 以 printLCS(x,y,...) + str[i] 相加,就可以得到我们要求的字符串。

我们再写出一个方法,来验证我们得到的字符串是否真正的LCS字符串。作为一个已经工作的人,不能写的代码像在校生那样,不做单元测试就放到线上让别人踩坑。

//by 司徒正美,打印一个LCS
function printLCS(dp, str1, str2, i, j){
 if (i == 0 || j == 0){
 return "";
 }
 if( str1[i-1] == str2[j-1] ){
 return printLCS(dp, str1, str2, i-1, j-1) + str1[i-1];
 }else{
 if (dp[i][j-1] > dp[i-1][j]){
 return printLCS(dp, str1, str2, i, j-1);
 }else{
 return printLCS(dp, str1, str2, i-1, j);
 }
 }
}
//by司徒正美, 将目标字符串转换成正则,验证是否为之前两个字符串的LCS
function validateLCS(el, str1, str2){
 var re = new RegExp( el.split("").join(".*") )
 console.log(el, re.test(str1),re.test(str2))
 return re.test(str1) && re.test(str2)
}

使用:

function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 //....略,自行补充
 var s = printLCS(dp, str1, str2, m, n)
 validateLCS(s, str1, str2)
 return dp[m][n]
}
var c1 = LCS( "ABCBDAB","BDCABA");
console.log(c1) //4 BCBA、BCAB、BDAB
var c2 = LCS("13456778" , "357486782" );
console.log(c2) //5 34678 
var c3 = LCS("ACCGGTCGAGTGCGCGGAAGCCGGCCGAA" ,"GTCGTTCGGAATGCCGTTGCTCTGTAAA" );
console.log(c3) //20 GTCGTCGGAAGCCGGCCGAA

打印全部LCS

思路与上面差不多,我们注意一下,在LCS方法有一个Math.max取值,这其实是整合了三种情况,因此可以分叉出三个字符串。我们的方法将返回一个es6集合对象,方便自动去掉。然后每次都用新的集合合并旧的集合的字任串。

//by 司徒正美 打印所有LCS
function printAllLCS(dp, str1, str2, i, j){
 if (i == 0 || j == 0){
 return new Set([""])
 }else if(str1[i-1] == str2[j-1]){
 var newSet = new Set()
 printAllLCS(dp, str1, str2, i-1, j-1).forEach(function(el){
 newSet.add(el + str1[i-1])
 })
 return newSet
 }else{
 var set = new Set()
 if (dp[i][j-1] >= dp[i-1][j]){
 printAllLCS(dp, str1, str2, i, j-1).forEach(function(el){
 set.add(el)
 })
 }
 if (dp[i-1][j] >= dp[i][j-1]){//必须用>=,不能简单一个else搞定
 printAllLCS(dp, str1, str2, i-1, j).forEach(function(el){
 set.add(el)
 })
 } 
 return set
 } 
 }

使用:

function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 //....略,自行补充
 var s = printAllLCS(dp, str1, str2, m, n)
 console.log(s)
 s.forEach(function(el){
 validateLCS(el,str1, str2)
 console.log("
输出LCS",el) }) return dp[m][n] } var c1 = LCS( "ABCBDAB","BDCABA"); console.log(c1) //4 BCBA、BCAB、BDAB var c2 = LCS("13456778" , "357486782" ); console.log(c2) //5 34678 var c3 = LCS("ACCGGTCGAGTGCGCGGAAGCCGGCCGAA" ,"GTCGTTCGGAATGCCGTTGCTCTGTAAA" ); console.log(c3) //20 GTCGTCGGAAGCCGGCCGAA

空间优化

使用滚动数组:

function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 var dp = [new Array(n+1).fill(0)],now = 1,row //第一行全是0
 for(var i = 1; i <= m; i++){ //一共有2行
 row = dp[now] = [0] //第一列全是0
 for(var j = 1; j <= n; j++){//一共有n+1列
 if(str1[i-1] === str2[j-1]){ 
 //注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理
 dp[now][j] = dp[i-now][j-1] + 1 //对角+1
 } else {
 dp[now][j] = Math.max( dp[i-now][j], dp[now][j-1]) 
 }
 }
 now = 1- now; //1-1=>0;1-0=>1; 1-1=>0 ...
 } 
 return row ? row[n]: 0
}

危险的递归解法

str1的一个子序列相应于下标序列{1, 2, …, m}的一个子序列,因此,str1共有${2^m}$个不同子序列(str2亦如此,如为${2^n}$),因此复杂度达到惊人的指数时间(${2^m * 2^n}$)。

//警告,字符串的长度一大就会爆栈
function LCS(str1, str2, a, b) {
 if(a === void 0){
 a = str1.length - 1
 }
 if(b === void 0){
 b = str2.length - 1
 }
 if(a == -1 || b == -1){
 return 0
 } 
 if(str1[a] == str2[b]) {
 return LCS(str1, str2, a-1, b-1)+1;
 }
 if(str1[a] != str2[b]) {
 var x = LCS(str1, str2, a, b-1)
 var y = LCS(str1, str2, a-1, b)
 return x >= y ? x : y
 }
 }

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

使用vue实现二级路由设置方法

react项目开发

在Vue-Router2.X中实现多种路由实现

热心网友 时间:2022-05-14 23:43

/*目标:输出两个字符串的所有公共最长子序列date:09-11-26BY:zggxjxcgx算法:判断较短串是否为较长串的子序列,如果是则得到结果;否则,对较短串进行逐个字符删除操作(将字符替换为'#'表示删除)。删除操作用递归函数进行实现。每层递归删除一个字符,若删除一个字符后未得到匹配子序列,则还原该字符所在位置。第n层递归未找到匹配子序列,则将递归层数加1,重复删除直到剩下空串。*/#include#includeintdep=0;/*较短串的长度*/intdepflag=0;/*下一步要探测的深度*/intlastflag=0;/*是否找到匹配子序列,1为找到*/intcount=0;/*目标结果的个数*/intmystrcmp(char*s1,char*s2)/*判断s2是否为s1的子串*/{while(*s1*s2)if(*s2=='#')s2++;elseif(*s2==*s1){s1++;s2++;}elses1++;while(*s2=='#')s2++;if(*s2=='\0')return1;return0;}voidpristr(char*str)/*打印最长子序列*/{if(0==count++)printf("\n公共最长子序列:\n");printf("%2d:",count);while(*str){if(*str!='#')printf("%c",*str);str++;}printf("\n");}/*递归函数求最长子序列。start控制下一个要检测的字符,deptemp控制递归的深度,first为's'表示第一层递归*/intfun(char*str1,char*str2,char*start,intdeptemp,charfirst){inti=0,j=0;char*s,cback;do{s=start;if('s'==first)deptemp=depflag;/*在第一层设置递归深度*/while(*s){if(*s=='#'){s++;continue;}cback=*s;*s='#';/*删除当前字符*/if(mystrcmp(str1,str2)){pristr(str2);lastflag=1;}/*找到匹配,将lastflag设为1,在完成深度为deptemp+1的探测(找到所有匹配)后退出递归*/elseif(deptemp>0)fun(str1,str2,s+1,deptemp-1,'n');/*深度探测,s+1表示从下一个位置开始删除*/*s=cback;s++;/*还原该位置的字符,以便下次进行探测*/}if('s'==first)++depflag;/*删除depflag+1个字符还未找到,则递归深度加1*/}while(depflagstrlen(st2))s1=st1,s2=st2;/*将s1设为较长的串*/elses1=st2,s2=st1;printf("\nstr1:%s\nstr2:%s\n",s1,s2);dep=strlen(s2);/*得到较短串的长度*/if(mystrcmp(s1,s2))pristr(s2);elseif(0==fun(s1,s2,s2,0,'s'))printf("\n没有公共元素!\n");//printf("%d\n",mystrcmp("afdebjewcwedw","abcdw#"));}
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
什么时候功率最大 最小 家电多少功率算高功率 大功率用电器在开启瞬间,需要比正常使用功率大很多的启动电流,这是为什 ... 大功率电器设备为什么不能一下开到最大 有一个节能灯突然坏了,是哪个件坏了? 我不知道是节能灯坏了,还是开关面板坏了,按下开关面板时,节能灯有时... 为什么节能灯老是烧坏 湖州南太湖新区包括龙之梦吗 南太湖新区有哪些地方 南太湖是哪里 java正则表达式的子序列是什么 miRNA的形成过程 变频空调漏电,接地后会不会产生耗申? 变频空调没接地线会漏电吗? 我的变频空调主机漏电找售后他们说变频的主机大多存在电漏电是不是真的 格力变频空调谦者为何漏电 为什么碰触变频空调外壳时会有漏电的感觉? 我家的变频空调怎么会有漏电的感觉了??? boost库的线程无法终止? 《披荆斩棘的哥哥》这档节目的比赛规则是什么? 小米平板系统0kb的东西可以删除吗? 小米平板2怎么显示网速 乌龟拉出这样的屎怎么回事 怎么办 急! 月入多少需要交1万以上的个人所得税? 乌龟拉出来大便不成形,怎么回事 乌龟粪便臭怎么办 怎么能最方便又干净的清理养陆龟的木箱?主要就是清理粪便用什么底材,怎么清理实用,方便,干净! 纳税1亿利润多少 乌龟大便干燥怎么办? 椰土冬眠乌龟拉屎怎么清洗 pattern类的用法 Ecxel,按照不同条件变换单元格序列后,该单元格还是显示以前选过的那个,如何在变更时自动清空? 31颗牙齿的人命运如何 三十一颗牙齿人的命运感情怎样 长三十颗牙齿正常吗 30颗牙正常吗 牙齿我有30颗牙齿正常吗 出句:暮雨一帘深重,打打秋风 【胡高哲】 将近三十长了一颗牙是什么呀 超级英雄每天去打打秋风会有意外收获 30颗牙齿代表什么意思 赵国为什么在邯郸、长平两战后元气大伤后还能吊打燕国? 我怎么会只有30颗牙齿?已经25岁了 家装用的地暖哪个牌子好,不会开裂? “打秋风”的由来 成年人长30颗牙是否正常 家装用的地暖哪个牌子好呢? 人有30颗牙齿算是正常人吗 史上最猛平乱名将,如何带领120人平定万人作乱? 专家讲讲家庭地暖系统用哪种比较好?