awesome-everything EN
↑ Обратно к восхождению

Алгоритмы с нуля

Рекурсия и backtracking: чтение кода

Суть Читайте реальные рекурсивные сниппеты и дерево рекурсии, предсказывайте поведение или стоимость и выбирайте баг или исправление с наибольшим эффектом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Баги рекурсии находят чтением кода и трассировкой вызовов, а не догадками. Читайте каждый сниппет, предсказывайте, что он делает, и выбирайте ответ, который аккуратный инженер защитил бы на ревью.

Цель

Отработайте цикл, который вы запускаете на любой рекурсивной функции: найдите base case, проверьте, что recursive case делает прогресс, посчитайте ветвление, чтобы прочитать стоимость, и убедитесь, что backtracking восстанавливает состояние перед следующим выбором.

Сниппет 1 — отсутствующий прогресс

function sumDigits(n) {
  if (n === 0) return 0;       // base case
  return (n % 10) + sumDigits(n / 10);   // recursive case
}

sumDigits(123);
Викторина

Это должно суммировать десятичные цифры n. Что происходит на самом деле и почему?

Сниппет 2 — дерево рекурсии

fib(4)
├─ fib(3)
│  ├─ fib(2)
│  │  ├─ fib(1)
│  │  └─ fib(0)
│  └─ fib(1)
└─ fib(2)
   ├─ fib(1)
   └─ fib(0)
Викторина

Читая это дерево вызовов наивного fib(4), какое утверждение верно?

Сниппет 3 — утёкшее состояние

function subsets(arr) {
  const result = [], current = [];
  function go(i) {
    if (i === arr.length) { result.push(current); return; }
    current.push(arr[i]);
    go(i + 1);
    current.pop();
    go(i + 1);
  }
  go(0);
  return result;
}
Викторина

Структура choose-explore-undo выглядит верно. Почему это возвращает массив пустых (или одинаковых) подмножеств?

Сниппет 4 — вопрос о pruning

function combine(arr, k) {
  const result = [];
  function go(start, current) {
    if (current.length === k) { result.push([...current]); return; }
    for (let i = start; i < arr.length; i++) {
      current.push(arr[i]);
      go(i + 1, current);
      current.pop();
    }
  }
  go(0, []);
  return result;
}
Викторина

Это корректно генерирует C(n, k) комбинаций. Что меняет добавление `if (current.length + (arr.length - start) < k) return;` в начало go?

Итог

Любой баг рекурсии читается одинаково: убедитесь, что base case действительно достижим (деление с плавающей точкой в Сниппете 1 никогда не даёт 0), следите за перекрывающимися подзадачами, которые делают дерево экспоненциальным (повторяющиеся вызовы fib в Сниппете 2), копируйте частичное решение при записи вместо хранения общей ссылки (Сниппет 3) и добавляйте pruning, который пропускает заведомо мёртвые ветки, не меняя выхода (Сниппет 4). Трассируйте вызовы, исправьте прогресс или состояние, затем перепроверьте.

Продолжить восхождение ↑Рекурсия и backtracking: постройте решатель с pruning
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.