Алгоритмизация и программирование · 1 курс · 80 мин
| Тема | 3.09 | 4.09 (сегодня) |
|---|---|---|
| Обходы | 3 рекурсивных (теория) | NEW + level-order в коде |
| Построение | вставка в БДП | NEW по массиву, -1 |
| Метрики | — | NEW высота, сравнения |
| БДП insert/find | теория | готово в tree.cpp |
На 3.09 вы узнали, что такое бинарное дерево и БДП. Сегодня учимся писать это на C++ и проверять тестами.
draft/c++.cpp./01.build.sh → ./03.run_tests.shСеминар 80 мин: ~18 теория + ~35 практика + ~12 разбор + ~15 итоги.
| Термин | Что это такое | Зачем на семинаре |
|---|---|---|
| Дерево | Связная структура без циклов: есть корень, у узла — родитель и дети, от корня до любого узла путь один. | Базовая модель для всех заданий |
| Бинарное дерево | У каждого узла не более двух детей: left и right. Порядок ключей не задан. | Задание 4.09.01 — строим из массива |
| БДП (BST) | Binary Search Tree — бинарное дерево поиска: в каждом узле все ключи слева меньше, справа больше ключа узла. | Задания 4.09.02–04 — вставка, поиск, высота |
| Ключ | Число (значение), хранящееся в узле. В коде — поле key. | Сравниваем при поиске и вставке |
| Указатель | Ссылка на другой узел в памяти (BinNode*). Если ребенка нет — nullptr. | Связываем узлы в дерево |
| Инвариант | Правило, которое структура обязана соблюдать всегда. У БДП: left < root < right в каждом узле. | Проверяем через Sorted inorder: YES/NO |
| Level-order (по уровням) | Способ задать форму дерева массивом: корень в [0], дети узла i — в 2i+1 и 2i+2; -1 = пусто. | buildByLevelOrder — это не вставка в БДП! |
| Термин | Что это такое | Зачем на семинаре |
|---|---|---|
| Обход дерева | Способ посетить все узлы и вывести ключи в определенном порядке. | 4.09.01 — четыре вида обхода |
| Preorder | Порядок: корень → левое → правое. Сначала обрабатываем узел, потом детей. | binPreorder, рекурсия |
| Inorder | Порядок: левое → корень → правое. В БДП дает ключи в отсортированном порядке. | Проверка Sorted inorder |
| Postorder | Порядок: левое → правое → корень. Сначала дети, потом узел. | Удобен при освобождении памяти (binFree) |
| Level-order обход | Уровень за уровнем, слева направо. Реализуется очередью (как BFS по детям). | binLevelOrder — новый обход в коде |
| Высота h | Длина самого длинного пути от корня до листа. В draft: пустое дерево → 0, один узел → 1. | 4.09.04 — сравниваем два порядка вставки |
| Сравнение (comparison) | Одно сравнение искомого ключа с ключом текущего узла при поиске в БДП (+1 на каждый шаг). | 4.09.03 — ключ: found=… comparisons=… |
| GTest | Библиотека автотестов для C++: TEST, EXPECT_EQ и др. Проверяет ваш код без ручного ввода. | 03.run_tests.sh запускает все тесты |
left < root < rightTEST, EXPECT_*taskNN.hpp/cpplib/ioutils/trees/Preorder — «сначала корень» (удобно копировать дерево). Inorder — «слева, потом корень» (дает сортировку в БДП). Postorder — «сначала дети» (удобно удалять).
Ниже повторим определения подробнее — даже если 3.09 был давно.
| Термин | Значение | На рисунке → |
|---|---|---|
| Узел (node) | Круг с ключом и ссылками на детей | любой круг |
| Корень (root) | Верхний узел, с него начинаем | 5 (оранжевый) |
| Ребенок | Узел на уровень ниже | 3 и 7 у 5 |
| Лист | Нет детей (left и right пусты) | 1 и 4 |
| Поддерево | Узел + все потомки | левое от 5: {3,1,4} |
| Высота h | Самый длинный путь корень → лист | здесь h = 3 (в draft) |
БДП: вставка 5, 3, 7, 1, 4
| Корень — 5 | |
| Дети корня — 3 и 7 | |
| Листья — 1 и 4 (у 7 тоже нет детей) | |
| ━━ | Левое поддерево корня — узлы 3, 1, 4 |
| Высота h = 3 (путь 5 → 3 → 1) |
Не больше двух детей: left и right (если нет ребенка — nullptr).
struct BinNode { int key; BinNode *left; BinNode *right; };
BST = Binary Search Tree = бинарное дерево поиска (БДП). Структура для хранения ключей с быстрым поиском: на каждом шаге отбрасываем половину вариантов.
v все ключи слева меньше v.key, все справа — больше.
Проверка для узла 5:
Inorder (лево → корень → право):
1 3 4 5 7 — отсортировано!
Поэтому в задании 4.09.02 выводим Sorted inorder: YES.
Ключи вставляем по очереди: 5 → 3 → 7 → 1 → 4
| Шаг | Ключ | Действие | Дерево после |
|---|---|---|---|
| 1 | 5 | Пусто → 5 становится корнем | 5 |
| 2 | 3 | 3 < 5 → влево | 5 / 3 |
| 3 | 7 | 7 > 5 → вправо | 5 / 3,7 |
| 4 | 1 | 1 < 5 → влево; 1 < 3 → влево от 3 | лист слева от 3 |
| 5 | 4 | 4 < 5 → влево; 4 > 3 → вправо от 3 | готовое дерево |
Алгоритм: начинаем с корня; если ключ меньше — в left, иначе в right; если ребенка нет — создаем новый узел. Сложность одной вставки: O(h), где h — высота.
h — высота дерева (сколько «ступенек» от корня до самого дальнего листа).
Поиск и вставка в БДП каждый раз выбирают левую или правую ветвь → проходим путь длиной не больше h.
| Операция | Время | Когда хорошо |
|---|---|---|
| Поиск | O(h) | h ≈ log₂ n |
| Вставка | O(h) | случайный порядок ключей |
| Обход всех узлов | O(n) | всегда посещаем каждый узел |
| Плохой случай | O(n) | вставка 1,2,3,…,n по порядку |
| Было на 3.09 | Новое сейчас |
|---|---|
| 3 обхода (теория) | NEW 4-й обход level-order в коде |
| Вставка в БДП | NEW Построение по массиву (-1 = пусто) |
| — | NEW Обычное бинарное дерево ≠ БДП |
| — | NEW Метрики: высота, число сравнений |
| — | NEW Sorted inorder: YES/NO |
| — | Задания 4.09.01–04 в draft/c++ |
| Обычное бинарное дерево (4.09.01) | БДП (4.09.02–04) | |
|---|---|---|
| Как строим | Массив level-order, индексы 2i+1, 2i+2 | Вставка ключей по одному |
| Порядок ключей | Может нарушать left < root < right | Всегда соблюдается |
| Файл | binaryTree.cpp, BinNode | tree.cpp, BstNode |
| Inorder | Не обязан быть отсортирован | Должен быть отсортирован |
bstInsert — только buildByLevelOrder!| Контекст | Семинар | Суть |
|---|---|---|
| Бинарное дерево / БДП | 3.09, 4.09 | Упорядоченная структура, insert/find |
| DFS-дерево обхода | 4.04, 4.05 | Подграф обхода графа |
| Лес DSU | 4.07 | Множество корней, find/union |
| Остовное дерево (MST) | 4.07 | Связный подграф, n−1 ребер |
| Обход | Порядок | Результат |
|---|---|---|
| Preorder | корень → лево → право | 5 3 1 4 7 |
| Inorder | лево → корень → право | 1 3 4 5 7 ★ |
| Postorder | лево → право → корень | 1 4 3 7 5 |
Inorder БДП → отсортированная последовательность. Это главная проверка корректности.
Inorder и postorder — те же три шага, но в другом порядке (см. таблицу на след. слайдах).
В tree.cpp уже есть bstPreOrder — скопируйте логику, заменив тип узла на BinNode.
i: 0 1 2 3 4 5 6 key: 10 5 15 3 7 -1 20
-1 → нет левого ребенка у 15Так же устроен массив в куче (семинар 4.10), но там другой смысл ключей.
| Обход | Порядок | Результат | Реализация |
|---|---|---|---|
| Preorder | корень → лево → право | 10 5 3 7 15 20 | рекурсия |
| Inorder | лево → корень → право | 3 5 7 10 15 20 | рекурсия |
| Postorder | лево → право → корень | 3 7 5 20 15 10 | рекурсия |
| Level-order | по уровням, слева направо | 10 5 15 3 7 20 | очередь |
Функции: binPreorder, binInorder, binPostorder, binLevelOrder
Нужно вывести узлы уровень за уровнем, слева направо: сначала корень, потом его дети, потом внуки…
10 → 5, 15 → 3, 7
Очередь хранит «кого обработать следующим».
В C++ используйте std::queue<BinNode*> или массив с индексами head/tail.
Один набор ключей {3, 1, 4, 2, 5} — два порядка вставки, разная высота:
Height original: 3
Height sorted: 5 → поиск O(n)
Высота считается в узлах: пустое дерево → 0, один узел → 1. Далее — пошаговый поиск ключа 4 в отдельном БДП.
nullptr (пустое) → высота 01 + max(h(left), h(right))Один и тот же набор ключей {3,1,4,2,5}:
Сортировку копии делаем вставками (как в задании), не std::sort.
4БДП построен вставкой ключей: 5 → 3 → 7 → 1 → 4
Найти ключ 4
Старт — в корне дерева.
На каждом шаге: +1 comparison при сравнении запроса с ключом узла.
→ Следующий слайд: шаг 1
Идем в левое поддерево (к узлу 3).
Узел 7 не посещаем — он в правой ветви.
Уже посетили: 5
Идем в правое поддерево (к узлу 4).
Узел 1 не посещаем — он слева от 3.
Путь: 5 → 3 → 4
4: found=YES comparisons=3
━━ путь поиска ○ текущий шаг ● найден
| Шаг | Сравнение | Действие |
|---|---|---|
| 1 | 4 vs 5 | влево |
| 2 | 4 vs 3 | вправо |
| 3 | 4 vs 4 | найдено |
Output: 4: found=YES comparisons=3 Total comparisons: 3 (для одного запроса)
Задание 4.09.02: Sorted inorder: YES для этого БДП (inorder: 1 3 4 5 7).
Ищем ключ 6 в том же БДП:
| Шаг | Сравнение | Куда |
|---|---|---|
| 1 | 6 > 5 | вправо → 7 |
| 2 | 6 < 7 | влево, но детей нет → стоп |
Output: 6: found=NO comparisons=2
Сравнение на последнем узле тоже считается! Не забывайте +1 при равенстве (found=YES).
| № | Задача | Ввод | Вывод |
|---|---|---|---|
| 4.09.01 | Дерево по уровням, 4 обхода | n, массив (-1) | Preorder: … Level-order: … |
| 4.09.02 | БДП + inorder | n, ключи | Inorder: … Sorted inorder: YES/NO |
| 4.09.03 | Поиск с метрикой | ключи + q запросов | ключ: found=… comparisons=…, Total comparisons: |
| 4.09.04 | Два порядка вставки | n, ключи | Height original: Height sorted: |
Код: seminars/4.09/draft/c++ · Постановки: seminar409Tasks.md
lib/treeAlgs/binaryTree.cpp — дописать построение, обходы и освобождение памяти
buildByLevelOrder(keys, n): if n <= 0: return nullptr return buildByIndex(keys, n, 0) buildByIndex(keys, n, i): if i >= n: return nullptr if keys[i] == -1: return nullptr node = new BinNode(keys[i]) node.left = buildByIndex(keys, n, 2*i + 1) node.right = buildByIndex(keys, n, 2*i + 2) return node
binPreorder(node, out):
if !node: return
push node.key → left → right
binInorder(node, out):
if !node: return
left → push node.key → right
binPostorder(node, out):
left → right → push node.key
binLevelOrder(root, out):
queue.push(root)
while !queue.empty():
cur = queue.front(); queue.pop()
push cur.key
if cur.left: queue.push(cur.left)
if cur.right: queue.push(cur.right)
binFree(root):
left → right → delete root
-1 означает пустой узел в массиве. Индексы детей в level-order массиве: 2*i+1 и 2*i+2.
task_4.09.01/task40901.cpp
runLevelOrderTreeTask(keys, n, output):
if !output or n < 0: return 1
root = buildByLevelOrder(keys, n)
preorder = []
inorder = []
postorder = []
levelorder = []
binPreorder(root, preorder)
binInorder(root, inorder)
binPostorder(root, postorder)
binLevelOrder(root, levelorder)
output << formatTraversalLine("Preorder", preorder)
output << formatTraversalLine("Inorder", inorder)
output << formatTraversalLine("Postorder", postorder)
output << formatTraversalLine("Level-order", levelorder)
binFree(root)
return 0
Input format: n followed by n integer keys (-1 means an empty node). 7 10 5 15 3 7 -1 20 Output: Preorder: 10 5 3 7 15 20 Inorder: 3 5 7 10 15 20 Postorder: 3 7 5 20 15 10 Level-order: 10 5 15 3 7 20
formatTraversalLine уже готова: не меняйте формат подписей и пробелов.
task_4.09.02/task40902.cpp
buildBstAndCheckInorder(keys, n, output):
if !keys or !output or n < 0: return 1
root = nullptr
for i in 0..n-1:
root = bstInsert(root, keys[i])
values = []
bstInOrder(root, values)
output << "Inorder: " + join(values)
if isStrictlyIncreasing(values):
output << "Sorted inorder: YES"
else:
output << "Sorted inorder: NO"
bstFree(root)
return 0
Input format: n followed by n integer keys for the BST. 5 5 3 7 1 4 Output: Inorder: 1 3 4 5 7 Sorted inorder: YES
Для корректной БДП симметричный обход идет по возрастанию. Если в данных или вставке нарушить инвариант, будет NO.
task_4.09.03/task40903.cpp
runBstSearchMetrics(keys, n, queries, q, output):
if bad pointers or n < 0 or q < 0: return 1
root = nullptr
for key in keys:
root = bstInsert(root, key)
total = 0
for query in queries:
comparisons = 0
found = bstFindWithComparisons(root, query, &comparisons)
total += comparisons
output << query
<< ": found=" << (found ? "YES" : "NO")
<< " comparisons=" << comparisons
output << "Total comparisons: " << total
bstFree(root)
return 0
Input format: n followed by n BST keys, then q followed by q search keys. 5 5 3 7 1 4 3 4 6 7 Output: 4: found=YES comparisons=3 6: found=NO comparisons=2 7: found=YES comparisons=2 Total comparisons: 7
Поиск 6: сравнили с 5, затем с 7, дальше нужного ребенка нет.
task_4.09.04/task40904.cpp
sortKeysCopy(src, n, dst):
скопировать src[0..n-1] в dst
for i = 1..n-1:
x = dst[i]
j = i - 1
while j >= 0 and dst[j] > x:
dst[j+1] = dst[j]
--j
dst[j+1] = x
bstHeightAfterInsertions(keys, n):
root = nullptr
for i = 0..n-1:
root = bstInsert(root, keys[i])
h = bstHeight(root)
bstFree(root)
return h
main/task: read n and keys sorted = new int[n] sortKeysCopy(keys, n, sorted) hOriginal = bstHeightAfterInsertions(keys, n) hSorted = bstHeightAfterInsertions(sorted, n) print "Height original: ..." print "Height sorted: ..." delete[] sorted
Input format: n followed by n integer keys for BST insertions. 5 3 1 4 2 5 Output: Height original: 3 Height sorted: 5
lib/ioutils/trees/tree.cpp — БДПbstMetrics.cpp — поиск с сравнениямиformatTraversalLine в task40901main.cpp, тесты GTestlib/treeAlgs/binaryTree.cpp ← ядроtask40901.cpp — склейка обходовtask40902.cpp — БДП + YES/NOtask40903.cpp — сумма сравненийtask40904.cpp — сортировка вставками + высотыbinPreorder — та же логика, что bstPreOrder в tree.cpp, но для BinNode.
Каждый new BinNode в buildByLevelOrder нужно освободить через binFree.
Аналогично bstFree для БДП.
run*TaskbinFree(root) не используйте rootcd seminars/4.09/draft/c++ ./01.build.sh ./03.run_tests.sh
task409NN.cppmain.cppstatic./03.run_tests.sh01.build.sh — создает build/, качает GTest (первый раз), компилирует03.run_tests.sh — проверяет наличие build/, запускает ctest --output-on-failure$ ./03.run_tests.sh
...
FAILED: task_4.09.01_tests
Expected equality of these values:
actual "Preorder: 10 5 ..."
expected "Preorder: 10 5 3 7 15 20"
test_task_40901.cppactual и expected./01.build.sh и ./03.run_tests.sh03.run_tests.sh = ctest --output-on-failure из папки build/.
inorder и preorderbinFree / bstFreebinLevelOrderfound=NOstd::sort вместо вставок-1 в [0])| Блок | Время | Содержание |
|---|---|---|
| Теория | ~18 мин | Термины, БДП, обходы, построение по массиву |
| Практика | ~35 мин | 4 задания ниже |
| Разбор | ~12 мин | Типичные ошибки, метрики |
| Итоги | ~15 мин | Чеклист, мост к 4.10 |
03.run_tests-1YES/NO03.run_tests проходит на draftБДП при плохом порядке → h = n, поиск O(n).
Решение: AVL, RB-tree, куча — сбалансированные структуры.
seminars/4.09/draft/c++