求两个DNA的距离-最短编辑距离变形

题意如下:

脱氧核糖核酸即常说的DNA,是一类带有遗传信息的生物大分子。它由4种主要的脱氧核苷酸(dAMP、dGMP、dCMT和dTMP)通过磷酸二酯键连接而成。这4种核苷酸可以分别记为:A、G、C、T。


DNA携带的遗传信息可以用形如:AGGTCGACTCCA.... 的串来表示。DNA在转录复制的过程中可能会发生随机的偏差,这才最终造就了生物的多样性。

为了简化问题,我们假设,DNA在复制的时候可能出现的偏差是(理论上,对每个碱基被复制时,都可能出现偏差):

1. 漏掉某个脱氧核苷酸。例如把 AGGT 复制成为:AGT

2. 错码,例如把 AGGT 复制成了:AGCT

3. 重码,例如把 AGGT 复制成了:AAGGT

如果某DNA串a,最少要经过 n 次出错,才能变为DNA串b,则称这两个DNA串的距离为 n。

例如:AGGTCATATTCC 与 CGGTCATATTC 的距离为 2

你的任务是:编写程序,找到两个DNA串的距离。

【输入、输出格式要求】

用户先输入整数n(n<100),表示接下来有2n行数据。

接下来输入的2n行每2行表示一组要比对的DNA。(每行数据长度<10000)

程序则输出n行,表示这n组DNA的距离。

例如:用户输入: 3

AGCTAAGGCCTT 

AGCTAAGGCCT 

AGCTAAGGCCTT 

AGGCTAAGGCCTT 

AGCTAAGGCCTT 

AGCTTAAGGCTT

则程序应输出: 1 1 2

分析:(个人理解,因为找不到标准答案,所以非常希望有人指出本人的错误所在)

这道题是蓝桥杯决赛的题目,难度适中,就是题意有些歧义,因为歧义造成现在网上很多版本的答案,基本全篇一律,该算法的类似版本在《编程之美》中也出现过,求的是字符串的相似度,但是仔细阅读题意,会发现这个和一般的最短编辑距离的题目有点不一样,主要体现在该题目中对重码的定义,很多网上的答案不能得到让人满意的结果主要就是没有考虑这里的重码意思所在。

举个例子:

一般的最短编辑距离可以从这里去了解:http://www.cnblogs.com/grenet/archive/2010/06/01/1748448.html

一般的最短编辑距离也是三种修改方式:

替换、删除、插入

这里的替换和删除对应该题的:错码、漏码

要非常注意这里的插入不是直接对应该题的重码,重码是一种受限制的插入,举个列子,如根据插入概念,则

GGT和AGGT的编辑距离就是1,即在GGT前面添加A,但是回到这道题,可是不允许这样做的,GGT是源串,AGGT是目标串,GGT前面不应该直接产生一个A,因为从题意中可以看出,要添加字符,只能通过复制,所以他是一种受限制的插入,即:

GGT变成AGGT的过程是:

第一个字符发生一次错码(GGT->AGT),第二个字符发生一次重码(AGT->AGGT),即编辑距离应该是2

再举些容易被忽视的例子:

ACA-->CC 编辑距离为2(一次漏码,一次错码)

CC--->ACA 编辑距离为3(先发生一次错码CC-->AC,再发生一次重码:AC-->ACC,再发生一次错码:ACC-->ACA)

B-->BDD 编辑距离为4(先发生两次重码,再发生两次错码)

B-->BBD编辑距离为3(先发生两次重码,再发生一次错码)


只要注意到重码这个问题,其他的只要理解了LD算法,对其进行一个修改即可:

java代码如下:(哪里有疑问的请留言)

import java.util.Scanner;

public class js_09 {
	private static char[] A;//源串
	private static char[] B;//目的串
	private static int[][] tmp;//辅助数组
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		String aStr=scanner.nextLine();
		String bStr=scanner.nextLine();
		A=aStr.toCharArray();
		B=bStr.toCharArray();
		tmp=new int[A.length+1][B.length+1];
		for(int i=0;i<=A.length;i++){
			for(int j=0;j<=B.length;j++){
				tmp[i][j]=-1;
			}
		}
		//tmp[i][j]表示A[0...i]和B[0...j]的编辑距离
		System.out.println(process(0,0));
	}
	
	public static int process(int i,int j){
		if(j==B.length){//目标串匹配结束
			return A.length-i;//再发生A.length-i次漏码即可变成目标串
		}
		if(i==A.length){//源串匹配结束
			return getNumber(j);//源串再发生getNumber(j)次变化即可变成目标串
		}
		if(tmp[i][j]!=-1){//如果已经计算过,直接取值
			return tmp[i][j];
		}
		//如果相等
		if(A[i]==B[j]){
			if(tmp[i+1][j+1]==-1){
				tmp[i+1][j+1]=process(i+1,j+1);
			}
			return tmp[i+1][j+1];
		}else{
			//process(i+1,j)->漏码
			//process(i, j+1)->重码
			//process(i+1,j+1)->错码
			if(tmp[i+1][j]==-1){
				tmp[i+1][j]=process(i+1,j);
			}
			if(tmp[i+1][j+1]==-1){
				tmp[i+1][j+1]=process(i+1,j+1);
			}
			//会发生重码的条件
			if(j!=0&&B[j]==B[j-1]){//判断是否可能是重码导致的情况,可能是重码

				if(tmp[i][j+1]==-1){//重码
					tmp[i][j+1]=process(i,j+1);
				}
				//1表示发生一次编辑(三种情况中的一种)
				tmp[i][j]=getMinNumber(tmp[i+1][j], tmp[i][j+1], tmp[i+1][j+1])+1;
				return tmp[i][j];
			}else{//不会有重码
				int a=tmp[i+1][j];
				int b=tmp[i+1][j+1];
				tmp[i][j]=(a>b?b:a)+1;
				return tmp[i][j];
			}
		}	
	}
	/**
	 * 当源串匹配到最后一个字符时,目标串剩下的字符为:为从j开始到目标串结束
	 * 因为源串已经匹配结束,所以算空串变成目标串剩下的字符串的最少编辑值
	 * 如AB->ABCD,当源串匹配完B之后,目标串剩下CD,那么此时返回的值为:4
	 * 如B->BBD,返回值为3
	 * @param j
	 * @return
	 */
	public static int getNumber(int j){
		if(j==B.length)return 0;
		char c=A[A.length-1];
		int len=B.length;
		int lastLen=len-j;
		int num=0;
		for(;j<len;j++){
			if(c==B[j])num++;
		}
		return num+(lastLen-num)*2;
	}
	
	public static int getMinNumber(int a,int b,int c){
		a=a>b?b:a;
		return a>c?c:a;
	}
}
输入:

GGT
AGGY

输出:

3

你可能感兴趣的:(java,算法)