Needleman-Wunsch全局序列比对算法

一、问题描述

尝试找到两个完整的序列 S1 和 S2 之间的最佳比对。如S1=GCCCTAGCG S2=GCGCAATG 如果设定每个匹配字符为1分,每个空格为-2分,每个不匹配为-1分,则下面的比对就是全局最优比对:S1’=GCCCTAGCG S2’=GCGC_AATG,连字符“_”代表空格。在 S2’ 中有五个匹配字符,一个空格(或者反过来说,在 S1’ 中有一个插入项),有三个不匹配字符。这样得到的分数是 (5×1) + (1×-2) + (3×-1) = 0,这是能够实现的最佳结果。

二、设计算法

1. 算法策略

Needleman-Wunsch全局序列比对算法:
它的思路与 LCS 算法相似。这个算法也使用二维表格,一个序列沿顶部展开,一个序列沿左侧展开。而且也能通过以下三个途径到达每个单元格:1.来自上面的单元格,代表将左侧的字符与空格比对。2.来自左侧的单元格,代表将上面的字符与空格比对。3.来自左上侧的单元格,代表与左侧和上面的字符比对(可能匹配也可能不匹配)。该单元格的值来自于以下3个中的最大值:(1)上方的值-2 (2)左边的值-2 (3)如果该单元格所在的行于所在的列对应的字符相等,则为左上值加1,否则为左上值-1。
在这里插入图片描述

2. 数据结构

① 得分矩阵
二维数组dp表示得分矩阵,s1位于dp数组的上方,s2位于dp数组的左方其中dp[i][j] 表示s1[0:j]与s2[0:i]的最高得分。
② 父节点矩阵
二维数组status记录当前节点的父节点。status[i][j] 记录dp[i][j] 的父节点。1代表左上角,2代表左边,4代表上边,若有多解也可表示,3=1+2,5=1+4,6=2+4,7=1+2+4。

3. 求解步骤

① 初始化得分矩阵
初始化第0行:所有节点的得分依次-2,且父节点均来自左边,status[0][j] = 2;
初始化第0列:所有节点的得分依次-2,且父节点均来自上边,status[i][0] = 4。
② 计算得分矩阵的每一项

状态转移方程(得分体系):dp[i][j] = max{ 1. dp[i-1][j-1] +/- 1 左上角
2. dp[i][j-1] - 2 左边s2为空
4. dp[i-1][j] - 2 上边s1为空
}

并填充status[i][j]。
③ 回溯,得到最优解
通过status数组进行回溯构造所有解,进行字符串拼接,最后反转得出所有正确解。
④ 输出打印
最优值(最高得分) = dp[n][m],打印由③所得的最优解s1和s2。

三、实现算法

1. Main.java

package com.全局序列比对;

import java.util.Scanner;

/**
 * @author fanb
 * @email  fanb@nwafu.edu.cn
 * 问题描述
 * 尝试找到两个完整的序列 S1 和 S2 之间的最佳比对。
 * 如果设定每个匹配字符为1分,每个空格为-2分,每个不匹配为-1分,
 *
 * 求解策略
 * Needleman-Wunsch(尼德曼-翁施)算法:
 * 它的思路与 LCS 算法相似。这个算法也使用二维表格,一个序列沿顶部展开,一个序列沿左侧展开
 * s1位于dp数组的上方,s2位于dp数组的左方
 * dp[i][j]得分矩阵 表示s1[0:j] 与 s2[0:i]的最高得分
 * 状态转移方程(得分体系) dp[i][j] = max{
 *                           1. dp[i-1][j-1] +/- 1 左上角
 *                           2. dp[i][j-1] - 2     左边s2为空
 *                           4. dp[i-1][j] - 2     上边s1为空
 *                           }
 * 回溯找出最优解 status[i][j] 表示dp[i][j] 的父节点 1.左上角 2.左边 4.上边
 *                          若有多解也可表示:3=1+2,5=1+4,6=2+4,7=1+2+4
 *
 * 时间复杂度 O(m*n)
 * 空间复杂度 O(m*n)
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("输入s1:");
        String s1 = scanner.next();
        System.out.print("输入s2:");
        String s2 = scanner.next();
        scanner.close();

        Solution solution = new Solution(s1, s2);
        int ans = solution.needlemanWunsch(); // 求解最优值
        System.out.println("最优值:"+ans);
        solution.print(); // 打印最优解
    }
}

class Solution{
    String s1,s2; // s1上侧 s2左侧
    int m, n; // m列 n行
    int[][] dp; // 得分矩阵
    int[][] status; // 记录父节点,1(2^0)代表左上,2(2^1)代表来自左边(左空格),4(2^2)代表来自上边(上空格)

    // 构造器
    public Solution(String s1, String s2) {
        this.s1 = s1;
        this.s2 = s2;
        this.m = s1.length();
        this.n = s2.length();
        this.dp = new int[n+1][m+1];
        this.status = new int[n+1][m+1];
        status[0][0] = 0;
        dp[0][0] = 0;
    }

    // Needleman-Wunsch 全局序列比对算法
    public int  needlemanWunsch(){
        // 初始化第0列
        for (int i = 1; i <= n; i++) {
            dp[i][0] = dp[i-1][0] - 2;
            status[i][0] = 4;
        }
        // 初始化第0行
        for (int i = 1; i <= m; i++) {
            dp[0][i] = dp[0][i-1] - 2;
            status[0][i] = 2;
        }
        // 动态规划,填满dp数组
        for (int i = 1; i <= n; i++) { // 行
            for (int j = 1; j <= m; j++) { // 列
                int leftTop = dp[i-1][j-1] + (s1.charAt(j-1) == s2.charAt(i-1) ? 1 : -1); // 左上角
                int left = dp[i][j-1] - 2; // 来自左边 左空格
                int top = dp[i-1][j] - 2; // 来自上边 上空格
                int state = 0;
                int max = Math.max(leftTop, left); // 当前位置最高得分
                max = Math.max(top,max);

                // 左上角
                if(leftTop == max) state += 1;
                // 来自左边 左空格
                if(left == max) state += 2;
                // 来自上边 上空格
                if(top == max) state += 4;

                status[i][j] = state;
                dp[i][j] = max;
               // System.out.print(max + " ");
            }
           // System.out.println();
        }
        return dp[n][m];
    }

    // print 回溯dp二维数组输出最优解
    public void print(){
        StringBuilder s11 = new StringBuilder();
        StringBuilder s22 = new StringBuilder();
        boolean kind = false; // 标志位,判断是否有多种情况
        int i, j;

        while(true){
            s11.append(' ');
            s22.append(' ');
            kind = false;
            i = n; // 行
            j = m; //列
            while(status[i][j] != 0){
                switch (status[i][j]){
                    case 1: // 只来自左上角
                        s11.append(s1.charAt(j-1));
                        s22.append(s2.charAt(i-1));
                        i--;
                        j--;
                        break;
                    case 2: // 只来自左空格
                        s11.append(s1.charAt(j-1));
                        s22.append('_');
                        j--;
                        break;
                    case 3: // 来自 1 + 2
                        // 若kind已经为true,说明已经走了一个分支,此时不需要-2
                        if(!kind){
                            status[i][j] -= 2;
                            kind = true;
                        }
                        s11.append(s1.charAt(j-1));
                        s22.append('_');
                        j--;
                        break;
                    case 4: // 只来自上空格
                        s11.append('_');
                        s22.append(s2.charAt(i-1));
                        i--;
                        break;
                    case 5: // 来自 1 + 4
                    case 6: // 来自 2 + 4
                    case 7: // 来自 1 + 2 + 4
                        // 若kind已经为true,说明已经走了一个分支,此时不需要-4
                        if(!kind){
                            status[i][j] -= 4;
                            kind = true;
                        }
                        s11.append('_');
                        s22.append(s2.charAt(i-1));
                        i--;
                        break;
                }
            }
            if(!kind) break;
        }
        System.out.println("s1:" + s11.reverse());
        System.out.println("s2:" + s22.reverse());
    }
}

2. 测试

测试用例
S1=GCCCTAGCG
S2=GCGCAATG

测试结果(存在多解)
最优值:0
s1: GCCCTAGCG GCCCTAGCG GCCCTAGCG
s2: GCGC_AATG GCGCAA_TG GCGCAAT_G

在这里插入图片描述

四、复杂度分析

(1)时间复杂度
初始化第0行T(m),初始化第0列T(n),填充得分矩阵T(mn)
T = T(m
n) + T(m) + T(n)
所以时间复杂度:O(m*n)

(2)空间复杂度
得分矩阵dp[n+1][m+1]占(n+1)(m+1),父节点矩阵status[n+1][m+1]占(n+1)(m+1)
S = 2mn + 2m + 2n + 2
所以空间复杂度:O(m*n)

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Needleman-Wunsch算法是一种用于比对两条生物序列(如DNA或蛋白质序列)的算法。它采用了动态规划的思想,通过构建一个二维矩阵来计算两条序列之间的最佳比对方式。它可以计算出两条序列之间的最高相似度,并用这个相似度来推断进化关系。 ### 回答2: Needleman-Wunsch算法是一种经典的序列比对算法,被广泛应用于生物信息学领域和DNA/RNA/蛋白质序列的比对工作中。该算法的核心思想是通过动态规划的方法,找到两个序列之间的最佳比对方案。 算法的步骤如下: 1. 初始化一个二维矩阵,大小为两个序列长度加1。矩阵的第一行和第一列分别对应两个序列的每个字符。 2. 初始化第一行和第一列,即给每个元素赋予相应的惩罚分数。一般来说,匹配得分为正,不匹配和缺失的得分为负。 3. 根据相应的匹配规则,计算每个矩阵元素的得分。矩阵中的每个元素都表示该位置匹配到的最佳得分。 4. 通过回溯的方式,根据得分矩阵确定最佳比对方案。从得分矩阵的右下角开始,根据当前位置的得分和其周围位置的得分,决定向上、向左还是左上方向移动。 5. 根据比对方案,生成最佳比对序列。 Needleman-Wunsch算法具有以下特点: 1. 能够找到两个序列之间的全局最佳比对方案,即找到最大得分的比对方式。 2. 能够处理序列长度不等的情况,能够对缺失或插入的位置进行补全。 3. 对于大规模的序列比对算法的时间复杂度较高,需要额外的计算资源。 4. 算法中的得分矩阵可以用于表示序列的相似性或差异性。 Needleman-Wunsch算法的应用广泛,例如在基因组学研究中,可以比对不同物种的基因组序列,寻找共同的基因功能区域。在药物设计中,可以比对蛋白质序列,寻找同源蛋白质并预测其结构和功能。此外,该算法还可以应用于DNA测序中,对测序结果进行比对和校正。 总之,Needleman-Wunsch算法是一种有效的序列比对算法,在生物信息学和相关领域具有重要的应用价值。 ### 回答3: Needleman-Wunsch算法是一种常见的序列比对算法,用于比较两个序列之间的相似性。它是由Saul Needleman和Christian Wunsch于1970年提出的,是一种全局比对算法,适用于字符串、蛋白质序列或DNA序列的比对。 需要进行比对的两个序列被放置在一个二维的矩阵中。算法根据预先定义的匹配得分、替换得分和惩罚值,计算出每个位置的得分。在计算的过程中,需要考虑序列间插入或删除字符的成本。 算法的具体步骤如下: 1. 初始化一个空的二维矩阵,矩阵的大小是两个序列的长度加一。 2. 在矩阵的边缘填充惩罚值。 3. 从矩阵的左上角开始,计算每个位置的得分。得分是根据上方、左方和左上方的得分和匹配情况计算的。 4. 根据得分确定最佳的替换、匹配或删除操作,并将对应的字符插入到比对结果中。 5. 重复步骤3和4,直到到达矩阵的右下角。 6. 根据得分矩阵构建最佳比对结果。 Needleman-Wunsch算法的时间复杂度为O(n^2),其中n是序列的长度。它可以找到两个序列之间的最佳比对结果,但可能会受限于较长序列的内存需求。虽然算法的计算量较大,但由于它的准确性和全局比对的能力,在生物信息学领域得到广泛应用,例如蛋白质结构的比对和进化树的构建等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值