Lesson · 40 min · Free
Edit Distance & Sequence Alignment
Edit Distance & Sequence Alignment When we compare two biological sequences—whether DNA, RNA, or protein—one of the most fundamental questions we can ask is: how different are they? The edit distance (also called Lev
Edit Distance & Sequence Alignment
When we compare two biological sequences—whether DNA, RNA, or protein—one of the most fundamental questions we can ask is: how different are they? The edit distance (also called Levenshtein distance) provides a rigorous answer. It is defined as the minimum number of single-character operations required to transform one sequence into another, where the allowed operations are insertion , deletion , and substitution . For example, transforming GACT into GCAT requires two edits: delete the A at position 2, then insert an A before the final T . In bioinformatics, edit distance is the mathematical foundation of sequence alignment . An alignment arranges two sequences so that corresponding residues are placed in columns, with gaps introduced where one sequence has a character the other lacks. The number and type of edits in an optimal alignment directly reflect evolutionary events: substitutions correspond to point mutations, while insertions and deletions (collectively called indels ) correspond to larger genomic rearrangements. Closely related species share sequences with low edit distance; distant organisms accumulate more edits over evolutionary time. Computing edit distance by brute force—trying every possible combination of edits—is computationally infeasible because the number of possibilities grows exponentially with sequence length. Fortunately, we can solve the problem efficiently using dynamic programming . The key insight is that the edit distance between two prefixes of the sequences can be computed from the edit distances of shorter prefixes. We build a two-dimensional table D where D[i][j] holds the edit distance between the first i characters of sequence A and the first j characters of sequence B. The recurrence relation is: D[i][j] = min(D[i-1][j] + 1, D[i][j-1] + 1, D[i-1][j-1] + cost) , where cost is 0 if the characters match and 1 if they are substituted. The first term represents deleting from A, the second inserting into A, and the third matching or substituting. Filling the table row by row takes O(m × n) time and space, where m and n are the sequence lengths—a dramatic improvement over exponential brute force. The classic bioinformatics application of this idea is the Needleman–Wunsch algorithm , published in 1970, which performs global alignment of two sequences. Rather than simply counting edits, it uses a scoring scheme : matches receive positive scores, mismatches and gaps receive penalties. This lets us weight biologically meaningful events realistically—for instance, transitions (purine↔purine or pyrimidine↔pyrimidine) may be penalized less than transversions. A related algorithm, Smith–Waterman, modifies the recurrence to find local alignments , useful when two sequences share only a conserved domain or motif. The first code example below implements a straightforward edit distance using dynamic programming: def edit_distance(seq_a, seq_b): """Compute the Levenshtein edit distance between two sequences.""" m, n = len(seq_a), len(seq_b) # Build (m+1) x (n+1) DP table D = [[0] * (n + 1) for _ in range(m + 1)] # Base cases: transforming to/from an empty sequence for i in range(m + 1): D[i][0] = i # delete all i characters of seq_a for j in range(n + 1): D[0][j] = j # insert all j characters of seq_b # Fill the table for i in range(1, m + 1): for j in range(1, n + 1): if seq_a[i - 1] == seq_b[j - 1]: cost = 0 # match: no edit needed else: cost = 1 # substitution D[i][j] = min( D[i - 1][j] + 1, # deletion D[i][j - 1] + 1, # insertion D[i - 1][j - 1] + cost # match or substitution ) return D[m][n] # Example usage print(edit_distance("GACT", "GCAT")) # Output: 2 print(edit_distance("ATCG", "ATCG")) # Output: 0 (identical) print(edit_distance("ACGT", "TAGC")) # Output: 3 The second example extends this into a simplified Needleman–Wunsch global alignment with a scoring matrix, and reconstructs the actual aligned sequences by tracing back through the DP table: def needleman_wunsch(seq_a, seq_b, match=2, mismatch=-1, gap=-2): """Global alignment with traceback (Needleman-Wunsch).""" m, n = len(seq_a), len(seq_b) score = [[0] * (n + 1) for _ in range(m + 1)] # Initialize first row and column with cumulative gap penalties for i in range(1, m + 1): score[i][0] = i * gap for j in range(1, n + 1): score[0][j] = j * gap # Fill scoring matrix for i in range(1, m + 1): for j in range(1, n + 1): diag = score[i - 1][j - 1] + ( match if seq_a[i - 1] == seq_b[j - 1] else mismatch) up = score[i - 1][j] + gap # gap in seq_b left = score[i][j - 1] + gap # gap in seq_a score[i][j] = max(diag, up, left) # Traceback from bottom-right corner to build alignment align_a, align_b = [], [] i, j = m, n while i > 0 or j > 0: if i > 0 and j > 0 and score[i][j] == score[i - 1][j - 1] + ( match if seq_a[i - 1] == seq_b[j - 1] else mismatch): align_a.append(seq_a[i - 1]) align_b.append(seq_b[j - 1]) i, j = i - 1, j - 1 elif i > 0 and score[i][j] == score[i - 1][j] + gap: align_a.append(seq_a[i - 1]) align_b.append('-') i -= 1 else: align_a.append('-') align_b.append(seq_b[j - 1]) j -= 1 return score[m][n], ''.join(reversed(align_a)), ''.join(reversed(align_b)) # Example usage s, a1, a2 = needleman_wunsch("GATTACA", "GCATGCU") print("Alignment score:", s) print(a1) # e.g., GATTACA-- print(a2) # e.g., G-AT-GCU Understanding these algorithms matters for pharmacy and biotech students because they underpin many practical tools: BLAST searches for homologous sequences, drug target identification through comparative genomics, phylogenetic tree construction, and mutation analysis in pharmacogenomics. When you interpret a percent-identity score or an E-value from a database search, you are looking at the output of edit-distance-based computations running at massive scale. Edit distance measures the minimum number of insertions, deletions, and substitutions needed to transform one sequence into another. Dynamic programming reduces the computation from exponential to O(m × n) time by solving overlapping subproblems and storing intermediate results in a table. Sequence alignment generalizes edit distance by using weighted scoring schemes (match rewards, mismatch and gap penalties) that reflect biological plausibility. Needleman–Wunsch performs global alignment of entire sequences; Smith–Waterman finds local alignments of conserved regions. Traceback through the DP matrix recovers the actual aligned sequences, not just the alignment score. These algorithms are the computational core of BLAST, phylogenetics, and comparative genomics tools used in drug discovery and pharmacogenomics. Practice exercise: Using the edit_distance function above as a starting point, modify it to also compute a similarity score instead of a distance: assign +1 for each match, −1 for each substitution, and −1 for each gap (this is the scoring scheme of a unit-cost Needleman–Wunsch). Run it on the two sequences ACGGCTA and AGGCTTA , and report both the optimal alignment score and the aligned strings. Then compare your result with the edit distance between the same two sequences, and write a short explanation of why the two numbers differ even though both algorithms use essentially the same dynamic programming table.
Watch the full lesson — free
This topic is part of Bioinformatics & Computational Genomics, a complete AI-narrated video course. Press play once and watch the entire lecture like a movie.
Start the course free →