BIOL 5/6800 — Intro to Comp Bio
Example 1 — Reading a genome you never see
You sequence a bacterial isolate and get back 4 million short reads, each ~150 bp, in random order, with errors.
The question: what is the genome?
The problem: this is a jigsaw puzzle with millions of pieces, no picture on the box, some pieces duplicated, and some pieces wrong.
Nobody solves this by hand. An assembly algorithm does — by building a graph of overlapping k-mers and finding a path through it.
Example 2 — Pathogen surveillance in real time
During SARS-CoV-2, sequences were deposited daily and phylogenies were rebuilt continuously to track which lineages were spreading where
That pipeline is a stack of algorithms:
If the tree takes six weeks to compute, the answer is useless. Speed is not a convenience here — it is the whole point.
How many possible trees relate \(n\) species?
\[(2n - 3)!!\]
| Taxa | Possible trees |
|---|---|
| 5 | 105 |
| 10 | ~\(3.4 \times 10^{7}\) |
| 20 | ~\(8.2 \times 10^{21}\) |
| 52 | ~\(2.8 \times 10^{80}\) |
There are roughly \(10^{80}\) atoms in the observable universe.
A biological question becomes a computational question when we have to specify exactly how to get from data to answer.
A finite, unambiguous sequence of steps that transforms an input into an output.
A recipe is an algorithm. So is long division. So is:
An algorithm is the idea; code is one expression of it
Does it give the right answer for every valid input, not just the one you tested?
Will it finish before your grant does? Will it fit in memory?
For most of biology, correctness is necessary but feasibility decides what you can actually ask
Rather than memorize algorithms, learn to recognize strategies:
These are not mutually exclusive; real tools mix them
Idea: enumerate every candidate solution, score each, keep the best
Pros
Cons
Biology: finding the optimal tree for 7 taxa (10,395 trees — fine). For 30 taxa — not fine, ever.
Idea: break the problem into smaller versions of itself, solve those, combine the results.
Each step throws away half the remaining data; searching 1 billion sorted values takes ~30 comparisons
Idea: the problem has overlapping subproblems. Solve each one once, store the answer, look it up later.
A canonical example in biology is the Needleman–Wunsch algorithm for aligning sequences
\[ F(i,j) = \max \begin{cases} F(i-1,\,j-1) + s(A_i, B_j) & \text{(match/mismatch)}\\ F(i-1,\,j) + g & \text{(gap in }B)\\ F(i,\,j-1) + g & \text{(gap in }A) \end{cases} \]
Every cell is computed from cells we already filled in; fill a table, then trace back.
Guaranteed optimal and way faster than enumerating alignments.
Example: Calculating Fibonacci numbers (\(F_{n}\))
| \(n\) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | \(\ldots\) |
| \(F_{n}\) | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | \(\ldots\) |
\(F_{0} = 0, F_{1} = 1, F_{n} = F_{n-1} + F_{n-2}\)
Naive recursive algo recomputes the same values over and over:
What if we store all previous Fibonacci numbers?
fib_slow(40) takes about a minute; fib_fast(40) is instant
Same answer; we just stopped throwing work away
Idea: at each step, take whatever looks best right now. Commit. Move on.
Pros
Cons
Biology example: UPGMA and neighbor-joining methods for estimating trees
Analogy: Hiking to the highest peak by always walking uphill. You will reach a summit. Not necessarily the tallest one.
Idea: deliberately give up the guarantee of optimality in exchange for an answer you can actually get (greedy is one type)
Biology example: Searching databases for similar sequences with BLAST1
BLAST can miss things, but it is used billions of times a year
“Approximate answer today” beats “exact answer in nine years.”
Idea: use randomness as part of the procedure.
Slightly different answers from each run; this is a feature
Which strategy would you reach for?
We want something that describes the algorithm, not the afternoon
Idea: count how the number of operations grows as the input (\(n\)) grows, and ignore everything else.
Specifically, we throw away:
We keep only the term that dominates as \(n\) gets large.
\(O(n)\): Loop over \(n\) bases
\(O(n^2)\): Nested for loops for all pairs of \(n\) sequences
Rough rule of thumb: each nested loop over the input multiplies by another factor of \(n\).
| Notation | Name | Example |
|---|---|---|
| \(O(1)\) | constant | dict lookup |
| \(O(\log n)\) | logarithmic | binary search |
| \(O(n)\) | linear | scanning a sequence |
| \(O(n \log n)\) | linearithmic | sorting |
| \(O(n^2)\) | quadratic | all pairwise comparisons |
| \(O(n^3)\) | cubic | some matrix operations |
| \(O(2^n)\) | exponential | all subsets |
| \(O(n!)\) | factorial | all orderings |
Assume ~10 million simple Python operations per second:
| \(n\) | \(O(n)\) | \(O(n \log n)\) | \(O(n^2)\) | \(O(n^3)\) |
|---|---|---|---|---|
| 10 | 1 µs | 3 µs | 10 µs | 100 µs |
| 100 | 10 µs | 66 µs | 1 ms | 100 ms |
| 1,000 | 100 µs | 1 ms | 100 ms | 1.7 min |
| 1,000,000 | 100 ms | 2 s | 1.2 days | 3,000 yr |
A better algorithm can scale to lots of data
| \(n\) | \(O(2^n)\) | \(O(n!)\) |
|---|---|---|
| 10 | 100 µs | 0.4 s |
| 20 | 100 ms | 7,700 yr |
| 30 | 1.8 min | \(8 \times 10^{17}\) yr |
| 50 | 3.6 yr | — |
Example: the exhaustive tree search from earlier
It is not a hardware problem, and buying a cluster does not fix it
The same notation describes memory.
Two 100,000-bp sequences:
Almost every real bioinformatics tool sits somewhere on this spectrum, and the position is a design choice.
The set is dramatically faster to search — and uses noticeably more memory, because it stores a hash table.
If you do one lookup, use the list
If you do a million lookups, building the set pays for itself
There is no “best” algorithm. There is a best algorithm for your problem, at your scale, under your constraints.
I used Anthropic’s Claude (Opus 5 model) to generate early drafts of some sections of these slides.