class Algorithms {
public:
Algorithms lecture {
.author = "Р.А. Нестеров"
.year = "2026/27"
.course = "НИУ ВШЭ ПИ, 2 курс"
};
// 01. Корректность и сложность рекурсивного алгоритма
void selection_sort(Iter first, Iter last);
void recursion_complexity();
// 02. Алгоритмическая парадигма «разделяй-и-властвуй»
void merge_sort(Iter first, Iter last);
void recursion_tree();
// 03. Домашняя работа set_1
void set_hw_environment();
->03
03.00
асимптотические границы временной сложности
интрузивный список: эффективное хранение
одних и тех же объектов в нескольких списках
void asymptotic_bounds();
порядок роста $\Theta$, верхняя $\mathrm{O}$ и нижняя $\Omega$ граница
для некоторых $c_1, c_2, n_0 >0$ и для всех $n \geqslant n_0$:
$T(n) = \Theta(g(n)) \Leftrightarrow T(n) = \mathrm{O}(g(n))$ и $T(n) = \Omega(g(n))$
$T_1(n) = \mathrm{O}(n^3)$
$T_2(n) = \Omega(n^2)$
* клик по ответу покажет, верный ли он
template <typename Tag> IntrusiveNode {...};
классический подход на основе std::list
#include <list>
#include <string>
struct Connection {
int socket_fd;
std::string buffer;
};
int main() {
std::list<Connection*> all;
std::list<Connection*> ready;
Connection c1{10, "Hello"};
Connection c2{11, "World"};
all.push_back(&c1);
all.push_back(&c2);
ready.push_back(&c1);
ready.push_back(&c2);
}
template <typename Tag> IntrusiveNode {...};
реализация на основе множественного наследования
#include <string>
#include <iostream>
struct All { All *next = nullptr, *prev = nullptr; };
struct Ready { Ready *next = nullptr, *prev = nullptr; };
struct Connection : All, Ready {
int socket_fd;
std::string buffer;
};
int main() {
Connection c1 {.socket_fd = 10, .buffer = "Hello"};
Connection c2 {.socket_fd = 11, .buffer = "World"};
c1.All::next = &c2;
c2.All::prev = &c1;
c1.Ready::next = &c2;
c2.Ready::prev = &c1;
for (All* cur = &c1; cur; cur = cur->next) {
Connection* con = static_cast<Connection*>(cur);
std::cout << "FD: " << con->socket_fd << "\n";
}
}
template <typename Tag> IntrusiveNode {...};
типобезопасные конструкции: теги и шаблоны
#include <string>
#include <iostream>
struct TagAll {};
struct TagReady {};
template<typename Tag>
struct IntrusiveNode {
IntrusiveNode *next = nullptr, *prev = nullptr;
}
struct Connection
: IntrusiveNode<TagAll>, IntrusiveNode<TagReady> {
int socket_fd;
std::string buffer;
};
template<typename Container, typename Tag>
Container* get_container(IntrusiveNode<Tag>* node) {
return static_cast<Container*>(node);
}
int main() {
Connection c1 { .socket_fd = 10, .buffer = "Hello" };
Connection c2 { .socket_fd = 11, .buffer = "World" };
c1.IntrusiveNode<TagAll>::next = &c2;
c2.IntrusiveNode<TagAll>::prev = &c1;
for (IntrusiveNode<TagAll>* cur = &c1; cur; cur = cur->next) {
Connection* con = get_container<Connection, TagAll>(cur);
std::cout << "FD: " << con->socket_fd << "\n";
}
}
03.01
сортировка выбором: прародитель сортировки
вставками и пузырькового алгоритма
корректность: инвариант и убывающая мера рекурсии
сложность: рекуррентное соотношение
метод итерации и математической индукции
для решения рекуррентного соотношения
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Iter>
void selection_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto min_iter = std::min_element(first, last);
std::iter_swap(first, min_iter);
// auto max_iter = std::max_element(first, last);
// std::iter_swap(last - 1, max_iter);
selection_sort(first + 1, last);
// selection_sort(first, last - 1);
}
int main() {
std::vector<int> arr = {5, 2, 4, 6, 1, 3};
selection_sort(arr.begin(), arr.end());
std::cout << std::boolalpha;
std::cout << std::is_sorted(arr.begin(), arr.end());
}
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Iter>
void selection_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto min_iter = std::min_element(first, last);
std::iter_swap(first, min_iter);
// auto max_iter = std::max_element(first, last);
// std::iter_swap(last - 1, max_iter);
selection_sort(first + 1, last);
// selection_sort(first, last - 1);
}
int main() {
std::vector<int> arr = {5, 2, 4, 6, 1, 3};
selection_sort(arr.begin(), arr.end());
std::cout << std::boolalpha;
std::cout << std::is_sorted(arr.begin(), arr.end());
}
для любых итераторов $\mathrm{first}$ и $\mathrm{last}$, где $\mathrm{first}\leqslant \mathrm{last}$, верно, что алгоритм selection_sort упорядочит элементы диапазона $[\mathrm{first}, \mathrm{last})$ в порядке возрастания за конечное число шагов
$d$ — расстояние между итераторами $\mathrm{first}$ и $\mathrm{last}$
инвариант $P(d) =\{$ диапазон $[\mathrm{first}, \mathrm{last})$ отсортирован $\}$
.cpp:
9-10
минимум $\to\mathrm{first}$
.cpp:
14
вызов для длины $d-1$
void recursion_complexity();
определение временной сложности рекурсивного алгоритма
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Iter>
void selection_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto min_iter = std::min_element(first, last);
std::iter_swap(first, min_iter);
selection_sort(first + 1, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ T(n-1) + \mathrm{O}(n) & n > 1 \end{cases}$
найти $g(n)$, для которой выполняется асимптотическое
соотношение верхней границы $T(n) = \mathrm{O}(g(n))$
где $k$ — это константа, которую мы не выбираем
$\begin{aligned} T(n) &\leqslant T(n-1) + kn = T(n-2) + k(n-1) + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)} &= T(n-3) + k(n-2) + k(n-1) + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)} &= \dots = T(0) + k(1 + 2 + 3 + \dots + n) \end{aligned}$
$\begin{aligned} \phantom{T(n)} &= \mathrm{O}(1) + \mathrm{O}(n^2) = \mathrm{O}(n^2) \end{aligned}$
ответ $g(n) = n^2$
void recursion_complexity();
определение временной сложности рекурсивного алгоритма
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Iter>
void selection_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto min_iter = std::min_element(first, last);
std::iter_swap(first, min_iter);
selection_sort(first + 1, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ T(n-1) + kn & n > 1 \end{cases}$
найти $g(n)$, для которой выполняется асимптотическое
соотношение верхней границы $T(n) = \mathrm{O}(g(n))$
$\begin{aligned} T(n)=T(n-1) + kn & \leqslant c(n-1)^2 + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)=T(n-1) + kn} &= cn^2 -2cn + c + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)=T(n-1) + kn} &= cn^2 - ((2c-k)n - c) \leqslant cn^2 \end{aligned}$
ответ (1) верно при $n \geqslant c \,/\,(2c -k)$, что влечет $c > k \,/\, 2$
void recursion_complexity();
определение временной сложности рекурсивного алгоритма
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Iter>
void selection_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto min_iter = std::min_element(first, last);
std::iter_swap(first, min_iter);
selection_sort(first + 1, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ T(n-1) + kn & n > 1 \end{cases}$
найти $g(n)$, для которой выполняется асимптотическое
соотношение верхней границы $T(n) = \mathrm{O}(g(n))$
$\begin{aligned} T(n)=T(n-1) + kn & \leqslant c(n-1) + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)=T(n-1) + kn} &= cn - (c - kn) \leqslant cn \end{aligned}$
03.02
сортировка слиянием и варианты ее реализации
дерево рекурсии: представление временной
сложности рекурсивного алгоритма
рекуррентное соотношение: правильно
избавляемся от знаков округлений
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
int main() {
std::vector<int> arr = {5, 2, 4, 6, 1, 3};
merge_sort(arr.begin(), arr.end());
std::cout << std::boolalpha;
std::cout << std::is_sorted(arr.begin(), arr.end());
}
#include <vector>
#include <algorithm>
#include <iterator>
// Вариант 1. Единый буфер на весь алгоритм
template<typename Iter, typename BufIter>
void merge_sort(Iter first, Iter last, BufIter buf) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid, buf);
merge_sort(mid, last, buf);
merge(first, mid, last, buf);
}
template<typename Iter, typename BufIter>
void merge(Iter first, Iter mid, Iter last, BufIter buf) {
auto buf_mid = std::copy(first, mid, buf);
std::copy(mid, last, buf_mid);
// ...
}
template<typename Iter>
void merge_sort_v1(Iter first, Iter last) {
using ValueType = typename std::iterator_traits<Iter>::value_type;
std::vector<ValueType> buffer(std::distance(first, last));
merge_sort(first, last, buffer.begin());
}
// Вариант 2. Локальный буфер на каждый шаг
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
template<typename Iter>
void merge(Iter first, Iter mid, Iter last) {
using ValueType = typename std::iterator_traits<Iter>::value_type;
std::vector<ValueType> buffer(std::distance(first, last));
// ...
}
// Вариант 3. In-place слияние через сдвиги
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
template<typename Iter>
void merge(Iter first, Iter mid, Iter last) {
auto left = first, right = mid;
while (left < mid && right < last) {
if (*right < *left) {
std::rotate(left, right, right + 1);
++mid;
++right;
}
++left;
}
}
// Вариант 4. С помощью inplace_merge
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
std::inplace_merge(first, mid, last);
}
| массив | V1 | V2 | V3 | V4 |
|---|---|---|---|---|
| 5 000 | 0.4 | 0.7 | 1.3 | 0.7 |
| 10 000 | 0.7 | 1.4 | 4.0 | 1.1 |
| 50 000 | 3.7 | 6.8 | 32.9 | 3.4 |
| 100 000 | 4.9 | 7.0 | 110.36 | 5.0 |
| 500 000 | 21.8 | 29.8 | 3386.3 | 27.0 |
| 1 000 000 | 46.5 | 62.7 | — | 55.8 |
| 5 000 000 | 254.4 | 330.0 | — | 302.9 |
| 10 000 000 | 533.6 | 686.0 | — | 619.1 |
#include <vector>
#include <algorithm>
#include <iterator>
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ 2\cdot T(n\,/\,2) + \mathrm{O}(n) & n > 1 \end{cases}$
найти $g(n)$, для которой выполняется асимптотическое
соотношение верхней границы $T(n) = \mathrm{O}(g(n))$
$\begin{aligned} T(n)\leqslant 2\cdot T(n\,/\,2) + kn & \leqslant 2c\cdot (n\,/\,2) + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)\leqslant 2\cdot T(n\,/\,2) + kn} &= cn +kn \leqslant cn \end{aligned}$
#include <vector>
#include <algorithm>
#include <iterator>
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ 2\cdot T(n\,/\,2) + \mathrm{O}(n) & n > 1 \end{cases}$
найти $g(n)$, для которой выполняется асимптотическое
соотношение верхней границы $T(n) = \mathrm{O}(g(n))$
$\begin{aligned} T(n)\leqslant 2\cdot T(n\,/\,2) + kn & \leqslant 2c\cdot (n^2\,/\,4) + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)\leqslant 2\cdot T(n\,/\,2) + kn} &= cn^2\,/\,2 +kn \end{aligned}$
$\begin{aligned} \phantom{T(n)\leqslant 2\cdot T(n\,/\,2) + kn} &= cn^2 - (cn^2\,/\,2 -kn) \leqslant cn^2 \end{aligned}$
ответ $T(n) = \mathrm{O}(n^2)$ при $c = k$ и $n \geqslant 2$
void recursion_tree();
точное определение сложности рекурсивного алгоритма
#include <vector>
#include <algorithm>
#include <iterator>
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ 2\cdot T(n\,/\,2) + kn & n > 1 \end{cases}$
void recursion_tree();
точное определение сложности рекурсивного алгоритма
#include <vector>
#include <algorithm>
#include <iterator>
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ 2\cdot T(n\,/\,2) + kn & n > 1 \end{cases}$
показать, что соотношение $T(n) = \mathrm{O}(n \log n)$ верно
$\begin{aligned} T(n)\leqslant 2\cdot T(n\,/\,2) + kn & \leqslant 2c\cdot (n\,/\,2) \cdot \log_2(n\,/\,2) + kn \end{aligned}$
$\begin{aligned} \phantom{T(n)\leqslant 2\cdot T(n\,/\,2) + kn} &= cn\cdot \log_2(n\,/\,2) +kn \end{aligned}$
$\begin{aligned} \phantom{T(n)\leqslant 2\cdot T(n\,/\,2) + kn} &= cn\cdot \log_2 n -(c-k)n \end{aligned}$
$\begin{aligned} \phantom{T(n)\leqslant 2\cdot T(n\,/\,2) + kn} & \leqslant cn\cdot \log_2 n \end{aligned}$
ответ $c-k\geqslant 0 \Rightarrow T(n) =\mathrm{O}(n \log n)$ при $c \geqslant k$ и $n \geqslant 1$
#include <vector>
#include <algorithm>
#include <iterator>
template<typename Iter>
void merge_sort(Iter first, Iter last) {
if (std::distance(first, last) <= 1) return;
auto mid = first + std::distance(first, last) / 2;
merge_sort(first, mid);
merge_sort(mid, last);
merge(first, mid, last);
}
$T(n) = \begin{cases} \mathrm{O}(1) & n \leqslant 1 \\ T(\lfloor n\,/\,2\rfloor) + T(\lceil n\,/\,2\rceil) + kn & n > 1 \end{cases}$
показать эквивалентность асимптотических границ для:
(2) $\Longrightarrow T(n+\alpha) \leqslant 2\cdot T(n\,/\,2 + \alpha) + k(n+\alpha)$
(1) $\Longrightarrow T(n+\alpha) \leqslant 2\cdot T((n+\alpha)\,/\,2 + 1) + k(n+\alpha)$
ответ $T(n) = S(n-2)$ и $T(n) = \mathrm{O}(n \log n)$
03.03
инфраструктура сдачи и проверки домашних работ: письменные и программные задачи
взаимодействие с SourceCraft и Manytask
void set_hw_environment();
из каких типов задач состоят
блок письменных задач
блок программных задач
специальная задача-кейс
void set_hw_environment();
инвариант цикла · асимптотический анализ · линейный контейнер
| ID | Баллы | Комментарий |
|---|---|---|
| a1_exact_complexity CORE | 7 | анализ временной сложности $T(n)$ |
| a2x_correctness extra | 8 | корректность алгоритма |
| a3_comparison CORE | 10 | сравнительный анализ нескольких алгоритмов |
| p1_list CORE | 15 | реализация списка |
| p2_array_rotate CORE | 8 | обработка одномерного массива |
| p3_unpack CORE | 7 | обработка строк |
| p4_wagons CORE | 8 | применение линейных контейнеров |
| p5_castle CORE | 7 | применение линейных контейнеров |
| k_swag CORE EXTRA | 20 + 10 | реализация очереди со скользящим окном и различными агрегатами |
// Лекция 03 завершена
// Резюме основных моментов
сортировка выбором и рекуррентное соотношение
итерация, математическая индукция с подстановкой
и дерево рекурсии
сортировка слиянием и варианты ее реализации
--> на следующей лекции
--> парадигма «разделяй-и-властвуй» (продолжение)
--> и мастер-теорема для анализа рекуррентных соотношений
};
return 0;
03->