Модуль 9 · Урок 36 із 58

Traversal і priority: BFS, DFS, heap та lazy iteration

Traversal — це не лише цикл: порядок frontier, visited policy і tie-breakers визначають результат. Priority queue теж потребує стабільного secondary key, якщо дві задачі мають однаковий пріоритет.

BFS/DFSvisitedheapqLazy iteration

Безпечна algorithm-практика модуля 9

Пакет містить тільки synthetic tasks і graph, deterministic operation counts, локальний Python CLI, tests та read-only verifier. Жодних персональних чи production-даних, credentials, мережевих викликів, machine paths або зовнішніх залежностей.

Завантажити практичний пакет →

Graph contract перед traversal

Graph задається nodes та edges; треба визначити directed/undirected semantics, missing neighbor policy, duplicate edges і deterministic order. Без visited cycle може повторювати nodes без кінця. Для tree visited інколи не потрібен, але загальний graph traversal має його явно.

FrontierЩо ще треба відвідати.
VisitedЩо вже оброблено за identity key.
Neighbor orderInput order або explicit sort.
Stop ruleFull traversal, target або depth limit.

BFS і DFS відповідають різним питанням

BFS використовує FIFO queue і проходить layers; у неваговому graph він підходить для найкоротшої кількості edges. DFS використовує stack або recursion і заглиблюється в branch; він корисний для повного обходу, component exploration чи backtracking, але recursion depth є runtime boundary.

from collections import deque

queue = deque([start])
visited = {start}
while queue:
    node = queue.popleft()
    for neighbor in sorted(graph.get(node, [])):
        if neighbor not in visited:
            visited.add(neighbor)
            queue.append(neighbor)

Mark-on-enqueue запобігає дублюванню frontier. Sorting neighbors дає deterministic навчальний output, але додає cost, який треба назвати.

Heap — partial order для priority workload

heapq підтримує heap invariant: найменший item у heap[0]. Для stable priority queue використовуйте tuple (priority, sequence, task); sequence розв’язує ties і не змушує Python порівнювати несумісні task objects. Stale-entry або priority-update policy має бути документована.

Top-k не завжди потребує full sort

Коли k мале відносно n, heapq.nsmallest/nlargest можуть виконати менше роботи й тримати лише частину елементів. Для великого k повний sort може бути простішим.

Lazy iteration контролює memory

Generator та itertools обробляють items поступово. Це не робить algorithm автоматично швидшим, але може зменшити peak memory і дозволити early stop. Materialization через list(...) повертає full allocation, тому boundary має бути свідомою.

SourceIterable або stream.
TransformLazy map/filter/chain.
Limitislice, target або batch.
MaterializeЛише коли downstream contract цього вимагає.

Performance evidence package

Спочатку доведіть correctness і deterministic operation counts на sizes 0, 1, 10, 100. Потім за потреби додайте timeit з окремим setup, кількома repeats, runtime/version та raw vector. Не перетворюйте мікробенчмарк на обіцянку production latency.

  • Same result and same validation policy for compared variants.
  • Input generation поза measured body.
  • Operation count або invariant evidence поруч із timing.
  • CPU, I/O і network claims не змішуються.
  • Висновок називає range sizes і limitations.
  • Regression threshold прив’язаний до contract, а не випадкового best run.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію та Python Wiki.

Практична перевірка · урок 36 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. Яка frontier structure відрізняє breadth-first traversal від depth-first у базовій реалізації?
2. Навіщо graph traversal підтримує visited set, навіть якщо target може знайтися швидко?
3. Чому mark-on-enqueue часто краще за mark-on-dequeue у BFS для graph із кількома incoming edges?