СЕМЕСТР 1
алгоритмы
и структуры данных
СЕМЕСТР 1алгоритмыи структуры данных
анализ сложности
и корректности
анализ сложностии корректности
сортировка
и связанные задачи
сортировкаи связанные задачи
деревья поиска
и балансировка
деревья поискаи балансировка
структуры данных
структуры данных
асимптотика и символы Ландау
асимптотика исимволы Ландау
корректность
и инварианты
корректностьи инварианты
точная функция сложности
точная функциясложности
ожидаемая сложность
ожидаемаясложность
рекуррентные соотношения
рекуррентныесоотношения
ближайшие точки
на плоскости
ближайшие точкина плоскости
нелинейные алгоритмы
нелинейныеалгоритмы
граница оптимальности
границаоптимальности
линейные алгоритмы
линейныеалгоритмы
порядковые статистики
порядковыестатистики
бинарное
дерево
бинарноедерево
абстрактные типы данных и контейнер
абстрактные типыданных и контейнер
приоритетная очередь
приоритетнаяочередь
список, стек
и очередь
список, стеки очередь
декартово дерево
декартоводерево
случайные
деревья
случайныедеревья
splay-дерево
и амортизация
splay-деревои амортизация
AVL-дерево
и баланс высоты
AVL-деревои баланс высоты
ветвистое
В-дерево
ветвистоеВ-дерево
RB-дерево
и баланс путей
RB-деревои баланс путей
дискретная
математика
дискретнаяматематика
теория
вероятностей
теориявероятностей
математический анализ
математическийанализ
Text is not SVG - cannot display

                            class Algorithms {
                            public:
                                Algorithms lecture {
                                    .author = "Р.А. Нестеров"
                                    .year = "2026/27"
                                    .course = "НИУ ВШЭ ПИ, 2 курс"
                                };
                        

алгоритмы
и структуры данных


                                // 01. Сортировка подсчетом
                                void bucket_sort(std::vector<int>& vec);
                                void counting_sort(std::vector<int>& vec);
                        

                                // 02. LSB-поразрядная сортировка и варианты ее реализации
                                void radix_sort(std::vector<int>& vec);
                        

                                // 03. Домашняя работа set_3: применение библиотеки Google Benchmark
                                // для многостороннего бенчмаркинга алгоритмов
                                void fetch_new_tasks();
                        
                        
                    

->07

07.00

краткая ретроспектива и повторение

алгоритмы сортировки на основе сравнений
[cb-сортировки]

классификация алгоритмов сортировки


                       void comparisons_lower_bound();
                    

дерево решений для сортировки

явная оценка высоты дерева решений cb-сортировки

$$a_1 \leqslant a_2$$
$$a_2 \leqslant a_3$$
$$a_1 \leqslant a_3$$
$$a_1 \leqslant a_2 \leqslant a_3$$
$$a_1 \leqslant a_3$$
$$a_2 \leqslant a_1 \leqslant a_3$$
$$a_2 \leqslant a_3$$
$$a_1 \leqslant a_3 \leqslant a_2$$
$$a_3 \leqslant a_1 \leqslant a_2$$
$$a_3 \leqslant a_2 \leqslant a_1$$
$$a_2 \leqslant a_3 \leqslant a_1$$
Text is not SVG - cannot display

утверждение 06.03

асимптотическая нижняя граница высоты $h$ дерева решений для cb-сортировки составляет $\Omega(n \log n)$

ключевое следствие

  • алгоритм сортировки merge_sort является оптимальным
  • алгоритм сортировки heap_sort является оптимальным

                       void classify_sorting_algorithms();
                    

классификация сортировок

ключевые параметры сравнения алгоритмов

внутренняя

  • алгоритм и входные данные полностью помещаются в оперативную память [RAM]
  • число I/O-операций с диском не зависит от размера входных данных [загрузить и выгрузить]

внешняя

  • блоки входных данных поочередно выгружаются в оперативную память для сортировки
  • алгоритм нацелен на минимизацию числа I/O-операций с диском при разбиении данных на блоки и их слиянии

                       void classify_sorting_algorithms();
                    

классификация сортировок

ключевые параметры сравнения алгоритмов

по месту, in-place

  • объем дополнительной памяти, необходимой алгоритму,
    не зависит от размера входных данных
  • пространственная сложность
    $\mathrm{O}(1)$, $\mathrm{O}(\log n)$, ...

с дополнительной памятью

  • объем дополнительной памяти, необходимой алгоритму,
    определяется размером входных данных
  • пространственная сложность
    $\mathrm{O}(n)$, $\mathrm{O}(n \log n)$, ...

                       void classify_sorting_algorithms();
                    

классификация сортировок

ключевые параметры сравнения алгоритмов

устойчивая, stable

  • алгоритм сохраняет исходный порядок следования эквивалентных объектов
[(8, fox), (9, dog), (4, wolf), (8, cow)] [(4, wolf), (8, fox), (8, cow), (9, dog)]

неустойчивая, unstable

  • алгоритм может нарушить исходный порядок
    следования эквивалентных объектов
[(8, fox), (9, dog), (4, wolf), (8, cow)] [(4, wolf), (8, cow), (8, fox), (9, dog)]
insertion_sort.cpp
                                    
                                        #include <vector>
                                        void insertion_sort(std::vector<int>& arr) { 
                                            int n = arr.size();
                                            for (int i = 1; i < n; ++i) {  
                                                int key = arr[i];
                                                int j = i - 1;  
                                                while (j >= 0 && arr[j] > key) {
                                                    arr[j + 1] = arr[j];
                                                    j = j - 1; 
                                                }
                                                arr[j + 1] = key;
                                            }
                                        }
                                    
                                

утверждение 08.01

алгоритм insertion_sort является устойчивым
quick_sort.cpp
                                    
                                        #include <vector>
                                        #include <algorithm>
                                        #include <iterator>

                                        template<typename Iter>
                                        void quick_sort(Iter first, Iter last) {
                                            if (std::distance(first, last) <= 1) return;
                                            
                                            auto pivot_pos = partition(first, last);
                                            
                                            quick_sort(first, pivot_pos);
                                            quick_sort(std::next(pivot_pos), last);
                                        }
                                    
                                

утверждение 08.01

алгоритм quick_sort не является устойчивым
Алгоритм Аналог в STL IN-PLACE? stable? Худший случай Примечание
selection_sort — ✅ ❌ $\mathrm{O}(n^2)$ —
insertion_sort — ✅ ✅ $\mathrm{O}(n^2)$ полезно для небольших массивов и тех, что уже упорядочены
shell_sort — ✅ ❌ $\mathrm{O}(n^{1.5})$ —
merge_sort std::stable_sort ❌ ✅ $\mathrm{O}(n \log n)$ устойчивый алгоритм с гарантированной сложностью
quick_sort std::sort ✅ ❌ $\mathrm{O}(n^2)$ вероятностная гарантия оптимальной сложности
heap_sort std::make_heap std::sort_heap ✅ ❌ $\mathrm{O}(n \log n)$ НЕустойчивый алгоритм с гарантированной сложностью

*std::nth_element для поиска порядковой статистики

*std::partial_sort для выборки заданного числа наименьших элементов массива [in-place]

неделя №9
--> квиз Л6


                       void classify_sorting_algorithms();
                    

классификация сортировок

ключевые параметры сравнения алгоритмов

на сравнениях

  • требуется определение полного порядка на множестве упорядочиваемых объектов
  • известная нижняя граница сложности — $\Omega(n \log n)$

нишевые, линейные

  • используется специфика входных данных для достижения линейной временной сложности
  • обычно требуется дополнительная память

07.01

сортировка подсчетом

базовая схема алгоритма bucket_sort
для сортировки массива уникальных объектов

алгоритм counting_sort для сортировки
массива объектов с повторениями


                       void bucket_sort(std::vector<int>& arr);
                    

сортировка номеров телефонов

массив уникальных чисел в известном диапазоне

задача 08.01

  • требуется отсортировать большое количество телефонных номеров — около 10 миллионов записей
  • телефонные номера являются уникальными
    и состоят из семи цифр

проблемы quick_sort

  • весь массив необходимо выгружать в оперативную память [потенциальные трудности масштабирования]
  • для 10 миллионов семизначных чисел потребуется
    $10^7 \cdot 32$ бита $\approx 38.1$ мегабайт

                       void bucket_sort(std::vector<int>& arr);
                    

сортировка номеров телефонов

массив уникальных чисел в известном диапазоне

задача 08.01

  • требуется отсортировать большое количество телефонных номеров — около 10 миллионов записей
  • телефонные номера являются уникальными
    и состоят из семи цифр

схема алгоритма bucket_sort

  • создаем битовый вектор A размера $10^7$, что потребует
    $10^7 \colon 2^3 \colon 2^{10} \colon 2^{10} = 1.2$ мегабайт [выигрыш более 30 раз]
  • для каждого номера phone устанавливаем A[phone] = 1
  • выводим индексы A, для которых A[phone] == 1
  • сложность $\Theta(n+m)$ — псевдолинейная


                       void bucket_sort(std::vector<int>& arr);
                    

сортировка номеров телефонов

массив уникальных чисел в известном диапазоне

схема алгоритма bucket_sort

  • создаем битовый вектор A размера $10^7$, что потребует
    $10^7 \colon 2^3 \colon 2^{10} \colon 2^{10} = 1.2$ мегабайт [выигрыш более 30 раз]
  • для каждого номера phone устанавливаем A[phone] = 1
  • выводим индексы A, для которых A[phone] == 1
  • сложность $\Theta(n+m)$ — псевдолинейная

ограничения bucket_sort

  • алгоритм не применяется для произвольных данных: нам известен диапазон $[\min, \max]$, в который они попадают
  • если во входных данных есть повторения, то простого битового вектора становится недостаточно
?

обладает ли counting_sort
этими свойствами

* клик по ответу покажет, верный ли он

  • обеспечивает устойчивость по умолчанию
  • позволяет сортировать отрицательные числа
  • позволяет сортировать вещественные числа

07.02

LSB-поразрядная сортировка

поразрядная сортировка на основе массива очередей

экономичная [по памяти] поразрядная сортировка двоичных чисел: «двусторонний» массив


                       void radix_sort(std::vector<int>& arr);
                    

сортировка десятизначных чисел

массив чисел / строк в известном алфавите

задача 08.02

  • требуется отсортировать массив, содержащий больше количество целых чисел
  • числа содержат повторения и состоят из десяти цифр [можно дополнить ведущими нулями]

проблемы counting_sort

  • весь массив счетчиков необходимо выгружать в оперативную память [потенциальное исчерпание ее емкости]
  • хранение $10^{10}$ однобайтовых счетчиков потребует
    $10^{10} \colon 2^{10} \colon 2^{10} \colon 2^{10} \approx 9.3$ гигабайта

                       void radix_sort(std::vector<int>& arr);
                    

сортировка десятизначных чисел

массив чисел / строк в известном алфавите

задача 08.02

  • требуется отсортировать массив, содержащий больше количество целых чисел [основание системы счисления $B$]
  • числа содержат повторения и состоят из десяти цифр [можно дополнить ведущими нулями]

схема алгоритма radix_sort

  • выбираем устойчивый линейный алгоритм
    [cb-сортировки — другой класс сложности]
  • пошагово упорядочиваем разряды в порядке от младшего разряда к старшему [LSB — least significant byte]

сложность $\mathrm{O}(\ell \cdot (n + B))$ — псевдолинейная


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 1 создаем дополнительный массив для хранения очередей


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 2 сортируем числа по третьему биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 2 сортируем числа по третьему биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 2 сортируем числа по третьему биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 3 очищаем исходный массив и сортируем по второму биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 3 очищаем исходный массив и сортируем по второму биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 3 очищаем исходный массив и сортируем по второму биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 3 очищаем исходный массив и сортируем по второму биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 4 очищаем дополнительный массив и сортируем по старшему биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 4 очищаем дополнительный массив и сортируем по старшему биту


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 5 очищаем исходный массив


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 6 копируем первую половину


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 7 копируем развернутую вторую половину


                       void radix_sort(std::vector<int>& arr);
                    

сортировка двоичных чисел

двусторонний массив c «двойной буферизацией»

шаг 8 сортировка завершена

?

обладает ли radix_sort
этими свойствами

* клик по ответу покажет, верный ли он

  • обеспечивает устойчивость по умолчанию
  • позволяет сортировать отрицательные числа
  • позволяет сортировать вещественные числа

07.03

домашняя работа set_3

задачи на сортировки и стохастические алгоритмы

часть k: бенчмаркинг алгоритмов с помощью
библиотеки Google Benchmark


                       void fetch_new_tasks();
                    

домашняя работа set_3

сортировки  ·  стохастические алгоритмы  ·  бенчмаркинг и экспериментальный анализ

CORE
обязательная задача
EXTRA
бонусная задача
ID Баллы Комментарий
p1_heap CORE 5 реализация пирамидальной сортировки
p2_counting CORE 6 реализация сортировки подсчетом
p3_radix256 CORE 6 реализация поразрядной сортировки с заданным основанием
p4_fractions CORE 5 применение библиотечной сортировки
p5_freivalds CORE 7 случайный алгоритм проверки матричного умножения
p6_queens CORE 7 случайный алгоритм расстановки ферзей на шахматной доске
k1_mersertion CORE 10 + 10 бенчмаркинг гибридной сортировки с помощью Google Benchmark
k2_3circles CORE 7 + 8 исследование сходимости метода Монте-Карло
chrono_time.cpp
                                    
                                        #include <iostream>
                                        #include <vector>
                                        #include <numeric>
                                        #include <algorithm>
                                        #include <random>
                                        #include <chrono>

                                        constexpr size_t kN = 128;

                                        void bubble_sort(std::vector<int>& arr) {
                                            for (size_t i = 0; i < arr.size() - 1; ++i) {
                                                for (size_t j = 0; j < arr.size() - i - 1; ++j) {
                                                    if (arr[j] > arr[j + 1]) std::swap(arr[j], arr[j + 1]);
                                                }
                                            }
                                        }

                                        int main() {
                                            using clock = std::chrono::high_resolution_clock;
                                            using nanos = std::chrono::nanoseconds;

                                            std::vector<int> arr(kN);
                                            std::iota(arr.begin(), arr.end(), 1);
                                            std::mt19937 g(42);
                                            std::shuffle(arr.begin(), arr.end(), g);
                                            
                                            auto start = clock::now();
                                            bubble_sort(arr);
                                            auto end = clock::now();
                                            auto dur = std::chrono::duration_cast<nanos>(end - start).count();

                                            std::cout << "время сортировки: " << dur << " нс\n";
                                            return 0;
                                        }
                                    
                                
bash — ~/project
user-algo:~$ ./chrono_time время сортировки: 63083 нс user-algo:~$

библиотека chrono

  • погрешность и точность системного таймера
  • измерение Wall Time [время на часах] с учетом лишнего «шума» операционной системы
  • статистическая нестабильность результатов
benchmark_demo.cpp
                                    
                                        #include <benchmark/benchmark.h>
                                        #include <vector>
                                        #include <algorithm>

                                        void HybridSort(std::vector<int>& arr, size_t threshold) {
                                            std::sort(arr.begin(), arr.end());
                                        }

                                        static void BM_Example(benchmark::State& state) {
                                            // 1. Извлекаем параметры из сгенерированной сетки
                                            const size_t kN = state.range(0);         
                                            const size_t kThreshold = state.range(1);

                                            // Инициализация исходных данных
                                            std::vector<int> source(kN, 42);

                                            // 2. Основной измеряемый цикл фреймворка
                                            for (auto _ : state) {
                                                state.PauseTiming();  // Пауза таймера
                                                std::vector<int> target = source;
                                                state.ResumeTiming(); // Включаем таймер

                                                HybridSort(target, kThreshold);

                                                // Защита от оптимизаций компилятора
                                                benchmark::DoNotOptimize(target.data());
                                                benchmark::ClobberMemory();
                                            }
                                        }

                                        // 3. Возможности задания диапазонов аргументов фреймворка:

                                        // Вариант А: Простая линейная сетка параметров (DenseRange)
                                        // Запустит бенчмарк для порогов: 4, 8, 12, 16
                                        // BENCHMARK(BM_HybridSortExample)->DenseRange(4, 16, 4);

                                        // Вариант Б: Двумерная матрица параметров (ArgsProduct)
                                        // Протестирует комбинации ВСЕХ размеров N и ВСЕХ порогов
                                        BENCHMARK(BM_Example)->ArgsProduct({
                                            {128, 512},
                                            benchmark::CreateDenseRange(4, 12, 4)
                                        });

                                        BENCHMARK_MAIN(); // Генерирует main() автоматически


                                    
                                
bash — ~/project
user-algo:~$ g++ -O3 benchmark_demo.cpp -I/opt/homebrew/include -L/opt/homebrew/lib -lbenchmark -lpthread -o benchmark_demo user-algo:~$ ./benchmark_demo Running ./benchmark_demo Run on (10 X 24 MHz CPU s) CPU Caches: L1 Data 64 KiB L1 Instruction 128 KiB L2 Unified 4096 KiB (x10) Load Average: 1.73, 2.23, 2.18 ------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------- BM_Example/128/4 512 ns 513 ns 1371473 BM_Example/512/4 902 ns 903 ns 784077 BM_Example/128/8 519 ns 520 ns 1350283 BM_Example/512/8 917 ns 919 ns 753799 BM_Example/128/12 527 ns 529 ns 1363088 BM_Example/512/12 931 ns 927 ns 766544 user-algo:~$

                                // Лекция 07 завершена
                                // Резюме основных моментов
                        

что мы
рассмотрели сегодня:

  • нишевая сортировка #1

    линейная сортировка
    и вопросы ее применимости

  • нишевая сортировка #2

    LSB-поразрядная сортировка
    и вопросы ее применимости / реализации

  • бенчмаркинг алгоритмов с помощью Google Benchmark

    практические приемы проведения
    экспериментального исследования производительности

--> на следующей лекции
--> бинарные деревья [поиска] — часть #1
--> обходы, итерация и основные операции


                            };
                            return 0;
                        

07->

/* Маркеры глав на таймлайне */ video::-webkit-media-text-track-container { display: none; /* скрываем всплывающие подписи */ } /* Подсветка маркеров при наведении */ video::-webkit-media-controls-timeline { background: linear-gradient(to right, transparent 0%, transparent 100%); }