Needleman Wunsch Algorithm - C++ Programming Tutorial
C++ Course / STL Algorithm / Needleman Wunsch Algorithm

Needleman Wunsch Algorithm

BLUF: Mastering Needleman Wunsch Algorithm is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Needleman Wunsch Algorithm

C++ is renowned for its efficiency. Learn how Needleman Wunsch Algorithm enables low-level control and high-performance computing in the tutorial below.

Overview of Sequence Alignment in Bioinformatics

Sequence alignment, a vital process in the field of bioinformatics, entails analyzing biological sequences like DNA, RNA, or proteins to recognize similarities and variances. This procedure plays a pivotal role in elucidating the evolutionary connections among various organisms, labeling genes, and interpreting the functional components present in genetic material. The Needleman-Wunsch algorithm stands out as one of the groundbreaking algorithms that transformed the landscape of sequence alignment.

Genesis of Needleman-Wunsch Algorithm

Crafted by Saul B. Needleman and Christian D. Wunsch back in 1970, the Needleman-Wunsch algorithm was created to fulfill the necessity for a technique that could globally align two sequences in a thorough manner. Before its introduction, local alignment techniques were prevalent, concentrating on pinpointing sub-sequences with significant resemblance. Nevertheless, to gain a more comprehensive insight into the relationships between sequences, researchers needed a solution that could align complete sequences.

Purpose and Significance

The main objective of the Needleman-Wunsch algorithm is to find the best alignment between two sequences by maximizing matching characters and strategically incorporating gaps to accommodate insertions or deletions. This method of global alignment guarantees that all positions in the sequences are taken into account, offering a comprehensive assessment of their similarities and variations. The algorithm plays a crucial role in bioinformatics applications, acting as a fundamental tool for a range of analyses such as database queries, evolutionary studies, and functional categorization.

Algorithmic Underpinnings

The Needleman-Wunsch algorithm utilizes a dynamic programming approach, which is a robust method in the field of computer science. This technique entails dissecting a intricate issue into interconnected smaller subproblems. When applied to sequence alignment, dynamic programming allows the algorithm to methodically assess every potential alignment and identify the most favorable one by progressing through a series of steps.

  1. Dynamic Programming Grid: The Scoring Grid

The core of the Needleman-Wunsch algorithm lies in the dynamic programming matrix, commonly known as the "score matrix." This table consists of rows and columns representing different alignment scenarios between two positions in the sequences. With n+1 rows and m+1 columns, where n signifies the initial sequence's length and m represents the second sequence's length, this matrix is pivotal in aligning sequences accurately.

The matrix is filled incrementally, with each cell's content indicating the total score of a specific alignment condition. These scores are determined by evaluating adjacent cells, resulting in a thorough depiction of potential alignments.

  1. Scoring Criteria: Matching, Non-matching, and Penalty for Gaps

Assigning scores to match, mismatch, and gap penalties is a fundamental aspect of the Needleman-Wunsch algorithm's scoring system. These scores play a pivotal role in guiding the algorithm's alignment decisions as it progresses through the process.

Match Score (S): A positive value is assigned to indicate alignment of characters in the sequences that are identical. This value signifies the likeness between the characters.

Mismatch Score (D): An assigned penalty score that comes into play when the characters at corresponding positions in the sequences are not identical. This score serves as a measure of dissimilarity.

Gap Penalty (G): A penalty value given for inserting a gap in the alignment, which represents either an insertion or deletion in the sequences.

Dynamic Programming Matrix

At the heart of the Needleman-Wunsch algorithm lies the dynamic programming matrix, commonly known as the "score matrix." This matrix is a two-dimensional array where each element represents a particular alignment state between two positions in the sequences. It is filled incrementally, with each element's value being calculated according to the neighboring elements. The dynamic programming matrix acts as a guide for the algorithm to traverse the various alignment scenarios.

Scoring System

A fundamental component of the Needleman-Wunsch algorithm pertains to the scoring mechanism, where values are allocated to match, mismatch, and gap penalties. These assigned values play a crucial role in guiding the algorithm's choices throughout the alignment process. Match scores play a constructive role in enhancing the alignment score, signifying likeness between characters, whereas mismatch scores denote differences. Gap penalties address the insertion of gaps within the alignment, penalizing their presence. Precise adjustment of these values guarantees the algorithm generates alignments that hold biological significance.

Dynamic Programming Recurrence Relation

The computation of values within the dynamic programming grid is dependent on a recursive connection. This connection specifies how the value of a particular cell is established by considering the values of neighboring cells. The recursive connection integrates the match, mismatch, and gap penalty values, offering a thorough approach to assessing the alignment conditions. The dynamic programming grid is formed by repetitively implementing the recursive connection to populate the values for every cell.

Algorithmic Workflow

  1. Initialization: Building the Base

Initializing the dynamic programming matrix marks the commencement of the algorithm. This matrix, a two-dimensional array, acts as the core for assessing alignment options. During this stage, the initial scores are established for the top row and the first column of the matrix.

  • Initialization of the Top Row:

The initial row of the matrix represents how the first sequence aligns with gaps in the second sequence. The value in each cell in this row is calculated by summing the score of the cell to its immediate left (indicating alignment with a gap in the second sequence) and the penalty for introducing a gap.

Matrix0=Matrix0+G

Here,

G symbolizes the penalty for introducing a gap. This procedure is repeated for every cell in the upper row, setting the total scores for alignments containing more and more gaps in the second sequence.

Initialization of the leftmost column:

The initial column of the matrix represents the positioning of the second sequence aligned with gaps in the initial sequence. Every cell within this column is calculated by summing the value from the cell directly above it (indicating alignment with a gap in the initial sequence) and the penalty assigned to the gap.

Matrixi=Matrixi-1+G

Similar to the initial row, this procedure repeats for every cell in the first column, determining the total scores for alignments with a growing quantity of spaces in the initial sequence.

The setup phase generates a grid containing values representing the total penalties incurred from inserting spaces in the alignment. This process establishes a foundation for analyzing potential alignment options.

  1. Filling the Matrix: Methodical Assessment of Alignment Conditions

With the matrix set up, the algorithm moves forward to populate the rest of the cells following the dynamic programming recurrence relation. This relation entails evaluating three possible actions at each cell: a match or mismatch (diagonal movement), introducing a gap in the first sequence (vertical movement), or introducing a gap in the second sequence (horizontal movement).

  • Dynamic Programming Recurrence Relation:

The standard format of the recurrence relation is as shown: (Difference in the subsequent sequence)

Matrixi=max

Matrixi-1+S (Match or Mismatch),

Matrixi-1+G (Gap in the first sequence),

Matrixi+G (Gap in the second sequence)

Here,

S denotes the match score or the penalty for a mismatch, and

G signifies the penalty for a gap in the sequence. The process chooses the action that yields the maximum score, indicating the best alignment scenario for the existing cell.

  • Sequential Matrix Population:

The process involves the algorithm moving through the unreached cells of the matrix, methodically computing scores according to the recursive formula. Beginning at the upper-left corner and progressing row by row, the score for each cell is established by evaluating the three possible moves. This sequence persists until the lower-right corner of the matrix is attained.

As each cell in the matrix gets populated, it holds a value that signifies the total effectiveness of the alignment state linked to that specific location. The step-by-step process of populating the matrix guarantees a thorough assessment of every conceivable alignment scenario.

  1. Backtracking: Forming the Best Alignment

Subsequently, after finalizing the dynamic programming matrix, the procedure initiates a backtracking phase to form the most efficient alignment. Commencing from the lower-right cell (which signifies the conclusion of the sequences), the process retraces its steps through the matrix, adhering to the route with the maximum scores.

  • Steps in Backtracking:

If the score in the current cell originates from a diagonal movement, it signifies a match or a mismatch between the aligned characters from the sequences, which are then appended to the alignment outcome.

Vertical Shift (Gap in Initial Sequence): When the score arises from a vertical shift, it signifies an absence in the initial sequence. The character aligned with the second sequence and a symbol for a gap are included in the alignment outcome.

Creating the Alignment:

When the score is a result of a horizontal move, it signifies a space in the second sequence. This involves adding the corresponding character from the initial sequence along with a space symbol to the alignment outcome.

  • Establishing the Alignment:

While retracing its steps through the matrix, the algorithm gradually builds the alignment outcome. This iterative procedure persists until it arrives at the upper-left corner of the matrix, symbolizing the commencement of the sequences.

The ultimate alignment showcases corresponding characters and added spaces, demonstrating the ideal global alignment of both sequences.

Significance of the Algorithmic Workflow:

Global Optimization: The Needleman-Wunsch algorithm guarantees global optimization by evaluating every potential alignment scenario within the dynamic programming matrix. The initialization, matrix population, and backtracking phases combine to pinpoint the best alignment that maximizes the total score.

The extensive alignment process ensures a detailed assessment of alignment options, enabling the algorithm to adjust to the various characters present in biological sequences. Through the evaluation of match scores, mismatch penalties, and gap penalties, the algorithm offers a comprehensive perspective on the relationships between sequences.

The algorithm's systematic approach allows for its utilization with sequences of different lengths and compositions, making it suitable for a wide range of applications. In bioinformatics, it is commonly employed to compare DNA, RNA, and protein sequences, offering essential information on evolutionary connections and functional descriptions.

Below is the program in C++

Example

#include <iostream>
#include <vector>

using namespace std;

const int matchScore = 2;
const int mismatchPenalty = -1;
const int gapPenalty = -1;

void printMatrix(const vector<vector<int>> &matrix) {
    for (const auto &row : matrix) {
        for (int val : row) {
            cout << val << "\t";
        }
        cout << endl;
    }
}

void needlemanWunsch(const string &sequence1, const string &sequence2) {
    int m = sequence1.size();
    int n = sequence2.size();

    // Initialize the dynamic programming matrix
    vector<vector<int>> dpMatrix(m + 1, vector<int>(n + 1, 0));

    // Initialize the first row and column with gap penalties
    for (int i = 1; i <= m; ++i) {
        dpMatrix[i][0] = dpMatrix[i - 1][0] + gapPenalty;
    }

    for (int j = 1; j <= n; ++j) {
        dpMatrix[0][j] = dpMatrix[0][j - 1] + gapPenalty;
    }

    // Fill the dynamic programming matrix
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            int match = dpMatrix[i - 1][j - 1] + (sequence1[i - 1] == sequence2[j - 1] ? matchScore : mismatchPenalty);
            int gap1 = dpMatrix[i - 1][j] + gapPenalty;
            int gap2 = dpMatrix[i][j - 1] + gapPenalty;

            dpMatrix[i][j] = max({match, gap1, gap2});
        }
    }

    // Print the dynamic programming matrix
    cout << "Dynamic Programming Matrix:" << endl;
    printMatrix(dpMatrix);
    cout << endl;

    // Backtrack to find the optimal alignment
    int i = m, j = n;
    string alignedSequence1, alignedSequence2;

    while (i > 0 || j > 0) {
        if (i > 0 && j > 0 && dpMatrix[i][j] == dpMatrix[i - 1][j - 1] + (sequence1[i - 1] == sequence2[j - 1] ? matchScore : mismatchPenalty)) {
            alignedSequence1 = sequence1[i - 1] + alignedSequence1;
            alignedSequence2 = sequence2[j - 1] + alignedSequence2;
            --i;
            --j;
        } else if (i > 0 && dpMatrix[i][j] == dpMatrix[i - 1][j] + gapPenalty) {
            alignedSequence1 = sequence1[i - 1] + alignedSequence1;
            alignedSequence2 = '-' + alignedSequence2;
            --i;
        } else {
            alignedSequence1 = '-' + alignedSequence1;
            alignedSequence2 = sequence2[j - 1] + alignedSequence2;
            --j;
        }
    }

    // Print the optimal alignment
    cout << "Optimal Alignment:" << endl;
    cout << alignedSequence1 << endl;
    cout << alignedSequence2 << endl;
}

int main() {
    string sequence1 = "AGTAC";
    string sequence2 = "GAGC";

    cout << "Sequence 1: " << sequence1 << endl;
    cout << "Sequence 2: " << sequence2 << endl << endl;

    needlemanWunsch(sequence1, sequence2);

    return 0;
}

Output:

Output

Sequence 1: AGTAC
Sequence 2: GAGC

Dynamic Programming Matrix:
0       -1      -2      -3      -4      -5
-1      2       1       0       -1     -2
-2      1       4       3       2

Explanation:

  1. Include Statements:
  • #include <iostream>: Includes the Input/Output Stream Library for input and output operations.
  • #include <vector>: Includes the Vector Library for dynamic array implementation.
  1. Constants Declaration:
  • const int matchScore = 2;: Defines a constant for the match score.
  • const int mismatchPenalty = -1;: Defines a constant for the mismatch penalty.
  • const int gapPenalty = -1;: Defines a constant for the gap penalty.
  1. Matrix Printing Function:
  • void printMatrix(const vector<vector<int>> &matrix): Declares a function to print a 2D matrix of integers.
  • for (const auto &row : matrix): Iterates through each row of the matrix.
  • for (int val : row): Iterates through each element in the row.
  • cout << val << "\t";: Prints each matrix element followed by a tab.
  • cout << endl;: Moves to the next line after printing each row.
  1. Needleman-Wunsch Function Initialization:
  • void needlemanWunsch(const string &sequence1, const string &sequence2): Declares the Needleman-Wunsch function that takes two input sequences.
  • int m = sequence1.size;: Gets the length of the first sequence.
  • int n = sequence2.size;: Gets the length of the second sequence.
  1. Matrix Initialization:
  • Initializes a 2D vector dpMatrix with dimensions (m + 1) x (n + 1) and initializes all elements to 0.
  1. Matrix Initialization with Gap Penalties:
  • Initializes the first row with cumulative gap penalties.
  • Initializes the first column with cumulative gap penalties.
  1. Matrix Filling Loop:
  • Nested loops iterate through the dynamic programming matrix, calculating scores based on the Needleman-Wunsch recurrence relation.
  1. Print Dynamic Programming Matrix:
  • Prints the dynamic programming matrix using the previously defined printMatrix function.
  1. Backtracking Loop:
  • Backtracks through the dynamic programming matrix to find the optimal alignment.
  1. Print Optimal Alignment:
  • Prints the optimal alignment found during the backtracking step.
  1. Main Function:
  • Initializes two example sequences.
  • Prints the sequences.
  • Calls the needlemanWunsch function with the input sequences.
  • Returns 0 to indicate successful program execution.
  • Time Complexity Analysis

The time complexity of the Needleman-Wunsch algorithm is mainly impacted by the two key phases during its operation: populating the matrix and tracing back.

Matrix Filling:

The interconnected loops handling the population of the matrix traverse through every individual cell within the dynamic programming grid. They evaluate three possible actions (matching, mismatching, inserting a gap in the first sequence, or inserting a gap in the second sequence) during each iteration. These loops execute for a total of m rows and n columns, with m and n representing the sizes of the input sequences.

In every cycle, instantaneous operations are executed to compute scores according to the recurrent formula, leading to a total time complexity of O(m⋅n) for the process of populating the matrix.

Backtracking:

The process of backtracking entails following the most efficient alignment route within the dynamic programming matrix. In the most unfavorable scenario, the algorithm might retrace its steps from the lower-right corner back to the upper-left corner, covering a total of m+n steps. Each backtrack iteration consists of operations that take constant time, thereby adding O(m+n) to the total time complexity.

Therefore, the primary determinant of the time complexity is the process of populating the matrix, and the Needleman-Wunsch algorithm operates with a time complexity of O(m⋅n).

Space Complexity

The storage complexity of the Needleman-Wunsch algorithm is mainly influenced by the memory needs of the dynamic programming matrix. Furthermore, a fixed quantity of memory is allocated for variables like indices and scores.

Dynamic Programming Matrix:

The dynamic programming grid is a 2D array with a size of (m+1)×(n+1), where m and n indicate the lengths of the input sequences. Every cell within this grid holds an integer value that signifies the total score for a particular alignment scenario. As a result, the space complexity associated with the dynamic programming matrix amounts to O(m⋅n).

Additional Space:

In addition to the matrix, the algorithm requires a fixed amount of extra space to store variables like matchScore, mismatchPenalty, gapPenalty, loop counters, and temporary variables. This consistent space complexity remains O(1).

Overall Space Complexity:

The total space complexity of the Needleman-Wunsch algorithm encompasses the space needed for the dynamic programming matrix and a fixed additional space. Hence, the algorithm's space complexity is O(m⋅n) + O(1), which can be simplified to O(m⋅n). To summarize, the Needleman-Wunsch algorithm demonstrates a quadratic time complexity (O(m⋅n)), primarily influenced by the matrix population phase, and a linear space complexity (O(m⋅n) because of the dynamic programming matrix's storage demands. This evaluation offers valuable insights into the algorithm's scalability and effectiveness, rendering it suitable for comparing sequences of different lengths in bioinformatics scenarios.

Applications in Bioinformatics

The Needleman-Wunsch algorithm is widely utilized in the fields of bioinformatics and computational biology for various applications:

In genomics, scientists utilize an algorithm to contrast a query sequence with a collection of established sequences, recognizing homologous segments and deducing functional similarities.

Phylogenetic Analysis: The algorithm assists in building phylogenetic trees through the alignment of sequences from various species. This process enables the exploration of evolutionary connections and differentiation.

Studying the role of genes and proteins is a fundamental part of molecular biology. The Needleman-Wunsch algorithm helps annotate functional components by identifying preserved sections within sequences.

Conclusion

The Needleman-Wunsch algorithm represents a significant breakthrough in the realm of bioinformatics, offering a reliable and adaptable solution for aligning sequences. By employing a dynamic programming strategy and a meticulously designed scoring system, this algorithm enables the determination of optimal global alignments for biological sequences. Its influence goes beyond mere alignment tasks, serving as a foundation for diverse analyses that enhance our comprehension of the intricate connections within the genetic blueprint. With technological progress and the expanding volume of biological information, the Needleman-Wunsch algorithm retains its essential position in the arsenal of bioinformatics professionals, playing a crucial role in deciphering the enigmatic details embedded in the fabric of life.

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience