diff --git a/labs/lab_01/example/Untitled.md b/labs/lab_01/example/Untitled.md new file mode 100644 index 0000000..6882abb --- /dev/null +++ b/labs/lab_01/example/Untitled.md @@ -0,0 +1,310 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Цель работы + +Целью данной лабораторной работы является приобретение практических навыков эмпирического анализа временной сложности алгоритмов. Это включает проведение серии экспериментальных замеров времени работы различных алгоритмов при изменении размера входных данных, построение графиков зависимости времени от объема данных, а также проведение теоретического анализа и сопоставление полученных эмпирических результатов с теоретическими оценками временной сложности. + + +1.2 сумма + +```python +import random +import usage_time +import matplotlib.pyplot as plt + +def sum_nums(v: list): + total = 0 + for num in v: + total += num + return total + +items = range(1, 10**5 * (20 - 6), 50000) +func = usage_time.get_usage_time()(sum_nums) +times = [ + func([ + random.randint(1, 3) + for _ in range(n) + ]) + for n in items +] + +plt.plot(items, times, 'bo-') +ax = plt.gca() + +plt.title('The execution time of the sum of list numbers algorithm') +ax.set_xlabel('Number of elements') +ax.set_ylabel('Time, sec') +plt.savefig('sum.png') +plt.show() + +``` + +![](images/sum.png) + +1.3 произведение эллементов + +```python + +``` + +```python +import random, usage_time +import matplotlib.pyplot as plt + + +def multiplication_nums(v: list): + mult = 1.0 + for num in v: + mult *= num + return mult + + +items = range(1, 10**5 * (20 - 6), 50000) +func = usage_time.get_usage_time()(multiplication_nums) +times = [ + func([ + random.randint(1, 3) + for _ in range(n) + ]) + for n in items +] + +fig = plt.plot(items, times, 'bo-') +ax = plt.gca() + +plt.title('The execution time of the get multiplication of list numbers algorithm') +ax.set_xlabel('Number of elements') +ax.set_ylabel('Time, sec') +plt.savefig('mult.png') +plt.show() +``` + +![](images/mult.png) + +1.6. поиск минимума простым перебором + +```python +import random +import usage_time +import matplotlib.pyplot as plt + + +def get_min(v: list) -> int: + min_num = v[0] + for num in v: + if num < min_num: + min_num = num + return min_num + + +items = range(1, 10**5 * (20 - 6), 50000) +func = usage_time.get_usage_time()(get_min) +times = [ + sum([ + func([ + random.randint(1, 10) + for _ in range(n) + ]) + for _ in range(20) + ]) / 20 + for n in items +] + +plt.plot(items, times, 'bo-') +ax = plt.gca() + +plt.title('The execution time of the getting min of list numbers algorithm') +ax.set_xlabel('Number of elements') +ax.set_ylabel('Time, sec') +plt.grid(True) +plt.savefig('min.png') +plt.show() +``` + +![](images/min.png) + +1.4. вычисление полинома методом Горнера + +```python +import random +import usage_time +import matplotlib.pyplot as plt + + +def horner(coeffs: list, x: float) -> float: + result = 0 + for coef in reversed(coeffs): + result = result * x + coef + return result + + +items = range(1, 10**5 * (20 - 6), 50000) +func = usage_time.get_usage_time()(horner) + +times = [ + sum([ + func( + [random.random() for _ in range(n)], + random.random() + ) + for _ in range(20) + ]) / 20 + for n in items +] + +plt.plot(items, times, 'bo-') +ax = plt.gca() + +plt.title('Execution time of polynomial evaluation by Horner\'s method') +ax.set_xlabel('Number of polynomial coefficients') +ax.set_ylabel('Time, sec') +plt.grid(True) +plt.savefig('horn.png') +plt.show() +``` +![](images/horn.png) + +Задание 2 + +```python +import random, usage_time +import matplotlib.pyplot as plt + + +def matrix_mult(matrix_a: list, matrix_b: list): + matrix_c = [ + [0 for _ in range(len(matrix_a))] + for _ in range(len(matrix_a)) + ] + + for y in range(len(matrix_a)): + for x in range(len(matrix_a)): + num = 0 + for i in range(len(matrix_a)): + num += matrix_a[y][i] * matrix_b[i][x] + matrix_c[y][x] = num + print(len(matrix_a)) + return matrix_c + + +items = range(1, 10**2 * (20 - 6), 100) +func = usage_time.get_usage_time()(matrix_mult) +times = [ + func( + [ + [ + random.randint(1, 3) + for _ in range(n) + ] + for _ in range(n) + ], + [ + [ + random.randint(4, 6) + for _ in range(n) + ] + for _ in range(n) + ] + ) + for n in items +] + +fig = plt.plot(items, times, 'bo-') +ax = plt.gca() + +plt.title('The execution time of the get matrix multiplication of list numbers algorithm') +ax.set_xlabel('Number of elements') +ax.set_ylabel('Time, sec') +plt.savefig('matr.png') +plt.show() +``` + +![](images/matr.png) + + +## Контрольные вопросы по вычислительной сложности алгоритмов + +1. Вычислительная сложность алгоритма — это характеристика количества ресурсов (времени и памяти), которые требуются алгоритму для обработки данных в зависимости от размера входа. Анализ сложности важен для оценки эффективности алгоритма и прогноза его поведения при большом объеме данных. +2. Время выполнения — сколько времени требуется алгоритму для обработки входных данных. Пространство (память) — сколько памяти алгоритм использует. Иногда приходится жертвовать временем ради уменьшения памяти (например, жадные алгоритмы) или наоборот — использовать больше памяти для ускорения работы (например, хеш-таблицы). +3. Асимптотический анализ изучает поведение алгоритма при больших объемах данных, избавляясь от постоянных и малозначимых факторов. Это удобнее, чем точные замеры в наносекундах, которые сильно зависят от конкретного оборудования и условий выполнения. +4. Нотация «О-большое» (Big O) описывает верхнюю границу времени или памяти, которую может потребовать алгоритм в худшем случае при росте объема данных. +5. Классы сложности (в порядке возрастания): +| Класс сложности | Описание | Пример алгоритма | +| :-- | :-- | :-- | +| O(1) | Константная | Доступ к элементу по индексу | +| O(log n) | Логарифмическая | Бинарный поиск | +| O(n) | Линейная | Простое сканирование массива | +| O(n log n) | Линейно-логарифмическая | Быстрая сортировка (QuickSort) | +| O(n²) | Квадратичная | Сортировка пузырьком | +| O(2ⁿ) | Экспоненциальная | Наивное вычисление Фибоначчи | + +6. Сложность фрагментов кода: + +- Простой цикл от 0 до n — O(n). +- Два вложенных цикла от 0 до n — O(n²). +- Цикл с удвоением счетчика (i = i * 2) — O(log n). +- Цикл с делением счетчика на 2 (i = i / 2) — O(log n). +- Два независимых цикла подряд — O(n + m), при m=n — O(n). +- Рекурсивная функция, вызывающая себя дважды — O(2ⁿ). + +7. Сложность в худшем, среднем и лучшем случае отличается по объему потребляемых ресурсов для разных входных данных. Например, QuickSort имеет O(n²) в худшем (плохой выбор опорного элемента) и O(n log n) в среднем и лучшем. +8. Пространственная сложность — количество дополнительной памяти, используемой алгоритмом. Для рекурсивных функций учитывается память стека вызовов, равная глубине рекурсии. +9. При очень малых n алгоритм с O(2ⁿ) может быть быстрее, чем O(n³), так как для маленьких n экспонента не успеет «выстрелить». Однако для средних и больших n O(n³) рациональнее из-за экспоненциального роста. +10. Временная сложность операций: + +| Операция | Сложность | +| :--: | :---: | +| Поиск в неотсортированном массиве | O(n) | +| Поиск в отсортированном массиве | O(log n) | +| Вставка в начало связного списка | O(1) | +| Вставка в хеш-таблицу (ср.случай) | O(1), (худший) O(n) | +| Поиск минимума в мин-куче | O(1) | + +11. Сравнение сортировок: + +- QuickSort: средний O(n log n), худший O(n²), зависит от выбора опорного элемента. +- MergeSort: всегда O(n log n), но из-за дополнительной памяти и копирования в практике медленнее. +- Insertion Sort: эффективнее MergeSort на почти отсортированных или очень маленьких массивах. + +12. Пространственно-временная дилемма — компромисс между временем и памятью, например, использование хеш-таблиц (больше памяти, меньше времени) vs. обход без дополнительной памяти (дольше). +13. NP-полнота — класс задач, решение которых можно проверить за полиномиальное время, но неизвестно, можно ли их решить за полиномиальное время. Класс P — задачи, решаемые за полиномиальное время. +14. Полиномиальное решение одной NP-полной задачи означает, что все NP-полные задачи можно быстро решить — важнейшая проблема теории алгоритмов (P=NP?). +15. NP-полноту доказывают сведением задачи к уже известной NP-полной задаче — так называемое "сведение по Карпу". +16. Омега (Ω) — нижняя оценка, тета (Θ) — точная оценка. В отличии от O, они дают соответственно минимум или точный порядок роста. +17. Сложность определяется по наибольшему слагаемому, так как при больших n остальные становятся несущественными. Константы отбрасываются для удобства анализа. +18. Не всегда. При малых n O(n) может быть быстрее O(log n) из-за констант и накладных расходов. +19. Для анализа можно предложить конкретный код. +20. Поиск двух чисел с суммой X в отсортированном массиве: + +- Используем два указателя — один слева, другой справа. +- Если сумма больше X — сдвигаем правый указатель, иначе левый. +- Сложность O(n), память O(1). +- Эффективнее чем полный перебор O(n²). + +21. Улучшение алгоритма: + +- Пример: из O(n²) сделать O(n) с помощью хеш-таблицы для поиска пар чисел. + + +## Выводы по лабораторной работе + +В ходе лабораторной работы были успешно реализованы замеры времени работы алгоритмов для различных функций и операций на случайных данных разного объема. Полученные эмпирические графики временных затрат в целом соответствуют ожидаемым теоретическим оценкам асимптотической сложности алгоритмов. + +Эксперимент подтвердил, что для алгоритмов с разной сложностью рост времени их исполнения при возрастании размера входных данных соответствует классификации: от константной и линейной до квадратичной и кубической. По мере увеличения объема данных различия во временных сложностях становятся все более очевидными. + +Эмпирический анализ показал важность учета практических особенностей исполнения, таких как накладные расходы системы и случайные отклонения времени, что требует усреднения результатов по нескольким запускам. + +Таким образом, лабораторная работа продемонстрировала применение эмпирического подхода для оценки временной сложности алгоритмов, что является важным дополнением к теоретическому анализу и помогает лучше понять их поведение на практике. + diff --git a/labs/lab_01/example/Untitled1.md b/labs/lab_01/example/Untitled1.md new file mode 100644 index 0000000..01f385f --- /dev/null +++ b/labs/lab_01/example/Untitled1.md @@ -0,0 +1,9 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 +--- diff --git a/labs/lab_01/example/images/horn.png b/labs/lab_01/example/images/horn.png new file mode 100644 index 0000000..3dec0db Binary files /dev/null and b/labs/lab_01/example/images/horn.png differ diff --git a/labs/lab_01/example/images/matr.png b/labs/lab_01/example/images/matr.png new file mode 100644 index 0000000..d337bdd Binary files /dev/null and b/labs/lab_01/example/images/matr.png differ diff --git a/labs/lab_01/example/images/min.png b/labs/lab_01/example/images/min.png new file mode 100644 index 0000000..ac264b3 Binary files /dev/null and b/labs/lab_01/example/images/min.png differ diff --git a/labs/lab_01/example/images/mult.png b/labs/lab_01/example/images/mult.png new file mode 100644 index 0000000..2b1ef3f Binary files /dev/null and b/labs/lab_01/example/images/mult.png differ diff --git a/labs/lab_01/example/images/sum.png b/labs/lab_01/example/images/sum.png new file mode 100644 index 0000000..0fa7b53 Binary files /dev/null and b/labs/lab_01/example/images/sum.png differ diff --git a/labs/lab_01/example/mult.png b/labs/lab_01/example/mult.png new file mode 100644 index 0000000..116927c Binary files /dev/null and b/labs/lab_01/example/mult.png differ diff --git a/labs/lab_02/var6/LaTeX/comb_sort.pdf b/labs/lab_02/var6/LaTeX/comb_sort.pdf new file mode 100644 index 0000000..53f9fe1 Binary files /dev/null and b/labs/lab_02/var6/LaTeX/comb_sort.pdf differ diff --git a/labs/lab_02/var6/LaTeX/comb_sort.png b/labs/lab_02/var6/LaTeX/comb_sort.png new file mode 100644 index 0000000..5a35472 Binary files /dev/null and b/labs/lab_02/var6/LaTeX/comb_sort.png differ diff --git a/labs/lab_02/var6/LaTeX/comb_sort.tex b/labs/lab_02/var6/LaTeX/comb_sort.tex new file mode 100644 index 0000000..d2cf5ab --- /dev/null +++ b/labs/lab_02/var6/LaTeX/comb_sort.tex @@ -0,0 +1,54 @@ +\documentclass[tikz,border=3.14mm]{standalone} +\usepackage{tikz} +\usetikzlibrary{shapes.geometric, arrows.meta, positioning} + +\tikzset{ + startstop/.style = {rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + process/.style = {rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + decision/.style = {diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30}, + arrow/.style = {thick, -Stealth} +} + +\begin{document} +\begin{tikzpicture}[node distance=1.5cm] + +% Main algorithm flow +\node (start) [startstop] {Start Comb Sort}; +\node (init) [process, below=of start] {gap = n\\shrink = 1.3\\sorted = false}; +\node (dec1) [decision, below=of init] {gap > 1 OR\\not sorted?}; +\node (update) [process, below=of dec1] {gap = max(1, floor(gap/shrink))\\sorted = true}; +\node (init_i) [process, below=of update] {i = 0}; +\node (dec2) [decision, below=of init_i] {i + gap < n?}; + +% Inner loop - comparison and swap +\node (dec3) [decision, right=2cm of dec2] {array[i] > array[i+gap]?}; +\node (swap) [process, below=of dec3] {Swap array[i] and array[i+gap]\\sorted = false}; +\node (increment) [process, below=2cm of dec2] {i = i + 1}; +\node (stop) [startstop, below=2.5cm of dec1] {End}; + +% Connections - main flow +\draw [arrow] (start) -- (init); +\draw [arrow] (init) -- (dec1); +\draw [arrow] (dec1) -- node[right] {Yes} (update); +\draw [arrow] (update) -- (init_i); +\draw [arrow] (init_i) -- (dec2); + +% Connections - inner loop +\draw [arrow] (dec2) -- node[above] {Yes} (dec3); +\draw [arrow] (dec3) -- node[right] {Yes} (swap); +\draw [arrow] (swap) |- (increment); + +% Connections - no swap path +\draw [arrow] (dec3.east) -- ++(0.5,0) node[above] {No} |- (increment.east); + +% Connections - loop back +\draw [arrow] (increment.west) -| node[below left] {Continue inner loop} ([xshift=-1cm]dec2.west) -- (dec2.west); + +% Connections - exit conditions +\draw [arrow] (dec2.south) -- node[left] {No} (increment.north); +\draw [arrow] (increment.south) -- ++(0,-0.5) -| node[below right] {Next iteration} ([xshift=1cm]dec1.east) -- (dec1.east); + +\draw [arrow] (dec1.west) -- node[above] {No} ++(-2,0) |- (stop.west); + +\end{tikzpicture} +\end{document} diff --git a/labs/lab_02/var6/LaTeX/radix_sort.pdf b/labs/lab_02/var6/LaTeX/radix_sort.pdf new file mode 100644 index 0000000..c8d4917 Binary files /dev/null and b/labs/lab_02/var6/LaTeX/radix_sort.pdf differ diff --git a/labs/lab_02/var6/LaTeX/radix_sort.png b/labs/lab_02/var6/LaTeX/radix_sort.png new file mode 100644 index 0000000..8eda452 Binary files /dev/null and b/labs/lab_02/var6/LaTeX/radix_sort.png differ diff --git a/labs/lab_02/var6/LaTeX/radix_sort.tex b/labs/lab_02/var6/LaTeX/radix_sort.tex new file mode 100644 index 0000000..11aa754 --- /dev/null +++ b/labs/lab_02/var6/LaTeX/radix_sort.tex @@ -0,0 +1,43 @@ +\documentclass[tikz,border=3.14mm]{standalone} +\usepackage{tikz} +\usetikzlibrary{shapes.geometric, arrows.meta, positioning} + +\tikzset{ + startstop/.style = {rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + process/.style = {rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + decision/.style = {diamond, minimum width=2.5cm, minimum height=1.5cm, text centered, draw=black, fill=green!30, align=center}, + arrow/.style = {thick, -Stealth} +} + +\begin{document} +\begin{tikzpicture}[node distance=1.2cm] + +\node (start) [startstop] {Start}; +\node (findMax) [process, below=of start] {Find maximum number}; +\node (initExp) [process, below=of findMax] {Set digit position = 1}; +\node (dec1) [decision, below=of initExp] {More digits?}; +\node (sortDigit) [process, below=of dec1] {Stable sort by current digit}; +\node (nextDigit) [process, below=of sortDigit] {Move to next digit position}; +\node (stop) [startstop, below=2cm of dec1] {End}; + +% Main flow +\draw [arrow] (start) -- (findMax); +\draw [arrow] (findMax) -- (initExp); +\draw [arrow] (initExp) -- (dec1); +\draw [arrow] (dec1) -- node[right] {Yes} (sortDigit); +\draw [arrow] (sortDigit) -- (nextDigit); +\draw [arrow] (nextDigit.west) -- ++(-1,0) |- (dec1.west); +\draw [arrow] (dec1.east) -- node[above] {No} ++(1,0) |- (stop.east); + +% Algorithm details +\node [right=1cm of sortDigit, align=left, font=\small] { + \textbf{For each digit (LSD first):}\\ + • Use Counting Sort\\ + • Sort by current digit\\ + • Maintain relative order\\ + • Time: O(d*(n+k))\\ + • Space: O(n+k) +}; + +\end{tikzpicture} +\end{document} diff --git a/labs/lab_02/var6/Untitled.md b/labs/lab_02/var6/Untitled.md new file mode 100644 index 0000000..db7516e --- /dev/null +++ b/labs/lab_02/var6/Untitled.md @@ -0,0 +1,1008 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +### Вариант 6 + +## Сортировка расческой (Comb Sort) + +# 1. Классификация сортировки расчесткой + +Сортировка расчесткой является внутренней сортировкой, сортирующей элементы в оперативной памяти. Она относится к адаптивным алгоритмам, так как учитывает уже относительно отсортированные участки массива. Это сортировка на месте (on place), не требующая дополнительной памяти, кроме переменных для индексов и шага. По устойчивости сортировка неустойчивая, поскольку при обмене элементов порядок одинаковых ключей может изменяться. По сложности в худшем и среднем случаях время работы близко к $O(n^2)$, но за счет использования "расчески" значительно быстрее пузырьковой сортировки, особенно на больших массивах. + +# 2. Теоретическое описание сортировки расчесткой + +Сортировка расчесткой улучшает сортировку пузырьком, уменьшая вероятность "залипания" мелких элементов в конце массива. Основная идея — использовать меняющийся интервал (gap) сравнения пар элементов, с шагом уменьшающимся примерно в 1.3 раза после каждой итерации, пока не дойдет до 1. При каждом проходе элементы, находящиеся на расстоянии gap, сравниваются и при необходимости меняются местами. Как только gap достигает 1, сортировка превращается в обычную пузырьковую, но к этому моменту массив уже сильно упорядочен, что ускоряет сортировку. + +# 3. Блок-схема сортировки расчесткой + +![](comb.jpeg) + + +![](LaTeX/comb_sort.png) + + +# 4. Псевдокод сортировки расчесткой + +```python +def comb_sort(arr): + gap = len(arr) + shrink = 1.247 # оптимальный коэффициент + sorted = False + + while not sorted: + gap = int(gap / shrink) + if gap <= 1: + gap = 1 + sorted = True + i = 0 + while i + gap < len(arr): + if arr[i] > arr[i + gap]: + arr[i], arr[i + gap] = arr[i + gap], arr[i] + sorted = False + i += 1 + return arr + +``` + +# 5. Достоинства и недостатки + +**Достоинства:** + +- Быстрее пузырьковой сортировки благодаря уменьшению инвертированных пар с большим шагом. +- Простая реализация. +- Работает на месте, не требует дополнительной памяти. + +**Недостатки:** + +- Неустойчивая сортировка. +- Сложность в худшем случае близка к $O(n^2)$, что медленнее более сложных алгоритмов (быстрой или поразрядной сортировки). +- Эффективность зависит от коэффициента уменьшения gap (обычно 1.3). + +*** + +# 6. Реализовать алгоритмы сортировки согласно номеру индивидуального варианта. + +```python +import random +import usage_time +import matplotlib.pyplot as plt +import numpy as np + +def comb_sort(arr): + gap = len(arr) + shrink = 1.247 + sorted = False + while not sorted: + gap = int(gap / shrink) + if gap <= 1: + gap = 1 + sorted = True + i = 0 + while i + gap < len(arr): + if arr[i] > arr[i + gap]: + arr[i], arr[i + gap] = arr[i + gap], arr[i] + sorted = False + i += 1 + return arr + +%store comb_sort +``` + +# 7 Протестировать корректность реализации алгоритма и 8 Провести ручную трассировку алгоритма + +```python +def quick_comb_sort_debug(arr): + gap = len(arr) + shrink = 1.247 + step = 0 + + print(f"Начало: {arr}") + + while True: + gap = max(1, int(gap / shrink)) + step += 1 + print(f"\nШаг {step}, gap={gap}:") + + swapped = False + for i in range(len(arr) - gap): + if arr[i] > arr[i + gap]: + arr[i], arr[i + gap] = arr[i + gap], arr[i] + swapped = True + print(f" Меняем {arr[i+gap]}↔{arr[i]}: {arr}") + + if gap == 1 and not swapped: + break + + print(f"\nФинальный результат: {arr}") + return arr + +# Быстрая проверка +test = [6, 2, 8, 1, 5, 3] +print("Быстрая проверка сортировки:") +quick_comb_sort_debug(test.copy()) +``` + +Провести сравнение указанных алгоритмов сортировки массивов, содержащих n1, n2, n3 и n4 элементов. +Каждую функцию сортировки вызывать трижды: для сортировки упорядоченного массива, массива, упорядоченного в обратном порядке и неупорядоченного массива. Сортируемая последовательность для всех методов должна быть одинаковой (сортировать копии одного массива). +Проиллюстрировать эффективность алгоритмов сортировок по заданному критерию. Построить диаграммы указанных зависимостей. + +```python +import random +import usage_time +import matplotlib.pyplot as plt +import numpy as np + +def comb_sort(arr): + gap = len(arr) + shrink = 1.247 + sorted_flag = False + + while not sorted_flag: + gap = int(gap / shrink) + if gap <= 1: + gap = 1 + sorted_flag = True + + i = 0 + while i + gap < len(arr): + if arr[i] > arr[i + gap]: + arr[i], arr[i + gap] = arr[i + gap], arr[i] + sorted_flag = False + i += 1 + return arr + +sizes = [1000, 5000, 10000, 100000] + +def generate_arrays(size): + sorted_arr = list(range(size)) + reversed_arr = list(range(size, 0, -1)) + random_arr = [random.randint(1, size * 10) for _ in range(size)] + + return { + 'sorted': sorted_arr, + 'reversed': reversed_arr, + 'random': random_arr + } + +func = usage_time.get_usage_time(ndigits=6)(comb_sort) + +results = { + 'sorted': [], + 'reversed': [], + 'random': [] +} + +print("ИЗМЕРЕНИЕ ВРЕМЕНИ СОРТИРОВКИ РАСЧЕСКОЙ") +print("=" * 70) + +for size in sizes: + print(f"Размер массива: {size} элементов") + print("-" * 50) + + arrays = generate_arrays(size) + + for array_type, arr in arrays.items(): + time_taken = func(arr.copy()) + results[array_type].append(time_taken) + + print(f" {array_type:>8}: {time_taken:.6f} сек") + +plt.figure(figsize=(15, 5)) + +colors = {'sorted': 'green', 'reversed': 'red', 'random': 'blue'} + +for i, array_type in enumerate(['sorted', 'reversed', 'random'], 1): + plt.subplot(1, 3, i) + plt.plot(sizes, results[array_type], 'o-', color=colors[array_type], + linewidth=2, markersize=8) + + for size, time in zip(sizes, results[array_type]): + plt.annotate(f'{time:.4f}с', (size, time), + textcoords="offset points", xytext=(0,10), + ha='center', fontsize=8) + + plt.xlabel('Размер массива') + plt.ylabel('Время, секунды') + plt.title(f'Тип: {array_type}') + plt.grid(True, alpha=0.3) + +plt.tight_layout() +plt.show() + +print("СТАТИСТИЧЕСКИЙ АНАЛИЗ") +print("=" * 70) + +for array_type in ['sorted', 'reversed', 'random']: + times = results[array_type] + print(f"{array_type.upper():>15}:") + for size, time in zip(sizes, times): + print(f" {size:6d} эл.: {time:8.4f} сек") + + if len(times) > 1: + growth = times[-1] / times[0] + print(f" Рост времени ({sizes[-1]}/{sizes[0]}): {growth:.2f}x") + +print("СРАВНЕНИЕ ЭФФЕКТИВНОСТИ:") +print(f"Упорядоченный массив сортируется в {results['reversed'][-1]/results['sorted'][-1]:.2f} раза медленнее") +print(f"Случайный массив сортируется в {results['random'][-1]/results['sorted'][-1]:.2f} раза медленнее") +``` + +## Сравнение + +```python +import random +import usage_time +import matplotlib.pyplot as plt +import numpy as np + +def comb_sort(arr): + gap = len(arr) + shrink = 1.247 + sorted_flag = False + + while not sorted_flag: + gap = int(gap / shrink) + if gap <= 1: + gap = 1 + sorted_flag = True + + i = 0 + while i + gap < len(arr): + if arr[i] > arr[i + gap]: + arr[i], arr[i + gap] = arr[i + gap], arr[i] + sorted_flag = False + i += 1 + return arr + +def radix_sort(arr): + if not arr: + return arr + + max_num = max(arr) + exp = 1 + + while max_num // exp > 0: + counting_sort_for_radix(arr, exp) + exp *= 10 + + return arr + +def counting_sort_for_radix(arr, exp): + n = len(arr) + output = [0] * n + count = [0] * 10 + + for i in range(n): + index = (arr[i] // exp) % 10 + count[index] += 1 + + for i in range(1, 10): + count[i] += count[i - 1] + + for i in range(n - 1, -1, -1): + index = (arr[i] // exp) % 10 + output[count[index] - 1] = arr[i] + count[index] -= 1 + + for i in range(n): + arr[i] = output[i] + +sizes = [1000, 5000, 10000, 100000] + +def generate_arrays(size): + sorted_arr = list(range(size)) + reversed_arr = list(range(size, 0, -1)) + random_arr = [random.randint(1, size * 10) for _ in range(size)] + + return { + 'sorted': sorted_arr, + 'reversed': reversed_arr, + 'random': random_arr + } + +comb_func = usage_time.get_usage_time(ndigits=6)(comb_sort) +radix_func = usage_time.get_usage_time(ndigits=6)(radix_sort) + +comb_results = { + 'sorted': [], + 'reversed': [], + 'random': [] +} + +radix_results = { + 'sorted': [], + 'reversed': [], + 'random': [] +} + +print("ИЗМЕРЕНИЕ ВРЕМЕНИ СОРТИРОВКИ РАСЧЕСКОЙ") +print("=" * 70) + +for size in sizes: + print(f"Размер массива: {size} элементов") + print("-" * 50) + + arrays = generate_arrays(size) + + for array_type, arr in arrays.items(): + time_taken = comb_func(arr.copy()) + comb_results[array_type].append(time_taken) + + print(f" {array_type:>8}: {time_taken:.6f} сек") + +print("\nИЗМЕРЕНИЕ ВРЕМЕНИ ПОРАЗРЯДНОЙ СОРТИРОВКИ") +print("=" * 70) + +for size in sizes: + print(f"Размер массива: {size} элементов") + print("-" * 50) + + arrays = generate_arrays(size) + + for array_type, arr in arrays.items(): + time_taken = radix_func(arr.copy()) + radix_results[array_type].append(time_taken) + + print(f" {array_type:>8}: {time_taken:.6f} сек") + +plt.figure(figsize=(15, 5)) + +for i, array_type in enumerate(['sorted', 'reversed', 'random'], 1): + plt.subplot(1, 3, i) + + plt.plot(sizes, comb_results[array_type], 'o-', color='red', + linewidth=2, markersize=8, label='Comb Sort') + + plt.plot(sizes, radix_results[array_type], 's-', color='blue', + linewidth=2, markersize=8, label='Radix Sort') + + for j, size in enumerate(sizes): + plt.annotate(f'{comb_results[array_type][j]:.4f}с', (size, comb_results[array_type][j]), + textcoords="offset points", xytext=(0,10), + ha='center', fontsize=7, color='red') + plt.annotate(f'{radix_results[array_type][j]:.4f}с', (size, radix_results[array_type][j]), + textcoords="offset points", xytext=(0,-15), + ha='center', fontsize=7, color='blue') + + plt.xlabel('Размер массива') + plt.ylabel('Время, секунды') + plt.title(f'Тип: {array_type}') + plt.legend() + plt.grid(True, alpha=0.3) + +plt.tight_layout() +plt.show() + +print("СТАТИСТИЧЕСКИЙ АНАЛИЗ - СОРТИРОВКА РАСЧЕСКОЙ") +print("=" * 70) + +for array_type in ['sorted', 'reversed', 'random']: + times = comb_results[array_type] + print(f"{array_type.upper():>15}:") + for size, time in zip(sizes, times): + print(f" {size:6d} эл.: {time:8.4f} сек") + + if len(times) > 1: + growth = times[-1] / times[0] + print(f" Рост времени ({sizes[-1]}/{sizes[0]}): {growth:.2f}x") + +print("\nСТАТИСТИЧЕСКИЙ АНАЛИЗ - ПОРАЗРЯДНАЯ СОРТИРОВКА") +print("=" * 70) + +for array_type in ['sorted', 'reversed', 'random']: + times = radix_results[array_type] + print(f"{array_type.upper():>15}:") + for size, time in zip(sizes, times): + print(f" {size:6d} эл.: {time:8.4f} сек") + + if len(times) > 1: + growth = times[-1] / times[0] + print(f" Рост времени ({sizes[-1]}/{sizes[0]}): {growth:.2f}x") + +print("\nСРАВНЕНИЕ ЭФФЕКТИВНОСТИ:") +print(f"Comb Sort - обратный/упорядоченный: {comb_results['reversed'][-1]/comb_results['sorted'][-1]:.2f}x") +print(f"Comb Sort - случайный/упорядоченный: {comb_results['random'][-1]/comb_results['sorted'][-1]:.2f}x") +print(f"Radix Sort - обратный/упорядоченный: {radix_results['reversed'][-1]/radix_results['sorted'][-1]:.2f}x") +print(f"Radix Sort - случайный/упорядоченный: {radix_results['random'][-1]/radix_results['sorted'][-1]:.2f}x") +print(f"Comb/Radix на случайных данных: {comb_results['random'][-1]/radix_results['random'][-1]:.2f}x") +``` + + +## Поразрядная сортировка +# 1. Классификация алгоритмов сортировки + +- Внутренняя сортировка (работает в оперативной памяти) +- Устойчивая сортировка (сохраняет порядок элементов с одинаковыми ключами) +- Использует дополнительную память для промежуточных структур +- Линейная временная сложность при фиксированной длине ключей: $O(n \cdot k)$, где $n$ — количество элементов, $k$ — количество разрядов +- Сортирует методом обхода и группировки по разрядам из системы счисления (основание radix) + + +# 2. Теоретическое описание алгоритма + +Поразрядная сортировка "Radix Sort" обрабатывает элементы по разрядам ключа, сначала группирует их по младшему разряду (LSD) или по старшему (MSD). + +Основные шаги: + +1. Определяется максимальное количество разрядов у элементов. +2. На каждом шаге выполняется устойчивая сортировка по текущему разряду (например, сортировка подсчётом). +3. После сортировки по всем разрядам массив оказывается полностью отсортированным. + +Преимущество — отсутствие прямых сравнений элементов, что ускоряет работу при ограниченной длине ключей. + +# 3. Блок-схема алгоритма + + +![](LaTeX/radix_sort.png) + +- Начало +- Найти максимальный элемент и определить количество разрядов $d$ +- Цикл по $i = 1 \text{ до } d$: + - Устойчивая сортировка массива по $i$-му разряду +- Конец — массив отсортирован + + +# 4. Псевдокод алгоритма (LSD-версия) + +``` +function radixSort(array A): + maxVal = max(A) + d = число_разрядов(maxVal) + for digit in 1 to d: + A = stableSortByDigit(A, digit) + return A +``` + +Где `stableSortByDigit` — устойчивая сортировка массива по $digit$-му разряду, чаще всего применяется сортировка подсчётом. + +# 5. Достоинства и недостатки + +| Достоинства | Недостатки | +| :-- | :-- | +| Линейная сложность при фиксированном $k$ | Требует дополнительной памяти для подсчёта | +| Стабильность сортировки | Неэффективна при очень больших или длинных ключах | +| Отсутствие прямых сравнений элементов | Требует знания основания системы счисления | + +# 6. Реализовать алгоритмы сортировки согласно номеру индивидуального варианта. + + +```python +def radix_sort(arr): + if not arr: + return arr + + max_num = max(arr) + + exp = 1 + while max_num // exp > 0: + counting_sort_for_radix(arr, exp) + exp *= 10 + + return arr + +def counting_sort_for_radix(arr, exp): + """ + Вспомогательная функция для сортировки подсчетом по текущему разряду + """ + n = len(arr) + output = [0] * n + count = [0] * 10 + + for i in range(n): + index = (arr[i] // exp) % 10 + count[index] += 1 + + for i in range(1, 10): + count[i] += count[i - 1] + + for i in range(n - 1, -1, -1): + index = (arr[i] // exp) % 10 + output[count[index] - 1] = arr[i] + count[index] -= 1 + + for i in range(n): + arr[i] = output[i] +``` + +# 7. Ручная тестировка + +```python +def radix_sort_visual(arr): + print("Начало сортировки") + print(f"Исходный массив: {arr}") + + if not arr: + return arr + + max_num = max(arr) + print(f"Максимальное число: {max_num}") + + exp = 1 + iteration = 1 + + while max_num // exp > 0: + print(f"\n--- Итерация {iteration}: разряд {exp} ---") + arr = counting_sort_visual(arr, exp) + exp *= 10 + iteration += 1 + + print(f"\nСортировка завершена!") + print(f"Результат: {arr}") + return arr + +def counting_sort_visual(arr, exp): + n = len(arr) + output = [0] * n + count = [0] * 10 + + print("Шаг 1: Подсчет цифр") + for i in range(n): + digit = (arr[i] // exp) % 10 + count[digit] += 1 + print(f"{arr[i]} -> цифра {digit}") + + print(f"Count: {count}") + + print("Шаг 2: Преобразование count") + for i in range(1, 10): + count[i] += count[i - 1] + + print(f"Positions: {count}") + + print("Шаг 3: Построение output") + print(f"Исходный: {arr}") + + for i in range(n - 1, -1, -1): + digit = (arr[i] // exp) % 10 + position = count[digit] - 1 + output[position] = arr[i] + count[digit] -= 1 + print(f"{arr[i]} (цифра {digit}) -> позиция {position}") + print(f"Output: {output}") + + print(f"Результат: {output}") + return output + +def simple_radix_sort_visual(arr): + print(f"Сортируем: {arr}") + + if len(arr) <= 1: + return arr + + max_num = max(arr) + exp = 1 + + while max_num // exp > 0: + print(f"\nРазряд: {exp}") + + n = len(arr) + count = [0] * 10 + output = [0] * n + + print("Цифры чисел:") + for num in arr: + digit = (num // exp) % 10 + count[digit] += 1 + print(f"{num} -> {digit}") + + print(f"Count: {count}") + + for i in range(1, 10): + count[i] += count[i - 1] + + print(f"Позиции: {count}") + + for i in range(n - 1, -1, -1): + num = arr[i] + digit = (num // exp) % 10 + pos = count[digit] - 1 + output[pos] = num + count[digit] -= 1 + print(f"{num} -> позиция {pos}: {output}") + + arr = output + exp *= 10 + + print(f"\nИтог: {arr}") + return arr + +# Тестирование +if __name__ == "__main__": + print("ТЕСТ 1:") + test1 = [170, 45, 75, 90] + radix_sort_visual(test1.copy()) + + print("\n" + "="*40 + "\n") + + print("ТЕСТ 2:") + test2 = [42, 17, 89, 5] + simple_radix_sort_visual(test2.copy()) +``` + +# Пункты 9, 10, 11 + +```python +import random +import usage_time +import matplotlib.pyplot as plt +import numpy as np + +def radix_sort(arr): + if not arr: + return arr + + max_num = max(arr) + exp = 1 + + while max_num // exp > 0: + counting_sort_for_radix(arr, exp) + exp *= 10 + + return arr + +def counting_sort_for_radix(arr, exp): + n = len(arr) + output = [0] * n + count = [0] * 10 + + for i in range(n): + index = (arr[i] // exp) % 10 + count[index] += 1 + + for i in range(1, 10): + count[i] += count[i - 1] + + for i in range(n - 1, -1, -1): + index = (arr[i] // exp) % 10 + output[count[index] - 1] = arr[i] + count[index] -= 1 + + for i in range(n): + arr[i] = output[i] + +sizes = [1000, 2000, 4000, 8000] + +def generate_arrays(size): + sorted_arr = list(range(size)) + reversed_arr = list(range(size, 0, -1)) + random_arr = [random.randint(1, size * 10) for _ in range(size)] + + return { + 'sorted': sorted_arr, + 'reversed': reversed_arr, + 'random': random_arr + } + +func = usage_time.get_usage_time(ndigits=6)(radix_sort) + +results = { + 'sorted': [], + 'reversed': [], + 'random': [] +} + +print("ИЗМЕРЕНИЕ ВРЕМЕНИ ПОРАЗРЯДНОЙ СОРТИРОВКИ") +print("=" * 70) + +for size in sizes: + print(f"Размер массива: {size} элементов") + print("-" * 50) + + arrays = generate_arrays(size) + + for array_type, arr in arrays.items(): + time_taken = func(arr.copy()) + results[array_type].append(time_taken) + + print(f" {array_type:>8}: {time_taken:.6f} сек") + +plt.figure(figsize=(15, 5)) + +colors = {'sorted': 'green', 'reversed': 'red', 'random': 'blue'} + +for i, array_type in enumerate(['sorted', 'reversed', 'random'], 1): + plt.subplot(1, 3, i) + plt.plot(sizes, results[array_type], 'o-', color=colors[array_type], + linewidth=2, markersize=8, markerfacecolor='white', markeredgewidth=2) + + for size, time in zip(sizes, results[array_type]): + plt.annotate(f'{time:.4f}с', (size, time), + textcoords="offset points", xytext=(0,10), + ha='center', fontsize=8, fontweight='bold') + + plt.xlabel('Размер массива') + plt.ylabel('Время, секунды') + plt.title(f'Поразрядная сортировка: {array_type}') + plt.grid(True, alpha=0.3) + +plt.tight_layout() +plt.show() + +print("СТАТИСТИЧЕСКИЙ АНАЛИЗ") +print("=" * 70) + +for array_type in ['sorted', 'reversed', 'random']: + times = results[array_type] + print(f"{array_type.upper():>15}:") + for size, time in zip(sizes, times): + print(f" {size:6d} эл.: {time:8.4f} сек") + + if len(times) > 1: + growth = times[-1] / times[0] + print(f" Рост времени ({sizes[-1]}/{sizes[0]}): {growth:.2f}x") + +print("СРАВНЕНИЕ ЭФФЕКТИВНОСТИ:") +print(f"Обратный порядок сортируется в {results['reversed'][-1]/results['sorted'][-1]:.2f} раза медленнее упорядоченного") +print(f"Случайный массив сортируется в {results['random'][-1]/results['sorted'][-1]:.2f} раза медленнее упорядоченного") +``` + + +1. В чем состоит суть метода сортировки вставками? +Суть метода заключается в том, что массив делится на отсортированную и неотсортированную части. На каждом шаге берется очередной элемент из неотсортированной части и вставляется на правильную позицию в отсортированной части. + +2. Какие шаги выполняет алгоритм сортировки вставками? + +Начинаем со второго элемента (i = 1) + +Сохраняем текущий элемент в временную переменную + +Сдвигаем элементы отсортированной части, которые больше текущего элемента, вправо + +Вставляем текущий элемент на освободившееся место + +Повторяем для всех элементов массива + +3. Как программно реализуется сортировка вставками? + +```python +def insertion_sort(arr): + for i in range(1, len(arr)): + key = arr[i] + j = i - 1 + while j >= 0 and arr[j] > key: + arr[j + 1] = arr[j] + j -= 1 + arr[j + 1] = key + return arr +``` +4. В чем достоинства и недостатки метода сортировки вставками? +Достоинства: простая реализация, эффективен на небольших массивах, устойчив, адаптивен +Недостатки: O(n²) в худшем случае, неэффективен на больших массивах + +5. Приведите практический пример сортировки массива методом вставок +Массив: [5, 2, 4, 6, 1, 3] +Шаги: [2, 5, 4, 6, 1, 3] → [2, 4, 5, 6, 1, 3] → [2, 4, 5, 6, 1, 3] → [1, 2, 4, 5, 6, 3] → [1, 2, 3, 4, 5, 6] + +6. В чем состоит суть сортировки методом Шелла? +Суть в сравнении элементов, стоящих на определенном расстоянии друг от друга (с убывающим шагом), что позволяет быстрее перемещать элементы на большие расстояния. + +7. За счет чего метод Шелла дает лучшие показатели по сравнению с простейшими методами? +За счет предварительной сортировки элементов на больших расстояниях, что уменьшает количество инверсий и позволяет быстрее упорядочивать массив. + +8. Приведите практический пример сортировки массива методом Шелла +Массив: [8, 3, 7, 4, 1, 9, 2, 6, 5] +Шаг 4: [1, 3, 7, 4, 8, 9, 2, 6, 5] +Шаг 2: [1, 3, 2, 4, 5, 6, 7, 9, 8] +Шаг 1: [1, 2, 3, 4, 5, 6, 7, 8, 9] + +9. Какой фактор оказывает наибольшее влияние на эффективность сортировки методом Шелла? +Выбор последовательности шагов (интервалов) для сравнения элементов. + +10. Какие последовательности шагов группировки рекомендуются для практического использования в методе Шелла? +Последовательность Кнута: 1, 4, 13, 40, 121... или последовательность Седжвика: 1, 8, 23, 77, 281... + +11. Как программно реализуется сортировка методом Шелла? + +```python +def shell_sort(arr): + n = len(arr) + gap = n // 2 + while gap > 0: + for i in range(gap, n): + temp = arr[i] + j = i + while j >= gap and arr[j - gap] > temp: + arr[j] = arr[j - gap] + j -= gap + arr[j] = temp + gap //= 2 + return arr +``` +12. В чем состоит суть метода сортировки выбором? +На каждом шаге находится минимальный элемент из неотсортированной части и помещается в конец отсортированной части. + +13. Какие шаги выполняет алгоритм сортировки выбором? + +Находим минимальный элемент в неотсортированной части + +Меняем его местами с первым элементом неотсортированной части + +Увеличиваем границу отсортированной части на 1 + +Повторяем пока весь массив не будет отсортирован + +14. Как программно реализуется сортировка выбором? + +```python +def selection_sort(arr): + for i in range(len(arr)): + min_idx = i + for j in range(i + 1, len(arr)): + if arr[j] < arr[min_idx]: + min_idx = j + arr[i], arr[min_idx] = arr[min_idx], arr[i] + return arr +``` + +15. В чем достоинства и недостатки метода сортировки выбором? +Достоинства: простая реализация, минимальное количество перестановок +Недостатки: O(n²) в любом случае, неустойчив + + +16. Приведите практический пример сортировки массива методом выбора +Массив: [64, 25, 12, 22, 11] +Шаги: [11, 25, 12, 22, 64] → [11, 12, 25, 22, 64] → [11, 12, 22, 25, 64] → [11, 12, 22, 25, 64] + +17. В чем состоит суть метода сортировки обменом? +Сравниваются соседние элементы и меняются местами, если они находятся в неправильном порядке. + +18. Какие шаги выполняет алгоритм сортировки обменом? + +Проходим по массиву несколько раз + +На каждой итерации сравниваем соседние элементы + +Если порядок неправильный - меняем их местами + +Повторяем до тех пор, пока массив не будет отсортирован + +19. Как программно реализуется сортировка обменом? + +```python +def bubble_sort(arr): + n = len(arr) + for i in range(n): + for j in range(0, n - i - 1): + if arr[j] > arr[j + 1]: + arr[j], arr[j + 1] = arr[j + 1], arr[j] + return arr +``` +20. В чем достоинства и недостатки метода сортировки обменом? +Достоинства: простая реализация, устойчив +Недостатки: O(n²) в худшем случае, медленный на больших массивах + +21. Приведите практический пример сортировки массива методом обмена +Массив: [5, 1, 4, 2, 8] +1 проход: [1, 5, 4, 2, 8] → [1, 4, 5, 2, 8] → [1, 4, 2, 5, 8] → [1, 4, 2, 5, 8] +2 проход: [1, 4, 2, 5, 8] → [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] + +22. В чем состоит суть метода быстрой сортировки? +Выбирается опорный элемент, массив разбивается на две части: элементы меньше опорного и элементы больше опорного, затем рекурсивно сортируются обе части. + +23. За счет чего метод быстрой сортировки дает лучшие показатели по сравнению с простейшими методами? +За счет принципа "разделяй и властвуй" и в среднем случае времени O(n log n). + +24. Что такое опорный элемент в методе быстрой сортировки и как он используется? +Опорный элемент - это элемент, относительно которого происходит разделение массива. Элементы меньше опорного перемещаются влево, больше - вправо. + +25. Приведите практический пример быстрой сортировки массива +Массив: [10, 80, 30, 90, 40, 50, 70] +Опорный: 70 +Разделение: [10, 30, 40, 50] 70 [80, 90] +Рекурсивная сортировка обеих частей + +26. Что можно сказать о применимости метода быстрой сортировки с точки зрения его эффективности? +Эффективен для больших массивов, но может деградировать до O(n²) при неудачном выборе опорного элемента. + +27. Какой фактор оказывает решающее влияние на эффективность метода быстрой сортировки? +Выбор опорного элемента. + +28. Почему выбор серединного элемента в качестве опорного в методе быстрой сортировки может резко ухудшать эффективность метода? +Если массив уже отсортирован или почти отсортирован, выбор серединного элемента может привести к несбалансированному разделению. + +29. Какое правило выбора опорного элемента в методе быстрой сортировки является наилучшим и почему его сложно использовать? +Медиана трех элементов (первого, среднего и последнего). Сложно использовать из-за дополнительных сравнений. + +30. Какое простое правило выбора опорного элемента в методе быстрой сортировки рекомендуется использовать на практике? +Случайный выбор или медиана трех. + +31. Какие усовершенствования имеет базовый алгоритм метода быстрой сортировки? + +Выбор опорного элемента через медиану трех + +Использование insertion sort для маленьких подмассивов + +Итеративная реализация для избежания переполнения стека + +32. Почему быстрая сортировка проще всего программно реализуется с помощью рекурсии? +Рекурсия естественным образом отражает принцип "разделяй и властвуй". + +33. Как программно реализуется рекурсивный вариант метода быстрой сортировки? + +```python +def quick_sort(arr): + if len(arr) <= 1: + return arr + pivot = arr[len(arr) // 2] + left = [x for x in arr if x < pivot] + middle = [x for x in arr if x == pivot] + right = [x for x in arr if x > pivot] + return quick_sort(left) + middle + quick_sort(right) +``` + +34. Какие особенности имеет не рекурсивная программная реализация метода быстрой сортировки? +Использует стек для хранения границ подмассивов, требует ручного управления памятью. + +35. В чем состоит суть метода пирамидальной сортировки? +Строится двоичная куча из элементов массива, затем многократно извлекается максимальный элемент и перестраивается куча. + +36. Какой набор данных имеет пирамидальную организацию? +Двоичная куча - полное двоичное дерево, где каждый узел больше (max-heap) или меньше (min-heap) своих потомков. + +37. Чем отличаются друг от друга дерево поиска и пирамидальное дерево? +Дерево поиска: левый потомок < родитель < правый потомок +Пирамидальное дерево: родитель > потомков (max-heap) или родитель < потомков (min-heap) + +38. Приведите пример пирамидального дерева с целочисленными ключами + + +```text + 100 + / \ + 19 36 + / \ / + 17 3 25 +``` +39. Какие полезные свойства имеет пирамидальное дерево? + +Максимальный/минимальный элемент всегда в корне + +Эффективные операции вставки и извлечения за O(log n) + +40. Какие шаги выполняются при построении пирамидального дерева? + +Начинаем с последнего нелистового узла + +Просеиваем узел вниз до правильной позиции + +Переходим к предыдущему узлу + +Повторяем до корня + +41. Что такое просеивание элемента через пирамиду? +Процесс перемещения элемента вниз по дереву до тех пор, пока он не займет правильную позицию относительно своих потомков. + +42. Приведите практический пример построения пирамидального дерева +Массив: [4, 10, 3, 5, 1] +Построение: [10, 5, 3, 4, 1] + +43. Какие шаги выполняются на втором этапе пирамидальной сортировки? + +Меняем корень (максимальный элемент) с последним элементом + +Уменьшаем размер кучи на 1 + +Просеиваем новый корень + +Повторяем пока куча не пуста + +44. Приведите практический пример реализации второго этапа пирамидальной сортировки +Куча: [10, 5, 3, 4, 1] +Шаг 1: [1, 5, 3, 4, 10] → Просеиваем: [5, 4, 3, 1, 10] +Шаг 2: [1, 4, 3, 5, 10] → Просеиваем: [4, 1, 3, 5, 10] +и т.д. + +45. Что можно сказать о трудоемкости метода пирамидальной сортировки? +Время: O(n log n) в худшем, среднем и лучшем случае. Память: O(1). + diff --git a/labs/lab_02/var6/comb.jpeg b/labs/lab_02/var6/comb.jpeg new file mode 100644 index 0000000..5fc0147 Binary files /dev/null and b/labs/lab_02/var6/comb.jpeg differ diff --git a/labs/lab_02/var6/comb.png b/labs/lab_02/var6/comb.png new file mode 100644 index 0000000..f45c72a --- /dev/null +++ b/labs/lab_02/var6/comb.png @@ -0,0 +1,55 @@ +┌─────────────────────────────────────────────────────────────────┐ +│ НАЧАЛО СОРТИРОВКИ РАСЧЕСКОЙ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Инициализация: │ +│ - gap = длина(массив) │ +│ - swapped = true │ +│ - factor = 1.3 (коэффициент уменьшения) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌───────────────┐ + │ gap > 1 ИЛИ │ + │ swapped = true? │ + └───────────────┘ + │ + ┌─────────────┴─────────────┐ + ▼ ▼ + НЕТ ┌─────────────────┐ ДА ┌─────────────────┐ + │ КОНЕЦ │ │ swapped = false │ + └─────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ gap = max(1, gap/factor)│ + │ i = 0 │ + └─────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────┐ + │ i + gap < длина(массива)? │ + └─────────────────────────────────┘ + │ + ┌─────────────┴─────────────┐ + ▼ ▼ + НЕТ ┌─────────────────┐ ДА ┌─────────────────┐ + │ Возврат к │ │ array[i] > │ + │ проверке gap │ │ array[i+gap]? │ + └─────────────────┘ └─────────────────┘ + │ + ┌─────────────┴─────────────┐ + ▼ ▼ + НЕТ ┌─────────────────┐ ДА ┌─────────────────┐ + │ i = i + 1 │ │ Поменять местами│ + └─────────────────┘ │ array[i] и │ + │ array[i+gap] │ + │ swapped = true │ + └─────────────────┘ + │ + ▼ + ┌─────────────────────┐ + │ i = i + 1 │ + └─────────────────────┘ \ No newline at end of file diff --git a/labs/lab_02/var6/comb_n1.png b/labs/lab_02/var6/comb_n1.png new file mode 100644 index 0000000..c7e5528 Binary files /dev/null and b/labs/lab_02/var6/comb_n1.png differ diff --git a/labs/lab_02/var6/usage_time.py b/labs/lab_02/var6/usage_time.py new file mode 100644 index 0000000..077358b --- /dev/null +++ b/labs/lab_02/var6/usage_time.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Usage Time + +Project: TryPython +A collection of educational materials for learning the Python + +Author: Alexander Krasnikov aka askras + +License: BSD 3 clause +""" + +import functools +import timeit +import typing + + +def get_usage_time( + *, number: int = 1, setup: str = 'pass', ndigits: int = 3 +) -> typing.Callable: + """Decorator for measuring the speed of the function (in seconds) + + Parameters + ---------- + number : int, optional + Number of code repetitions. + setup : str, optional + Code executed once before timing. + ndigits : int, optionalgit status + Number of decimal places in the returned value. + + Returns + ------- + decorator: typing.Callable + Decorator for measuring the time of the function in seconds. + + See Also + -------- + timeit + Measure execution time of small code snippets. + + References + ---------- + [1] timeit documentation : https://docs.python.org/3/library/timeit.html + + Examples + -------- + Decorating an existing function: + + >>> import time + >>> def sleep_func(n): + ... time.sleep(n) + ... return n + ... + >>> get_usage_time_sleep_func = get_usage_time()(sleep_func) + >>> time_sleep_func = get_usage_time_sleep_func(2) + >>> print(f'The function was executed for {time_sleep_func} seconds') + The function was executed for 2.0 seconds + >>> get_usage_time(number=5)(sleep_func)(4) + 4.0 + + Measuring the running time of a function for different parameter values: + + >>> import time + >>> def sleep_func(n): + ... time.sleep(n) + ... return n + ... + >>> for n in range(1,4): + ... get_usage_time_sleep_func = get_usage_time(number=2)(sleep_func) + ... print(get_usage_time_sleep_func(n)) + 1.0 + 2.0 + 3.0 + + Using the `setup` option: + + >>> import time + >>> def sleep_func(n): + ... time.sleep(n) + ... return n + ... + >>> setup = 'print("Start setup"); time.sleep(10); print("End setup")' + >>> get_usage_time_sleep_func = get_usage_time(setup=setup)(sleep_func) + >>> print(get_usage_time_sleep_func(3)) + Start setup + End setup + 3.0 + + Decoding the generated function: + + >>> import time + >>> @get_usage_time(number=2, setup='print("Start");', ndigits=0) + ... def sleep_func(n): + ... time.sleep(n) + ... return n + ... + >>> time_sleep_func = sleep_func(3) + Start + >>> print(time_sleep_func) + 3.0 + """ + + def decorator(func: typing.Callable) -> typing.Callable: + @functools.wraps(func) + def wrapper(*args, **kwargs) -> float: + usage_time = timeit.timeit( + lambda: func(*args, **kwargs), + setup=setup, + number=number, + ) + return round(usage_time / number, ndigits) + + return wrapper + + return decorator + + +if __name__ == '__main__': + import time + + def sleep_func(n): + time.sleep(n) + return n + + for i in range(1, 4): + time_sleep_func = get_usage_time(number=3)(sleep_func) + print(time_sleep_func(i)) diff --git a/labs/lab_03/Untitled.md b/labs/lab_03/Untitled.md new file mode 100644 index 0000000..056b7c5 --- /dev/null +++ b/labs/lab_03/Untitled.md @@ -0,0 +1,646 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Лабораторная работа №3 +## Вариант 6 +Цели лабораторной работы: +- Изучить, что такое линейный список — структура данных, где элементы связаны ссылками, а не расположены подряд в памяти. +- Научиться создавать и использовать односвязные линейные списки в программе. +- Освоить основные операции со списками: добавление, удаление, поиск элементов. +- Развить навыки работы с динамической памятью при построении и изменении списков. +- Научиться писать функции для обработки списков (например, удаление повторов, разворот, сортировка). +- Приобрести умения структурного и модульного программирования через реализацию этих задач. + +## Задание 1 +Версия 1: + +```python +from typing import Any, Self +import doctest + +class Node: + def __init__(self, data: Any = None, next: 'Node' = None): + self.data = data + self.next = next + + def __repr__(self): + return f'Node(data={self.data}, next={self.next})' + + +class SingleLinkedListV1: + def __init__(self) -> Self: + self._head = None + + def insert_first_node(self, value: Any) -> None: + '''Добавить элемент в начало списка''' + self._head = Node(value, self._head) + + def remove_first_node(self) -> Any: + '''Удалить первый элемент списка''' + if self._head is None: + raise ValueError("Список пуст") + temp = self._head.data + self._head = self._head.next + return temp + + def insert_last_node(self, value: Any) -> None: + '''Добавить элемент в конец списка''' + if self._head is None: + self.insert_first_node(value) + else: + current_node = self._head + while current_node.next is not None: + current_node = current_node.next + current_node.next = Node(value) + + def remove_last_node(self) -> Any: + '''Удалить последний элемент списка''' + if self._head is None: + raise ValueError("Список пуст") + + if self._head.next is None: + return self.remove_first_node() + + current_node = self._head + while current_node.next.next is not None: + current_node = current_node.next + temp = current_node.next.data + current_node.next = None + return temp + + def __repr__(self) -> str: + return f'SingleLinkedListV1({self._head})' + + def __str__(self): + node = self._head + elements = [] + while node: + elements.append(str(node.data)) + node = node.next + return 'LinkedList.head -> ' + ' -> '.join(elements) + ' -> None' + +``` + +Версия 2: Добавлены методы поиска, замены и удаления по значению + получение размера + +```python +class SingleLinkedListV2(SingleLinkedListV1): + def get_size(self) -> int: + '''Вернуть размер списка (сложность O(n))''' + count = 0 + current = self._head + while current: + count += 1 + current = current.next + return count + + def find_node(self, value: Any) -> Node: + '''Найти первый узел с заданным значением''' + current = self._head + while current: + if current.data == value: + return current + current = current.next + return None + + def replace_node(self, old_value: Any, new_value: Any) -> bool: + '''Заменить значение первого найденного узла''' + node = self.find_node(old_value) + if node: + node.data = new_value + return True + return False + + def remove_node(self, value: Any) -> bool: + '''Удалить первый узел с заданным значением''' + if self._head is None: + return False + + if self._head.data == value: + self.remove_first_node() + return True + + current = self._head + while current.next: + if current.next.data == value: + current.next = current.next.next + return True + current = current.next + + return False +``` + +Версия 3: Добавлено хранение размера списка для оптимизации + +```python +class SingleLinkedListV3(SingleLinkedListV2): + def __init__(self) -> Self: + '''Инициализация с счетчиком размера''' + super().__init__() + self.size = 0 + + def insert_first_node(self, value: Any) -> None: + '''Добавление в начало с обновлением размера''' + super().insert_first_node(value) + self.size += 1 + + def insert_last_node(self, value: Any) -> None: + '''Добавление в конец с обновлением размера''' + super().insert_last_node(value) + self.size += 1 + + def remove_first_node(self) -> Any: + '''Удаление из начала с обновлением размера''' + result = super().remove_first_node() + self.size -= 1 + return result + + def remove_last_node(self) -> Any: + '''Удаление из конца с обновлением размера''' + result = super().remove_last_node() + self.size -= 1 + return result + + def remove_node(self, value: Any) -> bool: + '''Удаление узла по значению с обновлением размера''' + if super().remove_node(value): + self.size -= 1 + return True + return False + + def get_size(self) -> int: + '''Получение размера за O(1)''' + return self.size +``` + +Версия 4: Добавлены методы для работы с соседними узлами + +```python +class SingleLinkedListV4(SingleLinkedListV3): + def find_previous_node(self, value: Any) -> Any: + '''Найти значение предыдущего узла относительно узла с заданным значением''' + if self._head is None or self._head.data == value: + return None + + current = self._head + while current.next: + if current.next.data == value: + return current.data + current = current.next + return None + + def find_next_node(self, value: Any) -> Any: + '''Найти значение следующего узла относительно узла с заданным значением''' + node = self.find_node(value) + if node and node.next: + return node.next.data + return None + + def insert_before_node(self, target_value: Any, new_value: Any) -> bool: + '''Вставить новый узел перед узлом с заданным значением''' + if self._head is None: + return False + + if self._head.data == target_value: + self.insert_first_node(new_value) + return True + + current = self._head + while current.next: + if current.next.data == target_value: + new_node = Node(new_value, current.next) + current.next = new_node + self.size += 1 + return True + current = current.next + + return False + + def insert_after_node(self, target_value: Any, new_value: Any) -> bool: + '''Вставить новый узел после узла с заданным значением''' + node = self.find_node(target_value) + if node: + new_node = Node(new_value, node.next) + node.next = new_node + self.size += 1 + return True + return False + + def replace_previous_node(self, target_value: Any, new_value: Any) -> bool: + '''Заменить значение в предыдущем узле относительно узла с заданным значением''' + if self._head is None or self._head.data == target_value: + return False + + current = self._head + while current.next: + if current.next.data == target_value: + current.data = new_value + return True + current = current.next + return False + + def replace_next_node(self, target_value: Any, new_value: Any) -> bool: + '''Заменить значение в следующем узле относительно узла с заданным значением''' + node = self.find_node(target_value) + if node and node.next: + node.next.data = new_value + return True + return False + + def remove_previous_node(self, target_value: Any) -> Any: + '''Удалить предыдущий узел относительно узла с заданным значением''' + if self._head is None or self._head.data == target_value: + return None + + if self._head.next and self._head.next.data == target_value: + return self.remove_first_node() + + current = self._head + while current.next and current.next.next: + if current.next.next.data == target_value: + removed_data = current.next.data + current.next = current.next.next + self.size -= 1 + return removed_data + current = current.next + return None + + def remove_next_node(self, target_value: Any) -> Any: + '''Удалить следующий узел относительно узла с заданным значением''' + node = self.find_node(target_value) + if node and node.next: + removed_data = node.next.data + node.next = node.next.next + self.size -= 1 + return removed_data + return None +``` + +Версия 5: Добавлен указатель на хвост для оптимизации операций + +```python +class SingleLinkedListV5(SingleLinkedListV4): + def __init__(self) -> Self: + '''Инициализация с указателем на хвост''' + super().__init__() + self._tail = None + + def insert_first_node(self, value: Any) -> None: + '''Добавление в начало с обновлением хвоста''' + super().insert_first_node(value) + if self.size == 1: + self._tail = self._head + + def insert_last_node(self, value: Any) -> None: + '''Добавление в конец за O(1) с использованием хвоста''' + if self._head is None: + self.insert_first_node(value) + else: + new_node = Node(value) + self._tail.next = new_node + self._tail = new_node + self.size += 1 + + def remove_first_node(self) -> Any: + '''Удаление из начала с обновлением хвоста''' + result = super().remove_first_node() + if self.size == 0: + self._tail = None + return result + + def remove_last_node(self) -> Any: + '''Удаление из конца с обновлением хвоста''' + if self._head is None: + raise ValueError("Список пуст") + + if self._head.next is None: + return self.remove_first_node() + + current = self._head + while current.next != self._tail: + current = current.next + + result = self._tail.data + current.next = None + self._tail = current + self.size -= 1 + + return result + + def find_node(self, value: Any) -> Node: + '''Оптимизированный поиск: O(1) для головы и хвоста''' + if self._head is None: + return None + + if self._head.data == value: + return self._head + + if self._tail and self._tail.data == value: + return self._tail + + return super().find_node(value) + + def insert_after_node(self, target_value: Any, new_value: Any) -> bool: + '''Оптимизированная вставка после узла: O(1) для хвоста''' + if self._tail and self._tail.data == target_value: + new_node = Node(new_value) + self._tail.next = new_node + self._tail = new_node + self.size += 1 + return True + + return super().insert_after_node(target_value, new_value) + + def replace_node(self, old_value: Any, new_value: Any) -> bool: + '''Оптимизированная замена: O(1) для головы и хвоста''' + if self._head is None: + return False + + if self._head.data == old_value: + self._head.data = new_value + return True + + if self._tail and self._tail.data == old_value: + self._tail.data = new_value + return True + + return super().replace_node(old_value, new_value) + + def remove_node(self, value: Any) -> bool: + '''Оптимизированное удаление: O(1) для головы и хвоста''' + if self._head is None: + return False + + if self._head.data == value: + self.remove_first_node() + return True + + if self._tail and self._tail.data == value: + self.remove_last_node() + return True + + return super().remove_node(value) + + def replace_next_node(self, target_value: Any, new_value: Any) -> bool: + '''Оптимизированная замена следующего узла: O(1) проверка для хвоста''' + if self._tail and self._tail.data == target_value: + return False + + return super().replace_next_node(target_value, new_value) + + def remove_next_node(self, target_value: Any) -> Any: + '''Оптимизированное удаление следующего узла: O(1) проверка для хвоста''' + if self._tail and self._tail.data == target_value: + return None + + return super().remove_next_node(target_value) + + def find_next_node(self, value: Any) -> Any: + '''Оптимизированный поиск следующего узла: O(1) проверка для хвоста''' + if self._tail and self._tail.data == value: + return None + + return super().find_next_node(value) + + def get_tail(self) -> Any: + '''Получить значение хвоста за O(1)''' + return self._tail.data if self._tail else None + + def __str__(self): + '''Строковое представление с информацией о хвосте''' + base_str = super().__str__() + tail_info = f" (tail: {self.get_tail()})" if self._tail else " (tail: None)" + return base_str + tail_info +``` + +Версия 6: Оптимизация операций через замену значений + +```python +class SingleLinkedListV6(SingleLinkedListV5): + + def insert_before_node_optimized(self, target_node: Node, new_value: Any) -> bool: + '''Оптимизированная вставка перед узлом за O(1) через замену значений''' + if target_node is None: + return False + + new_node = Node(target_node.data, target_node.next) + target_node.data = new_value + target_node.next = new_node + + if self._tail == target_node: + self._tail = new_node + + self.size += 1 + return True + + def remove_node_optimized(self, target_node: Node) -> Any: + '''Оптимизированное удаление узла за O(1) через замену значений''' + if target_node is None or target_node.next is None: + return None + + removed_data = target_node.data + target_node.data = target_node.next.data + target_node.next = target_node.next.next + + if target_node.next is None: + self._tail = target_node + + self.size -= 1 + return removed_data + + def remove_previous_node_optimized(self, target_node: Node) -> Any: + '''Оптимизированное удаление предыдущего узла за O(1)''' + if (target_node is None or self._head is None or + self._head == target_node or self._head.next == target_node): + return None + + if self._head.next == target_node: + return self.remove_first_node() + + current = self._head + while current.next and current.next.next: + if current.next.next == target_node: + removed_data = current.next.data + current.next = target_node + self.size -= 1 + return removed_data + current = current.next + return None + + def insert_before_node(self, target_value: Any, new_value: Any) -> bool: + '''Переопределение для использования оптимизации при наличии узла''' + target_node = self.find_node(target_value) + if target_node: + return self.insert_before_node_optimized(target_node, new_value) + return False + + def remove_node(self, value: Any) -> bool: + '''Переопределение для использования оптимизации при наличии узла''' + if self._head and self._head.data == value: + self.remove_first_node() + return True + + if self._tail and self._tail.data == value: + self.remove_last_node() + return True + + target_node = self.find_node(value) + if target_node: + self.remove_node_optimized(target_node) + return True + + return False + + def remove_previous_node(self, target_value: Any) -> Any: + '''Переопределение для использования оптимизации при наличии узла''' + if self._head is None or self._head.data == target_value: + return None + + target_node = self.find_node(target_value) + if target_node: + return self.remove_previous_node_optimized(target_node) + return None + + def sort(self) -> None: + '''Сортировка списка (пузырьковая сортировка)''' + if self.size < 2: + return + + for i in range(self.size): + current = self._head + for j in range(self.size - i - 1): + if current.data > current.next.data: + current.data, current.next.data = current.next.data, current.data + current = current.next + + def get_middle_node(self) -> Node: + '''Найти средний узел за один проход (для демонстрации)''' + if self._head is None: + return None + + slow = self._head + fast = self._head + + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + return slow + + #Задание 2 + + def reverse(self) -> None: + if self._head is None or self._head.next is None: + return + + prev = None + current = self._head + self._tail = self._head + + while current: + next_node = current.next + current.next = prev + prev = current + current = next_node + + self._head = prev + + #Задание 3 (сортировка вставками) + + def sort_insertion(self) -> None: + if self.size < 2: + return + + sorted_head = None + + current = self._head + while current: + next_node = current.next + + if sorted_head is None or current.data < sorted_head.data: + current.next = sorted_head + sorted_head = current + else: + temp = sorted_head + while temp.next and temp.next.data < current.data: + temp = temp.next + current.next = temp.next + temp.next = current + + current = next_node + + self._head = sorted_head + + if self._head: + current = self._head + while current.next: + current = current.next + self._tail = current +``` + +## Задание 2 +Сложность: O(n), Память: O(1) + +## Задание 3 +1. Лучший случай (уже отсортированный список): +- Вставка всегда происходит в начало: O(1) на элемент +- Итого: O(n) +2. Худший случай (обратно отсортированный список): +- Каждый новый элемент вставляется в конец отсортированной части +- Для i-го элемента нужно пройти i узлов +- Сумма: 1 + 2 + 3 + ... + (n-1) = n(n-1)/2 +- Итого: O(n²) +3. Средний случай: +- В среднем для каждого элемента нужно пройти половину отсортированной части +- Итого: O(n²) + +## Задание 4 + +```python +def remove_consecutive_duplicates(linked_list: 'SingleLinkedListV6') -> None: + if linked_list._head is None or linked_list._head.next is None: + return + + current = linked_list._head + + while current and current.next: + if current.data == current.next.data: + current.next = current.next.next + linked_list.size -= 1 + + if current.next is None: + linked_list._tail = current + else: + # Переходим к следующему узлу + current = current.next +``` + +## Таблица из контрольных вопросов +| Операция | Односвязный список (без tail) | Односвязный список (с tail) | Двусвязный список (с tail) | +| :--- | :---: | :---: | :---: | +| Вставка в начало | O(1) | O(1) | O(1) | +| Вставка в конец | O(n) | O(1) | O(1) | +| Удаление из начала | O(1) | O(1) | O(1) | +| Удаление из конца | O(n) | O(n) | O(1) | +| Поиск элемента по значению | O(n) | O(n) | O(n) | +| Вставка после известного узла | O(1) | O(1) | O(1) | +| Удаление известного узла | O(n) | O(n) | O(1) | +| Доступ к элементу по индексу | O(n) | O(n) | O(n) | + +## Выводы +- Линейные списки — это важная структура данных, позволяющая динамически хранить и обрабатывать упорядоченные наборы элементов без необходимости contiguous памяти. +- В ходе лабораторной работы были изучены основные виды списков (односвязный, двусвязный, кольцевой), а также реализованы базовые операции над ними: добавление, удаление, обход. +- Выполнение заданий позволило закрепить навыки работы с динамической памятью и освоить приемы структурного программирования на языке C++. +- Реализация функций обработки списков (удаление повторов, реверс, сортировка) помогла понять особенности алгоритмов и их временную сложность при работе со связными структурами. +- Полученные знания и практические навыки пригодятся при работе с более сложными структурами данных и алгоритмами в программировании. diff --git a/labs/lab_04/Untitled.md b/labs/lab_04/Untitled.md new file mode 100644 index 0000000..fd12007 --- /dev/null +++ b/labs/lab_04/Untitled.md @@ -0,0 +1,577 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Лаборатрная работа №4 +Цели: +- Познакомиться со структурами данных стек, очередь и дек, понять, как они устроены и работают. +- Научиться реализовывать эти структуры на основе массивов и связных списков. +- Изучить основные операции для каждой структуры (добавление, удаление, просмотр элементов). +- На практике применить стек для решения задач: проверка правильности скобок и вычисление выражений в обратной польской записи. +- Освоить перевод математических выражений из обычной записи в постфиксную форму. +### Базовые классы: + +```python +class Stack: + """Базовый класс стека""" + def __init__(self): + self.items = [] + + def push(self, item): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def pop(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def peek(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def is_empty(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def size(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + +class Queue: + """Базовый класс очереди""" + def __init__(self): + self.items = [] + + def enqueue(self, item): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def dequeue(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def front(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def is_empty(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def size(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + +class Deque: + """Базовый класс дека с методами по ТЗ""" + def __init__(self): + self.items = [] + + def insert_left(self, value): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def insert_right(self, value): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def remove_left(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def remove_right(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def get_left(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def get_right(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def is_empty(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") + + def size(self): + raise NotImplementedError("Метод должен быть реализован в подклассе") +``` + +### Задание 1 +1. Реализовать стек на основе массива + +```python +class ArrayStack(Stack): + def __init__(self): + super().__init__() + self.items = [] + + def push(self, item): + self.items.append(item) + + def pop(self): + if self.is_empty(): + raise IndexError("Стек пуст") + return self.items.pop() + + def peek(self): + if self.is_empty(): + raise IndexError("Стек пуст") + return self.items[-1] + + def is_empty(self): + return len(self.items) == 0 + + def size(self): + return len(self.items) + + def __str__(self): + return f"ArrayStack({self.items})" +``` + +2. Реализовать стек на основе связного списка. + +```python +class Node: + """Узел связного списка""" + def __init__(self, data): + self.data = data + self.next = None + +class LinkedListStack(Stack): + def __init__(self): + super().__init__() + self.head = None + self._size = 0 + + def push(self, item): + new_node = Node(item) + new_node.next = self.head + self.head = new_node + self._size += 1 + + def pop(self): + if self.is_empty(): + raise IndexError("Стек пуст") + data = self.head.data + self.head = self.head.next + self._size -= 1 + return data + + def peek(self): + if self.is_empty(): + raise IndexError("Стек пуст") + return self.head.data + + def is_empty(self): + return self.head is None + + def size(self): + return self._size + + def __str__(self): + items = [] + current = self.head + while current: + items.append(current.data) + current = current.next + return f"LinkedListStack({items})" +``` + +3. Реализовать очередь на основе массива. + +```python +class ArrayQueue(Queue): + def __init__(self): + super().__init__() + self.items = [] + + def enqueue(self, item): + self.items.append(item) + + def dequeue(self): + if self.is_empty(): + raise IndexError("Очередь пуста") + return self.items.pop(0) + + def front(self): + if self.is_empty(): + raise IndexError("Очередь пуста") + return self.items[0] + + def is_empty(self): + return len(self.items) == 0 + + def size(self): + return len(self.items) + + def __str__(self): + return f"ArrayQueue({self.items})" +``` + +4. Реализовать очередь на основе связного списка. + +```python +class LinkedListQueue(Queue): + def __init__(self): + super().__init__() + self.head = None + self.tail = None + self._size = 0 + + def enqueue(self, item): + new_node = Node(item) + if self.is_empty(): + self.head = self.tail = new_node + else: + self.tail.next = new_node + self.tail = new_node + self._size += 1 + + def dequeue(self): + if self.is_empty(): + raise IndexError("Очередь пуста") + data = self.head.data + self.head = self.head.next + if self.head is None: + self.tail = None + self._size -= 1 + return data + + def front(self): + if self.is_empty(): + raise IndexError("Очередь пуста") + return self.head.data + + def is_empty(self): + return self.head is None + + def size(self): + return self._size + + def __str__(self): + items = [] + current = self.head + while current: + items.append(current.data) + current = current.next + return f"LinkedListQueue({items})" +``` + +5. Реализовать дек на основе массива. + +```python +class ArrayDeque(Deque): + """Дек на основе массива""" + def __init__(self): + super().__init__() + self.items = [] + + def insert_left(self, value): + """Помещает объект на левый конец дека""" + self.items.insert(0, value) + + def insert_right(self, value): + """Помещает объект на правый конец дека""" + self.items.append(value) + + def remove_left(self): + """Удаляет первый объект с левого конца дека и возвращает его""" + if self.is_empty(): + raise IndexError("Дек пуст") + return self.items.pop(0) + + def remove_right(self): + """Удаляет первый объект с правого конца дека и возвращает его""" + if self.is_empty(): + raise IndexError("Дек пуст") + return self.items.pop() + + def get_left(self): + """Возвращает первый объект с левого конца дека не удаляя его""" + if self.is_empty(): + raise IndexError("Дек пуст") + return self.items[0] + + def get_right(self): + """Возвращает первый объект с правого конца дека не удаляя его""" + if self.is_empty(): + raise IndexError("Дек пуст") + return self.items[-1] + + def is_empty(self): + return len(self.items) == 0 + + def size(self): + return len(self.items) + + def __str__(self): + return f"ArrayDeque({self.items})" +``` + +6. Реализовать дек на основе связного списка. + +```python +class LinkedListDeque(Deque): + """Дек на основе связного списка с методами по ТЗ""" + def __init__(self): + super().__init__() + self.head = None + self.tail = None + self._size = 0 + + def insert_left(self, value): + """Помещает объект на левый конец дека""" + new_node = DoublyNode(value) + if self.is_empty(): + self.head = self.tail = new_node + else: + new_node.next = self.head + self.head.prev = new_node + self.head = new_node + self._size += 1 + + def insert_right(self, value): + """Помещает объект на правый конец дека""" + new_node = DoublyNode(value) + if self.is_empty(): + self.head = self.tail = new_node + else: + new_node.prev = self.tail + self.tail.next = new_node + self.tail = new_node + self._size += 1 + + def remove_left(self): + """Удаляет первый объект с левого конца дека и возвращает его""" + if self.is_empty(): + raise IndexError("Дек пуст") + data = self.head.data + if self.head == self.tail: + self.head = self.tail = None + else: + self.head = self.head.next + self.head.prev = None + self._size -= 1 + return data + + def remove_right(self): + """Удаляет первый объект с правого конца дека и возвращает его""" + if self.is_empty(): + raise IndexError("Дек пуст") + data = self.tail.data + if self.head == self.tail: + self.head = self.tail = None + else: + self.tail = self.tail.prev + self.tail.next = None + self._size -= 1 + return data + + def get_left(self): + """Возвращает первый объект с левого конца дека не удаляя его""" + if self.is_empty(): + raise IndexError("Дек пуст") + return self.head.data + + def get_right(self): + """Возвращает первый объект с правого конца дека не удаляя его""" + if self.is_empty(): + raise IndexError("Дек пуст") + return self.tail.data + + def is_empty(self): + return self.head is None + + def size(self): + return self._size + + def __str__(self): + items = [] + current = self.head + while current: + items.append(current.data) + current = current.next + return f"LinkedListDeque({items})" +``` + +### Задание 2 +Используя операции со стеком, написать программу, проверяющую своевременность закрытия скобок «(, ), [, ] ,{, }» в строке символов (строка состоит из одних скобок этих типов). + +В процессе решения анализируются символы строки. Если встречена одна из открывающихся скобок, то она записывается в стек. При обнаружении закрывающейся скобки, соответствующей скобке, находящейся в вершине стека, последняя удаляется. При несоответствии скобки выдается сообщение об ошибке, которое фиксируется в логической переменной. + +```python +def check_brackets(expression): + """ + Проверяет корректность расстановки скобок в выражении + Возвращает True если скобки расставлены правильно, False в противном случае + """ + stack = ArrayStack() + brackets = {'(': ')', '[': ']', '{': '}'} + + for char in expression: + if char in brackets.keys(): + stack.push(char) + elif char in brackets.values(): + if stack.is_empty(): + return False + opening_bracket = stack.pop() + if brackets[opening_bracket] != char: + return False + + return stack.is_empty() + +def test_bracket_checker(): + test_cases = [ + ("()", True), + ("()[]{}", True), + ("(]", False), + ("([)]", False), + ("{[]}", True), + ("((()))", True), + ("((())", False), + ("", True) + ] + + print("Тестирование проверки скобочной последовательности:") + print("=" * 55) + print(f"{'Выражение':<15} {'Результат':<10} {'Ожидалось':<10} {'Статус':<6}") + print("-" * 55) + + for expr, expected in test_cases: + result = check_brackets(expr) + status = "✓" if result == expected else "✗" + print(f"{expr:<15} {str(result):<10} {str(expected):<10} {status:<6}") + + print("=" * 55) + +test_bracket_checker() +``` + +### Задание 3 + +Написать программу вычисления значения выражения, представленного в обратной польской записи (в постфиксной записи). Выражение состоит из цифр от 1 до 9 и знаков операции. + +| Обычная (инфиксная) запись | Обратная польская (постфиксная) запись | +|:---|:---| +| (a+b) * c | a b + c * | +| a + (b+c)*d | a b c + d * + | + +Просматривая строку, анализируем очередной символ, если это: + - цифра, то записываем ее в стек; + - знак, то читаем два элемента из стека, выполняем математическую операцию, определяемую этим знаком, и заносим результат в стек. + +После просмотра всей строки в стеке должен оставаться один элемент, он и является решением задачи. + +```python +def evaluate_rpn(expression): + """ + Вычисляет значение выражения в обратной польской записи + """ + stack = ArrayStack() + operators = { + '+': lambda x, y: x + y, + '-': lambda x, y: x - y, + '*': lambda x, y: x * y, + '/': lambda x, y: x / y + } + + for token in expression.split(): + if token.isdigit() or (token[0] == '-' and token[1:].isdigit()): + stack.push(float(token)) + elif token in operators: + if stack.size() < 2: + raise ValueError("Недостаточно операндов для операции") + y = stack.pop() + x = stack.pop() + result = operators[token](x, y) + stack.push(result) + else: + raise ValueError(f"Неизвестный токен: {token}") + + if stack.size() != 1: + raise ValueError("Некорректное выражение") + + return stack.pop() + +def test_rpn_evaluator(): + test_cases = [ + ("3 4 +", 7), + ("5 2 * 3 +", 13), + ("10 5 / 2 *", 4), + ("4 2 + 3 * 6 -", 12), + ("1 2 + 4 * 3 +", 15) + ] + + print("Тестирование вычисления RPN выражений:") + print("-" * 50) + for expr, expected in test_cases: + result = evaluate_rpn(expr) + status = "Правильно" if abs(result - expected) < 1e-9 else "Не правиильно" + print(f"{status} '{expr}' = {result} (ожидалось: {expected})") + print() + +test_rpn_evaluator() +``` + +### Задание 4 +Реализовать перевод математических выражений из инфиксной в постфиксную форму записи. + +```python +def infix_to_postfix(expression): + """ + Переводит инфиксное выражение в постфиксную запись (обратную польскую) + """ + precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3} + + output = [] + stack = ArrayStack() + + for token in expression.split(): + if token.isdigit() or (token[0] == '-' and token[1:].isdigit()): + output.append(token) + elif token == '(': + stack.push(token) + elif token == ')': + while not stack.is_empty() and stack.peek() != '(': + output.append(stack.pop()) + stack.pop() + elif token in precedence: + while (not stack.is_empty() and + stack.peek() != '(' and + precedence.get(stack.peek(), 0) >= precedence[token]): + output.append(stack.pop()) + stack.push(token) + + while not stack.is_empty(): + output.append(stack.pop()) + + return ' '.join(output) + +def test_infix_to_postfix(): + test_cases = [ + ("3 + 4", "3 4 +"), + ("3 + 4 * 2", "3 4 2 * +"), + ("( 3 + 4 ) * 2", "3 4 + 2 *"), + ("3 + 4 * 2 / ( 1 - 5 )", "3 4 2 * 1 5 - / +"), + ("a + b * c", "a b c * +") + ] + + print("Тестирование перевода из инфиксной в постфиксную запись:") + print("-" * 50) + for infix, expected_postfix in test_cases: + result = infix_to_postfix(infix) + status = "Правиьно" if result == expected_postfix else "Не правильно" + print(f"{status} '{infix}' -> '{result}' (ожидалось: '{expected_postfix}')") + print() + +test_infix_to_postfix() +``` + +### Контрольные вопросы + +| Операция | Стек (список) | Очередь (список) | Дек (двусвязный список) | Дек (массив) | +| :--- | :---: | :---: | :---: | :---: | +| `push` / `enqueue` / `pushBack` | **O(1)** | **O(1)** | **O(1)** | **O(1)*** | +| `pop` / `dequeue` / `popBack` | **O(1)** | **O(1)** | **O(1)** | **O(1)*** | +| `pushFront` | — | — | **O(1)** | **O(n)** | +| `popFront` | — | — | **O(1)** | **O(n)** | +| `peek` / `front` / `back` | **O(1)** | **O(1)** | **O(1)** | **O(1)** | +| `isEmpty` | **O(1)** | **O(1)** | **O(1)** | **O(1)** | + + diff --git a/labs/lab_05/Untitled.md b/labs/lab_05/Untitled.md new file mode 100644 index 0000000..c563e73 --- /dev/null +++ b/labs/lab_05/Untitled.md @@ -0,0 +1,437 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Хеш-функции и хеш-таблицы + +## Задание 1: + +```python +class HashTableChaining: + def __init__(self, size=10): + self.size = size + self.table = [[] for _ in range(size)] + self.count = 0 + + def _hash(self, key): + return hash(key) % self.size + + def insert(self, key, value): + index = self._hash(key) + bucket = self.table[index] + + for i, (k, v) in enumerate(bucket): + if k == key: + bucket[i] = (key, value) + return + + bucket.append((key, value)) + self.count += 1 + + if self.count / self.size > 0.7: + self._rehash() + + def search(self, key): + index = self._hash(key) + bucket = self.table[index] + + for k, v in bucket: + if k == key: + return v + return None + + def delete(self, key): + index = self._hash(key) + bucket = self.table[index] + + for i, (k, v) in enumerate(bucket): + if k == key: + del bucket[i] + self.count -= 1 + return True + return False + + def _rehash(self): + old_table = self.table + self.size *= 2 + self.table = [[] for _ in range(self.size)] + self.count = 0 + + for bucket in old_table: + for key, value in bucket: + self.insert(key, value) + + def __str__(self): + result = [] + for i, bucket in enumerate(self.table): + if bucket: + result.append(f"{i}: {bucket}") + return "\n".join(result) +``` + +Тест: + +```python +print("=== Тестирование HashTableChaining ===") + +ht = HashTableChaining(5) + +ht.insert("apple", 1) +ht.insert("banana", 2) +ht.insert("cherry", 3) + +assert ht.search("apple") == 1, "Ошибка поиска apple" +assert ht.search("banana") == 2, "Ошибка поиска banana" +assert ht.search("cherry") == 3, "Ошибка поиска cherry" +assert ht.search("unknown") is None, "Ошибка поиска неизвестного ключа" +print("✓ Тест вставки и поиска пройден") + +ht.insert("apple", 10) +assert ht.search("apple") == 10, "Ошибка обновления значения" +print("✓ Тест обновления значения пройден") + +assert ht.delete("banana") == True, "Ошибка удаления существующего ключа" +assert ht.search("banana") is None, "Ошибка: ключ не удален" +assert ht.delete("unknown") == False, "Ошибка удаления несуществующего ключа" +print("✓ Тест удаления пройден") + +ht2 = HashTableChaining(1) +ht2.insert("key1", "value1") +ht2.insert("key2", "value2") +ht2.insert("key3", "value3") + +assert ht2.search("key1") == "value1", "Ошибка при коллизии" +assert ht2.search("key2") == "value2", "Ошибка при коллизии" +assert ht2.search("key3") == "value3", "Ошибка при коллизии" +print("✓ Тест коллизий пройден") + +print("Все тесты HashTableChaining пройдены успешно!\n") +``` + +## Задание 2: + +```python +class HashTableOpenAddressing: + def __init__(self, size=10): + self.size = size + self.table = [None] * size + self.count = 0 + self.DELETED = object() + + def _hash(self, key, i=0): + return (hash(key) + i) % self.size + + def insert(self, key, value): + if self.count / self.size > 0.7: + self._rehash() + + i = 0 + while i < self.size: + index = self._hash(key, i) + + if self.table[index] is None or self.table[index] is self.DELETED: + self.table[index] = (key, value) + self.count += 1 + return + elif self.table[index][0] == key: + self.table[index] = (key, value) + return + + i += 1 + + self._rehash() + self.insert(key, value) + + def search(self, key): + i = 0 + while i < self.size: + index = self._hash(key, i) + + if self.table[index] is None: + return None + elif self.table[index] is not self.DELETED and self.table[index][0] == key: + return self.table[index][1] + + i += 1 + + return None + + def delete(self, key): + i = 0 + while i < self.size: + index = self._hash(key, i) + + if self.table[index] is None: + return False + elif self.table[index] is not self.DELETED and self.table[index][0] == key: + self.table[index] = self.DELETED + self.count -= 1 + return True + + i += 1 + + return False + + def _rehash(self): + old_table = self.table + self.size *= 2 + self.table = [None] * self.size + self.count = 0 + + for item in old_table: + if item is not None and item is not self.DELETED: + self.insert(item[0], item[1]) + + def __str__(self): + result = [] + for i, item in enumerate(self.table): + if item is not None and item is not self.DELETED: + result.append(f"{i}: {item}") + elif item is self.DELETED: + result.append(f"{i}: DELETED") + return "\n".join(result) +``` + +Тесты + +```python +print("=== Тестирование HashTableOpenAddressing ===") + +ht = HashTableOpenAddressing(5) + +ht.insert("apple", 1) +ht.insert("banana", 2) +ht.insert("cherry", 3) + +assert ht.search("apple") == 1, "Ошибка поиска apple" +assert ht.search("banana") == 2, "Ошибка поиска banana" +assert ht.search("cherry") == 3, "Ошибка поиска cherry" +assert ht.search("unknown") is None, "Ошибка поиска неизвестного ключа" +print("✓ Тест вставки и поиска пройден") + +ht.insert("apple", 10) +assert ht.search("apple") == 10, "Ошибка обновления значения" +print("✓ Тест обновления значения пройден") + +assert ht.delete("banana") == True, "Ошибка удаления существующего ключа" +assert ht.search("banana") is None, "Ошибка: ключ не удален" +assert ht.delete("unknown") == False, "Ошибка удаления несуществующего ключа" +print("✓ Тест удаления пройден") + +ht2 = HashTableOpenAddressing(3) +ht2.insert("key1", "value1") +ht2.insert("key2", "value2") +ht2.insert("key3", "value3") + +assert ht2.search("key1") == "value1", "Ошибка при коллизии" +assert ht2.search("key2") == "value2", "Ошибка при коллизии" +assert ht2.search("key3") == "value3", "Ошибка при коллизии" +print("✓ Тест коллизий пройден") + +print("Все тесты HashTableOpenAddressing пройдены успешно!\n") +``` + +## Задание 4 + +```python +def array_intersection_custom(arr1, arr2): + """Проверка пересечения массивов через нашу хеш-таблицу""" + ht = HashTableChaining(len(arr1)) + + for item in arr1: + ht.insert(item, True) + + for item in arr2: + if ht.search(item) is not None: + return True + + return False + +print("=== Пересечение через HashTableChaining ===") +arr1 = [1, 2, 3, 4, 5] +arr2 = [4, 5, 6, 7, 8] +result = array_intersection_custom(arr1, arr2) +print(f"Массивы {arr1} и {arr2} пересекаются: {result}") +``` + +## Здание 5 + +```python +def has_unique_elements(arr): + """Проверка уникальности""" + ht = HashTableOpenAddressing(len(arr)) + + for item in arr: + if ht.search(item) is not None: + return False + ht.insert(item, True) + + return True + + +print("\n=== Уникальность через HashTableOpenAddressing ===") +arr = [1, 2, 3, 2, 4, 1] +arr2 = [1, 3, 4, 6] +print(f"Массив {arr} уникален: {has_unique_elements(arr)}") +print(f"Массив {arr2} уникален: {has_unique_elements(arr2)}") +``` + +## Задание 6 + +```python +def find_pairs_with_sum(arr, target_sum): + """Поиск пар с заданной суммой""" + ht = HashTableChaining(len(arr)) + pairs = [] + + for num in arr: + complement = target_sum - num + + if ht.search(complement) is not None: + pairs.append((complement, num)) + + ht.insert(num, True) + + return pairs + +print("\n=== Пары с суммой через HashTableChaining ===") +arr = [1, 4, 2, 3, 5, 6, 7] +target = 7 +pairs = find_pairs_with_sum(arr, target) +print(f"Пары с суммой {target} в {arr}: {pairs}") +``` + +## Задание 7 + +```python +def are_anagrams_custom(str1, str2): + """Проверка анаграмм через нашу хеш-таблицу""" + if len(str1) != len(str2): + return False + + ht = HashTableOpenAddressing(26) + + for char in str1: + current_count = ht.search(char) + if current_count is None: + ht.insert(char, 1) + else: + ht.insert(char, current_count + 1) + + for char in str2: + current_count = ht.search(char) + if current_count is None or current_count == 0: + return False + ht.insert(char, current_count - 1) + + return True + +print("\n=== Анаграммы через HashTableOpenAddressing ===") +str1 = "listen" +str2 = "silent" +print(f"'{str1}' и '{str2}' - анаграммы: {are_anagrams_custom(str1, str2)}") +``` + +## Задание 2 + +```python +import hashlib +import time +import json + +class Block: + def __init__(self, data, previous_hash=""): + self.timestamp = time.time() + self.data = data + self.previous_hash = previous_hash + self.hash = self.calculate_hash() + + def calculate_hash(self): + """Вычисляет хеш блока на основе его данных""" + import hashlib + data_string = f"{self.timestamp}{self.data}{self.previous_hash}" + return hashlib.sha256(data_string.encode()).hexdigest() + + def __str__(self): + return f"Данные: {self.data}\nХеш: {self.hash[:20]}...\nПредыдущий: {self.previous_hash[:20]}...\n" + +class BlockchainWithCustomHashTable: + def __init__(self): + self.chain = HashTableChaining(10) + self.current_hash = None + self.create_genesis_block() + + def create_genesis_block(self): + """Создает генезис-блок используя нашу хеш-таблицу""" + genesis_data = "Genesis Block" + genesis_hash = self._calculate_hash(genesis_data, "0") + self.chain.insert(genesis_hash, Block(genesis_data, "0")) + self.current_hash = genesis_hash + print("✓ Создан генезис-блок (в нашей хеш-таблице)") + + def _calculate_hash(self, data, previous_hash): + """Вычисляет хеш для блока""" + import hashlib + data_string = f"{data}{previous_hash}" + return hashlib.sha256(data_string.encode()).hexdigest() + + def add_block(self, data): + """Добавляет блок используя нашу хеш-таблицу""" + previous_hash = self.current_hash + new_hash = self._calculate_hash(data, previous_hash) + + new_block = Block(data, previous_hash) + self.chain.insert(new_hash, new_block) + self.current_hash = new_hash + + print(f"✓ Добавлен блок: {data}") + return new_hash + + def is_chain_valid(self): + """Проверяет целостность через нашу хеш-таблицу""" + current = self.current_hash + + while current: + block = self.chain.search(current) + if not block: + return False + + previous_hash = block.previous_hash + + if previous_hash == "0": + return True + + if block.calculate_hash() != current: + return False + + current = previous_hash + + return False + +def test_custom_blockchain(): + print("\n" + "="*60) + print("БЛОКЧЕЙН С НАШЕЙ HASHTableChaining") + print("="*60) + + blockchain = BlockchainWithCustomHashTable() + blockchain.add_block("Транзакция 1: Alice -> Bob") + blockchain.add_block("Транзакция 2: Bob -> Carol") + + print(f"✓ Цепочка валидна: {blockchain.is_chain_valid()}") + +test_custom_blockchain() +``` + +```python + +``` diff --git a/labs/lab_06/Untitled.md b/labs/lab_06/Untitled.md new file mode 100644 index 0000000..7ef31e3 --- /dev/null +++ b/labs/lab_06/Untitled.md @@ -0,0 +1,381 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Лаба 6 + +## ЗАДАНИЕ 1: Рекурсивный алгоритм + +```python +import math +import sys +from functools import wraps +import time + +def factorial(n): + if n == 0 or n == 1: + return 1 + result = 1 + for i in range(2, n + 1): + result *= i + return result + +def recursive_series_sum(x, n): + if n == 0: + return 0 + if n == 1: + return x + + previous_sum = recursive_series_sum(x, n - 1) + + current_term = (x ** (2 * n - 1)) / factorial(n) + + return previous_sum + current_term + +if __name__ == "__main__": + print("ЗАДАНИЕ 1: Рекурсивный алгоритм") + print("=" * 40) + + test_cases = [(1, 3), (2, 4), (0.5, 5)] + + for x, n in test_cases: + result = recursive_series_sum(x, n) + print(f"x={x}, n={n}: сумма = {result:.6f}") +``` + +![]() + + +## ЗАДАНИЕ 2: Итеративный алгоритм (без рекурсии) + +```python +def iterative_series_sum(x, n): + if n == 0: + return 0 + + total_sum = 0 + current_factorial = 1 + + for i in range(1, n + 1): + if i > 1: + current_factorial *= i + else: + current_factorial = 1 + + exponent = 2 * i - 1 + current_term = (x ** exponent) / current_factorial + total_sum += current_term + + print(f" Член {i}: x^{exponent}/{i}! = {current_term:.6f}") + + return total_sum + +if __name__ == "__main__": + x = 2 + n = 4 + print(f"Вычисление ряда для x={x}, n={n}:") + result = iterative_series_sum(x, n) + print(f"Итоговая сумма: {result:.6f}") +``` + +## ЗАДАНИЕ 3: Анализ и сравнение алгоритмов +### Часть 3.1: Класс для сбора статистики и декораторы + +```python +class SeriesStats: + def __init__(self): + self.iterations = 0 + self.max_stack_depth = 0 + self.execution_time = 0 + self.intermediate_results = [] + + def reset(self): + self.iterations = 0 + self.max_stack_depth = 0 + self.execution_time = 0 + self.intermediate_results = [] + +stats = SeriesStats() + +def save_intermediate_results(func): + @wraps(func) + def wrapper(x, n, depth=0): + stats.iterations += 1 + stats.max_stack_depth = max(stats.max_stack_depth, depth) + + intermediate = { + 'depth': depth, + 'x': x, + 'n': n, + 'timestamp': time.time() + } + + result = func(x, n, depth) + + intermediate['result'] = result + stats.intermediate_results.append(intermediate) + + return result + return wrapper +``` + +### Часть 3.2: Модернизированные реализации с сохранением результатов + +```python +@save_intermediate_results +def recursive_series_sum_with_stats(x, n, depth=0): + if n == 0: + return 0 + if n == 1: + return x + + previous_sum = recursive_series_sum_with_stats(x, n - 1, depth + 1) + current_term = (x ** (2 * n - 1)) / factorial(n) + + return previous_sum + current_term + +def manual_recursive_with_storage(x, n): + intermediate_results = [] + stack_depth = 0 + + def recursive_helper(x_val, n_val, depth): + nonlocal stack_depth + stack_depth = max(stack_depth, depth) + stats.iterations += 1 + + intermediate_results.append({ + 'depth': depth, + 'x': x_val, + 'n': n_val, + 'action': 'before_call' + }) + + if n_val == 0: + result = 0 + intermediate_results.append({ + 'depth': depth, + 'result': result, + 'action': 'base_case_0' + }) + return result + + if n_val == 1: + result = x_val + intermediate_results.append({ + 'depth': depth, + 'result': result, + 'action': 'base_case_1' + }) + return result + + recursive_result = recursive_helper(x_val, n_val - 1, depth + 1) + current_term = (x_val ** (2 * n_val - 1)) / factorial(n_val) + final_result = recursive_result + current_term + + intermediate_results.append({ + 'depth': depth, + 'current_term': current_term, + 'recursive_result': recursive_result, + 'final_result': final_result, + 'action': 'after_call' + }) + + return final_result + + result = recursive_helper(x, n, 0) + stats.max_stack_depth = stack_depth + stats.intermediate_results = intermediate_results + return result + +def iterative_series_sum_with_stats(x, n): + stats.reset() + start_time = time.time() + + if n == 0: + stats.execution_time = time.time() - start_time + return 0 + + total_sum = 0 + current_factorial = 1 + + for i in range(1, n + 1): + stats.iterations += 1 + + if i > 1: + current_factorial *= i + else: + current_factorial = 1 + + exponent = 2 * i - 1 + current_term = (x ** exponent) / current_factorial + total_sum += current_term + + stats.intermediate_results.append({ + 'iteration': i, + 'term': current_term, + 'sum_so_far': total_sum, + 'factorial': current_factorial + }) + + stats.execution_time = time.time() - start_time + return total_sum +``` + +### Часть 3.3: Анализ ограничений рекурсии + +```python +def analyze_stack_limit(): + original_limit = sys.getrecursionlimit() + print(f"Текущий лимит рекурсии: {original_limit}") + + max_safe_n = 0 + x_test = 2 + + for i in range(1, original_limit - 100): + try: + recursive_series_sum_with_stats(x_test, i) + max_safe_n = i + except RecursionError: + break + + return max_safe_n, original_limit +``` + +### Часть 3.4: Сравнение производительности + +```python +def performance_comparison(test_cases): + results = [] + + for x, n in test_cases: + print(f"\nТестирование: x={x}, n={n}") + print("-" * 40) + + stats.reset() + start_time = time.time() + result1 = recursive_series_sum_with_stats(x, n) + recursive_time = time.time() - start_time + + print(f"Рекурсивный (с декоратором): {result1:.6f}") + print(f" Время: {recursive_time:.6f}с, Итерации: {stats.iterations}, Глубина: {stats.max_stack_depth}") + + stats.reset() + start_time = time.time() + result2 = manual_recursive_with_storage(x, n) + manual_recursive_time = time.time() - start_time + + print(f"Рекурсивный (ручное сохранение): {result2:.6f}") + print(f" Время: {manual_recursive_time:.6f}с, Итерации: {stats.iterations}, Глубина: {stats.max_stack_depth}") + + stats.reset() + result3 = iterative_series_sum_with_stats(x, n) + iterative_time = stats.execution_time + + print(f"Итеративный: {result3:.6f}") + print(f" Время: {iterative_time:.6f}с, Итерации: {stats.iterations}") + + tolerance = 1e-10 + results_match = (abs(result1 - result2) < tolerance and + abs(result1 - result3) < tolerance) + + if results_match: + print("✓ Все алгоритмы дали одинаковый результат") + else: + print("✗ Ошибка: результаты различаются") + + results.append({ + 'x': x, + 'n': n, + 'recursive_time': recursive_time, + 'manual_recursive_time': manual_recursive_time, + 'iterative_time': iterative_time, + 'recursive_iterations': stats.iterations, + 'stack_depth': stats.max_stack_depth, + 'results_match': results_match + }) + + return results +``` + +### Часть 3.5: Визуализация и вывод результатов + +```python +def print_intermediate_results(): + print("\nПромежуточные результаты (последние 5 записей):") + for i, result in enumerate(stats.intermediate_results[-5:]): + print(f" {i+1}: {result}") + +def demonstrate_series_calculation(): + x = 2 + n = 4 + + print("Члены ряда:") + total = 0 + for i in range(1, n + 1): + exponent = 2 * i - 1 + fact = factorial(i) + term = (x ** exponent) / fact + total += term + print(f" {i}. x^{exponent}/{i}! = {x}^{exponent}/{fact} = {term:.6f}") + + print(f"\nСумма {n} членов: {total:.6f}") + +if __name__ == "__main__": + print("ЗАДАНИЕ 3: Анализ и сравнение алгоритмов") + print("=" * 50) + + demonstrate_series_calculation() + + print("\n1. АНАЛИЗ ОГРАНИЧЕНИЙ РЕКУРСИИ") + max_safe_n, recursion_limit = analyze_stack_limit() + print(f"Максимальное безопасное n: {max_safe_n}") + + test_cases = [ + (1, 5), + (2, 4), + (1, 10), + (2, 8), + (0.5, 6), + ] + + print("\n2. СРАВНЕНИЕ ПРОИЗВОДИТЕЛЬНОСТИ") + results = performance_comparison(test_cases) + + print("\n3. СВОДНАЯ СТАТИСТИКА") + print("Алгоритм | Среднее время | Макс. итераций | Макс. глубина") + print("-" * 75) + + recursive_times = [r['recursive_time'] for r in results] + manual_times = [r['manual_recursive_time'] for r in results] + iterative_times = [r['iterative_time'] for r in results] + + avg_recursive = sum(recursive_times) / len(recursive_times) + avg_manual = sum(manual_times) / len(manual_times) + avg_iterative = sum(iterative_times) / len(iterative_times) + + max_iterations = max(r['recursive_iterations'] for r in results) + max_depth = max(r['stack_depth'] for r in results) + + print(f"Рекурсивный | {avg_recursive:.6f}с | {max_iterations:12} | {max_depth:11}") + print(f"Ручной рекурсивный | {avg_manual:.6f}с | {max_iterations:12} | {max_depth:11}") + print(f"Итеративный | {avg_iterative:.6f}с | {max_iterations:12} | {'N/A':11}") + + print("\n4. ДЕМОНСТРАЦИЯ ПРОМЕЖУТОЧНЫХ РЕЗУЛЬТАТОВ") + stats.reset() + recursive_series_sum_with_stats(2, 4) + print_intermediate_results() +``` + +```python + +``` diff --git a/labs/lab_07/Untitled.md b/labs/lab_07/Untitled.md new file mode 100644 index 0000000..c729b2b --- /dev/null +++ b/labs/lab_07/Untitled.md @@ -0,0 +1,561 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Алгоритмы на графах + +### Задание 1 + +```python +from collections import deque +import heapq + +class Graph: + def __init__(self, directed=False): + self.vertices = {} + self.directed = directed + + def add_vertex(self, vertex): + if vertex not in self.vertices: + self.vertices[vertex] = {} + + def add_edge(self, vertex1, vertex2, weight=1): + self.add_vertex(vertex1) + self.add_vertex(vertex2) + + self.vertices[vertex1][vertex2] = weight + if not self.directed: + self.vertices[vertex2][vertex1] = weight + + def remove_edge(self, vertex1, vertex2): + if vertex1 in self.vertices and vertex2 in self.vertices[vertex1]: + del self.vertices[vertex1][vertex2] + if not self.directed and vertex2 in self.vertices and vertex1 in self.vertices[vertex2]: + del self.vertices[vertex2][vertex1] + + def remove_vertex(self, vertex): + if vertex in self.vertices: + for neighbor in list(self.vertices[vertex].keys()): + self.remove_edge(vertex, neighbor) + del self.vertices[vertex] + + def get_vertices(self): + return list(self.vertices.keys()) + + def get_edges(self): + edges = [] + for vertex in self.vertices: + for neighbor, weight in self.vertices[vertex].items(): + if self.directed: + edges.append((vertex, neighbor, weight)) + else: + if vertex < neighbor: + edges.append((vertex, neighbor, weight)) + return edges + + def bfs(self, start_vertex): + """Поиск в ширину""" + if start_vertex not in self.vertices: + return [] + + visited = set() + queue = deque([start_vertex]) + result = [] + + while queue: + vertex = queue.popleft() + if vertex not in visited: + visited.add(vertex) + result.append(vertex) + for neighbor in self.vertices[vertex]: + if neighbor not in visited: + queue.append(neighbor) + + return result + + def dfs(self, start_vertex): + """Поиск в глубину (итеративный)""" + if start_vertex not in self.vertices: + return [] + + visited = set() + stack = [start_vertex] + result = [] + + while stack: + vertex = stack.pop() + if vertex not in visited: + visited.add(vertex) + result.append(vertex) + for neighbor in reversed(list(self.vertices[vertex].keys())): + if neighbor not in visited: + stack.append(neighbor) + + return result + + def dijkstra(self, start_vertex, end_vertex): + """Алгоритм Дейкстры для поиска кратчайшего пути""" + if start_vertex not in self.vertices or end_vertex not in self.vertices: + return [], float('inf') + + distances = {vertex: float('inf') for vertex in self.vertices} + previous = {vertex: None for vertex in self.vertices} + distances[start_vertex] = 0 + + pq = [(0, start_vertex)] + + while pq: + current_distance, current_vertex = heapq.heappop(pq) + + if current_distance > distances[current_vertex]: + continue + + if current_vertex == end_vertex: + break + + for neighbor, weight in self.vertices[current_vertex].items(): + distance = current_distance + weight + if distance < distances[neighbor]: + distances[neighbor] = distance + previous[neighbor] = current_vertex + heapq.heappush(pq, (distance, neighbor)) + + path = [] + current = end_vertex + while current is not None: + path.append(current) + current = previous[current] + + path.reverse() + + if path[0] != start_vertex: + return [], float('inf') + + return path, distances[end_vertex] + + def has_eulerian_cycle(self): + """Проверка наличия эйлерова цикла""" + if self.directed: + for vertex in self.vertices: + in_degree = sum(1 for v in self.vertices if vertex in self.vertices[v]) + out_degree = len(self.vertices[vertex]) + if in_degree != out_degree: + return False + return True + else: + for vertex in self.vertices: + if len(self.vertices[vertex]) % 2 != 0: + return False + return True + + def find_eulerian_cycle(self): + """Поиск эйлерова цикла (алгоритм Флёри)""" + if not self.has_eulerian_cycle(): + return [] + + graph_copy = {v: dict(neighbors) for v, neighbors in self.vertices.items()} + + start_vertex = next(iter(self.vertices.keys())) + stack = [start_vertex] + cycle = [] + + while stack: + current_vertex = stack[-1] + + if graph_copy[current_vertex]: + next_vertex = next(iter(graph_copy[current_vertex].keys())) + stack.append(next_vertex) + del graph_copy[current_vertex][next_vertex] + if not self.directed: + del graph_copy[next_vertex][current_vertex] + else: + cycle.append(stack.pop()) + + return cycle[::-1] + + def hamiltonian_cycle_util(self, path, pos): + """Вспомогательная функция для поиска гамильтонова цикла""" + if pos == len(self.vertices): + last_vertex = path[pos - 1] + first_vertex = path[0] + if first_vertex in self.vertices[last_vertex]: + return True + else: + return False + + for v in self.vertices: + if v not in path: + if pos == 0 or path[pos - 1] in self.vertices and v in self.vertices[path[pos - 1]]: + path[pos] = v + if self.hamiltonian_cycle_util(path, pos + 1): + return True + path[pos] = -1 + + return False + + def find_hamiltonian_cycle(self): + """Поиск гамильтонова цикла""" + if len(self.vertices) == 0: + return [] + + path = [-1] * len(self.vertices) + path[0] = next(iter(self.vertices.keys())) + + if not self.hamiltonian_cycle_util(path, 1): + return [] + + return path + + def __str__(self): + result = "Граф:\n" + for vertex in self.vertices: + result += f"{vertex}: {self.vertices[vertex]}\n" + return result +``` + +### Задание 2 + +```python +def main(): + """Диалоговое приложение для работы с графом""" + graph = Graph(directed=False) + + while True: + print("\n=== МЕНЮ РАБОТЫ С ГРАФОМ ===") + print("1. Добавить вершину") + print("2. Добавить ребро") + print("3. Удалить вершину") + print("4. Удалить ребро") + print("5. Показать граф") + print("6. Обход в ширину (BFS)") + print("7. Обход в глубину (DFS)") + print("8. Алгоритм Дейкстры") + print("9. Поиск эйлерова цикла") + print("10. Поиск гамильтонова цикла") + print("0. Выход") + + choice = input("\nВыберите действие: ").strip() + + actions = { + '1': lambda: add_vertex_handler(graph), + '2': lambda: add_edge_handler(graph), + '3': lambda: remove_vertex_handler(graph), + '4': lambda: remove_edge_handler(graph), + '5': lambda: show_graph_handler(graph), + '6': lambda: bfs_handler(graph), + '7': lambda: dfs_handler(graph), + '8': lambda: dijkstra_handler(graph), + '9': lambda: eulerian_cycle_handler(graph), + '10': lambda: hamiltonian_cycle_handler(graph), + '0': lambda: exit_handler() + } + + if choice in actions: + actions[choice]() + else: + print("Неверный выбор. Попробуйте снова.") + + +def add_vertex_handler(graph): + vertex = input("Введите название вершины: ").strip() + graph.add_vertex(vertex) + print(f"Вершина '{vertex}' добавлена") + + +def add_edge_handler(graph): + v1 = input("Введите первую вершину: ").strip() + v2 = input("Введите вторую вершину: ").strip() + try: + weight = float(input("Введите вес ребра (по умолчанию 1): ").strip() or 1) + except ValueError: + weight = 1 + graph.add_edge(v1, v2, weight) + print(f"Ребро '{v1}' - '{v2}' с весом {weight} добавлено") + + +def remove_vertex_handler(graph): + vertex = input("Введите вершину для удаления: ").strip() + graph.remove_vertex(vertex) + print(f"Вершина '{vertex}' удалена") + + +def remove_edge_handler(graph): + v1 = input("Введите первую вершину: ").strip() + v2 = input("Введите вторую вершину: ").strip() + graph.remove_edge(v1, v2) + print(f"Ребро '{v1}' - '{v2}' удалено") + + +def show_graph_handler(graph): + print("\nТекущий граф:") + print(graph) + print("Вершины:", graph.get_vertices()) + print("Рёбра:", graph.get_edges()) + + +def bfs_handler(graph): + start = input("Введите начальную вершину для BFS: ").strip() + result = graph.bfs(start) + print(f"BFS обход: {result}") + + +def dfs_handler(graph): + start = input("Введите начальную вершину для DFS: ").strip() + result = graph.dfs(start) + print(f"DFS обход: {result}") + + +def dijkstra_handler(graph): + start = input("Введите начальную вершину: ").strip() + end = input("Введите конечную вершину: ").strip() + path, distance = graph.dijkstra(start, end) + if path: + print(f"Кратчайший путь: {' -> '.join(path)}") + print(f"Длина пути: {distance}") + else: + print("Путь не найден") + + +def eulerian_cycle_handler(graph): + if graph.has_eulerian_cycle(): + cycle = graph.find_eulerian_cycle() + print(f"Эйлеров цикл: {' -> '.join(cycle)}") + else: + print("Эйлеров цикл не существует") + + +def hamiltonian_cycle_handler(graph): + cycle = graph.find_hamiltonian_cycle() + if cycle: + print(f"Гамильтонов цикл: {' -> '.join(cycle)}") + else: + print("Гамильтонов цикл не найден") + + +def exit_handler(): + print("Выход из программы") + exit() + + +if __name__ == "__main__": + main() +``` + +### Задание 3 +### Задание 4 + +```python +def task_4(): + g = Graph(directed=True) + + edges = [ + ('1', '3', 13), ('1', '2', 2), ('1', '5', 17), ('1', '4', 25), + ('2', '4', 3), ('2', '5', 6), + ('3', '4', 1), ('3', '6', 7), + ('4', '6', 4), ('4', '5', 1), ('4', '7', 35), + ('5', '4', 1), ('5', '7', 20), + ('6', '7', 5) + ] + + for v1, v2, w in edges: + g.add_edge(v1, v2, w) + + print("Граф для задания 4:") + print(g) + + start, end = '1', '6' + path, distance = g.dijkstra(start, end) + + print(f"\nКратчайший путь от {start} до {end}:") + if path: + print(f"Путь: {' -> '.join(path)}") + print(f"Длина пути: {distance}") + + print("\nВсе рёбра графа:") + for edge in g.get_edges(): + print(f"{edge[0]} -> {edge[1]} (вес: {edge[2]})") + else: + print("Путь не найден") + + return path, distance + +print("=== ЗАДАНИЕ 4 ===") +path, distance = task_4() +``` + +### Задание 5 + +```python +def task_5(): + g = Graph(directed=True) + + edges = [ + ('1', '2', 14), ('1', '6', 8), + ('2', '3', 5), ('2', '6', 2), ('2', '4', 10), ('2', '5', 2), ('2', '8', 9), + ('3', '2', 5), ('3', '8', 11), + ('4', '5', 3), ('4', '6', 6), ('4', '7', 5), + ('5', '7', 8), ('5', '8', 1), + ('6', '7', 5), + ('7', '8', 7) + ] + + for v1, v2, w in edges: + g.add_edge(v1, v2, w) + + print("Граф для задания 5:") + print(g) + + start, end = '3', '8' + path, distance = g.dijkstra(start, end) + + print(f"\nКратчайший путь от {start} до {end}:") + if path: + print(f"Путь: {' -> '.join(path)}") + print(f"Длина пути: {distance}") + else: + print("Путь не найден") + + return path, distance + +print("=== ЗАДАНИЕ 5 ===") +path, distance = task_5() +``` + +### Задание 6 + +```python +import heapq + +def dijkstra_algorithm(graph, start, end): + distances = {vertex: float('inf') for vertex in graph.vertices} + previous = {vertex: None for vertex in graph.vertices} + distances[start] = 0 + + priority_queue = [(0, start)] + + print("АЛГОРИТМ ДЕЙКСТРЫ - ШАГИ:") + + step = 1 + while priority_queue: + current_distance, current_vertex = heapq.heappop(priority_queue) + + if current_distance > distances[current_vertex]: + continue + + print(f"\nШаг {step}: Обрабатываем вершину '{current_vertex}'") + print(f" Текущее расстояние: {current_distance}") + + if current_vertex == end: + print(f" Достигнута конечная вершина '{end}'") + break + + for neighbor, weight in graph.vertices[current_vertex].items(): + distance = current_distance + weight + + print(f" Проверяем {current_vertex} -> {neighbor} (вес: {weight})") + print(f" Новое расстояние до {neighbor}: {distance}") + + if distance < distances[neighbor]: + old_dist = distances[neighbor] + distances[neighbor] = distance + previous[neighbor] = current_vertex + heapq.heappush(priority_queue, (distance, neighbor)) + print(f" Обновлено: {neighbor}: {old_dist} -> {distance}") + else: + print(f" Не обновляем (текущее: {distances[neighbor]})") + + step += 1 + + path = [] + current = end + while current is not None: + path.append(current) + current = previous[current] + + path.reverse() + + if path[0] != start: + return [], float('inf') + + return path, distances[end] + +def detailed_dijkstra_demo(): + g = Graph(directed=True) + + edges = [ + ('1', '2', 14), ('1', '6', 8), + ('2', '3', 5), ('2', '6', 2), ('2', '4', 10), ('2', '5', 2), ('2', '8', 9), + ('3', '2', 5), ('3', '8', 11), + ('4', '5', 3), ('4', '6', 6), ('4', '7', 5), + ('5', '7', 8), ('5', '8', 1), + ('6', '7', 5), + ('7', '8', 7) + ] + + for v1, v2, w in edges: + g.add_edge(v1, v2, w) + + print("ДЕТАЛЬНАЯ РЕАЛИЗАЦИЯ АЛГОРИТМА ДЕЙКСТРЫ") + print("=" * 40) + + print("\nСтруктура графа:") + for vertex in sorted(g.vertices.keys()): + neighbors = g.vertices[vertex] + if neighbors: + connections = ", ".join([f"{n}({w})" for n, w in neighbors.items()]) + print(f" {vertex} -> [{connections}]") + + start, end = '3', '8' + print(f"\nЦель: Найти кратчайший путь от '{start}' до '{end}'") + + path, distance = dijkstra_algorithm(g, start, end) + + print("\n" + "=" * 40) + print("РЕЗУЛЬТАТЫ:") + print("=" * 40) + + if path: + print(f"Кратчайший путь найден!") + print(f"Путь: {' -> '.join(path)}") + print(f"Длина: {distance}") + + print(f"\nПодробный расчет:") + total = 0 + for i in range(len(path) - 1): + segment_distance = g.vertices[path[i]][path[i+1]] + total += segment_distance + print(f" {path[i]} -> {path[i+1]} = {segment_distance}") + print(f" Общая длина = {total}") + else: + print("Путь не найден") + + print(f"\nСравнение с другими путями:") + possible_paths = [ + (['3', '8'], 11), + (['3', '2', '8'], 5 + 9), + (['3', '2', '5', '8'], 5 + 2 + 1) + ] + + for p, dist in possible_paths: + status = "ЛУЧШИЙ" if p == path else "" + print(f" {' -> '.join(p):15} = {dist} {status}") + + return path, distance + +print("=== ЗАДАНИЕ 6 ===") +path, distance = detailed_dijkstra_demo() +``` + +```python + +``` diff --git a/labs/lab_08/Untitled.md b/labs/lab_08/Untitled.md new file mode 100644 index 0000000..fd82ea9 --- /dev/null +++ b/labs/lab_08/Untitled.md @@ -0,0 +1,249 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Вараинт 6 +### Задание 1, 3 + +```python +class TreeNode: + def __init__(self, value): + self.value = value + self.left = None + self.right = None + self.height = 1 + +class BinarySearchTree: + def __init__(self): + self.root = None + + def insert(self, value): + self.root = self._insert(self.root, value) + + def _insert(self, node, value): + if node is None: + return TreeNode(value) + if value < node.value: + node.left = self._insert(node.left, value) + elif value > node.value: + node.right = self._insert(node.right, value) + return node + + def search(self, value): + return self._search(self.root, value) + + def _search(self, node, value): + if node is None or node.value == value: + return node is not None + if value < node.value: + return self._search(node.left, value) + return self._search(node.right, value) + + def delete(self, value): + self.root = self._delete(self.root, value) + + def _delete(self, node, value): + if node is None: + return None + if value < node.value: + node.left = self._delete(node.left, value) + elif value > node.value: + node.right = self._delete(node.right, value) + else: + if node.left is None: + return node.right + elif node.right is None: + return node.left + temp = self._min_value_node(node.right) + node.value = temp.value + node.right = self._delete(node.right, temp.value) + return node + + def _min_value_node(self, node): + current = node + while current.left is not None: + current = current.left + return current + + def pre_order(self): + result = [] + self._pre_order(self.root, result) + return result + + def _pre_order(self, node, result): + if node: + result.append(node.value) + self._pre_order(node.left, result) + self._pre_order(node.right, result) + + def in_order(self): + result = [] + self._in_order(self.root, result) + return result + + def _in_order(self, node, result): + if node: + self._in_order(node.left, result) + result.append(node.value) + self._in_order(node.right, result) + + def post_order(self): + result = [] + self._post_order(self.root, result) + return result + + def _post_order(self, node, result): + if node: + self._post_order(node.left, result) + self._post_order(node.right, result) + result.append(node.value) + + def level_order(self): + if not self.root: + return [] + result = [] + queue = [self.root] + while queue: + node = queue.pop(0) + result.append(node.value) + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + return result + + def height(self): + return self._height(self.root) + + def _height(self, node): + if node is None: + return 0 + return 1 + max(self._height(node.left), self._height(node.right)) + + def is_empty(self): + return self.root is None + + def print_tree(self): + self._print_tree(self.root, 0) + + def _print_tree(self, node, level): + if node: + self._print_tree(node.right, level + 1) + print(" " * level + str(node.value)) + self._print_tree(node.left, level + 1) + + def has_duplicates(self): + elements = self.in_order() + return len(elements) != len(set(elements)) + +def demo_bst(): + bst = BinarySearchTree() + values = [50, 30, 70, 20, 40, 60, 80] + + for val in values: + bst.insert(val) + + print("BST структура:") + bst.print_tree() + + print("Pre-order:", bst.pre_order()) + print("In-order:", bst.in_order()) + print("Post-order:", bst.post_order()) + print("Level-order:", bst.level_order()) + + print("Высота:", bst.height()) + print("Пустое:", bst.is_empty()) + print("Поиск 40:", bst.search(40)) + print("Поиск 100:", bst.search(100)) + print("Дубликаты:", bst.has_duplicates()) + +demo_bst() +``` + +### Задание 2 + +```python +class AVLTree(BinarySearchTree): + def _insert(self, node, value): + if node is None: + return TreeNode(value) + if value < node.value: + node.left = self._insert(node.left, value) + elif value > node.value: + node.right = self._insert(node.right, value) + else: + return node + + node.height = 1 + max(self._get_height(node.left), self._get_height(node.right)) + balance = self._get_balance(node) + + if balance > 1 and value < node.left.value: + return self._right_rotate(node) + if balance < -1 and value > node.right.value: + return self._left_rotate(node) + if balance > 1 and value > node.left.value: + node.left = self._left_rotate(node.left) + return self._right_rotate(node) + if balance < -1 and value < node.right.value: + node.right = self._right_rotate(node.right) + return self._left_rotate(node) + return node + + def _left_rotate(self, z): + y = z.right + T2 = y.left + y.left = z + z.right = T2 + z.height = 1 + max(self._get_height(z.left), self._get_height(z.right)) + y.height = 1 + max(self._get_height(y.left), self._get_height(y.right)) + return y + + def _right_rotate(self, z): + y = z.left + T3 = y.right + y.right = z + z.left = T3 + z.height = 1 + max(self._get_height(z.left), self._get_height(z.right)) + y.height = 1 + max(self._get_height(y.left), self._get_height(y.right)) + return y + + def _get_height(self, node): + if node is None: + return 0 + return node.height + + def _get_balance(self, node): + if node is None: + return 0 + return self._get_height(node.left) - self._get_height(node.right) + +def demo_avl(): + avl = AVLTree() + values = [10, 20, 30, 40, 50, 25] + + for val in values: + avl.insert(val) + + print("AVL структура:") + avl.print_tree() + + print("In-order:", avl.in_order()) + print("Высота:", avl.height()) + print("Дубликаты:", avl.has_duplicates()) + +demo_avl() +``` + +```python + +``` diff --git a/labs/lab_09/Untitled.md b/labs/lab_09/Untitled.md new file mode 100644 index 0000000..722f76f --- /dev/null +++ b/labs/lab_09/Untitled.md @@ -0,0 +1,243 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +### Криптоалгоритмы +## Шифр Виженера + +```python +import random +import math +from typing import Tuple, List + +class VigenereCipher: + + def __init__(self): + self.alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + self.alphabet_lower = 'abcdefghijklmnopqrstuvwxyz' + + def prepare_key(self, text: str, key: str) -> str: + key = key.upper() + prepared_key = "" + key_index = 0 + + for char in text: + if char.isalpha(): + prepared_key += key[key_index % len(key)] + key_index += 1 + else: + prepared_key += char + + return prepared_key + + def encrypt(self, plaintext: str, key: str) -> str: + prepared_key = self.prepare_key(plaintext, key) + ciphertext = "" + + for i, char in enumerate(plaintext): + if char.isupper(): + shift = self.alphabet.index(prepared_key[i]) + original_pos = self.alphabet.index(char) + new_pos = (original_pos + shift) % 26 + ciphertext += self.alphabet[new_pos] + elif char.islower(): + shift = self.alphabet.index(prepared_key[i].upper()) + original_pos = self.alphabet_lower.index(char) + new_pos = (original_pos + shift) % 26 + ciphertext += self.alphabet_lower[new_pos] + else: + ciphertext += char + + return ciphertext + + def decrypt(self, ciphertext: str, key: str) -> str: + prepared_key = self.prepare_key(ciphertext, key) + plaintext = "" + + for i, char in enumerate(ciphertext): + if char.isupper(): + shift = self.alphabet.index(prepared_key[i]) + original_pos = self.alphabet.index(char) + new_pos = (original_pos - shift) % 26 + plaintext += self.alphabet[new_pos] + elif char.islower(): + shift = self.alphabet.index(prepared_key[i].upper()) + original_pos = self.alphabet_lower.index(char) + new_pos = (original_pos - shift) % 26 + plaintext += self.alphabet_lower[new_pos] + else: + plaintext += char + + return plaintext +``` + +### Криптосистема Пэйе + +```python +class PaillierCryptosystem: + + def __init__(self, key_length: int = 64): + self.key_length = key_length + + def gcd(self, a: int, b: int) -> int: + while b: + a, b = b, a % b + return a + + def lcm(self, a: int, b: int) -> int: + return abs(a * b) // self.gcd(a, b) + + def mod_inverse(self, a: int, m: int) -> int: + def extended_gcd(a, b): + if a == 0: + return b, 0, 1 + gcd, x1, y1 = extended_gcd(b % a, a) + x = y1 - (b // a) * x1 + y = x1 + return gcd, x, y + + gcd, x, _ = extended_gcd(a % m, m) + if gcd != 1: + raise ValueError("Inverse doesn't exist") + return (x % m + m) % m + + def is_prime(self, n: int, k: int = 5) -> bool: + if n < 2: + return False + if n in (2, 3): + return True + if n % 2 == 0: + return False + + s, d = 0, n - 1 + while d % 2 == 0: + s += 1 + d //= 2 + + for _ in range(k): + a = random.randint(2, n - 2) + x = pow(a, d, n) + if x in (1, n - 1): + continue + for _ in range(s - 1): + x = pow(x, 2, n) + if x == n - 1: + break + else: + return False + return True + + def generate_prime(self) -> int: + while True: + p = random.getrandbits(self.key_length // 2) + p |= (1 << (self.key_length // 2 - 1)) | 1 + if self.is_prime(p): + return p + + def generate_keys(self) -> Tuple[Tuple[int, int], Tuple[int, int]]: + p = self.generate_prime() + q = self.generate_prime() + + while p == q: + q = self.generate_prime() + + n = p * q + lambda_val = self.lcm(p - 1, q - 1) + + g = n + 1 + + n_sq = n * n + L_val = (pow(g, lambda_val, n_sq) - 1) // n + mu = self.mod_inverse(L_val, n) + + public_key = (n, g) + private_key = (lambda_val, mu) + + return public_key, private_key + + def L(self, x: int, n: int) -> int: + return (x - 1) // n + + def encrypt(self, m: int, public_key: Tuple[int, int]) -> int: + n, g = public_key + n_sq = n * n + + while True: + r = random.randint(1, n - 1) + if self.gcd(r, n) == 1: + break + + c = (pow(g, m, n_sq) * pow(r, n, n_sq)) % n_sq + return c + + def decrypt(self, c: int, public_key: Tuple[int, int], private_key: Tuple[int, int]) -> int: + n, g = public_key + lambda_val, mu = private_key + n_sq = n * n + + m = (self.L(pow(c, lambda_val, n_sq), n) * mu) % n + return m + + def text_to_numbers(self, text: str) -> List[int]: + return [ord(char) for char in text] + + def numbers_to_text(self, numbers: List[int]) -> str: + return ''.join(chr(num) for num in numbers) + + + +``` + +Генерация ключей: +Выбираем два больших простых числа p и q + +Вычисляем n = p * q (публичный модуль) + +Вычисляем λ = НОК(p-1, q-1) (секретный параметр) + +Выбираем g = n + 1 (публичный параметр) + +Публичный ключ: (n, g) +Приватный ключ: (λ, μ) + +Шифрование: +Для шифрования числа m: + +Выбираем случайное r (1 < r < n) + +Вычисляем: c = (gᵐ * rⁿ) mod n² + +Важно: Одно и то же m с разными r дает разные c! + +Дешифрование: +m = L(c^λ mod n²) * μ mod n + +```python +p = 541, q = 619 +n = p*q = 334879 +g = n+1 = 334880 +λ = НОК(540, 618) = 55620 +μ = вычисляется... + + +Выбираем r=123456 +c = (334880⁴² * 123456³³⁴⁸⁷⁹) mod n² +c = 83274619823... (очень большое число) + + +m = специальная_формула(c) = 42 +``` + +```python + +``` diff --git a/labs/lab_10/Untitled.md b/labs/lab_10/Untitled.md new file mode 100644 index 0000000..5ab28a7 --- /dev/null +++ b/labs/lab_10/Untitled.md @@ -0,0 +1,178 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.17.3 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +## Динамическое программирование +### Задача о рюкзаке с возможностью дробить предметы (Fractional Knapsack) + +```python +def fractional_knapsack(items, capacity): + items.sort(key=lambda x: x.price / x.weight, reverse=True) + total_value = 0.0 + for item in items: + if capacity >= item.weight: + total_value += item.price + capacity -= item.weight + else: + total_value += item.price * (capacity / item.weight) + break + return total_value + +class Item: + def __init__(self, price, weight): + self.price = price + self.weight = weight + +items = [Item(60, 15), Item(90, 30), Item(100, 50)] +capacity = 80 +print(f"Максимальная стоимость (дробный рюкзак): {fractional_knapsack(items, capacity)}") +``` + +### Задача о покрытии отрезками (Interval Covering Problem) + +```python +def interval_covering(segments, target_start, target_end): + segments.sort(key=lambda x: x[1]) + result = [] + current_pos = target_start + i = 0 + while current_pos < target_end: + best_end = current_pos + while i < len(segments) and segments[i][0] <= current_pos: + best_end = max(best_end, segments[i][1]) + i += 1 + if best_end == current_pos: + return [] + result.append((current_pos, best_end)) + current_pos = best_end + return result + +segments = [(1, 4), (2, 5), (3, 6), (5, 7), (6, 8)] +target = (1, 8) +print(f"Минимальное покрытие: {interval_covering(segments, *target)}") +``` + +### Задача о нахождении наименьшего пути (Floyd–Warshall) + +```python +def floyd_warshall(graph): + n = len(graph) + dist = [[float('inf')] * n for _ in range(n)] + for i in range(n): + dist[i][i] = 0 + for j, w in graph[i]: + dist[i][j] = w + + for k in range(n): + for i in range(n): + for j in range(n): + if dist[i][k] + dist[k][j] < dist[i][j]: + dist[i][j] = dist[i][k] + dist[k][j] + return dist + +graph = [ + [(1, 3), (2, 5)], + [(2, 1)], + [] +] +print("Матрица кратчайших путей:") +for row in floyd_warshall(graph): + print(row) +``` + +### Задача о размене монет (Coin Change, DP-версия) + +```python +def coin_change(coins, amount): + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + for coin in coins: + for i in range(coin, amount + 1): + dp[i] = min(dp[i], dp[i - coin] + 1) + return dp[amount] if dp[amount] != float('inf') else -1 + +coins = [1, 5, 11] +amount = 15 +print(f"Минимальное количество монет для суммы {amount}: {coin_change(coins, amount)}") +``` + +### Генерация разбиений множества (комбинаторный алгоритм) + +```python +def set_partitions(elements): + result = [] + + def backtrack(idx, current_partition): + if idx == len(elements): + result.append([list(subset) for subset in current_partition]) + return + for i in range(len(current_partition)): + current_partition[i].append(elements[idx]) + backtrack(idx + 1, current_partition) + current_partition[i].pop() + current_partition.append([elements[idx]]) + backtrack(idx + 1, current_partition) + current_partition.pop() + + backtrack(0, []) + return result + +print("Разбиения множества [1, 2, 3]:") +for part in set_partitions([1, 2, 3]): + print(part) +``` + +### Оптимизация многомерной функции (генетический алгоритм) + +```python +import random + +def fitness_function(individual): + x, y = individual + return -x**2 - y**2 + 10 + +def initialize_population(size, bounds=(-10, 10)): + return [(random.uniform(*bounds), random.uniform(*bounds)) for _ in range(size)] + +def tournament_selection(population, tournament_size=5): + tournament = random.sample(population, tournament_size) + return max(tournament, key=fitness_function) + +def crossover(p1, p2): + return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2) + +def mutate(individual, mutation_rate=0.1): + if random.random() < mutation_rate: + return (individual[0] + random.gauss(0, 0.5), individual[1] + random.gauss(0, 0.5)) + return individual + +def genetic_algorithm(generations=100, pop_size=50): + population = initialize_population(pop_size) + for _ in range(generations): + new_pop = [] + for _ in range(pop_size): + p1 = tournament_selection(population) + p2 = tournament_selection(population) + child = crossover(p1, p2) + child = mutate(child) + new_pop.append(child) + population = new_pop + return max(population, key=fitness_function) + +best = genetic_algorithm() +print(f"Лучшее решение: {best}, значение функции: {fitness_function(best)}") +``` + +```python + +``` diff --git a/quizes/quiz.md b/quizes/quiz.md index adf75d4..f06e16d 100644 --- a/quizes/quiz.md +++ b/quizes/quiz.md @@ -27,7 +27,7 @@ def quiz(lab_n, questions_num=10): ``` ```python editable=true slideshow={"slide_type": ""} -quiz(5) +quiz(9) ``` ```python @@ -48,5 +48,9 @@ display_quiz(questions, ``` ```python editable=true slideshow={"slide_type": ""} +!pip install jupyterquiz +``` + +```python ```