Граф де Брёйна и идея сборки

k-меры умеют не только искать — на них строят граф, по которому собирают геном из миллионов обрывков. Это идея графа де Брёйна.

Граф де Брёйна — граф, где узлы — это (k−1)-меры, а каждый k-мер задаёт ребро, соединяющее его префикс и суффикс. Сборка генома сводится к поиску пути, проходящего по всем рёбрам.

Сборка — это пазл: из миллионов коротких ридов восстановить исходный геном. Гениальная идея современных сборщиков — превратить эту задачу в задачу о пути в графе, построенном из k-меров. Разберём механику на маленьком примере.

Из k-мера — ребро графа

Каждый k-мер расщепляется на префикс (первые k−1 букв) и суффикс (последние k−1 букв). Префикс и суффикс — это узлы, а сам k-мер — ребро между ними. Соседние k-меры в последовательности перекрываются на k−1 букв, поэтому их рёбра стыкуются.

def de_bruijn_edges(seq, k):
    edges = []
    for i in range(len(seq) - k + 1):
        kmer = seq[i:i+k]
        edges.append((kmer[:-1], kmer[1:]))  # префикс -> суффикс
    return edges

for a, b in de_bruijn_edges('ATGGCG', 3):
    print(f'{a} -> {b}')

Вывод:

AT -> TG
TG -> GG
GG -> GC
GC -> CG

Получилась цепочка узлов AT → TG → GG → GC → CG. Пройдя по ней и склеивая по одной новой букве, мы восстанавливаем исходную строку ATGGCG. В этом и есть суть сборки.

Сборка как путь по графу

Если у нас не вся строка, а набор перекрывающихся k-меров (как риды), мы строим из них граф и ищем путь, проходящий по всем рёбрам ровно один раз — эйлеров путь. Пройдя его, склеиваем геном. Соберём строку обратно из её рёбер.

edges = [('AT','TG'), ('TG','GG'), ('GG','GC'), ('GC','CG')]

# простая реконструкция: идём по цепочке и добавляем по букве
path = [edges[0][0]]  # стартовый узел
for a, b in edges:
    path.append(b)

genome = path[0] + ''.join(node[-1] for node in path[1:])
print('Узлы пути:', ' -> '.join(path))
print('Собранный геном:', genome)

Вывод:

Узлы пути: AT -> TG -> GG -> GC -> CG
Собранный геном: ATGGCG

Из четырёх рёбер мы склеили исходную строку ATGGCG, добавляя по одной последней букве каждого следующего узла. На этом принципе работают реальные сборщики (SPAdes, Velvet) — только узлов там миллиарды.

Почему эйлеров, а не гамильтонов путь

Раньше сборку формулировали как поиск пути через все узлы (гамильтонов путь) — NP-трудная задача, практически неподъёмная. Прорыв де Брёйна: сделать k-меры рёбрами, тогда сборка — это эйлеров путь (через все рёбра), а он находится за линейное время. Один и тот же набор данных, но другая формулировка превращает невозможное в быстрое — красивый урок алгоритмики.

Гамильтонов путь: через все УЗЛЫ ровно раз  -> NP-трудно
Эйлеров путь:     через все РЁБРА ровно раз -> линейно (легко)
де Брёйн делает k-меры рёбрами => получаем эйлеров случай

Как работает под капотом: почему сборка трудна на практике

В теории всё красиво, но реальность ломает идиллию: ошибки прибора создают ложные узлы и тупиковые ветки; повторы (одинаковые участки в разных местах генома) дают несколько возможных путей, и граф запутывается; покрытие неравномерно. Поэтому настоящие сборщики тратят основную работу на чистку графа: удаление «пузырей» от ошибок, разрешение повторов с помощью длинных ридов или парных концов. Идея графа де Брёйна остаётся ядром, но вокруг неё — годы инженерии. Именно повторы — главная причина, почему геном редко собирается в одну идеальную строку, а распадается на куски (контиги).

Частые ошибки

  • Путать узлы и рёбра. В графе де Брёйна k-меры — рёбра, а (k−1)-меры — узлы; наоборот будет NP-трудно.
  • Игнорировать повторы. На реальных данных они главный источник неоднозначности сборки.
  • Слишком маленький k. Тогда граф схлопывается: разные участки дают одинаковые k-меры, и сборка рассыпается.

Итог

  • Граф де Брёйна: узлы — (k−1)-меры, рёбра — k-меры (префикс → суффикс).
  • Сборка генома = поиск эйлерова пути (через все рёбра) — это быстро.
  • Формулировка через рёбра превращает NP-трудный гамильтонов путь в линейный эйлеров.
  • На практике сборку усложняют ошибки и повторы; реальные сборщики чистят граф.
Проверьте себя
1. Что является узлами и рёбрами в графе де Брёйна?
AУзлы — k-меры, рёбра — (k−1)-меры
BУзлы — (k−1)-меры, рёбра — k-меры
CУзлы и рёбра — целые риды
DУзлы — нуклеотиды, рёбра — гены
2. Почему формулировка сборки через граф де Брёйна эффективнее, чем поиск гамильтонова пути?
AОна использует меньше памяти всегда
BСборка сводится к эйлерову пути (через все рёбра), который ищется за линейное время
CГамильтонов путь не существует для ДНК
DОна не требует k-меров
3. Что на реальных данных сильнее всего усложняет сборку генома?
AСлишком короткий геном
BПовторяющиеся участки и ошибки прибора, запутывающие граф
CОтсутствие компьютеров
DМалое число k-меров