软考中的动态规划-DNA的编辑距离

445 阅读3分钟

简介

生物学上通常采用编辑距离来定义两个物种DNA序列的相似性,从而刻画物种之间的进化关系。

具体来说,编辑距离 是指将一个字符串变换为另一个字符串所需要的最小操作次数。

操作有三种:插入一个字符、删除一个字符以及修改一个字符,选择最小操作次数的一种。

思路

在日常生活中我们也能用到这个算法。之前有一篇投稿的文章由于疏忽,写错位了一段内容,我决定修改这部分内容让逻辑通顺。但是投稿文章最多只能修改 20 个字,且只支持增、删、替换操作(跟DNA的编辑距离问题一模一样),于是我就用算法求出了一个最优方案,只用了 16 步就完成了修改。

再比如高大上一点的应用,DNA 序列是由 A,G,C,T 组成的序列,可以类比成字符串。编辑距离可以衡量两个 DNA 序列的相似度,编辑距离越小,说明这两段 DNA 越相似,说不定这俩 DNA 的主人是远古近亲啥的。

我们来看一下基本流程,首先是比较两个字符串,从字符串的尾部开始扫描,如下图所示。

image.png

字符数组 s1s1s2s2 分别表示长度为 len1len1len2len2 的字符串

iji、j 表示两个字符串的下标。

d[i][j]d[i][j] 表示 编辑距离

如果其中一个字符串 len1len1 长度为 00,那么将这个字符串变换为另一个字符串的最小操作次数就等于另一个字符串的长度 len2len2 。也就是:

d[i][j]={i,len2=0j,len1=0d[i][j]= \begin{cases} i, len2 =0 \\ j,len1 = 0 \end{cases}

如果两个字符串在比较的过程字符相同,那可以直接扫描下一个字符,即:

d[i][j]={i,len2=0j,len1=0d[i1][j1],s[i1]=s[j1]d[i][j]= \begin{cases} i, len2 =0 \\ j,len1 = 0 \\d[i-1][j-1],s[i-1]=s[j-1] \end{cases}

如果两个字符串在比较的过程字符不同,那么就要选用三种操作中最小操作次数的一种,即:

d[i][j]={i,len2=0j,len1=0d[i1][j1],s[i1]=s[j1]min(d[i1][j]+1,d[i][j1]+1,d[i1][j1]+1)d[i][j]= \begin{cases} i, len2 =0 \\ j,len1 = 0 \\d[i-1][j-1],s[i-1]=s[j-1] \\min(d[i-1][j]+1, d[i][j-1] + 1, d[i-1][j-1]+1) \end{cases}

d[i1,j]+1d[i - 1, j] + 1 表示删除操作,j 保持不动,i 左移一位

image.png

d[i,j1]+1d[i, j - 1]+ 1 表示插入操作,i 保持不动,j 左移一位

image.png

d[i1,j1]+1d[i - 1, j - 1] + 1 表示替换操作,i,j 同时左移一位

image.png

 

代码框架

下面是题目的代码框架,我们要根据框架完成整个代码的实现。

常量和变量注释

s1s2s1,s2:两个字符数组

d[N][N]d[N][N]:二维数组

iji,j:循环变量

temptemp:临时变量

函数说明

min函数min函数:比较两个数的大小

editdistance函数editdistance函数:求解编辑距离

#include <stdio.h>

#define N 100

 

char s1[N] = "CTGA";

char s2[N] = "ACGCTA";

int d[N][N];


int min(int a, int b) {

    return a < b ? a : b;

}

 

int editdistance(char *str1, int len1, char *str2, int len2) {

    int i, j;

    int diff;

    int temp;

    for (i = 0; i <= len1; i ++ ) {

        d[i][0] = i;

    }

    for (j = 0; j <= len2; j ++ ) {

         (1)  ;

    }

    for (i = 1; i <= len1; i ++ ) {

        for (j = 1; j <= len2; j ++ ) {

            if (  (2)  ) {

                d[i][j] = d[i - 1][j - 1];

            } else {

                temp = min(d[i - 1][j] + 1, d[i][j - 1] + 1);

                d[i][j] = min(temp,   (3)  );

            }

        }

    }

    return   (4)  ;

}

最终答案

根据题目给出的递推公式,我们可以快速完成第一空。

d[i][j]={i,len2=0j,len1=0d[i1][j1],s[i1]=s[j1]min(d[i1][j]+1,d[i][j1]+1,d[i1][j1]+1)d[i][j]= \begin{cases} i, len2 =0 \\ j,len1 = 0 \\d[i-1][j-1],s[i-1]=s[j-1] \\min(d[i-1][j]+1, d[i][j-1] + 1, d[i-1][j-1]+1) \end{cases}
for (i = 0; i <= len1; i ++ ) { 
    d[i][0] = i; 
} 
for (j = 0; j <= len2; j ++ ) {
    d[0][j] = j ; // 第一空:若 len 1 = 0, d[0][j] = j 
}

第二空和第三空也是相当的简单,根据递推公式中的判断条件即可完成。

if (  str[i-1] == str2[j-1]  ) {

    d[i][j] = d[i - 1][j - 1];

} else {

    temp = min(d[i - 1][j] + 1, d[i][j - 1] + 1);
    
    d[i][j] = min(temp,   d[i-1][j-1] + 1);
    
    ...
}

我们来看第四空,返回值应为d[len1][len2]d[len1][len2],也就是最终的编辑距离。

总结

软考虽然考察了动态规划题目,但难度不大,大部分是 leetcodeleetcode 经典题目,而且一般都会提供递推公式给我们,我们要理解递推公式的含义,才能快速解出题目的答案。