Files
eduplan-cpp-eltex/diag/tasks/02_list/task.md
T

77 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Задача 02 — связный список (C++)
## Глава I. Общая информация
- **Цель:** развернуть список, найти середину и определить цикл — без единой дополнительной
аллокации, только перелинковка указателей.
- **Почему это в Eltex:** списки соединений, очереди задач, таблицы состояний — везде связные
структуры. Приём двух указателей (slow/fast), которым решаются `find_middle` и `has_cycle`,
— это алгоритм Флойда, тот же паттерн всплывает при поиске циклов в графах зависимостей
и в обходе кольцевых структур.
- **Что сдаётся:** `solution.cpp`, проходящий `python3 grade.py 02`.
## Глава II. Что нужно знать до старта
Даны функции над односвязным списком `Node{int value; Node* next;}`:
```c
Node* reverse_list(Node* head); // развернуть список на месте, вернуть новую голову
Node* find_middle(Node* head); // середина; для чётной длины — второй из двух средних
bool has_cycle(Node* head); // есть ли цикл
```
Механизм двух указателей: `slow` идёт на 1 узел за шаг, `fast` — на 2. Для `find_middle`, когда
`fast` доходит до конца (`fast == nullptr` или `fast->next == nullptr`), `slow` стоит ровно в
середине — за счёт того, что `slow` проходит вдвое меньше узлов, чем `fast`. Для чётной длины
условие остановки должно давать именно второй из двух средних узлов — это проверяется на
списке длины 2 и 4.
Для `has_cycle` тот же дуэт указателей ловит цикл иначе: если цикл есть, `fast` заходит в него
и после каждого шага сокращает расстояние до `slow` внутри цикла на 1 узел (потому что относительная
скорость `fast` к `slow` внутри цикла — 1 узел/шаг), значит рано или поздно `slow == fast`; если
цикла нет, `fast` первым дойдёт до `nullptr`.
`reverse_list` разворачивается за один проход тремя указателями `prev/cur/next`: на каждом шаге
переставляется `cur->next = prev` до итерации по цепочке. Рекурсивный разворот сюда не годится —
он тратит O(n) памяти стека вызовов и нарушает требование O(1) дополнительной памяти.
Почему дальше: тот же принцип «два индекса вместо лишней памяти» — в задаче 03, только на
массиве, а не на указателях.
## Глава III. Требования
- никаких аллокаций, O(1) дополнительной памяти, один проход там, где это возможно;
- `find_middle` и `has_cycle` — через два указателя (медленный/быстрый);
- корректная работа с пустым списком и списком из одного узла.
## Глава IV. Критерии приёмки
Проверка: `python3 grade.py 02`. Критерий: все `ok`, ASAN/UBSAN чистые (утечки в тесте
считаются ошибкой — тест сам освобождает память).
## Глава V. Ловушки
- Забыть проверку `head == nullptr` в начале любой из трёх функций → разыменование нулевого
указателя → падение/ASAN SEGV на пустом списке.
- В `find_middle` неверное условие остановки цикла (`fast->next` без проверки самого `fast`
на `nullptr`) → на списке чётной длины либо возвращается не тот из двух средних узлов, либо
падение на последнем шаге.
- В `reverse_list` переставить `cur->next = prev` до сохранения старого `cur->next` во
временную переменную → потеря хвоста списка, обход обрывается раньше конца.
- Рекурсивный `reverse_list` вместо итеративного → лишняя память стека на каждый вызов →
нарушение требования O(1), на длинном списке возможен stack overflow.
**Факты для карточек**
- base | Во сколько раз быстрее идёт `fast` относительно `slow` в паре двух указателей? — в 2 раза
- core | Почему рекурсивный разворот списка не годится под требование O(1) памяти? — каждый
вызов кладёт кадр в стек, суммарно O(n) памяти
- core | На чём основано доказательство, что `fast` догонит `slow` при цикле? — внутри цикла
расстояние между ними сокращается на 1 узел за шаг
- base | Сколько дополнительных указателей нужно для итеративного разворота списка? — 3
(`prev`, `cur`, `next`)
## После сдачи
Разбор: почему алгоритм Флойда работает за O(n) времени и O(1) памяти в обоих режимах
(поиск середины и поиск цикла), и как тот же приём переносится на поиск цикла в графе.