diff --git a/README.md b/README.md index 58ccc3e7d..6b171db4e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ English日本語 + | + Русский

## 关于本书 diff --git a/en/README.md b/en/README.md index db534581f..1d444db02 100644 --- a/en/README.md +++ b/en/README.md @@ -45,6 +45,8 @@ English | 日本語 + | + Русский

## The book diff --git a/ja/README.md b/ja/README.md index 54f556580..733cc7763 100644 --- a/ja/README.md +++ b/ja/README.md @@ -43,6 +43,8 @@ English | 日本語 + | + Русский

## この本について diff --git a/mkdocs.yml b/mkdocs.yml index ccfd7023f..b63dba521 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,6 +80,9 @@ extra: - name: 日本語 link: /ja/ lang: ja + - name: Русский + link: /ru/ + lang: ru social: - icon: fontawesome/brands/github link: https://github.com/krahets diff --git a/ru/README.md b/ru/README.md new file mode 100644 index 000000000..df8a0e5da --- /dev/null +++ b/ru/README.md @@ -0,0 +1,101 @@ +

+ + +

+ +

+ hello-algo-typing-svg +
+ Учебник по структурам данных и алгоритмам с анимированными схемами и кодом, готовым к запуску в один клик +

+ +

+ Читать онлайн + | + Скачать PDF/EPUB +

+ +

+ + +

+ +

+ + + + + + + + + + + + + +

+ +

+ 简体中文 + | + 繁體中文 + | + English + | + 日本語 + | + Русский +

+ +## О книге + +Этот проект призван создать бесплатный, открытый и дружелюбный к начинающим учебник по структурам данных и алгоритмам. + +- Книга построена на анимированных схемах, понятном изложении и плавной кривой обучения, помогая начинающим выстроить карту знаний по структурам данных и алгоритмам. +- Исходный код можно запускать в один клик, чтобы на практике развивать навыки программирования и понимать, как работают алгоритмы и как устроены структуры данных внутри. +- Мы поддерживаем совместное обучение: задавайте вопросы, делитесь идеями и продвигайтесь вперед через обсуждение. + +Если книга оказалась вам полезной, пожалуйста, поставьте Star :star: в правом верхнем углу страницы. Спасибо! + +## Рекомендации + +> «Понятная вводная книга по структурам данных и алгоритмам, которая направляет читателя к обучению и умом, и руками. Настоятельно рекомендую начинающим изучать алгоритмы именно с нее.» +> +> **—— Junhui Deng, профессор факультета компьютерных наук Университета Цинхуа** + +> «Если бы у меня была “Hello Algo”, когда я изучал структуры данных и алгоритмы, учиться было бы в десять раз проще!» +> +> **—— Mu Li, Senior Principal Scientist, Amazon** + +## Благодарности + +

+ + Warp-Github-LG-02 +

+ +[Warp создан для программирования с несколькими AI-агентами.](https://go.warp.dev/hello-algo) + +Очень рекомендуем терминал Warp: красивый интерфейс, полезные AI-возможности и отличное общее впечатление от работы. + +## Участие + +Эта открытая книга продолжает активно развиваться, и мы будем рады вашему участию, чтобы сделать обучение для читателей еще качественнее. + +- [Исправление содержания](https://www.hello-algo.com/ru/chapter_appendix/contribution/): помогайте исправлять или указывать в комментариях грамматические ошибки, пропуски в содержании, двусмысленные формулировки, неработающие ссылки и баги в коде. +- [Перевод кода на другие языки](https://github.com/krahets/hello-algo/issues/15): приглашаем вносить вклад в код на разных языках программирования; сейчас уже поддерживаются Python, Java, C++, Go, JavaScript и другие. + +Будем рады вашим замечаниям и предложениям. Если у вас есть вопросы, создайте Issue или свяжитесь через WeChat: `krahets-jyd`. + +Мы благодарим каждого участника, работавшего над этой книгой. Именно их самоотверженный вклад делает ее лучше: + +

+ + + +

+ +## License + +The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/ru/codes/Dockerfile b/ru/codes/Dockerfile new file mode 100644 index 000000000..97d37bbcf --- /dev/null +++ b/ru/codes/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:latest + +# Use Ubuntu image from Aliyun +RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ + sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ + sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list + +RUN apt-get update && apt-get install -y wget + +# Install languages environment +ARG LANGS +RUN for LANG in $LANGS; do \ + case $LANG in \ + python) \ + apt-get install -y python3.10 && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 ;; \ + cpp) \ + apt-get install -y g++ gdb ;; \ + java) \ + apt-get install -y openjdk-17-jdk ;; \ + csharp) \ + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt-get update && \ + apt-get install -y dotnet-sdk-8.0 ;; \ + # More languages... + *) \ + echo "Warning: No installation workflow for $LANG" ;; \ + esac \ + done + +WORKDIR /codes +COPY ./ ./ + +CMD ["/bin/bash"] diff --git a/ru/codes/c/.gitignore b/ru/codes/c/.gitignore new file mode 100644 index 000000000..698ee4e21 --- /dev/null +++ b/ru/codes/c/.gitignore @@ -0,0 +1,9 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ +*.dSYM/ + +build/ diff --git a/ru/codes/c/CMakeLists.txt b/ru/codes/c/CMakeLists.txt new file mode 100644 index 000000000..bb5f8f6a9 --- /dev/null +++ b/ru/codes/c/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo C) + +set(CMAKE_C_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/ru/codes/c/chapter_array_and_linkedlist/CMakeLists.txt b/ru/codes/c/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 000000000..29677a0be --- /dev/null +++ b/ru/codes/c/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(array array.c) +add_executable(linked_list linked_list.c) +add_executable(my_list my_list.c) \ No newline at end of file diff --git a/ru/codes/c/chapter_array_and_linkedlist/array.c b/ru/codes/c/chapter_array_and_linkedlist/array.c new file mode 100644 index 000000000..4f26b5a76 --- /dev/null +++ b/ru/codes/c/chapter_array_and_linkedlist/array.c @@ -0,0 +1,114 @@ +/** + * File: array.c + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com) + */ + +#include "../utils/common.h" + +/* Случайный доступ к элементу */ +int randomAccess(int *nums, int size) { + // Случайным образом выбрать число из интервала [0, size) + int randomIndex = rand() % size; + // Получить и вернуть случайный элемент + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* Увеличить длину массива */ +int *extend(int *nums, int size, int enlarge) { + // Инициализировать массив увеличенной длины + int *res = (int *)malloc(sizeof(int) * (size + enlarge)); + // Скопировать все элементы исходного массива в новый массив + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // Инициализировать расширенное пространство + for (int i = size; i < size + enlarge; i++) { + res[i] = 0; + } + // Вернуть новый массив после расширения + return res; +} + +/* Вставить элемент num по индексу index в массив */ +void insert(int *nums, int size, int num, int index) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; +} + +/* Удалить элемент по индексу index */ +// Внимание: stdio.h уже использует ключевое слово remove +void removeItem(int *nums, int size, int index) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* Обход массива */ +void traverse(int *nums, int size) { + int count = 0; + // Обход массива по индексам + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* Найти заданный элемент в массиве */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* Инициализация массива */ + int size = 5; + int arr[5]; + printf("Массив arr = "); + printArray(arr, size); + + int nums[] = {1, 3, 2, 5, 4}; + printf("Массив nums = "); + printArray(nums, size); + + /* Случайный доступ */ + int randomNum = randomAccess(nums, size); + printf("Случайный элемент из nums = %d", randomNum); + + /* Расширение длины */ + int enlarge = 3; + int *res = extend(nums, size, enlarge); + size += enlarge; + printf("После увеличения длины массива до 8 nums = "); + printArray(res, size); + + /* Вставка элемента */ + insert(res, size, 6, 3); + printf("После вставки числа 6 по индексу 3 nums = "); + printArray(res, size); + + /* Удаление элемента */ + removeItem(res, size, 2); + printf("После удаления элемента по индексу 2 nums = "); + printArray(res, size); + + /* Обход массива */ + traverse(res, size); + + /* Поиск элемента */ + int index = find(res, size, 3); + printf("Индекс элемента 3 в res = %d\n", index); + + /* Освободить память */ + free(res); + return 0; +} diff --git a/ru/codes/c/chapter_array_and_linkedlist/linked_list.c b/ru/codes/c/chapter_array_and_linkedlist/linked_list.c new file mode 100644 index 000000000..99f595b59 --- /dev/null +++ b/ru/codes/c/chapter_array_and_linkedlist/linked_list.c @@ -0,0 +1,89 @@ +/** + * File: linked_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Вставить узел P после узла n0 в связном списке */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* Удалить первый узел после узла n0 в связном списке */ +// Внимание: stdio.h уже использует ключевое слово remove +void removeItem(ListNode *n0) { + if (!n0->next) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // Освободить память + free(P); +} + +/* Доступ к узлу связного списка по индексу index */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == NULL) + return NULL; + head = head->next; + } + return head; +} + +/* Найти в связном списке первый узел со значением target */ +int find(ListNode *head, int target) { + int index = 0; + while (head) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* Инициализация связного списка */ + // Инициализация всех узлов + ListNode *n0 = newListNode(1); + ListNode *n1 = newListNode(3); + ListNode *n2 = newListNode(2); + ListNode *n3 = newListNode(5); + ListNode *n4 = newListNode(4); + // Построить ссылки между узлами + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + printf("Инициализированный связный список\r\n"); + printLinkedList(n0); + + /* Вставка узла */ + insert(n0, newListNode(0)); + printf("Связный список после вставки узла\r\n"); + printLinkedList(n0); + + /* Удаление узла */ + removeItem(n0); + printf("Связный список после удаления узла\r\n"); + printLinkedList(n0); + + /* Доступ к узлу */ + ListNode *node = access(n0, 3); + printf("Значение узла по индексу 3 в связном списке = %d\r\n", node->val); + + /* Поиск узла */ + int index = find(n0, 2); + printf("Индекс узла со значением 2 в связном списке = %d\r\n", index); + + // Освободить память + freeMemoryLinkedList(n0); + return 0; +} diff --git a/ru/codes/c/chapter_array_and_linkedlist/my_list.c b/ru/codes/c/chapter_array_and_linkedlist/my_list.c new file mode 100644 index 000000000..3817de31a --- /dev/null +++ b/ru/codes/c/chapter_array_and_linkedlist/my_list.c @@ -0,0 +1,163 @@ +/** + * File: my_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Класс списка */ +typedef struct { + int *arr; // Массив (для хранения элементов списка) + int capacity; // Вместимость списка + int size; // Размер списка + int extendRatio; // Коэффициент расширения списка при каждом увеличении +} MyList; + +void extendCapacity(MyList *nums); + +/* Конструктор */ +MyList *newMyList() { + MyList *nums = malloc(sizeof(MyList)); + nums->capacity = 10; + nums->arr = malloc(sizeof(int) * nums->capacity); + nums->size = 0; + nums->extendRatio = 2; + return nums; +} + +/* Деструктор */ +void delMyList(MyList *nums) { + free(nums->arr); + free(nums); +} + +/* Получить длину списка */ +int size(MyList *nums) { + return nums->size; +} + +/* Получить вместимость списка */ +int capacity(MyList *nums) { + return nums->capacity; +} + +/* Доступ к элементу */ +int get(MyList *nums, int index) { + assert(index >= 0 && index < nums->size); + return nums->arr[index]; +} + +/* Обновление элемента */ +void set(MyList *nums, int index, int num) { + assert(index >= 0 && index < nums->size); + nums->arr[index] = num; +} + +/* Добавление элемента в конец */ +void add(MyList *nums, int num) { + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // Расширение емкости + } + nums->arr[size(nums)] = num; + nums->size++; +} + +/* Вставка элемента в середину */ +void insert(MyList *nums, int index, int num) { + assert(index >= 0 && index < size(nums)); + // При превышении вместимости по числу элементов запускается расширение + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // Расширение емкости + } + for (int i = size(nums); i > index; --i) { + nums->arr[i] = nums->arr[i - 1]; + } + nums->arr[index] = num; + nums->size++; +} + +/* Удаление элемента */ +// Внимание: stdio.h уже использует ключевое слово remove +int removeItem(MyList *nums, int index) { + assert(index >= 0 && index < size(nums)); + int num = nums->arr[index]; + for (int i = index; i < size(nums) - 1; i++) { + nums->arr[i] = nums->arr[i + 1]; + } + nums->size--; + return num; +} + +/* Расширение списка */ +void extendCapacity(MyList *nums) { + // Сначала выделить память + int newCapacity = capacity(nums) * nums->extendRatio; + int *extend = (int *)malloc(sizeof(int) * newCapacity); + int *temp = nums->arr; + + // Скопировать старые данные в новые + for (int i = 0; i < size(nums); i++) + extend[i] = nums->arr[i]; + + // Освободить старые данные + free(temp); + + // Обновить новые данные + nums->arr = extend; + nums->capacity = newCapacity; +} + +/* Преобразовать список в Array для вывода */ +int *toArray(MyList *nums) { + return nums->arr; +} + +/* Driver Code */ +int main() { + /* Инициализация списка */ + MyList *nums = newMyList(); + /* Добавление элемента в конец */ + add(nums, 1); + add(nums, 3); + add(nums, 2); + add(nums, 5); + add(nums, 4); + printf("Список nums = "); + printArray(toArray(nums), size(nums)); + printf("Вместимость = %d, длина = %d\n", capacity(nums), size(nums)); + + /* Вставка элемента в середину */ + insert(nums, 3, 6); + printf("После вставки числа 6 по индексу 3 nums = "); + printArray(toArray(nums), size(nums)); + + /* Удаление элемента */ + removeItem(nums, 3); + printf("После удаления элемента по индексу 3 nums = "); + printArray(toArray(nums), size(nums)); + + /* Доступ к элементу */ + int num = get(nums, 1); + printf("Элемент по индексу 1: num = %d\n", num); + + /* Обновление элемента */ + set(nums, 1, 0); + printf("После обновления элемента по индексу 1 на 0 nums = "); + printArray(toArray(nums), size(nums)); + + /* Проверка механизма расширения */ + for (int i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + add(nums, i); + } + + printf("После расширения список nums = "); + printArray(toArray(nums), size(nums)); + printf("Вместимость = %d, длина = %d\n", capacity(nums), size(nums)); + + /* Освободить выделенную память */ + delMyList(nums); + + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/CMakeLists.txt b/ru/codes/c/chapter_backtracking/CMakeLists.txt new file mode 100644 index 000000000..70161b6dd --- /dev/null +++ b/ru/codes/c/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(permutations_i permutations_i.c) +add_executable(permutations_ii permutations_ii.c) +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) +add_executable(subset_sum_i_naive subset_sum_i_naive.c) +add_executable(subset_sum_i subset_sum_i.c) +add_executable(subset_sum_ii subset_sum_ii.c) +add_executable(n_queens n_queens.c) \ No newline at end of file diff --git a/ru/codes/c/chapter_backtracking/n_queens.c b/ru/codes/c/chapter_backtracking/n_queens.c new file mode 100644 index 000000000..929da94a8 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/n_queens.c @@ -0,0 +1,95 @@ +/** + * File : n_queens.c + * Created Time: 2023-09-25 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* Алгоритм бэктрекинга: n ферзей */ +void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], + bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { + // Когда все строки уже обработаны, записать решение + if (row == n) { + res[*resSize] = (char **)malloc(sizeof(char *) * n); + for (int i = 0; i < n; ++i) { + res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); + strcpy(res[*resSize][i], state[i]); + } + (*resSize)++; + return; + } + // Обойти все столбцы + for (int col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* Решить задачу о n ферзях */ +char ***nQueens(int n, int *returnSize) { + char state[MAX_SIZE][MAX_SIZE]; + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + state[i][j] = '#'; + } + state[i][n] = '\0'; + } + bool cols[MAX_SIZE] = {false}; // Отмечать, есть ли ферзь в столбце + bool diags1[2 * MAX_SIZE - 1] = {false}; // Отмечать наличие ферзя на главной диагонали + bool diags2[2 * MAX_SIZE - 1] = {false}; // Отмечать наличие ферзя на побочной диагонали + + char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); + *returnSize = 0; + backtrack(0, n, state, res, returnSize, cols, diags1, diags2); + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + int returnSize; + char ***res = nQueens(n, &returnSize); + + printf("Размер входной доски = %d\n", n); + printf("Количество способов расстановки ферзей: %d\n", returnSize); + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + printf("["); + for (int k = 0; res[i][j][k] != '\0'; ++k) { + printf("%c", res[i][j][k]); + if (res[i][j][k + 1] != '\0') { + printf(", "); + } + } + printf("]\n"); + } + printf("---------------------\n"); + } + + // Освободить память + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + free(res[i][j]); + } + free(res[i]); + } + free(res); + + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/permutations_i.c b/ru/codes/c/chapter_backtracking/permutations_i.c new file mode 100644 index 000000000..5a94d4647 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/permutations_i.c @@ -0,0 +1,79 @@ +/** + * File: permutations_i.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// Предположим, что существует не более 1000 перестановок +#define MAX_SIZE 1000 + +/* Алгоритм бэктрекинга: все перестановки I */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // Когда длина состояния равна числу элементов, записать решение + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state[stateSize] = choice; + // Перейти к следующему выбору + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + } + } +} + +/* Все перестановки I */ +int **permutationsI(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 2, 3}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsI(nums, numsSize, &returnSize); + + printf("Входной массив nums = "); + printArray(nums, numsSize); + printf("\nВсе перестановки res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // Освободить память + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/permutations_ii.c b/ru/codes/c/chapter_backtracking/permutations_ii.c new file mode 100644 index 000000000..2caa13b50 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/permutations_ii.c @@ -0,0 +1,81 @@ +/** + * File: permutations_ii.c + * Created Time: 2023-10-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// Предположить, что существует не более 1000 перестановок, а максимальный элемент равен 1000 +#define MAX_SIZE 1000 + +/* Алгоритм бэктрекинга: все перестановки II */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // Когда длина состояния равна числу элементов, записать решение + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // Перебор всех вариантов выбора + bool duplicated[MAX_SIZE] = {false}; + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated[choice]) { + // Попытка: сделать выбор и обновить состояние + duplicated[choice] = true; // Записать значения уже выбранных элементов + selected[i] = true; + state[stateSize] = choice; + // Перейти к следующему выбору + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + } + } +} + +/* Все перестановки II */ +int **permutationsII(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 1, 2}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsII(nums, numsSize, &returnSize); + + printf("Входной массив nums = "); + printArray(nums, numsSize); + printf("\nВсе перестановки res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // Освободить память + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/preorder_traversal_i_compact.c b/ru/codes/c/chapter_backtracking/preorder_traversal_i_compact.c new file mode 100644 index 000000000..0dffbb407 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/preorder_traversal_i_compact.c @@ -0,0 +1,49 @@ +/** + * File: preorder_traversal_i_compact.c + * Created Time: 2023-05-10 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// Предположить, что длина результата не превышает 100 +#define MAX_SIZE 100 + +TreeNode *res[MAX_SIZE]; +int resSize = 0; + +/* Предварительный обход: пример 1 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + if (root->val == 7) { + // Записать решение + res[resSize++] = root; + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\nИнициализация двоичного дерева\n"); + printTree(root); + + // Предварительный обход + preOrder(root); + + printf("\nВывести все узлы со значением 7\n"); + int *vals = malloc(resSize * sizeof(int)); + for (int i = 0; i < resSize; i++) { + vals[i] = res[i]->val; + } + printArray(vals, resSize); + + // Освободить память + freeMemoryTree(root); + free(vals); + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c b/ru/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c new file mode 100644 index 000000000..3b5983e38 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c @@ -0,0 +1,61 @@ +/** + * File: preorder_traversal_ii_compact.c + * Created Time: 2023-05-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// Предположим, что длина пути и результата не превышает 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* Предварительный обход: пример 2 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + // Попытка + path[pathSize++] = root; + if (root->val == 7) { + // Записать решение + for (int i = 0; i < pathSize; ++i) { + res[resSize][i] = path[i]; + } + resSize++; + } + preOrder(root->left); + preOrder(root->right); + // Откат + pathSize--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\nИнициализация двоичного дерева\n"); + printTree(root); + + // Предварительный обход + preOrder(root); + + printf("\nВывести все пути от корня к узлу 7\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // Освободить память + freeMemoryTree(root); + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c b/ru/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c new file mode 100644 index 000000000..7a4e03adf --- /dev/null +++ b/ru/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c @@ -0,0 +1,62 @@ +/** + * File: preorder_traversal_iii_compact.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// Предположим, что длина пути и результата не превышает 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* Предварительный обход: пример 3 */ +void preOrder(TreeNode *root) { + // Отсечение + if (root == NULL || root->val == 3) { + return; + } + // Попытка + path[pathSize++] = root; + if (root->val == 7) { + // Записать решение + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; + } + preOrder(root->left); + preOrder(root->right); + // Откат + pathSize--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\nИнициализация двоичного дерева\n"); + printTree(root); + + // Предварительный обход + preOrder(root); + + printf("\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // Освободить память + freeMemoryTree(root); + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/preorder_traversal_iii_template.c b/ru/codes/c/chapter_backtracking/preorder_traversal_iii_template.c new file mode 100644 index 000000000..98ad4a116 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/preorder_traversal_iii_template.c @@ -0,0 +1,93 @@ +/** + * File: preorder_traversal_iii_template.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// Предположим, что длина пути и результата не превышает 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* Проверить, является ли текущее состояние решением */ +bool isSolution(void) { + return pathSize > 0 && path[pathSize - 1]->val == 7; +} + +/* Записать решение */ +void recordSolution(void) { + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +bool isValid(TreeNode *choice) { + return choice != NULL && choice->val != 3; +} + +/* Обновить состояние */ +void makeChoice(TreeNode *choice) { + path[pathSize++] = choice; +} + +/* Восстановить состояние */ +void undoChoice(void) { + pathSize--; +} + +/* Алгоритм бэктрекинга: пример 3 */ +void backtrack(TreeNode *choices[2]) { + // Проверить, является ли текущее состояние решением + if (isSolution()) { + // Записать решение + recordSolution(); + } + // Перебор всех вариантов выбора + for (int i = 0; i < 2; i++) { + TreeNode *choice = choices[i]; + // Отсечение: проверить допустимость выбора + if (isValid(choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(choice); + // Перейти к следующему выбору + TreeNode *nextChoices[2] = {choice->left, choice->right}; + backtrack(nextChoices); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(); + } + } +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\nИнициализация двоичного дерева\n"); + printTree(root); + + // Алгоритм бэктрекинга + TreeNode *choices[2] = {root, NULL}; + backtrack(choices); + + printf("\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // Освободить память + freeMemoryTree(root); + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/subset_sum_i.c b/ru/codes/c/chapter_backtracking/subset_sum_i.c new file mode 100644 index 000000000..ec5374445 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/subset_sum_i.c @@ -0,0 +1,78 @@ +/** + * File: subset_sum_i.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// Состояние (подмножество) +int state[MAX_SIZE]; +int stateSize = 0; + +// Список результатов (список подмножеств) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + for (int i = 0; i < stateSize; ++i) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (int i = start; i < choicesSize; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state[stateSize] = choices[i]; + stateSize++; + // Перейти к следующему выбору + backtrack(target - choices[i], choices, choicesSize, i); + // Откат: отменить выбор и восстановить предыдущее состояние + stateSize--; + } +} + +/* Функция сравнения */ +int cmp(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +/* Решить задачу суммы подмножеств I */ +void subsetSumI(int *nums, int numsSize, int target) { + qsort(nums, numsSize, sizeof(int), cmp); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + backtrack(target, nums, numsSize, start); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumI(nums, numsSize, target); + + printf("Входной массив nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("Все подмножества с суммой %d: \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/subset_sum_i_naive.c b/ru/codes/c/chapter_backtracking/subset_sum_i_naive.c new file mode 100644 index 000000000..a606a7366 --- /dev/null +++ b/ru/codes/c/chapter_backtracking/subset_sum_i_naive.c @@ -0,0 +1,69 @@ +/** + * File: subset_sum_i_naive.c + * Created Time: 2023-07-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// Состояние (подмножество) +int state[MAX_SIZE]; +int stateSize = 0; + +// Список результатов (список подмножеств) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +void backtrack(int target, int total, int *choices, int choicesSize) { + // Если сумма подмножества равна target, записать решение + if (total == target) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choicesSize; i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state[stateSize++] = choices[i]; + // Перейти к следующему выбору + backtrack(target, total + choices[i], choices, choicesSize); + // Откат: отменить выбор и восстановить предыдущее состояние + stateSize--; + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +void subsetSumINaive(int *nums, int numsSize, int target) { + resSize = 0; // Инициализировать число решений нулем + backtrack(target, 0, nums, numsSize); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumINaive(nums, numsSize, target); + + printf("Входной массив nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("Все подмножества с суммой %d: \n", target); + for (int i = 0; i < resSize; i++) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/ru/codes/c/chapter_backtracking/subset_sum_ii.c b/ru/codes/c/chapter_backtracking/subset_sum_ii.c new file mode 100644 index 000000000..9ea1c791b --- /dev/null +++ b/ru/codes/c/chapter_backtracking/subset_sum_ii.c @@ -0,0 +1,83 @@ +/** + * File: subset_sum_ii.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// Состояние (подмножество) +int state[MAX_SIZE]; +int stateSize = 0; + +// Список результатов (список подмножеств) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (int i = start; i < choicesSize; i++) { + // Отсечение 1: если сумма подмножества превышает target, сразу пропустить + if (target - choices[i] < 0) { + continue; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state[stateSize] = choices[i]; + stateSize++; + // Перейти к следующему выбору + backtrack(target - choices[i], choices, choicesSize, i + 1); + // Откат: отменить выбор и восстановить предыдущее состояние + stateSize--; + } +} + +/* Функция сравнения */ +int cmp(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +/* Решить задачу суммы подмножеств II */ +void subsetSumII(int *nums, int numsSize, int target) { + // Отсортировать nums + qsort(nums, numsSize, sizeof(int), cmp); + // Начать бэктрекинг + backtrack(target, nums, numsSize, 0); +} + +/* Driver Code */ +int main() { + int nums[] = {4, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumII(nums, numsSize, target); + + printf("Входной массив nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("Все подмножества с суммой %d: \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/ru/codes/c/chapter_computational_complexity/CMakeLists.txt b/ru/codes/c/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 000000000..dcfa063c3 --- /dev/null +++ b/ru/codes/c/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.c) +add_executable(recursion recursion.c) +add_executable(time_complexity time_complexity.c) +add_executable(worst_best_time_complexity worst_best_time_complexity.c) +add_executable(space_complexity space_complexity.c) diff --git a/ru/codes/c/chapter_computational_complexity/iteration.c b/ru/codes/c/chapter_computational_complexity/iteration.c new file mode 100644 index 000000000..00f6a9904 --- /dev/null +++ b/ru/codes/c/chapter_computational_complexity/iteration.c @@ -0,0 +1,81 @@ +/** + * File: iteration.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) + */ + +#include "../utils/common.h" + +/* Цикл for */ +int forLoop(int n) { + int res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* Цикл while */ +int whileLoop(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Обновить условную переменную + } + return res; +} + +/* Цикл while (двойное обновление) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i++; + i *= 2; + } + return res; +} + +/* Двойной цикл for */ +char *nestedForLoop(int n) { + // n * n — это число соответствующих точек, а максимальная длина строки "(i, j), " равна 6+10*2, плюс дополнительное место для завершающего нулевого символа \0 + int size = n * n * 26 + 1; + char *res = malloc(size * sizeof(char)); + // Цикл по i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // Цикл по j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + char tmp[26]; + snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); + strncat(res, tmp, size - strlen(res) - 1); + } + } + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + printf("\nРезультат суммирования в цикле for res = %d\n", res); + + res = whileLoop(n); + printf("\nРезультат суммирования в цикле while res = %d\n", res); + + res = whileLoopII(n); + printf("\nРезультат суммирования в цикле while (двойное обновление) res = %d\n", res); + + char *resStr = nestedForLoop(n); + printf("\nРезультат двойного цикла for %s\r\n", resStr); + free(resStr); + + return 0; +} diff --git a/ru/codes/c/chapter_computational_complexity/recursion.c b/ru/codes/c/chapter_computational_complexity/recursion.c new file mode 100644 index 000000000..55d4d697d --- /dev/null +++ b/ru/codes/c/chapter_computational_complexity/recursion.c @@ -0,0 +1,77 @@ +/** + * File: recursion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Рекурсия */ +int recur(int n) { + // Условие завершения + if (n == 1) + return 1; + // Рекурсия: рекурсивный вызов + int res = recur(n - 1); + // Возврат: вернуть результат + return n + res; +} + +/* Имитация рекурсии итерацией */ +int forLoopRecur(int n) { + int stack[1000]; // Использовать большой массив для имитации стека + int top = -1; // Индекс вершины стека + int res = 0; + // Рекурсия: рекурсивный вызов + for (int i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack[1 + top++] = i; + } + // Возврат: вернуть результат + while (top >= 0) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack[top--]; + } + // res = 1+2+3+...+n + return res; +} + +/* Хвостовая рекурсия */ +int tailRecur(int n, int res) { + // Условие завершения + if (n == 0) + return res; + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); +} + +/* Последовательность Фибоначчи: рекурсия */ +int fib(int n) { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + printf("\nРезультат суммирования в рекурсивной функции res = %d\n", res); + + res = forLoopRecur(n); + printf("\nРезультат суммирования с использованием итерации для имитации рекурсии res = %d\n", res); + + res = tailRecur(n, 0); + printf("\nРезультат суммирования в хвостовой рекурсии res = %d\n", res); + + res = fib(n); + printf("\nЭлемент последовательности Фибоначчи с индексом %d = %d\n", n, res); + + return 0; +} diff --git a/ru/codes/c/chapter_computational_complexity/space_complexity.c b/ru/codes/c/chapter_computational_complexity/space_complexity.c new file mode 100644 index 000000000..2846fdfe9 --- /dev/null +++ b/ru/codes/c/chapter_computational_complexity/space_complexity.c @@ -0,0 +1,141 @@ +/** + * File: space_complexity.c + * Created Time: 2023-04-15 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Функция */ +int func() { + // Выполнить некоторые операции + return 0; +} + +/* Постоянная сложность */ +void constant(int n) { + // Константы, переменные и объекты занимают O(1) памяти + const int a = 0; + int b = 0; + int nums[1000]; + ListNode *node = newListNode(0); + free(node); + // Переменные в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + int c = 0; + } + // Функции в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + func(); + } +} + +/* Хеш-таблица */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // Реализовано на основе uthash.h +} HashTable; + +/* Линейная сложность */ +void linear(int n) { + // Массив длины n занимает O(n) памяти + int *nums = malloc(sizeof(int) * n); + free(nums); + + // Список длины n занимает O(n) памяти + ListNode **nodes = malloc(sizeof(ListNode *) * n); + for (int i = 0; i < n; i++) { + nodes[i] = newListNode(i); + } + // Освобождение памяти + for (int i = 0; i < n; i++) { + free(nodes[i]); + } + free(nodes); + + // Хеш-таблица длины n занимает O(n) памяти + HashTable *h = NULL; + for (int i = 0; i < n; i++) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = i; + tmp->val = i; + HASH_ADD_INT(h, key, tmp); + } + + // Освобождение памяти + HashTable *curr, *tmp; + HASH_ITER(hh, h, curr, tmp) { + HASH_DEL(h, curr); + free(curr); + } +} + +/* Линейная сложность (рекурсивная реализация) */ +void linearRecur(int n) { + printf("Рекурсия n = %d\r\n", n); + if (n == 1) + return; + linearRecur(n - 1); +} + +/* Квадратичная сложность */ +void quadratic(int n) { + // Двумерный список занимает O(n^2) памяти + int **numMatrix = malloc(sizeof(int *) * n); + for (int i = 0; i < n; i++) { + int *tmp = malloc(sizeof(int) * n); + for (int j = 0; j < n; j++) { + tmp[j] = 0; + } + numMatrix[i] = tmp; + } + + // Освобождение памяти + for (int i = 0; i < n; i++) { + free(numMatrix[i]); + } + free(numMatrix); +} + +/* Квадратичная сложность (рекурсивная реализация) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + int *nums = malloc(sizeof(int) * n); + printf("Рекурсия n = %d, длина nums = %d\r\n", n, n); + int res = quadraticRecur(n - 1); + free(nums); + return res; +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +TreeNode *buildTree(int n) { + if (n == 0) + return NULL; + TreeNode *root = newTreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // Постоянная сложность + constant(n); + // Линейная сложность + linear(n); + linearRecur(n); + // Квадратичная сложность + quadratic(n); + quadraticRecur(n); + // Экспоненциальная сложность + TreeNode *root = buildTree(n); + printTree(root); + + // Освободить память + freeMemoryTree(root); + + return 0; +} diff --git a/ru/codes/c/chapter_computational_complexity/time_complexity.c b/ru/codes/c/chapter_computational_complexity/time_complexity.c new file mode 100644 index 000000000..56720669b --- /dev/null +++ b/ru/codes/c/chapter_computational_complexity/time_complexity.c @@ -0,0 +1,179 @@ +/** + * File: time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* Постоянная сложность */ +int constant(int n) { + int count = 0; + int size = 100000; + int i = 0; + for (int i = 0; i < size; i++) { + count++; + } + return count; +} + +/* Линейная сложность */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Линейная сложность (обход массива) */ +int arrayTraversal(int *nums, int n) { + int count = 0; + // Число итераций пропорционально длине массива + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Квадратичная сложность */ +int quadratic(int n) { + int count = 0; + // Число итераций квадратично зависит от размера данных n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +int bubbleSort(int *nums, int n) { + int count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = n - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; +} + +/* Экспоненциальная сложность (итеративная реализация) */ +int exponential(int n) { + int count = 0; + int bas = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* Логарифмическая сложность (итеративная реализация) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* Линейно-логарифмическая сложность */ +int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Факториальная сложность (рекурсивная реализация) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + int n = 8; + printf("Размер входных данных n = %d\n", n); + + int count = constant(n); + printf("Количество операций постоянной сложности = %d\n", count); + + count = linear(n); + printf("Количество операций линейной сложности = %d\n", count); + // Выделить память в куче (создать одномерный массив переменной длины: число элементов равно n, тип элементов — int) + int *nums = (int *)malloc(n * sizeof(int)); + count = arrayTraversal(nums, n); + printf("Количество операций линейной сложности (обход массива) = %d\n", count); + + count = quadratic(n); + printf("Количество операций квадратичной сложности = %d\n", count); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums, n); + printf("Количество операций квадратичной сложности (пузырьковая сортировка) = %d\n", count); + + count = exponential(n); + printf("Количество операций экспоненциальной сложности (итерация) = %d\n", count); + count = expRecur(n); + printf("Количество операций экспоненциальной сложности (рекурсия) = %d\n", count); + + count = logarithmic(n); + printf("Количество операций логарифмической сложности (итерация) = %d\n", count); + count = logRecur(n); + printf("Количество операций логарифмической сложности (рекурсия) = %d\n", count); + + count = linearLogRecur(n); + printf("Количество операций линейно-логарифмической сложности (рекурсия) = %d\n", count); + + count = factorialRecur(n); + printf("Количество операций факториальной сложности (рекурсия) = %d\n", count); + + // Освободить память в куче + if (nums != NULL) { + free(nums); + nums = NULL; + } + getchar(); + + return 0; +} diff --git a/ru/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/ru/codes/c/chapter_computational_complexity/worst_best_time_complexity.c new file mode 100644 index 000000000..0ac947c94 --- /dev/null +++ b/ru/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -0,0 +1,57 @@ +/** + * File: worst_best_time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +int *randomNumbers(int n) { + // Выделить память в куче (создать одномерный массив переменной длины: число элементов равно n, тип элементов — int) + int *nums = (int *)malloc(n * sizeof(int)); + // Создать массив nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Случайно перемешать элементы массива + for (int i = n - 1; i > 0; i--) { + int j = rand() % (i + 1); + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + return nums; +} + +/* Найти индекс числа 1 в массиве nums */ +int findOne(int *nums, int n) { + for (int i = 0; i < n; i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // Инициализировать seed генератора случайных чисел + srand((unsigned int)time(NULL)); + for (int i = 0; i < 10; i++) { + int n = 100; + int *nums = randomNumbers(n); + int index = findOne(nums, n); + printf("\nПосле перемешивания массива [ 1, 2, ..., n ] nums = "); + printArray(nums, n); + printf("Индекс числа 1 = %d\n", index); + // Освободить память в куче + if (nums != NULL) { + free(nums); + nums = NULL; + } + } + + return 0; +} diff --git a/ru/codes/c/chapter_divide_and_conquer/CMakeLists.txt b/ru/codes/c/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 000000000..e03b1c588 --- /dev/null +++ b/ru/codes/c/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.c) +add_executable(build_tree build_tree.c) +add_executable(hanota hanota.c) diff --git a/ru/codes/c/chapter_divide_and_conquer/binary_search_recur.c b/ru/codes/c/chapter_divide_and_conquer/binary_search_recur.c new file mode 100644 index 000000000..89bdbb8df --- /dev/null +++ b/ru/codes/c/chapter_divide_and_conquer/binary_search_recur.c @@ -0,0 +1,47 @@ +/** + * File: binary_search_recur.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Бинарный поиск: задача f(i, j) */ +int dfs(int nums[], int target, int i, int j) { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + int m = (i + j) / 2; + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } +} + +/* Бинарный поиск */ +int binarySearch(int nums[], int target, int numsSize) { + int n = numsSize; + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + + // Бинарный поиск (двусторонне замкнутый интервал) + int index = binarySearch(nums, target, numsSize); + printf("Индекс целевого элемента 6 = %d\n", index); + + return 0; +} diff --git a/ru/codes/c/chapter_divide_and_conquer/build_tree.c b/ru/codes/c/chapter_divide_and_conquer/build_tree.c new file mode 100644 index 000000000..57b59c062 --- /dev/null +++ b/ru/codes/c/chapter_divide_and_conquer/build_tree.c @@ -0,0 +1,61 @@ +/** + * File : build_tree.c + * Created Time: 2023-10-16 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// Предположить, что все элементы меньше 1000 +#define MAX_SIZE 1000 + +/* Построить двоичное дерево: разделяй и властвуй */ +TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) + return NULL; + // Инициализировать корневой узел + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = preorder[i]; + root->left = NULL; + root->right = NULL; + // Найти m, чтобы разделить левое и правое поддеревья + int m = inorderMap[preorder[i]]; + // Подзадача: построить левое поддерево + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); + // Подзадача: построить правое поддерево + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); + // Вернуть корневой узел + return root; +} + +/* Построить двоичное дерево */ +TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); + for (int i = 0; i < inorderSize; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); + free(inorderMap); + return root; +} + +/* Driver Code */ +int main() { + int preorder[] = {3, 9, 2, 1, 7}; + int inorder[] = {9, 3, 1, 2, 7}; + int preorderSize = sizeof(preorder) / sizeof(preorder[0]); + int inorderSize = sizeof(inorder) / sizeof(inorder[0]); + printf("Предварительный обход = "); + printArray(preorder, preorderSize); + printf("Симметричный обход = "); + printArray(inorder, inorderSize); + + TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); + printf("Построенное двоичное дерево:\n"); + printTree(root); + + freeMemoryTree(root); + return 0; +} diff --git a/ru/codes/c/chapter_divide_and_conquer/hanota.c b/ru/codes/c/chapter_divide_and_conquer/hanota.c new file mode 100644 index 000000000..8201b948b --- /dev/null +++ b/ru/codes/c/chapter_divide_and_conquer/hanota.c @@ -0,0 +1,74 @@ +/** + * File: hanota.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// Предположим, что существует не более 1000 перестановок +#define MAX_SIZE 1000 + +/* Переместить один диск */ +void move(int *src, int *srcSize, int *tar, int *tarSize) { + // Снять диск с вершины src + int pan = src[*srcSize - 1]; + src[*srcSize - 1] = 0; + (*srcSize)--; + // Положить диск на вершину tar + tar[*tarSize] = pan; + (*tarSize)++; +} + +/* Решить задачу Ханойской башни f(i) */ +void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { + // Если в src остался только один диск, сразу переместить его в tar + if (i == 1) { + move(src, srcSize, tar, tarSize); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, srcSize, tar, tarSize); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); +} + +/* Решить задачу Ханойской башни */ +void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { + // Переместить верхние n дисков из A в C с помощью B + dfs(*ASize, A, ASize, B, BSize, C, CSize); +} + +/* Driver Code */ +int main() { + // Хвост списка соответствует вершине столбца + int a[] = {5, 4, 3, 2, 1}; + int b[MAX_SIZE] = {0}; + int c[MAX_SIZE] = {0}; + + int ASize = sizeof(a) / sizeof(a[0]); + int BSize = 0; + int CSize = 0; + + printf("\nНачальное состояние:"); + printf("\nA = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + solveHanota(a, &ASize, b, &BSize, c, &CSize); + + printf("\nПосле завершения перемещения дисков:"); + printf("A = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/CMakeLists.txt b/ru/codes/c/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 000000000..dd769ebd5 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) +add_executable(min_path_sum min_path_sum.c) +add_executable(knapsack knapsack.c) +add_executable(unbounded_knapsack unbounded_knapsack.c) +add_executable(coin_change coin_change.c) +add_executable(coin_change_ii coin_change_ii.c) +add_executable(edit_distance edit_distance.c) diff --git a/ru/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c new file mode 100644 index 000000000..2a9c4cac3 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c @@ -0,0 +1,47 @@ +/** + * File: climbing_stairs_backtrack.c + * Created Time: 2023-09-22 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* Бэктрекинг */ +void backtrack(int *choices, int state, int n, int *res, int len) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) + res[0]++; + // Перебор всех вариантов выбора + for (int i = 0; i < len; i++) { + int choice = choices[i]; + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) + continue; + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res, len); + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +int climbingStairsBacktrack(int n) { + int choices[2] = {1, 2}; // Можно подняться на 1 или 2 ступени + int state = 0; // Начать подъем с 0-й ступени + int *res = (int *)malloc(sizeof(int)); + *res = 0; // Использовать res[0] для хранения числа решений + int len = sizeof(choices) / sizeof(int); + backtrack(choices, state, n, res, len); + int result = *res; + free(res); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c new file mode 100644 index 000000000..22b3f6619 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_constraint_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(3, sizeof(int)); + } + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + int res = dp[n][1] + dp[n][2]; + // Освободить память + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c new file mode 100644 index 000000000..4cabb613e --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* Поиск */ +int dfs(int i) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* Подъем по лестнице: поиск */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c new file mode 100644 index 000000000..f15c87a32 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_dfs_mem.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* Поиск с мемоизацией */ +int dfs(int i, int *mem) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; +} + +/* Подъем по лестнице: поиск с мемоизацией */ +int climbingStairsDFSMem(int n) { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + int *mem = (int *)malloc((n + 1) * sizeof(int)); + for (int i = 0; i <= n; i++) { + mem[i] = -1; + } + int result = dfs(n, mem); + free(mem); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c new file mode 100644 index 000000000..b27f91cee --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c @@ -0,0 +1,51 @@ +/** + * File: climbing_stairs_dp.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* Подъем по лестнице: динамическое программирование */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // Инициализация таблицы dp для хранения решений подзадач + int *dp = (int *)malloc((n + 1) * sizeof(int)); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + int result = dp[n]; + free(dp); + return result; +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); + + res = climbingStairsDPComp(n); + printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_dynamic_programming/coin_change.c b/ru/codes/c/chapter_dynamic_programming/coin_change.c new file mode 100644 index 000000000..99913f9f1 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/coin_change.c @@ -0,0 +1,92 @@ +/** + * File: coin_change.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Найти минимум */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* Размен монет: динамическое программирование */ +int coinChangeDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // Инициализация таблицы dp + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // Переход состояний: первая строка и первый столбец + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + int res = dp[n][amt] != MAX ? dp[n][amt] : -1; + // Освободить память + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +int coinChangeDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // Инициализация таблицы dp + int *dp = malloc((amt + 1) * sizeof(int)); + for (int j = 1; j <= amt; j++) { + dp[j] = MAX; + } + dp[0] = 0; + + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + int res = dp[amt] != MAX ? dp[amt] : -1; + // Освободить память + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 4; + + // Динамическое программирование + int res = coinChangeDP(coins, amt, coinsSize); + printf("Минимальное количество монет для целевой суммы = %d\n", res); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins, amt, coinsSize); + printf("Минимальное количество монет для целевой суммы = %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/coin_change_ii.c b/ru/codes/c/chapter_dynamic_programming/coin_change_ii.c new file mode 100644 index 000000000..20e2d0056 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/coin_change_ii.c @@ -0,0 +1,81 @@ +/** + * File: coin_change_ii.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Размен монет II: динамическое программирование */ +int coinChangeIIDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // Инициализация таблицы dp + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // Инициализация первого столбца + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + int res = dp[n][amt]; + // Освободить память + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // Инициализация таблицы dp + int *dp = calloc(amt + 1, sizeof(int)); + dp[0] = 1; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + int res = dp[amt]; + // Освободить память + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 5; + + // Динамическое программирование + int res = coinChangeIIDP(coins, amt, coinsSize); + printf("Количество комбинаций монет для набора целевой суммы = %d\n", res); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(coins, amt, coinsSize); + printf("Количество комбинаций монет для набора целевой суммы = %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/edit_distance.c b/ru/codes/c/chapter_dynamic_programming/edit_distance.c new file mode 100644 index 000000000..f352a0c45 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/edit_distance.c @@ -0,0 +1,159 @@ +/** + * File: edit_distance.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Найти минимум */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* Редакционное расстояние: полный перебор */ +int editDistanceDFS(char *s, char *t, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return myMin(myMin(insert, del), replace) + 1; +} + +/* Редакционное расстояние: поиск с мемоизацией */ +int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) + return mem[i][j]; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = myMin(myMin(insert, del), replace) + 1; + return mem[i][j]; +} + +/* Редакционное расстояние: динамическое программирование */ +int editDistanceDP(char *s, char *t, int n, int m) { + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(m + 1, sizeof(int)); + } + // Переход состояний: первая строка и первый столбец + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + int res = dp[n][m]; + // Освободить память + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +int editDistanceDPComp(char *s, char *t, int n, int m) { + int *dp = calloc(m + 1, sizeof(int)); + // Переход состояний: первая строка + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (int i = 1; i <= n; i++) { + // Переход состояний: первый столбец + int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + int res = dp[m]; + // Освободить память + free(dp); + return res; +} + +/* Driver Code */ +int main() { + char *s = "bag"; + char *t = "pack"; + int n = strlen(s), m = strlen(t); + + // Полный перебор + int res = editDistanceDFS(s, t, n, m); + printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); + + // Поиск с мемоизацией + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((m + 1) * sizeof(int)); + memset(mem[i], -1, (m + 1) * sizeof(int)); + } + res = editDistanceDFSMem(s, t, m + 1, mem, n, m); + printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); + // Освободить память + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // Динамическое программирование + res = editDistanceDP(s, t, n, m); + printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t, n, m); + printf("Чтобы заменить %s на %s, требуется минимум %d операций редактирования\n", s, t, res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/knapsack.c b/ru/codes/c/chapter_dynamic_programming/knapsack.c new file mode 100644 index 000000000..4f12a5e91 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/knapsack.c @@ -0,0 +1,137 @@ +/** + * File: knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Найти максимум */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* Рюкзак 0-1: полный перебор */ +int knapsackDFS(int wgt[], int val[], int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return myMax(no, yes); +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = myMax(no, yes); + return mem[i][c]; +} + +/* Рюкзак 0-1: динамическое программирование */ +int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // Инициализация таблицы dp + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // Освободить память + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // Инициализация таблицы dp + int *dp = calloc(cap + 1, sizeof(int)); + // Переход состояний + for (int i = 1; i <= n; i++) { + // Обход в обратном порядке + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // Освободить память + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int cap = 50; + int n = sizeof(wgt) / sizeof(wgt[0]); + int wgtSize = n; + + // Полный перебор + int res = knapsackDFS(wgt, val, n, cap); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); + + // Поиск с мемоизацией + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((cap + 1) * sizeof(int)); + memset(mem[i], -1, (cap + 1) * sizeof(int)); + } + res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); + // Освободить память + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // Динамическое программирование + res = knapsackDP(wgt, val, cap, wgtSize); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt, val, cap, wgtSize); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c b/ru/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c new file mode 100644 index 000000000..24175e4bb --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c @@ -0,0 +1,62 @@ +/** + * File: min_cost_climbing_stairs_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Найти минимум */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +int minCostClimbingStairsDP(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + // Инициализация таблицы dp для хранения решений подзадач + int *dp = calloc(n + 1, sizeof(int)); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; + } + int res = dp[n]; + // Освободить память + free(dp); + return res; +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +int minCostClimbingStairsDPComp(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = myMin(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + int costSize = sizeof(cost) / sizeof(cost[0]); + printf("Список стоимостей ступеней = "); + printArray(cost, costSize); + + int res = minCostClimbingStairsDP(cost, costSize); + printf("Минимальная стоимость подъема по лестнице = %d\n", res); + + res = minCostClimbingStairsDPComp(cost, costSize); + printf("Минимальная стоимость подъема по лестнице = %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/min_path_sum.c b/ru/codes/c/chapter_dynamic_programming/min_path_sum.c new file mode 100644 index 000000000..5f0db0575 --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/min_path_sum.c @@ -0,0 +1,134 @@ +/** + * File: min_path_sum.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +// Предположить, что максимальное число строк и столбцов матрицы равно 100 +#define MAX_SIZE 100 + +/* Найти минимум */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* Минимальная сумма пути: полный перебор */ +int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return INT_MAX; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return INT_MAX; + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* Минимальная сумма пути: динамическое программирование */ +int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // Инициализация таблицы dp + int **dp = malloc(n * sizeof(int *)); + for (int i = 0; i < n; i++) { + dp[i] = calloc(m, sizeof(int)); + } + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + int res = dp[n - 1][m - 1]; + // Освободить память + for (int i = 0; i < n; i++) { + free(dp[i]); + } + return res; +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // Инициализация таблицы dp + int *dp = calloc(m, sizeof(int)); + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (int i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (int j = 1; j < m; j++) { + dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; + } + } + int res = dp[m - 1]; + // Освободить память + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = 4, m = 4; // Емкость матрицы равна MAX_SIZE * MAX_SIZE, число эффективных строк и столбцов — n * m + + // Полный перебор + int res = minPathSumDFS(grid, n - 1, m - 1); + printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); + + // Поиск с мемоизацией + int mem[MAX_SIZE][MAX_SIZE]; + memset(mem, -1, sizeof(mem)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); + + // Динамическое программирование + res = minPathSumDP(grid, n, m); + printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); + + // Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(grid, n, m); + printf("Минимальная сумма пути из левого верхнего в правый нижний угол = %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_dynamic_programming/unbounded_knapsack.c b/ru/codes/c/chapter_dynamic_programming/unbounded_knapsack.c new file mode 100644 index 000000000..a39e116ff --- /dev/null +++ b/ru/codes/c/chapter_dynamic_programming/unbounded_knapsack.c @@ -0,0 +1,81 @@ +/** + * File: unbounded_knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* Найти максимум */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* Полный рюкзак: динамическое программирование */ +int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // Инициализация таблицы dp + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // Освободить память + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // Инициализация таблицы dp + int *dp = calloc(cap + 1, sizeof(int)); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // Освободить память + free(dp); + return res; +} + +/* Driver code */ +int main() { + int wgt[] = {1, 2, 3}; + int val[] = {5, 11, 15}; + int wgtSize = sizeof(wgt) / sizeof(wgt[0]); + int cap = 4; + + // Динамическое программирование + int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_graph/CMakeLists.txt b/ru/codes/c/chapter_graph/CMakeLists.txt new file mode 100644 index 000000000..28f8470f4 --- /dev/null +++ b/ru/codes/c/chapter_graph/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) +add_executable(graph_bfs graph_bfs.c) +add_executable(graph_dfs graph_dfs.c) diff --git a/ru/codes/c/chapter_graph/graph_adjacency_list.c b/ru/codes/c/chapter_graph/graph_adjacency_list.c new file mode 100644 index 000000000..2ee8281a8 --- /dev/null +++ b/ru/codes/c/chapter_graph/graph_adjacency_list.c @@ -0,0 +1,171 @@ +/** + * File: graph_adjacency_list.c + * Created Time: 2023-07-07 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// Предположим, что максимальное число узлов равно 100 +#define MAX_SIZE 100 + +/* Структура узла */ +typedef struct AdjListNode { + Vertex *vertex; // Вершина + struct AdjListNode *next; // Узел-преемник +} AdjListNode; + +/* Класс неориентированного графа на основе списка смежности */ +typedef struct { + AdjListNode *heads[MAX_SIZE]; // Массив узлов + int size; // Количество узлов +} GraphAdjList; + +/* Конструктор */ +GraphAdjList *newGraphAdjList() { + GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); + if (!graph) { + return NULL; + } + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + graph->heads[i] = NULL; + } + return graph; +} + +/* Деструктор */ +void delGraphAdjList(GraphAdjList *graph) { + for (int i = 0; i < graph->size; i++) { + AdjListNode *cur = graph->heads[i]; + while (cur != NULL) { + AdjListNode *next = cur->next; + if (cur != graph->heads[i]) { + free(cur); + } + cur = next; + } + free(graph->heads[i]->vertex); + free(graph->heads[i]); + } + free(graph); +} + +/* Найти узел, соответствующий вершине */ +AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; +} + +/* Вспомогательная функция добавления ребра */ +void addEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); + node->vertex = vet; + // Вставка в голову + node->next = head->next; + head->next = node; +} + +/* Добавление ребра */ +void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL && head1 != head2); + // Добавить ребро vet1 - vet2 + addEdgeHelper(head1, vet2); + addEdgeHelper(head2, vet1); +} + +/* Вспомогательная функция удаления ребра */ +void removeEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *pre = head; + AdjListNode *cur = head->next; + // Искать в связном списке узел, соответствующий vet + while (cur != NULL && cur->vertex != vet) { + pre = cur; + cur = cur->next; + } + if (cur == NULL) + return; + // Удалить из связного списка узел, соответствующий vet + pre->next = cur->next; + // Освободить память + free(cur); +} + +/* Удаление ребра */ +void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL); + // Удалить ребро vet1 - vet2 + removeEdgeHelper(head1, head2->vertex); + removeEdgeHelper(head2, head1->vertex); +} + +/* Добавление вершины */ +void addVertex(GraphAdjList *graph, Vertex *vet) { + assert(graph != NULL && graph->size < MAX_SIZE); + AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); + head->vertex = vet; + head->next = NULL; + // Добавить новый список в список смежности + graph->heads[graph->size++] = head; +} + +/* Удаление вершины */ +void removeVertex(GraphAdjList *graph, Vertex *vet) { + AdjListNode *node = findNode(graph, vet); + assert(node != NULL); + // Удалить из списка смежности список, соответствующий вершине vet + AdjListNode *cur = node, *pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + free(pre); + } + // Обойти списки других вершин и удалить все ребра, содержащие vet + for (int i = 0; i < graph->size; i++) { + cur = graph->heads[i]; + pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + if (cur && cur->vertex == vet) { + pre->next = cur->next; + free(cur); + break; + } + } + } + // Сдвинуть вершины после данной вперед, чтобы заполнить образовавшийся пробел + int i; + for (i = 0; i < graph->size; i++) { + if (graph->heads[i] == node) + break; + } + for (int j = i; j < graph->size - 1; j++) { + graph->heads[j] = graph->heads[j + 1]; + } + graph->size--; + free(vet); +} + +/* Вывести список смежности */ +void printGraph(const GraphAdjList *graph) { + printf("Список смежности =\n"); + for (int i = 0; i < graph->size; ++i) { + AdjListNode *node = graph->heads[i]; + printf("%d: [", node->vertex->val); + node = node->next; + while (node) { + printf("%d, ", node->vertex->val); + node = node->next; + } + printf("]\n"); + } +} diff --git a/ru/codes/c/chapter_graph/graph_adjacency_list_test.c b/ru/codes/c/chapter_graph/graph_adjacency_list_test.c new file mode 100644 index 000000000..7a2a22674 --- /dev/null +++ b/ru/codes/c/chapter_graph/graph_adjacency_list_test.c @@ -0,0 +1,55 @@ +/** + * File: graph_adjacency_list_test.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +/* Driver Code */ +int main() { + int vals[] = {1, 3, 2, 5, 4}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // Добавить все вершины и ребра + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\nПосле инициализации граф имеет вид\n"); + printGraph(graph); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + addEdge(graph, v[0], v[2]); + printf("\nПосле добавления ребра 1-2 граф имеет вид\n"); + printGraph(graph); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + removeEdge(graph, v[0], v[1]); + printf("\nПосле удаления ребра 1-3 граф имеет вид\n"); + printGraph(graph); + + /* Добавление вершины */ + Vertex *v5 = newVertex(6); + addVertex(graph, v5); + printf("\nПосле добавления вершины 6 граф имеет вид\n"); + printGraph(graph); + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + removeVertex(graph, v[1]); + printf("\nПосле удаления вершины 3 граф имеет вид:\n"); + printGraph(graph); + + // Освободить память + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/ru/codes/c/chapter_graph/graph_adjacency_matrix.c b/ru/codes/c/chapter_graph/graph_adjacency_matrix.c new file mode 100644 index 000000000..1ed36a736 --- /dev/null +++ b/ru/codes/c/chapter_graph/graph_adjacency_matrix.c @@ -0,0 +1,150 @@ +/** + * File: graph_adjacency_matrix.c + * Created Time: 2023-07-06 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// Предположить, что максимальное число вершин равно 100 +#define MAX_SIZE 100 + +/* Структура неориентированного графа на основе матрицы смежности */ +typedef struct { + int vertices[MAX_SIZE]; + int adjMat[MAX_SIZE][MAX_SIZE]; + int size; +} GraphAdjMat; + +/* Конструктор */ +GraphAdjMat *newGraphAdjMat() { + GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + for (int j = 0; j < MAX_SIZE; j++) { + graph->adjMat[i][j] = 0; + } + } + return graph; +} + +/* Деструктор */ +void delGraphAdjMat(GraphAdjMat *graph) { + free(graph); +} + +/* Добавление вершины */ +void addVertex(GraphAdjMat *graph, int val) { + if (graph->size == MAX_SIZE) { + fprintf(stderr, "Количество вершин графа уже достигло максимума\n"); + return; + } + // Добавить n-ю вершину и обнулить n-ю строку и столбец + int n = graph->size; + graph->vertices[n] = val; + for (int i = 0; i <= n; i++) { + graph->adjMat[n][i] = graph->adjMat[i][n] = 0; + } + graph->size++; +} + +/* Удаление вершины */ +void removeVertex(GraphAdjMat *graph, int index) { + if (index < 0 || index >= graph->size) { + fprintf(stderr, "индекс вершины выходит за границы\n"); + return; + } + // Удалить вершину с индексом index из списка вершин + for (int i = index; i < graph->size - 1; i++) { + graph->vertices[i] = graph->vertices[i + 1]; + } + // Удалить строку с индексом index из матрицы смежности + for (int i = index; i < graph->size - 1; i++) { + for (int j = 0; j < graph->size; j++) { + graph->adjMat[i][j] = graph->adjMat[i + 1][j]; + } + } + // Удалить столбец с индексом index из матрицы смежности + for (int i = 0; i < graph->size; i++) { + for (int j = index; j < graph->size - 1; j++) { + graph->adjMat[i][j] = graph->adjMat[i][j + 1]; + } + } + graph->size--; +} + +/* Добавление ребра */ +// Параметры i и j соответствуют индексам элементов vertices +void addEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "индексы ребра выходят за границы или совпадают\n"); + return; + } + graph->adjMat[i][j] = 1; + graph->adjMat[j][i] = 1; +} + +/* Удаление ребра */ +// Параметры i и j соответствуют индексам элементов vertices +void removeEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "индексы ребра выходят за границы или совпадают\n"); + return; + } + graph->adjMat[i][j] = 0; + graph->adjMat[j][i] = 0; +} + +/* Вывести матрицу смежности */ +void printGraphAdjMat(GraphAdjMat *graph) { + printf("Список вершин = "); + printArray(graph->vertices, graph->size); + printf("Матрица смежности =\n"); + for (int i = 0; i < graph->size; i++) { + printArray(graph->adjMat[i], graph->size); + } +} + +/* Driver Code */ +int main() { + // Инициализация неориентированного графа + GraphAdjMat *graph = newGraphAdjMat(); + int vertices[] = {1, 3, 2, 5, 4}; + for (int i = 0; i < 5; i++) { + addVertex(graph, vertices[i]); + } + int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + for (int i = 0; i < 6; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\nПосле инициализации граф имеет вид\n"); + printGraphAdjMat(graph); + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + addEdge(graph, 0, 2); + printf("\nПосле добавления ребра 1-2 граф имеет вид\n"); + printGraphAdjMat(graph); + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + removeEdge(graph, 0, 1); + printf("\nПосле удаления ребра 1-3 граф имеет вид\n"); + printGraphAdjMat(graph); + + /* Добавление вершины */ + addVertex(graph, 6); + printf("\nПосле добавления вершины 6 граф имеет вид\n"); + printGraphAdjMat(graph); + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + removeVertex(graph, 1); + printf("\nПосле удаления вершины 3 граф имеет вид\n"); + printGraphAdjMat(graph); + + // Освободить память + delGraphAdjMat(graph); + + return 0; +} diff --git a/ru/codes/c/chapter_graph/graph_bfs.c b/ru/codes/c/chapter_graph/graph_bfs.c new file mode 100644 index 000000000..8a41b29b3 --- /dev/null +++ b/ru/codes/c/chapter_graph/graph_bfs.c @@ -0,0 +1,116 @@ +/** + * File: graph_bfs.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// Предположим, что максимальное число узлов равно 100 +#define MAX_SIZE 100 + +/* Структура очереди узлов */ +typedef struct { + Vertex *vertices[MAX_SIZE]; + int front, rear, size; +} Queue; + +/* Конструктор */ +Queue *newQueue() { + Queue *q = (Queue *)malloc(sizeof(Queue)); + q->front = q->rear = q->size = 0; + return q; +} + +/* Проверка, пуста ли очередь */ +int isEmpty(Queue *q) { + return q->size == 0; +} + +/* Операция добавления в очередь */ +void enqueue(Queue *q, Vertex *vet) { + q->vertices[q->rear] = vet; + q->rear = (q->rear + 1) % MAX_SIZE; + q->size++; +} + +/* Операция извлечения из очереди */ +Vertex *dequeue(Queue *q) { + Vertex *vet = q->vertices[q->front]; + q->front = (q->front + 1) % MAX_SIZE; + q->size--; + return vet; +} + +/* Проверить, была ли вершина уже посещена */ +int isVisited(Vertex **visited, int size, Vertex *vet) { + // Искать узел обходом за O(n) времени + for (int i = 0; i < size; i++) { + if (visited[i] == vet) + return 1; + } + return 0; +} + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { + // Очередь используется для реализации BFS + Queue *queue = newQueue(); + enqueue(queue, startVet); + visited[(*visitedSize)++] = startVet; + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (!isEmpty(queue)) { + Vertex *vet = dequeue(queue); // Извлечь головную вершину из очереди + res[(*resSize)++] = vet; // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // Пропустить уже посещенную вершину + if (!isVisited(visited, *visitedSize, node->vertex)) { + enqueue(queue, node->vertex); // Помещать в очередь только непосещенные вершины + visited[(*visitedSize)++] = node->vertex; // Отметить эту вершину как посещенную + } + node = node->next; + } + } + // Освободить память + free(queue); +} + +/* Driver Code */ +int main() { + // Инициализация неориентированного графа + int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, + {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // Добавить все вершины и ребра + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\nПосле инициализации граф имеет вид\n"); + printGraph(graph); + + // Обход в ширину + // Последовательность обхода вершин + Vertex *res[MAX_SIZE]; + int resSize = 0; + // Используется для записи уже посещенных вершин + Vertex *visited[MAX_SIZE]; + int visitedSize = 0; + graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); + printf("\nПоследовательность вершин при обходе в ширину (BFS)\n"); + printArray(vetsToVals(res, resSize), resSize); + + // Освободить память + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/ru/codes/c/chapter_graph/graph_dfs.c b/ru/codes/c/chapter_graph/graph_dfs.c new file mode 100644 index 000000000..61cb4ad4a --- /dev/null +++ b/ru/codes/c/chapter_graph/graph_dfs.c @@ -0,0 +1,75 @@ +/** + * File: graph_dfs.c + * Created Time: 2023-07-13 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// Предположим, что максимальное число узлов равно 100 +#define MAX_SIZE 100 + +/* Проверить, была ли вершина уже посещена */ +int isVisited(Vertex **res, int size, Vertex *vet) { + // Искать узел обходом за O(n) времени + for (int i = 0; i < size; i++) { + if (res[i] == vet) { + return 1; + } + } + return 0; +} + +/* Вспомогательная функция обхода в глубину */ +void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { + // Отметить посещенную вершину + res[(*resSize)++] = vet; + // Обойти все смежные вершины данной вершины + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // Пропустить уже посещенную вершину + if (!isVisited(res, *resSize, node->vertex)) { + // Рекурсивно обходить смежные вершины + dfs(graph, res, resSize, node->vertex); + } + node = node->next; + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { + dfs(graph, res, resSize, startVet); +} + +/* Driver Code */ +int main() { + // Инициализация неориентированного графа + int vals[] = {0, 1, 2, 3, 4, 5, 6}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // Добавить все вершины и ребра + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\nПосле инициализации граф имеет вид\n"); + printGraph(graph); + + // Обход в глубину + Vertex *res[MAX_SIZE]; + int resSize = 0; + graphDFS(graph, v[0], res, &resSize); + printf("\nПоследовательность вершин при обходе в глубину (DFS)\n"); + printArray(vetsToVals(res, resSize), resSize); + + // Освободить память + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/ru/codes/c/chapter_greedy/CMakeLists.txt b/ru/codes/c/chapter_greedy/CMakeLists.txt new file mode 100644 index 000000000..b8e6ca425 --- /dev/null +++ b/ru/codes/c/chapter_greedy/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(coin_change_greedy coin_change_greedy.c) +add_executable(fractional_knapsack fractional_knapsack.c) +add_executable(max_capacity max_capacity.c) +add_executable(max_product_cutting max_product_cutting.c) + +if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_link_libraries(max_product_cutting m) +endif() diff --git a/ru/codes/c/chapter_greedy/coin_change_greedy.c b/ru/codes/c/chapter_greedy/coin_change_greedy.c new file mode 100644 index 000000000..9cb45bb8e --- /dev/null +++ b/ru/codes/c/chapter_greedy/coin_change_greedy.c @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.c + * Created Time: 2023-09-07 + * Author: lwbaptx (lwbaptx@gmail.com) + */ + +#include "../utils/common.h" + +/* Размен монет: жадный алгоритм */ +int coinChangeGreedy(int *coins, int size, int amt) { + // Предположить, что список coins упорядочен + int i = size - 1; + int count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + int coins1[6] = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins1, 6, amt); + printf("\ncoins = "); + printArray(coins1, 6); + printf("amt = %d\n", amt); + printf("Минимальное количество монет для набора суммы %d = %d\n", amt, res); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + int coins2[3] = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins2, 3, amt); + printf("\ncoins = "); + printArray(coins2, 3); + printf("amt = %d\n", amt); + printf("Минимальное количество монет для набора суммы %d = %d\n", amt, res); + printf("На самом деле минимальное количество равно 3, а именно 20 + 20 + 20\n"); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + int coins3[3] = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins3, 3, amt); + printf("\ncoins = "); + printArray(coins3, 3); + printf("amt = %d\n", amt); + printf("Минимальное количество монет для набора суммы %d = %d\n", amt, res); + printf("На самом деле минимальное количество равно 2, а именно 49 + 49\n"); + + return 0; +} diff --git a/ru/codes/c/chapter_greedy/fractional_knapsack.c b/ru/codes/c/chapter_greedy/fractional_knapsack.c new file mode 100644 index 000000000..1325b2e76 --- /dev/null +++ b/ru/codes/c/chapter_greedy/fractional_knapsack.c @@ -0,0 +1,60 @@ +/** + * File: fractional_knapsack.c + * Created Time: 2023-09-14 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* Предмет */ +typedef struct { + int w; // Вес предмета + int v; // Стоимость предмета +} Item; + +/* Отсортировать по удельной стоимости */ +int sortByValueDensity(const void *a, const void *b) { + Item *t1 = (Item *)a; + Item *t2 = (Item *)b; + return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; +} + +/* Дробный рюкзак: жадный алгоритм */ +float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { + // Создать список предметов с двумя свойствами: вес и стоимость + Item *items = malloc(sizeof(Item) * itemCount); + for (int i = 0; i < itemCount; i++) { + items[i] = (Item){.w = wgt[i], .v = val[i]}; + } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); + // Циклический жадный выбор + float res = 0.0; + for (int i = 0; i < itemCount; i++) { + if (items[i].w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += items[i].v; + cap -= items[i].w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (float)cap / items[i].w * items[i].v; + cap = 0; + break; + } + } + free(items); + return res; +} + +/* Driver Code */ +int main(void) { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int capacity = 50; + + // Жадный алгоритм + float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); + printf("Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна %0.2f\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_greedy/max_capacity.c b/ru/codes/c/chapter_greedy/max_capacity.c new file mode 100644 index 000000000..7300454c6 --- /dev/null +++ b/ru/codes/c/chapter_greedy/max_capacity.c @@ -0,0 +1,49 @@ +/** + * File: max_capacity.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* Найти минимум */ +int myMin(int a, int b) { + return a < b ? a : b; +} +/* Найти максимум */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* Максимальная вместимость: жадный алгоритм */ +int maxCapacity(int ht[], int htLength) { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + int i = 0; + int j = htLength - 1; + // Начальная максимальная вместимость равна 0 + int res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + int capacity = myMin(ht[i], ht[j]) * (j - i); + res = myMax(res, capacity); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main(void) { + int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; + + // Жадный алгоритм + int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); + printf("Максимальная вместимость = %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_greedy/max_product_cutting.c b/ru/codes/c/chapter_greedy/max_product_cutting.c new file mode 100644 index 000000000..19107c18c --- /dev/null +++ b/ru/codes/c/chapter_greedy/max_product_cutting.c @@ -0,0 +1,38 @@ +/** + * File: max_product_cutting.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* Максимальное произведение разрезания: жадный алгоритм */ +int maxProductCutting(int n) { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + int a = n / 3; + int b = n % 3; + if (b == 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // Если остаток равен 2, ничего не делать + return pow(3, a) * 2; + } + // Если остаток равен 0, ничего не делать + return pow(3, a); +} + +/* Driver Code */ +int main(void) { + int n = 58; + // Жадный алгоритм + int res = maxProductCutting(n); + printf("Максимальное произведение после разрезания = %d\n", res); + + return 0; +} diff --git a/ru/codes/c/chapter_hashing/CMakeLists.txt b/ru/codes/c/chapter_hashing/CMakeLists.txt new file mode 100644 index 000000000..9ac951ae4 --- /dev/null +++ b/ru/codes/c/chapter_hashing/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array_hash_map array_hash_map.c) +add_executable(hash_map_chaining hash_map_chaining.c) +add_executable(hash_map_open_addressing hash_map_open_addressing.c) +add_executable(simple_hash simple_hash.c) diff --git a/ru/codes/c/chapter_hashing/array_hash_map.c b/ru/codes/c/chapter_hashing/array_hash_map.c new file mode 100644 index 000000000..cb2c2cdb3 --- /dev/null +++ b/ru/codes/c/chapter_hashing/array_hash_map.c @@ -0,0 +1,215 @@ +/** + * File: array_hash_map.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* Размер хеш-таблицы по умолчанию */ +#define MAX_SIZE 100 + +/* Пара ключ-значение int->string */ +typedef struct { + int key; + char *val; +} Pair; + +/* Набор пар ключ-значение */ +typedef struct { + void *set; + int len; +} MapSet; + +/* Хеш-таблица на основе массива */ +typedef struct { + Pair *buckets[MAX_SIZE]; +} ArrayHashMap; + +/* Конструктор */ +ArrayHashMap *newArrayHashMap() { + ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); + for (int i=0; i < MAX_SIZE; i++) { + hmap->buckets[i] = NULL; + } + return hmap; +} + +/* Деструктор */ +void delArrayHashMap(ArrayHashMap *hmap) { + for (int i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + free(hmap->buckets[i]->val); + free(hmap->buckets[i]); + } + } + free(hmap); +} + +/* Хеш-функция */ +int hashFunc(int key) { + int index = key % MAX_SIZE; + return index; +} + +/* Операция поиска */ +const char *get(const ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + const Pair *Pair = hmap->buckets[index]; + if (Pair == NULL) + return NULL; + return Pair->val; +} + +/* Операция добавления */ +void put(ArrayHashMap *hmap, const int key, const char *val) { + Pair *Pair = malloc(sizeof(Pair)); + Pair->key = key; + Pair->val = malloc(strlen(val) + 1); + strcpy(Pair->val, val); + + int index = hashFunc(key); + hmap->buckets[index] = Pair; +} + +/* Операция удаления */ +void removeItem(ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + free(hmap->buckets[index]->val); + free(hmap->buckets[index]); + hmap->buckets[index] = NULL; +} + +/* Получить все пары ключ-значение */ +void pairSet(ArrayHashMap *hmap, MapSet *set) { + Pair *entries; + int i = 0, index = 0; + int total = 0; + /* Подсчитать число действительных пар ключ-значение */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + entries = malloc(sizeof(Pair) * total); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + entries[index].key = hmap->buckets[i]->key; + entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); + strcpy(entries[index].val, hmap->buckets[i]->val); + index++; + } + } + set->set = entries; + set->len = total; +} + +/* Получить все ключи */ +void keySet(ArrayHashMap *hmap, MapSet *set) { + int *keys; + int i = 0, index = 0; + int total = 0; + /* Подсчитать число действительных пар ключ-значение */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + keys = malloc(total * sizeof(int)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + keys[index] = hmap->buckets[i]->key; + index++; + } + } + set->set = keys; + set->len = total; +} + +/* Получить все значения */ +void valueSet(ArrayHashMap *hmap, MapSet *set) { + char **vals; + int i = 0, index = 0; + int total = 0; + /* Подсчитать число действительных пар ключ-значение */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + vals = malloc(total * sizeof(char *)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + vals[index] = hmap->buckets[i]->val; + index++; + } + } + set->set = vals; + set->len = total; +} + +/* Вывести хеш-таблицу */ +void print(ArrayHashMap *hmap) { + int i; + MapSet set; + pairSet(hmap, &set); + Pair *entries = (Pair *)set.set; + for (i = 0; i < set.len; i++) { + printf("%d -> %s\n", entries[i].key, entries[i].val); + } + free(set.set); +} + +/* Driver Code */ +int main() { + /* Инициализация хеш-таблицы */ + ArrayHashMap *hmap = newArrayHashMap(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + put(hmap, 12836, "Сяо Ха"); + put(hmap, 15937, "Сяо Ло"); + put(hmap, 16750, "Сяо Суань"); + put(hmap, 13276, "Сяо Фа"); + put(hmap, 10583, "Сяо Я"); + printf("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n"); + print(hmap); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + const char *name = get(hmap, 15937); + printf("\nДля студенческого номера 15937 найдено имя %s\n", name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + removeItem(hmap, 10583); + printf("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение\n"); + print(hmap); + + /* Обход хеш-таблицы */ + int i; + + printf("\nОтдельный обход пар ключ-значение\n"); + print(hmap); + + MapSet set; + + keySet(hmap, &set); + int *keys = (int *)set.set; + printf("\nОтдельный обход ключей\n"); + for (i = 0; i < set.len; i++) { + printf("%d\n", keys[i]); + } + free(set.set); + + valueSet(hmap, &set); + char **vals = (char **)set.set; + printf("\nОбход только значений Value\n"); + for (i = 0; i < set.len; i++) { + printf("%s\n", vals[i]); + } + free(set.set); + + delArrayHashMap(hmap); + return 0; +} diff --git a/ru/codes/c/chapter_hashing/hash_map_chaining.c b/ru/codes/c/chapter_hashing/hash_map_chaining.c new file mode 100644 index 000000000..6ff3702fc --- /dev/null +++ b/ru/codes/c/chapter_hashing/hash_map_chaining.c @@ -0,0 +1,213 @@ +/** + * File: hash_map_chaining.c + * Created Time: 2023-10-13 + * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) + */ + +#include +#include +#include + +// Предположить, что максимальная длина val равна 100 +#define MAX_SIZE 100 + +/* Пара ключ-значение */ +typedef struct { + int key; + char val[MAX_SIZE]; +} Pair; + +/* Узел связного списка */ +typedef struct Node { + Pair *pair; + struct Node *next; +} Node; + +/* Хеш-таблица с цепочками */ +typedef struct { + int size; // Число пар ключ-значение + int capacity; // Вместимость хеш-таблицы + double loadThres; // Порог коэффициента загрузки для запуска расширения + int extendRatio; // Коэффициент расширения + Node **buckets; // Массив корзин +} HashMapChaining; + +/* Конструктор */ +HashMapChaining *newHashMapChaining() { + HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + return hashMap; +} + +/* Деструктор */ +void delHashMapChaining(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + while (cur) { + Node *tmp = cur; + cur = cur->next; + free(tmp->pair); + free(tmp); + } + } + free(hashMap->buckets); + free(hashMap); +} + +/* Хеш-функция */ +int hashFunc(HashMapChaining *hashMap, int key) { + return key % hashMap->capacity; +} + +/* Коэффициент загрузки */ +double loadFactor(HashMapChaining *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* Операция поиска */ +char *get(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + // Обойти корзину; если найден key, вернуть соответствующее val + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + return cur->pair->val; + } + cur = cur->next; + } + return ""; // Если key не найден, вернуть пустую строку +} + +/* Операция добавления */ +void put(HashMapChaining *hashMap, int key, const char *val); + +/* Расширить хеш-таблицу */ +void extend(HashMapChaining *hashMap) { + // Временно сохранить исходную хеш-таблицу + int oldCapacity = hashMap->capacity; + Node **oldBuckets = hashMap->buckets; + // Инициализация новой хеш-таблицы после расширения + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + hashMap->size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (int i = 0; i < oldCapacity; i++) { + Node *cur = oldBuckets[i]; + while (cur) { + put(hashMap, cur->pair->key, cur->pair->val); + Node *temp = cur; + cur = cur->next; + // Освободить память + free(temp->pair); + free(temp); + } + } + + free(oldBuckets); +} + +/* Операция добавления */ +void put(HashMapChaining *hashMap, int key, const char *val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + int index = hashFunc(hashMap, key); + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + strcpy(cur->pair->val, val); // Если встретился указанный key, обновить соответствующий val и вернуть + return; + } + cur = cur->next; + } + // Если такого key нет, добавить пару ключ-значение в голову связного списка + Pair *newPair = (Pair *)malloc(sizeof(Pair)); + newPair->key = key; + strcpy(newPair->val, val); + Node *newNode = (Node *)malloc(sizeof(Node)); + newNode->pair = newPair; + newNode->next = hashMap->buckets[index]; + hashMap->buckets[index] = newNode; + hashMap->size++; +} + +/* Операция удаления */ +void removeItem(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + Node *cur = hashMap->buckets[index]; + Node *pre = NULL; + while (cur) { + if (cur->pair->key == key) { + // Удалить из него пару ключ-значение + if (pre) { + pre->next = cur->next; + } else { + hashMap->buckets[index] = cur->next; + } + // Освободить память + free(cur->pair); + free(cur); + hashMap->size--; + return; + } + pre = cur; + cur = cur->next; + } +} + +/* Вывести хеш-таблицу */ +void print(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + printf("["); + while (cur) { + printf("%d -> %s, ", cur->pair->key, cur->pair->val); + cur = cur->next; + } + printf("]\n"); + } +} + +/* Driver Code */ +int main() { + /* Инициализация хеш-таблицы */ + HashMapChaining *hashMap = newHashMapChaining(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + put(hashMap, 12836, "Сяо Ха"); + put(hashMap, 15937, "Сяо Ло"); + put(hashMap, 16750, "Сяо Суань"); + put(hashMap, 13276, "Сяо Фа"); + put(hashMap, 10583, "Сяо Я"); + printf("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n"); + print(hashMap); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + char *name = get(hashMap, 13276); + printf("\nДля студенческого номера 13276 найдено имя %s\n", name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + removeItem(hashMap, 12836); + printf("\nПосле удаления студенческого номера 12836 хеш-таблица имеет вид\nКлюч -> Значение\n"); + print(hashMap); + + /* Освободить память хеш-таблицы */ + delHashMapChaining(hashMap); + + return 0; +} diff --git a/ru/codes/c/chapter_hashing/hash_map_open_addressing.c b/ru/codes/c/chapter_hashing/hash_map_open_addressing.c new file mode 100644 index 000000000..41eba6053 --- /dev/null +++ b/ru/codes/c/chapter_hashing/hash_map_open_addressing.c @@ -0,0 +1,211 @@ +/** + * File: hash_map_open_addressing.c + * Created Time: 2023-10-6 + * Author: lclc6 (w1929522410@163.com) + */ + +#include "../utils/common.h" + +/* Хеш-таблица с открытой адресацией */ +typedef struct { + int key; + char *val; +} Pair; + +/* Хеш-таблица с открытой адресацией */ +typedef struct { + int size; // Число пар ключ-значение + int capacity; // Вместимость хеш-таблицы + double loadThres; // Порог коэффициента загрузки для запуска расширения + int extendRatio; // Коэффициент расширения + Pair **buckets; // Массив корзин + Pair *TOMBSTONE; // Удалить метку +} HashMapOpenAddressing; + +// Объявление функции +void extend(HashMapOpenAddressing *hashMap); + +/* Конструктор */ +HashMapOpenAddressing *newHashMapOpenAddressing() { + HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); + hashMap->TOMBSTONE->key = -1; + hashMap->TOMBSTONE->val = "-1"; + + return hashMap; +} + +/* Деструктор */ +void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + free(pair->val); + free(pair); + } + } + free(hashMap->buckets); + free(hashMap->TOMBSTONE); + free(hashMap); +} + +/* Хеш-функция */ +int hashFunc(HashMapOpenAddressing *hashMap, int key) { + return key % hashMap->capacity; +} + +/* Коэффициент загрузки */ +double loadFactor(HashMapOpenAddressing *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* Найти индекс корзины, соответствующий key */ +int findBucket(HashMapOpenAddressing *hashMap, int key) { + int index = hashFunc(hashMap, key); + int firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (hashMap->buckets[index] != NULL) { + // Если встретился key, вернуть соответствующий индекс корзины + if (hashMap->buckets[index]->key == key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone != -1) { + hashMap->buckets[firstTombstone] = hashMap->buckets[index]; + hashMap->buckets[index] = hashMap->TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % hashMap->capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone == -1 ? index : firstTombstone; +} + +/* Операция поиска */ +char *get(HashMapOpenAddressing *hashMap, int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(hashMap, key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + return hashMap->buckets[index]->val; + } + // Если пары ключ-значение не существует, вернуть пустую строку + return ""; +} + +/* Операция добавления */ +void put(HashMapOpenAddressing *hashMap, int key, char *val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + // Найти индекс корзины, соответствующий key + int index = findBucket(hashMap, key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + free(hashMap->buckets[index]->val); + hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(hashMap->buckets[index]->val, val); + hashMap->buckets[index]->val[strlen(val)] = '\0'; + return; + } + // Если пары ключ-значение нет, добавить ее + Pair *pair = (Pair *)malloc(sizeof(Pair)); + pair->key = key; + pair->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(pair->val, val); + pair->val[strlen(val)] = '\0'; + + hashMap->buckets[index] = pair; + hashMap->size++; +} + +/* Операция удаления */ +void removeItem(HashMapOpenAddressing *hashMap, int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(hashMap, key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + Pair *pair = hashMap->buckets[index]; + free(pair->val); + free(pair); + hashMap->buckets[index] = hashMap->TOMBSTONE; + hashMap->size--; + } +} + +/* Расширить хеш-таблицу */ +void extend(HashMapOpenAddressing *hashMap) { + // Временно сохранить исходную хеш-таблицу + Pair **bucketsTmp = hashMap->buckets; + int oldCapacity = hashMap->capacity; + // Инициализация новой хеш-таблицы после расширения + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (int i = 0; i < oldCapacity; i++) { + Pair *pair = bucketsTmp[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + put(hashMap, pair->key, pair->val); + free(pair->val); + free(pair); + } + } + free(bucketsTmp); +} + +/* Вывести хеш-таблицу */ +void print(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair == NULL) { + printf("NULL\n"); + } else if (pair == hashMap->TOMBSTONE) { + printf("TOMBSTONE\n"); + } else { + printf("%d -> %s\n", pair->key, pair->val); + } + } +} + +/* Driver Code */ +int main() { + // Инициализация хеш-таблицы + HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); + + // Операция добавления + // Добавить пару (key, val) в хеш-таблицу + put(hashmap, 12836, "Сяо Ха"); + put(hashmap, 15937, "Сяо Ло"); + put(hashmap, 16750, "Сяо Суань"); + put(hashmap, 13276, "Сяо Фа"); + put(hashmap, 10583, "Сяо Я"); + printf("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n"); + print(hashmap); + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение val + char *name = get(hashmap, 13276); + printf("\nДля студенческого номера 13276 найдено имя %s\n", name); + + // Операция удаления + // Удалить пару (key, val) из хеш-таблицы + removeItem(hashmap, 16750); + printf("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение\n"); + print(hashmap); + + // Уничтожить хеш-таблицу + delHashMapOpenAddressing(hashmap); + return 0; +} diff --git a/ru/codes/c/chapter_hashing/simple_hash.c b/ru/codes/c/chapter_hashing/simple_hash.c new file mode 100644 index 000000000..d4107c0ef --- /dev/null +++ b/ru/codes/c/chapter_hashing/simple_hash.c @@ -0,0 +1,68 @@ +/** + * File: simple_hash.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Аддитивное хеширование */ +int addHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* Мультипликативное хеширование */ +int mulHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (31 * hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* XOR-хеширование */ +int xorHash(char *key) { + int hash = 0; + const int MODULUS = 1000000007; + + for (int i = 0; i < strlen(key); i++) { + hash ^= (unsigned char)key[i]; + } + return hash & MODULUS; +} + +/* Хеширование с циклическим сдвигом */ +int rotHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; + } + + return (int)hash; +} + +/* Driver Code */ +int main() { + char *key = "Hello Algo"; + + int hash = addHash(key); + printf("Хеш суммы = %d\n", hash); + + hash = mulHash(key); + printf("Хеш произведения = %d\n", hash); + + hash = xorHash(key); + printf("XOR-хеш = %d\n", hash); + + hash = rotHash(key); + printf("Хеш с циклическим сдвигом = %d\n", hash); + + return 0; +} diff --git a/ru/codes/c/chapter_heap/CMakeLists.txt b/ru/codes/c/chapter_heap/CMakeLists.txt new file mode 100644 index 000000000..357ec702c --- /dev/null +++ b/ru/codes/c/chapter_heap/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(my_heap_test my_heap_test.c) +add_executable(top_k top_k.c) diff --git a/ru/codes/c/chapter_heap/my_heap.c b/ru/codes/c/chapter_heap/my_heap.c new file mode 100644 index 000000000..0cf692ce4 --- /dev/null +++ b/ru/codes/c/chapter_heap/my_heap.c @@ -0,0 +1,152 @@ +/** + * File: my_heap.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* Максимальная куча */ +typedef struct { + // size обозначает фактическое число элементов + int size; + // Использовать массив с заранее выделенной памятью, чтобы избежать расширения + int data[MAX_SIZE]; +} MaxHeap; + +// Объявление функции +void siftDown(MaxHeap *maxHeap, int i); +void siftUp(MaxHeap *maxHeap, int i); +int parent(MaxHeap *maxHeap, int i); + +/* Конструктор, строящий кучу по срезу */ +MaxHeap *newMaxHeap(int nums[], int size) { + // Поместить все элементы в кучу + MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); + maxHeap->size = size; + memcpy(maxHeap->data, nums, size * sizeof(int)); + for (int i = parent(maxHeap, size - 1); i >= 0; i--) { + // Выполнить heapify для всех узлов, кроме листовых + siftDown(maxHeap, i); + } + return maxHeap; +} + +/* Деструктор */ +void delMaxHeap(MaxHeap *maxHeap) { + // Освободить память + free(maxHeap); +} + +/* Получить индекс левого дочернего узла */ +int left(MaxHeap *maxHeap, int i) { + return 2 * i + 1; +} + +/* Получить индекс правого дочернего узла */ +int right(MaxHeap *maxHeap, int i) { + return 2 * i + 2; +} + +/* Получить индекс родительского узла */ +int parent(MaxHeap *maxHeap, int i) { + return (i - 1) / 2; // Округление вниз +} + +/* Поменять элементы местами */ +void swap(MaxHeap *maxHeap, int i, int j) { + int temp = maxHeap->data[i]; + maxHeap->data[i] = maxHeap->data[j]; + maxHeap->data[j] = temp; +} + +/* Получение размера кучи */ +int size(MaxHeap *maxHeap) { + return maxHeap->size; +} + +/* Проверка, пуста ли куча */ +int isEmpty(MaxHeap *maxHeap) { + return maxHeap->size == 0; +} + +/* Доступ к элементу на вершине кучи */ +int peek(MaxHeap *maxHeap) { + return maxHeap->data[0]; +} + +/* Добавление элемента в кучу */ +void push(MaxHeap *maxHeap, int val) { + // По умолчанию не следует добавлять так много узлов + if (maxHeap->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // Добавление узла + maxHeap->data[maxHeap->size] = val; + maxHeap->size++; + + // Просеивание снизу вверх + siftUp(maxHeap, maxHeap->size - 1); +} + +/* Извлечение элемента из кучи */ +int pop(MaxHeap *maxHeap) { + // Обработка пустого случая + if (isEmpty(maxHeap)) { + printf("heap is empty!"); + return INT_MAX; + } + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(maxHeap, 0, size(maxHeap) - 1); + // Удаление узла + int val = maxHeap->data[maxHeap->size - 1]; + maxHeap->size--; + // Просеивание сверху вниз + siftDown(maxHeap, 0); + + // Вернуть элемент с вершины кучи + return val; +} + +/* Начиная с узла i, выполнить просеивание сверху вниз */ +void siftDown(MaxHeap *maxHeap, int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как max + int l = left(maxHeap, i); + int r = right(maxHeap, i); + int max = i; + if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { + max = l; + } + if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { + max = r; + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (max == i) { + break; + } + // Поменять два узла местами + swap(maxHeap, i, max); + // Циклическое просеивание вниз + i = max; + } +} + +/* Начиная с узла i, выполнить просеивание снизу вверх */ +void siftUp(MaxHeap *maxHeap, int i) { + while (true) { + // Получение родительского узла для узла i + int p = parent(maxHeap, i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { + break; + } + // Поменять два узла местами + swap(maxHeap, i, p); + // Циклическое просеивание вверх + i = p; + } +} diff --git a/ru/codes/c/chapter_heap/my_heap_test.c b/ru/codes/c/chapter_heap/my_heap_test.c new file mode 100644 index 000000000..670358033 --- /dev/null +++ b/ru/codes/c/chapter_heap/my_heap_test.c @@ -0,0 +1,41 @@ +/** + * File: my_heap_test.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "my_heap.c" + +/* Driver Code */ +int main() { + /* Инициализация кучи */ + // Инициализация максимальной кучи + int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); + printf("После построения кучи из входного массива\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* Получение элемента с вершины кучи */ + printf("\nВерхний элемент кучи = %d\n", peek(maxHeap)); + + /* Добавление элемента в кучу */ + push(maxHeap, 7); + printf("\nПосле добавления элемента 7 в кучу\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* Извлечение элемента с вершины кучи */ + int top = pop(maxHeap); + printf("\nПосле извлечения верхнего элемента %d из кучи\n", top); + printHeap(maxHeap->data, maxHeap->size); + + /* Получение размера кучи */ + printf("\nКоличество элементов в куче = %d\n", size(maxHeap)); + + /* Проверка, пуста ли куча */ + printf("\nПуста ли куча: %d\n", isEmpty(maxHeap)); + + // Освободить память + delMaxHeap(maxHeap); + + return 0; +} diff --git a/ru/codes/c/chapter_heap/top_k.c b/ru/codes/c/chapter_heap/top_k.c new file mode 100644 index 000000000..2f5ce1cd3 --- /dev/null +++ b/ru/codes/c/chapter_heap/top_k.c @@ -0,0 +1,73 @@ +/** + * File: top_k.c + * Created Time: 2023-10-26 + * Author: krahets (krahets163.com) + */ + +#include "my_heap.c" + +/* Добавление элемента в кучу */ +void pushMinHeap(MaxHeap *maxHeap, int val) { + // Инвертировать знак элемента + push(maxHeap, -val); +} + +/* Извлечение элемента из кучи */ +int popMinHeap(MaxHeap *maxHeap) { + // Инвертировать знак элемента + return -pop(maxHeap); +} + +/* Доступ к элементу на вершине кучи */ +int peekMinHeap(MaxHeap *maxHeap) { + // Инвертировать знак элемента + return -peek(maxHeap); +} + +/* Извлечь элементы из кучи */ +int *getMinHeap(MaxHeap *maxHeap) { + // Инвертировать все элементы кучи и записать их в массив res + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; +} + +// Функция поиска k наибольших элементов массива на основе кучи +int *topKHeap(int *nums, int sizeNums, int k) { + // Инициализация минимальной кучи + // Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную + int *empty = (int *)malloc(0); + MaxHeap *maxHeap = newMaxHeap(empty, 0); + // Поместить первые k элементов массива в кучу + for (int i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (int i = k; i < sizeNums; i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + int *res = getMinHeap(maxHeap); + // Освободить память + delMaxHeap(maxHeap); + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 7, 6, 3, 2}; + int k = 3; + int sizeNums = sizeof(nums) / sizeof(nums[0]); + + int *res = topKHeap(nums, sizeNums, k); + printf("Наибольшие %d элементов: ", k); + printArray(res, k); + + free(res); + return 0; +} diff --git a/ru/codes/c/chapter_searching/CMakeLists.txt b/ru/codes/c/chapter_searching/CMakeLists.txt new file mode 100644 index 000000000..7b2a152d5 --- /dev/null +++ b/ru/codes/c/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.c) +add_executable(two_sum two_sum.c) +add_executable(binary_search_edge binary_search_edge.c) +add_executable(binary_search_insertion binary_search_insertion.c) diff --git a/ru/codes/c/chapter_searching/binary_search.c b/ru/codes/c/chapter_searching/binary_search.c new file mode 100644 index 000000000..250cb4e8c --- /dev/null +++ b/ru/codes/c/chapter_searching/binary_search.c @@ -0,0 +1,59 @@ +/** + * File: binary_search.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +int binarySearch(int *nums, int len, int target) { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + int i = 0, j = len - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +int binarySearchLCRO(int *nums, int len, int target) { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + int i = 0, j = len; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) + j = m; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + int index = binarySearch(nums, 10, target); + printf("Индекс целевого элемента 6 = %d\n", index); + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = binarySearchLCRO(nums, 10, target); + printf("Индекс целевого элемента 6 = %d\n", index); + + return 0; +} diff --git a/ru/codes/c/chapter_searching/binary_search_edge.c b/ru/codes/c/chapter_searching/binary_search_edge.c new file mode 100644 index 000000000..71dbef1bb --- /dev/null +++ b/ru/codes/c/chapter_searching/binary_search_edge.c @@ -0,0 +1,67 @@ +/** + * File: binary_search_edge.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Бинарный поиск самого левого target */ +int binarySearchLeftEdge(int *nums, int numSize, int target) { + // Эквивалентно поиску точки вставки target + int i = binarySearchInsertion(nums, numSize, target); + // target не найден, вернуть -1 + if (i == numSize || nums[i] != target) { + return -1; + } + // Найти target и вернуть индекс i + return i; +} + +/* Бинарный поиск самого правого target */ +int binarySearchRightEdge(int *nums, int numSize, int target) { + // Преобразовать задачу в поиск самого левого target + 1 + int i = binarySearchInsertion(nums, numSize, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + int j = i - 1; + // target не найден, вернуть -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Найти target и вернуть индекс j + return j; +} + +/* Driver Code */ +int main() { + // Массив с повторяющимися элементами + int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\nМассив nums = "); + printArray(nums, sizeof(nums) / sizeof(nums[0])); + + // Бинарный поиск левой и правой границы + int targets[] = {6, 7}; + for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { + int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); + printf("Индекс самого левого элемента %d = %d\n", targets[i], index); + index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); + printf("Индекс самого правого элемента %d = %d\n", targets[i], index); + } + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_searching/binary_search_insertion.c b/ru/codes/c/chapter_searching/binary_search_insertion.c new file mode 100644 index 000000000..3d99ba0f7 --- /dev/null +++ b/ru/codes/c/chapter_searching/binary_search_insertion.c @@ -0,0 +1,68 @@ +/** + * File: binary_search_insertion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +int binarySearchInsertionSimple(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Driver Code */ +int main() { + // Массив без повторяющихся элементов + int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + printf("\nМассив nums = "); + printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); + // Бинарный поиск точки вставки + int targets1[] = {6, 9}; + for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { + int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); + printf("Индекс позиции вставки для элемента %d = %d\n", targets1[i], index); + } + + // Массив с повторяющимися элементами + int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\nМассив nums = "); + printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); + // Бинарный поиск точки вставки + int targets2[] = {2, 6, 20}; + for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { + int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); + printf("Индекс позиции вставки для элемента %d = %d\n", targets2[i], index); + } + + return 0; +} diff --git a/ru/codes/c/chapter_searching/two_sum.c b/ru/codes/c/chapter_searching/two_sum.c new file mode 100644 index 000000000..e9b3ce103 --- /dev/null +++ b/ru/codes/c/chapter_searching/two_sum.c @@ -0,0 +1,86 @@ +/** + * File: two_sum.c + * Created Time: 2023-01-19 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Метод 1: полный перебор */ +int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { + for (int i = 0; i < numsSize; ++i) { + for (int j = i + 1; j < numsSize; ++j) { + if (nums[i] + nums[j] == target) { + int *res = malloc(sizeof(int) * 2); + res[0] = i, res[1] = j; + *returnSize = 2; + return res; + } + } + } + *returnSize = 0; + return NULL; +} + +/* Хеш-таблица */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // Реализовано на основе uthash.h +} HashTable; + +/* Поиск в хеш-таблице */ +HashTable *find(HashTable *h, int key) { + HashTable *tmp; + HASH_FIND_INT(h, &key, tmp); + return tmp; +} + +/* Вставка элемента в хеш-таблицу */ +void insert(HashTable **h, int key, int val) { + HashTable *t = find(*h, key); + if (t == NULL) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = key, tmp->val = val; + HASH_ADD_INT(*h, key, tmp); + } else { + t->val = val; + } +} + +/* Метод 2: вспомогательная хеш-таблица */ +int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { + HashTable *hashtable = NULL; + for (int i = 0; i < numsSize; i++) { + HashTable *t = find(hashtable, target - nums[i]); + if (t != NULL) { + int *res = malloc(sizeof(int) * 2); + res[0] = t->val, res[1] = i; + *returnSize = 2; + return res; + } + insert(&hashtable, nums[i], i); + } + *returnSize = 0; + return NULL; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + int nums[] = {2, 7, 11, 15}; + int target = 13; + // ====== Driver Code ====== + int returnSize; + int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); + // Метод 1 + printf("Способ 1: res = "); + printArray(res, returnSize); + + // Метод 2 + res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); + printf("Способ 2: res = "); + printArray(res, returnSize); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_sorting/CMakeLists.txt b/ru/codes/c/chapter_sorting/CMakeLists.txt new file mode 100644 index 000000000..88756b4c9 --- /dev/null +++ b/ru/codes/c/chapter_sorting/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(bubble_sort bubble_sort.c) +add_executable(insertion_sort insertion_sort.c) +add_executable(quick_sort quick_sort.c) +add_executable(counting_sort counting_sort.c) +add_executable(radix_sort radix_sort.c) +add_executable(merge_sort merge_sort.c) +add_executable(heap_sort heap_sort.c) +add_executable(bucket_sort bucket_sort.c) +add_executable(selection_sort selection_sort.c) diff --git a/ru/codes/c/chapter_sorting/bubble_sort.c b/ru/codes/c/chapter_sorting/bubble_sort.c new file mode 100644 index 000000000..f5c48dd80 --- /dev/null +++ b/ru/codes/c/chapter_sorting/bubble_sort.c @@ -0,0 +1,61 @@ +/** + * File: bubble_sort.c + * Created Time: 2022-12-26 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* Пузырьковая сортировка */ +void bubbleSort(int nums[], int size) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = size - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +void bubbleSortWithFlag(int nums[], int size) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = size - 1; i > 0; i--) { + bool flag = false; + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + flag = true; + } + } + if (!flag) + break; + } +} + +/* Driver Code */ +int main() { + int nums[6] = {4, 1, 3, 1, 5, 2}; + printf("После пузырьковой сортировки: "); + bubbleSort(nums, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + + int nums1[6] = {4, 1, 3, 1, 5, 2}; + printf("\nПосле оптимизированной пузырьковой сортировки: "); + bubbleSortWithFlag(nums1, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums1[i]); + } + printf("\n"); + + return 0; +} diff --git a/ru/codes/c/chapter_sorting/bucket_sort.c b/ru/codes/c/chapter_sorting/bucket_sort.c new file mode 100644 index 000000000..2f54d502a --- /dev/null +++ b/ru/codes/c/chapter_sorting/bucket_sort.c @@ -0,0 +1,57 @@ +/** + * File: bucket_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define SIZE 10 + +/* Функция сравнения для qsort */ +int compare(const void *a, const void *b) { + float fa = *(const float *)a; + float fb = *(const float *)b; + return (fa > fb) - (fa < fb); +} + +/* Сортировка корзинами */ +void bucketSort(float nums[], int n) { + int k = n / 2; // Инициализировать k = n/2 корзин + int *sizes = malloc(k * sizeof(int)); // Записать размер каждой корзины + float **buckets = malloc(k * sizeof(float *)); // Массив динамических массивов (корзины) + // Предварительно выделить достаточно места для каждой корзины + for (int i = 0; i < k; ++i) { + buckets[i] = (float *)malloc(n * sizeof(float)); + sizes[i] = 0; + } + // 1. Распределить элементы массива по корзинам + for (int i = 0; i < n; ++i) { + int idx = (int)(nums[i] * k); + buckets[idx][sizes[idx]++] = nums[i]; + } + // 2. Выполнить сортировку внутри каждой корзины + for (int i = 0; i < k; ++i) { + qsort(buckets[i], sizes[i], sizeof(float), compare); + } + // 3. Объединить отсортированные корзины + int idx = 0; + for (int i = 0; i < k; ++i) { + for (int j = 0; j < sizes[i]; ++j) { + nums[idx++] = buckets[i][j]; + } + // Освободить память + free(buckets[i]); + } +} + +/* Driver Code */ +int main() { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums, SIZE); + printf("После сортировки корзинами nums = "); + printArrayFloat(nums, SIZE); + + return 0; +} diff --git a/ru/codes/c/chapter_sorting/counting_sort.c b/ru/codes/c/chapter_sorting/counting_sort.c new file mode 100644 index 000000000..5aba17388 --- /dev/null +++ b/ru/codes/c/chapter_sorting/counting_sort.c @@ -0,0 +1,87 @@ +/** + * File: counting_sort.c + * Created Time: 2023-03-20 + * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +void countingSortNaive(int nums[], int size) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + int *counter = calloc(m + 1, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + // 4. Освободить память + free(counter); +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +void countingSort(int nums[], int size) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + int *counter = calloc(m, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + int *res = malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // Поместить num по соответствующему индексу + counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + memcpy(nums, res, size * sizeof(int)); + // 5. Освободить память + free(res); + free(counter); +} + +/* Driver Code */ +int main() { + int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size = sizeof(nums) / sizeof(int); + countingSortNaive(nums, size); + printf("После сортировки подсчетом (объекты не поддерживаются) nums = "); + printArray(nums, size); + + int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size1 = sizeof(nums1) / sizeof(int); + countingSort(nums1, size1); + printf("После сортировки подсчетом nums1 = "); + printArray(nums1, size1); + + return 0; +} diff --git a/ru/codes/c/chapter_sorting/heap_sort.c b/ru/codes/c/chapter_sorting/heap_sort.c new file mode 100644 index 000000000..c5fe1d5a1 --- /dev/null +++ b/ru/codes/c/chapter_sorting/heap_sort.c @@ -0,0 +1,60 @@ +/** + * File: heap_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +void siftDown(int nums[], int n, int i) { + while (1) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) { + break; + } + // Поменять два узла местами + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // Циклическое просеивание вниз + i = ma; + } +} + +/* Сортировка кучей */ +void heapSort(int nums[], int n) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (int i = n / 2 - 1; i >= 0; --i) { + siftDown(nums, n, i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (int i = n - 1; i > 0; --i) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + heapSort(nums, n); + printf("После сортировки кучей nums = "); + printArray(nums, n); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_sorting/insertion_sort.c b/ru/codes/c/chapter_sorting/insertion_sort.c new file mode 100644 index 000000000..a30f3b1bf --- /dev/null +++ b/ru/codes/c/chapter_sorting/insertion_sort.c @@ -0,0 +1,36 @@ +/** + * File: insertion_sort.c + * Created Time: 2022-12-29 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* Сортировка вставками */ +void insertionSort(int nums[], int size) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (int i = 1; i < size; i++) { + int base = nums[i], j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + // Сдвинуть nums[j] на одну позицию вправо + nums[j + 1] = nums[j]; + j--; + } + // Поместить base в правильную позицию + nums[j + 1] = base; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + insertionSort(nums, 6); + printf("После сортировки вставками nums = "); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + printf("\n"); + + return 0; +} diff --git a/ru/codes/c/chapter_sorting/merge_sort.c b/ru/codes/c/chapter_sorting/merge_sort.c new file mode 100644 index 000000000..274f2a256 --- /dev/null +++ b/ru/codes/c/chapter_sorting/merge_sort.c @@ -0,0 +1,63 @@ +/** + * File: merge_sort.c + * Created Time: 2022-03-21 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* Объединить левый и правый подмассивы */ +void merge(int *nums, int left, int mid, int right) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + int tmpSize = right - left + 1; + int *tmp = (int *)malloc(tmpSize * sizeof(int)); + // Инициализировать начальные индексы левого и правого подмассивов + int i = left, j = mid + 1, k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmpSize; ++k) { + nums[left + k] = tmp[k]; + } + // Освободить память + free(tmp); +} + +/* Сортировка слиянием */ +void mergeSort(int *nums, int left, int right) { + // Условие завершения + if (left >= right) + return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + int mid = left + (right - left) / 2; // Вычислить середину + mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* Сортировка слиянием */ + int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; + int size = sizeof(nums) / sizeof(int); + mergeSort(nums, 0, size - 1); + printf("После сортировки слиянием nums = "); + printArray(nums, size); + + return 0; +} diff --git a/ru/codes/c/chapter_sorting/quick_sort.c b/ru/codes/c/chapter_sorting/quick_sort.c new file mode 100644 index 000000000..289f7c86b --- /dev/null +++ b/ru/codes/c/chapter_sorting/quick_sort.c @@ -0,0 +1,137 @@ +/** + * File: quick_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Обмен элементов */ +void swap(int nums[], int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; +} + +/* Разбиение с опорными указателями */ +int partition(int nums[], int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // Идти справа налево в поисках первого элемента меньше опорного + } + while (i < j && nums[i] <= nums[left]) { + i++; // Идти слева направо в поисках первого элемента больше опорного + } + // Поменять эти два элемента местами + swap(nums, i, j); + } + // Переместить опорный элемент на границу двух подмассивов + swap(nums, i, left); + // Вернуть индекс опорного элемента + return i; +} + +/* Быстрая сортировка */ +void quickSort(int nums[], int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) { + return; + } + // Разбиение с опорными указателями + int pivot = partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); +} + +// Ниже приведена быстрая сортировка с оптимизацией медианой + +/* Выбрать медиану из трех кандидатов */ +int medianThree(int nums[], int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m находится между l и r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l находится между m и r + return right; +} + +/* Разбиение с опорными указателями (медиана трех) */ +int partitionMedian(int nums[], int left, int right) { + // Выбрать медиану из трех кандидатов + int med = medianThree(nums, left, (left + right) / 2, right); + // Переместить медиану в крайний левый элемент массива + swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка (медиана трех) */ +void quickSortMedian(int nums[], int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = partitionMedian(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSortMedian(nums, left, pivot - 1); + quickSortMedian(nums, pivot + 1, right); +} + +// Ниже приведена быстрая сортировка с оптимизацией глубины рекурсии + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +void quickSortTailCall(int nums[], int left, int right) { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + int pivot = partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + // Рекурсивно отсортировать левый подмассив + quickSortTailCall(nums, left, pivot - 1); + // Оставшийся неотсортированный диапазон: [pivot + 1, right] + left = pivot + 1; + } else { + // Рекурсивно отсортировать правый подмассив + quickSortTailCall(nums, pivot + 1, right); + // Оставшийся неотсортированный диапазон: [left, pivot - 1] + right = pivot - 1; + } + } +} + +/* Driver Code */ +int main() { + /* Быстрая сортировка */ + int nums[] = {2, 4, 1, 0, 3, 5}; + int size = sizeof(nums) / sizeof(int); + quickSort(nums, 0, size - 1); + printf("После быстрой сортировки nums = "); + printArray(nums, size); + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + int nums1[] = {2, 4, 1, 0, 3, 5}; + quickSortMedian(nums1, 0, size - 1); + printf("После быстрой сортировки (оптимизация медианным опорным элементом) nums = "); + printArray(nums1, size); + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + int nums2[] = {2, 4, 1, 0, 3, 5}; + quickSortTailCall(nums2, 0, size - 1); + printf("После быстрой сортировки (оптимизация глубины рекурсии) nums = "); + printArray(nums1, size); + + return 0; +} diff --git a/ru/codes/c/chapter_sorting/radix_sort.c b/ru/codes/c/chapter_sorting/radix_sort.c new file mode 100644 index 000000000..994084243 --- /dev/null +++ b/ru/codes/c/chapter_sorting/radix_sort.c @@ -0,0 +1,75 @@ +/** + * File: radix_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +int digit(int num, int exp) { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num / exp) % 10; +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +void countingSortDigit(int nums[], int size, int exp) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + int *counter = (int *)malloc((sizeof(int) * 10)); + memset(counter, 0, sizeof(int) * 10); // Инициализировать нулем для последующего освобождения памяти + // Подсчитать число появлений каждой цифры от 0 до 9 + for (int i = 0; i < size; i++) { + // Получить k-й разряд nums[i], обозначив его как d + int d = digit(nums[i], exp); + // Подсчитать число появлений цифры d + counter[d]++; + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + int *res = (int *)malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (int i = 0; i < size; i++) { + nums[i] = res[i]; + } + // Освободить память + free(res); + free(counter); +} + +/* Поразрядная сортировка */ +void radixSort(int nums[], int size) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + int max = INT32_MIN; + for (int i = 0; i < size; i++) { + if (nums[i] > max) { + max = nums[i]; + } + } + // Проходить разряды от младшего к старшему + for (int exp = 1; max >= exp; exp *= 10) + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, size, exp); +} + +/* Driver Code */ +int main() { + // Поразрядная сортировка + int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + int size = sizeof(nums) / sizeof(int); + radixSort(nums, size); + printf("После поразрядной сортировки nums = "); + printArray(nums, size); +} diff --git a/ru/codes/c/chapter_sorting/selection_sort.c b/ru/codes/c/chapter_sorting/selection_sort.c new file mode 100644 index 000000000..9fb799cb0 --- /dev/null +++ b/ru/codes/c/chapter_sorting/selection_sort.c @@ -0,0 +1,37 @@ +/** + * File: selection_sort.c + * Created Time: 2023-05-31 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Сортировка выбором */ +void selectionSort(int nums[], int n) { + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // Записать индекс минимального элемента + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + selectionSort(nums, n); + + printf("После сортировки выбором nums = "); + printArray(nums, n); + + return 0; +} diff --git a/ru/codes/c/chapter_stack_and_queue/CMakeLists.txt b/ru/codes/c/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 000000000..ed3ba840c --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(array_stack array_stack.c) +add_executable(linkedlist_stack linkedlist_stack.c) +add_executable(array_queue array_queue.c) +add_executable(linkedlist_queue linkedlist_queue.c) +add_executable(array_deque array_deque.c) +add_executable(linkedlist_deque linkedlist_deque.c) diff --git a/ru/codes/c/chapter_stack_and_queue/array_deque.c b/ru/codes/c/chapter_stack_and_queue/array_deque.c new file mode 100644 index 000000000..1e72fc628 --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/array_deque.c @@ -0,0 +1,172 @@ +/** + * File: array_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Двусторонняя очередь на основе кольцевого массива */ +typedef struct { + int *nums; // Массив для хранения элементов очереди + int front; // Указатель head, указывающий на первый элемент очереди + int queSize; // Указатель хвоста, указывающий на позицию после хвоста + int queCapacity; // Вместимость очереди +} ArrayDeque; + +/* Конструктор */ +ArrayDeque *newArrayDeque(int capacity) { + ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); + // Инициализация массива + deque->queCapacity = capacity; + deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); + deque->front = deque->queSize = 0; + return deque; +} + +/* Деструктор */ +void delArrayDeque(ArrayDeque *deque) { + free(deque->nums); + free(deque); +} + +/* Получить вместимость двусторонней очереди */ +int capacity(ArrayDeque *deque) { + return deque->queCapacity; +} + +/* Получение длины двусторонней очереди */ +int size(ArrayDeque *deque) { + return deque->queSize; +} + +/* Проверка, пуста ли двусторонняя очередь */ +bool empty(ArrayDeque *deque) { + return deque->queSize == 0; +} + +/* Вычислить индекс в кольцевом массиве */ +int dequeIndex(ArrayDeque *deque, int i) { + // С помощью операции взятия остатка соединить начало и конец массива + // Когда i выходит за хвост массива, вернуться к началу + // Когда i выходит за голову массива, вернуться к концу + return ((i + capacity(deque)) % capacity(deque)); +} + +/* Добавление в голову очереди */ +void pushFirst(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("Дек заполнен\r\n"); + return; + } + // Указатель головы сместить влево на одну позицию + // С помощью операции взятия остатка реализовать возврат front к хвосту после выхода за начало массива + deque->front = dequeIndex(deque, deque->front - 1); + // Добавить num в голову очереди + deque->nums[deque->front] = num; + deque->queSize++; +} + +/* Добавление в хвост очереди */ +void pushLast(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("Дек заполнен\r\n"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + int rear = dequeIndex(deque, deque->front + deque->queSize); + // Добавить num в хвост очереди + deque->nums[rear] = num; + deque->queSize++; +} + +/* Доступ к элементу в начале очереди */ +int peekFirst(ArrayDeque *deque) { + // Ошибка доступа: двусторонняя очередь пуста + assert(empty(deque) == 0); + return deque->nums[deque->front]; +} + +/* Доступ к элементу в конце очереди */ +int peekLast(ArrayDeque *deque) { + // Ошибка доступа: двусторонняя очередь пуста + assert(empty(deque) == 0); + int last = dequeIndex(deque, deque->front + deque->queSize - 1); + return deque->nums[last]; +} + +/* Извлечение из головы очереди */ +int popFirst(ArrayDeque *deque) { + int num = peekFirst(deque); + // Указатель головы сдвигается на одну позицию назад + deque->front = dequeIndex(deque, deque->front + 1); + deque->queSize--; + return num; +} + +/* Извлечение из хвоста очереди */ +int popLast(ArrayDeque *deque) { + int num = peekLast(deque); + deque->queSize--; + return num; +} + +/* Вернуть массив для вывода */ +int *toArray(ArrayDeque *deque, int *queSize) { + *queSize = deque->queSize; + int *res = (int *)calloc(deque->queSize, sizeof(int)); + int j = deque->front; + for (int i = 0; i < deque->queSize; i++) { + res[i] = deque->nums[j % deque->queCapacity]; + j++; + } + return res; +} + +/* Driver Code */ +int main() { + /* Инициализация очереди */ + int capacity = 10; + int queSize; + ArrayDeque *deque = newArrayDeque(capacity); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("Дек deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* Доступ к элементу */ + int peekFirstNum = peekFirst(deque); + printf("Элемент в голове peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("Элемент в хвосте peekLast = %d\r\n", peekLastNum); + + /* Добавление элемента в очередь */ + pushLast(deque, 4); + printf("После вставки элемента 4 в хвост дек = "); + printArray(toArray(deque, &queSize), queSize); + pushFirst(deque, 1); + printf("После вставки элемента 1 в голову дек = "); + printArray(toArray(deque, &queSize), queSize); + + /* Извлечение элемента из очереди */ + int popLastNum = popLast(deque); + printf("Извлечен элемент из хвоста = %d, дек после извлечения из хвоста = ", popLastNum); + printArray(toArray(deque, &queSize), queSize); + int popFirstNum = popFirst(deque); + printf("Извлечен элемент из головы = %d, дек после извлечения из головы = ", popFirstNum); + printArray(toArray(deque, &queSize), queSize); + + /* Получение длины очереди */ + int dequeSize = size(deque); + printf("Длина дека size = %d\r\n", dequeSize); + + /* Проверка, пуста ли очередь */ + bool isEmpty = empty(deque); + printf("Пуста ли очередь = %s\r\n", isEmpty ? "true" : "false"); + + // Освободить память + delArrayDeque(deque); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_stack_and_queue/array_queue.c b/ru/codes/c/chapter_stack_and_queue/array_queue.c new file mode 100644 index 000000000..00e05c0b3 --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/array_queue.c @@ -0,0 +1,134 @@ +/** + * File: array_queue.c + * Created Time: 2023-01-28 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Очередь на основе кольцевого массива */ +typedef struct { + int *nums; // Массив для хранения элементов очереди + int front; // Указатель head, указывающий на первый элемент очереди + int queSize; // Указатель хвоста, указывающий на позицию после хвоста + int queCapacity; // Вместимость очереди +} ArrayQueue; + +/* Конструктор */ +ArrayQueue *newArrayQueue(int capacity) { + ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); + // Инициализация массива + queue->queCapacity = capacity; + queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); + queue->front = queue->queSize = 0; + return queue; +} + +/* Деструктор */ +void delArrayQueue(ArrayQueue *queue) { + free(queue->nums); + free(queue); +} + +/* Получить вместимость очереди */ +int capacity(ArrayQueue *queue) { + return queue->queCapacity; +} + +/* Получение длины очереди */ +int size(ArrayQueue *queue) { + return queue->queSize; +} + +/* Проверка, пуста ли очередь */ +bool empty(ArrayQueue *queue) { + return queue->queSize == 0; +} + +/* Доступ к элементу в начале очереди */ +int peek(ArrayQueue *queue) { + assert(size(queue) != 0); + return queue->nums[queue->front]; +} + +/* Поместить в очередь */ +void push(ArrayQueue *queue, int num) { + if (size(queue) == capacity(queue)) { + printf("Очередь заполнена\r\n"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + int rear = (queue->front + queue->queSize) % queue->queCapacity; + // Добавить num в хвост очереди + queue->nums[rear] = num; + queue->queSize++; +} + +/* Извлечь из очереди */ +int pop(ArrayQueue *queue) { + int num = peek(queue); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + queue->front = (queue->front + 1) % queue->queCapacity; + queue->queSize--; + return num; +} + +/* Вернуть массив для вывода */ +int *toArray(ArrayQueue *queue, int *queSize) { + *queSize = queue->queSize; + int *res = (int *)calloc(queue->queSize, sizeof(int)); + int j = queue->front; + for (int i = 0; i < queue->queSize; i++) { + res[i] = queue->nums[j % queue->queCapacity]; + j++; + } + return res; +} + +/* Driver Code */ +int main() { + /* Инициализация очереди */ + int capacity = 10; + int queSize; + ArrayQueue *queue = newArrayQueue(capacity); + + /* Добавление элемента в очередь */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("Очередь queue = "); + printArray(toArray(queue, &queSize), queSize); + + /* Доступ к элементу в начале очереди */ + int peekNum = peek(queue); + printf("Элемент в голове peek = %d\r\n", peekNum); + + /* Извлечение элемента из очереди */ + peekNum = pop(queue); + printf("Извлечен элемент из очереди pop = %d, очередь после извлечения = ", peekNum); + printArray(toArray(queue, &queSize), queSize); + + /* Получение длины очереди */ + int queueSize = size(queue); + printf("Длина очереди size = %d\r\n", queueSize); + + /* Проверка, пуста ли очередь */ + bool isEmpty = empty(queue); + printf("Пуста ли очередь = %s\r\n", isEmpty ? "true" : "false"); + + /* Проверка кольцевого массива */ + for (int i = 0; i < 10; i++) { + push(queue, i); + pop(queue); + printf("После %d-го цикла enqueue + dequeue queue = ", i); + printArray(toArray(queue, &queSize), queSize); + } + + // Освободить память + delArrayQueue(queue); + + return 0; +} \ No newline at end of file diff --git a/ru/codes/c/chapter_stack_and_queue/array_stack.c b/ru/codes/c/chapter_stack_and_queue/array_stack.c new file mode 100644 index 000000000..e11bd22f7 --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/array_stack.c @@ -0,0 +1,103 @@ +/** + * File: array_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* Стек на основе массива */ +typedef struct { + int *data; + int size; +} ArrayStack; + +/* Конструктор */ +ArrayStack *newArrayStack() { + ArrayStack *stack = malloc(sizeof(ArrayStack)); + // Инициализировать большую вместимость, чтобы избежать расширения + stack->data = malloc(sizeof(int) * MAX_SIZE); + stack->size = 0; + return stack; +} + +/* Деструктор */ +void delArrayStack(ArrayStack *stack) { + free(stack->data); + free(stack); +} + +/* Получение длины стека */ +int size(ArrayStack *stack) { + return stack->size; +} + +/* Проверка, пуст ли стек */ +bool isEmpty(ArrayStack *stack) { + return stack->size == 0; +} + +/* Поместить в стек */ +void push(ArrayStack *stack, int num) { + if (stack->size == MAX_SIZE) { + printf("Стек заполнен\n"); + return; + } + stack->data[stack->size] = num; + stack->size++; +} + +/* Доступ к верхнему элементу стека */ +int peek(ArrayStack *stack) { + if (stack->size == 0) { + printf("стек пуст\n"); + return INT_MAX; + } + return stack->data[stack->size - 1]; +} + +/* Извлечь из стека */ +int pop(ArrayStack *stack) { + int val = peek(stack); + stack->size--; + return val; +} + +/* Driver Code */ +int main() { + /* Инициализация стека */ + ArrayStack *stack = newArrayStack(); + + /* Помещение элемента в стек */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + printf("Стек stack = "); + printArray(stack->data, stack->size); + + /* Доступ к верхнему элементу стека */ + int val = peek(stack); + printf("Верхний элемент стека top = %d\n", val); + + /* Извлечение элемента из стека */ + val = pop(stack); + printf("Извлечен элемент из стека pop = %d, стек после извлечения = ", val); + printArray(stack->data, stack->size); + + /* Получение длины стека */ + int size = stack->size; + printf("Длина стека size = %d\n", size); + + /* Проверка на пустоту */ + bool empty = isEmpty(stack); + printf("Пуст ли стек = %s\n", empty ? "true" : "false"); + + // Освободить память + delArrayStack(stack); + + return 0; +} diff --git a/ru/codes/c/chapter_stack_and_queue/linkedlist_deque.c b/ru/codes/c/chapter_stack_and_queue/linkedlist_deque.c new file mode 100644 index 000000000..eae7185a1 --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/linkedlist_deque.c @@ -0,0 +1,212 @@ +/** + * File: linkedlist_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Узел двусвязного списка */ +typedef struct DoublyListNode { + int val; // Значение узла + struct DoublyListNode *next; // Узел-преемник + struct DoublyListNode *prev; // Узел-предшественник +} DoublyListNode; + +/* Конструктор */ +DoublyListNode *newDoublyListNode(int num) { + DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; +} + +/* Деструктор */ +void delDoublyListNode(DoublyListNode *node) { + free(node); +} + +/* Двусторонняя очередь на основе двусвязного списка */ +typedef struct { + DoublyListNode *front, *rear; // Головной узел front, хвостовой узел rear + int queSize; // Длина двусторонней очереди +} LinkedListDeque; + +/* Конструктор */ +LinkedListDeque *newLinkedListDeque() { + LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; +} + +/* Деструктор */ +void delLinkedListdeque(LinkedListDeque *deque) { + // Освободить все узлы + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + DoublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // Освободить структуру deque + free(deque); +} + +/* Получение длины очереди */ +int size(LinkedListDeque *deque) { + return deque->queSize; +} + +/* Проверка, пуста ли очередь */ +bool empty(LinkedListDeque *deque) { + return (size(deque) == 0); +} + +/* Поместить в очередь */ +void push(LinkedListDeque *deque, int num, bool isFront) { + DoublyListNode *node = newDoublyListNode(num); + // Если связный список пуст, пусть front и rear оба указывают на node + if (empty(deque)) { + deque->front = deque->rear = node; + } + // Операция добавления в голову очереди + else if (isFront) { + // Добавить node в голову списка + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // Обновить головной узел + } + // Операция добавления в хвост очереди + else { + // Добавить node в хвост списка + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // Обновить длину очереди +} + +/* Добавление в голову очереди */ +void pushFirst(LinkedListDeque *deque, int num) { + push(deque, num, true); +} + +/* Добавление в хвост очереди */ +void pushLast(LinkedListDeque *deque, int num) { + push(deque, num, false); +} + +/* Доступ к элементу в начале очереди */ +int peekFirst(LinkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; +} + +/* Доступ к элементу в конце очереди */ +int peekLast(LinkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; +} + +/* Извлечь из очереди */ +int pop(LinkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // Операция извлечения из головы очереди + if (isFront) { + val = peekFirst(deque); // Временно сохранить значение головного узла + DoublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + } + delDoublyListNode(deque->front); + deque->front = fNext; // Обновить головной узел + } + // Операция извлечения из хвоста очереди + else { + val = peekLast(deque); // Временно сохранить значение хвостового узла + DoublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + } + delDoublyListNode(deque->rear); + deque->rear = rPrev; // Обновить хвостовой узел + } + deque->queSize--; // Обновить длину очереди + return val; +} + +/* Извлечение из головы очереди */ +int popFirst(LinkedListDeque *deque) { + return pop(deque, true); +} + +/* Извлечение из хвоста очереди */ +int popLast(LinkedListDeque *deque) { + return pop(deque, false); +} + +/* Вывести очередь */ +void printLinkedListDeque(LinkedListDeque *deque) { + int *arr = malloc(sizeof(int) * deque->queSize); + // Скопировать данные связного списка в массив + int i; + DoublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* Инициализация двусторонней очереди */ + LinkedListDeque *deque = newLinkedListDeque(); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("Дек deque = "); + printLinkedListDeque(deque); + + /* Доступ к элементу */ + int peekFirstNum = peekFirst(deque); + printf("Элемент в голове peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("Элемент в хвосте peekLast = %d\r\n", peekLastNum); + + /* Добавление элемента в очередь */ + pushLast(deque, 4); + printf("После вставки элемента 4 в хвост дек ="); + printLinkedListDeque(deque); + pushFirst(deque, 1); + printf("После вставки элемента 1 в голову дек ="); + printLinkedListDeque(deque); + + /* Извлечение элемента из очереди */ + int popLastNum = popLast(deque); + printf("Извлечен элемент из хвоста popLast = %d, дек после извлечения из хвоста = ", popLastNum); + printLinkedListDeque(deque); + int popFirstNum = popFirst(deque); + printf("Извлечен элемент из головы popFirst = %d, дек после извлечения из головы = ", popFirstNum); + printLinkedListDeque(deque); + + /* Получение длины очереди */ + int dequeSize = size(deque); + printf("Длина дека size = %d\r\n", dequeSize); + + /* Проверка, пуста ли очередь */ + bool isEmpty = empty(deque); + printf("Пуст ли дек = %s\r\n", isEmpty ? "true" : "false"); + + // Освободить память + delLinkedListdeque(deque); + + return 0; +} diff --git a/ru/codes/c/chapter_stack_and_queue/linkedlist_queue.c b/ru/codes/c/chapter_stack_and_queue/linkedlist_queue.c new file mode 100644 index 000000000..9ba64740f --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/linkedlist_queue.c @@ -0,0 +1,128 @@ +/** + * File: linkedlist_queue.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Очередь на основе связного списка */ +typedef struct { + ListNode *front, *rear; + int queSize; +} LinkedListQueue; + +/* Конструктор */ +LinkedListQueue *newLinkedListQueue() { + LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; +} + +/* Деструктор */ +void delLinkedListQueue(LinkedListQueue *queue) { + // Освободить все узлы + while (queue->front != NULL) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // Освободить структуру queue + free(queue); +} + +/* Получение длины очереди */ +int size(LinkedListQueue *queue) { + return queue->queSize; +} + +/* Проверка, пуста ли очередь */ +bool empty(LinkedListQueue *queue) { + return (size(queue) == 0); +} + +/* Поместить в очередь */ +void push(LinkedListQueue *queue, int num) { + // Добавить node в хвост + ListNode *node = newListNode(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // Если очередь не пуста, добавить этот узел после хвостового узла + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; +} + +/* Доступ к элементу в начале очереди */ +int peek(LinkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; +} + +/* Извлечь из очереди */ +int pop(LinkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + return num; +} + +/* Вывести очередь */ +void printLinkedListQueue(LinkedListQueue *queue) { + int *arr = malloc(sizeof(int) * queue->queSize); + // Скопировать данные связного списка в массив + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* Инициализация очереди */ + LinkedListQueue *queue = newLinkedListQueue(); + + /* Добавление элемента в очередь */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("Очередь queue = "); + printLinkedListQueue(queue); + + /* Доступ к элементу в начале очереди */ + int peekNum = peek(queue); + printf("Элемент в голове peek = %d\r\n", peekNum); + + /* Извлечение элемента из очереди */ + peekNum = pop(queue); + printf("Извлечен элемент из очереди pop = %d, очередь после извлечения = ", peekNum); + printLinkedListQueue(queue); + + /* Получение длины очереди */ + int queueSize = size(queue); + printf("Длина очереди size = %d\r\n", queueSize); + + /* Проверка, пуста ли очередь */ + bool isEmpty = empty(queue); + printf("Пуста ли очередь = %s\r\n", isEmpty ? "true" : "false"); + + // Освободить память + delLinkedListQueue(queue); + + return 0; +} diff --git a/ru/codes/c/chapter_stack_and_queue/linkedlist_stack.c b/ru/codes/c/chapter_stack_and_queue/linkedlist_stack.c new file mode 100644 index 000000000..cba0b13c5 --- /dev/null +++ b/ru/codes/c/chapter_stack_and_queue/linkedlist_stack.c @@ -0,0 +1,107 @@ +/** + * File: linkedlist_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Стек на основе связного списка */ +typedef struct { + ListNode *top; // Использовать головной узел как вершину стека + int size; // Длина стека +} LinkedListStack; + +/* Конструктор */ +LinkedListStack *newLinkedListStack() { + LinkedListStack *s = malloc(sizeof(LinkedListStack)); + s->top = NULL; + s->size = 0; + return s; +} + +/* Деструктор */ +void delLinkedListStack(LinkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); +} + +/* Получение длины стека */ +int size(LinkedListStack *s) { + return s->size; +} + +/* Проверка, пуст ли стек */ +bool isEmpty(LinkedListStack *s) { + return size(s) == 0; +} + +/* Поместить в стек */ +void push(LinkedListStack *s, int num) { + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // Обновить поле указателя нового узла + node->val = num; // Обновить поле данных нового узла + s->top = node; // Обновить вершину стека + s->size++; // Обновить размер стека +} + +/* Доступ к верхнему элементу стека */ +int peek(LinkedListStack *s) { + if (s->size == 0) { + printf("стек пуст\n"); + return INT_MAX; + } + return s->top->val; +} + +/* Извлечь из стека */ +int pop(LinkedListStack *s) { + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // Освободить память + free(tmp); + s->size--; + return val; +} + +/* Driver Code */ +int main() { + /* Инициализация стека */ + LinkedListStack *stack = newLinkedListStack(); + + /* Помещение элемента в стек */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + + printf("Стек stack = "); + printLinkedList(stack->top); + + /* Доступ к верхнему элементу стека */ + int val = peek(stack); + printf("Верхний элемент стека top = %d\r\n", val); + + /* Извлечение элемента из стека */ + val = pop(stack); + printf("Извлечен элемент из стека pop = %d, стек после извлечения = ", val); + printLinkedList(stack->top); + + /* Получение длины стека */ + printf("Длина стека size = %d\n", size(stack)); + + /* Проверка на пустоту */ + bool empty = isEmpty(stack); + printf("Пуст ли стек = %s\n", empty ? "true" : "false"); + + // Освободить память + delLinkedListStack(stack); + + return 0; +} diff --git a/ru/codes/c/chapter_tree/CMakeLists.txt b/ru/codes/c/chapter_tree/CMakeLists.txt new file mode 100644 index 000000000..9b4e825ff --- /dev/null +++ b/ru/codes/c/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.c) +add_executable(binary_tree binary_tree.c) +add_executable(binary_tree_bfs binary_tree_bfs.c) +add_executable(binary_tree_dfs binary_tree_dfs.c) +add_executable(binary_search_tree binary_search_tree.c) +add_executable(array_binary_tree array_binary_tree.c) diff --git a/ru/codes/c/chapter_tree/array_binary_tree.c b/ru/codes/c/chapter_tree/array_binary_tree.c new file mode 100644 index 000000000..04a08b927 --- /dev/null +++ b/ru/codes/c/chapter_tree/array_binary_tree.c @@ -0,0 +1,166 @@ +/** + * File: array_binary_tree.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* Структура двоичного дерева в представлении массивом */ +typedef struct { + int *tree; + int size; +} ArrayBinaryTree; + +/* Конструктор */ +ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { + ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); + abt->tree = malloc(sizeof(int) * arrSize); + memcpy(abt->tree, arr, sizeof(int) * arrSize); + abt->size = arrSize; + return abt; +} + +/* Деструктор */ +void delArrayBinaryTree(ArrayBinaryTree *abt) { + free(abt->tree); + free(abt); +} + +/* Вместимость списка */ +int size(ArrayBinaryTree *abt) { + return abt->size; +} + +/* Получить значение узла с индексом i */ +int val(ArrayBinaryTree *abt, int i) { + // Если индекс выходит за границы, вернуть INT_MAX, обозначающий пустую позицию + if (i < 0 || i >= size(abt)) + return INT_MAX; + return abt->tree[i]; +} + +/* Получить индекс левого дочернего узла узла с индексом i */ +int left(int i) { + return 2 * i + 1; +} + +/* Получить индекс правого дочернего узла узла с индексом i */ +int right(int i) { + return 2 * i + 2; +} + +/* Получить индекс родительского узла узла с индексом i */ +int parent(int i) { + return (i - 1) / 2; +} + +/* Обход в ширину */ +int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + // Непосредственно обходить массив + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) + res[index++] = val(abt, i); + } + *returnSize = index; + return res; +} + +/* Обход в глубину */ +void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { + // Если это пустая позиция, вернуть + if (val(abt, i) == INT_MAX) + return; + // Предварительный обход + if (strcmp(order, "pre") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, left(i), order, res, index); + // Симметричный обход + if (strcmp(order, "in") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, right(i), order, res, index); + // Обратный обход + if (strcmp(order, "post") == 0) + res[(*index)++] = val(abt, i); +} + +/* Предварительный обход */ +int *preOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "pre", res, &index); + *returnSize = index; + return res; +} + +/* Симметричный обход */ +int *inOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "in", res, &index); + *returnSize = index; + return res; +} + +/* Обратный обход */ +int *postOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "post", res, &index); + *returnSize = index; + return res; +} + +/* Driver Code */ +int main() { + // Инициализировать двоичное дерево + // Использовать INT_MAX для обозначения пустой позиции NULL + int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + int arrSize = sizeof(arr) / sizeof(arr[0]); + TreeNode *root = arrayToTree(arr, arrSize); + printf("\nИнициализация двоичного дерева\n"); + printf("Массивное представление двоичного дерева:\n"); + printArray(arr, arrSize); + printf("Связное представление двоичного дерева:\n"); + printTree(root); + + ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); + + // Доступ к узлу + int i = 1; + int l = left(i), r = right(i), p = parent(i); + printf("\nТекущий индекс узла = %d, значение = %d\n", i, val(abt, i)); + printf("Индекс левого дочернего узла = %d, значение = %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); + printf("Индекс правого дочернего узла = %d, значение = %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); + printf("Индекс родительского узла = %d, значение = %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); + + // Обходить дерево + int returnSize; + int *res; + + res = levelOrder(abt, &returnSize); + printf("\nОбход по уровням: "); + printArray(res, returnSize); + free(res); + + res = preOrder(abt, &returnSize); + printf("Предварительный обход: "); + printArray(res, returnSize); + free(res); + + res = inOrder(abt, &returnSize); + printf("Симметричный обход: "); + printArray(res, returnSize); + free(res); + + res = postOrder(abt, &returnSize); + printf("Обратный обход: "); + printArray(res, returnSize); + free(res); + + // Освободить память + delArrayBinaryTree(abt); + return 0; +} diff --git a/ru/codes/c/chapter_tree/avl_tree.c b/ru/codes/c/chapter_tree/avl_tree.c new file mode 100644 index 000000000..1c1e55121 --- /dev/null +++ b/ru/codes/c/chapter_tree/avl_tree.c @@ -0,0 +1,259 @@ +/** + * File: avl_tree.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Структура AVL-дерева */ +typedef struct { + TreeNode *root; +} AVLTree; + +/* Конструктор */ +AVLTree *newAVLTree() { + AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); + tree->root = NULL; + return tree; +} + +/* Деструктор */ +void delAVLTree(AVLTree *tree) { + freeMemoryTree(tree->root); + free(tree); +} + +/* Получить высоту узла */ +int height(TreeNode *node) { + // Высота пустого узла равна -1, высота листового узла равна 0 + if (node != NULL) { + return node->height; + } + return -1; +} + +/* Обновить высоту узла */ +void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // Высота узла равна высоте более высокого поддерева + 1 + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } +} + +/* Получить коэффициент баланса */ +int balanceFactor(TreeNode *node) { + // Коэффициент баланса пустого узла равен 0 + if (node == NULL) { + return 0; + } + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return height(node->left) - height(node->right); +} + +/* Операция правого вращения */ +TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // Выполнить правое вращение узла node вокруг child + child->right = node; + node->left = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; +} + +/* Операция левого вращения */ +TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // Выполнить левое вращение узла node вокруг child + child->left = node; + node->right = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; +} + +/* Выполнить вращение, чтобы снова сбалансировать поддерево */ +TreeNode *rotate(TreeNode *node) { + // Получить коэффициент баланса узла node + int bf = balanceFactor(node); + // Левосторонне перекошенное дерево + if (bf > 1) { + if (balanceFactor(node->left) >= 0) { + // Правое вращение + return rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (bf < -1) { + if (balanceFactor(node->right) <= 0) { + // Левое вращение + return leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; +} + +/* Рекурсивная вставка узла (вспомогательная функция) */ +TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); + } + /* 1. Найти позицию вставки и вставить узел */ + if (val < node->val) { + node->left = insertHelper(node->left, val); + } else if (val > node->val) { + node->right = insertHelper(node->right, val); + } else { + // Повторяющийся узел не вставлять, сразу вернуть + return node; + } + // Обновить высоту узла + updateHeight(node); + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; +} + +/* Вставка узла */ +void insert(AVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); +} + +/* Рекурсивное удаление узла (вспомогательная функция) */ +TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + if (node == NULL) { + return NULL; + } + /* 1. Найти узел и удалить его */ + if (val < node->val) { + node->left = removeHelper(node->left, val); + } else if (val > node->val) { + node->right = removeHelper(node->right, val); + } else { + if (node->left == NULL || node->right == NULL) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == NULL) { + return NULL; + } else { + // Число дочерних узлов = 1, удалить node напрямую + node = child; + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // Обновить высоту узла + updateHeight(node); + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; +} + +/* Удаление узла */ +// Из-за подключения stdio.h здесь нельзя использовать ключевое слово remove +void removeItem(AVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); +} + +/* Поиск узла */ +TreeNode *search(AVLTree *tree, int val) { + TreeNode *cur = tree->root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != NULL) { + if (cur->val < val) { + // Целевой узел находится в правом поддереве cur + cur = cur->right; + } else if (cur->val > val) { + // Целевой узел находится в левом поддереве cur + cur = cur->left; + } else { + // Найти целевой узел и выйти из цикла + break; + } + } + // Найти целевой узел и выйти из цикла + return cur; +} + +void testInsert(AVLTree *tree, int val) { + insert(tree, val); + printf("\nПосле вставки узла %d AVL-дерево имеет вид \n", val); + printTree(tree->root); +} + +void testRemove(AVLTree *tree, int val) { + removeItem(tree, val); + printf("\nПосле удаления узла %d AVL-дерево имеет вид \n", val); + printTree(tree->root); +} + +/* Driver Code */ +int main() { + /* Инициализация пустого AVL-дерева */ + AVLTree *tree = (AVLTree *)newAVLTree(); + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(tree, 1); + testInsert(tree, 2); + testInsert(tree, 3); + testInsert(tree, 4); + testInsert(tree, 5); + testInsert(tree, 8); + testInsert(tree, 7); + testInsert(tree, 9); + testInsert(tree, 10); + testInsert(tree, 6); + + /* Вставка повторяющегося узла */ + testInsert(tree, 7); + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(tree, 8); // Удаление узла степени 0 + testRemove(tree, 5); // Удаление узла степени 1 + testRemove(tree, 4); // Удаление узла степени 2 + + /* Поиск узла */ + TreeNode *node = search(tree, 7); + printf("\nНайденный объект узла, значение узла = %d \n", node->val); + + // Освободить память + delAVLTree(tree); + return 0; +} diff --git a/ru/codes/c/chapter_tree/binary_search_tree.c b/ru/codes/c/chapter_tree/binary_search_tree.c new file mode 100644 index 000000000..d7c7b4158 --- /dev/null +++ b/ru/codes/c/chapter_tree/binary_search_tree.c @@ -0,0 +1,171 @@ +/** + * File: binary_search_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Структура двоичного дерева поиска */ +typedef struct { + TreeNode *root; +} BinarySearchTree; + +/* Конструктор */ +BinarySearchTree *newBinarySearchTree() { + // Инициализировать пустое дерево + BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); + bst->root = NULL; + return bst; +} + +/* Деструктор */ +void delBinarySearchTree(BinarySearchTree *bst) { + freeMemoryTree(bst->root); + free(bst); +} + +/* Получить корневой узел двоичного дерева */ +TreeNode *getRoot(BinarySearchTree *bst) { + return bst->root; +} + +/* Поиск узла */ +TreeNode *search(BinarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != NULL) { + if (cur->val < num) { + // Целевой узел находится в правом поддереве cur + cur = cur->right; + } else if (cur->val > num) { + // Целевой узел находится в левом поддереве cur + cur = cur->left; + } else { + // Найти целевой узел и выйти из цикла + break; + } + } + // Вернуть целевой узел + return cur; +} + +/* Вставка узла */ +void insert(BinarySearchTree *bst, int num) { + // Если дерево пусто, инициализировать корневой узел + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != NULL) { + // Найти повторяющийся узел и сразу вернуть + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // Позиция вставки находится в правом поддереве cur + cur = cur->right; + } else { + // Позиция вставки находится в левом поддереве cur + cur = cur->left; + } + } + // Вставка узла + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } +} + +/* Удаление узла */ +// Из-за подключения stdio.h здесь нельзя использовать ключевое слово remove +void removeItem(BinarySearchTree *bst, int num) { + // Если дерево пусто, сразу вернуть + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != NULL) { + // Найти узел для удаления и выйти из цикла + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // Удаляемый узел находится в правом поддереве root + cur = cur->right; + } else { + // Удаляемый узел находится в левом поддереве root + cur = cur->left; + } + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur == NULL) + return; + // Проверить, есть ли дочерние узлы у удаляемого узла + if (cur->left == NULL || cur->right == NULL) { + /* Число дочерних узлов = 0 или 1 */ + // Когда число дочерних узлов = 0 / 1, child = nullptr / этот дочерний узел + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // Удалить узел cur + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + // Освободить память + free(cur); + } else { + /* Число дочерних узлов = 2 */ + // Получить следующий узел после cur в симметричном обходе + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // Рекурсивно удалить узел tmp + removeItem(bst, tmp->val); + // Перезаписать cur значением tmp + cur->val = tmpVal; + } +} + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева поиска */ + int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + BinarySearchTree *bst = newBinarySearchTree(); + for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { + insert(bst, nums[i]); + } + printf("Инициализированное двоичное дерево\n"); + printTree(getRoot(bst)); + + /* Поиск узла */ + TreeNode *node = search(bst, 7); + printf("Значение найденного объекта узла = %d\n", node->val); + + /* Вставка узла */ + insert(bst, 16); + printf("После вставки узла 16 двоичное дерево имеет вид\n"); + printTree(getRoot(bst)); + + /* Удаление узла */ + removeItem(bst, 1); + printf("После удаления узла 1 двоичное дерево имеет вид\n"); + printTree(getRoot(bst)); + removeItem(bst, 2); + printf("После удаления узла 2 двоичное дерево имеет вид\n"); + printTree(getRoot(bst)); + removeItem(bst, 4); + printf("После удаления узла 4 двоичное дерево имеет вид\n"); + printTree(getRoot(bst)); + + // Освободить память + delBinarySearchTree(bst); + return 0; +} diff --git a/ru/codes/c/chapter_tree/binary_tree.c b/ru/codes/c/chapter_tree/binary_tree.c new file mode 100644 index 000000000..5410c858f --- /dev/null +++ b/ru/codes/c/chapter_tree/binary_tree.c @@ -0,0 +1,43 @@ +/** + * File: binary_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева */ + // Инициализация узла + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // Построить связи между узлами (указатели) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + printf("Инициализация двоичного дерева\n"); + printTree(n1); + + /* Вставка и удаление узлов */ + TreeNode *P = newTreeNode(0); + // Вставить узел P между n1 -> n2 + n1->left = P; + P->left = n2; + printf("После вставки узла P\n"); + printTree(n1); + + // Удалить узел P + n1->left = n2; + // Освободить память + free(P); + printf("После удаления узла P\n"); + printTree(n1); + + freeMemoryTree(n1); + return 0; +} diff --git a/ru/codes/c/chapter_tree/binary_tree_bfs.c b/ru/codes/c/chapter_tree/binary_tree_bfs.c new file mode 100644 index 000000000..cf851844c --- /dev/null +++ b/ru/codes/c/chapter_tree/binary_tree_bfs.c @@ -0,0 +1,73 @@ +/** + * File: binary_tree_bfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* Обход в ширину */ +int *levelOrder(TreeNode *root, int *size) { + /* Вспомогательная очередь */ + int front, rear; + int index, *arr; + TreeNode *node; + TreeNode **queue; + + /* Вспомогательная очередь */ + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); + // Указатель очереди + front = 0, rear = 0; + // Добавить корневой узел + queue[rear++] = root; + // Инициализировать список для хранения последовательности обхода + /* Вспомогательный массив */ + arr = (int *)malloc(sizeof(int) * MAX_SIZE); + // Указатель на массив + index = 0; + while (front < rear) { + // Извлечение из очереди + node = queue[front++]; + // Сохранить значение узла + arr[index++] = node->val; + if (node->left != NULL) { + // Поместить левый дочерний узел в очередь + queue[rear++] = node->left; + } + if (node->right != NULL) { + // Поместить правый дочерний узел в очередь + queue[rear++] = node->right; + } + } + // Обновить значение длины массива + *size = index; + arr = realloc(arr, sizeof(int) * (*size)); + + // Освободить память вспомогательного массива + free(queue); + return arr; +} + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("Инициализация двоичного дерева\n"); + printTree(root); + + /* Обход в ширину */ + // Нужно передать длину массива + int *arr = levelOrder(root, &size); + printf("Последовательность узлов при обходе по уровням = "); + printArray(arr, size); + + // Освободить память + freeMemoryTree(root); + free(arr); + return 0; +} diff --git a/ru/codes/c/chapter_tree/binary_tree_dfs.c b/ru/codes/c/chapter_tree/binary_tree_dfs.c new file mode 100644 index 000000000..fcfe0942b --- /dev/null +++ b/ru/codes/c/chapter_tree/binary_tree_dfs.c @@ -0,0 +1,75 @@ +/** + * File: binary_tree_dfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +// Вспомогательный массив для хранения последовательности обхода +int arr[MAX_SIZE]; + +/* Предварительный обход */ +void preOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + arr[(*size)++] = root->val; + preOrder(root->left, size); + preOrder(root->right, size); +} + +/* Симметричный обход */ +void inOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root->left, size); + arr[(*size)++] = root->val; + inOrder(root->right, size); +} + +/* Обратный обход */ +void postOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root->left, size); + postOrder(root->right, size); + arr[(*size)++] = root->val; +} + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("Инициализация двоичного дерева\n"); + printTree(root); + + /* Предварительный обход */ + // Инициализация вспомогательного массива + size = 0; + preOrder(root, &size); + printf("Последовательность узлов при предварительном обходе = "); + printArray(arr, size); + + /* Симметричный обход */ + size = 0; + inOrder(root, &size); + printf("Последовательность узлов при симметричном обходе = "); + printArray(arr, size); + + /* Обратный обход */ + size = 0; + postOrder(root, &size); + printf("Последовательность узлов при обратном обходе = "); + printArray(arr, size); + + freeMemoryTree(root); + return 0; +} diff --git a/ru/codes/c/utils/CMakeLists.txt b/ru/codes/c/utils/CMakeLists.txt new file mode 100644 index 000000000..c1ece2e38 --- /dev/null +++ b/ru/codes/c/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(utils + common_test.c + common.h print_util.h + list_node.h tree_node.h + uthash.h) \ No newline at end of file diff --git a/ru/codes/c/utils/common.h b/ru/codes/c/utils/common.h new file mode 100644 index 000000000..8b9adeff7 --- /dev/null +++ b/ru/codes/c/utils/common.h @@ -0,0 +1,36 @@ +/** + * File: common.h + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.h" +#include "print_util.h" +#include "tree_node.h" +#include "vertex.h" + +// hash table lib +#include "uthash.h" + +#include "vector.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_H diff --git a/ru/codes/c/utils/common_test.c b/ru/codes/c/utils/common_test.c new file mode 100644 index 000000000..a889b423b --- /dev/null +++ b/ru/codes/c/utils/common_test.c @@ -0,0 +1,35 @@ +/** + * File: include_test.c + * Created Time: 2023-01-10 + * Author: Reanon (793584285@qq.com) + */ + +#include "common.h" + +void testListNode() { + int nums[] = {2, 3, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + ListNode *head = arrToLinkedList(nums, size); + printLinkedList(head); +} + +void testTreeNode() { + int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + + // print tree + printTree(root); + + // tree to arr + int *arr = treeToArray(root, &size); + printArray(arr, size); +} + +int main(int argc, char *argv[]) { + printf("==testListNode==\n"); + testListNode(); + printf("==testTreeNode==\n"); + testTreeNode(); + return 0; +} diff --git a/ru/codes/c/utils/list_node.h b/ru/codes/c/utils/list_node.h new file mode 100644 index 000000000..76775e419 --- /dev/null +++ b/ru/codes/c/utils/list_node.h @@ -0,0 +1,59 @@ +/** + * File: list_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef LIST_NODE_H +#define LIST_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Структура узла связного списка */ +typedef struct ListNode { + int val; // Значение узла + struct ListNode *next; // Ссылка на следующий узел +} ListNode; + +/* Конструктор, инициализирующий новый узел */ +ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *)malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; +} + +/* Десериализовать массив в связный список */ +ListNode *arrToLinkedList(const int *arr, size_t size) { + if (size <= 0) { + return NULL; + } + + ListNode *dummy = newListNode(0); + ListNode *node = dummy; + for (int i = 0; i < size; i++) { + node->next = newListNode(arr[i]); + node = node->next; + } + return dummy->next; +} + +/* Освободить память, выделенную под связный список */ +void freeMemoryLinkedList(ListNode *cur) { + // Освободить память + ListNode *pre; + while (cur != NULL) { + pre = cur; + cur = cur->next; + free(pre); + } +} + +#ifdef __cplusplus +} +#endif + +#endif // LIST_NODE_H diff --git a/ru/codes/c/utils/print_util.h b/ru/codes/c/utils/print_util.h new file mode 100644 index 000000000..fd963dd66 --- /dev/null +++ b/ru/codes/c/utils/print_util.h @@ -0,0 +1,131 @@ +/** + * File: print_util.h + * Created Time: 2022-12-21 + * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) + */ + +#ifndef PRINT_UTIL_H +#define PRINT_UTIL_H + +#include +#include +#include + +#include "list_node.h" +#include "tree_node.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Вывести массив */ +void printArray(int arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%d, ", arr[i]); + } + printf("%d]\n", arr[size - 1]); +} + +/* Вывести массив */ +void printArrayFloat(float arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%.2f, ", arr[i]); + } + printf("%.2f]\n", arr[size - 1]); +} + +/* Вывести связный список */ +void printLinkedList(ListNode *node) { + if (node == NULL) { + return; + } + while (node->next != NULL) { + printf("%d -> ", node->val); + node = node->next; + } + printf("%d\n", node->val); +} + +typedef struct Trunk { + struct Trunk *prev; + char *str; +} Trunk; + +Trunk *newTrunk(Trunk *prev, char *str) { + Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); + trunk->prev = prev; + trunk->str = (char *)malloc(sizeof(char) * 10); + strcpy(trunk->str, str); + return trunk; +} + +void showTrunks(Trunk *trunk) { + if (trunk == NULL) { + return; + } + showTrunks(trunk->prev); + printf("%s", trunk->str); +} + +/** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { + if (node == NULL) { + return; + } + char *prev_str = " "; + Trunk *trunk = newTrunk(prev, prev_str); + printTreeHelper(node->right, trunk, true); + if (prev == NULL) { + trunk->str = "———"; + } else if (isRight) { + trunk->str = "/———"; + prev_str = " |"; + } else { + trunk->str = "\\———"; + prev->str = prev_str; + } + showTrunks(trunk); + printf("%d\n", node->val); + + if (prev != NULL) { + prev->str = prev_str; + } + trunk->str = " |"; + + printTreeHelper(node->left, trunk, false); +} + +/* Вывести двоичное дерево */ +void printTree(TreeNode *root) { + printTreeHelper(root, NULL, false); +} + +/* Вывести кучу */ +void printHeap(int arr[], int size) { + TreeNode *root; + printf("Массивное представление кучи:"); + printArray(arr, size); + printf("Древовидное представление кучи:\n"); + root = arrayToTree(arr, size); + printTree(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // PRINT_UTIL_H diff --git a/ru/codes/c/utils/tree_node.h b/ru/codes/c/utils/tree_node.h new file mode 100644 index 000000000..32b9e1c06 --- /dev/null +++ b/ru/codes/c/utils/tree_node.h @@ -0,0 +1,107 @@ +/** + * File: tree_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef TREE_NODE_H +#define TREE_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define MAX_NODE_SIZE 5000 + +/* Структура узла двоичного дерева */ +typedef struct TreeNode { + int val; // Значение узла + int height; // Высота узла + struct TreeNode *left; // Указатель на левый дочерний узел + struct TreeNode *right; // Указатель на правый дочерний узел +} TreeNode; + +/* Конструктор */ +TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; +} + +// Правила кодирования сериализации см.: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// Массивное представление двоичного дерева: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// Связное представление двоичного дерева: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* Десериализовать список в двоичное дерево: рекурсия */ +TreeNode *arrayToTreeDFS(int *arr, int size, int i) { + if (i < 0 || i >= size || arr[i] == INT_MAX) { + return NULL; + } + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = arr[i]; + root->left = arrayToTreeDFS(arr, size, 2 * i + 1); + root->right = arrayToTreeDFS(arr, size, 2 * i + 2); + return root; +} + +/* Десериализовать список в двоичное дерево */ +TreeNode *arrayToTree(int *arr, int size) { + return arrayToTreeDFS(arr, size, 0); +} + +/* Сериализовать двоичное дерево в список: рекурсия */ +void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { + if (root == NULL) { + return; + } + while (i >= *size) { + res = realloc(res, (*size + 1) * sizeof(int)); + res[*size] = INT_MAX; + (*size)++; + } + res[i] = root->val; + treeToArrayDFS(root->left, 2 * i + 1, res, size); + treeToArrayDFS(root->right, 2 * i + 2, res, size); +} + +/* Сериализовать двоичное дерево в список */ +int *treeToArray(TreeNode *root, int *size) { + *size = 0; + int *res = NULL; + treeToArrayDFS(root, 0, res, size); + return res; +} + +/* Освободить память двоичного дерева */ +void freeMemoryTree(TreeNode *root) { + if (root == NULL) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + free(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_NODE_H diff --git a/ru/codes/c/utils/uthash.h b/ru/codes/c/utils/uthash.h new file mode 100644 index 000000000..68693bf39 --- /dev/null +++ b/ru/codes/c/utils/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/ru/codes/c/utils/vector.h b/ru/codes/c/utils/vector.h new file mode 100644 index 000000000..a022fa6f2 --- /dev/null +++ b/ru/codes/c/utils/vector.h @@ -0,0 +1,259 @@ +/** + * File: vector.h + * Created Time: 2023-07-13 + * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Определить тип вектора */ +typedef struct vector { + int size; // Текущий размер вектора + int capacity; // Текущая емкость вектора + int depth; // Текущая глубина вектора + void **data; // Массив указателей на данные +} vector; + +/* Создать вектор */ +vector *newVector() { + vector *v = malloc(sizeof(vector)); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + return v; +} + +/* Создать вектор, указав размер и значение элементов по умолчанию */ +vector *_newVector(int size, void *elem, int elemSize) { + vector *v = malloc(sizeof(vector)); + v->size = size; + v->capacity = size; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + for (int i = 0; i < size; i++) { + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[i] = tmp; + } + return v; +} + +/* Уничтожить вектор */ +void delVector(vector *v) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + for (int i = 0; i < v->size; i++) { + free(v->data[i]); + } + free(v); + } else { + for (int i = 0; i < v->size; i++) { + delVector(v->data[i]); + } + v->depth--; + } + } +} + +/* Добавить элемент в конец вектора (копированием) */ +void vectorPushback(vector *v, void *elem, int elemSize) { + if (v->size == v->capacity) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[v->size++] = tmp; +} + +/* Извлечь элемент из конца вектора */ +void vectorPopback(vector *v) { + if (v->size != 0) { + free(v->data[v->size - 1]); + v->size--; + } +} + +/* Очистить вектор */ +void vectorClear(vector *v) { + delVector(v); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); +} + +/* Получить размер вектора */ +int vectorSize(vector *v) { + return v->size; +} + +/* Получить последний элемент вектора */ +void *vectorBack(vector *v) { + int n = v->size; + return n > 0 ? v->data[n - 1] : NULL; +} + +/* Получить первый элемент вектора */ +void *vectorFront(vector *v) { + return v->size > 0 ? v->data[0] : NULL; +} + +/* Получить элемент вектора по индексу pos */ +void *vectorAt(vector *v, int pos) { + if (pos < 0 || pos >= v->size) { + printf("vectorAt: out of range\n"); + return NULL; + } + return v->data[pos]; +} + +/* Установить элемент вектора по индексу pos */ +void vectorSet(vector *v, int pos, void *elem, int elemSize) { + if (pos < 0 || pos >= v->size) { + printf("vectorSet: out of range\n"); + return; + } + free(v->data[pos]); + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; +} + +/* Расширение вектора */ +void vectorExpand(vector *v) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* Сжатие вектора */ +void vectorShrink(vector *v) { + v->capacity /= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* Вставить элемент по индексу pos в вектор */ +void vectorInsert(vector *v, int pos, void *elem, int elemSize) { + if (v->size == v->capacity) { + vectorExpand(v); + } + for (int j = v->size; j > pos; j--) { + v->data[j] = v->data[j - 1]; + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; + v->size++; +} + +/* Удалить элемент вектора по индексу pos */ +void vectorErase(vector *v, int pos) { + if (v->size != 0) { + free(v->data[pos]); + for (int j = pos; j < v->size - 1; j++) { + v->data[j] = v->data[j + 1]; + } + v->size--; + } +} + +/* Обмен элементов вектора */ +void vectorSwap(vector *v, int i, int j) { + void *tmp = v->data[i]; + v->data[i] = v->data[j]; + v->data[j] = tmp; +} + +/* Пуст ли вектор */ +bool vectorEmpty(vector *v) { + return v->size == 0; +} + +/* Заполнен ли вектор */ +bool vectorFull(vector *v) { + return v->size == v->capacity; +} + +/* Равны ли векторы */ +bool vectorEqual(vector *v1, vector *v2) { + if (v1->size != v2->size) { + printf("size not equal\n"); + return false; + } + for (int i = 0; i < v1->size; i++) { + void *a = v1->data[i]; + void *b = v2->data[i]; + if (memcmp(a, b, sizeof(a)) != 0) { + printf("data %d not equal\n", i); + return false; + } + } + return true; +} + +/* Отсортировать содержимое вектора */ +void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { + qsort(v->data, v->size, sizeof(void *), cmp); +} + +/* Функция печати: нужно передать функцию для вывода значения переменной */ +/* В настоящее время поддерживается только вывод vector глубины 1 */ +void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + if(v->size == 0) { + printf("\n"); + return; + } + for (int i = 0; i < v->size; i++) { + if (i == 0) { + printf("["); + } else if (i == v->size - 1) { + printFunc(v, v->data[i]); + printf("]\r\n"); + break; + } + printFunc(v, v->data[i]); + printf(","); + } + } else { + for (int i = 0; i < v->size; i++) { + printVector(v->data[i], printFunc); + } + v->depth--; + } + } +} + +/* В настоящее время поддерживается только вывод vector глубины 2 */ +void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { + printf("[\n"); + for (int i = 0; i < vv->size; i++) { + vector *v = (vector *)vv->data[i]; + printf(" ["); + for (int j = 0; j < v->size; j++) { + printFunc(v, v->data[j]); + if (j != v->size - 1) + printf(","); + } + printf("],"); + printf("\n"); + } + printf("]\n"); +} + +#ifdef __cplusplus +} +#endif + +#endif // VECTOR_H diff --git a/ru/codes/c/utils/vertex.h b/ru/codes/c/utils/vertex.h new file mode 100644 index 000000000..28ca1f2a8 --- /dev/null +++ b/ru/codes/c/utils/vertex.h @@ -0,0 +1,49 @@ +/** + * File: vertex.h + * Created Time: 2023-10-28 + * Author: krahets (krahets@163.com) + */ + +#ifndef VERTEX_H +#define VERTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Структура вершины */ +typedef struct { + int val; +} Vertex; + +/* Конструктор, инициализирующий новый узел */ +Vertex *newVertex(int val) { + Vertex *vet; + vet = (Vertex *)malloc(sizeof(Vertex)); + vet->val = val; + return vet; +} + +/* Преобразовать массив значений в массив вершин */ +Vertex **valsToVets(int *vals, int size) { + Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); + for (int i = 0; i < size; ++i) { + vertices[i] = newVertex(vals[i]); + } + return vertices; +} + +/* Преобразовать массив вершин в массив значений */ +int *vetsToVals(Vertex **vertices, int size) { + int *vals = (int *)malloc(size * sizeof(int)); + for (int i = 0; i < size; ++i) { + vals[i] = vertices[i]->val; + } + return vals; +} + +#ifdef __cplusplus +} +#endif + +#endif // VERTEX_H diff --git a/ru/codes/cpp/.gitignore b/ru/codes/cpp/.gitignore new file mode 100644 index 000000000..dc1ffacf4 --- /dev/null +++ b/ru/codes/cpp/.gitignore @@ -0,0 +1,10 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ + +*.dSYM/ + +build/ diff --git a/ru/codes/cpp/CMakeLists.txt b/ru/codes/cpp/CMakeLists.txt new file mode 100644 index 000000000..1e80bc4d7 --- /dev/null +++ b/ru/codes/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/ru/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/ru/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 000000000..2e933e016 --- /dev/null +++ b/ru/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/ru/codes/cpp/chapter_array_and_linkedlist/array.cpp b/ru/codes/cpp/chapter_array_and_linkedlist/array.cpp new file mode 100644 index 000000000..50ca477c8 --- /dev/null +++ b/ru/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -0,0 +1,113 @@ +/** + * File: array.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Случайный доступ к элементу */ +int randomAccess(int *nums, int size) { + // Случайным образом выбрать число из интервала [0, size) + int randomIndex = rand() % size; + // Получить и вернуть случайный элемент + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* Увеличить длину массива */ +int *extend(int *nums, int size, int enlarge) { + // Инициализировать массив увеличенной длины + int *res = new int[size + enlarge]; + // Скопировать все элементы исходного массива в новый массив + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // Освободить память + delete[] nums; + // Вернуть новый массив после расширения + return res; +} + +/* Вставить элемент num по индексу index в массив */ +void insert(int *nums, int size, int num, int index) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; +} + +/* Удалить элемент по индексу index */ +void remove(int *nums, int size, int index) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* Обход массива */ +void traverse(int *nums, int size) { + int count = 0; + // Обход массива по индексам + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* Найти заданный элемент в массиве */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* Инициализация массива */ + int size = 5; + int *arr = new int[size]; + cout << "Массив arr = "; + printArray(arr, size); + + int *nums = new int[size]{1, 3, 2, 5, 4}; + cout << "Массив nums = "; + printArray(nums, size); + + /* Случайный доступ */ + int randomNum = randomAccess(nums, size); + cout << "Случайный элемент из nums = " << randomNum << endl; + + /* Расширение длины */ + int enlarge = 3; + nums = extend(nums, size, enlarge); + size += enlarge; + cout << "После увеличения длины массива до 8 nums = "; + printArray(nums, size); + + /* Вставка элемента */ + insert(nums, size, 6, 3); + cout << "После вставки числа 6 по индексу 3 nums = "; + printArray(nums, size); + + /* Удаление элемента */ + remove(nums, size, 2); + cout << "После удаления элемента по индексу 2 nums = "; + printArray(nums, size); + + /* Обход массива */ + traverse(nums, size); + + /* Поиск элемента */ + int index = find(nums, size, 3); + cout << "Индекс элемента 3 в nums = " << index << endl; + + // Освободить память + delete[] arr; + delete[] nums; + + return 0; +} diff --git a/ru/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/ru/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp new file mode 100644 index 000000000..f0f52242a --- /dev/null +++ b/ru/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -0,0 +1,89 @@ +/** + * File: linked_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Вставить узел P после узла n0 в связном списке */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* Удалить первый узел после узла n0 в связном списке */ +void remove(ListNode *n0) { + if (n0->next == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // Освободить память + delete P; +} + +/* Доступ к узлу связного списка по индексу index */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == nullptr) + return nullptr; + head = head->next; + } + return head; +} + +/* Найти в связном списке первый узел со значением target */ +int find(ListNode *head, int target) { + int index = 0; + while (head != nullptr) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* Инициализация связного списка */ + // Инициализация всех узлов + ListNode *n0 = new ListNode(1); + ListNode *n1 = new ListNode(3); + ListNode *n2 = new ListNode(2); + ListNode *n3 = new ListNode(5); + ListNode *n4 = new ListNode(4); + // Построить ссылки между узлами + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + cout << "Инициализированный связный список" << endl; + printLinkedList(n0); + + /* Вставка узла */ + insert(n0, new ListNode(0)); + cout << "Связный список после вставки узла" << endl; + printLinkedList(n0); + + /* Удаление узла */ + remove(n0); + cout << "Связный список после удаления узла" << endl; + printLinkedList(n0); + + /* Доступ к узлу */ + ListNode *node = access(n0, 3); + cout << "Значение узла по индексу 3 в связном списке = " << node->val << endl; + + /* Поиск узла */ + int index = find(n0, 2); + cout << "Индекс узла со значением 2 в связном списке = " << index << endl; + + // Освободить память + freeMemoryLinkedList(n0); + + return 0; +} diff --git a/ru/codes/cpp/chapter_array_and_linkedlist/list.cpp b/ru/codes/cpp/chapter_array_and_linkedlist/list.cpp new file mode 100644 index 000000000..04c7956eb --- /dev/null +++ b/ru/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -0,0 +1,72 @@ +/** + * File: list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Инициализация списка */ + vector nums = {1, 3, 2, 5, 4}; + cout << "Список nums = "; + printVector(nums); + + /* Доступ к элементу */ + int num = nums[1]; + cout << "Элемент по индексу 1: num = " << num << endl; + + /* Обновление элемента */ + nums[1] = 0; + cout << "После обновления элемента по индексу 1 на 0 nums = "; + printVector(nums); + + /* Очистить список */ + nums.clear(); + cout << "После очистки списка nums = "; + printVector(nums); + + /* Добавление элемента в конец */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "После добавления элемента nums = "; + printVector(nums); + + /* Вставка элемента в середину */ + nums.insert(nums.begin() + 3, 6); + cout << "После вставки числа 6 по индексу 3 nums = "; + printVector(nums); + + /* Удаление элемента */ + nums.erase(nums.begin() + 3); + cout << "После удаления элемента по индексу 3 nums = "; + printVector(nums); + + /* Обходить список по индексам */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + /* Непосредственно обходить элементы списка */ + count = 0; + for (int x : nums) { + count += x; + } + + /* Объединить два списка */ + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "После присоединения списка nums1 к nums nums = "; + printVector(nums); + + /* Отсортировать список */ + sort(nums.begin(), nums.end()); + cout << "После сортировки списка nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/ru/codes/cpp/chapter_array_and_linkedlist/my_list.cpp new file mode 100644 index 000000000..4ced0b491 --- /dev/null +++ b/ru/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -0,0 +1,171 @@ +/** + * File: my_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Класс списка */ +class MyList { + private: + int *arr; // Массив (для хранения элементов списка) + int arrCapacity = 10; // Вместимость списка + int arrSize = 0; // Длина списка (текущее число элементов) + int extendRatio = 2; // Коэффициент увеличения списка при каждом расширении + + public: + /* Конструктор */ + MyList() { + arr = new int[arrCapacity]; + } + + /* Метод-деструктор */ + ~MyList() { + delete[] arr; + } + + /* Получить длину списка (текущее число элементов) */ + int size() { + return arrSize; + } + + /* Получить вместимость списка */ + int capacity() { + return arrCapacity; + } + + /* Доступ к элементу */ + int get(int index) { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 || index >= size()) + throw out_of_range("индекс выходит за границы"); + return arr[index]; + } + + /* Обновление элемента */ + void set(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("индекс выходит за границы"); + arr[index] = num; + } + + /* Добавление элемента в конец */ + void add(int num) { + // При превышении вместимости по числу элементов запускается расширение + if (size() == capacity()) + extendCapacity(); + arr[size()] = num; + // Обновить число элементов + arrSize++; + } + + /* Вставка элемента в середину */ + void insert(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("индекс выходит за границы"); + // При превышении вместимости по числу элементов запускается расширение + if (size() == capacity()) + extendCapacity(); + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (int j = size() - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // Обновить число элементов + arrSize++; + } + + /* Удаление элемента */ + int remove(int index) { + if (index < 0 || index >= size()) + throw out_of_range("индекс выходит за границы"); + int num = arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int j = index; j < size() - 1; j++) { + arr[j] = arr[j + 1]; + } + // Обновить число элементов + arrSize--; + // Вернуть удаленный элемент + return num; + } + + /* Расширение списка */ + void extendCapacity() { + // Создать новый массив длиной в extendRatio раз больше исходного массива + int newCapacity = capacity() * extendRatio; + int *tmp = arr; + arr = new int[newCapacity]; + // Скопировать все элементы исходного массива в новый массив + for (int i = 0; i < size(); i++) { + arr[i] = tmp[i]; + } + // Освободить память + delete[] tmp; + arrCapacity = newCapacity; + } + + /* Преобразовать список в Vector для вывода */ + vector toVector() { + // Преобразовывать только элементы списка в пределах фактической длины + vector vec(size()); + for (int i = 0; i < size(); i++) { + vec[i] = arr[i]; + } + return vec; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация списка */ + MyList *nums = new MyList(); + /* Добавление элемента в конец */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "Список nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "Вместимость = " << nums->capacity() << ", длина = " << nums->size() << endl; + + /* Вставка элемента в середину */ + nums->insert(3, 6); + cout << "После вставки числа 6 по индексу 3 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* Удаление элемента */ + nums->remove(3); + cout << "После удаления элемента по индексу 3 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* Доступ к элементу */ + int num = nums->get(1); + cout << "Элемент по индексу 1: num = " << num << endl; + + /* Обновление элемента */ + nums->set(1, 0); + cout << "После обновления элемента по индексу 1 на 0 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* Проверка механизма расширения */ + for (int i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums->add(i); + } + cout << "После расширения список nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "Вместимость = " << nums->capacity() << ", длина = " << nums->size() << endl; + + // Освободить память + delete nums; + + return 0; +} diff --git a/ru/codes/cpp/chapter_backtracking/CMakeLists.txt b/ru/codes/cpp/chapter_backtracking/CMakeLists.txt new file mode 100644 index 000000000..6c271e330 --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/ru/codes/cpp/chapter_backtracking/n_queens.cpp b/ru/codes/cpp/chapter_backtracking/n_queens.cpp new file mode 100644 index 000000000..d40a8714f --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Алгоритм бэктрекинга: n ферзей */ +void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // Когда все строки уже обработаны, записать решение + if (row == n) { + res.push_back(state); + return; + } + // Обойти все столбцы + for (int col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* Решить задачу о n ферзях */ +vector>> nQueens(int n) { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + vector> state(n, vector(n, "#")); + vector cols(n, false); // Отмечать, есть ли ферзь в столбце + vector diags1(2 * n - 1, false); // Отмечать наличие ферзя на главной диагонали + vector diags2(2 * n - 1, false); // Отмечать наличие ферзя на побочной диагонали + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "Размер входной доски = " << n << endl; + cout << "Количество способов расстановки ферзей: " << res.size() << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_backtracking/permutations_i.cpp b/ru/codes/cpp/chapter_backtracking/permutations_i.cpp new file mode 100644 index 000000000..64b77a5f0 --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Алгоритм бэктрекинга: все перестановки I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.push_back(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop_back(); + } + } +} + +/* Все перестановки I */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "Входной массив nums = "; + printVector(nums); + cout << "Все перестановки res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_backtracking/permutations_ii.cpp b/ru/codes/cpp/chapter_backtracking/permutations_ii.cpp new file mode 100644 index 000000000..d52b6ed47 --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Алгоритм бэктрекинга: все перестановки II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // Перебор всех вариантов выбора + unordered_set duplicated; + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && duplicated.find(choice) == duplicated.end()) { + // Попытка: сделать выбор и обновить состояние + duplicated.emplace(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.push_back(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop_back(); + } + } +} + +/* Все перестановки II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "Входной массив nums = "; + printVector(nums); + cout << "Все перестановки res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/ru/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp new file mode 100644 index 000000000..0e92e7253 --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,39 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* Предварительный обход: пример 1 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // Записать решение + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nИнициализация двоичного дерева" << endl; + printTree(root); + + // Предварительный обход + preOrder(root); + + cout << "\nВывести все узлы со значением 7" << endl; + vector vals; + for (TreeNode *node : res) { + vals.push_back(node->val); + } + printVector(vals); +} diff --git a/ru/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/ru/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp new file mode 100644 index 000000000..12ccd265e --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,46 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* Предварительный обход: пример 2 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // Попытка + path.push_back(root); + if (root->val == 7) { + // Записать решение + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // Откат + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nИнициализация двоичного дерева" << endl; + printTree(root); + + // Предварительный обход + preOrder(root); + + cout << "\nВывести все пути от корня к узлу 7" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 000000000..09e95ec7f --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* Предварительный обход: пример 3 */ +void preOrder(TreeNode *root) { + // Отсечение + if (root == nullptr || root->val == 3) { + return; + } + // Попытка + path.push_back(root); + if (root->val == 7) { + // Записать решение + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // Откат + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nИнициализация двоичного дерева" << endl; + printTree(root); + + // Предварительный обход + preOrder(root); + + cout << "\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 000000000..081c475b9 --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Проверить, является ли текущее состояние решением */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* Записать решение */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* Обновить состояние */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* Восстановить состояние */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* Алгоритм бэктрекинга: пример 3 */ +void backtrack(vector &state, vector &choices, vector> &res) { + // Проверить, является ли текущее состояние решением + if (isSolution(state)) { + // Записать решение + recordSolution(state, res); + } + // Перебор всех вариантов выбора + for (TreeNode *choice : choices) { + // Отсечение: проверить допустимость выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + // Перейти к следующему выбору + vector nextChoices{choice->left, choice->right}; + backtrack(state, nextChoices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\nИнициализация двоичного дерева" << endl; + printTree(root); + + // Алгоритм бэктрекинга + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/ru/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/ru/codes/cpp/chapter_backtracking/subset_sum_i.cpp new file mode 100644 index 000000000..df3d2e91b --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.push_back(state); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (int i = start; i < choices.size(); i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state.push_back(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop_back(); + } +} + +/* Решить задачу суммы подмножеств I */ +vector> subsetSumI(vector &nums, int target) { + vector state; // Состояние (подмножество) + sort(nums.begin(), nums.end()); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + vector> res; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "Входной массив nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "Все подмножества с суммой " << target << ": " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/ru/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp new file mode 100644 index 000000000..2cf0b98aa --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // Если сумма подмножества равна target, записать решение + if (total == target) { + res.push_back(state); + return; + } + // Перебор всех вариантов выбора + for (size_t i = 0; i < choices.size(); i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.push_back(choices[i]); + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop_back(); + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +vector> subsetSumINaive(vector &nums, int target) { + vector state; // Состояние (подмножество) + int total = 0; // Сумма подмножеств + vector> res; // Список результатов (список подмножеств) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "Входной массив nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "Все подмножества с суммой " << target << ": " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/ru/codes/cpp/chapter_backtracking/subset_sum_ii.cpp new file mode 100644 index 000000000..20af16c3e --- /dev/null +++ b/ru/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.push_back(state); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (int i = start; i < choices.size(); i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.push_back(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop_back(); + } +} + +/* Решить задачу суммы подмножеств II */ +vector> subsetSumII(vector &nums, int target) { + vector state; // Состояние (подмножество) + sort(nums.begin(), nums.end()); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + vector> res; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "Входной массив nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "Все подмножества с суммой " << target << ": " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/ru/codes/cpp/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 000000000..ea2845b75 --- /dev/null +++ b/ru/codes/cpp/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_computational_complexity/iteration.cpp b/ru/codes/cpp/chapter_computational_complexity/iteration.cpp new file mode 100644 index 000000000..9f3b0296a --- /dev/null +++ b/ru/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Цикл for */ +int forLoop(int n) { + int res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + res += i; + } + return res; +} + +/* Цикл while */ +int whileLoop(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Обновить условную переменную + } + return res; +} + +/* Цикл while (двойное обновление) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i++; + i *= 2; + } + return res; +} + +/* Двойной цикл for */ +string nestedForLoop(int n) { + ostringstream res; + // Цикл по i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + // Цикл по j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; ++j) { + res << "(" << i << ", " << j << "), "; + } + } + return res.str(); +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nРезультат суммирования в цикле for res = " << res << endl; + + res = whileLoop(n); + cout << "\nРезультат суммирования в цикле while res = " << res << endl; + + res = whileLoopII(n); + cout << "\nРезультат суммирования в цикле while (двойное обновление) res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\nРезультат двойного цикла for " << resStr << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_computational_complexity/recursion.cpp b/ru/codes/cpp/chapter_computational_complexity/recursion.cpp new file mode 100644 index 000000000..e067c9061 --- /dev/null +++ b/ru/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Рекурсия */ +int recur(int n) { + // Условие завершения + if (n == 1) + return 1; + // Рекурсия: рекурсивный вызов + int res = recur(n - 1); + // Возврат: вернуть результат + return n + res; +} + +/* Имитация рекурсии итерацией */ +int forLoopRecur(int n) { + // Использовать явный стек для имитации системного стека вызовов + stack stack; + int res = 0; + // Рекурсия: рекурсивный вызов + for (int i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.push(i); + } + // Возврат: вернуть результат + while (!stack.empty()) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.top(); + stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* Хвостовая рекурсия */ +int tailRecur(int n, int res) { + // Условие завершения + if (n == 0) + return res; + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); +} + +/* Последовательность Фибоначчи: рекурсия */ +int fib(int n) { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\nРезультат суммирования в рекурсивной функции res = " << res << endl; + + res = forLoopRecur(n); + cout << "\nРезультат суммирования с использованием итерации для имитации рекурсии res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\nРезультат суммирования в хвостовой рекурсии res = " << res << endl; + + res = fib(n); + cout << "\nЭлемент последовательности Фибоначчи с индексом " << n << " = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/ru/codes/cpp/chapter_computational_complexity/space_complexity.cpp new file mode 100644 index 000000000..b105792d8 --- /dev/null +++ b/ru/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -0,0 +1,107 @@ +/** + * File: space_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Функция */ +int func() { + // Выполнить некоторые операции + return 0; +} + +/* Постоянная сложность */ +void constant(int n) { + // Константы, переменные и объекты занимают O(1) памяти + const int a = 0; + int b = 0; + vector nums(10000); + ListNode node(0); + // Переменные в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + int c = 0; + } + // Функции в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + func(); + } +} + +/* Линейная сложность */ +void linear(int n) { + // Массив длины n занимает O(n) памяти + vector nums(n); + // Список длины n занимает O(n) памяти + vector nodes; + for (int i = 0; i < n; i++) { + nodes.push_back(ListNode(i)); + } + // Хеш-таблица длины n занимает O(n) памяти + unordered_map map; + for (int i = 0; i < n; i++) { + map[i] = to_string(i); + } +} + +/* Линейная сложность (рекурсивная реализация) */ +void linearRecur(int n) { + cout << "Рекурсия n = " << n << endl; + if (n == 1) + return; + linearRecur(n - 1); +} + +/* Квадратичная сложность */ +void quadratic(int n) { + // Двумерный список занимает O(n^2) памяти + vector> numMatrix; + for (int i = 0; i < n; i++) { + vector tmp; + for (int j = 0; j < n; j++) { + tmp.push_back(0); + } + numMatrix.push_back(tmp); + } +} + +/* Квадратичная сложность (рекурсивная реализация) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + vector nums(n); + cout << "Рекурсия n = " << n << " , длина nums = " << nums.size() << endl; + return quadraticRecur(n - 1); +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // Постоянная сложность + constant(n); + // Линейная сложность + linear(n); + linearRecur(n); + // Квадратичная сложность + quadratic(n); + quadraticRecur(n); + // Экспоненциальная сложность + TreeNode *root = buildTree(n); + printTree(root); + + // Освободить память + freeMemoryTree(root); + + return 0; +} diff --git a/ru/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/ru/codes/cpp/chapter_computational_complexity/time_complexity.cpp new file mode 100644 index 000000000..4ba231343 --- /dev/null +++ b/ru/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -0,0 +1,168 @@ +/** + * File: time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Постоянная сложность */ +int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; +} + +/* Линейная сложность */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; +} + +/* Линейная сложность (обход массива) */ +int arrayTraversal(vector &nums) { + int count = 0; + // Число итераций пропорционально длине массива + for (int num : nums) { + count++; + } + return count; +} + +/* Квадратичная сложность */ +int quadratic(int n) { + int count = 0; + // Число итераций квадратично зависит от размера данных n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +int bubbleSort(vector &nums) { + int count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; +} + +/* Экспоненциальная сложность (итеративная реализация) */ +int exponential(int n) { + int count = 0, base = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* Логарифмическая сложность (итеративная реализация) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* Линейно-логарифмическая сложность */ +int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Факториальная сложность (рекурсивная реализация) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // Из одного получается n + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main() { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + int n = 8; + cout << "Размер входных данных n = " << n << endl; + + int count = constant(n); + cout << "Количество операций постоянной сложности = " << count << endl; + + count = linear(n); + cout << "Количество операций линейной сложности = " << count << endl; + vector arr(n); + count = arrayTraversal(arr); + cout << "Количество операций линейной сложности (обход массива) = " << count << endl; + + count = quadratic(n); + cout << "Количество операций квадратичной сложности = " << count << endl; + vector nums(n); + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + cout << "Количество операций квадратичной сложности (пузырьковая сортировка) = " << count << endl; + + count = exponential(n); + cout << "Количество операций экспоненциальной сложности (итерация) = " << count << endl; + count = expRecur(n); + cout << "Количество операций экспоненциальной сложности (рекурсия) = " << count << endl; + + count = logarithmic(n); + cout << "Количество операций логарифмической сложности (итерация) = " << count << endl; + count = logRecur(n); + cout << "Количество операций логарифмической сложности (рекурсия) = " << count << endl; + + count = linearLogRecur(n); + cout << "Количество операций линейно-логарифмической сложности (рекурсия) = " << count << endl; + + count = factorialRecur(n); + cout << "Количество операций факториальной сложности (рекурсия) = " << count << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/ru/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp new file mode 100644 index 000000000..bfb342011 --- /dev/null +++ b/ru/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +vector randomNumbers(int n) { + vector nums(n); + // Создать массив nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Использовать системное время для генерации случайного seed + unsigned seed = chrono::system_clock::now().time_since_epoch().count(); + // Случайно перемешать элементы массива + shuffle(nums.begin(), nums.end(), default_random_engine(seed)); + return nums; +} + +/* Найти индекс числа 1 в массиве nums */ +int findOne(vector &nums) { + for (int i = 0; i < nums.size(); i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + for (int i = 0; i < 1000; i++) { + int n = 100; + vector nums = randomNumbers(n); + int index = findOne(nums); + cout << "\nПосле перемешивания массива [ 1, 2, ..., n ] nums = "; + printVector(nums); + cout << "Индекс числа 1 = " << index << endl; + } + return 0; +} diff --git a/ru/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/ru/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 000000000..38dfff710 --- /dev/null +++ b/ru/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/ru/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp new file mode 100644 index 000000000..2281bb66b --- /dev/null +++ b/ru/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Бинарный поиск: задача f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + int m = (i + j) / 2; + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } +} + +/* Бинарный поиск */ +int binarySearch(vector &nums, int target) { + int n = nums.size(); + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + // Бинарный поиск (двусторонне замкнутый интервал) + int index = binarySearch(nums, target); + cout << "Индекс целевого элемента 6 = " << index << endl; + + return 0; +} \ No newline at end of file diff --git a/ru/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/ru/codes/cpp/chapter_divide_and_conquer/build_tree.cpp new file mode 100644 index 000000000..f47833d59 --- /dev/null +++ b/ru/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Построить двоичное дерево: разделяй и властвуй */ +TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) + return NULL; + // Инициализировать корневой узел + TreeNode *root = new TreeNode(preorder[i]); + // Найти m, чтобы разделить левое и правое поддеревья + int m = inorderMap[preorder[i]]; + // Подзадача: построить левое поддерево + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + return root; +} + +/* Построить двоичное дерево */ +TreeNode *buildTree(vector &preorder, vector &inorder) { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* Driver Code */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "Предварительный обход = "; + printVector(preorder); + cout << "Симметричный обход = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + cout << "Построенное двоичное дерево:\n"; + printTree(root); + + return 0; +} diff --git a/ru/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/ru/codes/cpp/chapter_divide_and_conquer/hanota.cpp new file mode 100644 index 000000000..15ac1f1ea --- /dev/null +++ b/ru/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Переместить один диск */ +void move(vector &src, vector &tar) { + // Снять диск с вершины src + int pan = src.back(); + src.pop_back(); + // Положить диск на вершину tar + tar.push_back(pan); +} + +/* Решить задачу Ханойской башни f(i) */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // Если в src остался только один диск, сразу переместить его в tar + if (i == 1) { + move(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar); +} + +/* Решить задачу Ханойской башни */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C); +} + +/* Driver Code */ +int main() { + // Хвост списка соответствует вершине столбца + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "Начальное состояние:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "После завершения перемещения дисков:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/ru/codes/cpp/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 000000000..ed185458a --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp new file mode 100644 index 000000000..b3ee775cb --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,43 @@ + +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Бэктрекинг */ +void backtrack(vector &choices, int state, int n, vector &res) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) + res[0]++; + // Перебор всех вариантов выбора + for (auto &choice : choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) + continue; + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +int climbingStairsBacktrack(int n) { + vector choices = {1, 2}; // Можно подняться на 1 или 2 ступени + int state = 0; // Начать подъем с 0-й ступени + vector res = {0}; // Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp new file mode 100644 index 000000000..4afdd1af2 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + vector> dp(n + 1, vector(3, 0)); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp new file mode 100644 index 000000000..f49fc40ea --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Поиск */ +int dfs(int i) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* Подъем по лестнице: поиск */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp new file mode 100644 index 000000000..eeb114042 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Поиск с мемоизацией */ +int dfs(int i, vector &mem) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; +} + +/* Подъем по лестнице: поиск с мемоизацией */ +int climbingStairsDFSMem(int n) { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp new file mode 100644 index 000000000..e3db584a8 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Подъем по лестнице: динамическое программирование */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // Инициализация таблицы dp для хранения решений подзадач + vector dp(n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; + + res = climbingStairsDPComp(n); + cout << "Количество способов подняться по лестнице из " << n << " ступеней: " << res << " вариантов" << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/ru/codes/cpp/chapter_dynamic_programming/coin_change.cpp new file mode 100644 index 000000000..2986dbaf9 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Размен монет: динамическое программирование */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // Инициализация таблицы dp + vector> dp(n + 1, vector(amt + 1, 0)); + // Переход состояний: первая строка и первый столбец + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +int coinChangeDPComp(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // Инициализация таблицы dp + vector dp(amt + 1, MAX); + dp[0] = 0; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // Динамическое программирование + int res = coinChangeDP(coins, amt); + cout << "Минимальное количество монет для целевой суммы = " << res << endl; + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins, amt); + cout << "Минимальное количество монет для целевой суммы = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/ru/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp new file mode 100644 index 000000000..a63e1b740 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Размен монет II: динамическое программирование */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // Инициализация таблицы dp + vector> dp(n + 1, vector(amt + 1, 0)); + // Инициализация первого столбца + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // Инициализация таблицы dp + vector dp(amt + 1, 0); + dp[0] = 1; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // Динамическое программирование + int res = coinChangeIIDP(coins, amt); + cout << "Количество комбинаций монет для набора целевой суммы = " << res << endl; + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(coins, amt); + cout << "Количество комбинаций монет для набора целевой суммы = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/ru/codes/cpp/chapter_dynamic_programming/edit_distance.cpp new file mode 100644 index 000000000..488e999ec --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,136 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Редакционное расстояние: полный перебор */ +int editDistanceDFS(string s, string t, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return min(min(insert, del), replace) + 1; +} + +/* Редакционное расстояние: поиск с мемоизацией */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) + return mem[i][j]; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* Редакционное расстояние: динамическое программирование */ +int editDistanceDP(string s, string t) { + int n = s.length(), m = t.length(); + vector> dp(n + 1, vector(m + 1, 0)); + // Переход состояний: первая строка и первый столбец + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // Переход состояний: первая строка + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (int i = 1; i <= n; i++) { + // Переход состояний: первый столбец + int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; +} + +/* Driver Code */ +int main() { + string s = "bag"; + string t = "pack"; + int n = s.length(), m = t.length(); + + // Полный перебор + int res = editDistanceDFS(s, t, n, m); + cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; + + // Поиск с мемоизацией + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; + + // Динамическое программирование + res = editDistanceDP(s, t); + cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t); + cout << "Чтобы заменить " << s << " на " << t << " , требуется минимум " << res << " операций редактирования\n"; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/ru/codes/cpp/chapter_dynamic_programming/knapsack.cpp new file mode 100644 index 000000000..65ebdab48 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +using namespace std; + +/* Рюкзак 0-1: полный перебор */ +int knapsackDFS(vector &wgt, vector &val, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return max(no, yes); +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* Рюкзак 0-1: динамическое программирование */ +int knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Инициализация таблицы dp + vector> dp(n + 1, vector(cap + 1, 0)); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +int knapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Инициализация таблицы dp + vector dp(cap + 1, 0); + // Переход состояний + for (int i = 1; i <= n; i++) { + // Обход в обратном порядке + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + int n = wgt.size(); + + // Полный перебор + int res = knapsackDFS(wgt, val, n, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + // Поиск с мемоизацией + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + // Динамическое программирование + res = knapsackDP(wgt, val, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt, val, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/ru/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp new file mode 100644 index 000000000..5de0c080b --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // Инициализация таблицы dp для хранения решений подзадач + vector dp(n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +int minCostClimbingStairsDPComp(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + cout << "Список стоимостей ступеней = "; + printVector(cost); + + int res = minCostClimbingStairsDP(cost); + cout << "Минимальная стоимость подъема по лестнице = " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + cout << "Минимальная стоимость подъема по лестнице = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/ru/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp new file mode 100644 index 000000000..d5c0bb534 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,116 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Минимальная сумма пути: полный перебор */ +int minPathSumDFS(vector> &grid, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return INT_MAX; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return INT_MAX; + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* Минимальная сумма пути: динамическое программирование */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // Инициализация таблицы dp + vector> dp(n, vector(m)); + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // Инициализация таблицы dp + vector dp(m); + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (int i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +int main() { + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = grid.size(), m = grid[0].size(); + + // Полный перебор + int res = minPathSumDFS(grid, n - 1, m - 1); + cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; + + // Поиск с мемоизацией + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; + + // Динамическое программирование + res = minPathSumDP(grid); + cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; + + // Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(grid); + cout << "Минимальная сумма пути из левого верхнего в правый нижний угол = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/ru/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp new file mode 100644 index 000000000..f40accb04 --- /dev/null +++ b/ru/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Полный рюкзак: динамическое программирование */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Инициализация таблицы dp + vector> dp(n + 1, vector(cap + 1, 0)); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // Инициализация таблицы dp + vector dp(cap + 1, 0); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver code */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // Динамическое программирование + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_graph/CMakeLists.txt b/ru/codes/cpp/chapter_graph/CMakeLists.txt new file mode 100644 index 000000000..4a56ce35b --- /dev/null +++ b/ru/codes/cpp/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/ru/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/ru/codes/cpp/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 000000000..7a14735fd --- /dev/null +++ b/ru/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Класс неориентированного графа на основе списка смежности */ +class GraphAdjList { + public: + // Список смежности, где key — вершина, а value — все смежные ей вершины + unordered_map> adjList; + + /* Удалить указанный узел из vector */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* Конструктор */ + GraphAdjList(const vector> &edges) { + // Добавить все вершины и ребра + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + int size() { + return adjList.size(); + } + + /* Добавление ребра */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("вершина не существует"); + // Добавить ребро vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* Удаление ребра */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("вершина не существует"); + // Удалить ребро vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* Добавление вершины */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // Добавить новый список в список смежности + adjList[vet] = vector(); + } + + /* Удаление вершины */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("вершина не существует"); + // Удалить из списка смежности список, соответствующий вершине vet + adjList.erase(vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* Вывести список смежности */ + void print() { + cout << "Список смежности =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// Тестовые примеры см. в graph_adjacency_list_test.cpp diff --git a/ru/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/ru/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 000000000..12dd82ca4 --- /dev/null +++ b/ru/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* Инициализация неориентированного графа */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + GraphAdjList graph(edges); + cout << "\nПосле инициализации граф имеет вид" << endl; + graph.print(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(v[0], v[2]); + cout << "\nПосле добавления ребра 1-2 граф имеет вид" << endl; + graph.print(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(v[0], v[1]); + cout << "\nПосле удаления ребра 1-3 граф имеет вид" << endl; + graph.print(); + + /* Добавление вершины */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\nПосле добавления вершины 6 граф имеет вид" << endl; + graph.print(); + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(v[1]); + cout << "\nПосле удаления вершины 3 граф имеет вид" << endl; + graph.print(); + + // Освободить память + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/ru/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 000000000..d524dce66 --- /dev/null +++ b/ru/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + vector vertices; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + vector> adjMat; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + public: + /* Конструктор */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // Добавление вершины + for (int val : vertices) { + addVertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + int size() const { + return vertices.size(); + } + + /* Добавление вершины */ + void addVertex(int val) { + int n = size(); + // Добавить значение новой вершины в список вершин + vertices.push_back(val); + // Добавить строку в матрицу смежности + adjMat.emplace_back(vector(n, 0)); + // Добавить столбец в матрицу смежности + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* Удаление вершины */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("вершина не существует"); + } + // Удалить вершину с индексом index из списка вершин + vertices.erase(vertices.begin() + index); + // Удалить строку с индексом index из матрицы смежности + adjMat.erase(adjMat.begin() + index); + // Удалить столбец с индексом index из матрицы смежности + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + void addEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("вершина не существует"); + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + void removeEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("вершина не существует"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* Вывести матрицу смежности */ + void print() { + cout << "Список вершин = "; + printVector(vertices); + cout << "Матрица смежности =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* Driver Code */ +int main() { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + vector vertices = {1, 3, 2, 5, 4}; + vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + GraphAdjMat graph(vertices, edges); + cout << "\nПосле инициализации граф имеет вид" << endl; + graph.print(); + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.addEdge(0, 2); + cout << "\nПосле добавления ребра 1-2 граф имеет вид" << endl; + graph.print(); + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.removeEdge(0, 1); + cout << "\nПосле удаления ребра 1-3 граф имеет вид" << endl; + graph.print(); + + /* Добавление вершины */ + graph.addVertex(6); + cout << "\nПосле добавления вершины 6 граф имеет вид" << endl; + graph.print(); + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.removeVertex(1); + cout << "\nПосле удаления вершины 3 граф имеет вид" << endl; + graph.print(); + + return 0; +} diff --git a/ru/codes/cpp/chapter_graph/graph_bfs.cpp b/ru/codes/cpp/chapter_graph/graph_bfs.cpp new file mode 100644 index 000000000..ac80cb966 --- /dev/null +++ b/ru/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // Последовательность обхода вершин + vector res; + // Хеш-множество для хранения уже посещенных вершин + unordered_set visited = {startVet}; + // Очередь используется для реализации BFS + queue que; + que.push(startVet); + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // Извлечь головную вершину из очереди + res.push_back(vet); // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // Пропустить уже посещенную вершину + que.push(adjVet); // Помещать в очередь только непосещенные вершины + visited.emplace(adjVet); // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res; +} + +/* Driver Code */ +int main() { + /* Инициализация неориентированного графа */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, + {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, + {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; + GraphAdjList graph(edges); + cout << "\nПосле инициализации граф имеет вид\n"; + graph.print(); + + /* Обход в ширину */ + vector res = graphBFS(graph, v[0]); + cout << "\nПоследовательность вершин при обходе в ширину (BFS)" << endl; + printVector(vetsToVals(res)); + + // Освободить память + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_graph/graph_dfs.cpp b/ru/codes/cpp/chapter_graph/graph_dfs.cpp new file mode 100644 index 000000000..034c00da6 --- /dev/null +++ b/ru/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* Вспомогательная функция обхода в глубину */ +void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // Отметить посещенную вершину + visited.emplace(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // Пропустить уже посещенную вершину + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet); + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // Последовательность обхода вершин + vector res; + // Хеш-множество для хранения уже посещенных вершин + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* Инициализация неориентированного графа */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + GraphAdjList graph(edges); + cout << "\nПосле инициализации граф имеет вид" << endl; + graph.print(); + + /* Обход в глубину */ + vector res = graphDFS(graph, v[0]); + cout << "\nПоследовательность вершин при обходе в глубину (DFS)" << endl; + printVector(vetsToVals(res)); + + // Освободить память + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_greedy/CMakeLists.txt b/ru/codes/cpp/chapter_greedy/CMakeLists.txt new file mode 100644 index 000000000..91788668d --- /dev/null +++ b/ru/codes/cpp/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_greedy/coin_change_greedy.cpp b/ru/codes/cpp/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 000000000..6d34f46cd --- /dev/null +++ b/ru/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Размен монет: жадный алгоритм */ +int coinChangeGreedy(vector &coins, int amt) { + // Предположить, что список coins упорядочен + int i = coins.size() - 1; + int count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + vector coins = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "Минимальное количество монет для набора суммы " << amt << " = " << res << endl; + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "Минимальное количество монет для набора суммы " << amt << " = " << res << endl; + cout << "На самом деле минимальное количество равно 3, а именно 20 + 20 + 20" << endl; + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "Минимальное количество монет для набора суммы " << amt << " = " << res << endl; + cout << "На самом деле минимальное количество равно 2, а именно 49 + 49" << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_greedy/fractional_knapsack.cpp b/ru/codes/cpp/chapter_greedy/fractional_knapsack.cpp new file mode 100644 index 000000000..eca0abe1c --- /dev/null +++ b/ru/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Предмет */ +class Item { + public: + int w; // Вес предмета + int v; // Стоимость предмета + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* Дробный рюкзак: жадный алгоритм */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // Создать список предметов с двумя свойствами: вес и стоимость + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); + // Циклический жадный выбор + double res = 0; + for (auto &item : items) { + if (item.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (double)item.v / item.w * cap; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + + // Жадный алгоритм + double res = fractionalKnapsack(wgt, val, cap); + cout << "Максимальная стоимость предметов, не превышающая вместимость рюкзака, равна " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_greedy/max_capacity.cpp b/ru/codes/cpp/chapter_greedy/max_capacity.cpp new file mode 100644 index 000000000..9bf15ff0f --- /dev/null +++ b/ru/codes/cpp/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Максимальная вместимость: жадный алгоритм */ +int maxCapacity(vector &ht) { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + int i = 0, j = ht.size() - 1; + // Начальная максимальная вместимость равна 0 + int res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // Жадный алгоритм + int res = maxCapacity(ht); + cout << "Максимальная вместимость = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_greedy/max_product_cutting.cpp b/ru/codes/cpp/chapter_greedy/max_product_cutting.cpp new file mode 100644 index 000000000..4adcaae10 --- /dev/null +++ b/ru/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Максимальное произведение разрезания: жадный алгоритм */ +int maxProductCutting(int n) { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + int a = n / 3; + int b = n % 3; + if (b == 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return (int)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // Если остаток равен 2, ничего не делать + return (int)pow(3, a) * 2; + } + // Если остаток равен 0, ничего не делать + return (int)pow(3, a); +} + +/* Driver Code */ +int main() { + int n = 58; + + // Жадный алгоритм + int res = maxProductCutting(n); + cout << "Максимальное произведение после разрезания = " << res << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_hashing/CMakeLists.txt b/ru/codes/cpp/chapter_hashing/CMakeLists.txt new file mode 100644 index 000000000..6b583ef55 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_hashing/array_hash_map.cpp b/ru/codes/cpp/chapter_hashing/array_hash_map.cpp new file mode 100644 index 000000000..30d83a080 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Пара ключ-значение */ +struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } +}; + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + private: + vector buckets; + + public: + ArrayHashMap() { + // Инициализировать массив, содержащий 100 корзин + buckets = vector(100); + } + + ~ArrayHashMap() { + // Освободить память + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); + } + + /* Хеш-функция */ + int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* Операция поиска */ + string get(int key) { + int index = hashFunc(key); + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; + return pair->val; + } + + /* Операция добавления */ + void put(int key, string val) { + Pair *pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* Операция удаления */ + void remove(int key) { + int index = hashFunc(key); + // Освободить память и присвоить nullptr + delete buckets[index]; + buckets[index] = nullptr; + } + + /* Получить все пары ключ-значение */ + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + pairSet.push_back(pair); + } + } + return pairSet; + } + + /* Получить все ключи */ + vector keySet() { + vector keySet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + keySet.push_back(pair->key); + } + } + return keySet; + } + + /* Получить все значения */ + vector valueSet() { + vector valueSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + valueSet.push_back(pair->val); + } + } + return valueSet; + } + + /* Вывести хеш-таблицу */ + void print() { + for (Pair *kv : pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + } +}; + +// Тестовые примеры см. в array_hash_map_test.cpp diff --git a/ru/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/ru/codes/cpp/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 000000000..e2d596e57 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* Driver Code */ +int main() { + /* Инициализация хеш-таблицы */ + ArrayHashMap map = ArrayHashMap(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; + map.print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string name = map.get(15937); + cout << "\nДля студенческого номера 15937 найдено имя " << name << endl; + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583); + cout << "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" << endl; + map.print(); + + /* Обход хеш-таблицы */ + cout << "\nОтдельный обход пар ключ-значение" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\nОтдельный обход ключей" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\nОтдельный обход значений" << endl; + for (auto val : map.valueSet()) { + cout << val << endl; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_hashing/built_in_hash.cpp b/ru/codes/cpp/chapter_hashing/built_in_hash.cpp new file mode 100644 index 000000000..55d3d1d38 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "Хеш-значение целого числа " << num << " = " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "Хеш-значение булева значения " << bol << " = " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "Хеш-значение десятичного числа " << dec << " = " << hashDec << "\n"; + + string str = "Hello Algo"; + size_t hashStr = hash()(str); + cout << "Хеш-значение строки " << str << " = " << hashStr << "\n"; + + // В C++ встроенный std::hash() предоставляет вычисление хеша только для базовых типов данных + // Вычисление хеша для массивов и объектов нужно реализовывать самостоятельно +} diff --git a/ru/codes/cpp/chapter_hashing/hash_map.cpp b/ru/codes/cpp/chapter_hashing/hash_map.cpp new file mode 100644 index 000000000..7e076327e --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/hash_map.cpp @@ -0,0 +1,46 @@ +/** + * File: hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Инициализация хеш-таблицы */ + unordered_map map; + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map[12836] = "Сяо Ха"; + map[15937] = "Сяо Ло"; + map[16750] = "Сяо Суань"; + map[13276] = "Сяо Фа"; + map[10583] = "Сяо Я"; + cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; + printHashMap(map); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string name = map[15937]; + cout << "\nДля студенческого номера 15937 найдено имя " << name << endl; + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.erase(10583); + cout << "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" << endl; + printHashMap(map); + + /* Обход хеш-таблицы */ + cout << "\nОтдельный обход пар ключ-значение" << endl; + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << endl; + } + cout << "\nОбход пар Key->Value с помощью итератора" << endl; + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_hashing/hash_map_chaining.cpp b/ru/codes/cpp/chapter_hashing/hash_map_chaining.cpp new file mode 100644 index 000000000..953c2d558 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + private: + int size; // Число пар ключ-значение + int capacity; // Вместимость хеш-таблицы + double loadThres; // Порог коэффициента загрузки для запуска расширения + int extendRatio; // Коэффициент расширения + vector> buckets; // Массив корзин + + public: + /* Конструктор */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* Метод-деструктор */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // Освободить память + delete pair; + } + } + } + + /* Хеш-функция */ + int hashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* Операция поиска */ + string get(int key) { + int index = hashFunc(key); + // Обойти корзину; если найден key, вернуть соответствующее val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // Если key не найден, вернуть пустую строку + return ""; + } + + /* Операция добавления */ + void put(int key, string val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // Если такого key нет, добавить пару ключ-значение в конец + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* Операция удаления */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // Обойти корзину и удалить из нее пару ключ-значение + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // Удалить из него пару ключ-значение + delete tmp; // Освободить память + size--; + return; + } + } + } + + /* Расширить хеш-таблицу */ + void extend() { + // Временно сохранить исходную хеш-таблицу + vector> bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + // Освободить память + delete pair; + } + } + } + + /* Вывести хеш-таблицу */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* Driver Code */ +int main() { + /* Инициализация хеш-таблицы */ + HashMapChaining map = HashMapChaining(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; + map.print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string name = map.get(13276); + cout << "\nДля студенческого номера 13276 найдено имя " << name << endl; + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(12836); + cout << "\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение" << endl; + map.print(); + + return 0; +} diff --git a/ru/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/ru/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp new file mode 100644 index 000000000..a41ac0d79 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + private: + int size; // Число пар ключ-значение + int capacity = 4; // Вместимость хеш-таблицы + const double loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения + const int extendRatio = 2; // Коэффициент расширения + vector buckets; // Массив корзин + Pair *TOMBSTONE = new Pair(-1, "-1"); // Удалить метку + + public: + /* Конструктор */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* Метод-деструктор */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* Хеш-функция */ + int hashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + double loadFactor() { + return (double)size / capacity; + } + + /* Найти индекс корзины, соответствующий key */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (buckets[index] != nullptr) { + // Если встретился key, вернуть соответствующий индекс корзины + if (buckets[index]->key == key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone == -1 ? index : firstTombstone; + } + + /* Операция поиска */ + string get(int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // Если пары ключ-значение не существует, вернуть пустую строку + return ""; + } + + /* Операция добавления */ + void put(int key, string val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > loadThres) { + extend(); + } + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + buckets[index]->val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + buckets[index] = new Pair(key, val); + size++; + } + + /* Операция удаления */ + void remove(int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* Расширить хеш-таблицу */ + void extend() { + // Временно сохранить исходную хеш-таблицу + vector bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != TOMBSTONE) { + put(pair->key, pair->val); + delete pair; + } + } + } + + /* Вывести хеш-таблицу */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* Driver Code */ +int main() { + // Инициализация хеш-таблицы + HashMapOpenAddressing hashmap; + + // Операция добавления + // Добавить пару (key, val) в хеш-таблицу + hashmap.put(12836, "Сяо Ха"); + hashmap.put(15937, "Сяо Ло"); + hashmap.put(16750, "Сяо Суань"); + hashmap.put(13276, "Сяо Фа"); + hashmap.put(10583, "Сяо Я"); + cout << "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" << endl; + hashmap.print(); + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение val + string name = hashmap.get(13276); + cout << "\nДля студенческого номера 13276 найдено имя " << name << endl; + + // Операция удаления + // Удалить пару (key, val) из хеш-таблицы + hashmap.remove(16750); + cout << "\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение" << endl; + hashmap.print(); + + return 0; +} diff --git a/ru/codes/cpp/chapter_hashing/simple_hash.cpp b/ru/codes/cpp/chapter_hashing/simple_hash.cpp new file mode 100644 index 000000000..a49b62182 --- /dev/null +++ b/ru/codes/cpp/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Аддитивное хеширование */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* Мультипликативное хеширование */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* XOR-хеширование */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* Хеширование с циклическим сдвигом */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* Driver Code */ +int main() { + string key = "Hello Algo"; + + int hash = addHash(key); + cout << "Хеш суммы = " << hash << endl; + + hash = mulHash(key); + cout << "Хеш произведения = " << hash << endl; + + hash = xorHash(key); + cout << "XOR-хеш = " << hash << endl; + + hash = rotHash(key); + cout << "Хеш с циклическим сдвигом = " << hash << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_heap/CMakeLists.txt b/ru/codes/cpp/chapter_heap/CMakeLists.txt new file mode 100644 index 000000000..1ac33a44f --- /dev/null +++ b/ru/codes/cpp/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/ru/codes/cpp/chapter_heap/heap.cpp b/ru/codes/cpp/chapter_heap/heap.cpp new file mode 100644 index 000000000..973018009 --- /dev/null +++ b/ru/codes/cpp/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // Добавление элемента в кучу + cout << "\nПосле добавления элемента " << val << " в кучу" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\nПосле извлечения верхнего элемента " << val << " из кучи" << endl; + printHeap(heap); +} + +/* Driver Code */ +int main() { + /* Инициализация кучи */ + // Инициализировать минимальную кучу + // priority_queue, greater> minHeap; + // Инициализировать максимальную кучу + priority_queue, less> maxHeap; + + cout << "\nНиже приведены тестовые примеры для max-heap" << endl; + + /* Добавление элемента в кучу */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.top(); + cout << "\nВерхний элемент кучи = " << peek << endl; + + /* Извлечение элемента с вершины кучи */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* Получение размера кучи */ + int size = maxHeap.size(); + cout << "\nКоличество элементов в куче = " << size << endl; + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.empty(); + cout << "\nПуста ли куча: " << isEmpty << endl; + + /* Построить кучу по входному списку */ + // Временная сложность равна O(n), а не O(nlogn) + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + cout << "После построения min-heap из входного списка" << endl; + printHeap(minHeap); + + return 0; +} diff --git a/ru/codes/cpp/chapter_heap/my_heap.cpp b/ru/codes/cpp/chapter_heap/my_heap.cpp new file mode 100644 index 000000000..8143b4eec --- /dev/null +++ b/ru/codes/cpp/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* Максимальная куча */ +class MaxHeap { + private: + // Использовать динамический массив, чтобы не учитывать проблему расширения + vector maxHeap; + + /* Получить индекс левого дочернего узла */ + int left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + int right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + int parent(int i) { + return (i - 1) / 2; // Округление вниз при делении + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + void siftUp(int i) { + while (true) { + // Получение родительского узла для узла i + int p = parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // Поменять два узла местами + swap(maxHeap[i], maxHeap[p]); + // Циклическое просеивание вверх + i = p; + } + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + void siftDown(int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // Циклическое просеивание вниз + i = ma; + } + } + + public: + /* Конструктор, строящий кучу по входному списку */ + MaxHeap(vector nums) { + // Добавить элементы списка в кучу без изменений + maxHeap = nums; + // Выполнить heapify для всех узлов, кроме листовых + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* Получение размера кучи */ + int size() { + return maxHeap.size(); + } + + /* Проверка, пуста ли куча */ + bool isEmpty() { + return size() == 0; + } + + /* Доступ к элементу на вершине кучи */ + int peek() { + return maxHeap[0]; + } + + /* Добавление элемента в кучу */ + void push(int val) { + // Добавление узла + maxHeap.push_back(val); + // Просеивание снизу вверх + siftUp(size() - 1); + } + + /* Извлечение элемента из кучи */ + void pop() { + // Обработка пустого случая + if (isEmpty()) { + throw out_of_range("куча пуста"); + } + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(maxHeap[0], maxHeap[size() - 1]); + // Удаление узла + maxHeap.pop_back(); + // Просеивание сверху вниз + siftDown(0); + } + + /* Вывести кучу (двоичное дерево) */ + void print() { + cout << "Массивное представление кучи:"; + printVector(maxHeap); + cout << "Древовидное представление кучи:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* Driver Code */ +int main() { + /* Инициализация максимальной кучи */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\nПосле построения кучи из входного списка" << endl; + maxHeap.print(); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.peek(); + cout << "\nВерхний элемент кучи = " << peek << endl; + + /* Добавление элемента в кучу */ + int val = 7; + maxHeap.push(val); + cout << "\nПосле добавления элемента " << val << " в кучу" << endl; + maxHeap.print(); + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\nПосле извлечения верхнего элемента " << peek << " из кучи" << endl; + maxHeap.print(); + + /* Получение размера кучи */ + int size = maxHeap.size(); + cout << "\nКоличество элементов в куче = " << size << endl; + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.isEmpty(); + cout << "\nПуста ли куча: " << isEmpty << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_heap/top_k.cpp b/ru/codes/cpp/chapter_heap/top_k.cpp new file mode 100644 index 000000000..63775fedb --- /dev/null +++ b/ru/codes/cpp/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Найти k наибольших элементов массива с помощью кучи */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // Инициализация минимальной кучи + priority_queue, greater> heap; + // Поместить первые k элементов массива в кучу + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (int i = k; i < nums.size(); i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// Driver Code +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "Наибольшие " << k << " элементов: "; + printHeap(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_searching/CMakeLists.txt b/ru/codes/cpp/chapter_searching/CMakeLists.txt new file mode 100644 index 000000000..60a223d83 --- /dev/null +++ b/ru/codes/cpp/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/ru/codes/cpp/chapter_searching/binary_search.cpp b/ru/codes/cpp/chapter_searching/binary_search.cpp new file mode 100644 index 000000000..c83d28e9f --- /dev/null +++ b/ru/codes/cpp/chapter_searching/binary_search.cpp @@ -0,0 +1,59 @@ +/** + * File: binary_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +int binarySearch(vector &nums, int target) { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + int i = 0, j = nums.size() - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +int binarySearchLCRO(vector &nums, int target) { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + int i = 0, j = nums.size(); + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) + j = m; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + int index = binarySearch(nums, target); + cout << "Индекс целевого элемента 6 = " << index << endl; + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = binarySearchLCRO(nums, target); + cout << "Индекс целевого элемента 6 = " << index << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_searching/binary_search_edge.cpp b/ru/codes/cpp/chapter_searching/binary_search_edge.cpp new file mode 100644 index 000000000..674db9a8b --- /dev/null +++ b/ru/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Бинарный поиск самого левого target */ +int binarySearchLeftEdge(vector &nums, int target) { + // Эквивалентно поиску точки вставки target + int i = binarySearchInsertion(nums, target); + // target не найден, вернуть -1 + if (i == nums.size() || nums[i] != target) { + return -1; + } + // Найти target и вернуть индекс i + return i; +} + +/* Бинарный поиск самого правого target */ +int binarySearchRightEdge(vector &nums, int target) { + // Преобразовать задачу в поиск самого левого target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + int j = i - 1; + // target не найден, вернуть -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Найти target и вернуть индекс j + return j; +} + +/* Driver Code */ +int main() { + // Массив с повторяющимися элементами + vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\nМассив nums = "; + printVector(nums); + + // Бинарный поиск левой и правой границы + for (int target : {6, 7}) { + int index = binarySearchLeftEdge(nums, target); + cout << "Индекс самого левого элемента " << target << " равен " << index << endl; + index = binarySearchRightEdge(nums, target); + cout << "Индекс самого правого элемента " << target << " равен " << index << endl; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_searching/binary_search_insertion.cpp b/ru/codes/cpp/chapter_searching/binary_search_insertion.cpp new file mode 100644 index 000000000..60d1afc37 --- /dev/null +++ b/ru/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +int binarySearchInsertion(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Driver Code */ +int main() { + // Массив без повторяющихся элементов + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + cout << "\nМассив nums = "; + printVector(nums); + // Бинарный поиск точки вставки + for (int target : {6, 9}) { + int index = binarySearchInsertionSimple(nums, target); + cout << "Индекс позиции вставки элемента " << target << " равен " << index << endl; + } + + // Массив с повторяющимися элементами + nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\nМассив nums = "; + printVector(nums); + // Бинарный поиск точки вставки + for (int target : {2, 6, 20}) { + int index = binarySearchInsertion(nums, target); + cout << "Индекс позиции вставки элемента " << target << " равен " << index << endl; + } + + return 0; +} diff --git a/ru/codes/cpp/chapter_searching/hashing_search.cpp b/ru/codes/cpp/chapter_searching/hashing_search.cpp new file mode 100644 index 000000000..9fa5f44bd --- /dev/null +++ b/ru/codes/cpp/chapter_searching/hashing_search.cpp @@ -0,0 +1,53 @@ +/** + * File: hashing_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Хеш-поиск (массив) */ +int hashingSearchArray(unordered_map map, int target) { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + if (map.find(target) == map.end()) + return -1; + return map[target]; +} + +/* Хеш-поиск (связный список) */ +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть nullptr + if (map.find(target) == map.end()) + return nullptr; + return map[target]; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* Хеш-поиск (массив) */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + // Инициализация хеш-таблицы + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + map[nums[i]] = i; // key: элемент, value: индекс + } + int index = hashingSearchArray(map, target); + cout << "Индекс целевого элемента 3 = " << index << endl; + + /* Хеш-поиск (связный список) */ + ListNode *head = vecToLinkedList(nums); + // Инициализация хеш-таблицы + unordered_map map1; + while (head != nullptr) { + map1[head->val] = head; // key: значение узла, value: узел + head = head->next; + } + ListNode *node = hashingSearchLinkedList(map1, target); + cout << "Объект узла со значением 3 = " << node << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_searching/linear_search.cpp b/ru/codes/cpp/chapter_searching/linear_search.cpp new file mode 100644 index 000000000..31f2514b1 --- /dev/null +++ b/ru/codes/cpp/chapter_searching/linear_search.cpp @@ -0,0 +1,49 @@ +/** + * File: linear_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Линейный поиск (массив) */ +int linearSearchArray(vector &nums, int target) { + // Обход массива + for (int i = 0; i < nums.size(); i++) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] == target) + return i; + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Линейный поиск (связный список) */ +ListNode *linearSearchLinkedList(ListNode *head, int target) { + // Обойти связный список + while (head != nullptr) { + // Найти целевой узел и вернуть его + if (head->val == target) + return head; + head = head->next; + } + // Целевой узел не найден, вернуть nullptr + return nullptr; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* Выполнить линейный поиск в массиве */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + int index = linearSearchArray(nums, target); + cout << "Индекс целевого элемента 3 = " << index << endl; + + /* Выполнить линейный поиск в связном списке */ + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + cout << "Объект узла со значением 3 = " << node << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_searching/two_sum.cpp b/ru/codes/cpp/chapter_searching/two_sum.cpp new file mode 100644 index 000000000..1893fef63 --- /dev/null +++ b/ru/codes/cpp/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Метод 1: полный перебор */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // Два вложенных цикла, временная сложность O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return {i, j}; + } + } + return {}; +} + +/* Метод 2: вспомогательная хеш-таблица */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // Вспомогательная хеш-таблица, пространственная сложность O(n) + unordered_map dic; + // Один цикл, временная сложность O(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== Основной код ====== + // Метод 1 + vector res = twoSumBruteForce(nums, target); + cout << "Результат метода 1 res = "; + printVector(res); + // Метод 2 + res = twoSumHashTable(nums, target); + cout << "Результат метода 2 res = "; + printVector(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/CMakeLists.txt b/ru/codes/cpp/chapter_sorting/CMakeLists.txt new file mode 100644 index 000000000..e6347cf9f --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_sorting/bubble_sort.cpp b/ru/codes/cpp/chapter_sorting/bubble_sort.cpp new file mode 100644 index 000000000..7801f0d10 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -0,0 +1,56 @@ +/** + * File: bubble_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Пузырьковая сортировка */ +void bubbleSort(vector &nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + // Здесь используется функция std::swap() + swap(nums[j], nums[j + 1]); + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +void bubbleSortWithFlag(vector &nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + bool flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + // Здесь используется функция std::swap() + swap(nums[j], nums[j + 1]); + flag = true; // Записать обмен элементов + } + } + if (!flag) + break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + bubbleSort(nums); + cout << "После пузырьковой сортировки nums = "; + printVector(nums); + + vector nums1 = {4, 1, 3, 1, 5, 2}; + bubbleSortWithFlag(nums1); + cout << "После пузырьковой сортировки nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/bucket_sort.cpp b/ru/codes/cpp/chapter_sorting/bucket_sort.cpp new file mode 100644 index 000000000..ad0bc2a90 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Сортировка корзинами */ +void bucketSort(vector &nums) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + int k = nums.size() / 2; + vector> buckets(k); + // 1. Распределить элементы массива по корзинам + for (float num : nums) { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + int i = num * k; + // Добавить num в корзину bucket_idx + buckets[i].push_back(num); + } + // 2. Выполнить сортировку внутри каждой корзины + for (vector &bucket : buckets) { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + sort(bucket.begin(), bucket.end()); + } + // 3. Обойти корзины и объединить результаты + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +int main() { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums); + cout << "После сортировки корзинами nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/counting_sort.cpp b/ru/codes/cpp/chapter_sorting/counting_sort.cpp new file mode 100644 index 000000000..653984812 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +void countingSortNaive(vector &nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +void countingSort(vector &nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + int n = nums.size(); + vector res(n); + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // Поместить num по соответствующему индексу + counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + nums = res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "После сортировки подсчетом (объекты не поддерживаются) nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(nums1); + cout << "После сортировки подсчетом nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/heap_sort.cpp b/ru/codes/cpp/chapter_sorting/heap_sort.cpp new file mode 100644 index 000000000..13c1cf99d --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +void siftDown(vector &nums, int n, int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) { + break; + } + // Поменять два узла местами + swap(nums[i], nums[ma]); + // Циклическое просеивание вниз + i = ma; + } +} + +/* Сортировка кучей */ +void heapSort(vector &nums) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (int i = nums.size() - 1; i > 0; --i) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(nums[0], nums[i]); + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(nums); + cout << "После сортировки кучей nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/insertion_sort.cpp b/ru/codes/cpp/chapter_sorting/insertion_sort.cpp new file mode 100644 index 000000000..ed5de0072 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Сортировка вставками */ +void insertionSort(vector &nums) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (int i = 1; i < nums.size(); i++) { + int base = nums[i], j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо + j--; + } + nums[j + 1] = base; // Поместить base в правильную позицию + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + insertionSort(nums); + cout << "После сортировки вставками nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/merge_sort.cpp b/ru/codes/cpp/chapter_sorting/merge_sort.cpp new file mode 100644 index 000000000..5efa81412 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/merge_sort.cpp @@ -0,0 +1,58 @@ +/** + * File: merge_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Объединить левый и правый подмассивы */ +void merge(vector &nums, int left, int mid, int right) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + vector tmp(right - left + 1); + // Инициализировать начальные индексы левого и правого подмассивов + int i = left, j = mid + 1, k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmp.size(); k++) { + nums[left + k] = tmp[k]; + } +} + +/* Сортировка слиянием */ +void mergeSort(vector &nums, int left, int right) { + // Условие завершения + if (left >= right) + return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + int mid = left + (right - left) / 2; // Вычислить середину + mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* Сортировка слиянием */ + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; + mergeSort(nums, 0, nums.size() - 1); + cout << "После сортировки слиянием nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/quick_sort.cpp b/ru/codes/cpp/chapter_sorting/quick_sort.cpp new file mode 100644 index 000000000..0a85eabb8 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/quick_sort.cpp @@ -0,0 +1,145 @@ +/** + * File: quick_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Класс быстрой сортировки */ +class QuickSort { + private: + /* Разбиение с опорными указателями */ + static int partition(vector &nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums[i], nums[j]); // Поменять эти два элемента местами + } + swap(nums[i], nums[left]); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + public: + /* Быстрая сортировка */ + static void quickSort(vector &nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +class QuickSortMedian { + private: + /* Выбрать медиану из трех кандидатов */ + static int medianThree(vector &nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m находится между l и r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l находится между m и r + return right; + } + + /* Разбиение с опорными указателями (медиана трех) */ + static int partition(vector &nums, int left, int right) { + // Выбрать медиану из трех кандидатов + int med = medianThree(nums, left, (left + right) / 2, right); + // Переместить медиану в крайний левый элемент массива + swap(nums[left], nums[med]); + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums[i], nums[j]); // Поменять эти два элемента местами + } + swap(nums[i], nums[left]); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + public: + /* Быстрая сортировка */ + static void quickSort(vector &nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* Класс быстрой сортировки (оптимизация глубины рекурсии) */ +class QuickSortTailCall { + private: + /* Разбиение с опорными указателями */ + static int partition(vector &nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums[i], nums[j]); // Поменять эти два элемента местами + } + swap(nums[i], nums[left]); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + public: + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + static void quickSort(vector &nums, int left, int right) { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + int pivot = partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +}; + +/* Driver Code */ +int main() { + /* Быстрая сортировка */ + vector nums{2, 4, 1, 0, 3, 5}; + QuickSort::quickSort(nums, 0, nums.size() - 1); + cout << "После быстрой сортировки nums = "; + printVector(nums); + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + vector nums1 = {2, 4, 1, 0, 3, 5}; + QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); + cout << "После быстрой сортировки (оптимизация медианным опорным элементом) nums = "; + printVector(nums1); + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + vector nums2 = {2, 4, 1, 0, 3, 5}; + QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); + cout << "После быстрой сортировки (оптимизация глубины рекурсии) nums = "; + printVector(nums2); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/radix_sort.cpp b/ru/codes/cpp/chapter_sorting/radix_sort.cpp new file mode 100644 index 000000000..0186d2225 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +int digit(int num, int exp) { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num / exp) % 10; +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +void countingSortDigit(vector &nums, int exp) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + vector counter(10, 0); + int n = nums.size(); + // Подсчитать число появлений каждой цифры от 0 до 9 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d]++; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + vector res(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (int i = 0; i < n; i++) + nums[i] = res[i]; +} + +/* Поразрядная сортировка */ +void radixSort(vector &nums) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + int m = *max_element(nums.begin(), nums.end()); + // Проходить разряды от младшего к старшему + for (int exp = 1; exp <= m; exp *= 10) + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +int main() { + // Поразрядная сортировка + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(nums); + cout << "После поразрядной сортировки nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_sorting/selection_sort.cpp b/ru/codes/cpp/chapter_sorting/selection_sort.cpp new file mode 100644 index 000000000..cd4464be2 --- /dev/null +++ b/ru/codes/cpp/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Сортировка выбором */ +void selectionSort(vector &nums) { + int n = nums.size(); + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // Записать индекс минимального элемента + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + swap(nums[i], nums[k]); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "После сортировки выбором nums = "; + printVector(nums); + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/CMakeLists.txt b/ru/codes/cpp/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 000000000..b55878a17 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/ru/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/ru/codes/cpp/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 000000000..d374af324 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Двусторонняя очередь на основе кольцевого массива */ +class ArrayDeque { + private: + vector nums; // Массив для хранения элементов двусторонней очереди + int front; // Указатель head, указывающий на первый элемент очереди + int queSize; // Длина двусторонней очереди + + public: + /* Конструктор */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* Получить вместимость двусторонней очереди */ + int capacity() { + return nums.size(); + } + + /* Получение длины двусторонней очереди */ + int size() { + return queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty() { + return queSize == 0; + } + + /* Вычислить индекс в кольцевом массиве */ + int index(int i) { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + capacity()) % capacity(); + } + + /* Добавление в голову очереди */ + void pushFirst(int num) { + if (queSize == capacity()) { + cout << "Двусторонняя очередь заполнена" << endl; + return; + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + front = index(front - 1); + // Добавить num в голову очереди + nums[front] = num; + queSize++; + } + + /* Добавление в хвост очереди */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "Двусторонняя очередь заполнена" << endl; + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + int rear = index(front + queSize); + // Добавить num в хвост очереди + nums[rear] = num; + queSize++; + } + + /* Извлечение из головы очереди */ + int popFirst() { + int num = peekFirst(); + // Указатель головы сдвигается на одну позицию назад + front = index(front + 1); + queSize--; + return num; + } + + /* Извлечение из хвоста очереди */ + int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("двусторонняя очередь пуста"); + return nums[front]; + } + + /* Доступ к элементу в конце очереди */ + int peekLast() { + if (isEmpty()) + throw out_of_range("двусторонняя очередь пуста"); + // Вычислить индекс хвостового элемента + int last = index(front + queSize - 1); + return nums[last]; + } + + /* Вернуть массив для вывода */ + vector toVector() { + // Преобразовывать только элементы списка в пределах фактической длины + vector res(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация двусторонней очереди */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "Двусторонняя очередь deque = "; + printVector(deque->toVector()); + + /* Доступ к элементу */ + int peekFirst = deque->peekFirst(); + cout << "Первый элемент peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "Последний элемент peekLast = " << peekLast << endl; + + /* Добавление элемента в очередь */ + deque->pushLast(4); + cout << "После добавления элемента 4 в хвост deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "После добавления элемента 1 в голову deque = "; + printVector(deque->toVector()); + + /* Извлечение элемента из очереди */ + int popLast = deque->popLast(); + cout << "Извлеченный из хвоста элемент = " << popLast << ", deque после извлечения из хвоста = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "Извлеченный из головы элемент = " << popFirst << ", deque после извлечения из головы = "; + printVector(deque->toVector()); + + /* Получение длины двусторонней очереди */ + int size = deque->size(); + cout << "Длина двусторонней очереди size = " << size << endl; + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty = deque->isEmpty(); + cout << "Пуста ли двусторонняя очередь = " << boolalpha << isEmpty << endl; + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/ru/codes/cpp/chapter_stack_and_queue/array_queue.cpp new file mode 100644 index 000000000..eee44154b --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -0,0 +1,129 @@ +/** + * File: array_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + private: + int *nums; // Массив для хранения элементов очереди + int front; // Указатель head, указывающий на первый элемент очереди + int queSize; // Длина очереди + int queCapacity; // Вместимость очереди + + public: + ArrayQueue(int capacity) { + // Инициализация массива + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* Получить вместимость очереди */ + int capacity() { + return queCapacity; + } + + /* Получение длины очереди */ + int size() { + return queSize; + } + + /* Проверка, пуста ли очередь */ + bool isEmpty() { + return size() == 0; + } + + /* Поместить в очередь */ + void push(int num) { + if (queSize == queCapacity) { + cout << "Очередь заполнена" << endl; + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + int rear = (front + queSize) % queCapacity; + // Добавить num в хвост очереди + nums[rear] = num; + queSize++; + } + + /* Извлечь из очереди */ + int pop() { + int num = peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + int peek() { + if (isEmpty()) + throw out_of_range("очередь пуста"); + return nums[front]; + } + + /* Преобразовать массив в Vector и вернуть */ + vector toVector() { + // Преобразовывать только элементы списка в пределах фактической длины + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация очереди */ + int capacity = 10; + ArrayQueue *queue = new ArrayQueue(capacity); + + /* Добавление элемента в очередь */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "Очередь queue = "; + printVector(queue->toVector()); + + /* Доступ к элементу в начале очереди */ + int peek = queue->peek(); + cout << "Первый элемент peek = " << peek << endl; + + /* Извлечение элемента из очереди */ + peek = queue->pop(); + cout << "Извлеченный элемент pop = " << peek << ", queue после извлечения = "; + printVector(queue->toVector()); + + /* Получение длины очереди */ + int size = queue->size(); + cout << "Длина очереди size = " << size << endl; + + /* Проверка, пуста ли очередь */ + bool empty = queue->isEmpty(); + cout << "Пуста ли очередь = " << empty << endl; + + /* Проверка кольцевого массива */ + for (int i = 0; i < 10; i++) { + queue->push(i); + queue->pop(); + cout << "После " << i << "-го раунда операций enqueue и dequeue queue = "; + printVector(queue->toVector()); + } + + // Освободить память + delete queue; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/ru/codes/cpp/chapter_stack_and_queue/array_stack.cpp new file mode 100644 index 000000000..11add19ca --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -0,0 +1,85 @@ +/** + * File: array_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Стек на основе массива */ +class ArrayStack { + private: + vector stack; + + public: + /* Получение длины стека */ + int size() { + return stack.size(); + } + + /* Проверка, пуст ли стек */ + bool isEmpty() { + return stack.size() == 0; + } + + /* Поместить в стек */ + void push(int num) { + stack.push_back(num); + } + + /* Извлечь из стека */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* Доступ к верхнему элементу стека */ + int top() { + if (isEmpty()) + throw out_of_range("стек пуст"); + return stack.back(); + } + + /* Вернуть Vector */ + vector toVector() { + return stack; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация стека */ + ArrayStack *stack = new ArrayStack(); + + /* Помещение элемента в стек */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "Стек stack = "; + printVector(stack->toVector()); + + /* Доступ к верхнему элементу стека */ + int top = stack->top(); + cout << "Верхний элемент top = " << top << endl; + + /* Извлечение элемента из стека */ + top = stack->pop(); + cout << "Извлеченный элемент pop = " << top << ", stack после извлечения = "; + printVector(stack->toVector()); + + /* Получение длины стека */ + int size = stack->size(); + cout << "Длина стека size = " << size << endl; + + /* Проверка на пустоту */ + bool empty = stack->isEmpty(); + cout << "Пуст ли стек = " << empty << endl; + + // Освободить память + delete stack; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/deque.cpp b/ru/codes/cpp/chapter_stack_and_queue/deque.cpp new file mode 100644 index 000000000..494fbe9d6 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -0,0 +1,46 @@ +/** + * File: deque.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Инициализация двусторонней очереди */ + deque deque; + + /* Добавление элемента в очередь */ + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); + deque.push_front(1); + cout << "Двусторонняя очередь deque = "; + printDeque(deque); + + /* Доступ к элементу */ + int front = deque.front(); + cout << "Первый элемент front = " << front << endl; + int back = deque.back(); + cout << "Последний элемент back = " << back << endl; + + /* Извлечение элемента из очереди */ + deque.pop_front(); + cout << "Извлеченный из головы элемент popFront = " << front << ", deque после извлечения из головы = "; + printDeque(deque); + deque.pop_back(); + cout << "Извлеченный из хвоста элемент popLast = " << back << ", deque после извлечения из хвоста = "; + printDeque(deque); + + /* Получение длины двусторонней очереди */ + int size = deque.size(); + cout << "Длина двусторонней очереди size = " << size << endl; + + /* Проверка, пуста ли двусторонняя очередь */ + bool empty = deque.empty(); + cout << "Пуста ли двусторонняя очередь = " << empty << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/ru/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp new file mode 100644 index 000000000..91361058e --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Узел двусвязного списка */ +struct DoublyListNode { + int val; // Значение узла + DoublyListNode *next; // Указатель на узел-преемник + DoublyListNode *prev; // Указатель на узел-предшественник + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // Головной узел front, хвостовой узел rear + int queSize = 0; // Длина двусторонней очереди + + public: + /* Конструктор */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* Метод-деструктор */ + ~LinkedListDeque() { + // Обходить связный список, удалять узлы и освобождать память + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* Получение длины двусторонней очереди */ + int size() { + return queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty() { + return size() == 0; + } + + /* Операция добавления в очередь */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (isEmpty()) + front = rear = node; + // Операция добавления в голову очереди + else if (isFront) { + // Добавить node в голову списка + front->prev = node; + node->next = front; + front = node; // Обновить головной узел + // Операция добавления в хвост очереди + } else { + // Добавить node в хвост списка + rear->next = node; + node->prev = rear; + rear = node; // Обновить хвостовой узел + } + queSize++; // Обновить длину очереди + } + + /* Добавление в голову очереди */ + void pushFirst(int num) { + push(num, true); + } + + /* Добавление в хвост очереди */ + void pushLast(int num) { + push(num, false); + } + + /* Операция извлечения из очереди */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("очередь пуста"); + int val; + // Операция извлечения из головы очереди + if (isFront) { + val = front->val; // Временно сохранить значение головного узла + // Удалить головной узел + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // Обновить головной узел + // Операция извлечения из хвоста очереди + } else { + val = rear->val; // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // Обновить хвостовой узел + } + queSize--; // Обновить длину очереди + return val; + } + + /* Извлечение из головы очереди */ + int popFirst() { + return pop(true); + } + + /* Извлечение из хвоста очереди */ + int popLast() { + return pop(false); + } + + /* Доступ к элементу в начале очереди */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("двусторонняя очередь пуста"); + return front->val; + } + + /* Доступ к элементу в конце очереди */ + int peekLast() { + if (isEmpty()) + throw out_of_range("двусторонняя очередь пуста"); + return rear->val; + } + + /* Вернуть массив для вывода */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация двусторонней очереди */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "Двусторонняя очередь deque = "; + printVector(deque->toVector()); + + /* Доступ к элементу */ + int peekFirst = deque->peekFirst(); + cout << "Первый элемент peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "Последний элемент peekLast = " << peekLast << endl; + + /* Добавление элемента в очередь */ + deque->pushLast(4); + cout << "После добавления элемента 4 в хвост deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "После добавления элемента 1 в голову deque = "; + printVector(deque->toVector()); + + /* Извлечение элемента из очереди */ + int popLast = deque->popLast(); + cout << "Извлеченный из хвоста элемент = " << popLast << ", deque после извлечения из хвоста = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "Извлеченный из головы элемент = " << popFirst << ", deque после извлечения из головы = "; + printVector(deque->toVector()); + + /* Получение длины двусторонней очереди */ + int size = deque->size(); + cout << "Длина двусторонней очереди size = " << size << endl; + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty = deque->isEmpty(); + cout << "Пуста ли двусторонняя очередь = " << boolalpha << isEmpty << endl; + + // Освободить память + delete deque; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/ru/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp new file mode 100644 index 000000000..2d08d6364 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -0,0 +1,120 @@ +/** + * File: linkedlist_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Очередь на основе связного списка */ +class LinkedListQueue { + private: + ListNode *front, *rear; // Головной узел front, хвостовой узел rear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // Обходить связный список, удалять узлы и освобождать память + freeMemoryLinkedList(front); + } + + /* Получение длины очереди */ + int size() { + return queSize; + } + + /* Проверка, пуста ли очередь */ + bool isEmpty() { + return queSize == 0; + } + + /* Поместить в очередь */ + void push(int num) { + // Добавить num после хвостового узла + ListNode *node = new ListNode(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (front == nullptr) { + front = node; + rear = node; + } + // Если очередь не пуста, добавить этот узел после хвостового узла + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* Извлечь из очереди */ + int pop() { + int num = peek(); + // Удалить головной узел + ListNode *tmp = front; + front = front->next; + // Освободить память + delete tmp; + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + int peek() { + if (size() == 0) + throw out_of_range("очередь пуста"); + return front->val; + } + + /* Преобразовать связный список в Vector и вернуть */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация очереди */ + LinkedListQueue *queue = new LinkedListQueue(); + + /* Добавление элемента в очередь */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "Очередь queue = "; + printVector(queue->toVector()); + + /* Доступ к элементу в начале очереди */ + int peek = queue->peek(); + cout << "Первый элемент peek = " << peek << endl; + + /* Извлечение элемента из очереди */ + peek = queue->pop(); + cout << "Извлеченный элемент pop = " << peek << ", queue после извлечения = "; + printVector(queue->toVector()); + + /* Получение длины очереди */ + int size = queue->size(); + cout << "Длина очереди size = " << size << endl; + + /* Проверка, пуста ли очередь */ + bool empty = queue->isEmpty(); + cout << "Пуста ли очередь = " << empty << endl; + + // Освободить память + delete queue; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/ru/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp new file mode 100644 index 000000000..01d4ac341 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -0,0 +1,109 @@ +/** + * File: linkedlist_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Стек на основе связного списка */ +class LinkedListStack { + private: + ListNode *stackTop; // Использовать головной узел как вершину стека + int stkSize; // Длина стека + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // Обходить связный список, удалять узлы и освобождать память + freeMemoryLinkedList(stackTop); + } + + /* Получение длины стека */ + int size() { + return stkSize; + } + + /* Проверка, пуст ли стек */ + bool isEmpty() { + return size() == 0; + } + + /* Поместить в стек */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* Извлечь из стека */ + int pop() { + int num = top(); + ListNode *tmp = stackTop; + stackTop = stackTop->next; + // Освободить память + delete tmp; + stkSize--; + return num; + } + + /* Доступ к верхнему элементу стека */ + int top() { + if (isEmpty()) + throw out_of_range("стек пуст"); + return stackTop->val; + } + + /* Преобразовать List в Array и вернуть */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* Инициализация стека */ + LinkedListStack *stack = new LinkedListStack(); + + /* Помещение элемента в стек */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "Стек stack = "; + printVector(stack->toVector()); + + /* Доступ к верхнему элементу стека */ + int top = stack->top(); + cout << "Верхний элемент top = " << top << endl; + + /* Извлечение элемента из стека */ + top = stack->pop(); + cout << "Извлеченный элемент pop = " << top << ", stack после извлечения = "; + printVector(stack->toVector()); + + /* Получение длины стека */ + int size = stack->size(); + cout << "Длина стека size = " << size << endl; + + /* Проверка на пустоту */ + bool empty = stack->isEmpty(); + cout << "Пуст ли стек = " << empty << endl; + + // Освободить память + delete stack; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/queue.cpp b/ru/codes/cpp/chapter_stack_and_queue/queue.cpp new file mode 100644 index 000000000..e1ce496e0 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -0,0 +1,41 @@ +/** + * File: queue.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Инициализация очереди */ + queue queue; + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + cout << "Очередь queue = "; + printQueue(queue); + + /* Доступ к элементу в начале очереди */ + int front = queue.front(); + cout << "Первый элемент front = " << front << endl; + + /* Извлечение элемента из очереди */ + queue.pop(); + cout << "Извлеченный элемент front = " << front << ", queue после извлечения = "; + printQueue(queue); + + /* Получение длины очереди */ + int size = queue.size(); + cout << "Длина очереди size = " << size << endl; + + /* Проверка, пуста ли очередь */ + bool empty = queue.empty(); + cout << "Пуста ли очередь = " << empty << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_stack_and_queue/stack.cpp b/ru/codes/cpp/chapter_stack_and_queue/stack.cpp new file mode 100644 index 000000000..b536bb3e6 --- /dev/null +++ b/ru/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -0,0 +1,41 @@ +/** + * File: stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Инициализация стека */ + stack stack; + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + cout << "Стек stack = "; + printStack(stack); + + /* Доступ к верхнему элементу стека */ + int top = stack.top(); + cout << "Верхний элемент top = " << top << endl; + + /* Извлечение элемента из стека */ + stack.pop(); // Без возвращаемого значения + cout << "Извлеченный элемент pop = " << top << ", stack после извлечения = "; + printStack(stack); + + /* Получение длины стека */ + int size = stack.size(); + cout << "Длина стека size = " << size << endl; + + /* Проверка на пустоту */ + bool empty = stack.empty(); + cout << "Пуст ли стек = " << empty << endl; + + return 0; +} diff --git a/ru/codes/cpp/chapter_tree/CMakeLists.txt b/ru/codes/cpp/chapter_tree/CMakeLists.txt new file mode 100644 index 000000000..fa7009bcb --- /dev/null +++ b/ru/codes/cpp/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/ru/codes/cpp/chapter_tree/array_binary_tree.cpp b/ru/codes/cpp/chapter_tree/array_binary_tree.cpp new file mode 100644 index 000000000..342d6d8c8 --- /dev/null +++ b/ru/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree { + public: + /* Конструктор */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* Вместимость списка */ + int size() { + return tree.size(); + } + + /* Получить значение узла с индексом i */ + int val(int i) { + // Если индекс выходит за границы, вернуть INT_MAX, обозначающий пустую позицию + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + int left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + int right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла узла с индексом i */ + int parent(int i) { + return (i - 1) / 2; + } + + /* Обход в ширину */ + vector levelOrder() { + vector res; + // Непосредственно обходить массив + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* Предварительный обход */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* Симметричный обход */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* Обратный обход */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* Обход в глубину */ + void dfs(int i, string order, vector &res) { + // Если это пустая позиция, вернуть + if (val(i) == INT_MAX) + return; + // Предварительный обход + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // Симметричный обход + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // Обратный обход + if (order == "post") + res.push_back(val(i)); + } +}; + +/* Driver Code */ +int main() { + // Инициализировать двоичное дерево + // Использовать INT_MAX для обозначения пустой позиции nullptr + vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + TreeNode *root = vectorToTree(arr); + cout << "\nИнициализация двоичного дерева\n"; + cout << "Массивное представление двоичного дерева:\n"; + printVector(arr); + cout << "Связное представление двоичного дерева:\n"; + printTree(root); + + // Класс двоичного дерева в массивном представлении + ArrayBinaryTree abt(arr); + + // Доступ к узлу + int i = 1; + int l = abt.left(i), r = abt.right(i), p = abt.parent(i); + cout << "\nТекущий узел: индекс = " << i << ", значение = " << abt.val(i) << "\n"; + cout << "Индекс левого дочернего узла = " << l << ", значение = " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "Индекс правого дочернего узла = " << r << ", значение = " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "Индекс родительского узла = " << p << ", значение = " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + + // Обходить дерево + vector res = abt.levelOrder(); + cout << "\nОбход в ширину: "; + printVector(res); + res = abt.preOrder(); + cout << "Предварительный обход: "; + printVector(res); + res = abt.inOrder(); + cout << "Симметричный обход: "; + printVector(res); + res = abt.postOrder(); + cout << "Обратный обход: "; + printVector(res); + + return 0; +} diff --git a/ru/codes/cpp/chapter_tree/avl_tree.cpp b/ru/codes/cpp/chapter_tree/avl_tree.cpp new file mode 100644 index 000000000..89d6477d1 --- /dev/null +++ b/ru/codes/cpp/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL-дерево */ +class AVLTree { + private: + /* Обновить высоту узла */ + void updateHeight(TreeNode *node) { + // Высота узла равна высоте более высокого поддерева + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + + /* Операция правого вращения */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // Выполнить правое вращение узла node вокруг child + child->right = node; + node->left = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Операция левого вращения */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // Выполнить левое вращение узла node вокруг child + child->left = node; + node->right = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + TreeNode *rotate(TreeNode *node) { + // Получить коэффициент баланса узла node + int _balanceFactor = balanceFactor(node); + // Левосторонне перекошенное дерево + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // Правое вращение + return rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // Левое вращение + return leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. Найти позицию вставки и вставить узел */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // Повторяющийся узел не вставлять, сразу вернуть + updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. Найти узел и удалить его */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == nullptr) { + delete node; + return nullptr; + } + // Число дочерних узлов = 1, удалить node напрямую + else { + delete node; + node = child; + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + public: + TreeNode *root; // Корневой узел + + /* Получить высоту узла */ + int height(TreeNode *node) { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node == nullptr ? -1 : node->height; + } + + /* Получить коэффициент баланса */ + int balanceFactor(TreeNode *node) { + // Коэффициент баланса пустого узла равен 0 + if (node == nullptr) + return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return height(node->left) - height(node->right); + } + + /* Вставка узла */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* Удаление узла */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* Поиск узла */ + TreeNode *search(int val) { + TreeNode *cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != nullptr) { + // Целевой узел находится в правом поддереве cur + if (cur->val < val) + cur = cur->right; + // Целевой узел находится в левом поддереве cur + else if (cur->val > val) + cur = cur->left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } + + /* Конструктор */ + AVLTree() : root(nullptr) { + } + + /* Метод-деструктор */ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\nПосле вставки узла " << val << " AVL-дерево имеет вид" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\nПосле удаления узла " << val << " AVL-дерево имеет вид" << endl; + printTree(tree.root); +} + +/* Driver Code */ +int main() { + /* Инициализация пустого AVL-дерева */ + AVLTree avlTree; + + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* Вставка повторяющегося узла */ + testInsert(avlTree, 7); + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(avlTree, 8); // Удаление узла степени 0 + testRemove(avlTree, 5); // Удаление узла степени 1 + testRemove(avlTree, 4); // Удаление узла степени 2 + + /* Поиск узла */ + TreeNode *node = avlTree.search(7); + cout << "\nНайденный объект узла = " << node << ", значение узла = " << node->val << endl; +} diff --git a/ru/codes/cpp/chapter_tree/binary_search_tree.cpp b/ru/codes/cpp/chapter_tree/binary_search_tree.cpp new file mode 100644 index 000000000..c8fe5294e --- /dev/null +++ b/ru/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -0,0 +1,170 @@ +/** + * File: binary_search_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Двоичное дерево поиска */ +class BinarySearchTree { + private: + TreeNode *root; + + public: + /* Конструктор */ + BinarySearchTree() { + // Инициализировать пустое дерево + root = nullptr; + } + + /* Метод-деструктор */ + ~BinarySearchTree() { + freeMemoryTree(root); + } + + /* Получить корневой узел двоичного дерева */ + TreeNode *getRoot() { + return root; + } + + /* Поиск узла */ + TreeNode *search(int num) { + TreeNode *cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != nullptr) { + // Целевой узел находится в правом поддереве cur + if (cur->val < num) + cur = cur->right; + // Целевой узел находится в левом поддереве cur + else if (cur->val > num) + cur = cur->left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } + + /* Вставка узла */ + void insert(int num) { + // Если дерево пусто, инициализировать корневой узел + if (root == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != nullptr) { + // Найти повторяющийся узел и сразу вернуть + if (cur->val == num) + return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur->val < num) + cur = cur->right; + // Позиция вставки находится в левом поддереве cur + else + cur = cur->left; + } + // Вставка узла + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + + /* Удаление узла */ + void remove(int num) { + // Если дерево пусто, сразу вернуть + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != nullptr) { + // Найти узел для удаления и выйти из цикла + if (cur->val == num) + break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur->val < num) + cur = cur->right; + // Узел для удаления находится в левом поддереве cur + else + cur = cur->left; + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur == nullptr) + return; + // Число дочерних узлов = 0 или 1 + if (cur->left == nullptr || cur->right == nullptr) { + // Когда число дочерних узлов = 0 / 1, child = nullptr / этот дочерний узел + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // Удалить узел cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + root = child; + } + // Освободить память + delete cur; + } + // Число дочерних узлов = 2 + else { + // Получить следующий узел после cur в симметричном обходе + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // Рекурсивно удалить узел tmp + remove(tmp->val); + // Перезаписать cur значением tmp + cur->val = tmpVal; + } + } +}; + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева поиска */ + BinarySearchTree *bst = new BinarySearchTree(); + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + for (int num : nums) { + bst->insert(num); + } + cout << endl << "Исходное двоичное дерево\n" << endl; + printTree(bst->getRoot()); + + /* Поиск узла */ + TreeNode *node = bst->search(7); + cout << endl << "Найденный объект узла = " << node << ", значение узла = " << node->val << endl; + + /* Вставка узла */ + bst->insert(16); + cout << endl << "После вставки узла 16 двоичное дерево имеет вид\n" << endl; + printTree(bst->getRoot()); + + /* Удаление узла */ + bst->remove(1); + cout << endl << "После удаления узла 1 двоичное дерево имеет вид\n" << endl; + printTree(bst->getRoot()); + bst->remove(2); + cout << endl << "После удаления узла 2 двоичное дерево имеет вид\n" << endl; + printTree(bst->getRoot()); + bst->remove(4); + cout << endl << "После удаления узла 4 двоичное дерево имеет вид\n" << endl; + printTree(bst->getRoot()); + + // Освободить память + delete bst; + + return 0; +} diff --git a/ru/codes/cpp/chapter_tree/binary_tree.cpp b/ru/codes/cpp/chapter_tree/binary_tree.cpp new file mode 100644 index 000000000..fd35e8c79 --- /dev/null +++ b/ru/codes/cpp/chapter_tree/binary_tree.cpp @@ -0,0 +1,43 @@ +/** + * File: binary_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева */ + // Инициализация узла + TreeNode *n1 = new TreeNode(1); + TreeNode *n2 = new TreeNode(2); + TreeNode *n3 = new TreeNode(3); + TreeNode *n4 = new TreeNode(4); + TreeNode *n5 = new TreeNode(5); + // Построить связи между узлами (указатели) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + cout << endl << "Инициализация двоичного дерева\n" << endl; + printTree(n1); + + /* Вставка и удаление узлов */ + TreeNode *P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1->left = P; + P->left = n2; + cout << endl << "После вставки узла P\n" << endl; + printTree(n1); + // Удалить узел P + n1->left = n2; + delete P; // Освободить память + cout << endl << "После удаления узла P\n" << endl; + printTree(n1); + + // Освободить память + freeMemoryTree(n1); + + return 0; +} diff --git a/ru/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/ru/codes/cpp/chapter_tree/binary_tree_bfs.cpp new file mode 100644 index 000000000..81e5fb82f --- /dev/null +++ b/ru/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Обход в ширину */ +vector levelOrder(TreeNode *root) { + // Инициализировать очередь и добавить корневой узел + queue queue; + queue.push(root); + // Инициализировать список для хранения последовательности обхода + vector vec; + while (!queue.empty()) { + TreeNode *node = queue.front(); + queue.pop(); // Извлечение из очереди + vec.push_back(node->val); // Сохранить значение узла + if (node->left != nullptr) + queue.push(node->left); // Поместить левый дочерний узел в очередь + if (node->right != nullptr) + queue.push(node->right); // Поместить правый дочерний узел в очередь + } + return vec; +} + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "Инициализация двоичного дерева\n" << endl; + printTree(root); + + /* Обход в ширину */ + vector vec = levelOrder(root); + cout << endl << "Последовательность печати узлов при обходе в ширину = "; + printVector(vec); + + return 0; +} diff --git a/ru/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/ru/codes/cpp/chapter_tree/binary_tree_dfs.cpp new file mode 100644 index 000000000..ceb601b81 --- /dev/null +++ b/ru/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +// Инициализировать список для хранения последовательности обхода +vector vec; + +/* Предварительный обход */ +void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + vec.push_back(root->val); + preOrder(root->left); + preOrder(root->right); +} + +/* Симметричный обход */ +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); +} + +/* Обратный обход */ +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); +} + +/* Driver Code */ +int main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "Инициализация двоичного дерева\n" << endl; + printTree(root); + + /* Предварительный обход */ + vec.clear(); + preOrder(root); + cout << endl << "Последовательность печати узлов при предварительном обходе = "; + printVector(vec); + + /* Симметричный обход */ + vec.clear(); + inOrder(root); + cout << endl << "Последовательность печати узлов при симметричном обходе = "; + printVector(vec); + + /* Обратный обход */ + vec.clear(); + postOrder(root); + cout << endl << "Последовательность печати узлов при обратном обходе = "; + printVector(vec); + + return 0; +} diff --git a/ru/codes/cpp/utils/CMakeLists.txt b/ru/codes/cpp/utils/CMakeLists.txt new file mode 100644 index 000000000..775a55869 --- /dev/null +++ b/ru/codes/cpp/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/ru/codes/cpp/utils/common.hpp b/ru/codes/cpp/utils/common.hpp new file mode 100644 index 000000000..c72dabd88 --- /dev/null +++ b/ru/codes/cpp/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; diff --git a/ru/codes/cpp/utils/list_node.hpp b/ru/codes/cpp/utils/list_node.hpp new file mode 100644 index 000000000..4cfc235ce --- /dev/null +++ b/ru/codes/cpp/utils/list_node.hpp @@ -0,0 +1,42 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* Узел связного списка */ +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { + } +}; + +/* Десериализовать список в связный список */ +ListNode *vecToLinkedList(vector list) { + ListNode *dum = new ListNode(0); + ListNode *head = dum; + for (int val : list) { + head->next = new ListNode(val); + head = head->next; + } + return dum->next; +} + +/* Освободить память, выделенную под связный список */ +void freeMemoryLinkedList(ListNode *cur) { + // Освободить память + ListNode *pre; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } +} diff --git a/ru/codes/cpp/utils/print_utils.hpp b/ru/codes/cpp/utils/print_utils.hpp new file mode 100644 index 000000000..72449cff6 --- /dev/null +++ b/ru/codes/cpp/utils/print_utils.hpp @@ -0,0 +1,228 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* Find an element in a vector */ +template int vecFind(const vector &vec, T ele) { + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == ele) { + j = i; + } + } + return j; +} + +/* Concatenate a vector with a delim */ +template string strJoin(const string &delim, const T &vec) { + ostringstream s; + for (const auto &i : vec) { + if (&i != &vec[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + +/* Repeat a string for n times */ +string strRepeat(string str, int n) { + ostringstream os; + for (int i = 0; i < n; i++) + os << str; + return os.str(); +} + +/* Вывести массив */ +template void printArray(T *arr, int n) { + cout << "["; + for (int i = 0; i < n - 1; i++) { + cout << arr[i] << ", "; + } + if (n >= 1) + cout << arr[n - 1] << "]" << endl; + else + cout << "]" << endl; +} + +/* Get the Vector String object */ +template string getVectorString(vector &list) { + return "[" + strJoin(", ", list) + "]"; +} + +/* Вывести список */ +template void printVector(vector list) { + cout << getVectorString(list) << '\n'; +} + +/* Вывести матрицу */ +template void printVectorMatrix(vector> &matrix) { + cout << "[" << '\n'; + for (vector &list : matrix) + cout << " " + getVectorString(list) + "," << '\n'; + cout << "]" << '\n'; +} + +/* Вывести связный список */ +void printLinkedList(ListNode *head) { + vector list; + while (head != nullptr) { + list.push_back(head->val); + head = head->next; + } + + cout << strJoin(" -> ", list) << '\n'; +} + +struct Trunk { + Trunk *prev; + string str; + Trunk(Trunk *prev, string str) { + this->prev = prev; + this->str = str; + } +}; + +void showTrunks(Trunk *p) { + if (p == nullptr) { + return; + } + + showTrunks(p->prev); + cout << p->str; +} + +/** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode *root, Trunk *prev, bool isRight) { + if (root == nullptr) { + return; + } + + string prev_str = " "; + Trunk trunk(prev, prev_str); + + printTree(root->right, &trunk, true); + + if (!prev) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev->str = prev_str; + } + + showTrunks(&trunk); + cout << " " << root->val << endl; + + if (prev) { + prev->str = prev_str; + } + trunk.str = " |"; + + printTree(root->left, &trunk, false); +} + +/* Вывести двоичное дерево */ +void printTree(TreeNode *root) { + printTree(root, nullptr, false); +} + +/* Вывести стек */ +template void printStack(stack stk) { + // Reverse the input stack + stack tmp; + while (!stk.empty()) { + tmp.push(stk.top()); + stk.pop(); + } + // Generate the string to print + ostringstream s; + bool flag = true; + while (!tmp.empty()) { + if (flag) { + s << tmp.top(); + flag = false; + } else + s << ", " << tmp.top(); + tmp.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Вывести очередь */ +template void printQueue(queue queue) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!queue.empty()) { + if (flag) { + s << queue.front(); + flag = false; + } else + s << ", " << queue.front(); + queue.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Вывести двустороннюю очередь */ +template void printDeque(deque deque) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!deque.empty()) { + if (flag) { + s << deque.front(); + flag = false; + } else + s << ", " << deque.front(); + deque.pop_front(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Вывести хеш-таблицу */ +// Определить параметры шаблона TKey и TValue для указания типов пары ключ-значение +template void printHashMap(unordered_map map) { + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << '\n'; + } +} + +/* Expose the underlying storage of the priority_queue container */ +template S &Container(priority_queue &pq) { + struct HackedQueue : private priority_queue { + static S &Container(priority_queue &pq) { + return pq.*&HackedQueue::c; + } + }; + return HackedQueue::Container(pq); +} + +/* Вывести кучу (приоритетную очередь) */ +template void printHeap(priority_queue &heap) { + vector vec = Container(heap); + cout << "Массивное представление кучи:"; + printVector(vec); + cout << "Древовидное представление кучи:" << endl; + TreeNode *root = vectorToTree(vec); + printTree(root); + freeMemoryTree(root); +} diff --git a/ru/codes/cpp/utils/tree_node.hpp b/ru/codes/cpp/utils/tree_node.hpp new file mode 100644 index 000000000..e2f338c95 --- /dev/null +++ b/ru/codes/cpp/utils/tree_node.hpp @@ -0,0 +1,84 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* Структура узла двоичного дерева */ +struct TreeNode { + int val{}; + int height = 0; + TreeNode *parent{}; + TreeNode *left{}; + TreeNode *right{}; + TreeNode() = default; + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) { + } +}; + +// Правила кодирования сериализации см.: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// Массивное представление двоичного дерева: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// Связное представление двоичного дерева: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* Десериализовать список в двоичное дерево: рекурсия */ +TreeNode *vectorToTreeDFS(vector &arr, int i) { + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); + root->right = vectorToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* Десериализовать список в двоичное дерево */ +TreeNode *vectorToTree(vector arr) { + return vectorToTreeDFS(arr, 0); +} + +/* Сериализовать двоичное дерево в список: рекурсия */ +void treeToVecorDFS(TreeNode *root, int i, vector &res) { + if (root == nullptr) + return; + while (i >= res.size()) { + res.push_back(INT_MAX); + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, res); + treeToVecorDFS(root->right, 2 * i + 2, res); +} + +/* Сериализовать двоичное дерево в список */ +vector treeToVecor(TreeNode *root) { + vector res; + treeToVecorDFS(root, 0, res); + return res; +} + +/* Освободить память двоичного дерева */ +void freeMemoryTree(TreeNode *root) { + if (root == nullptr) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + delete root; +} diff --git a/ru/codes/cpp/utils/vertex.hpp b/ru/codes/cpp/utils/vertex.hpp new file mode 100644 index 000000000..a9d0f405d --- /dev/null +++ b/ru/codes/cpp/utils/vertex.hpp @@ -0,0 +1,36 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include + +using namespace std; + +/* Класс вершины */ +struct Vertex { + int val; + Vertex(int x) : val(x) { + } +}; + +/* На вход подается список значений vals, на выходе возвращается список вершин vets */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* На вход подается список вершин vets, на выходе возвращается список значений vals */ +vector vetsToVals(vector vets) { + vector vals; + for (Vertex *vet : vets) { + vals.push_back(vet->val); + } + return vals; +} diff --git a/ru/codes/csharp/.editorconfig b/ru/codes/csharp/.editorconfig new file mode 100644 index 000000000..0a2c1df58 --- /dev/null +++ b/ru/codes/csharp/.editorconfig @@ -0,0 +1,88 @@ +# CSharp formatting rules +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent + +# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +dotnet_diagnostic.CS8981.severity = silent + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = silent + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = silent + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = silent + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = silent diff --git a/ru/codes/csharp/.gitignore b/ru/codes/csharp/.gitignore new file mode 100644 index 000000000..a4b66a94a --- /dev/null +++ b/ru/codes/csharp/.gitignore @@ -0,0 +1,5 @@ +.idea/ +.vs/ +obj/ +.Debug +bin/ diff --git a/ru/codes/csharp/.vscode/launch.json b/ru/codes/csharp/.vscode/launch.json new file mode 100644 index 000000000..c3201256b --- /dev/null +++ b/ru/codes/csharp/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net6.0/hello-algo.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} diff --git a/ru/codes/csharp/.vscode/tasks.json b/ru/codes/csharp/.vscode/tasks.json new file mode 100644 index 000000000..dc310d887 --- /dev/null +++ b/ru/codes/csharp/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/hello-algo.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/hello-algo.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/hello-algo.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/ru/codes/csharp/GlobalUsing.cs b/ru/codes/csharp/GlobalUsing.cs new file mode 100644 index 000000000..402066ff4 --- /dev/null +++ b/ru/codes/csharp/GlobalUsing.cs @@ -0,0 +1,3 @@ +global using NUnit.Framework; +global using hello_algo.utils; +global using System.Text; \ No newline at end of file diff --git a/ru/codes/csharp/chapter_array_and_linkedlist/array.cs b/ru/codes/csharp/chapter_array_and_linkedlist/array.cs new file mode 100644 index 000000000..1cc5b9b13 --- /dev/null +++ b/ru/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -0,0 +1,107 @@ +// File: array.cs +// Created Time: 2022-12-14 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class array { + /* Случайный доступ к элементу */ + int RandomAccess(int[] nums) { + Random random = new(); + // Случайным образом выбрать число из интервала [0, nums.Length) + int randomIndex = random.Next(nums.Length); + // Получить и вернуть случайный элемент + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* Увеличить длину массива */ + int[] Extend(int[] nums, int enlarge) { + // Инициализировать массив увеличенной длины + int[] res = new int[nums.Length + enlarge]; + // Скопировать все элементы исходного массива в новый массив + for (int i = 0; i < nums.Length; i++) { + res[i] = nums[i]; + } + // Вернуть новый массив после расширения + return res; + } + + /* Вставить элемент num по индексу index в массив */ + void Insert(int[] nums, int num, int index) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (int i = nums.Length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; + } + + /* Удалить элемент по индексу index */ + void Remove(int[] nums, int index) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* Обход массива */ + void Traverse(int[] nums) { + int count = 0; + // Обход массива по индексам + for (int i = 0; i < nums.Length; i++) { + count += nums[i]; + } + // Непосредственно обходить элементы массива + foreach (int num in nums) { + count += num; + } + } + + /* Найти заданный элемент в массиве */ + int Find(int[] nums, int target) { + for (int i = 0; i < nums.Length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* Вспомогательная функция: преобразовать массив в строку */ + string ToString(int[] nums) { + return string.Join(",", nums); + } + + + [Test] + public void Test() { + // Инициализация массива + int[] arr = new int[5]; + Console.WriteLine("Массив arr = " + ToString(arr)); + int[] nums = [1, 3, 2, 5, 4]; + Console.WriteLine("Массив nums = " + ToString(nums)); + + // Случайный доступ + int randomNum = RandomAccess(nums); + Console.WriteLine("Случайный элемент из nums = " + randomNum); + + // Расширение длины + nums = Extend(nums, 3); + Console.WriteLine("После увеличения длины массива до 8 nums = " + ToString(nums)); + + // Вставка элемента + Insert(nums, 6, 3); + Console.WriteLine("После вставки числа 6 по индексу 3 nums = " + ToString(nums)); + + // Удаление элемента + Remove(nums, 2); + Console.WriteLine("После удаления элемента по индексу 2 nums = " + ToString(nums)); + + // Обход массива + Traverse(nums); + + // Поиск элемента + int index = Find(nums, 3); + Console.WriteLine("Поиск элемента 3 в nums: индекс = " + index); + } +} diff --git a/ru/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/ru/codes/csharp/chapter_array_and_linkedlist/linked_list.cs new file mode 100644 index 000000000..68b82d445 --- /dev/null +++ b/ru/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -0,0 +1,80 @@ +// File: linked_list.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class linked_list { + /* Вставить узел P после узла n0 в связном списке */ + void Insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* Удалить первый узел после узла n0 в связном списке */ + void Remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } + + /* Доступ к узлу связного списка по индексу index */ + ListNode? Access(ListNode? head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* Найти в связном списке первый узел со значением target */ + int Find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + + [Test] + public void Test() { + // Инициализация связного списка + // Инициализация всех узлов + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // Построить ссылки между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + Console.WriteLine($"Исходный связный список: {n0}"); + + // Вставка узла + Insert(n0, new ListNode(0)); + Console.WriteLine($"Связный список после вставки узла: {n0}"); + + // Удаление узла + Remove(n0); + Console.WriteLine($"Связный список после удаления узла: {n0}"); + + // Доступ к узлу + ListNode? node = Access(n0, 3); + Console.WriteLine($"Значение узла по индексу 3 в связном списке = {node?.val}"); + + // Поиск узла + int index = Find(n0, 2); + Console.WriteLine($"Индекс узла со значением 2 в связном списке = {index}"); + } +} diff --git a/ru/codes/csharp/chapter_array_and_linkedlist/list.cs b/ru/codes/csharp/chapter_array_and_linkedlist/list.cs new file mode 100644 index 000000000..bea624e5e --- /dev/null +++ b/ru/codes/csharp/chapter_array_and_linkedlist/list.cs @@ -0,0 +1,66 @@ +/** + * File: list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +public class list { + [Test] + public void Test() { + + /* Инициализация списка */ + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + Console.WriteLine("Список nums = " + string.Join(",", nums)); + + /* Доступ к элементу */ + int num = nums[1]; + Console.WriteLine("Элемент по индексу 1: num = " + num); + + /* Обновление элемента */ + nums[1] = 0; + Console.WriteLine("После обновления элемента по индексу 1 до 0 nums = " + string.Join(",", nums)); + + /* Очистить список */ + nums.Clear(); + Console.WriteLine("После очистки списка nums = " + string.Join(",", nums)); + + /* Добавление элемента в конец */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("После добавления элементов nums = " + string.Join(",", nums)); + + /* Вставка элемента в середину */ + nums.Insert(3, 6); + Console.WriteLine("После вставки числа 6 по индексу 3 nums = " + string.Join(",", nums)); + + /* Удаление элемента */ + nums.RemoveAt(3); + Console.WriteLine("После удаления элемента по индексу 3 nums = " + string.Join(",", nums)); + + /* Обходить список по индексам */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + /* Непосредственно обходить элементы списка */ + count = 0; + foreach (int x in nums) { + count += x; + } + + /* Объединить два списка */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); + Console.WriteLine("После конкатенации списка nums1 к nums nums = " + string.Join(",", nums)); + + /* Отсортировать список */ + nums.Sort(); // После сортировки элементы списка располагаются по возрастанию + Console.WriteLine("После сортировки списка nums = " + string.Join(",", nums)); + } +} diff --git a/ru/codes/csharp/chapter_array_and_linkedlist/my_list.cs b/ru/codes/csharp/chapter_array_and_linkedlist/my_list.cs new file mode 100644 index 000000000..a47098526 --- /dev/null +++ b/ru/codes/csharp/chapter_array_and_linkedlist/my_list.cs @@ -0,0 +1,144 @@ +/** + * File: my_list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +/* Класс списка */ +class MyList { + private int[] arr; // Массив (для хранения элементов списка) + private int arrCapacity = 10; // Вместимость списка + private int arrSize = 0; // Длина списка (текущее число элементов) + private readonly int extendRatio = 2; // Коэффициент увеличения списка при каждом расширении + + /* Конструктор */ + public MyList() { + arr = new int[arrCapacity]; + } + + /* Получить длину списка (текущее число элементов) */ + public int Size() { + return arrSize; + } + + /* Получить вместимость списка */ + public int Capacity() { + return arrCapacity; + } + + /* Доступ к элементу */ + public int Get(int index) { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("индекс выходит за границы"); + return arr[index]; + } + + /* Обновление элемента */ + public void Set(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("индекс выходит за границы"); + arr[index] = num; + } + + /* Добавление элемента в конец */ + public void Add(int num) { + // При превышении вместимости по числу элементов запускается расширение + if (arrSize == arrCapacity) + ExtendCapacity(); + arr[arrSize] = num; + // Обновить число элементов + arrSize++; + } + + /* Вставка элемента в середину */ + public void Insert(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("индекс выходит за границы"); + // При превышении вместимости по числу элементов запускается расширение + if (arrSize == arrCapacity) + ExtendCapacity(); + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (int j = arrSize - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // Обновить число элементов + arrSize++; + } + + /* Удаление элемента */ + public int Remove(int index) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("индекс выходит за границы"); + int num = arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int j = index; j < arrSize - 1; j++) { + arr[j] = arr[j + 1]; + } + // Обновить число элементов + arrSize--; + // Вернуть удаленный элемент + return num; + } + + /* Расширение списка */ + public void ExtendCapacity() { + // Создать новый массив длиной arrCapacity * extendRatio и скопировать в него исходный массив + Array.Resize(ref arr, arrCapacity * extendRatio); + // Обновить вместимость списка + arrCapacity = arr.Length; + } + + /* Преобразовать список в массив */ + public int[] ToArray() { + // Преобразовывать только элементы списка в пределах фактической длины + int[] arr = new int[arrSize]; + for (int i = 0; i < arrSize; i++) { + arr[i] = Get(i); + } + return arr; + } +} + +public class my_list { + [Test] + public void Test() { + /* Инициализация списка */ + MyList nums = new(); + /* Добавление элемента в конец */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("Список nums = " + string.Join(",", nums.ToArray()) + + ", вместимость = " + nums.Capacity() + " , длина = " + nums.Size()); + + /* Вставка элемента в середину */ + nums.Insert(3, 6); + Console.WriteLine("После вставки числа 6 по индексу 3 nums = " + string.Join(",", nums.ToArray())); + + /* Удаление элемента */ + nums.Remove(3); + Console.WriteLine("После удаления элемента по индексу 3 nums = " + string.Join(",", nums.ToArray())); + + /* Доступ к элементу */ + int num = nums.Get(1); + Console.WriteLine("Элемент по индексу 1: num = " + num); + + /* Обновление элемента */ + nums.Set(1, 0); + Console.WriteLine("После обновления элемента по индексу 1 до 0 nums = " + string.Join(",", nums.ToArray())); + + /* Проверка механизма расширения */ + for (int i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.Add(i); + } + Console.WriteLine("Список nums после увеличения вместимости = " + string.Join(",", nums.ToArray()) + + ", вместимость = " + nums.Capacity() + " , длина = " + nums.Size()); + } +} diff --git a/ru/codes/csharp/chapter_backtracking/n_queens.cs b/ru/codes/csharp/chapter_backtracking/n_queens.cs new file mode 100644 index 000000000..7d6465e80 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/n_queens.cs @@ -0,0 +1,76 @@ +/** + * File: n_queens.cs + * Created Time: 2023-05-04 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class n_queens { + /* Алгоритм бэктрекинга: n ферзей */ + void Backtrack(int row, int n, List> state, List>> res, + bool[] cols, bool[] diags1, bool[] diags2) { + // Когда все строки уже обработаны, записать решение + if (row == n) { + List> copyState = []; + foreach (List sRow in state) { + copyState.Add(new List(sRow)); + } + res.Add(copyState); + return; + } + // Обойти все столбцы + for (int col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Перейти к размещению следующей строки + Backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* Решить задачу о n ферзях */ + List>> NQueens(int n) { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + List> state = []; + for (int i = 0; i < n; i++) { + List row = []; + for (int j = 0; j < n; j++) { + row.Add("#"); + } + state.Add(row); + } + bool[] cols = new bool[n]; // Отмечать, есть ли ферзь в столбце + bool[] diags1 = new bool[2 * n - 1]; // Отмечать наличие ферзя на главной диагонали + bool[] diags2 = new bool[2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали + List>> res = []; + + Backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + [Test] + public void Test() { + int n = 4; + List>> res = NQueens(n); + + Console.WriteLine("Размер входной доски = " + n); + Console.WriteLine("Количество способов расстановки ферзей: " + res.Count); + foreach (List> state in res) { + Console.WriteLine("--------------------"); + foreach (List row in state) { + PrintUtil.PrintList(row); + } + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/permutations_i.cs b/ru/codes/csharp/chapter_backtracking/permutations_i.cs new file mode 100644 index 000000000..6c59e0fa5 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/permutations_i.cs @@ -0,0 +1,53 @@ +/** + * File: permutations_i.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_i { + /* Алгоритм бэктрекинга: все перестановки I */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.Add(choice); + // Перейти к следующему выбору + Backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* Все перестановки I */ + List> PermutationsI(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 3]; + + List> res = PermutationsI(nums); + + Console.WriteLine("Входной массив nums = " + string.Join(", ", nums)); + Console.WriteLine("Все перестановки res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/permutations_ii.cs b/ru/codes/csharp/chapter_backtracking/permutations_ii.cs new file mode 100644 index 000000000..c0f175477 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/permutations_ii.cs @@ -0,0 +1,55 @@ +/** + * File: permutations_ii.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_ii { + /* Алгоритм бэктрекинга: все перестановки II */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // Перебор всех вариантов выбора + HashSet duplicated = []; + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated.Contains(choice)) { + // Попытка: сделать выбор и обновить состояние + duplicated.Add(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.Add(choice); + // Перейти к следующему выбору + Backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* Все перестановки II */ + List> PermutationsII(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 2]; + + List> res = PermutationsII(nums); + + Console.WriteLine("Входной массив nums = " + string.Join(", ", nums)); + Console.WriteLine("Все перестановки res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs b/ru/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs new file mode 100644 index 000000000..18cd35708 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs @@ -0,0 +1,37 @@ +/** + * File: preorder_traversal_i_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_i_compact { + List res = []; + + /* Предварительный обход: пример 1 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + if (root.val == 7) { + // Записать решение + res.Add(root); + } + PreOrder(root.left); + PreOrder(root.right); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\nИнициализация двоичного дерева"); + PrintUtil.PrintTree(root); + + // Предварительный обход + PreOrder(root); + + Console.WriteLine("\nВсе узлы со значением 7"); + PrintUtil.PrintList(res.Select(p => p.val).ToList()); + } +} diff --git a/ru/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs b/ru/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs new file mode 100644 index 000000000..29babf146 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_ii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_ii_compact { + List path = []; + List> res = []; + + /* Предварительный обход: пример 2 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + // Попытка + path.Add(root); + if (root.val == 7) { + // Записать решение + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // Откат + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\nИнициализация двоичного дерева"); + PrintUtil.PrintTree(root); + + // Предварительный обход + PreOrder(root); + + Console.WriteLine("\nВсе пути от корня к узлу 7"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs b/ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs new file mode 100644 index 000000000..5d0731efa --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs @@ -0,0 +1,45 @@ +/** + * File: preorder_traversal_iii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_compact { + List path = []; + List> res = []; + + /* Предварительный обход: пример 3 */ + void PreOrder(TreeNode? root) { + // Отсечение + if (root == null || root.val == 3) { + return; + } + // Попытка + path.Add(root); + if (root.val == 7) { + // Записать решение + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // Откат + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\nИнициализация двоичного дерева"); + PrintUtil.PrintTree(root); + + // Предварительный обход + PreOrder(root); + + Console.WriteLine("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs b/ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs new file mode 100644 index 000000000..c396774d5 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs @@ -0,0 +1,72 @@ +/** + * File: preorder_traversal_iii_template.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_template { + /* Проверить, является ли текущее состояние решением */ + bool IsSolution(List state) { + return state.Count != 0 && state[^1].val == 7; + } + + /* Записать решение */ + void RecordSolution(List state, List> res) { + res.Add(new List(state)); + } + + /* Проверить, допустим ли этот выбор в текущем состоянии */ + bool IsValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* Обновить состояние */ + void MakeChoice(List state, TreeNode choice) { + state.Add(choice); + } + + /* Восстановить состояние */ + void UndoChoice(List state, TreeNode choice) { + state.RemoveAt(state.Count - 1); + } + + /* Алгоритм бэктрекинга: пример 3 */ + void Backtrack(List state, List choices, List> res) { + // Проверить, является ли текущее состояние решением + if (IsSolution(state)) { + // Записать решение + RecordSolution(state, res); + } + // Перебор всех вариантов выбора + foreach (TreeNode choice in choices) { + // Отсечение: проверить допустимость выбора + if (IsValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + MakeChoice(state, choice); + // Перейти к следующему выбору + Backtrack(state, [choice.left!, choice.right!], res); + // Откат: отменить выбор и восстановить предыдущее состояние + UndoChoice(state, choice); + } + } + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\nИнициализация двоичного дерева"); + PrintUtil.PrintTree(root); + + // Алгоритм бэктрекинга + List> res = []; + List choices = [root!]; + Backtrack([], choices, res); + + Console.WriteLine("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/subset_sum_i.cs b/ru/codes/csharp/chapter_backtracking/subset_sum_i.cs new file mode 100644 index 000000000..9682bb7d8 --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/subset_sum_i.cs @@ -0,0 +1,55 @@ +/** +* File: subset_sum_i.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i { + /* Алгоритм бэктрекинга: сумма подмножеств I */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.Add(new List(state)); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (int i = start; i < choices.Length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state.Add(choices[i]); + // Перейти к следующему выбору + Backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.RemoveAt(state.Count - 1); + } + } + + /* Решить задачу суммы подмножеств I */ + List> SubsetSumI(int[] nums, int target) { + List state = []; // Состояние (подмножество) + Array.Sort(nums); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + List> res = []; // Список результатов (список подмножеств) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumI(nums, target); + Console.WriteLine("Входной массив nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("Все подмножества с суммой " + target + ": res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/ru/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs b/ru/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs new file mode 100644 index 000000000..68a2594fa --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs @@ -0,0 +1,53 @@ +/** +* File: subset_sum_i_naive.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i_naive { + /* Алгоритм бэктрекинга: сумма подмножеств I */ + void Backtrack(List state, int target, int total, int[] choices, List> res) { + // Если сумма подмножества равна target, записать решение + if (total == target) { + res.Add(new List(state)); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.Length; i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.Add(choices[i]); + // Перейти к следующему выбору + Backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.RemoveAt(state.Count - 1); + } + } + + /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ + List> SubsetSumINaive(int[] nums, int target) { + List state = []; // Состояние (подмножество) + int total = 0; // Сумма подмножеств + List> res = []; // Список результатов (список подмножеств) + Backtrack(state, target, total, nums, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumINaive(nums, target); + Console.WriteLine("Входной массив nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("Все подмножества с суммой " + target + ": res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + Console.WriteLine("Обратите внимание: результат этого метода содержит повторяющиеся множества"); + } +} diff --git a/ru/codes/csharp/chapter_backtracking/subset_sum_ii.cs b/ru/codes/csharp/chapter_backtracking/subset_sum_ii.cs new file mode 100644 index 000000000..2c7ffae6d --- /dev/null +++ b/ru/codes/csharp/chapter_backtracking/subset_sum_ii.cs @@ -0,0 +1,60 @@ +/** +* File: subset_sum_ii.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_ii { + /* Алгоритм бэктрекинга: сумма подмножеств II */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.Add(new List(state)); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (int i = start; i < choices.Length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.Add(choices[i]); + // Перейти к следующему выбору + Backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.RemoveAt(state.Count - 1); + } + } + + /* Решить задачу суммы подмножеств II */ + List> SubsetSumII(int[] nums, int target) { + List state = []; // Состояние (подмножество) + Array.Sort(nums); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + List> res = []; // Список результатов (список подмножеств) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [4, 4, 5]; + int target = 9; + List> res = SubsetSumII(nums, target); + Console.WriteLine("Входной массив nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("Все подмножества с суммой " + target + ": res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/ru/codes/csharp/chapter_computational_complexity/iteration.cs b/ru/codes/csharp/chapter_computational_complexity/iteration.cs new file mode 100644 index 000000000..318e43363 --- /dev/null +++ b/ru/codes/csharp/chapter_computational_complexity/iteration.cs @@ -0,0 +1,77 @@ +/** +* File: iteration.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class iteration { + /* Цикл for */ + int ForLoop(int n) { + int res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* Цикл while */ + int WhileLoop(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i += 1; // Обновить условную переменную + } + return res; + } + + /* Цикл while (двойное обновление) */ + int WhileLoopII(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i += 1; + i *= 2; + } + return res; + } + + /* Двойной цикл for */ + string NestedForLoop(int n) { + StringBuilder res = new(); + // Цикл по i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // Цикл по j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.Append($"({i}, {j}), "); + } + } + return res.ToString(); + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = ForLoop(n); + Console.WriteLine("\nРезультат суммирования в цикле for res = " + res); + + res = WhileLoop(n); + Console.WriteLine("\nРезультат суммирования в цикле while res = " + res); + + res = WhileLoopII(n); + Console.WriteLine("\nРезультат суммирования в цикле while (двойное обновление) res = " + res); + + string resStr = NestedForLoop(n); + Console.WriteLine("\nРезультат обхода в двойном цикле for " + resStr); + } +} diff --git a/ru/codes/csharp/chapter_computational_complexity/recursion.cs b/ru/codes/csharp/chapter_computational_complexity/recursion.cs new file mode 100644 index 000000000..7f8bd218f --- /dev/null +++ b/ru/codes/csharp/chapter_computational_complexity/recursion.cs @@ -0,0 +1,78 @@ +/** +* File: recursion.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class recursion { + /* Рекурсия */ + int Recur(int n) { + // Условие завершения + if (n == 1) + return 1; + // Рекурсия: рекурсивный вызов + int res = Recur(n - 1); + // Возврат: вернуть результат + return n + res; + } + + /* Имитация рекурсии итерацией */ + int ForLoopRecur(int n) { + // Использовать явный стек для имитации системного стека вызовов + Stack stack = new(); + int res = 0; + // Рекурсия: рекурсивный вызов + for (int i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.Push(i); + } + // Возврат: вернуть результат + while (stack.Count > 0) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.Pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* Хвостовая рекурсия */ + int TailRecur(int n, int res) { + // Условие завершения + if (n == 0) + return res; + // Хвостовой рекурсивный вызов + return TailRecur(n - 1, res + n); + } + + /* Последовательность Фибоначчи: рекурсия */ + int Fib(int n) { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + int res = Fib(n - 1) + Fib(n - 2); + // Вернуть результат f(n) + return res; + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = Recur(n); + Console.WriteLine("\nРезультат суммирования в рекурсивной функции res = " + res); + + res = ForLoopRecur(n); + Console.WriteLine("\nРезультат суммирования при имитации рекурсии итерацией res = " + res); + + res = TailRecur(n, 0); + Console.WriteLine("\nРезультат суммирования в хвостовой рекурсии res = " + res); + + res = Fib(n); + Console.WriteLine("\nЧлен последовательности Фибоначчи с номером " + n + " = " + res); + } +} diff --git a/ru/codes/csharp/chapter_computational_complexity/space_complexity.cs b/ru/codes/csharp/chapter_computational_complexity/space_complexity.cs new file mode 100644 index 000000000..e30af8b36 --- /dev/null +++ b/ru/codes/csharp/chapter_computational_complexity/space_complexity.cs @@ -0,0 +1,104 @@ +/** + * File: space_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class space_complexity { + /* Функция */ + int Function() { + // Выполнить некоторые операции + return 0; + } + + /* Постоянная сложность */ + void Constant(int n) { + // Константы, переменные и объекты занимают O(1) памяти + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new(0); + // Переменные в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + int c = 0; + } + // Функции в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + Function(); + } + } + + /* Линейная сложность */ + void Linear(int n) { + // Массив длины n занимает O(n) памяти + int[] nums = new int[n]; + // Список длины n занимает O(n) памяти + List nodes = []; + for (int i = 0; i < n; i++) { + nodes.Add(new ListNode(i)); + } + // Хеш-таблица длины n занимает O(n) памяти + Dictionary map = []; + for (int i = 0; i < n; i++) { + map.Add(i, i.ToString()); + } + } + + /* Линейная сложность (рекурсивная реализация) */ + void LinearRecur(int n) { + Console.WriteLine("Рекурсия n = " + n); + if (n == 1) return; + LinearRecur(n - 1); + } + + /* Квадратичная сложность */ + void Quadratic(int n) { + // Матрица занимает O(n^2) памяти + int[,] numMatrix = new int[n, n]; + // Двумерный список занимает O(n^2) памяти + List> numList = []; + for (int i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.Add(0); + } + numList.Add(tmp); + } + } + + /* Квадратичная сложность (рекурсивная реализация) */ + int QuadraticRecur(int n) { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("В рекурсии n = " + n + ", длина nums = " + nums.Length); + return QuadraticRecur(n - 1); + } + + /* Экспоненциальная сложность (построение полного двоичного дерева) */ + TreeNode? BuildTree(int n) { + if (n == 0) return null; + TreeNode root = new(0) { + left = BuildTree(n - 1), + right = BuildTree(n - 1) + }; + return root; + } + + [Test] + public void Test() { + int n = 5; + // Постоянная сложность + Constant(n); + // Линейная сложность + Linear(n); + LinearRecur(n); + // Квадратичная сложность + Quadratic(n); + QuadraticRecur(n); + // Экспоненциальная сложность + TreeNode? root = BuildTree(n); + PrintUtil.PrintTree(root); + } +} diff --git a/ru/codes/csharp/chapter_computational_complexity/time_complexity.cs b/ru/codes/csharp/chapter_computational_complexity/time_complexity.cs new file mode 100644 index 000000000..f60495bfb --- /dev/null +++ b/ru/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -0,0 +1,195 @@ +/** + * File: time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class time_complexity { + void Algorithm(int n) { + int a = 1; // +0 (прием 1) + a += n; // +0 (прием 1) + // +n (прием 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n (прием 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + + // Временная сложность алгоритма A: константная + void AlgorithmA(int n) { + Console.WriteLine(0); + } + + // Временная сложность алгоритма B: линейная + void AlgorithmB(int n) { + for (int i = 0; i < n; i++) { + Console.WriteLine(0); + } + } + + // Временная сложность алгоритма C: константная + void AlgorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + Console.WriteLine(0); + } + } + + /* Постоянная сложность */ + int Constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* Линейная сложность */ + int Linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* Линейная сложность (обход массива) */ + int ArrayTraversal(int[] nums) { + int count = 0; + // Число итераций пропорционально длине массива + foreach (int num in nums) { + count++; + } + return count; + } + + /* Квадратичная сложность */ + int Quadratic(int n) { + int count = 0; + // Число итераций квадратично зависит от размера данных n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* Квадратичная сложность (пузырьковая сортировка) */ + int BubbleSort(int[] nums) { + int count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; + } + + /* Экспоненциальная сложность (итеративная реализация) */ + int Exponential(int n) { + int count = 0, bas = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* Экспоненциальная сложность (рекурсивная реализация) */ + int ExpRecur(int n) { + if (n == 1) return 1; + return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; + } + + /* Логарифмическая сложность (итеративная реализация) */ + int Logarithmic(int n) { + int count = 0; + while (n > 1) { + n /= 2; + count++; + } + return count; + } + + /* Логарифмическая сложность (рекурсивная реализация) */ + int LogRecur(int n) { + if (n <= 1) return 0; + return LogRecur(n / 2) + 1; + } + + /* Линейно-логарифмическая сложность */ + int LinearLogRecur(int n) { + if (n <= 1) return 1; + int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; + } + + /* Факториальная сложность (рекурсивная реализация) */ + int FactorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // Из одного получается n + for (int i = 0; i < n; i++) { + count += FactorialRecur(n - 1); + } + return count; + } + + [Test] + public void Test() { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + int n = 8; + Console.WriteLine("Размер входных данных n = " + n); + + int count = Constant(n); + Console.WriteLine("Число операций константной сложности = " + count); + + count = Linear(n); + Console.WriteLine("Число операций линейной сложности = " + count); + count = ArrayTraversal(new int[n]); + Console.WriteLine("Число операций линейной сложности (обход массива) = " + count); + + count = Quadratic(n); + Console.WriteLine("Число операций квадратичной сложности = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = BubbleSort(nums); + Console.WriteLine("Число операций квадратичной сложности (пузырьковая сортировка) = " + count); + + count = Exponential(n); + Console.WriteLine("Число операций экспоненциальной сложности (итеративная реализация) = " + count); + count = ExpRecur(n); + Console.WriteLine("Число операций экспоненциальной сложности (рекурсивная реализация) = " + count); + + count = Logarithmic(n); + Console.WriteLine("Число операций логарифмической сложности (итеративная реализация) = " + count); + count = LogRecur(n); + Console.WriteLine("Число операций логарифмической сложности (рекурсивная реализация) = " + count); + + count = LinearLogRecur(n); + Console.WriteLine("Число операций линейно-логарифмической сложности (рекурсивная реализация) = " + count); + + count = FactorialRecur(n); + Console.WriteLine("Число операций факториальной сложности (рекурсивная реализация) = " + count); + } +} diff --git a/ru/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs b/ru/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs new file mode 100644 index 000000000..330b9f542 --- /dev/null +++ b/ru/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs @@ -0,0 +1,49 @@ +/** + * File: worst_best_time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class worst_best_time_complexity { + /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ + int[] RandomNumbers(int n) { + int[] nums = new int[n]; + // Создать массив nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + + // Случайно перемешать элементы массива + for (int i = 0; i < nums.Length; i++) { + int index = new Random().Next(i, nums.Length); + (nums[i], nums[index]) = (nums[index], nums[i]); + } + return nums; + } + + /* Найти индекс числа 1 в массиве nums */ + int FindOne(int[] nums) { + for (int i = 0; i < nums.Length; i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + + /* Driver Code */ + [Test] + public void Test() { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = RandomNumbers(n); + int index = FindOne(nums); + Console.WriteLine("\nМассив [1, 2, ..., n] после перемешивания = " + string.Join(",", nums)); + Console.WriteLine("Индекс числа 1 = " + index); + } + } +} diff --git a/ru/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs b/ru/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs new file mode 100644 index 000000000..0227e8587 --- /dev/null +++ b/ru/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs @@ -0,0 +1,46 @@ +/** +* File: binary_search_recur.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class binary_search_recur { + /* Бинарный поиск: задача f(i, j) */ + int DFS(int[] nums, int target, int i, int j) { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + int m = (i + j) / 2; + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return DFS(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return DFS(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + + /* Бинарный поиск */ + int BinarySearch(int[] nums, int target) { + int n = nums.Length; + // Решить задачу f(0, n-1) + return DFS(nums, target, 0, n - 1); + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // Бинарный поиск (двусторонне замкнутый интервал) + int index = BinarySearch(nums, target); + Console.WriteLine("Индекс целевого элемента 6 = " + index); + } +} diff --git a/ru/codes/csharp/chapter_divide_and_conquer/build_tree.cs b/ru/codes/csharp/chapter_divide_and_conquer/build_tree.cs new file mode 100644 index 000000000..a732553f7 --- /dev/null +++ b/ru/codes/csharp/chapter_divide_and_conquer/build_tree.cs @@ -0,0 +1,49 @@ +/** +* File: build_tree.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class build_tree { + /* Построить двоичное дерево: разделяй и властвуй */ + TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) + return null; + // Инициализировать корневой узел + TreeNode root = new(preorder[i]); + // Найти m, чтобы разделить левое и правое поддеревья + int m = inorderMap[preorder[i]]; + // Подзадача: построить левое поддерево + root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + return root; + } + + /* Построить двоичное дерево */ + TreeNode? BuildTree(int[] preorder, int[] inorder) { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + Dictionary inorderMap = []; + for (int i = 0; i < inorder.Length; i++) { + inorderMap.TryAdd(inorder[i], i); + } + TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); + return root; + } + + [Test] + public void Test() { + int[] preorder = [3, 9, 2, 1, 7]; + int[] inorder = [9, 3, 1, 2, 7]; + Console.WriteLine("Предварительный обход = " + string.Join(", ", preorder)); + Console.WriteLine("Симметричный обход = " + string.Join(", ", inorder)); + + TreeNode? root = BuildTree(preorder, inorder); + Console.WriteLine("Построенное двоичное дерево:"); + PrintUtil.PrintTree(root); + } +} diff --git a/ru/codes/csharp/chapter_divide_and_conquer/hanota.cs b/ru/codes/csharp/chapter_divide_and_conquer/hanota.cs new file mode 100644 index 000000000..dab44215e --- /dev/null +++ b/ru/codes/csharp/chapter_divide_and_conquer/hanota.cs @@ -0,0 +1,59 @@ +/** +* File: hanota.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class hanota { + /* Переместить один диск */ + void Move(List src, List tar) { + // Снять диск с вершины src + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // Положить диск на вершину tar + tar.Add(pan); + } + + /* Решить задачу Ханойской башни f(i) */ + void DFS(int i, List src, List buf, List tar) { + // Если в src остался только один диск, сразу переместить его в tar + if (i == 1) { + Move(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + DFS(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + Move(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + DFS(i - 1, buf, src, tar); + } + + /* Решить задачу Ханойской башни */ + void SolveHanota(List A, List B, List C) { + int n = A.Count; + // Переместить верхние n дисков из A в C с помощью B + DFS(n, A, B, C); + } + + [Test] + public void Test() { + // Хвост списка соответствует вершине столбца + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + Console.WriteLine("Исходное состояние:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + + SolveHanota(A, B, C); + + Console.WriteLine("После завершения перемещения дисков:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs new file mode 100644 index 000000000..d8f418c4c --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs @@ -0,0 +1,41 @@ +/** +* File: climbing_stairs_backtrack.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_backtrack { + /* Бэктрекинг */ + void Backtrack(List choices, int state, int n, List res) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) + res[0]++; + // Перебор всех вариантов выбора + foreach (int choice in choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) + continue; + // Попытка: сделать выбор и обновить состояние + Backtrack(choices, state + choice, n, res); + // Откат + } + } + + /* Подъем по лестнице: бэктрекинг */ + int ClimbingStairsBacktrack(int n) { + List choices = [1, 2]; // Можно подняться на 1 или 2 ступени + int state = 0; // Начать подъем с 0-й ступени + List res = [0]; // Использовать res[0] для хранения числа решений + Backtrack(choices, state, n, res); + return res[0]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsBacktrack(n); + Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs new file mode 100644 index 000000000..6331ffcb8 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs @@ -0,0 +1,36 @@ +/** +* File: climbing_stairs_constraint_dp.cs +* Created Time: 2023-07-03 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* Подъем по лестнице с ограничениями: динамическое программирование */ + int ClimbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + int[,] dp = new int[n + 1, 3]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1, 1] = 1; + dp[1, 2] = 0; + dp[2, 1] = 0; + dp[2, 2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i, 1] = dp[i - 1, 2]; + dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; + } + return dp[n, 1] + dp[n, 2]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsConstraintDP(n); + Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs new file mode 100644 index 000000000..de677fcf1 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs @@ -0,0 +1,31 @@ +/** +* File: climbing_stairs_dfs.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* Поиск */ + int DFS(int i) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = DFS(i - 1) + DFS(i - 2); + return count; + } + + /* Подъем по лестнице: поиск */ + int ClimbingStairsDFS(int n) { + return DFS(n); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFS(n); + Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs new file mode 100644 index 000000000..94628f521 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs @@ -0,0 +1,39 @@ +/** +* File: climbing_stairs_dfs_mem.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs_mem { + /* Поиск с мемоизацией */ + int DFS(int i, int[] mem) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = DFS(i - 1, mem) + DFS(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; + } + + /* Подъем по лестнице: поиск с мемоизацией */ + int ClimbingStairsDFSMem(int n) { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + int[] mem = new int[n + 1]; + Array.Fill(mem, -1); + return DFS(n, mem); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFSMem(n); + Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs new file mode 100644 index 000000000..53c8e171d --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs @@ -0,0 +1,49 @@ +/** +* File: climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* Подъем по лестнице: динамическое программирование */ + int ClimbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // Инициализация таблицы dp для хранения решений подзадач + int[] dp = new int[n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ + int ClimbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int n = 9; + + int res = ClimbingStairsDP(n); + Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); + + res = ClimbingStairsDPComp(n); + Console.WriteLine($"Количество способов подняться по лестнице из {n} ступеней = {res}"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/coin_change.cs b/ru/codes/csharp/chapter_dynamic_programming/coin_change.cs new file mode 100644 index 000000000..bb73e89c8 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/coin_change.cs @@ -0,0 +1,71 @@ +/** +* File: coin_change.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change { + /* Размен монет: динамическое программирование */ + int CoinChangeDP(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // Инициализация таблицы dp + int[,] dp = new int[n + 1, amt + 1]; + // Переход состояний: первая строка и первый столбец + for (int a = 1; a <= amt; a++) { + dp[0, a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i, a] = dp[i - 1, a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); + } + } + } + return dp[n, amt] != MAX ? dp[n, amt] : -1; + } + + /* Размен монет: динамическое программирование с оптимизацией памяти */ + int CoinChangeDPComp(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // Инициализация таблицы dp + int[] dp = new int[amt + 1]; + Array.Fill(dp, MAX); + dp[0] = 0; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 4; + + // Динамическое программирование + int res = CoinChangeDP(coins, amt); + Console.WriteLine("Минимальное число монет для набора целевой суммы = " + res); + + // Динамическое программирование с оптимизацией памяти + res = CoinChangeDPComp(coins, amt); + Console.WriteLine("Минимальное число монет для набора целевой суммы = " + res); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs b/ru/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs new file mode 100644 index 000000000..01fa17e3c --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs @@ -0,0 +1,68 @@ +/** +* File: coin_change_ii.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change_ii { + /* Размен монет II: динамическое программирование */ + int CoinChangeIIDP(int[] coins, int amt) { + int n = coins.Length; + // Инициализация таблицы dp + int[,] dp = new int[n + 1, amt + 1]; + // Инициализация первого столбца + for (int i = 0; i <= n; i++) { + dp[i, 0] = 1; + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i, a] = dp[i - 1, a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; + } + } + } + return dp[n, amt]; + } + + /* Размен монет II: динамическое программирование с оптимизацией памяти */ + int CoinChangeIIDPComp(int[] coins, int amt) { + int n = coins.Length; + // Инициализация таблицы dp + int[] dp = new int[amt + 1]; + dp[0] = 1; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 5; + + // Динамическое программирование + int res = CoinChangeIIDP(coins, amt); + Console.WriteLine("Количество комбинаций монет для набора целевой суммы = " + res); + + // Динамическое программирование с оптимизацией памяти + res = CoinChangeIIDPComp(coins, amt); + Console.WriteLine("Количество комбинаций монет для набора целевой суммы = " + res); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/edit_distance.cs b/ru/codes/csharp/chapter_dynamic_programming/edit_distance.cs new file mode 100644 index 000000000..afc1848bb --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/edit_distance.cs @@ -0,0 +1,141 @@ +/** +* File: edit_distance.cs +* Created Time: 2023-07-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class edit_distance { + /* Редакционное расстояние: полный перебор */ + int EditDistanceDFS(string s, string t, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) + return EditDistanceDFS(s, t, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = EditDistanceDFS(s, t, i, j - 1); + int delete = EditDistanceDFS(s, t, i - 1, j); + int replace = EditDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return Math.Min(Math.Min(insert, delete), replace) + 1; + } + + /* Редакционное расстояние: поиск с мемоизацией */ + int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) + return mem[i][j]; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) + return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); + int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); + int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* Редакционное расстояние: динамическое программирование */ + int EditDistanceDP(string s, string t) { + int n = s.Length, m = t.Length; + int[,] dp = new int[n + 1, m + 1]; + // Переход состояний: первая строка и первый столбец + for (int i = 1; i <= n; i++) { + dp[i, 0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0, j] = j; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[i, j] = dp[i - 1, j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; + } + } + } + return dp[n, m]; + } + + /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ + int EditDistanceDPComp(string s, string t) { + int n = s.Length, m = t.Length; + int[] dp = new int[m + 1]; + // Переход состояний: первая строка + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (int i = 1; i <= n; i++) { + // Переход состояний: первый столбец + int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; + } + + [Test] + public void Test() { + string s = "bag"; + string t = "pack"; + int n = s.Length, m = t.Length; + + // Полный перебор + int res = EditDistanceDFS(s, t, n, m); + Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); + + // Поиск с мемоизацией + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[m + 1]; + Array.Fill(mem[i], -1); + } + + res = EditDistanceDFSMem(s, t, mem, n, m); + Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); + + // Динамическое программирование + res = EditDistanceDP(s, t); + Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); + + // Динамическое программирование с оптимизацией памяти + res = EditDistanceDPComp(s, t); + Console.WriteLine("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов редактирования"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/knapsack.cs b/ru/codes/csharp/chapter_dynamic_programming/knapsack.cs new file mode 100644 index 000000000..c04e15bc6 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/knapsack.cs @@ -0,0 +1,118 @@ +/** +* File: knapsack.cs +* Created Time: 2023-07-07 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class knapsack { + /* Рюкзак 0-1: полный перебор */ + int KnapsackDFS(int[] weight, int[] val, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (weight[i - 1] > c) { + return KnapsackDFS(weight, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = KnapsackDFS(weight, val, i - 1, c); + int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return Math.Max(no, yes); + } + + /* Рюкзак 0-1: поиск с мемоизацией */ + int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (weight[i - 1] > c) { + return KnapsackDFSMem(weight, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = KnapsackDFSMem(weight, val, mem, i - 1, c); + int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = Math.Max(no, yes); + return mem[i][c]; + } + + /* Рюкзак 0-1: динамическое программирование */ + int KnapsackDP(int[] weight, int[] val, int cap) { + int n = weight.Length; + // Инициализация таблицы dp + int[,] dp = new int[n + 1, cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (weight[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i, c] = dp[i - 1, c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); + } + } + } + return dp[n, cap]; + } + + /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ + int KnapsackDPComp(int[] weight, int[] val, int cap) { + int n = weight.Length; + // Инициализация таблицы dp + int[] dp = new int[cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + // Обход в обратном порядке + for (int c = cap; c > 0; c--) { + if (weight[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] weight = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = weight.Length; + + // Полный перебор + int res = KnapsackDFS(weight, val, n, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Поиск с мемоизацией + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[cap + 1]; + Array.Fill(mem[i], -1); + } + res = KnapsackDFSMem(weight, val, mem, n, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Динамическое программирование + res = KnapsackDP(weight, val, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Динамическое программирование с оптимизацией памяти + res = KnapsackDPComp(weight, val, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs b/ru/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs new file mode 100644 index 000000000..0ff229d93 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs @@ -0,0 +1,53 @@ +/** +* File: min_cost_climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_cost_climbing_stairs_dp { + /* Минимальная стоимость подъема по лестнице: динамическое программирование */ + int MinCostClimbingStairsDP(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + // Инициализация таблицы dp для хранения решений подзадач + int[] dp = new int[n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ + int MinCostClimbingStairsDPComp(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.Min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + Console.WriteLine("Список стоимостей ступеней:"); + PrintUtil.PrintList(cost); + + int res = MinCostClimbingStairsDP(cost); + Console.WriteLine($"Минимальная стоимость подъема по лестнице = {res}"); + + res = MinCostClimbingStairsDPComp(cost); + Console.WriteLine($"Минимальная стоимость подъема по лестнице = {res}"); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/min_path_sum.cs b/ru/codes/csharp/chapter_dynamic_programming/min_path_sum.cs new file mode 100644 index 000000000..7ffe88a64 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/min_path_sum.cs @@ -0,0 +1,127 @@ +/** +* File: min_path_sum.cs +* Created Time: 2023-07-10 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_path_sum { + /* Минимальная сумма пути: полный перебор */ + int MinPathSumDFS(int[][] grid, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return int.MaxValue; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + int up = MinPathSumDFS(grid, i - 1, j); + int left = MinPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return Math.Min(left, up) + grid[i][j]; + } + + /* Минимальная сумма пути: поиск с мемоизацией */ + int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return int.MaxValue; + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + int up = MinPathSumDFSMem(grid, mem, i - 1, j); + int left = MinPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = Math.Min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* Минимальная сумма пути: динамическое программирование */ + int MinPathSumDP(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // Инициализация таблицы dp + int[,] dp = new int[n, m]; + dp[0, 0] = grid[0][0]; + // Переход состояний: первая строка + for (int j = 1; j < m; j++) { + dp[0, j] = dp[0, j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (int i = 1; i < n; i++) { + dp[i, 0] = dp[i - 1, 0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; + } + } + return dp[n - 1, m - 1]; + } + + /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ + int MinPathSumDPComp(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // Инициализация таблицы dp + int[] dp = new int[m]; + dp[0] = grid[0][0]; + // Переход состояний: первая строка + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (int i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (int j = 1; j < m; j++) { + dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + [Test] + public void Test() { + int[][] grid = + [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2] + ]; + + int n = grid.Length, m = grid[0].Length; + + // Полный перебор + int res = MinPathSumDFS(grid, n - 1, m - 1); + Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + + // Поиск с мемоизацией + int[][] mem = new int[n][]; + for (int i = 0; i < n; i++) { + mem[i] = new int[m]; + Array.Fill(mem[i], -1); + } + res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); + Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + + // Динамическое программирование + res = MinPathSumDP(grid); + Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + + // Динамическое программирование с оптимизацией памяти + res = MinPathSumDPComp(grid); + Console.WriteLine("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + } +} diff --git a/ru/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs b/ru/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs new file mode 100644 index 000000000..be198e848 --- /dev/null +++ b/ru/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs @@ -0,0 +1,64 @@ +/** +* File: unbounded_knapsack.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class unbounded_knapsack { + /* Полный рюкзак: динамическое программирование */ + int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // Инициализация таблицы dp + int[,] dp = new int[n + 1, cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i, c] = dp[i - 1, c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n, cap]; + } + + /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ + int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // Инициализация таблицы dp + int[] dp = new int[cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] wgt = [1, 2, 3]; + int[] val = [5, 11, 15]; + int cap = 4; + + // Динамическое программирование + int res = UnboundedKnapsackDP(wgt, val, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Динамическое программирование с оптимизацией памяти + res = UnboundedKnapsackDPComp(wgt, val, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + } +} diff --git a/ru/codes/csharp/chapter_graph/graph_adjacency_list.cs b/ru/codes/csharp/chapter_graph/graph_adjacency_list.cs new file mode 100644 index 000000000..c0ad57623 --- /dev/null +++ b/ru/codes/csharp/chapter_graph/graph_adjacency_list.cs @@ -0,0 +1,122 @@ +/** + * File: graph_adjacency_list.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* Класс неориентированного графа на основе списка смежности */ +public class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + public Dictionary> adjList; + + /* Конструктор */ + public GraphAdjList(Vertex[][] edges) { + adjList = []; + // Добавить все вершины и ребра + foreach (Vertex[] edge in edges) { + AddVertex(edge[0]); + AddVertex(edge[1]); + AddEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + int Size() { + return adjList.Count; + } + + /* Добавление ребра */ + public void AddEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // Добавить ребро vet1 - vet2 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* Удаление ребра */ + public void RemoveEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // Удалить ребро vet1 - vet2 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* Добавление вершины */ + public void AddVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // Добавить новый список в список смежности + adjList.Add(vet, []); + } + + /* Удаление вершины */ + public void RemoveVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // Удалить из списка смежности список, соответствующий вершине vet + adjList.Remove(vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* Вывести список смежности */ + public void Print() { + Console.WriteLine("Список смежности ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = []; + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } +} + +public class graph_adjacency_list { + [Test] + public void Test() { + /* Инициализация неориентированного графа */ + Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); + Vertex[][] edges = + [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]] + ]; + GraphAdjList graph = new(edges); + Console.WriteLine("\nГраф после инициализации"); + graph.Print(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.AddEdge(v[0], v[2]); + Console.WriteLine("\nГраф после добавления ребра 1-2"); + graph.Print(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.RemoveEdge(v[0], v[1]); + Console.WriteLine("\nГраф после удаления ребра 1-3"); + graph.Print(); + + /* Добавление вершины */ + Vertex v5 = new(6); + graph.AddVertex(v5); + Console.WriteLine("\nГраф после добавления вершины 6"); + graph.Print(); + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.RemoveVertex(v[1]); + Console.WriteLine("\nГраф после удаления вершины 3"); + graph.Print(); + } +} diff --git a/ru/codes/csharp/chapter_graph/graph_adjacency_matrix.cs b/ru/codes/csharp/chapter_graph/graph_adjacency_matrix.cs new file mode 100644 index 000000000..0d11468b8 --- /dev/null +++ b/ru/codes/csharp/chapter_graph/graph_adjacency_matrix.cs @@ -0,0 +1,137 @@ +/** + * File: graph_adjacency_matrix.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + List vertices; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + List> adjMat; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = []; + this.adjMat = []; + // Добавление вершины + foreach (int val in vertices) { + AddVertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + foreach (int[] e in edges) { + AddEdge(e[0], e[1]); + } + } + + /* Получить число вершин */ + int Size() { + return vertices.Count; + } + + /* Добавление вершины */ + public void AddVertex(int val) { + int n = Size(); + // Добавить значение новой вершины в список вершин + vertices.Add(val); + // Добавить строку в матрицу смежности + List newRow = new(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); + // Добавить столбец в матрицу смежности + foreach (List row in adjMat) { + row.Add(0); + } + } + + /* Удаление вершины */ + public void RemoveVertex(int index) { + if (index >= Size()) + throw new IndexOutOfRangeException(); + // Удалить вершину с индексом index из списка вершин + vertices.RemoveAt(index); + // Удалить строку с индексом index из матрицы смежности + adjMat.RemoveAt(index); + // Удалить столбец с индексом index из матрицы смежности + foreach (List row in adjMat) { + row.RemoveAt(index); + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + public void AddEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + public void RemoveEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* Вывести матрицу смежности */ + public void Print() { + Console.Write("Список вершин = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("Матрица смежности ="); + PrintUtil.PrintMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + [Test] + public void Test() { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + int[] vertices = [1, 3, 2, 5, 4]; + int[][] edges = + [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4] + ]; + GraphAdjMat graph = new(vertices, edges); + Console.WriteLine("\nГраф после инициализации"); + graph.Print(); + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.AddEdge(0, 2); + Console.WriteLine("\nГраф после добавления ребра 1-2"); + graph.Print(); + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.RemoveEdge(0, 1); + Console.WriteLine("\nГраф после удаления ребра 1-3"); + graph.Print(); + + /* Добавление вершины */ + graph.AddVertex(6); + Console.WriteLine("\nГраф после добавления вершины 6"); + graph.Print(); + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.RemoveVertex(1); + Console.WriteLine("\nГраф после удаления вершины 3"); + graph.Print(); + } +} diff --git a/ru/codes/csharp/chapter_graph/graph_bfs.cs b/ru/codes/csharp/chapter_graph/graph_bfs.cs new file mode 100644 index 000000000..79e6da4b2 --- /dev/null +++ b/ru/codes/csharp/chapter_graph/graph_bfs.cs @@ -0,0 +1,58 @@ +/** + * File: graph_bfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_bfs { + /* Обход в ширину */ + // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины + List GraphBFS(GraphAdjList graph, Vertex startVet) { + // Последовательность обхода вершин + List res = []; + // Хеш-множество для хранения уже посещенных вершин + HashSet visited = [startVet]; + // Очередь используется для реализации BFS + Queue que = new(); + que.Enqueue(startVet); + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // Извлечь головную вершину из очереди + res.Add(vet); // Отметить посещенную вершину + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + que.Enqueue(adjVet); // Помещать в очередь только непосещенные вершины + visited.Add(adjVet); // Отметить эту вершину как посещенную + } + } + + // Вернуть последовательность обхода вершин + return res; + } + + [Test] + public void Test() { + /* Инициализация неориентированного графа */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + Vertex[][] edges = + [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], + [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], + [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\nГраф после инициализации"); + graph.Print(); + + /* Обход в ширину */ + List res = GraphBFS(graph, v[0]); + Console.WriteLine("\nПоследовательность вершин при обходе в ширину (BFS)"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/ru/codes/csharp/chapter_graph/graph_dfs.cs b/ru/codes/csharp/chapter_graph/graph_dfs.cs new file mode 100644 index 000000000..03a527f29 --- /dev/null +++ b/ru/codes/csharp/chapter_graph/graph_dfs.cs @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_dfs { + /* Вспомогательная функция обхода в глубину */ + void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // Отметить посещенную вершину + visited.Add(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + // Рекурсивно обходить смежные вершины + DFS(graph, visited, res, adjVet); + } + } + + /* Обход в глубину */ + // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины + List GraphDFS(GraphAdjList graph, Vertex startVet) { + // Последовательность обхода вершин + List res = []; + // Хеш-множество для хранения уже посещенных вершин + HashSet visited = []; + DFS(graph, visited, res, startVet); + return res; + } + + [Test] + public void Test() { + /* Инициализация неориентированного графа */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); + Vertex[][] edges = + [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\nГраф после инициализации"); + graph.Print(); + + /* Обход в глубину */ + List res = GraphDFS(graph, v[0]); + Console.WriteLine("\nПоследовательность вершин при обходе в глубину (DFS)"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/ru/codes/csharp/chapter_greedy/coin_change_greedy.cs b/ru/codes/csharp/chapter_greedy/coin_change_greedy.cs new file mode 100644 index 000000000..e285c2941 --- /dev/null +++ b/ru/codes/csharp/chapter_greedy/coin_change_greedy.cs @@ -0,0 +1,54 @@ +/** +* File: coin_change_greedy.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class coin_change_greedy { + /* Размен монет: жадный алгоритм */ + int CoinChangeGreedy(int[] coins, int amt) { + // Предположить, что список coins упорядочен + int i = coins.Length - 1; + int count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt == 0 ? count : -1; + } + + [Test] + public void Test() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + int[] coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("Минимальное число монет для набора суммы " + amt + " = " + res); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 20, 50]; + amt = 60; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("Минимальное число монет для набора суммы " + amt + " = " + res); + Console.WriteLine("На самом деле минимум равен 3: 20 + 20 + 20"); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 49, 50]; + amt = 98; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("Минимальное число монет для набора суммы " + amt + " = " + res); + Console.WriteLine("На самом деле минимум равен 2: 49 + 49"); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/chapter_greedy/fractional_knapsack.cs b/ru/codes/csharp/chapter_greedy/fractional_knapsack.cs new file mode 100644 index 000000000..3b2f48bd2 --- /dev/null +++ b/ru/codes/csharp/chapter_greedy/fractional_knapsack.cs @@ -0,0 +1,52 @@ +/** +* File: fractional_knapsack.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +/* Предмет */ +class Item(int w, int v) { + public int w = w; // Вес предмета + public int v = v; // Стоимость предмета +} + +public class fractional_knapsack { + /* Дробный рюкзак: жадный алгоритм */ + double FractionalKnapsack(int[] wgt, int[] val, int cap) { + // Создать список предметов с двумя свойствами: вес и стоимость + Item[] items = new Item[wgt.Length]; + for (int i = 0; i < wgt.Length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); + // Циклический жадный выбор + double res = 0; + foreach (Item item in items) { + if (item.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (double)item.v / item.w * cap; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + return res; + } + + [Test] + public void Test() { + int[] wgt = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + + // Жадный алгоритм + double res = FractionalKnapsack(wgt, val, cap); + Console.WriteLine("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/chapter_greedy/max_capacity.cs b/ru/codes/csharp/chapter_greedy/max_capacity.cs new file mode 100644 index 000000000..84635f084 --- /dev/null +++ b/ru/codes/csharp/chapter_greedy/max_capacity.cs @@ -0,0 +1,39 @@ +/** +* File: max_capacity.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_capacity { + /* Максимальная вместимость: жадный алгоритм */ + int MaxCapacity(int[] ht) { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + int i = 0, j = ht.Length - 1; + // Начальная максимальная вместимость равна 0 + int res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + int cap = Math.Min(ht[i], ht[j]) * (j - i); + res = Math.Max(res, cap); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + [Test] + public void Test() { + int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // Жадный алгоритм + int res = MaxCapacity(ht); + Console.WriteLine("Максимальная вместимость = " + res); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/chapter_greedy/max_product_cutting.cs b/ru/codes/csharp/chapter_greedy/max_product_cutting.cs new file mode 100644 index 000000000..b5da7a03b --- /dev/null +++ b/ru/codes/csharp/chapter_greedy/max_product_cutting.cs @@ -0,0 +1,39 @@ +/** +* File: max_product_cutting.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_product_cutting { + /* Максимальное произведение разрезания: жадный алгоритм */ + int MaxProductCutting(int n) { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + int a = n / 3; + int b = n % 3; + if (b == 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return (int)Math.Pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // Если остаток равен 2, ничего не делать + return (int)Math.Pow(3, a) * 2; + } + // Если остаток равен 0, ничего не делать + return (int)Math.Pow(3, a); + } + + [Test] + public void Test() { + int n = 58; + + // Жадный алгоритм + int res = MaxProductCutting(n); + Console.WriteLine("Максимальное произведение после разрезания = " + res); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/chapter_hashing/array_hash_map.cs b/ru/codes/csharp/chapter_hashing/array_hash_map.cs new file mode 100644 index 000000000..b4892f53d --- /dev/null +++ b/ru/codes/csharp/chapter_hashing/array_hash_map.cs @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +/* Пара ключ-значение int->string */ +class Pair(int key, string val) { + public int key = key; + public string val = val; +} + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + List buckets; + public ArrayHashMap() { + // Инициализировать массив, содержащий 100 корзин + buckets = []; + for (int i = 0; i < 100; i++) { + buckets.Add(null); + } + } + + /* Хеш-функция */ + int HashFunc(int key) { + int index = key % 100; + return index; + } + + /* Операция поиска */ + public string? Get(int key) { + int index = HashFunc(key); + Pair? pair = buckets[index]; + if (pair == null) return null; + return pair.val; + } + + /* Операция добавления */ + public void Put(int key, string val) { + Pair pair = new(key, val); + int index = HashFunc(key); + buckets[index] = pair; + } + + /* Операция удаления */ + public void Remove(int key) { + int index = HashFunc(key); + // Присвоить null, что означает удаление + buckets[index] = null; + } + + /* Получить все пары ключ-значение */ + public List PairSet() { + List pairSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + pairSet.Add(pair); + } + return pairSet; + } + + /* Получить все ключи */ + public List KeySet() { + List keySet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + keySet.Add(pair.key); + } + return keySet; + } + + /* Получить все значения */ + public List ValueSet() { + List valueSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + valueSet.Add(pair.val); + } + return valueSet; + } + + /* Вывести хеш-таблицу */ + public void Print() { + foreach (Pair kv in PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + } +} + + +public class array_hash_map { + [Test] + public void Test() { + /* Инициализация хеш-таблицы */ + ArrayHashMap map = new(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.Put(12836, "Сяо Ха"); + map.Put(15937, "Сяо Ло"); + map.Put(16750, "Сяо Суань"); + map.Put(13276, "Сяо Фа"); + map.Put(10583, "Сяо Я"); + Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.Print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string? name = map.Get(15937); + Console.WriteLine("\nДля номера 15937 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.Remove(10583); + Console.WriteLine("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + map.Print(); + + /* Обход хеш-таблицы */ + Console.WriteLine("\nОтдельный обход пар ключ-значение"); + foreach (Pair kv in map.PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + Console.WriteLine("\nОтдельный обход ключей"); + foreach (int key in map.KeySet()) { + Console.WriteLine(key); + } + Console.WriteLine("\nОтдельный обход значений"); + foreach (string val in map.ValueSet()) { + Console.WriteLine(val); + } + } +} diff --git a/ru/codes/csharp/chapter_hashing/built_in_hash.cs b/ru/codes/csharp/chapter_hashing/built_in_hash.cs new file mode 100644 index 000000000..b3cc581a8 --- /dev/null +++ b/ru/codes/csharp/chapter_hashing/built_in_hash.cs @@ -0,0 +1,36 @@ +/** +* File: built_in_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class built_in_hash { + [Test] + public void Test() { + int num = 3; + int hashNum = num.GetHashCode(); + Console.WriteLine("Хеш-значение целого числа " + num + " = " + hashNum); + + bool bol = true; + int hashBol = bol.GetHashCode(); + Console.WriteLine("Хеш-значение булева значения " + bol + " = " + hashBol); + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + Console.WriteLine("Хеш-значение десятичного числа " + dec + " = " + hashDec); + + string str = "Hello Algo"; + int hashStr = str.GetHashCode(); + Console.WriteLine("Хеш-значение строки " + str + " = " + hashStr); + + object[] arr = [12836, "Сяо Ха"]; + int hashTup = arr.GetHashCode(); + Console.WriteLine("Хеш-значение массива [" + string.Join(", ", arr) + "] = " + hashTup); + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + Console.WriteLine("Хеш-значение объекта узла " + obj + " = " + hashObj); + } +} diff --git a/ru/codes/csharp/chapter_hashing/hash_map.cs b/ru/codes/csharp/chapter_hashing/hash_map.cs new file mode 100644 index 000000000..4d1770c62 --- /dev/null +++ b/ru/codes/csharp/chapter_hashing/hash_map.cs @@ -0,0 +1,51 @@ + +/** + * File: hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +public class hash_map { + [Test] + public void Test() { + /* Инициализация хеш-таблицы */ + Dictionary map = new() { + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + { 12836, "Сяо Ха" }, + { 15937, "Сяо Ло" }, + { 16750, "Сяо Суань" }, + { 13276, "Сяо Фа" }, + { 10583, "Сяо Я" } + }; + Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + PrintUtil.PrintHashMap(map); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string name = map[15937]; + Console.WriteLine("\nДля номера 15937 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.Remove(10583); + Console.WriteLine("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + PrintUtil.PrintHashMap(map); + + /* Обход хеш-таблицы */ + Console.WriteLine("\nОтдельный обход пар ключ-значение"); + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + Console.WriteLine("\nОтдельный обход ключей"); + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + Console.WriteLine("\nОтдельный обход значений"); + foreach (string val in map.Values) { + Console.WriteLine(val); + } + } +} diff --git a/ru/codes/csharp/chapter_hashing/hash_map_chaining.cs b/ru/codes/csharp/chapter_hashing/hash_map_chaining.cs new file mode 100644 index 000000000..7763bcb1b --- /dev/null +++ b/ru/codes/csharp/chapter_hashing/hash_map_chaining.cs @@ -0,0 +1,144 @@ +/** +* File: hash_map_chaining.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + int size; // Число пар ключ-значение + int capacity; // Вместимость хеш-таблицы + double loadThres; // Порог коэффициента загрузки для запуска расширения + int extendRatio; // Коэффициент расширения + List> buckets; // Массив корзин + + /* Конструктор */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + } + + /* Хеш-функция */ + int HashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + double LoadFactor() { + return (double)size / capacity; + } + + /* Операция поиска */ + public string? Get(int key) { + int index = HashFunc(key); + // Обойти корзину; если найден key, вернуть соответствующее val + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + return pair.val; + } + } + // Если key не найден, вернуть null + return null; + } + + /* Операция добавления */ + public void Put(int key, string val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (LoadFactor() > loadThres) { + Extend(); + } + int index = HashFunc(key); + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // Если такого key нет, добавить пару ключ-значение в конец + buckets[index].Add(new Pair(key, val)); + size++; + } + + /* Операция удаления */ + public void Remove(int key) { + int index = HashFunc(key); + // Обойти корзину и удалить из нее пару ключ-значение + foreach (Pair pair in buckets[index].ToList()) { + if (pair.key == key) { + buckets[index].Remove(pair); + size--; + break; + } + } + } + + /* Расширить хеш-таблицу */ + void Extend() { + // Временно сохранить исходную хеш-таблицу + List> bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + foreach (List bucket in bucketsTmp) { + foreach (Pair pair in bucket) { + Put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + public void Print() { + foreach (List bucket in buckets) { + List res = []; + foreach (Pair pair in bucket) { + res.Add(pair.key + " -> " + pair.val); + } + foreach (string kv in res) { + Console.WriteLine(kv); + } + } + } +} + +public class hash_map_chaining { + [Test] + public void Test() { + /* Инициализация хеш-таблицы */ + HashMapChaining map = new(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.Put(12836, "Сяо Ха"); + map.Put(15937, "Сяо Ло"); + map.Put(16750, "Сяо Суань"); + map.Put(13276, "Сяо Фа"); + map.Put(10583, "Сяо Я"); + Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.Print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string? name = map.Get(13276); + Console.WriteLine("\nДля номера 13276 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.Remove(12836); + Console.WriteLine("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); + map.Print(); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/chapter_hashing/hash_map_open_addressing.cs b/ru/codes/csharp/chapter_hashing/hash_map_open_addressing.cs new file mode 100644 index 000000000..80367ee03 --- /dev/null +++ b/ru/codes/csharp/chapter_hashing/hash_map_open_addressing.cs @@ -0,0 +1,159 @@ +/** +* File: hash_map_open_addressing.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + int size; // Число пар ключ-значение + int capacity = 4; // Вместимость хеш-таблицы + double loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения + int extendRatio = 2; // Коэффициент расширения + Pair[] buckets; // Массив корзин + Pair TOMBSTONE = new(-1, "-1"); // Удалить метку + + /* Конструктор */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* Хеш-функция */ + int HashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + double LoadFactor() { + return (double)size / capacity; + } + + /* Найти индекс корзины, соответствующий key */ + int FindBucket(int key) { + int index = HashFunc(key); + int firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (buckets[index] != null) { + // Если встретился key, вернуть соответствующий индекс корзины + if (buckets[index].key == key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone == -1 ? index : firstTombstone; + } + + /* Операция поиска */ + public string? Get(int key) { + // Найти индекс корзины, соответствующий key + int index = FindBucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // Если пары ключ-значение не существует, вернуть null + return null; + } + + /* Операция добавления */ + public void Put(int key, string val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (LoadFactor() > loadThres) { + Extend(); + } + // Найти индекс корзины, соответствующий key + int index = FindBucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + buckets[index] = new Pair(key, val); + size++; + } + + /* Операция удаления */ + public void Remove(int key) { + // Найти индекс корзины, соответствующий key + int index = FindBucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* Расширить хеш-таблицу */ + void Extend() { + // Временно сохранить исходную хеш-таблицу + Pair[] bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + foreach (Pair pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + Put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + public void Print() { + foreach (Pair pair in buckets) { + if (pair == null) { + Console.WriteLine("null"); + } else if (pair == TOMBSTONE) { + Console.WriteLine("TOMBSTONE"); + } else { + Console.WriteLine(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + [Test] + public void Test() { + /* Инициализация хеш-таблицы */ + HashMapOpenAddressing map = new(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.Put(12836, "Сяо Ха"); + map.Put(15937, "Сяо Ло"); + map.Put(16750, "Сяо Суань"); + map.Put(13276, "Сяо Фа"); + map.Put(10583, "Сяо Я"); + Console.WriteLine("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.Print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + string? name = map.Get(13276); + Console.WriteLine("\nДля номера 13276 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.Remove(16750); + Console.WriteLine("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); + map.Print(); + } +} diff --git a/ru/codes/csharp/chapter_hashing/simple_hash.cs b/ru/codes/csharp/chapter_hashing/simple_hash.cs new file mode 100644 index 000000000..feda043a8 --- /dev/null +++ b/ru/codes/csharp/chapter_hashing/simple_hash.cs @@ -0,0 +1,66 @@ +/** +* File: simple_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class simple_hash { + /* Аддитивное хеширование */ + int AddHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } + + /* Мультипликативное хеширование */ + int MulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } + + /* XOR-хеширование */ + int XorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } + + /* Хеширование с циклическим сдвигом */ + int RotHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; + } + return (int)hash; + } + + [Test] + public void Test() { + string key = "Hello Algo"; + + int hash = AddHash(key); + Console.WriteLine("Хеш-сумма сложением = " + hash); + + hash = MulHash(key); + Console.WriteLine("Хеш-сумма умножением = " + hash); + + hash = XorHash(key); + Console.WriteLine("Хеш-сумма XOR = " + hash); + + hash = RotHash(key); + Console.WriteLine("Хеш-сумма с циклическим сдвигом = " + hash); + } +} diff --git a/ru/codes/csharp/chapter_heap/heap.cs b/ru/codes/csharp/chapter_heap/heap.cs new file mode 100644 index 000000000..fc73cec56 --- /dev/null +++ b/ru/codes/csharp/chapter_heap/heap.cs @@ -0,0 +1,64 @@ +/** + * File: heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +public class heap { + void TestPush(PriorityQueue heap, int val) { + heap.Enqueue(val, val); // Добавление элемента в кучу + Console.WriteLine($"\nПосле добавления элемента {val} в кучу\n"); + PrintUtil.PrintHeap(heap); + } + + void TestPop(PriorityQueue heap) { + int val = heap.Dequeue(); // Извлечение элемента с вершины кучи + Console.WriteLine($"\nПосле извлечения элемента вершины кучи {val}\n"); + PrintUtil.PrintHeap(heap); + } + + [Test] + public void Test() { + /* Инициализация кучи */ + // Инициализация минимальной кучи + PriorityQueue minHeap = new(); + // Инициализировать максимальную кучу (достаточно изменить Comparer с помощью lambda-выражения) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); + Console.WriteLine("Ниже приведен тестовый пример для max-heap"); + + /* Добавление элемента в кучу */ + TestPush(maxHeap, 1); + TestPush(maxHeap, 3); + TestPush(maxHeap, 2); + TestPush(maxHeap, 5); + TestPush(maxHeap, 4); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.Peek(); + Console.WriteLine($"Элемент на вершине кучи = {peek}"); + + /* Извлечение элемента с вершины кучи */ + // Элементы, извлеченные из кучи, образуют последовательность от большего к меньшему + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + + /* Получение размера кучи */ + int size = maxHeap.Count; + Console.WriteLine($"Количество элементов в куче = {size}"); + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.Count == 0; + Console.WriteLine($"Пуста ли куча: {isEmpty}"); + + /* Построить кучу по входному списку */ + var list = new int[] { 1, 3, 2, 5, 4 }; + minHeap = new PriorityQueue(list.Select(x => (x, x))); + Console.WriteLine("После построения min-heap из входного списка"); + PrintUtil.PrintHeap(minHeap); + } +} diff --git a/ru/codes/csharp/chapter_heap/my_heap.cs b/ru/codes/csharp/chapter_heap/my_heap.cs new file mode 100644 index 000000000..69199f206 --- /dev/null +++ b/ru/codes/csharp/chapter_heap/my_heap.cs @@ -0,0 +1,160 @@ +/** + * File: my_heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +/* Максимальная куча */ +class MaxHeap { + // Использовать список вместо массива, чтобы не учитывать проблему расширения + List maxHeap; + + /* Конструктор, создающий пустую кучу */ + public MaxHeap() { + maxHeap = []; + } + + /* Конструктор: построить кучу по входному списку */ + public MaxHeap(IEnumerable nums) { + // Добавить элементы списка в кучу без изменений + maxHeap = new List(nums); + // Выполнить heapify для всех узлов, кроме листовых + var size = Parent(this.Size() - 1); + for (int i = size; i >= 0; i--) { + SiftDown(i); + } + } + + /* Получить индекс левого дочернего узла */ + int Left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + int Right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + int Parent(int i) { + return (i - 1) / 2; // Округление вниз при делении + } + + /* Доступ к элементу на вершине кучи */ + public int Peek() { + return maxHeap[0]; + } + + /* Добавление элемента в кучу */ + public void Push(int val) { + // Добавление узла + maxHeap.Add(val); + // Просеивание снизу вверх + SiftUp(Size() - 1); + } + + /* Получение размера кучи */ + public int Size() { + return maxHeap.Count; + } + + /* Проверка, пуста ли куча */ + public bool IsEmpty() { + return Size() == 0; + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + void SiftUp(int i) { + while (true) { + // Получение родительского узла для узла i + int p = Parent(i); + // Если «выход за пределы корневого узла» или «узел не требует исправления», завершить просеивание + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // Поменять два узла местами + Swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + public int Pop() { + // Обработка пустого случая + if (IsEmpty()) + throw new IndexOutOfRangeException(); + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + Swap(0, Size() - 1); + // Удаление узла + int val = maxHeap.Last(); + maxHeap.RemoveAt(Size() - 1); + // Просеивание сверху вниз + SiftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + void SiftDown(int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = Left(i), r = Right(i), ma = i; + if (l < Size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < Size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // Если «узел i максимален» или «выход за пределы листовых узлов», завершить просеивание + if (ma == i) break; + // Поменять два узла местами + Swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Поменять элементы местами */ + void Swap(int i, int p) { + (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); + } + + /* Вывести кучу (двоичное дерево) */ + public void Print() { + var queue = new Queue(maxHeap); + PrintUtil.PrintHeap(queue); + } +} + +public class my_heap { + [Test] + public void Test() { + /* Инициализация максимальной кучи */ + MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + Console.WriteLine("\nПосле построения кучи из входного списка"); + maxHeap.Print(); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.Peek(); + Console.WriteLine($"Элемент на вершине кучи = {peek}"); + + /* Добавление элемента в кучу */ + int val = 7; + maxHeap.Push(val); + Console.WriteLine($"После добавления элемента {val} в кучу"); + maxHeap.Print(); + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.Pop(); + Console.WriteLine($"После извлечения элемента вершины кучи {peek}"); + maxHeap.Print(); + + /* Получение размера кучи */ + int size = maxHeap.Size(); + Console.WriteLine($"Количество элементов в куче = {size}"); + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.IsEmpty(); + Console.WriteLine($"Пуста ли куча: {isEmpty}"); + } +} diff --git a/ru/codes/csharp/chapter_heap/top_k.cs b/ru/codes/csharp/chapter_heap/top_k.cs new file mode 100644 index 000000000..6d6f6739c --- /dev/null +++ b/ru/codes/csharp/chapter_heap/top_k.cs @@ -0,0 +1,37 @@ +/** +* File: top_k.cs +* Created Time: 2023-06-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_heap; + +public class top_k { + /* Найти k наибольших элементов массива с помощью кучи */ + PriorityQueue TopKHeap(int[] nums, int k) { + // Инициализация минимальной кучи + PriorityQueue heap = new(); + // Поместить первые k элементов массива в кучу + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (int i = k; i < nums.Length; i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } + + [Test] + public void Test() { + int[] nums = [1, 7, 6, 3, 2]; + int k = 3; + PriorityQueue res = TopKHeap(nums, k); + Console.WriteLine("Наибольшие " + k + " элементов ="); + PrintUtil.PrintHeap(res); + } +} diff --git a/ru/codes/csharp/chapter_searching/binary_search.cs b/ru/codes/csharp/chapter_searching/binary_search.cs new file mode 100644 index 000000000..3cc98a657 --- /dev/null +++ b/ru/codes/csharp/chapter_searching/binary_search.cs @@ -0,0 +1,59 @@ +/** + * File: binary_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class binary_search { + /* Бинарный поиск (двусторонне замкнутый интервал) */ + int BinarySearch(int[] nums, int target) { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + int i = 0, j = nums.Length - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; + } + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + int BinarySearchLCRO(int[] nums, int target) { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + int i = 0, j = nums.Length; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) + j = m; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + int index = BinarySearch(nums, target); + Console.WriteLine("Индекс целевого элемента 6 = " + index); + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = BinarySearchLCRO(nums, target); + Console.WriteLine("Индекс целевого элемента 6 = " + index); + } +} diff --git a/ru/codes/csharp/chapter_searching/binary_search_edge.cs b/ru/codes/csharp/chapter_searching/binary_search_edge.cs new file mode 100644 index 000000000..c6cbc288e --- /dev/null +++ b/ru/codes/csharp/chapter_searching/binary_search_edge.cs @@ -0,0 +1,50 @@ +/** +* File: binary_search_edge.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_edge { + /* Бинарный поиск самого левого target */ + int BinarySearchLeftEdge(int[] nums, int target) { + // Эквивалентно поиску точки вставки target + int i = binary_search_insertion.BinarySearchInsertion(nums, target); + // target не найден, вернуть -1 + if (i == nums.Length || nums[i] != target) { + return -1; + } + // Найти target и вернуть индекс i + return i; + } + + /* Бинарный поиск самого правого target */ + int BinarySearchRightEdge(int[] nums, int target) { + // Преобразовать задачу в поиск самого левого target + 1 + int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + int j = i - 1; + // target не найден, вернуть -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Найти target и вернуть индекс j + return j; + } + + [Test] + public void Test() { + // Массив с повторяющимися элементами + int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\nМассив nums = " + nums.PrintList()); + + // Бинарный поиск левой и правой границы + foreach (int target in new int[] { 6, 7 }) { + int index = BinarySearchLeftEdge(nums, target); + Console.WriteLine("Индекс самого левого элемента " + target + " равен " + index); + index = BinarySearchRightEdge(nums, target); + Console.WriteLine("Индекс самого правого элемента " + target + " равен " + index); + } + } +} diff --git a/ru/codes/csharp/chapter_searching/binary_search_insertion.cs b/ru/codes/csharp/chapter_searching/binary_search_insertion.cs new file mode 100644 index 000000000..d0300c5d7 --- /dev/null +++ b/ru/codes/csharp/chapter_searching/binary_search_insertion.cs @@ -0,0 +1,64 @@ +/** +* File: binary_search_insertion.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_insertion { + /* Бинарный поиск точки вставки (без повторяющихся элементов) */ + public static int BinarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; + } + + /* Бинарный поиск точки вставки (с повторяющимися элементами) */ + public static int BinarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; + } + + [Test] + public void Test() { + // Массив без повторяющихся элементов + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + Console.WriteLine("\nМассив nums = " + nums.PrintList()); + // Бинарный поиск точки вставки + foreach (int target in new int[] { 6, 9 }) { + int index = BinarySearchInsertionSimple(nums, target); + Console.WriteLine("Индекс позиции вставки элемента " + target + " равен " + index); + } + + // Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\nМассив nums = " + nums.PrintList()); + // Бинарный поиск точки вставки + foreach (int target in new int[] { 2, 6, 20 }) { + int index = BinarySearchInsertion(nums, target); + Console.WriteLine("Индекс позиции вставки элемента " + target + " равен " + index); + } + } +} diff --git a/ru/codes/csharp/chapter_searching/hashing_search.cs b/ru/codes/csharp/chapter_searching/hashing_search.cs new file mode 100644 index 000000000..824ed83b0 --- /dev/null +++ b/ru/codes/csharp/chapter_searching/hashing_search.cs @@ -0,0 +1,50 @@ +/** + * File: hashing_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class hashing_search { + /* Хеш-поиск (массив) */ + int HashingSearchArray(Dictionary map, int target) { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + return map.GetValueOrDefault(target, -1); + } + + /* Хеш-поиск (связный список) */ + ListNode? HashingSearchLinkedList(Dictionary map, int target) { + + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + return map.GetValueOrDefault(target); + } + + [Test] + public void Test() { + int target = 3; + + /* Хеш-поиск (массив) */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // Инициализация хеш-таблицы + Dictionary map = []; + for (int i = 0; i < nums.Length; i++) { + map[nums[i]] = i; // key: элемент, value: индекс + } + int index = HashingSearchArray(map, target); + Console.WriteLine("Индекс целевого элемента 3 = " + index); + + /* Хеш-поиск (связный список) */ + ListNode? head = ListNode.ArrToLinkedList(nums); + // Инициализация хеш-таблицы + Dictionary map1 = []; + while (head != null) { + map1[head.val] = head; // key: значение узла, value: узел + head = head.next; + } + ListNode? node = HashingSearchLinkedList(map1, target); + Console.WriteLine("Объект узла со значением 3 = " + node); + } +} diff --git a/ru/codes/csharp/chapter_searching/linear_search.cs b/ru/codes/csharp/chapter_searching/linear_search.cs new file mode 100644 index 000000000..51bba2d9f --- /dev/null +++ b/ru/codes/csharp/chapter_searching/linear_search.cs @@ -0,0 +1,49 @@ +/** + * File: linear_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class linear_search { + /* Линейный поиск (массив) */ + int LinearSearchArray(int[] nums, int target) { + // Обход массива + for (int i = 0; i < nums.Length; i++) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] == target) + return i; + } + // Целевой элемент не найден, вернуть -1 + return -1; + } + + /* Линейный поиск (связный список) */ + ListNode? LinearSearchLinkedList(ListNode? head, int target) { + // Обойти связный список + while (head != null) { + // Найти целевой узел и вернуть его + if (head.val == target) + return head; + head = head.next; + } + // Целевой узел не найден, вернуть null + return null; + } + + [Test] + public void Test() { + int target = 3; + + /* Выполнить линейный поиск в массиве */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = LinearSearchArray(nums, target); + Console.WriteLine("Индекс целевого элемента 3 = " + index); + + /* Выполнить линейный поиск в связном списке */ + ListNode? head = ListNode.ArrToLinkedList(nums); + ListNode? node = LinearSearchLinkedList(head, target); + Console.WriteLine("Объект узла со значением 3 = " + node); + } +} diff --git a/ru/codes/csharp/chapter_searching/two_sum.cs b/ru/codes/csharp/chapter_searching/two_sum.cs new file mode 100644 index 000000000..9a3c8e6a9 --- /dev/null +++ b/ru/codes/csharp/chapter_searching/two_sum.cs @@ -0,0 +1,52 @@ +/** + * File: two_sum.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class two_sum { + /* Метод 1: полный перебор */ + int[] TwoSumBruteForce(int[] nums, int target) { + int size = nums.Length; + // Два вложенных цикла, временная сложность O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return [i, j]; + } + } + return []; + } + + /* Метод 2: вспомогательная хеш-таблица */ + int[] TwoSumHashTable(int[] nums, int target) { + int size = nums.Length; + // Вспомогательная хеш-таблица, пространственная сложность O(n) + Dictionary dic = []; + // Один цикл, временная сложность O(n) + for (int i = 0; i < size; i++) { + if (dic.ContainsKey(target - nums[i])) { + return [dic[target - nums[i]], i]; + } + dic.Add(nums[i], i); + } + return []; + } + + [Test] + public void Test() { + // ======= Test Case ======= + int[] nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Основной код ====== + // Метод 1 + int[] res = TwoSumBruteForce(nums, target); + Console.WriteLine("Результат метода 1 res = " + string.Join(",", res)); + // Метод 2 + res = TwoSumHashTable(nums, target); + Console.WriteLine("Результат метода 2 res = " + string.Join(",", res)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/bubble_sort.cs b/ru/codes/csharp/chapter_sorting/bubble_sort.cs new file mode 100644 index 000000000..ee43d8c54 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/bubble_sort.cs @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bubble_sort { + /* Пузырьковая сортировка */ + void BubbleSort(int[] nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + } + } + } + } + + /* Пузырьковая сортировка (оптимизация флагом) */ + void BubbleSortWithFlag(int[] nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + bool flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + flag = true; // Записать обмен элементов + } + } + if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + BubbleSort(nums); + Console.WriteLine("После пузырьковой сортировки nums = " + string.Join(",", nums)); + + int[] nums1 = [4, 1, 3, 1, 5, 2]; + BubbleSortWithFlag(nums1); + Console.WriteLine("После пузырьковой сортировки nums1 = " + string.Join(",", nums1)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/bucket_sort.cs b/ru/codes/csharp/chapter_sorting/bucket_sort.cs new file mode 100644 index 000000000..550a0fcb7 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/bucket_sort.cs @@ -0,0 +1,46 @@ +/** + * File: bucket_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bucket_sort { + /* Сортировка корзинами */ + void BucketSort(float[] nums) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + int k = nums.Length / 2; + List> buckets = []; + for (int i = 0; i < k; i++) { + buckets.Add([]); + } + // 1. Распределить элементы массива по корзинам + foreach (float num in nums) { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + int i = (int)(num * k); + // Добавить num в корзину i + buckets[i].Add(num); + } + // 2. Выполнить сортировку внутри каждой корзины + foreach (List bucket in buckets) { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + bucket.Sort(); + } + // 3. Обойти корзины и объединить результаты + int j = 0; + foreach (List bucket in buckets) { + foreach (float num in bucket) { + nums[j++] = num; + } + } + } + + [Test] + public void Test() { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; + BucketSort(nums); + Console.WriteLine("После сортировки корзинами nums = " + string.Join(" ", nums)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/counting_sort.cs b/ru/codes/csharp/chapter_sorting/counting_sort.cs new file mode 100644 index 000000000..b6f2c9e67 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/counting_sort.cs @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class counting_sort { + /* Сортировка подсчетом */ + // Простая реализация, не подходит для сортировки объектов + void CountingSortNaive(int[] nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + + /* Сортировка подсчетом */ + // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой + void CountingSort(int[] nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + int n = nums.Length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // Поместить num по соответствующему индексу + counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + [Test] + public void Test() { + int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSortNaive(nums); + Console.WriteLine("После сортировки подсчетом (объекты не поддерживаются) nums = " + string.Join(" ", nums)); + + int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSort(nums1); + Console.WriteLine("После сортировки подсчетом nums1 = " + string.Join(" ", nums)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/heap_sort.cs b/ru/codes/csharp/chapter_sorting/heap_sort.cs new file mode 100644 index 000000000..8cdd0465f --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/heap_sort.cs @@ -0,0 +1,52 @@ +/** +* File: heap_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class heap_sort { + /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ + void SiftDown(int[] nums, int n, int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) + break; + // Поменять два узла местами + (nums[ma], nums[i]) = (nums[i], nums[ma]); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Сортировка кучей */ + void HeapSort(int[] nums) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (int i = nums.Length / 2 - 1; i >= 0; i--) { + SiftDown(nums, nums.Length, i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (int i = nums.Length - 1; i > 0; i--) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + (nums[i], nums[0]) = (nums[0], nums[i]); + // Начиная с корневого узла, выполнить просеивание сверху вниз + SiftDown(nums, i, 0); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + HeapSort(nums); + Console.WriteLine("После сортировки кучей nums = " + string.Join(" ", nums)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/insertion_sort.cs b/ru/codes/csharp/chapter_sorting/insertion_sort.cs new file mode 100644 index 000000000..85c5ddcc7 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/insertion_sort.cs @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class insertion_sort { + /* Сортировка вставками */ + void InsertionSort(int[] nums) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (int i = 1; i < nums.Length; i++) { + int bas = nums[i], j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > bas) { + nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо + j--; + } + nums[j + 1] = bas; // Поместить base в правильную позицию + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + InsertionSort(nums); + Console.WriteLine("После сортировки вставками nums = " + string.Join(",", nums)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/merge_sort.cs b/ru/codes/csharp/chapter_sorting/merge_sort.cs new file mode 100644 index 000000000..ab368d979 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/merge_sort.cs @@ -0,0 +1,56 @@ +/** + * File: merge_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class merge_sort { + /* Объединить левый и правый подмассивы */ + void Merge(int[] nums, int left, int mid, int right) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + int[] tmp = new int[right - left + 1]; + // Инициализировать начальные индексы левого и правого подмассивов + int i = left, j = mid + 1, k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmp.Length; ++k) { + nums[left + k] = tmp[k]; + } + } + + /* Сортировка слиянием */ + void MergeSort(int[] nums, int left, int right) { + // Условие завершения + if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + int mid = left + (right - left) / 2; // Вычислить середину + MergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + MergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + Merge(nums, left, mid, right); + } + + [Test] + public void Test() { + /* Сортировка слиянием */ + int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; + MergeSort(nums, 0, nums.Length - 1); + Console.WriteLine("После сортировки слиянием nums = " + string.Join(",", nums)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/quick_sort.cs b/ru/codes/csharp/chapter_sorting/quick_sort.cs new file mode 100644 index 000000000..9f0ff7112 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/quick_sort.cs @@ -0,0 +1,150 @@ +/** + * File: quick_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +class quickSort { + /* Обмен элементов */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* Разбиение с опорными указателями */ + static int Partition(int[] nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + Swap(nums, i, j); // Поменять эти два элемента местами + } + Swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + public static void QuickSort(int[] nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = Partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + QuickSort(nums, left, pivot - 1); + QuickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +class QuickSortMedian { + /* Обмен элементов */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* Выбрать медиану из трех кандидатов */ + static int MedianThree(int[] nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m находится между l и r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l находится между m и r + return right; + } + + /* Разбиение с опорными указателями (медиана трех) */ + static int Partition(int[] nums, int left, int right) { + // Выбрать медиану из трех кандидатов + int med = MedianThree(nums, left, (left + right) / 2, right); + // Переместить медиану в крайний левый элемент массива + Swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + Swap(nums, i, j); // Поменять эти два элемента местами + } + Swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + public static void QuickSort(int[] nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = Partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + QuickSort(nums, left, pivot - 1); + QuickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация глубины рекурсии) */ +class QuickSortTailCall { + /* Обмен элементов */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* Разбиение с опорными указателями */ + static int Partition(int[] nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + Swap(nums, i, j); // Поменять эти два элемента местами + } + Swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + public static void QuickSort(int[] nums, int left, int right) { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + int pivot = Partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + QuickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + QuickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +} + +public class quick_sort { + [Test] + public void Test() { + /* Быстрая сортировка */ + int[] nums = [2, 4, 1, 0, 3, 5]; + quickSort.QuickSort(nums, 0, nums.Length - 1); + Console.WriteLine("После быстрой сортировки nums = " + string.Join(",", nums)); + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + int[] nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); + Console.WriteLine("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = " + string.Join(",", nums1)); + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + int[] nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); + Console.WriteLine("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = " + string.Join(",", nums2)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/radix_sort.cs b/ru/codes/csharp/chapter_sorting/radix_sort.cs new file mode 100644 index 000000000..d7ba9675a --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/radix_sort.cs @@ -0,0 +1,69 @@ +/** + * File: radix_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class radix_sort { + /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ + int Digit(int num, int exp) { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num / exp) % 10; + } + + /* Сортировка подсчетом (сортировка по k-му разряду nums) */ + void CountingSortDigit(int[] nums, int exp) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + int[] counter = new int[10]; + int n = nums.Length; + // Подсчитать число появлений каждой цифры от 0 до 9 + for (int i = 0; i < n; i++) { + int d = Digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d]++; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int d = Digit(nums[i], exp); + int j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + /* Поразрядная сортировка */ + void RadixSort(int[] nums) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + int m = int.MinValue; + foreach (int num in nums) { + if (num > m) m = num; + } + // Проходить разряды от младшего к старшему + for (int exp = 1; exp <= m; exp *= 10) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + CountingSortDigit(nums, exp); + } + } + + [Test] + public void Test() { + // Поразрядная сортировка + int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 ]; + RadixSort(nums); + Console.WriteLine("После поразрядной сортировки nums = " + string.Join(" ", nums)); + } +} diff --git a/ru/codes/csharp/chapter_sorting/selection_sort.cs b/ru/codes/csharp/chapter_sorting/selection_sort.cs new file mode 100644 index 000000000..395985bf6 --- /dev/null +++ b/ru/codes/csharp/chapter_sorting/selection_sort.cs @@ -0,0 +1,32 @@ +/** +* File: selection_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class selection_sort { + /* Сортировка выбором */ + void SelectionSort(int[] nums) { + int n = nums.Length; + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // Записать индекс минимального элемента + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + (nums[k], nums[i]) = (nums[i], nums[k]); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + SelectionSort(nums); + Console.WriteLine("После сортировки выбором nums = " + string.Join(" ", nums)); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/array_deque.cs b/ru/codes/csharp/chapter_stack_and_queue/array_deque.cs new file mode 100644 index 000000000..69ad08426 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/array_deque.cs @@ -0,0 +1,152 @@ +/** + * File: array_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* Двусторонняя очередь на основе кольцевого массива */ +public class ArrayDeque { + int[] nums; // Массив для хранения элементов двусторонней очереди + int front; // Указатель head, указывающий на первый элемент очереди + int queSize; // Длина двусторонней очереди + + /* Конструктор */ + public ArrayDeque(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* Получить вместимость двусторонней очереди */ + int Capacity() { + return nums.Length; + } + + /* Получение длины двусторонней очереди */ + public int Size() { + return queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + public bool IsEmpty() { + return queSize == 0; + } + + /* Вычислить индекс в кольцевом массиве */ + int Index(int i) { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + Capacity()) % Capacity(); + } + + /* Добавление в голову очереди */ + public void PushFirst(int num) { + if (queSize == Capacity()) { + Console.WriteLine("Двусторонняя очередь заполнена"); + return; + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + front = Index(front - 1); + // Добавить num в голову очереди + nums[front] = num; + queSize++; + } + + /* Добавление в хвост очереди */ + public void PushLast(int num) { + if (queSize == Capacity()) { + Console.WriteLine("Двусторонняя очередь заполнена"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + int rear = Index(front + queSize); + // Добавить num в хвост очереди + nums[rear] = num; + queSize++; + } + + /* Извлечение из головы очереди */ + public int PopFirst() { + int num = PeekFirst(); + // Указатель головы сдвигается на одну позицию назад + front = Index(front + 1); + queSize--; + return num; + } + + /* Извлечение из хвоста очереди */ + public int PopLast() { + int num = PeekLast(); + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + public int PeekFirst() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* Доступ к элементу в конце очереди */ + public int PeekLast() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + // Вычислить индекс хвостового элемента + int last = Index(front + queSize - 1); + return nums[last]; + } + + /* Вернуть массив для вывода */ + public int[] ToArray() { + // Преобразовывать только элементы списка в пределах фактической длины + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[Index(j)]; + } + return res; + } +} + +public class array_deque { + [Test] + public void Test() { + /* Инициализация двусторонней очереди */ + ArrayDeque deque = new(10); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("Двусторонняя очередь deque = " + string.Join(" ", deque.ToArray())); + + /* Доступ к элементу */ + int peekFirst = deque.PeekFirst(); + Console.WriteLine("Первый элемент peekFirst = " + peekFirst); + int peekLast = deque.PeekLast(); + Console.WriteLine("Последний элемент peekLast = " + peekLast); + + /* Добавление элемента в очередь */ + deque.PushLast(4); + Console.WriteLine("После добавления элемента 4 в хвост deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("После добавления элемента 1 в голову deque = " + string.Join(" ", deque.ToArray())); + + /* Извлечение элемента из очереди */ + int popLast = deque.PopLast(); + Console.WriteLine("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + string.Join(" ", deque.ToArray())); + int popFirst = deque.PopFirst(); + Console.WriteLine("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + string.Join(" ", deque.ToArray())); + + /* Получение длины двусторонней очереди */ + int size = deque.Size(); + Console.WriteLine("Длина двусторонней очереди size = " + size); + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("Пуста ли двусторонняя очередь = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/array_queue.cs b/ru/codes/csharp/chapter_stack_and_queue/array_queue.cs new file mode 100644 index 000000000..6a0dee214 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/array_queue.cs @@ -0,0 +1,114 @@ +/** + * File: array_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + int[] nums; // Массив для хранения элементов очереди + int front; // Указатель head, указывающий на первый элемент очереди + int queSize; // Длина очереди + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* Получить вместимость очереди */ + int Capacity() { + return nums.Length; + } + + /* Получение длины очереди */ + public int Size() { + return queSize; + } + + /* Проверка, пуста ли очередь */ + public bool IsEmpty() { + return queSize == 0; + } + + /* Поместить в очередь */ + public void Push(int num) { + if (queSize == Capacity()) { + Console.WriteLine("Очередь заполнена"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + int rear = (front + queSize) % Capacity(); + // Добавить num в хвост очереди + nums[rear] = num; + queSize++; + } + + /* Извлечь из очереди */ + public int Pop() { + int num = Peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + front = (front + 1) % Capacity(); + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return nums[front]; + } + + /* Вернуть массив */ + public int[] ToArray() { + // Преобразовывать только элементы списка в пределах фактической длины + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % this.Capacity()]; + } + return res; + } +} + +public class array_queue { + [Test] + public void Test() { + /* Инициализация очереди */ + int capacity = 10; + ArrayQueue queue = new(capacity); + + /* Добавление элемента в очередь */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("Очередь queue = " + string.Join(",", queue.ToArray())); + + /* Доступ к элементу в начале очереди */ + int peek = queue.Peek(); + Console.WriteLine("Первый элемент peek = " + peek); + + /* Извлечение элемента из очереди */ + int pop = queue.Pop(); + Console.WriteLine("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + string.Join(",", queue.ToArray())); + + /* Получение длины очереди */ + int size = queue.Size(); + Console.WriteLine("Длина очереди size = " + size); + + /* Проверка, пуста ли очередь */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("Пуста ли очередь = " + isEmpty); + + /* Проверка кольцевого массива */ + for (int i = 0; i < 10; i++) { + queue.Push(i); + queue.Pop(); + Console.WriteLine("После " + i + "-го раунда операций enqueue и dequeue queue = " + string.Join(",", queue.ToArray())); + } + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/array_stack.cs b/ru/codes/csharp/chapter_stack_and_queue/array_stack.cs new file mode 100644 index 000000000..c0c963701 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/array_stack.cs @@ -0,0 +1,84 @@ +/** + * File: array_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* Стек на основе массива */ +class ArrayStack { + List stack; + public ArrayStack() { + // Инициализация списка (динамического массива) + stack = []; + } + + /* Получение длины стека */ + public int Size() { + return stack.Count; + } + + /* Проверка, пуст ли стек */ + public bool IsEmpty() { + return Size() == 0; + } + + /* Поместить в стек */ + public void Push(int num) { + stack.Add(num); + } + + /* Извлечь из стека */ + public int Pop() { + if (IsEmpty()) + throw new Exception(); + var val = Peek(); + stack.RemoveAt(Size() - 1); + return val; + } + + /* Доступ к верхнему элементу стека */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stack[Size() - 1]; + } + + /* Преобразовать List в Array и вернуть */ + public int[] ToArray() { + return [.. stack]; + } +} + +public class array_stack { + [Test] + public void Test() { + /* Инициализация стека */ + ArrayStack stack = new(); + + /* Помещение элемента в стек */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("Стек stack = " + string.Join(",", stack.ToArray())); + + /* Доступ к верхнему элементу стека */ + int peek = stack.Peek(); + Console.WriteLine("Верхний элемент peek = " + peek); + + /* Извлечение элемента из стека */ + int pop = stack.Pop(); + Console.WriteLine("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + string.Join(",", stack.ToArray())); + + /* Получение длины стека */ + int size = stack.Size(); + Console.WriteLine("Длина стека size = " + size); + + /* Проверка на пустоту */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("Пуст ли стек = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/deque.cs b/ru/codes/csharp/chapter_stack_and_queue/deque.cs new file mode 100644 index 000000000..6709b5c88 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/deque.cs @@ -0,0 +1,44 @@ +/** + * File: deque.cs + * Created Time: 2022-12-30 + * Author: moonache (microin1301@outlook.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class deque { + [Test] + public void Test() { + /* Инициализация двусторонней очереди */ + // В C# связный список LinkedList рассматривается как двусторонняя очередь + LinkedList deque = new(); + + /* Добавление элемента в очередь */ + deque.AddLast(2); // Добавить в хвост очереди + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // Добавить в голову очереди + deque.AddFirst(1); + Console.WriteLine("Двусторонняя очередь deque = " + string.Join(",", deque)); + + /* Доступ к элементу */ + int? peekFirst = deque.First?.Value; // Элемент в голове очереди + Console.WriteLine("Первый элемент peekFirst = " + peekFirst); + int? peekLast = deque.Last?.Value; // Элемент в хвосте очереди + Console.WriteLine("Последний элемент peekLast = " + peekLast); + + /* Извлечение элемента из очереди */ + deque.RemoveFirst(); // Извлечь элемент из головы очереди + Console.WriteLine("После извлечения элемента из головы deque = " + string.Join(",", deque)); + deque.RemoveLast(); // Извлечь элемент из хвоста очереди + Console.WriteLine("После извлечения элемента из хвоста deque = " + string.Join(",", deque)); + + /* Получение длины двусторонней очереди */ + int size = deque.Count; + Console.WriteLine("Длина двусторонней очереди size = " + size); + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty = deque.Count == 0; + Console.WriteLine("Пуста ли двусторонняя очередь = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs b/ru/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs new file mode 100644 index 000000000..82e35cb17 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs @@ -0,0 +1,177 @@ +/** + * File: linkedlist_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* Узел двусвязного списка */ +public class ListNode(int val) { + public int val = val; // Значение узла + public ListNode? next = null; // Ссылка на узел-преемник + public ListNode? prev = null; // Ссылка на узел-предшественник +} + +/* Двусторонняя очередь на основе двусвязного списка */ +public class LinkedListDeque { + ListNode? front, rear; // Головной узел front, хвостовой узел rear + int queSize = 0; // Длина двусторонней очереди + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* Получение длины двусторонней очереди */ + public int Size() { + return queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + public bool IsEmpty() { + return Size() == 0; + } + + /* Операция добавления в очередь */ + void Push(int num, bool isFront) { + ListNode node = new(num); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (IsEmpty()) { + front = node; + rear = node; + } + // Операция добавления в голову очереди + else if (isFront) { + // Добавить node в голову списка + front!.prev = node; + node.next = front; + front = node; // Обновить головной узел + } + // Операция добавления в хвост очереди + else { + // Добавить node в хвост списка + rear!.next = node; + node.prev = rear; + rear = node; // Обновить хвостовой узел + } + + queSize++; // Обновить длину очереди + } + + /* Добавление в голову очереди */ + public void PushFirst(int num) { + Push(num, true); + } + + /* Добавление в хвост очереди */ + public void PushLast(int num) { + Push(num, false); + } + + /* Операция извлечения из очереди */ + int? Pop(bool isFront) { + if (IsEmpty()) + throw new Exception(); + int? val; + // Операция извлечения из головы очереди + if (isFront) { + val = front?.val; // Временно сохранить значение головного узла + // Удалить головной узел + ListNode? fNext = front?.next; + if (fNext != null) { + fNext.prev = null; + front!.next = null; + } + front = fNext; // Обновить головной узел + } + // Операция извлечения из хвоста очереди + else { + val = rear?.val; // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + ListNode? rPrev = rear?.prev; + if (rPrev != null) { + rPrev.next = null; + rear!.prev = null; + } + rear = rPrev; // Обновить хвостовой узел + } + + queSize--; // Обновить длину очереди + return val; + } + + /* Извлечение из головы очереди */ + public int? PopFirst() { + return Pop(true); + } + + /* Извлечение из хвоста очереди */ + public int? PopLast() { + return Pop(false); + } + + /* Доступ к элементу в начале очереди */ + public int? PeekFirst() { + if (IsEmpty()) + throw new Exception(); + return front?.val; + } + + /* Доступ к элементу в конце очереди */ + public int? PeekLast() { + if (IsEmpty()) + throw new Exception(); + return rear?.val; + } + + /* Вернуть массив для вывода */ + public int?[] ToArray() { + ListNode? node = front; + int?[] res = new int?[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node?.val; + node = node?.next; + } + + return res; + } +} + +public class linkedlist_deque { + [Test] + public void Test() { + /* Инициализация двусторонней очереди */ + LinkedListDeque deque = new(); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("Двусторонняя очередь deque = " + string.Join(" ", deque.ToArray())); + + /* Доступ к элементу */ + int? peekFirst = deque.PeekFirst(); + Console.WriteLine("Первый элемент peekFirst = " + peekFirst); + int? peekLast = deque.PeekLast(); + Console.WriteLine("Последний элемент peekLast = " + peekLast); + + /* Добавление элемента в очередь */ + deque.PushLast(4); + Console.WriteLine("После добавления элемента 4 в хвост deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("После добавления элемента 1 в голову deque = " + string.Join(" ", deque.ToArray())); + + /* Извлечение элемента из очереди */ + int? popLast = deque.PopLast(); + Console.WriteLine("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + string.Join(" ", deque.ToArray())); + int? popFirst = deque.PopFirst(); + Console.WriteLine("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + string.Join(" ", deque.ToArray())); + + /* Получение длины двусторонней очереди */ + int size = deque.Size(); + Console.WriteLine("Длина двусторонней очереди size = " + size); + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("Пуста ли двусторонняя очередь = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/ru/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs new file mode 100644 index 000000000..931c87b7b --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -0,0 +1,106 @@ +/** + * File: linkedlist_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* Очередь на основе связного списка */ +class LinkedListQueue { + ListNode? front, rear; // Головной узел front, хвостовой узел rear + int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* Получение длины очереди */ + public int Size() { + return queSize; + } + + /* Проверка, пуста ли очередь */ + public bool IsEmpty() { + return Size() == 0; + } + + /* Поместить в очередь */ + public void Push(int num) { + // Добавить num после хвостового узла + ListNode node = new(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (front == null) { + front = node; + rear = node; + // Если очередь не пуста, добавить этот узел после хвостового узла + } else if (rear != null) { + rear.next = node; + rear = node; + } + queSize++; + } + + /* Извлечь из очереди */ + public int Pop() { + int num = Peek(); + // Удалить головной узел + front = front?.next; + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return front!.val; + } + + /* Преобразовать связный список в Array и вернуть */ + public int[] ToArray() { + if (front == null) + return []; + + ListNode? node = front; + int[] res = new int[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + [Test] + public void Test() { + /* Инициализация очереди */ + LinkedListQueue queue = new(); + + /* Добавление элемента в очередь */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("Очередь queue = " + string.Join(",", queue.ToArray())); + + /* Доступ к элементу в начале очереди */ + int peek = queue.Peek(); + Console.WriteLine("Первый элемент peek = " + peek); + + /* Извлечение элемента из очереди */ + int pop = queue.Pop(); + Console.WriteLine("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + string.Join(",", queue.ToArray())); + + /* Получение длины очереди */ + int size = queue.Size(); + Console.WriteLine("Длина очереди size = " + size); + + /* Проверка, пуста ли очередь */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("Пуста ли очередь = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/ru/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs new file mode 100644 index 000000000..daa83ab42 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -0,0 +1,97 @@ +/** + * File: linkedlist_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* Стек на основе связного списка */ +class LinkedListStack { + ListNode? stackPeek; // Использовать головной узел как вершину стека + int stkSize = 0; // Длина стека + + public LinkedListStack() { + stackPeek = null; + } + + /* Получение длины стека */ + public int Size() { + return stkSize; + } + + /* Проверка, пуст ли стек */ + public bool IsEmpty() { + return Size() == 0; + } + + /* Поместить в стек */ + public void Push(int num) { + ListNode node = new(num) { + next = stackPeek + }; + stackPeek = node; + stkSize++; + } + + /* Извлечь из стека */ + public int Pop() { + int num = Peek(); + stackPeek = stackPeek!.next; + stkSize--; + return num; + } + + /* Доступ к верхнему элементу стека */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stackPeek!.val; + } + + /* Преобразовать List в Array и вернуть */ + public int[] ToArray() { + if (stackPeek == null) + return []; + + ListNode? node = stackPeek; + int[] res = new int[Size()]; + for (int i = res.Length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + [Test] + public void Test() { + /* Инициализация стека */ + LinkedListStack stack = new(); + + /* Помещение элемента в стек */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("Стек stack = " + string.Join(",", stack.ToArray())); + + /* Доступ к верхнему элементу стека */ + int peek = stack.Peek(); + Console.WriteLine("Верхний элемент peek = " + peek); + + /* Извлечение элемента из стека */ + int pop = stack.Pop(); + Console.WriteLine("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + string.Join(",", stack.ToArray())); + + /* Получение длины стека */ + int size = stack.Size(); + Console.WriteLine("Длина стека size = " + size); + + /* Проверка на пустоту */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("Пуст ли стек = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/queue.cs b/ru/codes/csharp/chapter_stack_and_queue/queue.cs new file mode 100644 index 000000000..22e3a14f6 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/queue.cs @@ -0,0 +1,39 @@ +/** + * File: queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class queue { + [Test] + public void Test() { + /* Инициализация очереди */ + Queue queue = new(); + + /* Добавление элемента в очередь */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + Console.WriteLine("Очередь queue = " + string.Join(",", queue)); + + /* Доступ к элементу в начале очереди */ + int peek = queue.Peek(); + Console.WriteLine("Первый элемент peek = " + peek); + + /* Извлечение элемента из очереди */ + int pop = queue.Dequeue(); + Console.WriteLine("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + string.Join(",", queue)); + + /* Получение длины очереди */ + int size = queue.Count; + Console.WriteLine("Длина очереди size = " + size); + + /* Проверка, пуста ли очередь */ + bool isEmpty = queue.Count == 0; + Console.WriteLine("Пуста ли очередь = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_stack_and_queue/stack.cs b/ru/codes/csharp/chapter_stack_and_queue/stack.cs new file mode 100644 index 000000000..68d518c41 --- /dev/null +++ b/ru/codes/csharp/chapter_stack_and_queue/stack.cs @@ -0,0 +1,40 @@ +/** + * File: stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class stack { + [Test] + public void Test() { + /* Инициализация стека */ + Stack stack = new(); + + /* Помещение элемента в стек */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + // Обратите внимание: stack.ToArray() возвращает последовательность в обратном порядке, то есть индекс 0 соответствует вершине стека + Console.WriteLine("Стек stack = " + string.Join(",", stack)); + + /* Доступ к верхнему элементу стека */ + int peek = stack.Peek(); + Console.WriteLine("Верхний элемент peek = " + peek); + + /* Извлечение элемента из стека */ + int pop = stack.Pop(); + Console.WriteLine("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + string.Join(",", stack)); + + /* Получение длины стека */ + int size = stack.Count; + Console.WriteLine("Длина стека size = " + size); + + /* Проверка на пустоту */ + bool isEmpty = stack.Count == 0; + Console.WriteLine("Пуст ли стек = " + isEmpty); + } +} diff --git a/ru/codes/csharp/chapter_tree/array_binary_tree.cs b/ru/codes/csharp/chapter_tree/array_binary_tree.cs new file mode 100644 index 000000000..ec864a169 --- /dev/null +++ b/ru/codes/csharp/chapter_tree/array_binary_tree.cs @@ -0,0 +1,129 @@ +/** +* File: array_binary_tree.cs +* Created Time: 2023-07-20 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_tree; + +/* Класс двоичного дерева в массивном представлении */ +public class ArrayBinaryTree(List arr) { + List tree = new(arr); + + /* Вместимость списка */ + public int Size() { + return tree.Count; + } + + /* Получить значение узла с индексом i */ + public int? Val(int i) { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if (i < 0 || i >= Size()) + return null; + return tree[i]; + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + public int Left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + public int Right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла узла с индексом i */ + public int Parent(int i) { + return (i - 1) / 2; + } + + /* Обход в ширину */ + public List LevelOrder() { + List res = []; + // Непосредственно обходить массив + for (int i = 0; i < Size(); i++) { + if (Val(i).HasValue) + res.Add(Val(i)!.Value); + } + return res; + } + + /* Обход в глубину */ + void DFS(int i, string order, List res) { + // Если это пустая позиция, вернуть + if (!Val(i).HasValue) + return; + // Предварительный обход + if (order == "pre") + res.Add(Val(i)!.Value); + DFS(Left(i), order, res); + // Симметричный обход + if (order == "in") + res.Add(Val(i)!.Value); + DFS(Right(i), order, res); + // Обратный обход + if (order == "post") + res.Add(Val(i)!.Value); + } + + /* Предварительный обход */ + public List PreOrder() { + List res = []; + DFS(0, "pre", res); + return res; + } + + /* Симметричный обход */ + public List InOrder() { + List res = []; + DFS(0, "in", res); + return res; + } + + /* Обратный обход */ + public List PostOrder() { + List res = []; + DFS(0, "post", res); + return res; + } +} + +public class array_binary_tree { + [Test] + public void Test() { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + + TreeNode? root = TreeNode.ListToTree(arr); + Console.WriteLine("\nИнициализация двоичного дерева\n"); + Console.WriteLine("Массивное представление двоичного дерева:"); + Console.WriteLine(arr.PrintList()); + Console.WriteLine("Связное представление двоичного дерева:"); + PrintUtil.PrintTree(root); + + // Класс двоичного дерева в массивном представлении + ArrayBinaryTree abt = new(arr); + + // Доступ к узлу + int i = 1; + int l = abt.Left(i); + int r = abt.Right(i); + int p = abt.Parent(i); + Console.WriteLine("\nТекущий узел: индекс = " + i + " , значение = " + abt.Val(i)); + Console.WriteLine("Индекс левого дочернего узла = " + l + " , значение = " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); + Console.WriteLine("Индекс правого дочернего узла = " + r + " , значение = " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); + Console.WriteLine("Индекс родительского узла = " + p + " , значение = " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); + + // Обходить дерево + List res = abt.LevelOrder(); + Console.WriteLine("\nОбход в ширину = " + res.PrintList()); + res = abt.PreOrder(); + Console.WriteLine("Предварительный обход = " + res.PrintList()); + res = abt.InOrder(); + Console.WriteLine("Симметричный обход = " + res.PrintList()); + res = abt.PostOrder(); + Console.WriteLine("Обратный обход = " + res.PrintList()); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/chapter_tree/avl_tree.cs b/ru/codes/csharp/chapter_tree/avl_tree.cs new file mode 100644 index 000000000..369e2c5fe --- /dev/null +++ b/ru/codes/csharp/chapter_tree/avl_tree.cs @@ -0,0 +1,216 @@ +/** + * File: avl_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +/* AVL-дерево */ +class AVLTree { + public TreeNode? root; // Корневой узел + + /* Получить высоту узла */ + int Height(TreeNode? node) { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node == null ? -1 : node.height; + } + + /* Обновить высоту узла */ + void UpdateHeight(TreeNode node) { + // Высота узла равна высоте более высокого поддерева + 1 + node.height = Math.Max(Height(node.left), Height(node.right)) + 1; + } + + /* Получить коэффициент баланса */ + public int BalanceFactor(TreeNode? node) { + // Коэффициент баланса пустого узла равен 0 + if (node == null) return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return Height(node.left) - Height(node.right); + } + + /* Операция правого вращения */ + TreeNode? RightRotate(TreeNode? node) { + TreeNode? child = node?.left; + TreeNode? grandChild = child?.right; + // Выполнить правое вращение узла node вокруг child + child.right = node; + node.left = grandChild; + // Обновить высоту узла + UpdateHeight(node); + UpdateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Операция левого вращения */ + TreeNode? LeftRotate(TreeNode? node) { + TreeNode? child = node?.right; + TreeNode? grandChild = child?.left; + // Выполнить левое вращение узла node вокруг child + child.left = node; + node.right = grandChild; + // Обновить высоту узла + UpdateHeight(node); + UpdateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + TreeNode? Rotate(TreeNode? node) { + // Получить коэффициент баланса узла node + int balanceFactorInt = BalanceFactor(node); + // Левосторонне перекошенное дерево + if (balanceFactorInt > 1) { + if (BalanceFactor(node?.left) >= 0) { + // Правое вращение + return RightRotate(node); + } else { + // Сначала левое вращение, затем правое + node!.left = LeftRotate(node!.left); + return RightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (balanceFactorInt < -1) { + if (BalanceFactor(node?.right) <= 0) { + // Левое вращение + return LeftRotate(node); + } else { + // Сначала правое вращение, затем левое + node!.right = RightRotate(node!.right); + return LeftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + /* Вставка узла */ + public void Insert(int val) { + root = InsertHelper(root, val); + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + TreeNode? InsertHelper(TreeNode? node, int val) { + if (node == null) return new TreeNode(val); + /* 1. Найти позицию вставки и вставить узел */ + if (val < node.val) + node.left = InsertHelper(node.left, val); + else if (val > node.val) + node.right = InsertHelper(node.right, val); + else + return node; // Повторяющийся узел не вставлять, сразу вернуть + UpdateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = Rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Удаление узла */ + public void Remove(int val) { + root = RemoveHelper(root, val); + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + TreeNode? RemoveHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. Найти узел и удалить его */ + if (val < node.val) + node.left = RemoveHelper(node.left, val); + else if (val > node.val) + node.right = RemoveHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == null) + return null; + // Число дочерних узлов = 1, удалить node напрямую + else + node = child; + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + TreeNode? temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = RemoveHelper(node.right, temp.val!.Value); + node.val = temp.val; + } + } + UpdateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = Rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Поиск узла */ + public TreeNode? Search(int val) { + TreeNode? cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < val) + cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > val) + cur = cur.left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } +} + +public class avl_tree { + static void TestInsert(AVLTree tree, int val) { + tree.Insert(val); + Console.WriteLine("\nПосле вставки узла " + val + " AVL-дерево имеет вид"); + PrintUtil.PrintTree(tree.root); + } + + static void TestRemove(AVLTree tree, int val) { + tree.Remove(val); + Console.WriteLine("\nПосле удаления узла " + val + " AVL-дерево имеет вид"); + PrintUtil.PrintTree(tree.root); + } + + [Test] + public void Test() { + /* Инициализация пустого AVL-дерева */ + AVLTree avlTree = new(); + + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + TestInsert(avlTree, 1); + TestInsert(avlTree, 2); + TestInsert(avlTree, 3); + TestInsert(avlTree, 4); + TestInsert(avlTree, 5); + TestInsert(avlTree, 8); + TestInsert(avlTree, 7); + TestInsert(avlTree, 9); + TestInsert(avlTree, 10); + TestInsert(avlTree, 6); + + /* Вставка повторяющегося узла */ + TestInsert(avlTree, 7); + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + TestRemove(avlTree, 8); // Удаление узла степени 0 + TestRemove(avlTree, 5); // Удаление узла степени 1 + TestRemove(avlTree, 4); // Удаление узла степени 2 + + /* Поиск узла */ + TreeNode? node = avlTree.Search(7); + Console.WriteLine("\nНайденный объект узла = " + node + ", значение узла = " + node?.val); + } +} diff --git a/ru/codes/csharp/chapter_tree/binary_search_tree.cs b/ru/codes/csharp/chapter_tree/binary_search_tree.cs new file mode 100644 index 000000000..31db5d896 --- /dev/null +++ b/ru/codes/csharp/chapter_tree/binary_search_tree.cs @@ -0,0 +1,160 @@ +/** + * File: binary_search_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +class BinarySearchTree { + TreeNode? root; + + public BinarySearchTree() { + // Инициализировать пустое дерево + root = null; + } + + /* Получить корневой узел двоичного дерева */ + public TreeNode? GetRoot() { + return root; + } + + /* Поиск узла */ + public TreeNode? Search(int num) { + TreeNode? cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < num) cur = + cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > num) + cur = cur.left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } + + /* Вставка узла */ + public void Insert(int num) { + // Если дерево пусто, инициализировать корневой узел + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти повторяющийся узел и сразу вернуть + if (cur.val == num) + return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur.val < num) + cur = cur.right; + // Позиция вставки находится в левом поддереве cur + else + cur = cur.left; + } + + // Вставка узла + TreeNode node = new(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + } + + + /* Удаление узла */ + public void Remove(int num) { + // Если дерево пусто, сразу вернуть + if (root == null) + return; + TreeNode? cur = root, pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти узел для удаления и выйти из цикла + if (cur.val == num) + break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur.val < num) + cur = cur.right; + // Узел для удаления находится в левом поддереве cur + else + cur = cur.left; + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur == null) + return; + // Число дочерних узлов = 0 или 1 + if (cur.left == null || cur.right == null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + TreeNode? child = cur.left ?? cur.right; + // Удалить узел cur + if (cur != root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + root = child; + } + } + // Число дочерних узлов = 2 + else { + // Получить следующий узел после cur в симметричном обходе + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // Рекурсивно удалить узел tmp + Remove(tmp.val!.Value); + // Перезаписать cur значением tmp + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + [Test] + public void Test() { + /* Инициализация двоичного дерева поиска */ + BinarySearchTree bst = new(); + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + foreach (int num in nums) { + bst.Insert(num); + } + + Console.WriteLine("\nИсходное двоичное дерево\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* Поиск узла */ + TreeNode? node = bst.Search(7); + Console.WriteLine("\nНайденный объект узла = " + node + ", значение узла = " + node?.val); + + /* Вставка узла */ + bst.Insert(16); + Console.WriteLine("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* Удаление узла */ + bst.Remove(1); + Console.WriteLine("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(2); + Console.WriteLine("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(4); + Console.WriteLine("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); + PrintUtil.PrintTree(bst.GetRoot()); + } +} diff --git a/ru/codes/csharp/chapter_tree/binary_tree.cs b/ru/codes/csharp/chapter_tree/binary_tree.cs new file mode 100644 index 000000000..5910e9c75 --- /dev/null +++ b/ru/codes/csharp/chapter_tree/binary_tree.cs @@ -0,0 +1,39 @@ +/** + * File: binary_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree { + [Test] + public void Test() { + /* Инициализация двоичного дерева */ + // Инициализация узла + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // Построить связи между узлами (указатели) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + Console.WriteLine("\nИнициализация двоичного дерева\n"); + PrintUtil.PrintTree(n1); + + /* Вставка и удаление узлов */ + TreeNode P = new(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + Console.WriteLine("\nПосле вставки узла P\n"); + PrintUtil.PrintTree(n1); + // Удалить узел P + n1.left = n2; + Console.WriteLine("\nПосле удаления узла P\n"); + PrintUtil.PrintTree(n1); + } +} diff --git a/ru/codes/csharp/chapter_tree/binary_tree_bfs.cs b/ru/codes/csharp/chapter_tree/binary_tree_bfs.cs new file mode 100644 index 000000000..13a2ada40 --- /dev/null +++ b/ru/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -0,0 +1,40 @@ +/** + * File: binary_tree_bfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_bfs { + + /* Обход в ширину */ + List LevelOrder(TreeNode root) { + // Инициализировать очередь и добавить корневой узел + Queue queue = new(); + queue.Enqueue(root); + // Инициализировать список для хранения последовательности обхода + List list = []; + while (queue.Count != 0) { + TreeNode node = queue.Dequeue(); // Извлечение из очереди + list.Add(node.val!.Value); // Сохранить значение узла + if (node.left != null) + queue.Enqueue(node.left); // Поместить левый дочерний узел в очередь + if (node.right != null) + queue.Enqueue(node.right); // Поместить правый дочерний узел в очередь + } + return list; + } + + [Test] + public void Test() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\nИнициализация двоичного дерева\n"); + PrintUtil.PrintTree(root); + + List list = LevelOrder(root!); + Console.WriteLine("\nПоследовательность печати узлов при обходе в ширину = " + string.Join(",", list)); + } +} diff --git a/ru/codes/csharp/chapter_tree/binary_tree_dfs.cs b/ru/codes/csharp/chapter_tree/binary_tree_dfs.cs new file mode 100644 index 000000000..3173825e5 --- /dev/null +++ b/ru/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -0,0 +1,59 @@ +/** + * File: binary_tree_dfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_dfs { + List list = []; + + /* Предварительный обход */ + void PreOrder(TreeNode? root) { + if (root == null) return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.Add(root.val!.Value); + PreOrder(root.left); + PreOrder(root.right); + } + + /* Симметричный обход */ + void InOrder(TreeNode? root) { + if (root == null) return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + InOrder(root.left); + list.Add(root.val!.Value); + InOrder(root.right); + } + + /* Обратный обход */ + void PostOrder(TreeNode? root) { + if (root == null) return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + PostOrder(root.left); + PostOrder(root.right); + list.Add(root.val!.Value); + } + + [Test] + public void Test() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\nИнициализация двоичного дерева\n"); + PrintUtil.PrintTree(root); + + list.Clear(); + PreOrder(root); + Console.WriteLine("\nПоследовательность печати узлов при предварительном обходе = " + string.Join(",", list)); + + list.Clear(); + InOrder(root); + Console.WriteLine("\nПоследовательность печати узлов при симметричном обходе = " + string.Join(",", list)); + + list.Clear(); + PostOrder(root); + Console.WriteLine("\nПоследовательность печати узлов при обратном обходе = " + string.Join(",", list)); + } +} diff --git a/ru/codes/csharp/csharp.sln b/ru/codes/csharp/csharp.sln new file mode 100644 index 000000000..0c74ccc53 --- /dev/null +++ b/ru/codes/csharp/csharp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} + EndGlobalSection +EndGlobal diff --git a/ru/codes/csharp/hello-algo.csproj b/ru/codes/csharp/hello-algo.csproj new file mode 100644 index 000000000..43817cc38 --- /dev/null +++ b/ru/codes/csharp/hello-algo.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + hello_algo + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/ru/codes/csharp/utils/ListNode.cs b/ru/codes/csharp/utils/ListNode.cs new file mode 100644 index 000000000..35f1b555c --- /dev/null +++ b/ru/codes/csharp/utils/ListNode.cs @@ -0,0 +1,32 @@ +// File: ListNode.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.utils; + +/* Узел связного списка */ +public class ListNode(int x) { + public int val = x; + public ListNode? next; + + /* Десериализовать массив в связный список */ + public static ListNode? ArrToLinkedList(int[] arr) { + ListNode dum = new(0); + ListNode head = dum; + foreach (int val in arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } + + public override string? ToString() { + List list = []; + var head = this; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + return string.Join("->", list); + } +} diff --git a/ru/codes/csharp/utils/PrintUtil.cs b/ru/codes/csharp/utils/PrintUtil.cs new file mode 100644 index 000000000..794c6a0e9 --- /dev/null +++ b/ru/codes/csharp/utils/PrintUtil.cs @@ -0,0 +1,132 @@ +/** +* File: PrintUtil.cs +* Created Time: 2022-12-23 +* Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) +*/ + +namespace hello_algo.utils; + +public class Trunk(Trunk? prev, string str) { + public Trunk? prev = prev; + public string str = str; +}; + +public static class PrintUtil { + /* Вывести список */ + public static void PrintList(IList list) { + Console.WriteLine("[" + string.Join(", ", list) + "]"); + } + + public static string PrintList(this IEnumerable list) { + return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; + } + + /* Вывести матрицу (Array) */ + public static void PrintMatrix(T[][] matrix) { + Console.WriteLine("["); + foreach (T[] row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* Вывести матрицу (List) */ + public static void PrintMatrix(List> matrix) { + Console.WriteLine("["); + foreach (List row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* Вывести связный список */ + public static void PrintLinkedList(ListNode? head) { + List list = []; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + Console.Write(string.Join(" -> ", list)); + } + + /** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void PrintTree(TreeNode? root) { + PrintTree(root, null, false); + } + + /* Вывести двоичное дерево */ + public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { + if (root == null) { + return; + } + + string prev_str = " "; + Trunk trunk = new(prev, prev_str); + + PrintTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + ShowTrunks(trunk); + Console.WriteLine(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + PrintTree(root.left, trunk, false); + } + + public static void ShowTrunks(Trunk? p) { + if (p == null) { + return; + } + + ShowTrunks(p.prev); + Console.Write(p.str); + } + + /* Вывести хеш-таблицу */ + public static void PrintHashMap(Dictionary map) where K : notnull { + foreach (var kv in map.Keys) { + Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); + } + } + + /* Вывести кучу */ + public static void PrintHeap(Queue queue) { + Console.Write("Массивное представление кучи:"); + List list = [.. queue]; + Console.WriteLine(string.Join(',', list)); + Console.WriteLine("Древовидное представление кучи:"); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } + + /* Вывести приоритетную очередь */ + public static void PrintHeap(PriorityQueue queue) { + var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); + Console.Write("Массивное представление кучи:"); + List list = []; + while (newQueue.TryDequeue(out int element, out _)) { + list.Add(element); + } + Console.WriteLine("Древовидное представление кучи:"); + Console.WriteLine(string.Join(',', list.ToList())); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } +} \ No newline at end of file diff --git a/ru/codes/csharp/utils/TreeNode.cs b/ru/codes/csharp/utils/TreeNode.cs new file mode 100644 index 000000000..481f6df64 --- /dev/null +++ b/ru/codes/csharp/utils/TreeNode.cs @@ -0,0 +1,67 @@ +/** + * File: TreeNode.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.utils; + +/* Класс узла двоичного дерева */ +public class TreeNode(int? x) { + public int? val = x; // Значение узла + public int height; // Высота узла + public TreeNode? left; // Ссылка на левый дочерний узел + public TreeNode? right; // Ссылка на правый дочерний узел + + // Правила кодирования сериализации см.: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // Массивное представление двоичного дерева: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // Связное представление двоичного дерева: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* Десериализовать список в двоичное дерево: рекурсия */ + static TreeNode? ListToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.Count || !arr[i].HasValue) { + return null; + } + TreeNode root = new(arr[i]) { + left = ListToTreeDFS(arr, 2 * i + 1), + right = ListToTreeDFS(arr, 2 * i + 2) + }; + return root; + } + + /* Десериализовать список в двоичное дерево */ + public static TreeNode? ListToTree(List arr) { + return ListToTreeDFS(arr, 0); + } + + /* Сериализовать двоичное дерево в список: рекурсия */ + static void TreeToListDFS(TreeNode? root, int i, List res) { + if (root == null) + return; + while (i >= res.Count) { + res.Add(null); + } + res[i] = root.val; + TreeToListDFS(root.left, 2 * i + 1, res); + TreeToListDFS(root.right, 2 * i + 2, res); + } + + /* Сериализовать двоичное дерево в список */ + public static List TreeToList(TreeNode root) { + List res = []; + TreeToListDFS(root, 0, res); + return res; + } +} diff --git a/ru/codes/csharp/utils/Vertex.cs b/ru/codes/csharp/utils/Vertex.cs new file mode 100644 index 000000000..2ea29f0d6 --- /dev/null +++ b/ru/codes/csharp/utils/Vertex.cs @@ -0,0 +1,30 @@ +/** + * File: Vertex.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) + */ + +namespace hello_algo.utils; + +/* Класс вершины */ +public class Vertex(int val) { + public int val = val; + + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + public static Vertex[] ValsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.Length]; + for (int i = 0; i < vals.Length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + public static List VetsToVals(List vets) { + List vals = []; + foreach (Vertex vet in vets) { + vals.Add(vet.val); + } + return vals; + } +} diff --git a/ru/codes/dart/build.dart b/ru/codes/dart/build.dart new file mode 100644 index 000000000..7bd5b51a1 --- /dev/null +++ b/ru/codes/dart/build.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +void main() { + Directory foldPath = Directory('codes/dart/'); + List files = foldPath.listSync(); + int totalCount = 0; + int errorCount = 0; + for (var file in files) { + if (file.path.endsWith('build.dart')) continue; + if (file is File && file.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [file.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } else if (file is Directory) { + List subFiles = file.listSync(); + for (var subFile in subFiles) { + if (subFile is File && subFile.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [subFile.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } + } + } + } + + print('===== Build Complete ====='); + print('Total: $totalCount'); + print('Error: $errorCount'); +} diff --git a/ru/codes/dart/chapter_array_and_linkedlist/array.dart b/ru/codes/dart/chapter_array_and_linkedlist/array.dart new file mode 100644 index 000000000..e6108f9fe --- /dev/null +++ b/ru/codes/dart/chapter_array_and_linkedlist/array.dart @@ -0,0 +1,105 @@ +/** + * File: array.dart + * Created Time: 2023-01-20 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:math'; + +/* Случайный доступ к элементу */ +int randomAccess(List nums) { + // Случайным образом выбрать число из интервала [0, nums.length) + int randomIndex = Random().nextInt(nums.length); + // Получить и вернуть случайный элемент + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* Увеличить длину массива */ +List extend(List nums, int enlarge) { + // Инициализировать массив увеличенной длины + List res = List.filled(nums.length + enlarge, 0); + // Скопировать все элементы исходного массива в новый массив + for (var i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // Вернуть новый массив после расширения + return res; +} + +/* Вставить элемент _num по индексу index в массив */ +void insert(List nums, int _num, int index) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (var i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить _num элементу по индексу index + nums[index] = _num; +} + +/* Удалить элемент по индексу index */ +void remove(List nums, int index) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (var i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* Перебрать элементы массива */ +void traverse(List nums) { + int count = 0; + // Обход массива по индексам + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + // Непосредственно обходить элементы массива + for (int _num in nums) { + count += _num; + } + // Перебрать массив методом forEach + nums.forEach((_num) { + count += _num; + }); +} + +/* Найти заданный элемент в массиве */ +int find(List nums, int target) { + for (var i = 0; i < nums.length; i++) { + if (nums[i] == target) return i; + } + return -1; +} + +/* Driver Code */ +void main() { + /* Инициализация массива */ + var arr = List.filled(5, 0); + print('Массив arr = $arr'); + List nums = [1, 3, 2, 5, 4]; + print('Массив nums = $nums'); + + /* Случайный доступ */ + int randomNum = randomAccess(nums); + print('Случайный элемент из nums = $randomNum'); + + /* Расширение длины */ + nums = extend(nums, 3); + print('После увеличения длины массива до 8 nums = $nums'); + + /* Вставка элемента */ + insert(nums, 6, 3); + print('После вставки числа 6 по индексу 3 nums = $nums'); + + /* Удаление элемента */ + remove(nums, 2); + print('После удаления элемента по индексу 2 nums = $nums'); + + /* Обход массива */ + traverse(nums); + + /* Поиск элемента */ + int index = find(nums, 3); + print('Поиск элемента 3 в nums: индекс = $index'); +} diff --git a/ru/codes/dart/chapter_array_and_linkedlist/linked_list.dart b/ru/codes/dart/chapter_array_and_linkedlist/linked_list.dart new file mode 100644 index 000000000..6763176f7 --- /dev/null +++ b/ru/codes/dart/chapter_array_and_linkedlist/linked_list.dart @@ -0,0 +1,83 @@ +/** + * File: linked_list.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; + +/* Вставить узел P после узла n0 в связном списке */ +void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* Удалить первый узел после узла n0 в связном списке */ +void remove(ListNode n0) { + if (n0.next == null) return; + // n0 -> P -> n1 + ListNode P = n0.next!; + ListNode? n1 = P.next; + n0.next = n1; +} + +/* Доступ к узлу связного списка по индексу index */ +ListNode? access(ListNode? head, int index) { + for (var i = 0; i < index; i++) { + if (head == null) return null; + head = head.next; + } + return head; +} + +/* Найти в связном списке первый узел со значением target */ +int find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) { + return index; + } + head = head.next; + index++; + } + return -1; +} + +/* Driver Code */ +void main() { + // Инициализация связного списка + // Инициализация всех узлов + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // Построить ссылки между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + + print('Исходный связный список'); + printLinkedList(n0); + + /* Вставка узла */ + insert(n0, ListNode(0)); + print('Связный список после вставки узла'); + printLinkedList(n0); + + /* Удаление узла */ + remove(n0); + print('Связный список после удаления узла'); + printLinkedList(n0); + + /* Доступ к узлу */ + ListNode? node = access(n0, 3); + print('Значение узла по индексу 3 в связном списке = ${node!.val}'); + + /* Поиск узла */ + int index = find(n0, 2); + print('Индекс узла со значением 2 в связном списке = $index'); +} diff --git a/ru/codes/dart/chapter_array_and_linkedlist/list.dart b/ru/codes/dart/chapter_array_and_linkedlist/list.dart new file mode 100644 index 000000000..e376b8684 --- /dev/null +++ b/ru/codes/dart/chapter_array_and_linkedlist/list.dart @@ -0,0 +1,62 @@ +/** + * File: list.dart + * Created Time: 2023-01-24 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* Driver Code */ +void main() { + /* Инициализация списка */ + List nums = [1, 3, 2, 5, 4]; + print('Список nums = $nums'); + + /* Доступ к элементу */ + int _num = nums[1]; + print('Элемент по индексу 1: _num = $_num'); + + /* Обновление элемента */ + nums[1] = 0; + print('После обновления элемента по индексу 1 до 0 nums = $nums'); + + /* Очистить список */ + nums.clear(); + print('После очистки списка nums = $nums'); + + /* Добавление элемента в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print('После добавления элементов nums = $nums'); + + /* Вставка элемента в середину */ + nums.insert(3, 6); + print('После вставки числа 6 по индексу 3 nums = $nums'); + + /* Удаление элемента */ + nums.removeAt(3); + print('После удаления элемента по индексу 3 nums = $nums'); + + /* Обходить список по индексам */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + /* Непосредственно обходить элементы списка */ + count = 0; + for (var x in nums) { + count += x; + } + + /* Объединить два списка */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); + print('После конкатенации списка nums1 к nums nums = $nums'); + + /* Отсортировать список */ + nums.sort(); + print('После сортировки списка nums = $nums'); +} diff --git a/ru/codes/dart/chapter_array_and_linkedlist/my_list.dart b/ru/codes/dart/chapter_array_and_linkedlist/my_list.dart new file mode 100644 index 000000000..27f1e9b8d --- /dev/null +++ b/ru/codes/dart/chapter_array_and_linkedlist/my_list.dart @@ -0,0 +1,132 @@ +/** + * File: my_list.dart + * Created Time: 2023-02-05 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* Класс списка */ +class MyList { + late List _arr; // Массив (для хранения элементов списка) + int _capacity = 10; // Вместимость списка + int _size = 0; // Длина списка (текущее число элементов) + int _extendRatio = 2; // Коэффициент увеличения списка при каждом расширении + + /* Конструктор */ + MyList() { + _arr = List.filled(_capacity, 0); + } + + /* Получить длину списка (текущее число элементов) */ + int size() => _size; + + /* Получить вместимость списка */ + int capacity() => _capacity; + + /* Доступ к элементу */ + int get(int index) { + if (index >= _size) throw RangeError('индекс выходит за границы'); + return _arr[index]; + } + + /* Обновление элемента */ + void set(int index, int _num) { + if (index >= _size) throw RangeError('индекс выходит за границы'); + _arr[index] = _num; + } + + /* Добавление элемента в конец */ + void add(int _num) { + // При превышении вместимости по числу элементов запускается расширение + if (_size == _capacity) extendCapacity(); + _arr[_size] = _num; + // Обновить число элементов + _size++; + } + + /* Вставка элемента в середину */ + void insert(int index, int _num) { + if (index >= _size) throw RangeError('индекс выходит за границы'); + // При превышении вместимости по числу элементов запускается расширение + if (_size == _capacity) extendCapacity(); + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (var j = _size - 1; j >= index; j--) { + _arr[j + 1] = _arr[j]; + } + _arr[index] = _num; + // Обновить число элементов + _size++; + } + + /* Удаление элемента */ + int remove(int index) { + if (index >= _size) throw RangeError('индекс выходит за границы'); + int _num = _arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (var j = index; j < _size - 1; j++) { + _arr[j] = _arr[j + 1]; + } + // Обновить число элементов + _size--; + // Вернуть удаленный элемент + return _num; + } + + /* Расширение списка */ + void extendCapacity() { + // Создать новый массив длиной в _extendRatio раз больше исходного массива + final _newNums = List.filled(_capacity * _extendRatio, 0); + // Скопировать исходный массив в новый массив + List.copyRange(_newNums, 0, _arr); + // Обновить ссылку на _arr + _arr = _newNums; + // Обновить вместимость списка + _capacity = _arr.length; + } + + /* Преобразовать список в массив */ + List toArray() { + List arr = []; + for (var i = 0; i < _size; i++) { + arr.add(get(i)); + } + return arr; + } +} + +/* Driver Code */ +void main() { + /* Инициализация списка */ + MyList nums = MyList(); + /* Добавление элемента в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print( + 'Список nums = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}'); + + /* Вставка элемента в середину */ + nums.insert(3, 6); + print('После вставки числа 6 по индексу 3 nums = ${nums.toArray()}'); + + /* Удаление элемента */ + nums.remove(3); + print('После удаления элемента по индексу 3 nums = ${nums.toArray()}'); + + /* Доступ к элементу */ + int _num = nums.get(1); + print('Элемент по индексу 1: _num = $_num'); + + /* Обновление элемента */ + nums.set(1, 0); + print('После обновления элемента по индексу 1 до 0 nums = ${nums.toArray()}'); + + /* Проверка механизма расширения */ + for (var i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i); + } + print( + 'Список nums после увеличения вместимости = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}'); +} diff --git a/ru/codes/dart/chapter_backtracking/n_queens.dart b/ru/codes/dart/chapter_backtracking/n_queens.dart new file mode 100644 index 000000000..b072d9f59 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/n_queens.dart @@ -0,0 +1,75 @@ +/** + * File: n_queens.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Алгоритм бэктрекинга: n ферзей */ +void backtrack( + int row, + int n, + List> state, + List>> res, + List cols, + List diags1, + List diags2, +) { + // Когда все строки уже обработаны, записать решение + if (row == n) { + List> copyState = []; + for (List sRow in state) { + copyState.add(List.from(sRow)); + } + res.add(copyState); + return; + } + // Обойти все столбцы + for (int col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state[row][col] = "Q"; + cols[col] = true; + diags1[diag1] = true; + diags2[diag2] = true; + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = "#"; + cols[col] = false; + diags1[diag1] = false; + diags2[diag2] = false; + } + } +} + +/* Решить задачу о n ферзях */ +List>> nQueens(int n) { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + List> state = List.generate(n, (index) => List.filled(n, "#")); + List cols = List.filled(n, false); // Отмечать, есть ли ферзь в столбце + List diags1 = List.filled(2 * n - 1, false); // Отмечать наличие ферзя на главной диагонали + List diags2 = List.filled(2 * n - 1, false); // Отмечать наличие ферзя на побочной диагонали + List>> res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +void main() { + int n = 4; + List>> res = nQueens(n); + print("Размер входной доски = $n"); + print("Количество способов расстановки ферзей: ${res.length}"); + for (List> state in res) { + print("--------------------"); + for (List row in state) { + print(row); + } + } +} diff --git a/ru/codes/dart/chapter_backtracking/permutations_i.dart b/ru/codes/dart/chapter_backtracking/permutations_i.dart new file mode 100644 index 000000000..19071a5e9 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/permutations_i.dart @@ -0,0 +1,51 @@ +/** + * File: permutations_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки I */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // Когда длина состояния равна числу элементов, записать решение + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.add(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.removeLast(); + } + } +} + +/* Все перестановки I */ +List> permutationsI(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 3]; + + List> res = permutationsI(nums); + + print("Входной массив nums = $nums"); + print("Все перестановки res = $res"); +} diff --git a/ru/codes/dart/chapter_backtracking/permutations_ii.dart b/ru/codes/dart/chapter_backtracking/permutations_ii.dart new file mode 100644 index 000000000..816c442e7 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/permutations_ii.dart @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки II */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // Когда длина состояния равна числу элементов, записать решение + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // Перебор всех вариантов выбора + Set duplicated = {}; + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated.contains(choice)) { + // Попытка: сделать выбор и обновить состояние + duplicated.add(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.add(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.removeLast(); + } + } +} + +/* Все перестановки II */ +List> permutationsII(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 2]; + + List> res = permutationsII(nums); + + print("Входной массив nums = $nums"); + print("Все перестановки res = $res"); +} diff --git a/ru/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart b/ru/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart new file mode 100644 index 000000000..4c3001474 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart @@ -0,0 +1,35 @@ +/** + * File: preorder_traversal_i_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Предварительный обход: пример 1 */ +void preOrder(TreeNode? root, List res) { + if (root == null) { + return; + } + if (root.val == 7) { + // Записать решение + res.add(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\nИнициализация двоичного дерева"); + printTree(root); + + // Предварительный обход + List res = []; + preOrder(root, res); + + print("\nВсе узлы со значением 7"); + print(List.generate(res.length, (i) => res[i].val)); +} diff --git a/ru/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart b/ru/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart new file mode 100644 index 000000000..efc949725 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Предварительный обход: пример 2 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null) { + return; + } + + // Попытка + path.add(root); + if (root.val == 7) { + // Записать решение + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // Откат + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\nИнициализация двоичного дерева"); + printTree(root); + + // Предварительный обход + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\nВсе пути от корня к узлу 7"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/ru/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart b/ru/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart new file mode 100644 index 000000000..39a1c4283 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Предварительный обход: пример 3 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null || root.val == 3) { + return; + } + + // Попытка + path.add(root); + if (root.val == 7) { + // Записать решение + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // Откат + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\nИнициализация двоичного дерева"); + printTree(root); + + // Предварительный обход + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\nВсе пути от корня к узлу 7"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/ru/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart b/ru/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart new file mode 100644 index 000000000..8035177b1 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart @@ -0,0 +1,73 @@ +/** + * File: preorder_traversal_iii_template.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Проверить, является ли текущее состояние решением */ +bool isSolution(List state) { + return state.isNotEmpty && state.last.val == 7; +} + +/* Записать решение */ +void recordSolution(List state, List> res) { + res.add(List.from(state)); +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +bool isValid(List state, TreeNode? choice) { + return choice != null && choice.val != 3; +} + +/* Обновить состояние */ +void makeChoice(List state, TreeNode? choice) { + state.add(choice!); +} + +/* Восстановить состояние */ +void undoChoice(List state, TreeNode? choice) { + state.removeLast(); +} + +/* Алгоритм бэктрекинга: пример 3 */ +void backtrack( + List state, + List choices, + List> res, +) { + // Проверить, является ли текущее состояние решением + if (isSolution(state)) { + // Записать решение + recordSolution(state, res); + } + // Перебор всех вариантов выбора + for (TreeNode? choice in choices) { + // Отсечение: проверить допустимость выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + // Перейти к следующему выбору + backtrack(state, [choice!.left, choice.right], res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\nИнициализация двоичного дерева"); + printTree(root); + + // Алгоритм бэктрекинга + List> res = []; + backtrack([], [root!], res); + print("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); + for (List path in res) { + print(List.from(path.map((e) => e.val))); + } +} diff --git a/ru/codes/dart/chapter_backtracking/subset_sum_i.dart b/ru/codes/dart/chapter_backtracking/subset_sum_i.dart new file mode 100644 index 000000000..550e46712 --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/subset_sum_i.dart @@ -0,0 +1,56 @@ +/** + * File: subset_sum_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.add(List.from(state)); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (int i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state.add(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeLast(); + } +} + +/* Решить задачу суммы подмножеств I */ +List> subsetSumI(List nums, int target) { + List state = []; // Состояние (подмножество) + nums.sort(); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + List> res = []; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumI(nums, target); + + print("Входной массив nums = $nums, target = $target"); + print("Все подмножества с суммой $target: res = $res"); +} diff --git a/ru/codes/dart/chapter_backtracking/subset_sum_i_naive.dart b/ru/codes/dart/chapter_backtracking/subset_sum_i_naive.dart new file mode 100644 index 000000000..c21adfa6d --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/subset_sum_i_naive.dart @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +void backtrack( + List state, + int target, + int total, + List choices, + List> res, +) { + // Если сумма подмножества равна target, записать решение + if (total == target) { + res.add(List.from(state)); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.length; i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.add(choices[i]); + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeLast(); + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +List> subsetSumINaive(List nums, int target) { + List state = []; // Состояние (подмножество) + int total = 0; // Сумма элементов + List> res = []; // Список результатов (список подмножеств) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + print("Входной массив nums = $nums, target = $target"); + print("Все подмножества с суммой $target: res = $res"); + print("Обратите внимание: результат этого метода содержит повторяющиеся множества"); +} diff --git a/ru/codes/dart/chapter_backtracking/subset_sum_ii.dart b/ru/codes/dart/chapter_backtracking/subset_sum_ii.dart new file mode 100644 index 000000000..6341b999f --- /dev/null +++ b/ru/codes/dart/chapter_backtracking/subset_sum_ii.dart @@ -0,0 +1,61 @@ +/** + * File: subset_sum_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.add(List.from(state)); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (int i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.add(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeLast(); + } +} + +/* Решить задачу суммы подмножеств II */ +List> subsetSumII(List nums, int target) { + List state = []; // Состояние (подмножество) + nums.sort(); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + List> res = []; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [4, 4, 5]; + int target = 9; + + List> res = subsetSumII(nums, target); + + print("Входной массив nums = $nums, target = $target"); + print("Все подмножества с суммой $target: res = $res"); +} diff --git a/ru/codes/dart/chapter_computational_complexity/iteration.dart b/ru/codes/dart/chapter_computational_complexity/iteration.dart new file mode 100644 index 000000000..bf980be9f --- /dev/null +++ b/ru/codes/dart/chapter_computational_complexity/iteration.dart @@ -0,0 +1,72 @@ +/** + * File: iteration.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Цикл for */ +int forLoop(int n) { + int res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* Цикл while */ +int whileLoop(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Обновить условную переменную + } + return res; +} + +/* Цикл while (двойное обновление) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i++; + i *= 2; + } + return res; +} + +/* Двойной цикл for */ +String nestedForLoop(int n) { + String res = ""; + // Цикл по i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // Цикл по j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res += "($i, $j), "; + } + } + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = forLoop(n); + print("\nРезультат суммирования в цикле for res = $res"); + + res = whileLoop(n); + print("\nРезультат суммирования в цикле while res = $res"); + + res = whileLoopII(n); + print("\nРезультат суммирования в цикле while (двойное обновление) res = $res"); + + String resStr = nestedForLoop(n); + print("\nРезультат двойного цикла for $resStr"); +} diff --git a/ru/codes/dart/chapter_computational_complexity/recursion.dart b/ru/codes/dart/chapter_computational_complexity/recursion.dart new file mode 100644 index 000000000..a98231a6b --- /dev/null +++ b/ru/codes/dart/chapter_computational_complexity/recursion.dart @@ -0,0 +1,70 @@ +/** + * File: recursion.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Рекурсия */ +int recur(int n) { + // Условие завершения + if (n == 1) return 1; + // Рекурсия: рекурсивный вызов + int res = recur(n - 1); + // Возврат: вернуть результат + return n + res; +} + +/* Имитация рекурсии итерацией */ +int forLoopRecur(int n) { + // Использовать явный стек для имитации системного стека вызовов + List stack = []; + int res = 0; + // Рекурсия: рекурсивный вызов + for (int i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.add(i); + } + // Возврат: вернуть результат + while (!stack.isEmpty) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.removeLast(); + } + // res = 1+2+3+...+n + return res; +} + +/* Хвостовая рекурсия */ +int tailRecur(int n, int res) { + // Условие завершения + if (n == 0) return res; + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); +} + +/* Последовательность Фибоначчи: рекурсия */ +int fib(int n) { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = recur(n); + print("\nРезультат суммирования в рекурсивной функции res = $res"); + + res = tailRecur(n, 0); + print("\nРезультат суммирования в хвостовой рекурсии res = $res"); + + res = forLoopRecur(n); + print("\nРезультат суммирования при имитации рекурсии итерацией res = $res"); + + res = fib(n); + print("\nЧлен последовательности Фибоначчи с номером $n = $res"); +} diff --git a/ru/codes/dart/chapter_computational_complexity/space_complexity.dart b/ru/codes/dart/chapter_computational_complexity/space_complexity.dart new file mode 100644 index 000000000..de17009d4 --- /dev/null +++ b/ru/codes/dart/chapter_computational_complexity/space_complexity.dart @@ -0,0 +1,106 @@ +/** + * File: space_complexity.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:collection'; +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Функция */ +int function() { + // Выполнить некоторые операции + return 0; +} + +/* Постоянная сложность */ +void constant(int n) { + // Константы, переменные и объекты занимают O(1) памяти + final int a = 0; + int b = 0; + List nums = List.filled(10000, 0); + ListNode node = ListNode(0); + // Переменные в цикле занимают O(1) памяти + for (var i = 0; i < n; i++) { + int c = 0; + } + // Функции в цикле занимают O(1) памяти + for (var i = 0; i < n; i++) { + function(); + } +} + +/* Линейная сложность */ +void linear(int n) { + // Массив длины n занимает O(n) памяти + List nums = List.filled(n, 0); + // Список длины n занимает O(n) памяти + List nodes = []; + for (var i = 0; i < n; i++) { + nodes.add(ListNode(i)); + } + // Хеш-таблица длины n занимает O(n) памяти + Map map = HashMap(); + for (var i = 0; i < n; i++) { + map.putIfAbsent(i, () => i.toString()); + } +} + +/* Линейная сложность (рекурсивная реализация) */ +void linearRecur(int n) { + print('Рекурсия n = $n'); + if (n == 1) return; + linearRecur(n - 1); +} + +/* Квадратичная сложность */ +void quadratic(int n) { + // Матрица занимает O(n^2) памяти + List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); + // Двумерный список занимает O(n^2) памяти + List> numList = []; + for (var i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } +} + +/* Квадратичная сложность (рекурсивная реализация) */ +int quadraticRecur(int n) { + if (n <= 0) return 0; + List nums = List.filled(n, 0); + print('В рекурсии n = $n длина nums = ${nums.length}'); + return quadraticRecur(n - 1); +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +TreeNode? buildTree(int n) { + if (n == 0) return null; + TreeNode root = TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +void main() { + int n = 5; + // Постоянная сложность + constant(n); + // Линейная сложность + linear(n); + linearRecur(n); + // Квадратичная сложность + quadratic(n); + quadraticRecur(n); + // Экспоненциальная сложность + TreeNode? root = buildTree(n); + printTree(root); +} diff --git a/ru/codes/dart/chapter_computational_complexity/time_complexity.dart b/ru/codes/dart/chapter_computational_complexity/time_complexity.dart new file mode 100644 index 000000000..22916c7f9 --- /dev/null +++ b/ru/codes/dart/chapter_computational_complexity/time_complexity.dart @@ -0,0 +1,165 @@ +/** + * File: time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* Постоянная сложность */ +int constant(int n) { + int count = 0; + int size = 100000; + for (var i = 0; i < size; i++) { + count++; + } + return count; +} + +/* Линейная сложность */ +int linear(int n) { + int count = 0; + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Линейная сложность (обход массива) */ +int arrayTraversal(List nums) { + int count = 0; + // Число итераций пропорционально длине массива + for (var _num in nums) { + count++; + } + return count; +} + +/* Квадратичная сложность */ +int quadratic(int n) { + int count = 0; + // Число итераций квадратично зависит от размера данных n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +int bubbleSort(List nums) { + int count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (var i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (var j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; +} + +/* Экспоненциальная сложность (итеративная реализация) */ +int exponential(int n) { + int count = 0, base = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (var i = 0; i < n; i++) { + for (var j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +int expRecur(int n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* Логарифмическая сложность (итеративная реализация) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n ~/ 2; + count++; + } + return count; +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +int logRecur(int n) { + if (n <= 1) return 0; + return logRecur(n ~/ 2) + 1; +} + +/* Линейно-логарифмическая сложность */ +int linearLogRecur(int n) { + if (n <= 1) return 1; + int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Факториальная сложность (рекурсивная реализация) */ +int factorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // Из одного получается n + for (var i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +void main() { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + int n = 8; + print('Размер входных данных n = $n'); + + int count = constant(n); + print('Число операций константной сложности = $count'); + + count = linear(n); + print('Число операций линейной сложности = $count'); + + count = arrayTraversal(List.filled(n, 0)); + print('Число операций линейной сложности (обход массива) = $count'); + + count = quadratic(n); + print('Число операций квадратичной сложности = $count'); + final nums = List.filled(n, 0); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums); + print('Число операций квадратичной сложности (пузырьковая сортировка) = $count'); + + count = exponential(n); + print('Число операций экспоненциальной сложности (итеративная реализация) = $count'); + count = expRecur(n); + print('Число операций экспоненциальной сложности (рекурсивная реализация) = $count'); + + count = logarithmic(n); + print('Число операций логарифмической сложности (итеративная реализация) = $count'); + count = logRecur(n); + print('Число операций логарифмической сложности (рекурсивная реализация) = $count'); + + count = linearLogRecur(n); + print('Число операций линейно-логарифмической сложности (рекурсивная реализация) = $count'); + + count = factorialRecur(n); + print('Число операций факториальной сложности (рекурсивная реализация) = $count'); +} diff --git a/ru/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart b/ru/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart new file mode 100644 index 000000000..73256fcb2 --- /dev/null +++ b/ru/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +List randomNumbers(int n) { + final nums = List.filled(n, 0); + // Создать массив nums = { 1, 2, 3, ..., n } + for (var i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Случайно перемешать элементы массива + nums.shuffle(); + + return nums; +} + +/* Найти индекс числа 1 в массиве nums */ +int findOne(List nums) { + for (var i = 0; i < nums.length; i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] == 1) return i; + } + + return -1; +} + +/* Driver Code */ +void main() { + for (var i = 0; i < 10; i++) { + int n = 100; + final nums = randomNumbers(n); + int index = findOne(nums); + print('\nМассив [1, 2, ..., n] после перемешивания = $nums'); + print('Индекс числа 1 = $index'); + } +} diff --git a/ru/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart b/ru/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart new file mode 100644 index 000000000..a927c7be4 --- /dev/null +++ b/ru/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart @@ -0,0 +1,42 @@ +/** + * File: binary_search_recur.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Бинарный поиск: задача f(i, j) */ +int dfs(List nums, int target, int i, int j) { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + int m = (i + j) ~/ 2; + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } +} + +/* Бинарный поиск */ +int binarySearch(List nums, int target) { + int n = nums.length; + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +void main() { + int target = 6; + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // Бинарный поиск (двусторонне замкнутый интервал) + int index = binarySearch(nums, target); + print("Индекс целевого элемента 6 = $index"); +} diff --git a/ru/codes/dart/chapter_divide_and_conquer/build_tree.dart b/ru/codes/dart/chapter_divide_and_conquer/build_tree.dart new file mode 100644 index 000000000..3af3e307a --- /dev/null +++ b/ru/codes/dart/chapter_divide_and_conquer/build_tree.dart @@ -0,0 +1,55 @@ +/** + * File: build_tree.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Построить двоичное дерево: разделяй и властвуй */ +TreeNode? dfs( + List preorder, + Map inorderMap, + int i, + int l, + int r, +) { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) { + return null; + } + // Инициализировать корневой узел + TreeNode? root = TreeNode(preorder[i]); + // Найти m, чтобы разделить левое и правое поддеревья + int m = inorderMap[preorder[i]]!; + // Подзадача: построить левое поддерево + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + return root; +} + +/* Построить двоичное дерево */ +TreeNode? buildTree(List preorder, List inorder) { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + Map inorderMap = {}; + for (int i = 0; i < inorder.length; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +void main() { + List preorder = [3, 9, 2, 1, 7]; + List inorder = [9, 3, 1, 2, 7]; + print("Предварительный обход = $preorder"); + print("Симметричный обход = $inorder"); + + TreeNode? root = buildTree(preorder, inorder); + print("Построенное двоичное дерево:"); + printTree(root!); +} diff --git a/ru/codes/dart/chapter_divide_and_conquer/hanota.dart b/ru/codes/dart/chapter_divide_and_conquer/hanota.dart new file mode 100644 index 000000000..fe00cd1f4 --- /dev/null +++ b/ru/codes/dart/chapter_divide_and_conquer/hanota.dart @@ -0,0 +1,54 @@ +/** + * File: hanota.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Переместить один диск */ +void move(List src, List tar) { + // Снять диск с вершины src + int pan = src.removeLast(); + // Положить диск на вершину tar + tar.add(pan); +} + +/* Решить задачу Ханойской башни f(i) */ +void dfs(int i, List src, List buf, List tar) { + // Если в src остался только один диск, сразу переместить его в tar + if (i == 1) { + move(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar); +} + +/* Решить задачу Ханойской башни */ +void solveHanota(List A, List B, List C) { + int n = A.length; + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C); +} + +/* Driver Code */ +void main() { + // Хвост списка соответствует вершине столбца + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + print("Исходное состояние:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); + + solveHanota(A, B, C); + + print("После завершения перемещения дисков:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart new file mode 100644 index 000000000..35533da5b --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_backtrack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Бэктрекинг */ +void backtrack(List choices, int state, int n, List res) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) { + res[0]++; + } + // Перебор всех вариантов выбора + for (int choice in choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) continue; + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +int climbingStairsBacktrack(int n) { + List choices = [1, 2]; // Можно подняться на 1 или 2 ступени + int state = 0; // Начать подъем с 0-й ступени + List res = []; + res.add(0); // Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + print("Количество способов подняться по лестнице из $n ступеней = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart new file mode 100644 index 000000000..a3e495eea --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_constraint_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + print("Количество способов подняться по лестнице из $n ступеней = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart new file mode 100644 index 000000000..e1747d55d --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart @@ -0,0 +1,27 @@ +/** + * File: climbing_stairs_dfs.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Поиск */ +int dfs(int i) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* Подъем по лестнице: поиск */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFS(n); + print("Количество способов подняться по лестнице из $n ступеней = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart new file mode 100644 index 000000000..bd7569deb --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_dfs_mem.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Поиск с мемоизацией */ +int dfs(int i, List mem) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; +} + +/* Подъем по лестнице: поиск с мемоизацией */ +int climbingStairsDFSMem(int n) { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + List mem = List.filled(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + print("Количество способов подняться по лестнице из $n ступеней = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart new file mode 100644 index 000000000..94c4585a3 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart @@ -0,0 +1,43 @@ +/** + * File: climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Подъем по лестнице: динамическое программирование */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) return n; + // Инициализация таблицы dp для хранения решений подзадач + List dp = List.filled(n + 1, 0); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDP(n); + print("Количество способов подняться по лестнице из $n ступеней = $res"); + + res = climbingStairsDPComp(n); + print("Количество способов подняться по лестнице из $n ступеней = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/coin_change.dart b/ru/codes/dart/chapter_dynamic_programming/coin_change.dart new file mode 100644 index 000000000..b1d4ac034 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/coin_change.dart @@ -0,0 +1,68 @@ +/** + * File: coin_change.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Размен монет: динамическое программирование */ +int coinChangeDP(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // Инициализация таблицы dp + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // Переход состояний: первая строка и первый столбец + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +int coinChangeDPComp(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // Инициализация таблицы dp + List dp = List.filled(amt + 1, MAX); + dp[0] = 0; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 4; + + // Динамическое программирование + int res = coinChangeDP(coins, amt); + print("Минимальное число монет для набора целевой суммы = $res"); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins, amt); + print("Минимальное число монет для набора целевой суммы = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/coin_change_ii.dart b/ru/codes/dart/chapter_dynamic_programming/coin_change_ii.dart new file mode 100644 index 000000000..009d69179 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/coin_change_ii.dart @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Размен монет II: динамическое программирование */ +int coinChangeIIDP(List coins, int amt) { + int n = coins.length; + // Инициализация таблицы dp + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // Инициализация первого столбца + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +int coinChangeIIDPComp(List coins, int amt) { + int n = coins.length; + // Инициализация таблицы dp + List dp = List.filled(amt + 1, 0); + dp[0] = 1; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 5; + + // Динамическое программирование + int res = coinChangeIIDP(coins, amt); + print("Количество комбинаций монет для набора целевой суммы = $res"); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(coins, amt); + print("Количество комбинаций монет для набора целевой суммы = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/edit_distance.dart b/ru/codes/dart/chapter_dynamic_programming/edit_distance.dart new file mode 100644 index 000000000..83df45386 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/edit_distance.dart @@ -0,0 +1,125 @@ +/** + * File: edit_distance.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Редакционное расстояние: полный перебор */ +int editDistanceDFS(String s, String t, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) return 0; + // Если s пусто, вернуть длину t + if (i == 0) return j; + // Если t пусто, вернуть длину s + if (j == 0) return i; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return min(min(insert, delete), replace) + 1; +} + +/* Редакционное расстояние: поиск с мемоизацией */ +int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) return 0; + // Если s пусто, вернуть длину t + if (i == 0) return j; + // Если t пусто, вернуть длину s + if (j == 0) return i; + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) return mem[i][j]; + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = min(min(insert, delete), replace) + 1; + return mem[i][j]; +} + +/* Редакционное расстояние: динамическое программирование */ +int editDistanceDP(String s, String t) { + int n = s.length, m = t.length; + List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); + // Переход состояний: первая строка и первый столбец + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +int editDistanceDPComp(String s, String t) { + int n = s.length, m = t.length; + List dp = List.filled(m + 1, 0); + // Переход состояний: первая строка + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (int i = 1; i <= n; i++) { + // Переход состояний: первый столбец + int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; +} + +/* Driver Code */ +void main() { + String s = "bag"; + String t = "pack"; + int n = s.length, m = t.length; + + // Полный перебор + int res = editDistanceDFS(s, t, n, m); + print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); + + // Поиск с мемоизацией + List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); + + // Динамическое программирование + res = editDistanceDP(s, t); + print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t); + print("Чтобы преобразовать " + s + " в " + t + ", нужно минимум $res шагов"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/knapsack.dart b/ru/codes/dart/chapter_dynamic_programming/knapsack.dart new file mode 100644 index 000000000..d4a109a07 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/knapsack.dart @@ -0,0 +1,116 @@ +/** + * File: knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Рюкзак 0-1: полный перебор */ +int knapsackDFS(List wgt, List val, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return max(no, yes); +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +int knapsackDFSMem( + List wgt, + List val, + List> mem, + int i, + int c, +) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* Рюкзак 0-1: динамическое программирование */ +int knapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +int knapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + List dp = List.filled(cap + 1, 0); + // Переход состояний + for (int i = 1; i <= n; i++) { + // Обход в обратном порядке + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = wgt.length; + + // Полный перебор + int res = knapsackDFS(wgt, val, n, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); + + // Поиск с мемоизацией + List> mem = + List.generate(n + 1, (index) => List.filled(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); + + // Динамическое программирование + res = knapsackDP(wgt, val, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt, val, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart b/ru/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart new file mode 100644 index 000000000..812977b80 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart @@ -0,0 +1,48 @@ +/** + * File: min_cost_climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +int minCostClimbingStairsDP(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + // Инициализация таблицы dp для хранения решений подзадач + List dp = List.filled(n + 1, 0); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +int minCostClimbingStairsDPComp(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + print("Список стоимостей ступеней = $cost"); + + int res = minCostClimbingStairsDP(cost); + print("Минимальная стоимость подъема по лестнице = $res"); + + res = minCostClimbingStairsDPComp(cost); + print("Минимальная стоимость подъема по лестнице = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/min_path_sum.dart b/ru/codes/dart/chapter_dynamic_programming/min_path_sum.dart new file mode 100644 index 000000000..e43fbf2e4 --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/min_path_sum.dart @@ -0,0 +1,120 @@ +/** + * File: min_path_sum.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Минимальная сумма пути: полный перебор */ +int minPathSumDFS(List> grid, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + // В Dart тип int — целое число фиксированного диапазона; значения, представляющего «бесконечность», не существует + return BigInt.from(2).pow(31).toInt(); + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return min(left, up) + grid[i][j]; +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +int minPathSumDFSMem(List> grid, List> mem, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + // В Dart тип int — целое число фиксированного диапазона; значения, представляющего «бесконечность», не существует + return BigInt.from(2).pow(31).toInt(); + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* Минимальная сумма пути: динамическое программирование */ +int minPathSumDP(List> grid) { + int n = grid.length, m = grid[0].length; + // Инициализация таблицы dp + List> dp = List.generate(n, (i) => List.filled(m, 0)); + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +int minPathSumDPComp(List> grid) { + int n = grid.length, m = grid[0].length; + // Инициализация таблицы dp + List dp = List.filled(m, 0); + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (int i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +void main() { + List> grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ]; + int n = grid.length, m = grid[0].length; + +// Полный перебор + int res = minPathSumDFS(grid, n - 1, m - 1); + print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); + +// Поиск с мемоизацией + List> mem = List.generate(n, (i) => List.filled(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); + +// Динамическое программирование + res = minPathSumDP(grid); + print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); + +// Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(grid); + print("Минимальная сумма пути из левого верхнего угла в правый нижний = $res"); +} diff --git a/ru/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart b/ru/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart new file mode 100644 index 000000000..ffe581e4d --- /dev/null +++ b/ru/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart @@ -0,0 +1,62 @@ +/** + * File: unbounded_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Полный рюкзак: динамическое программирование */ +int unboundedKnapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +int unboundedKnapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + List dp = List.filled(cap + 1, 0); + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [1, 2, 3]; + List val = [5, 11, 15]; + int cap = 4; + + // Динамическое программирование + int res = unboundedKnapsackDP(wgt, val, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); + + // Динамическое программирование с оптимизацией памяти + int resComp = unboundedKnapsackDPComp(wgt, val, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $resComp"); +} diff --git a/ru/codes/dart/chapter_graph/graph_adjacency_list.dart b/ru/codes/dart/chapter_graph/graph_adjacency_list.dart new file mode 100644 index 000000000..5248ea48d --- /dev/null +++ b/ru/codes/dart/chapter_graph/graph_adjacency_list.dart @@ -0,0 +1,124 @@ +/** + * File: graph_adjacency_list.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; + +/* Класс неориентированного графа на основе списка смежности */ +class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + Map> adjList = {}; + + /* Конструктор */ + GraphAdjList(List> edges) { + for (List edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + int size() { + return adjList.length; + } + + /* Добавление ребра */ + void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // Добавить ребро vet1 - vet2 + adjList[vet1]!.add(vet2); + adjList[vet2]!.add(vet1); + } + + /* Удаление ребра */ + void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // Удалить ребро vet1 - vet2 + adjList[vet1]!.remove(vet2); + adjList[vet2]!.remove(vet1); + } + + /* Добавление вершины */ + void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) return; + // Добавить новый список в список смежности + adjList[vet] = []; + } + + /* Удаление вершины */ + void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) { + throw ArgumentError; + } + // Удалить из списка смежности список, соответствующий вершине vet + adjList.remove(vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + adjList.forEach((key, value) { + value.remove(vet); + }); + } + + /* Вывести список смежности */ + void printAdjList() { + print("Список смежности ="); + adjList.forEach((key, value) { + List tmp = []; + for (Vertex vertex in value) { + tmp.add(vertex.val); + } + print("${key.val}: $tmp,"); + }); + } +} + +/* Driver Code */ +void main() { + /* Инициализация неориентированного графа */ + List v = Vertex.valsToVets([1, 3, 2, 5, 4]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\nГраф после инициализации"); + graph.printAdjList(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(v[0], v[2]); + print("\nГраф после добавления ребра 1-2"); + graph.printAdjList(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(v[0], v[1]); + print("\nГраф после удаления ребра 1-3"); + graph.printAdjList(); + + /* Добавление вершины */ + Vertex v5 = Vertex(6); + graph.addVertex(v5); + print("\nГраф после добавления вершины 6"); + graph.printAdjList(); + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(v[1]); + print("\nГраф после удаления вершины 3"); + graph.printAdjList(); +} diff --git a/ru/codes/dart/chapter_graph/graph_adjacency_matrix.dart b/ru/codes/dart/chapter_graph/graph_adjacency_matrix.dart new file mode 100644 index 000000000..3b3b84a2a --- /dev/null +++ b/ru/codes/dart/chapter_graph/graph_adjacency_matrix.dart @@ -0,0 +1,133 @@ +/** + * File: graph_adjacency_matrix.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + List vertices = []; // Элемент вершины: элемент представляет «значение вершины», индекс представляет «индекс вершины» + List> adjMat = []; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + GraphAdjMat(List vertices, List> edges) { + this.vertices = []; + this.adjMat = []; + // Добавление вершины + for (int val in vertices) { + addVertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for (List e in edges) { + addEdge(e[0], e[1]); + } + } + + /* Получить число вершин */ + int size() { + return vertices.length; + } + + /* Добавление вершины */ + void addVertex(int val) { + int n = size(); + // Добавить значение новой вершины в список вершин + vertices.add(val); + // Добавить строку в матрицу смежности + List newRow = List.filled(n, 0, growable: true); + adjMat.add(newRow); + // Добавить столбец в матрицу смежности + for (List row in adjMat) { + row.add(0); + } + } + + /* Удаление вершины */ + void removeVertex(int index) { + if (index >= size()) { + throw IndexError; + } + // Удалить вершину с индексом index из списка вершин + vertices.removeAt(index); + // Удалить строку с индексом index из матрицы смежности + adjMat.removeAt(index); + // Удалить столбец с индексом index из матрицы смежности + for (List row in adjMat) { + row.removeAt(index); + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + void addEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + void removeEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* Вывести матрицу смежности */ + void printAdjMat() { + print("Список вершин = $vertices"); + print("Матрица смежности = "); + printMatrix(adjMat); + } +} + +/* Driver Code */ +void main() { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + List vertices = [1, 3, 2, 5, 4]; + List> edges = [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4], + ]; + GraphAdjMat graph = GraphAdjMat(vertices, edges); + print("\nГраф после инициализации"); + graph.printAdjMat(); + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.addEdge(0, 2); + print("\nГраф после добавления ребра 1-2"); + graph.printAdjMat(); + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.removeEdge(0, 1); + print("\nГраф после удаления ребра 1-3"); + graph.printAdjMat(); + + /* Добавление вершины */ + graph.addVertex(6); + print("\nГраф после добавления вершины 6"); + graph.printAdjMat(); + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.removeVertex(1); + print("\nГраф после удаления вершины 3"); + graph.printAdjMat(); +} diff --git a/ru/codes/dart/chapter_graph/graph_bfs.dart b/ru/codes/dart/chapter_graph/graph_bfs.dart new file mode 100644 index 000000000..4750ed1eb --- /dev/null +++ b/ru/codes/dart/chapter_graph/graph_bfs.dart @@ -0,0 +1,66 @@ +/** + * File: graph_bfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* Обход в ширину */ +List graphBFS(GraphAdjList graph, Vertex startVet) { + // Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины + // Последовательность обхода вершин + List res = []; + // Хеш-множество для хранения уже посещенных вершин + Set visited = {}; + visited.add(startVet); + // Очередь используется для реализации BFS + Queue que = Queue(); + que.add(startVet); + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (que.isNotEmpty) { + Vertex vet = que.removeFirst(); // Извлечь головную вершину из очереди + res.add(vet); // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + que.add(adjVet); // Помещать в очередь только непосещенные вершины + visited.add(adjVet); // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res; +} + +/* Dirver Code */ +void main() { + /* Инициализация неориентированного графа */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\nГраф после инициализации"); + graph.printAdjList(); + + /* Обход в ширину */ + List res = graphBFS(graph, v[0]); + print("\nПоследовательность вершин при обходе в ширину (BFS)"); + print(Vertex.vetsToVals(res)); +} diff --git a/ru/codes/dart/chapter_graph/graph_dfs.dart b/ru/codes/dart/chapter_graph/graph_dfs.dart new file mode 100644 index 000000000..25df94f52 --- /dev/null +++ b/ru/codes/dart/chapter_graph/graph_dfs.dart @@ -0,0 +1,59 @@ +/** + * File: graph_dfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* Вспомогательная функция обхода в глубину */ +void dfs( + GraphAdjList graph, + Set visited, + List res, + Vertex vet, +) { + res.add(vet); // Отметить посещенную вершину + visited.add(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet); + } +} + +/* Обход в глубину */ +List graphDFS(GraphAdjList graph, Vertex startVet) { + // Последовательность обхода вершин + List res = []; + // Хеш-множество для хранения уже посещенных вершин + Set visited = {}; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +void main() { + /* Инициализация неориентированного графа */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\nГраф после инициализации"); + graph.printAdjList(); + + /* Обход в глубину */ + List res = graphDFS(graph, v[0]); + print("\nПоследовательность вершин при обходе в глубину (DFS)"); + print(Vertex.vetsToVals(res)); +} diff --git a/ru/codes/dart/chapter_greedy/coin_change_greedy.dart b/ru/codes/dart/chapter_greedy/coin_change_greedy.dart new file mode 100644 index 000000000..9de834e8e --- /dev/null +++ b/ru/codes/dart/chapter_greedy/coin_change_greedy.dart @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Размен монет: жадный алгоритм */ +int coinChangeGreedy(List coins, int amt) { + // Предположить, что список coins упорядочен + int i = coins.length - 1; + int count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +void main() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + List coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("Минимальное число монет для набора суммы $amt = $res"); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 20, 50]; + amt = 60; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("Минимальное число монет для набора суммы $amt = $res"); + print("На самом деле минимум равен 3: 20 + 20 + 20"); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 49, 50]; + amt = 98; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("Минимальное число монет для набора суммы $amt = $res"); + print("На самом деле минимум равен 2: 49 + 49"); +} diff --git a/ru/codes/dart/chapter_greedy/fractional_knapsack.dart b/ru/codes/dart/chapter_greedy/fractional_knapsack.dart new file mode 100644 index 000000000..4aec0780e --- /dev/null +++ b/ru/codes/dart/chapter_greedy/fractional_knapsack.dart @@ -0,0 +1,47 @@ +/** + * File: fractional_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Предмет */ +class Item { + int w; // Вес предмета + int v; // Стоимость предмета + + Item(this.w, this.v); +} + +/* Дробный рюкзак: жадный алгоритм */ +double fractionalKnapsack(List wgt, List val, int cap) { + // Создать список предметов с двумя свойствами: вес и стоимость + List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); + // Циклический жадный выбор + double res = 0; + for (Item item in items) { + if (item.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += item.v / item.w * cap; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + return res; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + + // Жадный алгоритм + double res = fractionalKnapsack(wgt, val, cap); + print("Максимальная стоимость предметов без превышения вместимости рюкзака = $res"); +} diff --git a/ru/codes/dart/chapter_greedy/max_capacity.dart b/ru/codes/dart/chapter_greedy/max_capacity.dart new file mode 100644 index 000000000..c9f4c1e1b --- /dev/null +++ b/ru/codes/dart/chapter_greedy/max_capacity.dart @@ -0,0 +1,37 @@ +/** + * File: max_capacity.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Максимальная вместимость: жадный алгоритм */ +int maxCapacity(List ht) { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + int i = 0, j = ht.length - 1; + // Начальная максимальная вместимость равна 0 + int res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +void main() { + List ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // Жадный алгоритм + int res = maxCapacity(ht); + print("Максимальная вместимость = $res"); +} diff --git a/ru/codes/dart/chapter_greedy/max_product_cutting.dart b/ru/codes/dart/chapter_greedy/max_product_cutting.dart new file mode 100644 index 000000000..4d63c6717 --- /dev/null +++ b/ru/codes/dart/chapter_greedy/max_product_cutting.dart @@ -0,0 +1,37 @@ +/** + * File: max_product_cutting.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* Максимальное произведение разрезания: жадный алгоритм */ +int maxProductCutting(int n) { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + int a = n ~/ 3; + int b = n % 3; + if (b == 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return (pow(3, a - 1) * 2 * 2).toInt(); + } + if (b == 2) { + // Если остаток равен 2, ничего не делать + return (pow(3, a) * 2).toInt(); + } + // Если остаток равен 0, ничего не делать + return pow(3, a).toInt(); +} + +/* Driver Code */ +void main() { + int n = 58; + + // Жадный алгоритм + int res = maxProductCutting(n); + print("Максимальное произведение после разрезания = $res"); +} diff --git a/ru/codes/dart/chapter_hashing/array_hash_map.dart b/ru/codes/dart/chapter_hashing/array_hash_map.dart new file mode 100644 index 000000000..a26892a3d --- /dev/null +++ b/ru/codes/dart/chapter_hashing/array_hash_map.dart @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Пара ключ-значение */ +class Pair { + int key; + String val; + Pair(this.key, this.val); +} + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + late List _buckets; + + ArrayHashMap() { + // Инициализировать массив, содержащий 100 корзин + _buckets = List.filled(100, null); + } + + /* Хеш-функция */ + int _hashFunc(int key) { + final int index = key % 100; + return index; + } + + /* Операция поиска */ + String? get(int key) { + final int index = _hashFunc(key); + final Pair? pair = _buckets[index]; + if (pair == null) { + return null; + } + return pair.val; + } + + /* Операция добавления */ + void put(int key, String val) { + final Pair pair = Pair(key, val); + final int index = _hashFunc(key); + _buckets[index] = pair; + } + + /* Операция удаления */ + void remove(int key) { + final int index = _hashFunc(key); + _buckets[index] = null; + } + + /* Получить все пары ключ-значение */ + List pairSet() { + List pairSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + pairSet.add(pair); + } + } + return pairSet; + } + + /* Получить все ключи */ + List keySet() { + List keySet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + keySet.add(pair.key); + } + } + return keySet; + } + + /* Получить все значения */ + List values() { + List valueSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + valueSet.add(pair.val); + } + } + return valueSet; + } + + /* Вывести хеш-таблицу */ + void printHashMap() { + for (final Pair kv in pairSet()) { + print("${kv.key} -> ${kv.val}"); + } + } +} + +/* Driver Code */ +void main() { + /* Инициализация хеш-таблицы */ + final ArrayHashMap map = ArrayHashMap(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.printHashMap(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + String? name = map.get(15937); + print("\nДля номера 15937 найдено имя $name"); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583); + print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + map.printHashMap(); + + /* Обход хеш-таблицы */ + print("\nОтдельный обход пар ключ-значение"); + map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); + print("\nОтдельный обход ключей"); + map.keySet().forEach((key) => print("$key")); + print("\nОтдельный обход значений"); + map.values().forEach((val) => print("$val")); +} diff --git a/ru/codes/dart/chapter_hashing/built_in_hash.dart b/ru/codes/dart/chapter_hashing/built_in_hash.dart new file mode 100644 index 000000000..9e8cce0e9 --- /dev/null +++ b/ru/codes/dart/chapter_hashing/built_in_hash.dart @@ -0,0 +1,34 @@ +/** + * File: built_in_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../chapter_stack_and_queue/linkedlist_deque.dart'; + +/* Driver Code */ +void main() { + int _num = 3; + int hashNum = _num.hashCode; + print("Хеш-значение целого числа $_num = $hashNum"); + + bool bol = true; + int hashBol = bol.hashCode; + print("Хеш-значение булева значения $bol = $hashBol"); + + double dec = 3.14159; + int hashDec = dec.hashCode; + print("Хеш-значение десятичного числа $dec = $hashDec"); + + String str = "Hello Algo"; + int hashStr = str.hashCode; + print("Хеш-значение строки $str = $hashStr"); + + List arr = [12836, "Сяо Ха"]; + int hashArr = arr.hashCode; + print("Хеш-значение массива $arr = $hashArr"); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + print("Хеш-значение объекта узла $obj = $hashObj"); +} diff --git a/ru/codes/dart/chapter_hashing/hash_map.dart b/ru/codes/dart/chapter_hashing/hash_map.dart new file mode 100644 index 000000000..674d7d93c --- /dev/null +++ b/ru/codes/dart/chapter_hashing/hash_map.dart @@ -0,0 +1,41 @@ +/** + * File: hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Driver Code */ +void main() { + /* Инициализация хеш-таблицы */ + final Map map = {}; + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map[12836] = "Сяо Ха"; + map[15937] = "Сяо Ло"; + map[16750] = "Сяо Суань"; + map[13276] = "Сяо Фа"; + map[10583] = "Сяо Я"; + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.forEach((key, value) => print("$key -> $value")); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + final String? name = map[15937]; + print("\nДля номера 15937 найдено имя $name"); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583); + print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + map.forEach((key, value) => print("$key -> $value")); + + /* Обход хеш-таблицы */ + print("\nОтдельный обход пар ключ-значение"); + map.forEach((key, value) => print("$key -> $value")); + print("\nОтдельный обход ключей"); + map.keys.forEach((key) => print(key)); + print("\nОтдельный обход значений"); + map.forEach((key, value) => print("$value")); + map.values.forEach((value) => print(value)); +} diff --git a/ru/codes/dart/chapter_hashing/hash_map_chaining.dart b/ru/codes/dart/chapter_hashing/hash_map_chaining.dart new file mode 100644 index 000000000..2748c7d13 --- /dev/null +++ b/ru/codes/dart/chapter_hashing/hash_map_chaining.dart @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.dart + * Created Time: 2023-06-24 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + late int size; // Число пар ключ-значение + late int capacity; // Вместимость хеш-таблицы + late double loadThres; // Порог коэффициента загрузки для запуска расширения + late int extendRatio; // Коэффициент расширения + late List> buckets; // Массив корзин + + /* Конструктор */ + HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = List.generate(capacity, (_) => []); + } + + /* Хеш-функция */ + int hashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + double loadFactor() { + return size / capacity; + } + + /* Операция поиска */ + String? get(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // Обойти корзину; если найден key, вернуть соответствующее val + for (Pair pair in bucket) { + if (pair.key == key) { + return pair.val; + } + } + // Если key не найден, вернуть null + return null; + } + + /* Операция добавления */ + void put(int key, String val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets[index]; + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for (Pair pair in bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // Если такого key нет, добавить пару ключ-значение в конец + Pair pair = Pair(key, val); + bucket.add(pair); + size++; + } + + /* Операция удаления */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // Обойти корзину и удалить из нее пару ключ-значение + for (Pair pair in bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* Расширить хеш-таблицу */ + void extend() { + // Временно сохранить исходную хеш-таблицу + List> bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets = List.generate(capacity, (_) => []); + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (List bucket in bucketsTmp) { + for (Pair pair in bucket) { + put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + void printHashMap() { + for (List bucket in buckets) { + List res = []; + for (Pair pair in bucket) { + res.add("${pair.key} -> ${pair.val}"); + } + print(res); + } + } +} + +/* Driver Code */ +void main() { + /* Инициализация хеш-таблицы */ + HashMapChaining map = HashMapChaining(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.printHashMap(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + String? name = map.get(13276); + print("\nДля номера 13276 найдено имя ${name}"); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(12836); + print("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); + map.printHashMap(); +} diff --git a/ru/codes/dart/chapter_hashing/hash_map_open_addressing.dart b/ru/codes/dart/chapter_hashing/hash_map_open_addressing.dart new file mode 100644 index 000000000..e0e97a131 --- /dev/null +++ b/ru/codes/dart/chapter_hashing/hash_map_open_addressing.dart @@ -0,0 +1,157 @@ +/** + * File: hash_map_open_addressing.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + late int _size; // Число пар ключ-значение + int _capacity = 4; // Вместимость хеш-таблицы + double _loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения + int _extendRatio = 2; // Коэффициент расширения + late List _buckets; // Массив корзин + Pair _TOMBSTONE = Pair(-1, "-1"); // Удалить метку + + /* Конструктор */ + HashMapOpenAddressing() { + _size = 0; + _buckets = List.generate(_capacity, (index) => null); + } + + /* Хеш-функция */ + int hashFunc(int key) { + return key % _capacity; + } + + /* Коэффициент загрузки */ + double loadFactor() { + return _size / _capacity; + } + + /* Найти индекс корзины, соответствующий key */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (_buckets[index] != null) { + // Если встретился key, вернуть соответствующий индекс корзины + if (_buckets[index]!.key == key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone != -1) { + _buckets[firstTombstone] = _buckets[index]; + _buckets[index] = _TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % _capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone == -1 ? index : firstTombstone; + } + + /* Операция поиска */ + String? get(int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + return _buckets[index]!.val; + } + // Если пары ключ-значение не существует, вернуть null + return null; + } + + /* Операция добавления */ + void put(int key, String val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > _loadThres) { + extend(); + } + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index]!.val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + _buckets[index] = new Pair(key, val); + _size++; + } + + /* Операция удаления */ + void remove(int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index] = _TOMBSTONE; + _size--; + } + } + + /* Расширить хеш-таблицу */ + void extend() { + // Временно сохранить исходную хеш-таблицу + List bucketsTmp = _buckets; + // Инициализация новой хеш-таблицы после расширения + _capacity *= _extendRatio; + _buckets = List.generate(_capacity, (index) => null); + _size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (Pair? pair in bucketsTmp) { + if (pair != null && pair != _TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + void printHashMap() { + for (Pair? pair in _buckets) { + if (pair == null) { + print("null"); + } else if (pair == _TOMBSTONE) { + print("TOMBSTONE"); + } else { + print("${pair.key} -> ${pair.val}"); + } + } + } +} + +/* Driver Code */ +void main() { + /* Инициализация хеш-таблицы */ + HashMapOpenAddressing map = HashMapOpenAddressing(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.printHashMap(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + String? name = map.get(13276); + print("\nДля номера 13276 найдено имя $name"); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(16750); + print("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); + map.printHashMap(); +} diff --git a/ru/codes/dart/chapter_hashing/simple_hash.dart b/ru/codes/dart/chapter_hashing/simple_hash.dart new file mode 100644 index 000000000..d4c59c5bd --- /dev/null +++ b/ru/codes/dart/chapter_hashing/simple_hash.dart @@ -0,0 +1,62 @@ +/** + * File: simple_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Аддитивное хеширование */ +int addHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* Мультипликативное хеширование */ +int mulHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* XOR-хеширование */ +int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash ^= key.codeUnitAt(i); + } + return hash & MODULUS; +} + +/* Хеширование с циклическим сдвигом */ +int rotHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* Dirver Code */ +void main() { + String key = "Hello Algo"; + + int hash = addHash(key); + print("Хеш-сумма сложением = $hash"); + + hash = mulHash(key); + print("Хеш-сумма умножением = $hash"); + + hash = xorHash(key); + print("Хеш-сумма XOR = $hash"); + + hash = rotHash(key); + print("Хеш-сумма с циклическим сдвигом = $hash"); +} diff --git a/ru/codes/dart/chapter_heap/my_heap.dart b/ru/codes/dart/chapter_heap/my_heap.dart new file mode 100644 index 000000000..c76e94269 --- /dev/null +++ b/ru/codes/dart/chapter_heap/my_heap.dart @@ -0,0 +1,151 @@ +/** + * File: my_heap.dart + * Created Time: 2023-04-09 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* Максимальная куча */ +class MaxHeap { + late List _maxHeap; + + /* Конструктор, строящий кучу по входному списку */ + MaxHeap(List nums) { + // Добавить элементы списка в кучу без изменений + _maxHeap = nums; + // Выполнить heapify для всех узлов, кроме листовых + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* Получить индекс левого дочернего узла */ + int _left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + int _right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + int _parent(int i) { + return (i - 1) ~/ 2; // Округление вниз при делении + } + + /* Поменять элементы местами */ + void _swap(int i, int j) { + int tmp = _maxHeap[i]; + _maxHeap[i] = _maxHeap[j]; + _maxHeap[j] = tmp; + } + + /* Получение размера кучи */ + int size() { + return _maxHeap.length; + } + + /* Проверка, пуста ли куча */ + bool isEmpty() { + return size() == 0; + } + + /* Доступ к элементу на вершине кучи */ + int peek() { + return _maxHeap[0]; + } + + /* Добавление элемента в кучу */ + void push(int val) { + // Добавление узла + _maxHeap.add(val); + // Просеивание снизу вверх + siftUp(size() - 1); + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + void siftUp(int i) { + while (true) { + // Получение родительского узла для узла i + int p = _parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { + break; + } + // Поменять два узла местами + _swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + int pop() { + // Обработка пустого случая + if (isEmpty()) throw Exception('куча пуста'); + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + _swap(0, size() - 1); + // Удаление узла + int val = _maxHeap.removeLast(); + // Просеивание сверху вниз + siftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + void siftDown(int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = _left(i); + int r = _right(i); + int ma = i; + if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; + if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) break; + // Поменять два узла местами + _swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Вывести кучу (двоичное дерево) */ + void print() { + printHeap(_maxHeap); + } +} + +/* Driver Code */ +void main() { + /* Инициализация максимальной кучи */ + MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + print("\nПосле построения кучи из входного списка"); + maxHeap.print(); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.peek(); + print("\nЭлемент на вершине кучи = $peek"); + + /* Добавление элемента в кучу */ + int val = 7; + maxHeap.push(val); + print("\nПосле добавления элемента $val в кучу"); + maxHeap.print(); + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop(); + print("\nПосле извлечения элемента вершины кучи $peek"); + maxHeap.print(); + + /* Получение размера кучи */ + int size = maxHeap.size(); + print("\nКоличество элементов в куче = $size"); + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.isEmpty(); + print("\nПуста ли куча: $isEmpty"); +} diff --git a/ru/codes/dart/chapter_heap/top_k.dart b/ru/codes/dart/chapter_heap/top_k.dart new file mode 100644 index 000000000..7cf6f705e --- /dev/null +++ b/ru/codes/dart/chapter_heap/top_k.dart @@ -0,0 +1,150 @@ +/** + * File: top_k.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* Найти k наибольших элементов массива с помощью кучи */ +MinHeap topKHeap(List nums, int k) { + // Инициализировать минимальную кучу, поместив в нее первые k элементов массива + MinHeap heap = MinHeap(nums.sublist(0, k)); + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (int i = k; i < nums.length; i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > heap.peek()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +/* Driver Code */ +void main() { + List nums = [1, 7, 6, 3, 2]; + int k = 3; + + MinHeap res = topKHeap(nums, k); + print("Наибольшие $k элементов"); + res.print(); +} + +/* Минимальная куча */ +class MinHeap { + late List _minHeap; + + /* Конструктор, строящий кучу по входному списку */ + MinHeap(List nums) { + // Добавить элементы списка в кучу без изменений + _minHeap = nums; + // Выполнить heapify для всех узлов, кроме листовых + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* Вернуть элементы в куче */ + List getHeap() { + return _minHeap; + } + + /* Получить индекс левого дочернего узла */ + int _left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + int _right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + int _parent(int i) { + return (i - 1) ~/ 2; // Округление вниз при делении + } + + /* Поменять элементы местами */ + void _swap(int i, int j) { + int tmp = _minHeap[i]; + _minHeap[i] = _minHeap[j]; + _minHeap[j] = tmp; + } + + /* Получение размера кучи */ + int size() { + return _minHeap.length; + } + + /* Проверка, пуста ли куча */ + bool isEmpty() { + return size() == 0; + } + + /* Доступ к элементу на вершине кучи */ + int peek() { + return _minHeap[0]; + } + + /* Добавление элемента в кучу */ + void push(int val) { + // Добавление узла + _minHeap.add(val); + // Просеивание снизу вверх + siftUp(size() - 1); + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + void siftUp(int i) { + while (true) { + // Получение родительского узла для узла i + int p = _parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || _minHeap[i] >= _minHeap[p]) { + break; + } + // Поменять два узла местами + _swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + int pop() { + // Обработка пустого случая + if (isEmpty()) throw Exception('куча пуста'); + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + _swap(0, size() - 1); + // Удаление узла + int val = _minHeap.removeLast(); + // Просеивание сверху вниз + siftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + void siftDown(int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = _left(i); + int r = _right(i); + int mi = i; + if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; + if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (mi == i) break; + // Поменять два узла местами + _swap(i, mi); + // Циклическое просеивание вниз + i = mi; + } + } + + /* Вывести кучу (двоичное дерево) */ + void print() { + printHeap(_minHeap); + } +} diff --git a/ru/codes/dart/chapter_searching/binary_search.dart b/ru/codes/dart/chapter_searching/binary_search.dart new file mode 100644 index 000000000..0d40b91a2 --- /dev/null +++ b/ru/codes/dart/chapter_searching/binary_search.dart @@ -0,0 +1,63 @@ +/** + * File: binary_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +int binarySearch(List nums, int target) { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + int i = 0, j = nums.length - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + int m = i + (j - i) ~/ 2; // Вычислить индекс середины m + if (nums[m] < target) { + // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + } else if (nums[m] > target) { + // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +int binarySearchLCRO(List nums, int target) { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + int i = 0, j = nums.length; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + int m = i + (j - i) ~/ 2; // Вычислить индекс середины m + if (nums[m] < target) { + // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + } else if (nums[m] > target) { + // Это означает, что target находится в интервале [i, m) + j = m; + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Driver Code*/ +void main() { + int target = 6; + final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + int index = binarySearch(nums, target); + print('Индекс целевого элемента 6 = $index'); + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = binarySearchLCRO(nums, target); + print('Индекс целевого элемента 6 = $index'); +} diff --git a/ru/codes/dart/chapter_searching/binary_search_edge.dart b/ru/codes/dart/chapter_searching/binary_search_edge.dart new file mode 100644 index 000000000..403b1367d --- /dev/null +++ b/ru/codes/dart/chapter_searching/binary_search_edge.dart @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'binary_search_insertion.dart'; + +/* Бинарный поиск самого левого target */ +int binarySearchLeftEdge(List nums, int target) { + // Эквивалентно поиску точки вставки target + int i = binarySearchInsertion(nums, target); + // target не найден, вернуть -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // Найти target и вернуть индекс i + return i; +} + +/* Бинарный поиск самого правого target */ +int binarySearchRightEdge(List nums, int target) { + // Преобразовать задачу в поиск самого левого target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + int j = i - 1; + // target не найден, вернуть -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Найти target и вернуть индекс j + return j; +} + +/* Driver Code */ +void main() { + // Массив с повторяющимися элементами + List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\nМассив nums = $nums"); + + // Бинарный поиск левой и правой границы + for (int target in [6, 7]) { + int index = binarySearchLeftEdge(nums, target); + print("Индекс самого левого элемента $target равен $index"); + index = binarySearchRightEdge(nums, target); + print("Индекс самого правого элемента $target равен $index"); + } +} diff --git a/ru/codes/dart/chapter_searching/binary_search_insertion.dart b/ru/codes/dart/chapter_searching/binary_search_insertion.dart new file mode 100644 index 000000000..31aaf7ea1 --- /dev/null +++ b/ru/codes/dart/chapter_searching/binary_search_insertion.dart @@ -0,0 +1,60 @@ +/** + * File: binary_search_insertion.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +int binarySearchInsertionSimple(List nums, int target) { + int i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) ~/ 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +int binarySearchInsertion(List nums, int target) { + int i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) ~/ 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Driver Code */ +void main() { + // Массив без повторяющихся элементов + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + print("\nМассив nums = $nums"); + // Бинарный поиск точки вставки + for (int target in [6, 9]) { + int index = binarySearchInsertionSimple(nums, target); + print("Индекс позиции вставки элемента $target равен $index"); + } + + // Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\nМассив nums = $nums"); + // Бинарный поиск точки вставки + for (int target in [2, 6, 20]) { + int index = binarySearchInsertion(nums, target); + print("Индекс позиции вставки элемента $target равен $index"); + } +} diff --git a/ru/codes/dart/chapter_searching/hashing_search.dart b/ru/codes/dart/chapter_searching/hashing_search.dart new file mode 100644 index 000000000..c4450b747 --- /dev/null +++ b/ru/codes/dart/chapter_searching/hashing_search.dart @@ -0,0 +1,54 @@ +/** + * File: hashing_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; +import '../utils/list_node.dart'; + +/* Хеш-поиск (массив) */ +int hashingSearchArray(Map map, int target) { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + if (!map.containsKey(target)) { + return -1; + } + return map[target]!; +} + +/* Хеш-поиск (связный список) */ +ListNode? hashingSearchLinkedList(Map map, int target) { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + if (!map.containsKey(target)) { + return null; + } + return map[target]!; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* Хеш-поиск (массив) */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // Инициализация хеш-таблицы + Map map = HashMap(); + for (int i = 0; i < nums.length; i++) { + map.putIfAbsent(nums[i], () => i); // key: элемент, value: индекс + } + int index = hashingSearchArray(map, target); + print('Индекс целевого элемента 3 = $index'); + + /* Хеш-поиск (связный список) */ + ListNode? head = listToLinkedList(nums); + // Инициализация хеш-таблицы + Map map1 = HashMap(); + while (head != null) { + map1.putIfAbsent(head.val, () => head!); // key: значение узла, value: узел + head = head.next; + } + ListNode? node = hashingSearchLinkedList(map1, target); + print('Объект узла со значением 3 = $node'); +} diff --git a/ru/codes/dart/chapter_searching/linear_search.dart b/ru/codes/dart/chapter_searching/linear_search.dart new file mode 100644 index 000000000..72bd1b13f --- /dev/null +++ b/ru/codes/dart/chapter_searching/linear_search.dart @@ -0,0 +1,47 @@ +/** + * File: linear_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* Линейный поиск (массив) */ +int linearSearchArray(List nums, int target) { + // Обход массива + for (int i = 0; i < nums.length; i++) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] == target) { + return i; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Линейный поиск (связный список) */ +ListNode? linearSearchList(ListNode? head, int target) { + // Обойти связный список + while (head != null) { + // Найти целевой узел и вернуть его + if (head.val == target) return head; + head = head.next; + } + // Целевой элемент не найден, вернуть null + return null; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* Выполнить линейный поиск в массиве */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = linearSearchArray(nums, target); + print('Индекс целевого элемента 3 = $index'); + + /* Выполнить линейный поиск в связном списке */ + ListNode? head = listToLinkedList(nums); + ListNode? node = linearSearchList(head, target); + print('Объект узла со значением 3 = $node'); +} diff --git a/ru/codes/dart/chapter_searching/two_sum.dart b/ru/codes/dart/chapter_searching/two_sum.dart new file mode 100644 index 000000000..d7a1d7c26 --- /dev/null +++ b/ru/codes/dart/chapter_searching/two_sum.dart @@ -0,0 +1,49 @@ +/** + * File: two_sum.dart + * Created Time: 2023-2-11 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; + +/* Способ 1: полный перебор */ +List twoSumBruteForce(List nums, int target) { + int size = nums.length; + // Два вложенных цикла, временная сложность O(n^2) + for (var i = 0; i < size - 1; i++) { + for (var j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) return [i, j]; + } + } + return [0]; +} + +/* Способ 2: вспомогательная хеш-таблица */ +List twoSumHashTable(List nums, int target) { + int size = nums.length; + // Вспомогательная хеш-таблица, пространственная сложность O(n) + Map dic = HashMap(); + // Один цикл, временная сложность O(n) + for (var i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return [dic[target - nums[i]]!, i]; + } + dic.putIfAbsent(nums[i], () => i); + } + return [0]; +} + +/* Driver Code */ +void main() { + // ======= Test Case ======= + List nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Основной код ====== + // Метод 1 + List res = twoSumBruteForce(nums, target); + print('Результат метода 1 res = $res'); + // Метод 2 + res = twoSumHashTable(nums, target); + print('Результат метода 2 res = $res'); +} diff --git a/ru/codes/dart/chapter_sorting/bubble_sort.dart b/ru/codes/dart/chapter_sorting/bubble_sort.dart new file mode 100644 index 000000000..bb83d8544 --- /dev/null +++ b/ru/codes/dart/chapter_sorting/bubble_sort.dart @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* Пузырьковая сортировка */ +void bubbleSort(List nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +void bubbleSortWithFlag(List nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.length - 1; i > 0; i--) { + bool flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // Записать обмен элементов + } + } + if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + bubbleSort(nums); + print("После пузырьковой сортировки nums = $nums"); + + List nums1 = [4, 1, 3, 1, 5, 2]; + bubbleSortWithFlag(nums1); + print("После пузырьковой сортировки nums1 = $nums1"); +} diff --git a/ru/codes/dart/chapter_sorting/bucket_sort.dart b/ru/codes/dart/chapter_sorting/bucket_sort.dart new file mode 100644 index 000000000..62cadfba0 --- /dev/null +++ b/ru/codes/dart/chapter_sorting/bucket_sort.dart @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* Сортировка корзинами */ +void bucketSort(List nums) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + int k = nums.length ~/ 2; + List> buckets = List.generate(k, (index) => []); + + // 1. Распределить элементы массива по корзинам + for (double _num in nums) { + // Входные данные находятся в диапазоне [0, 1), используем _num * k для отображения в диапазон индексов [0, k-1] + int i = (_num * k).toInt(); + // Добавить _num в корзину bucket_idx + buckets[i].add(_num); + } + // 2. Выполнить сортировку внутри каждой корзины + for (List bucket in buckets) { + bucket.sort(); + } + // 3. Обойти корзины и объединить результаты + int i = 0; + for (List bucket in buckets) { + for (double _num in bucket) { + nums[i++] = _num; + } + } +} + +/* Driver Code*/ +void main() { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucketSort(nums); + print('После сортировки корзинами nums = $nums'); +} diff --git a/ru/codes/dart/chapter_sorting/counting_sort.dart b/ru/codes/dart/chapter_sorting/counting_sort.dart new file mode 100644 index 000000000..9dfd232ac --- /dev/null +++ b/ru/codes/dart/chapter_sorting/counting_sort.dart @@ -0,0 +1,72 @@ +/** + * File: counting_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ +import 'dart:math'; + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +void countingSortNaive(List nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[_num] обозначает число появлений _num + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + int i = 0; + for (int _num = 0; _num < m + 1; _num++) { + for (int j = 0; j < counter[_num]; j++, i++) { + nums[i] = _num; + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +void countingSort(List nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[_num] обозначает число появлений _num + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[_num]-1 — это индекс последнего появления _num в res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + int n = nums.length; + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int _num = nums[i]; + res[counter[_num] - 1] = _num; // Поместить _num по соответствующему индексу + counter[_num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения _num + } + // Перезаписать исходный массив nums массивом результата res + nums.setAll(0, res); +} + +/* Driver Code*/ +void main() { + final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSortNaive(nums); + print('После сортировки подсчетом (объекты не поддерживаются) nums = $nums'); + + final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSort(nums1); + print('После сортировки подсчетом nums1 = $nums1'); +} diff --git a/ru/codes/dart/chapter_sorting/heap_sort.dart b/ru/codes/dart/chapter_sorting/heap_sort.dart new file mode 100644 index 000000000..de8a56ee1 --- /dev/null +++ b/ru/codes/dart/chapter_sorting/heap_sort.dart @@ -0,0 +1,49 @@ +/** + * File: heap_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +void siftDown(List nums, int n, int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) ma = l; + if (r < n && nums[r] > nums[ma]) ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) break; + // Поменять два узла местами + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // Циклическое просеивание вниз + i = ma; + } +} + +/* Сортировка кучей */ +void heapSort(List nums) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (int i = nums.length - 1; i > 0; i--) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + heapSort(nums); + print("После сортировки кучей nums = $nums"); +} diff --git a/ru/codes/dart/chapter_sorting/insertion_sort.dart b/ru/codes/dart/chapter_sorting/insertion_sort.dart new file mode 100644 index 000000000..cba7a5b2d --- /dev/null +++ b/ru/codes/dart/chapter_sorting/insertion_sort.dart @@ -0,0 +1,26 @@ +/** + * File: insertion_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* Сортировка вставками */ +void insertionSort(List nums) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо + j--; + } + nums[j + 1] = base; // Поместить base в правильную позицию + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + insertionSort(nums); + print("После сортировки вставками nums = $nums"); +} diff --git a/ru/codes/dart/chapter_sorting/merge_sort.dart b/ru/codes/dart/chapter_sorting/merge_sort.dart new file mode 100644 index 000000000..c1f7bf43a --- /dev/null +++ b/ru/codes/dart/chapter_sorting/merge_sort.dart @@ -0,0 +1,52 @@ +/** + * File: merge_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* Объединить левый и правый подмассивы */ +void merge(List nums, int left, int mid, int right) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + List tmp = List.filled(right - left + 1, 0); + // Инициализировать начальные индексы левого и правого подмассивов + int i = left, j = mid + 1, k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* Сортировка слиянием */ +void mergeSort(List nums, int left, int right) { + // Условие завершения + if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + int mid = left + (right - left) ~/ 2; // Вычислить середину + mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right); +} + +/* Driver Code */ +void main() { + /* Сортировка слиянием */ + List nums = [7, 3, 2, 6, 0, 1, 5, 4]; + mergeSort(nums, 0, nums.length - 1); + print("После сортировки слиянием nums = $nums"); +} diff --git a/ru/codes/dart/chapter_sorting/quick_sort.dart b/ru/codes/dart/chapter_sorting/quick_sort.dart new file mode 100644 index 000000000..46bb2f072 --- /dev/null +++ b/ru/codes/dart/chapter_sorting/quick_sort.dart @@ -0,0 +1,145 @@ +/** + * File: quick_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* Класс быстрой сортировки */ +class QuickSort { + /* Обмен элементов */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + static int _partition(List nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного + _swap(nums, i, j); // Поменять эти два элемента местами + } + _swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + static void quickSort(List nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return; + // Разбиение с опорными указателями + int pivot = _partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +class QuickSortMedian { + /* Обмен элементов */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Выбрать медиану из трех кандидатов */ + static int _medianThree(List nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m находится между l и r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l находится между m и r + return right; + } + + /* Разбиение с опорными указателями (медиана трех) */ + static int _partition(List nums, int left, int right) { + // Выбрать медиану из трех кандидатов + int med = _medianThree(nums, left, (left + right) ~/ 2, right); + // Переместить медиану в крайний левый элемент массива + _swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного + _swap(nums, i, j); // Поменять эти два элемента местами + } + _swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + static void quickSort(List nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return; + // Разбиение с опорными указателями + int pivot = _partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация глубины рекурсии) */ +class QuickSortTailCall { + /* Обмен элементов */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + static int _partition(List nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного + _swap(nums, i, j); // Поменять эти два элемента местами + } + _swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + static void quickSort(List nums, int left, int right) { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + int pivot = _partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +void main() { + /* Быстрая сортировка */ + List nums = [2, 4, 1, 0, 3, 5]; + QuickSort.quickSort(nums, 0, nums.length - 1); + print("После быстрой сортировки nums = $nums"); + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + List nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + print("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = $nums1"); + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + List nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + print("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = $nums2"); +} diff --git a/ru/codes/dart/chapter_sorting/radix_sort.dart b/ru/codes/dart/chapter_sorting/radix_sort.dart new file mode 100644 index 000000000..d5ff43449 --- /dev/null +++ b/ru/codes/dart/chapter_sorting/radix_sort.dart @@ -0,0 +1,71 @@ +/** + * File: radix_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* Получить k-й разряд элемента _num, где exp = 10^(k-1) */ +int digit(int _num, int exp) { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (_num ~/ exp) % 10; +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +void countingSortDigit(List nums, int exp) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + List counter = List.filled(10, 0); + int n = nums.length; + // Подсчитать число появлений каждой цифры от 0 до 9 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d]++; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (int i = 0; i < n; i++) nums[i] = res[i]; +} + +/* Поразрядная сортировка */ +void radixSort(List nums) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + // В dart длина int составляет 64 бита + int m = -1 << 63; + for (int _num in nums) if (_num > m) m = _num; + // Проходить разряды от младшего к старшему + for (int exp = 1; exp <= m; exp *= 10) + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +void main() { + // Поразрядная сортировка + List nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996 + ]; + radixSort(nums); + print("После поразрядной сортировки nums = $nums"); +} diff --git a/ru/codes/dart/chapter_sorting/selection_sort.dart b/ru/codes/dart/chapter_sorting/selection_sort.dart new file mode 100644 index 000000000..78fd3e882 --- /dev/null +++ b/ru/codes/dart/chapter_sorting/selection_sort.dart @@ -0,0 +1,29 @@ +/** + * File: selection_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Сортировка выбором */ +void selectionSort(List nums) { + int n = nums.length; + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) k = j; // Записать индекс минимального элемента + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + selectionSort(nums); + print("После сортировки выбором nums = $nums"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/array_deque.dart b/ru/codes/dart/chapter_stack_and_queue/array_deque.dart new file mode 100644 index 000000000..cbe00a24c --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/array_deque.dart @@ -0,0 +1,146 @@ +/** + * File: array_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Двусторонняя очередь на основе кольцевого массива */ +class ArrayDeque { + late List _nums; // Массив для хранения элементов двусторонней очереди + late int _front; // Указатель head, указывающий на первый элемент очереди + late int _queSize; // Длина двусторонней очереди + + /* Конструктор */ + ArrayDeque(int capacity) { + this._nums = List.filled(capacity, 0); + this._front = this._queSize = 0; + } + + /* Получить вместимость двусторонней очереди */ + int capacity() { + return _nums.length; + } + + /* Получение длины двусторонней очереди */ + int size() { + return _queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty() { + return _queSize == 0; + } + + /* Вычислить индекс в кольцевом массиве */ + int index(int i) { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + capacity()) % capacity(); + } + + /* Добавление в голову очереди */ + void pushFirst(int _num) { + if (_queSize == capacity()) { + throw Exception("Двусторонняя очередь заполнена"); + } + // Указатель головы сместить влево на одну позицию + // С помощью операции взятия остатка реализовать возврат _front к хвосту после выхода за начало массива + _front = index(_front - 1); + // Добавить _num в голову очереди + _nums[_front] = _num; + _queSize++; + } + + /* Добавление в хвост очереди */ + void pushLast(int _num) { + if (_queSize == capacity()) { + throw Exception("Двусторонняя очередь заполнена"); + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + int rear = index(_front + _queSize); + // Добавить _num в хвост очереди + _nums[rear] = _num; + _queSize++; + } + + /* Извлечение из головы очереди */ + int popFirst() { + int _num = peekFirst(); + // Указатель головы сместить вправо на одну позицию + _front = index(_front + 1); + _queSize--; + return _num; + } + + /* Извлечение из хвоста очереди */ + int popLast() { + int _num = peekLast(); + _queSize--; + return _num; + } + + /* Доступ к элементу в начале очереди */ + int peekFirst() { + if (isEmpty()) { + throw Exception("двусторонняя очередь пуста"); + } + return _nums[_front]; + } + + /* Доступ к элементу в конце очереди */ + int peekLast() { + if (isEmpty()) { + throw Exception("двусторонняя очередь пуста"); + } + // Вычислить индекс хвостового элемента + int last = index(_front + _queSize - 1); + return _nums[last]; + } + + /* Вернуть массив для вывода */ + List toArray() { + // Преобразовывать только элементы списка в пределах фактической длины + List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[index(j)]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* Инициализация двусторонней очереди */ + final ArrayDeque deque = ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("Двусторонняя очередь deque = ${deque.toArray()}"); + + /* Доступ к элементу */ + final int peekFirst = deque.peekFirst(); + print("Первый элемент peekFirst = $peekFirst"); + final int peekLast = deque.peekLast(); + print("Последний элемент peekLast = $peekLast"); + + /* Добавление элемента в очередь */ + deque.pushLast(4); + print("После добавления элемента 4 в хвост deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("После добавления элемента 1 в голову deque = ${deque.toArray()}"); + + /* Извлечение элемента из очереди */ + final int popLast = deque.popLast(); + print("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = ${deque.toArray()}"); + final int popFirst = deque.popFirst(); + print("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = ${deque.toArray()}"); + + /* Получение длины двусторонней очереди */ + final int size = deque.size(); + print("Длина двусторонней очереди size = $size"); + + /* Проверка, пуста ли двусторонняя очередь */ + final bool isEmpty = deque.isEmpty(); + print("Пуста ли двусторонняя очередь = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/array_queue.dart b/ru/codes/dart/chapter_stack_and_queue/array_queue.dart new file mode 100644 index 000000000..47abf3aaa --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/array_queue.dart @@ -0,0 +1,110 @@ +/** + * File: array_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + late List _nums; // Массив для хранения элементов очереди + late int _front; // Указатель head, указывающий на первый элемент очереди + late int _queSize; // Длина очереди + + ArrayQueue(int capacity) { + _nums = List.filled(capacity, 0); + _front = _queSize = 0; + } + + /* Получить вместимость очереди */ + int capaCity() { + return _nums.length; + } + + /* Получение длины очереди */ + int size() { + return _queSize; + } + + /* Проверка, пуста ли очередь */ + bool isEmpty() { + return _queSize == 0; + } + + /* Поместить в очередь */ + void push(int _num) { + if (_queSize == capaCity()) { + throw Exception("Очередь заполнена"); + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + int rear = (_front + _queSize) % capaCity(); + // Добавить _num в хвост очереди + _nums[rear] = _num; + _queSize++; + } + + /* Извлечь из очереди */ + int pop() { + int _num = peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + _front = (_front + 1) % capaCity(); + _queSize--; + return _num; + } + + /* Доступ к элементу в начале очереди */ + int peek() { + if (isEmpty()) { + throw Exception("очередь пуста"); + } + return _nums[_front]; + } + + /* Вернуть Array */ + List toArray() { + // Преобразовывать только элементы списка в пределах фактической длины + final List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[j % capaCity()]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* Инициализация очереди */ + final int capacity = 10; + final ArrayQueue queue = ArrayQueue(capacity); + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("Очередь queue = ${queue.toArray()}"); + + /* Доступ к элементу в начале очереди */ + final int peek = queue.peek(); + print("Первый элемент peek = $peek"); + + /* Извлечение элемента из очереди */ + final int pop = queue.pop(); + print("Извлеченный элемент pop = $pop, queue после извлечения = ${queue.toArray()}"); + + /* Получить длину очереди */ + final int size = queue.size(); + print("Длина очереди size = $size"); + + /* Проверка, пуста ли очередь */ + final bool isEmpty = queue.isEmpty(); + print("Пуста ли очередь = $isEmpty"); + + /* Проверка кольцевого массива */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + print("После $i-го раунда операций enqueue и dequeue queue = ${queue.toArray()}"); + } +} diff --git a/ru/codes/dart/chapter_stack_and_queue/array_stack.dart b/ru/codes/dart/chapter_stack_and_queue/array_stack.dart new file mode 100644 index 000000000..c40aeb793 --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/array_stack.dart @@ -0,0 +1,77 @@ +/** + * File: array_stack.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Стек на основе массива */ +class ArrayStack { + late List _stack; + ArrayStack() { + _stack = []; + } + + /* Получение длины стека */ + int size() { + return _stack.length; + } + + /* Проверка, пуст ли стек */ + bool isEmpty() { + return _stack.isEmpty; + } + + /* Поместить в стек */ + void push(int _num) { + _stack.add(_num); + } + + /* Извлечь из стека */ + int pop() { + if (isEmpty()) { + throw Exception("стек пуст"); + } + return _stack.removeLast(); + } + + /* Доступ к верхнему элементу стека */ + int peek() { + if (isEmpty()) { + throw Exception("стек пуст"); + } + return _stack.last; + } + + /* Преобразовать стек в Array и вернуть */ + List toArray() => _stack; +} + +/* Driver Code */ +void main() { + /* Инициализация стека */ + final ArrayStack stack = ArrayStack(); + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("Стек stack = ${stack.toArray()}"); + + /* Доступ к верхнему элементу стека */ + final int peek = stack.peek(); + print("Верхний элемент peek = $peek"); + + /* Извлечение элемента из стека */ + final int pop = stack.pop(); + print("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toArray()}"); + + /* Получение длины стека */ + final int size = stack.size(); + print("Длина стека size = $size"); + + /* Проверка на пустоту */ + final bool isEmpty = stack.isEmpty(); + print("Пуст ли стек = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/deque.dart b/ru/codes/dart/chapter_stack_and_queue/deque.dart new file mode 100644 index 000000000..6d3e1007e --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/deque.dart @@ -0,0 +1,42 @@ +/** + * File: deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* Инициализация двусторонней очереди */ + final Queue deque = Queue(); + deque.addFirst(3); + deque.addLast(2); + deque.addLast(5); + print("Двусторонняя очередь deque = $deque"); + + /* Доступ к элементу */ + final int peekFirst = deque.first; + print("Первый элемент peekFirst = $peekFirst"); + final int peekLast = deque.last; + print("Последний элемент peekLast = $peekLast"); + + /* Добавление элемента в очередь */ + deque.addLast(4); + print("После добавления элемента 4 в хвост deque = $deque"); + deque.addFirst(1); + print("После добавления элемента 1 в голову deque = $deque"); + + /* Извлечение элемента из очереди */ + final int popLast = deque.removeLast(); + print("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = $deque"); + final int popFirst = deque.removeFirst(); + print("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = $deque"); + + /* Получение длины двусторонней очереди */ + final int size = deque.length; + print("Длина двусторонней очереди size = $size"); + + /* Проверка, пуста ли двусторонняя очередь */ + final bool isEmpty = deque.isEmpty; + print("Пуста ли двусторонняя очередь = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart b/ru/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart new file mode 100644 index 000000000..43bafda60 --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Узел двусвязного списка */ +class ListNode { + int val; // Значение узла + ListNode? next; // Ссылка на узел-преемник + ListNode? prev; // Ссылка на узел-предшественник + + ListNode(this.val, {this.next, this.prev}); +} + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + late ListNode? _front; // Головной узел _front + late ListNode? _rear; // Хвостовой узел _rear + int _queSize = 0; // Длина двусторонней очереди + + LinkedListDeque() { + this._front = null; + this._rear = null; + } + + /* Получить длину двусторонней очереди */ + int size() { + return this._queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty() { + return size() == 0; + } + + /* Операция добавления в очередь */ + void push(int _num, bool isFront) { + final ListNode node = ListNode(_num); + if (isEmpty()) { + // Если связный список пуст, пусть _front и _rear оба указывают на node + _front = _rear = node; + } else if (isFront) { + // Операция добавления в голову очереди + // Добавить node в начало связного списка + _front!.prev = node; + node.next = _front; + _front = node; // Обновить головной узел + } else { + // Операция добавления в хвост очереди + // Добавить node в конец связного списка + _rear!.next = node; + node.prev = _rear; + _rear = node; // Обновить хвостовой узел + } + _queSize++; // Обновить длину очереди + } + + /* Добавление в голову очереди */ + void pushFirst(int _num) { + push(_num, true); + } + + /* Добавление в хвост очереди */ + void pushLast(int _num) { + push(_num, false); + } + + /* Операция извлечения из очереди */ + int? pop(bool isFront) { + // Если очередь пуста, сразу вернуть null + if (isEmpty()) { + return null; + } + final int val; + if (isFront) { + // Операция извлечения из головы очереди + val = _front!.val; // Временно сохранить значение головного узла + // Удалить головной узел + ListNode? fNext = _front!.next; + if (fNext != null) { + fNext.prev = null; + _front!.next = null; + } + _front = fNext; // Обновить головной узел + } else { + // Операция извлечения из хвоста очереди + val = _rear!.val; // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + ListNode? rPrev = _rear!.prev; + if (rPrev != null) { + rPrev.next = null; + _rear!.prev = null; + } + _rear = rPrev; // Обновить хвостовой узел + } + _queSize--; // Обновить длину очереди + return val; + } + + /* Извлечение из головы очереди */ + int? popFirst() { + return pop(true); + } + + /* Извлечение из хвоста очереди */ + int? popLast() { + return pop(false); + } + + /* Доступ к элементу в начале очереди */ + int? peekFirst() { + return _front?.val; + } + + /* Доступ к элементу в конце очереди */ + int? peekLast() { + return _rear?.val; + } + + /* Вернуть массив для вывода */ + List toArray() { + ListNode? node = _front; + final List res = []; + for (int i = 0; i < _queSize; i++) { + res.add(node!.val); + node = node.next; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* Инициализация двусторонней очереди */ + final LinkedListDeque deque = LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("Двусторонняя очередь deque = ${deque.toArray()}"); + + /* Доступ к элементу */ + int? peekFirst = deque.peekFirst(); + print("Первый элемент peekFirst = $peekFirst"); + int? peekLast = deque.peekLast(); + print("Последний элемент peekLast = $peekLast"); + + /* Добавление элемента в очередь */ + deque.pushLast(4); + print("После добавления элемента 4 в хвост deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("После добавления элемента 1 в голову deque = ${deque.toArray()}"); + + /* Извлечение элемента из очереди */ + int? popLast = deque.popLast(); + print("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = ${deque.toArray()}"); + int? popFirst = deque.popFirst(); + print("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = ${deque.toArray()}"); + + /* Получение длины двусторонней очереди */ + int size = deque.size(); + print("Длина двусторонней очереди size = $size"); + + /* Проверка, пуста ли двусторонняя очередь */ + bool isEmpty = deque.isEmpty(); + print("Пуста ли двусторонняя очередь = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart b/ru/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart new file mode 100644 index 000000000..7d839ae5c --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart @@ -0,0 +1,103 @@ +/** + * File: linkedlist_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* Очередь на основе связного списка */ +class LinkedListQueue { + ListNode? _front; // Головной узел _front + ListNode? _rear; // Хвостовой узел _rear + int _queSize = 0; // Длина очереди + + LinkedListQueue() { + _front = null; + _rear = null; + } + + /* Получение длины очереди */ + int size() { + return _queSize; + } + + /* Проверка, пуста ли очередь */ + bool isEmpty() { + return _queSize == 0; + } + + /* Поместить в очередь */ + void push(int _num) { + // Добавить _num после хвостового узла + final node = ListNode(_num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (_front == null) { + _front = node; + _rear = node; + } else { + // Если очередь не пуста, добавить этот узел после хвостового узла + _rear!.next = node; + _rear = node; + } + _queSize++; + } + + /* Извлечь из очереди */ + int pop() { + final int _num = peek(); + // Удалить головной узел + _front = _front!.next; + _queSize--; + return _num; + } + + /* Доступ к элементу в начале очереди */ + int peek() { + if (_queSize == 0) { + throw Exception('очередь пуста'); + } + return _front!.val; + } + + /* Преобразовать связный список в Array и вернуть */ + List toArray() { + ListNode? node = _front; + final List queue = []; + while (node != null) { + queue.add(node.val); + node = node.next; + } + return queue; + } +} + +/* Driver Code */ +void main() { + /* Инициализация очереди */ + final queue = LinkedListQueue(); + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("Очередь queue = ${queue.toArray()}"); + + /* Доступ к элементу в начале очереди */ + final int peek = queue.peek(); + print("Первый элемент peek = $peek"); + + /* Извлечение элемента из очереди */ + final int pop = queue.pop(); + print("Извлеченный элемент pop = $pop, queue после извлечения = ${queue.toArray()}"); + + /* Получение длины очереди */ + final int size = queue.size(); + print("Длина очереди size = $size"); + + /* Проверка, пуста ли очередь */ + final bool isEmpty = queue.isEmpty(); + print("Пуста ли очередь = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart b/ru/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart new file mode 100644 index 000000000..e538d0bca --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart @@ -0,0 +1,93 @@ +/** + * File: linkedlist_stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* Стек на основе класса связного списка */ +class LinkedListStack { + ListNode? _stackPeek; // Использовать головной узел как вершину стека + int _stkSize = 0; // Длина стека + + LinkedListStack() { + _stackPeek = null; + } + + /* Получение длины стека */ + int size() { + return _stkSize; + } + + /* Проверка, пуст ли стек */ + bool isEmpty() { + return _stkSize == 0; + } + + /* Поместить в стек */ + void push(int _num) { + final ListNode node = ListNode(_num); + node.next = _stackPeek; + _stackPeek = node; + _stkSize++; + } + + /* Извлечь из стека */ + int pop() { + final int _num = peek(); + _stackPeek = _stackPeek!.next; + _stkSize--; + return _num; + } + + /* Доступ к верхнему элементу стека */ + int peek() { + if (_stackPeek == null) { + throw Exception("стек пуст"); + } + return _stackPeek!.val; + } + + /* Преобразовать связный список в List и вернуть */ + List toList() { + ListNode? node = _stackPeek; + List list = []; + while (node != null) { + list.add(node.val); + node = node.next; + } + list = list.reversed.toList(); + return list; + } +} + +/* Driver Code */ +void main() { + /* Инициализация стека */ + final LinkedListStack stack = LinkedListStack(); + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("Стек stack = ${stack.toList()}"); + + /* Доступ к верхнему элементу стека */ + final int peek = stack.peek(); + print("Верхний элемент peek = $peek"); + + /* Извлечение элемента из стека */ + final int pop = stack.pop(); + print("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toList()}"); + + /* Получение длины стека */ + final int size = stack.size(); + print("Длина стека size = $size"); + + /* Проверка на пустоту */ + final bool isEmpty = stack.isEmpty(); + print("Пуст ли стек = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/queue.dart b/ru/codes/dart/chapter_stack_and_queue/queue.dart new file mode 100644 index 000000000..0d483178a --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/queue.dart @@ -0,0 +1,37 @@ +/** + * File: queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* Инициализация очереди */ + // В Dart двусторонняя очередь Queue обычно рассматривается как обычная очередь + final Queue queue = Queue(); + + /* Добавление элемента в очередь */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + print("Очередь queue = $queue"); + + /* Доступ к элементу в начале очереди */ + final int peek = queue.first; + print("Первый элемент peek = $peek"); + + /* Извлечение элемента из очереди */ + final int pop = queue.removeFirst(); + print("Извлеченный элемент pop = $pop, queue после извлечения = $queue"); + + /* Получить длину очереди */ + final int size = queue.length; + print("Длина очереди size = $size"); + + /* Проверка, пуста ли очередь */ + final bool isEmpty = queue.isEmpty; + print("Пуста ли очередь = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_stack_and_queue/stack.dart b/ru/codes/dart/chapter_stack_and_queue/stack.dart new file mode 100644 index 000000000..cf1911b7a --- /dev/null +++ b/ru/codes/dart/chapter_stack_and_queue/stack.dart @@ -0,0 +1,35 @@ +/** + * File: stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +void main() { + /* Инициализация стека */ + // В Dart нет встроенного класса стека, поэтому List можно использовать как стек + final List stack = []; + + /* Помещение элемента в стек */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + print("Стек stack = $stack"); + + /* Доступ к верхнему элементу стека */ + final int peek = stack.last; + print("Верхний элемент peek = $peek"); + + /* Извлечение элемента из стека */ + final int pop = stack.removeLast(); + print("Извлеченный элемент pop = $pop, stack после извлечения = $stack"); + + /* Получение длины стека */ + final int size = stack.length; + print("Длина стека size = $size"); + + /* Проверка на пустоту */ + final bool isEmpty = stack.isEmpty; + print("Пуст ли стек = $isEmpty"); +} diff --git a/ru/codes/dart/chapter_tree/array_binary_tree.dart b/ru/codes/dart/chapter_tree/array_binary_tree.dart new file mode 100644 index 000000000..8f802233f --- /dev/null +++ b/ru/codes/dart/chapter_tree/array_binary_tree.dart @@ -0,0 +1,152 @@ +/** + * File: array_binary_tree.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree { + late List _tree; + + /* Конструктор */ + ArrayBinaryTree(this._tree); + + /* Вместимость списка */ + int size() { + return _tree.length; + } + + /* Получить значение узла с индексом i */ + int? val(int i) { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if (i < 0 || i >= size()) { + return null; + } + return _tree[i]; + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + int? left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + int? right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла узла с индексом i */ + int? parent(int i) { + return (i - 1) ~/ 2; + } + + /* Обход в ширину */ + List levelOrder() { + List res = []; + for (int i = 0; i < size(); i++) { + if (val(i) != null) { + res.add(val(i)!); + } + } + return res; + } + + /* Обход в глубину */ + void dfs(int i, String order, List res) { + // Если это пустая позиция, вернуть + if (val(i) == null) { + return; + } + // Предварительный обход + if (order == 'pre') { + res.add(val(i)); + } + dfs(left(i)!, order, res); + // Симметричный обход + if (order == 'in') { + res.add(val(i)); + } + dfs(right(i)!, order, res); + // Обратный обход + if (order == 'post') { + res.add(val(i)); + } + } + + /* Предварительный обход */ + List preOrder() { + List res = []; + dfs(0, 'pre', res); + return res; + } + + /* Симметричный обход */ + List inOrder() { + List res = []; + dfs(0, 'in', res); + return res; + } + + /* Обратный обход */ + List postOrder() { + List res = []; + dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +void main() { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + List arr = [ + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 + ]; + + TreeNode? root = listToTree(arr); + print("\nИнициализация двоичного дерева\n"); + print("Массивное представление двоичного дерева:"); + print(arr); + print("Связное представление двоичного дерева:"); + printTree(root); + + // Класс двоичного дерева в массивном представлении + ArrayBinaryTree abt = ArrayBinaryTree(arr); + + // Доступ к узлу + int i = 1; + int? l = abt.left(i); + int? r = abt.right(i); + int? p = abt.parent(i); + print("\nТекущий узел: индекс = $i, значение = ${abt.val(i)}"); + print("Индекс левого дочернего узла = $l, значение = ${(l == null ? "null" : abt.val(l))}"); + print("Индекс правого дочернего узла = $r, значение = ${(r == null ? "null" : abt.val(r))}"); + print("Индекс родительского узла = $p, значение = ${(p == null ? "null" : abt.val(p))}"); + + // Обходить дерево + List res = abt.levelOrder(); + print("\nОбход в ширину = $res"); + res = abt.preOrder(); + print("Предварительный обход = $res"); + res = abt.inOrder(); + print("Симметричный обход = $res"); + res = abt.postOrder(); + print("Обратный обход = $res"); +} diff --git a/ru/codes/dart/chapter_tree/avl_tree.dart b/ru/codes/dart/chapter_tree/avl_tree.dart new file mode 100644 index 000000000..6cc00f117 --- /dev/null +++ b/ru/codes/dart/chapter_tree/avl_tree.dart @@ -0,0 +1,218 @@ +/** + * File: avl_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +class AVLTree { + TreeNode? root; + + /* Конструктор */ + AVLTree() { + root = null; + } + + /* Получить высоту узла */ + int height(TreeNode? node) { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node == null ? -1 : node.height; + } + + /* Обновить высоту узла */ + void updateHeight(TreeNode? node) { + // Высота узла равна высоте более высокого поддерева + 1 + node!.height = max(height(node.left), height(node.right)) + 1; + } + + /* Получить коэффициент баланса */ + int balanceFactor(TreeNode? node) { + // Коэффициент баланса пустого узла равен 0 + if (node == null) return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return height(node.left) - height(node.right); + } + + /* Операция правого вращения */ + TreeNode? rightRotate(TreeNode? node) { + TreeNode? child = node!.left; + TreeNode? grandChild = child!.right; + // Выполнить правое вращение узла node вокруг child + child.right = node; + node.left = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Операция левого вращения */ + TreeNode? leftRotate(TreeNode? node) { + TreeNode? child = node!.right; + TreeNode? grandChild = child!.left; + // Выполнить левое вращение узла node вокруг child + child.left = node; + node.right = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + TreeNode? rotate(TreeNode? node) { + // Получить коэффициент баланса узла node + int factor = balanceFactor(node); + // Левосторонне перекошенное дерево + if (factor > 1) { + if (balanceFactor(node!.left) >= 0) { + // Правое вращение + return rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (factor < -1) { + if (balanceFactor(node!.right) <= 0) { + // Левое вращение + return leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + /* Вставка узла */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + TreeNode? insertHelper(TreeNode? node, int val) { + if (node == null) return TreeNode(val); + /* 1. Найти позицию вставки и вставить узел */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // Повторяющийся узел не вставлять, сразу вернуть + updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Удаление узла */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + TreeNode? removeHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. Найти узел и удалить его */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == null) + return null; + // Число дочерних узлов = 1, удалить node напрямую + else + node = child; + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + TreeNode? temp = node.right; + while (temp!.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Поиск узла */ + TreeNode? search(int val) { + TreeNode? cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (val < cur.val) + cur = cur.left; + // Целевой узел находится в левом поддереве cur + else if (val > cur.val) + cur = cur.right; + // Целевой узел равен текущему узлу + else + break; + } + return cur; + } +} + +void testInsert(AVLTree tree, int val) { + tree.insert(val); + print("\nПосле вставки узла $val AVL-дерево имеет вид"); + printTree(tree.root); +} + +void testRemove(AVLTree tree, int val) { + tree.remove(val); + print("\nПосле удаления узла $val AVL-дерево имеет вид"); + printTree(tree.root); +} + +/* Driver Code */ +void main() { + /* Инициализация пустого AVL-дерева */ + AVLTree avlTree = AVLTree(); + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* Вставка повторяющегося узла */ + testInsert(avlTree, 7); + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(avlTree, 8); // Удаление узла степени 0 + testRemove(avlTree, 5); // Удаление узла степени 1 + testRemove(avlTree, 4); // Удаление узла степени 2 + + /* Поиск узла */ + TreeNode? node = avlTree.search(7); + print("\nНайденный объект узла = $node, значение узла = ${node!.val}"); +} diff --git a/ru/codes/dart/chapter_tree/binary_search_tree.dart b/ru/codes/dart/chapter_tree/binary_search_tree.dart new file mode 100644 index 000000000..ca70894c9 --- /dev/null +++ b/ru/codes/dart/chapter_tree/binary_search_tree.dart @@ -0,0 +1,153 @@ +/** + * File: binary_search_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Двоичное дерево поиска */ +class BinarySearchTree { + late TreeNode? _root; + + /* Конструктор */ + BinarySearchTree() { + // Инициализировать пустое дерево + _root = null; + } + + /* Получить корневой узел двоичного дерева */ + TreeNode? getRoot() { + return _root; + } + + /* Поиск узла */ + TreeNode? search(int _num) { + TreeNode? cur = _root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < _num) + cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > _num) + cur = cur.left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } + + /* Вставка узла */ + void insert(int _num) { + // Если дерево пусто, инициализировать корневой узел + if (_root == null) { + _root = TreeNode(_num); + return; + } + TreeNode? cur = _root; + TreeNode? pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти повторяющийся узел и сразу вернуть + if (cur.val == _num) return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur.val < _num) + cur = cur.right; + // Позиция вставки находится в левом поддереве cur + else + cur = cur.left; + } + // Вставка узла + TreeNode? node = TreeNode(_num); + if (pre!.val < _num) + pre.right = node; + else + pre.left = node; + } + + /* Удаление узла */ + void remove(int _num) { + // Если дерево пусто, сразу вернуть + if (_root == null) return; + TreeNode? cur = _root; + TreeNode? pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти узел для удаления и выйти из цикла + if (cur.val == _num) break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur.val < _num) + cur = cur.right; + // Узел для удаления находится в левом поддереве cur + else + cur = cur.left; + } + // Если удаляемого узла нет, сразу вернуть + if (cur == null) return; + // Число дочерних узлов = 0 или 1 + if (cur.left == null || cur.right == null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + TreeNode? child = cur.left ?? cur.right; + // Удалить узел cur + if (cur != _root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + _root = child; + } + } else { + // Число дочерних узлов = 2 + // Получить следующий узел после cur в симметричном обходе + TreeNode? tmp = cur.right; + while (tmp!.left != null) { + tmp = tmp.left; + } + // Рекурсивно удалить узел tmp + remove(tmp.val); + // Перезаписать cur значением tmp + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +void main() { + /* Инициализация двоичного дерева поиска */ + BinarySearchTree bst = BinarySearchTree(); + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for (int _num in nums) { + bst.insert(_num); + } + print("\nИсходное двоичное дерево\n"); + printTree(bst.getRoot()); + + /* Поиск узла */ + TreeNode? node = bst.search(7); + print("\nНайденный объект узла = $node, значение узла = ${node?.val}"); + + /* Вставка узла */ + bst.insert(16); + print("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); + printTree(bst.getRoot()); + + /* Удаление узла */ + bst.remove(1); + print("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); + printTree(bst.getRoot()); + bst.remove(2); + print("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); + printTree(bst.getRoot()); + bst.remove(4); + print("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); + printTree(bst.getRoot()); +} diff --git a/ru/codes/dart/chapter_tree/binary_tree.dart b/ru/codes/dart/chapter_tree/binary_tree.dart new file mode 100644 index 000000000..77f80b612 --- /dev/null +++ b/ru/codes/dart/chapter_tree/binary_tree.dart @@ -0,0 +1,37 @@ +/** + * File: binary_tree.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +void main() { + /* Инициализация двоичного дерева */ + // Инициализировать узлы дерева + TreeNode n1 = TreeNode(1); + TreeNode n2 = TreeNode(2); + TreeNode n3 = TreeNode(3); + TreeNode n4 = TreeNode(4); + TreeNode n5 = TreeNode(5); + // Построить связи между узлами (указатели) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + print("\nИнициализация двоичного дерева\n"); + printTree(n1); + + /* Вставка и удаление узлов */ + TreeNode p = TreeNode(0); + // Вставить узел p между n1 -> n2 + n1.left = p; + p.left = n2; + print("\nПосле вставки узла P\n"); + printTree(n1); + // Удалить узел P + n1.left = n2; + print("\nПосле удаления узла P\n"); + printTree(n1); +} diff --git a/ru/codes/dart/chapter_tree/binary_tree_bfs.dart b/ru/codes/dart/chapter_tree/binary_tree_bfs.dart new file mode 100644 index 000000000..2fbd6f8bc --- /dev/null +++ b/ru/codes/dart/chapter_tree/binary_tree_bfs.dart @@ -0,0 +1,38 @@ +/** + * File: binary_tree_bfs.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmai.com) + */ + +import 'dart:collection'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* Обход в ширину */ +List levelOrder(TreeNode? root) { + // Инициализировать очередь и добавить корневой узел + Queue queue = Queue(); + queue.add(root); + // Инициализировать список для хранения последовательности обхода + List res = []; + while (queue.isNotEmpty) { + TreeNode? node = queue.removeFirst(); // Извлечение из очереди + res.add(node!.val); // Сохранить значение узла + if (node.left != null) queue.add(node.left); // Поместить левый дочерний узел в очередь + if (node.right != null) queue.add(node.right); // Поместить правый дочерний узел в очередь + } + return res; +} + +/* Driver Code */ +void main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\nИнициализация двоичного дерева\n"); + printTree(root); + + // Обход в ширину + List res = levelOrder(root); + print("\nПоследовательность печати узлов при обходе в ширину = $res"); +} diff --git a/ru/codes/dart/chapter_tree/binary_tree_dfs.dart b/ru/codes/dart/chapter_tree/binary_tree_dfs.dart new file mode 100644 index 000000000..dc3b20f2c --- /dev/null +++ b/ru/codes/dart/chapter_tree/binary_tree_dfs.dart @@ -0,0 +1,62 @@ +/** + * File: binary_tree_dfs.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +// Инициализировать список для хранения последовательности обхода +List list = []; + +/* Предварительный обход */ +void preOrder(TreeNode? node) { + if (node == null) return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.add(node.val); + preOrder(node.left); + preOrder(node.right); +} + +/* Симметричный обход */ +void inOrder(TreeNode? node) { + if (node == null) return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(node.left); + list.add(node.val); + inOrder(node.right); +} + +/* Обратный обход */ +void postOrder(TreeNode? node) { + if (node == null) return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(node.left); + postOrder(node.right); + list.add(node.val); +} + +/* Driver Code */ +void main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\nИнициализация двоичного дерева\n"); + printTree(root); + + /* Предварительный обход */ + list.clear(); + preOrder(root); + print("\nПоследовательность печати узлов при предварительном обходе = $list"); + + /* Симметричный обход */ + list.clear(); + inOrder(root); + print("\nПоследовательность печати узлов при симметричном обходе = $list"); + + /* Обратный обход */ + list.clear(); + postOrder(root); + print("\nПоследовательность печати узлов при обратном обходе = $list"); +} diff --git a/ru/codes/dart/utils/list_node.dart b/ru/codes/dart/utils/list_node.dart new file mode 100644 index 000000000..8425b4db5 --- /dev/null +++ b/ru/codes/dart/utils/list_node.dart @@ -0,0 +1,24 @@ +/** + * File: list_node.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* Узел связного списка */ +class ListNode { + int val; + ListNode? next; + + ListNode(this.val, [this.next]); +} + +/* Десериализовать список в связный список */ +ListNode? listToLinkedList(List list) { + ListNode dum = ListNode(0); + ListNode? head = dum; + for (int val in list) { + head?.next = ListNode(val); + head = head?.next; + } + return dum.next; +} diff --git a/ru/codes/dart/utils/print_util.dart b/ru/codes/dart/utils/print_util.dart new file mode 100644 index 000000000..d6d383854 --- /dev/null +++ b/ru/codes/dart/utils/print_util.dart @@ -0,0 +1,90 @@ +/** + * File: print_util.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:io'; + +import 'list_node.dart'; +import 'tree_node.dart'; + +class Trunk { + Trunk? prev; + String str; + + Trunk(this.prev, this.str); +} + +/* Вывести матрицу (Array) */ +void printMatrix(List> matrix) { + print("["); + for (List row in matrix) { + print(" $row,"); + } + print("]"); +} + +/* Вывести связный список */ +void printLinkedList(ListNode? head) { + List list = []; + + while (head != null) { + list.add('${head.val}'); + head = head.next; + } + + print(list.join(' -> ')); +} + +/** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { + if (root == null) { + return; + } + + String prev_str = ' '; + Trunk trunk = Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + showTrunks(trunk); + print(' ${root.val}'); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +void showTrunks(Trunk? p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + stdout.write(p.str); +} + +/* Вывести кучу */ +void printHeap(List heap) { + print("Массивное представление кучи: $heap"); + print("Древовидное представление кучи:"); + TreeNode? root = listToTree(heap); + printTree(root); +} diff --git a/ru/codes/dart/utils/tree_node.dart b/ru/codes/dart/utils/tree_node.dart new file mode 100644 index 000000000..0db8c1ee4 --- /dev/null +++ b/ru/codes/dart/utils/tree_node.dart @@ -0,0 +1,50 @@ +/** + * File: tree_node.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* Класс узла двоичного дерева */ +class TreeNode { + int val; // Значение узла + int height; // Высота узла + TreeNode? left; // Ссылка на левый дочерний узел + TreeNode? right; // Ссылка на правый дочерний узел + + /* Конструктор */ + TreeNode(this.val, [this.height = 0, this.left, this.right]); +} + +/* Десериализовать список в двоичное дерево: рекурсия */ +TreeNode? listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.length || arr[i] == null) { + return null; + } + TreeNode? root = TreeNode(arr[i]!); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* Десериализовать список в двоичное дерево */ +TreeNode? listToTree(List arr) { + return listToTreeDFS(arr, 0); +} + +/* Сериализовать двоичное дерево в список: рекурсия */ +void treeToListDFS(TreeNode? root, int i, List res) { + if (root == null) return; + while (i >= res.length) { + res.add(null); + } + res[i] = root.val; + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); +} + +/* Сериализовать двоичное дерево в список */ +List treeToList(TreeNode? root) { + List res = []; + treeToListDFS(root, 0, res); + return res; +} diff --git a/ru/codes/dart/utils/vertex.dart b/ru/codes/dart/utils/vertex.dart new file mode 100644 index 000000000..03fb10c1d --- /dev/null +++ b/ru/codes/dart/utils/vertex.dart @@ -0,0 +1,29 @@ +/** + * File: Vertex.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Класс вершины */ +class Vertex { + int val; + Vertex(this.val); + + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + static List valsToVets(List vals) { + List vets = []; + for (int i in vals) { + vets.add(Vertex(i)); + } + return vets; + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + static List vetsToVals(List vets) { + List vals = []; + for (Vertex vet in vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/ru/codes/docker-compose.yml b/ru/codes/docker-compose.yml new file mode 100644 index 000000000..cfeb4e559 --- /dev/null +++ b/ru/codes/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.8' +services: + hello-algo-code: + build: + context: . + args: + # Set the languages to be installed, separated by spaces + LANGS: "python cpp java csharp" + image: hello-algo-code + container_name: hello-algo-code + stdin_open: true + tty: true diff --git a/ru/codes/go/chapter_array_and_linkedlist/array.go b/ru/codes/go/chapter_array_and_linkedlist/array.go new file mode 100644 index 000000000..07fbb2b3d --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/array.go @@ -0,0 +1,79 @@ +// File: array.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "math/rand" +) + +/* Случайный доступ к элементу */ +func randomAccess(nums []int) (randomNum int) { + // Случайным образом выбрать число из интервала [0, nums.length) + randomIndex := rand.Intn(len(nums)) + // Получить и вернуть случайный элемент + randomNum = nums[randomIndex] + return +} + +/* Увеличить длину массива */ +func extend(nums []int, enlarge int) []int { + // Инициализировать массив увеличенной длины + res := make([]int, len(nums)+enlarge) + // Скопировать все элементы исходного массива в новый массив + for i, num := range nums { + res[i] = num + } + // Вернуть новый массив после расширения + return res +} + +/* Вставить элемент num по индексу index в массив */ +func insert(nums []int, num int, index int) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for i := len(nums) - 1; i > index; i-- { + nums[i] = nums[i-1] + } + // Присвоить num элементу по индексу index + nums[index] = num +} + +/* Удалить элемент по индексу index */ +func remove(nums []int, index int) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for i := index; i < len(nums)-1; i++ { + nums[i] = nums[i+1] + } +} + +/* Обход массива */ +func traverse(nums []int) { + count := 0 + // Обход массива по индексам + for i := 0; i < len(nums); i++ { + count += nums[i] + } + count = 0 + // Непосредственно обходить элементы массива + for _, num := range nums { + count += num + } + // Одновременно обходить индексы и элементы данных + for i, num := range nums { + count += nums[i] + count += num + } +} + +/* Найти заданный элемент в массиве */ +func find(nums []int, target int) (index int) { + index = -1 + for i := 0; i < len(nums); i++ { + if nums[i] == target { + index = i + break + } + } + return +} diff --git a/ru/codes/go/chapter_array_and_linkedlist/array_test.go b/ru/codes/go/chapter_array_and_linkedlist/array_test.go new file mode 100644 index 000000000..67b1e8c5b --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/array_test.go @@ -0,0 +1,50 @@ +// File: array_test.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +/** + * Мы рассматриваем срез Slice в Go как массив Array. Это позволяет + * снизить порог понимания и сосредоточиться на структурах данных и алгоритмах. + */ + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestArray(t *testing.T) { + /* Инициализация массива */ + var arr [5]int + fmt.Println("Массив arr =", arr) + // В Go при указании длины ([5]int) получается массив, а без указания длины ([]int) — срез + // Так как массивы в Go имеют длину, определяемую на этапе компиляции, для задания длины можно использовать только константы + // Для удобства реализации функции расширения extend() ниже срез (Slice) рассматривается как массив (Array) + nums := []int{1, 3, 2, 5, 4} + fmt.Println("Массив nums =", nums) + + /* Случайный доступ */ + randomNum := randomAccess(nums) + fmt.Println("Случайный элемент из nums =", randomNum) + + /* Расширение длины */ + nums = extend(nums, 3) + fmt.Println("После увеличения длины массива до 8 nums =", nums) + + /* Вставка элемента */ + insert(nums, 6, 3) + fmt.Println("После вставки числа 6 по индексу 3 nums =", nums) + + /* Удаление элемента */ + remove(nums, 2) + fmt.Println("После удаления элемента по индексу 2 nums =", nums) + + /* Обход массива */ + traverse(nums) + + /* Поиск элемента */ + index := find(nums, 3) + fmt.Println("Поиск элемента 3 в nums: индекс =", index) +} diff --git a/ru/codes/go/chapter_array_and_linkedlist/linked_list.go b/ru/codes/go/chapter_array_and_linkedlist/linked_list.go new file mode 100644 index 000000000..e0d8e54cc --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/linked_list.go @@ -0,0 +1,51 @@ +// File: linked_list.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Вставить узел P после узла n0 в связном списке */ +func insertNode(n0 *ListNode, P *ListNode) { + n1 := n0.Next + P.Next = n1 + n0.Next = P +} + +/* Удалить первый узел после узла n0 в связном списке */ +func removeItem(n0 *ListNode) { + if n0.Next == nil { + return + } + // n0 -> P -> n1 + P := n0.Next + n1 := P.Next + n0.Next = n1 +} + +/* Доступ к узлу связного списка по индексу index */ +func access(head *ListNode, index int) *ListNode { + for i := 0; i < index; i++ { + if head == nil { + return nil + } + head = head.Next + } + return head +} + +/* Найти в связном списке первый узел со значением target */ +func findNode(head *ListNode, target int) int { + index := 0 + for head != nil { + if head.Val == target { + return index + } + head = head.Next + index++ + } + return -1 +} diff --git a/ru/codes/go/chapter_array_and_linkedlist/linked_list_test.go b/ru/codes/go/chapter_array_and_linkedlist/linked_list_test.go new file mode 100644 index 000000000..3946c5a02 --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/linked_list_test.go @@ -0,0 +1,48 @@ +// File: linked_list_test.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinkedList(t *testing.T) { + /* Инициализировать связный список 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация всех узлов + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + + // Построить ссылки между узлами + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + fmt.Println("Исходный связный список") + PrintLinkedList(n0) + + /* Вставка узла */ + insertNode(n0, NewListNode(0)) + fmt.Println("Связный список после вставки узла") + PrintLinkedList(n0) + + /* Удаление узла */ + removeItem(n0) + fmt.Println("Связный список после удаления узла") + PrintLinkedList(n0) + + /* Доступ к узлу */ + node := access(n0, 3) + fmt.Println("Значение узла по индексу 3 в связном списке =", node) + + /* Поиск узла */ + index := findNode(n0, 2) + fmt.Println("Индекс узла со значением 2 в связном списке =", index) +} diff --git a/ru/codes/go/chapter_array_and_linkedlist/list_test.go b/ru/codes/go/chapter_array_and_linkedlist/list_test.go new file mode 100644 index 000000000..1b6ccff91 --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/list_test.go @@ -0,0 +1,66 @@ +// File: list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "sort" + "testing" +) + +/* Driver Code */ +func TestList(t *testing.T) { + /* Инициализация списка */ + nums := []int{1, 3, 2, 5, 4} + fmt.Println("Список nums =", nums) + + /* Доступ к элементу */ + num := nums[1] // Обратиться к элементу по индексу 1 + fmt.Println("Элемент по индексу 1: num =", num) + + /* Обновление элемента */ + nums[1] = 0 // Обновить элемент по индексу 1 до 0 + fmt.Println("После обновления элемента по индексу 1 до 0 nums =", nums) + + /* Очистить список */ + nums = nil + fmt.Println("После очистки списка nums =", nums) + + /* Добавление элемента в конец */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + fmt.Println("После добавления элементов nums =", nums) + + /* Вставка элемента в середину */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Вставить число 6 по индексу 3 + fmt.Println("После вставки числа 6 по индексу 3 nums =", nums) + + /* Удаление элемента */ + nums = append(nums[:3], nums[4:]...) // Удалить элемент по индексу 3 + fmt.Println("После удаления элемента по индексу 3 nums =", nums) + + /* Обходить список по индексам */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + /* Непосредственно обходить элементы списка */ + count = 0 + for _, x := range nums { + count += x + } + + /* Объединить два списка */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // Присоединить список nums1 после nums + fmt.Println("После конкатенации списка nums1 к nums nums =", nums) + + /* Отсортировать список */ + sort.Ints(nums) // После сортировки элементы списка располагаются по возрастанию + fmt.Println("После сортировки списка nums =", nums) +} diff --git a/ru/codes/go/chapter_array_and_linkedlist/my_list.go b/ru/codes/go/chapter_array_and_linkedlist/my_list.go new file mode 100644 index 000000000..eeb278bc2 --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/my_list.go @@ -0,0 +1,109 @@ +// File: my_list.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +/* Класс списка */ +type myList struct { + arrCapacity int + arr []int + arrSize int + extendRatio int +} + +/* Конструктор */ +func newMyList() *myList { + return &myList{ + arrCapacity: 10, // Вместимость списка + arr: make([]int, 10), // Массив (для хранения элементов списка) + arrSize: 0, // Длина списка (текущее число элементов) + extendRatio: 2, // Коэффициент увеличения списка при каждом расширении + } +} + +/* Получить длину списка (текущее число элементов) */ +func (l *myList) size() int { + return l.arrSize +} + +/* Получить вместимость списка */ +func (l *myList) capacity() int { + return l.arrCapacity +} + +/* Доступ к элементу */ +func (l *myList) get(index int) int { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if index < 0 || index >= l.arrSize { + panic("индекс выходит за границы") + } + return l.arr[index] +} + +/* Обновление элемента */ +func (l *myList) set(num, index int) { + if index < 0 || index >= l.arrSize { + panic("индекс выходит за границы") + } + l.arr[index] = num +} + +/* Добавление элемента в конец */ +func (l *myList) add(num int) { + // При превышении вместимости по числу элементов запускается расширение + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + l.arr[l.arrSize] = num + // Обновить число элементов + l.arrSize++ +} + +/* Вставка элемента в середину */ +func (l *myList) insert(num, index int) { + if index < 0 || index >= l.arrSize { + panic("индекс выходит за границы") + } + // При превышении вместимости по числу элементов запускается расширение + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for j := l.arrSize - 1; j >= index; j-- { + l.arr[j+1] = l.arr[j] + } + l.arr[index] = num + // Обновить число элементов + l.arrSize++ +} + +/* Удаление элемента */ +func (l *myList) remove(index int) int { + if index < 0 || index >= l.arrSize { + panic("индекс выходит за границы") + } + num := l.arr[index] + // Сдвинуть все элементы после индекса index на одну позицию вперед + for j := index; j < l.arrSize-1; j++ { + l.arr[j] = l.arr[j+1] + } + // Обновить число элементов + l.arrSize-- + // Вернуть удаленный элемент + return num +} + +/* Расширение списка */ +func (l *myList) extendCapacity() { + // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив + l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) + // Обновить вместимость списка + l.arrCapacity = len(l.arr) +} + +/* Вернуть список фактической длины */ +func (l *myList) toArray() []int { + // Преобразовывать только элементы списка в пределах фактической длины + return l.arr[:l.arrSize] +} diff --git a/ru/codes/go/chapter_array_and_linkedlist/my_list_test.go b/ru/codes/go/chapter_array_and_linkedlist/my_list_test.go new file mode 100644 index 000000000..0ad6f8c33 --- /dev/null +++ b/ru/codes/go/chapter_array_and_linkedlist/my_list_test.go @@ -0,0 +1,46 @@ +// File: my_list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestMyList(t *testing.T) { + /* Инициализация списка */ + nums := newMyList() + /* Добавление элемента в конец */ + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + fmt.Printf("Список nums = %v, вместимость = %v, длина = %v\n", nums.toArray(), nums.capacity(), nums.size()) + + /* Вставка элемента в середину */ + nums.insert(6, 3) + fmt.Printf("После вставки числа 6 по индексу 3 nums = %v\n", nums.toArray()) + + /* Удаление элемента */ + nums.remove(3) + fmt.Printf("После удаления элемента по индексу 3 nums = %v\n", nums.toArray()) + + /* Доступ к элементу */ + num := nums.get(1) + fmt.Printf("Элемент по индексу 1: num = %v\n", num) + + /* Обновление элемента */ + nums.set(0, 1) + fmt.Printf("После обновления элемента по индексу 1 до 0 nums = %v\n", nums.toArray()) + + /* Проверка механизма расширения */ + for i := 0; i < 10; i++ { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i) + } + fmt.Printf("Список nums после увеличения вместимости = %v, вместимость = %v, длина = %v\n", nums.toArray(), nums.capacity(), nums.size()) +} diff --git a/ru/codes/go/chapter_backtracking/n_queens.go b/ru/codes/go/chapter_backtracking/n_queens.go new file mode 100644 index 000000000..bec68189e --- /dev/null +++ b/ru/codes/go/chapter_backtracking/n_queens.go @@ -0,0 +1,57 @@ +// File: n_queens.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* Алгоритм бэктрекинга: n ферзей */ +func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { + // Когда все строки уже обработаны, записать решение + if row == n { + newState := make([][]string, len(*state)) + for i, _ := range newState { + newState[i] = make([]string, len((*state)[0])) + copy(newState[i], (*state)[i]) + + } + *res = append(*res, newState) + return + } + // Обойти все столбцы + for col := 0; col < n; col++ { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + diag1 := row - col + n - 1 + diag2 := row + col + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { + // Попытка: поставить ферзя в эту клетку + (*state)[row][col] = "Q" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true + // Перейти к размещению следующей строки + backtrack(row+1, n, state, res, cols, diags1, diags2) + // Откат: восстановить эту клетку как пустую + (*state)[row][col] = "#" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false + } + } +} + +/* Решить задачу о n ферзях */ +func nQueens(n int) [][][]string { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + state := make([][]string, n) + for i := 0; i < n; i++ { + row := make([]string, n) + for i := 0; i < n; i++ { + row[i] = "#" + } + state[i] = row + } + // Отмечать, есть ли ферзь в столбце + cols := make([]bool, n) + diags1 := make([]bool, 2*n-1) + diags2 := make([]bool, 2*n-1) + res := make([][][]string, 0) + backtrack(0, n, &state, &res, &cols, &diags1, &diags2) + return res +} diff --git a/ru/codes/go/chapter_backtracking/n_queens_test.go b/ru/codes/go/chapter_backtracking/n_queens_test.go new file mode 100644 index 000000000..2219a6e9c --- /dev/null +++ b/ru/codes/go/chapter_backtracking/n_queens_test.go @@ -0,0 +1,24 @@ +// File: n_queens_test.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" +) + +func TestNQueens(t *testing.T) { + n := 4 + res := nQueens(n) + + fmt.Println("Размер входной доски =", n) + fmt.Println("Количество способов расстановки ферзей:", len(res)) + for _, state := range res { + fmt.Println("--------------------") + for _, row := range state { + fmt.Println(row) + } + } +} diff --git a/ru/codes/go/chapter_backtracking/permutation_test.go b/ru/codes/go/chapter_backtracking/permutation_test.go new file mode 100644 index 000000000..590328da7 --- /dev/null +++ b/ru/codes/go/chapter_backtracking/permutation_test.go @@ -0,0 +1,33 @@ +// File: permutation_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPermutationI(t *testing.T) { + /* Все перестановки I */ + nums := []int{1, 2, 3} + fmt.Printf("Входной массив nums = ") + PrintSlice(nums) + + res := permutationsI(nums) + fmt.Printf("Все перестановки res = ") + fmt.Println(res) +} + +func TestPermutationII(t *testing.T) { + nums := []int{1, 2, 2} + fmt.Printf("Входной массив nums = ") + PrintSlice(nums) + + res := permutationsII(nums) + fmt.Printf("Все перестановки res = ") + fmt.Println(res) +} diff --git a/ru/codes/go/chapter_backtracking/permutations_i.go b/ru/codes/go/chapter_backtracking/permutations_i.go new file mode 100644 index 000000000..80e71ac07 --- /dev/null +++ b/ru/codes/go/chapter_backtracking/permutations_i.go @@ -0,0 +1,38 @@ +// File: permutations_i.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* Алгоритм бэктрекинга: все перестановки I */ +func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // Когда длина состояния равна числу элементов, записать решение + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // Перебор всех вариантов выбора + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // Отсечение: нельзя выбирать один и тот же элемент повторно + if !(*selected)[i] { + // Попытка: сделать выбор и обновить состояние + (*selected)[i] = true + *state = append(*state, choice) + // Перейти к следующему выбору + backtrackI(state, choices, selected, res) + // Откат: отменить выбор и восстановить предыдущее состояние + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* Все перестановки I */ +func permutationsI(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackI(&state, &nums, &selected, &res) + return res +} diff --git a/ru/codes/go/chapter_backtracking/permutations_ii.go b/ru/codes/go/chapter_backtracking/permutations_ii.go new file mode 100644 index 000000000..c020e93fe --- /dev/null +++ b/ru/codes/go/chapter_backtracking/permutations_ii.go @@ -0,0 +1,41 @@ +// File: permutations_ii.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* Алгоритм бэктрекинга: все перестановки II */ +func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // Когда длина состояния равна числу элементов, записать решение + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // Перебор всех вариантов выбора + duplicated := make(map[int]struct{}, 0) + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if _, ok := duplicated[choice]; !ok && !(*selected)[i] { + // Попробовать: сделать выбор, обновить состояние + // Записать значение уже выбранного элемента + duplicated[choice] = struct{}{} + (*selected)[i] = true + *state = append(*state, choice) + // Перейти к следующему выбору + backtrackII(state, choices, selected, res) + // Откат: отменить выбор и восстановить предыдущее состояние + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* Все перестановки II */ +func permutationsII(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackII(&state, &nums, &selected, &res) + return res +} diff --git a/ru/codes/go/chapter_backtracking/preorder_traversal_i_compact.go b/ru/codes/go/chapter_backtracking/preorder_traversal_i_compact.go new file mode 100644 index 000000000..23596177f --- /dev/null +++ b/ru/codes/go/chapter_backtracking/preorder_traversal_i_compact.go @@ -0,0 +1,22 @@ +// File: preorder_traversal_i_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Предварительный обход: пример 1 */ +func preOrderI(root *TreeNode, res *[]*TreeNode) { + if root == nil { + return + } + if (root.Val).(int) == 7 { + // Записать решение + *res = append(*res, root) + } + preOrderI(root.Left, res) + preOrderI(root.Right, res) +} diff --git a/ru/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go b/ru/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go new file mode 100644 index 000000000..a028c4076 --- /dev/null +++ b/ru/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go @@ -0,0 +1,26 @@ +// File: preorder_traversal_ii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Предварительный обход: пример 2 */ +func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + if root == nil { + return + } + // Попытка + *path = append(*path, root) + if root.Val.(int) == 7 { + // Записать решение + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderII(root.Left, res, path) + preOrderII(root.Right, res, path) + // Откат + *path = (*path)[:len(*path)-1] +} diff --git a/ru/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go b/ru/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go new file mode 100644 index 000000000..bacead63b --- /dev/null +++ b/ru/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go @@ -0,0 +1,27 @@ +// File: preorder_traversal_iii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Предварительный обход: пример 3 */ +func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + // Отсечение + if root == nil || root.Val == 3 { + return + } + // Попытка + *path = append(*path, root) + if root.Val.(int) == 7 { + // Записать решение + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderIII(root.Left, res, path) + preOrderIII(root.Right, res, path) + // Откат + *path = (*path)[:len(*path)-1] +} diff --git a/ru/codes/go/chapter_backtracking/preorder_traversal_iii_template.go b/ru/codes/go/chapter_backtracking/preorder_traversal_iii_template.go new file mode 100644 index 000000000..9fefdd93d --- /dev/null +++ b/ru/codes/go/chapter_backtracking/preorder_traversal_iii_template.go @@ -0,0 +1,57 @@ +// File: preorder_traversal_iii_template.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Проверить, является ли текущее состояние решением */ +func isSolution(state *[]*TreeNode) bool { + return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 +} + +/* Записать решение */ +func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { + *res = append(*res, append([]*TreeNode{}, *state...)) +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +func isValid(state *[]*TreeNode, choice *TreeNode) bool { + return choice != nil && choice.Val != 3 +} + +/* Обновить состояние */ +func makeChoice(state *[]*TreeNode, choice *TreeNode) { + *state = append(*state, choice) +} + +/* Восстановить состояние */ +func undoChoice(state *[]*TreeNode, choice *TreeNode) { + *state = (*state)[:len(*state)-1] +} + +/* Алгоритм бэктрекинга: пример 3 */ +func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { + // Проверить, является ли текущее состояние решением + if isSolution(state) { + // Записать решение + recordSolution(state, res) + } + // Перебор всех вариантов выбора + for _, choice := range *choices { + // Отсечение: проверить допустимость выбора + if isValid(state, choice) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice) + // Перейти к следующему выбору + temp := make([]*TreeNode, 0) + temp = append(temp, choice.Left, choice.Right) + backtrackIII(state, &temp, res) + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice) + } + } +} diff --git a/ru/codes/go/chapter_backtracking/preorder_traversal_test.go b/ru/codes/go/chapter_backtracking/preorder_traversal_test.go new file mode 100644 index 000000000..f15709b89 --- /dev/null +++ b/ru/codes/go/chapter_backtracking/preorder_traversal_test.go @@ -0,0 +1,91 @@ +// File: preorder_traversal_i_compact_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreorderTraversalICompact(t *testing.T) { + /* Инициализация двоичного дерева */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\nИнициализация двоичного дерева") + PrintTree(root) + + // Предварительный обход + res := make([]*TreeNode, 0) + preOrderI(root, &res) + + fmt.Println("\nВсе узлы со значением 7") + for _, node := range res { + fmt.Printf("%v ", node.Val) + } + fmt.Println() +} + +func TestPreorderTraversalIICompact(t *testing.T) { + /* Инициализация двоичного дерева */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\nИнициализация двоичного дерева") + PrintTree(root) + + // Предварительный обход + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderII(root, &res, &path) + + fmt.Println("\nВсе пути от корня к узлу 7") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIICompact(t *testing.T) { + /* Инициализация двоичного дерева */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\nИнициализация двоичного дерева") + PrintTree(root) + + // Предварительный обход + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderIII(root, &res, &path) + + fmt.Println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIITemplate(t *testing.T) { + /* Инициализация двоичного дерева */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\nИнициализация двоичного дерева") + PrintTree(root) + + // Алгоритм бэктрекинга + res := make([][]*TreeNode, 0) + state := make([]*TreeNode, 0) + choices := make([]*TreeNode, 0) + choices = append(choices, root) + backtrackIII(&state, &choices, &res) + + fmt.Println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} diff --git a/ru/codes/go/chapter_backtracking/subset_sum_i.go b/ru/codes/go/chapter_backtracking/subset_sum_i.go new file mode 100644 index 000000000..1a4f01d60 --- /dev/null +++ b/ru/codes/go/chapter_backtracking/subset_sum_i.go @@ -0,0 +1,42 @@ +// File: subset_sum_i.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { + // Если сумма подмножества равна target, записать решение + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for i := start; i < len(*choices); i++ { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target-(*choices)[i] < 0 { + break + } + // Попытка: сделать выбор и обновить target и start + *state = append(*state, (*choices)[i]) + // Перейти к следующему выбору + backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) + // Откат: отменить выбор и восстановить предыдущее состояние + *state = (*state)[:len(*state)-1] + } +} + +/* Решить задачу суммы подмножеств I */ +func subsetSumI(nums []int, target int) [][]int { + state := make([]int, 0) // Состояние (подмножество) + sort.Ints(nums) // Отсортировать nums + start := 0 // Стартовая вершина обхода + res := make([][]int, 0) // Список результатов (список подмножеств) + backtrackSubsetSumI(start, target, &state, &nums, &res) + return res +} diff --git a/ru/codes/go/chapter_backtracking/subset_sum_i_naive.go b/ru/codes/go/chapter_backtracking/subset_sum_i_naive.go new file mode 100644 index 000000000..2c2490206 --- /dev/null +++ b/ru/codes/go/chapter_backtracking/subset_sum_i_naive.go @@ -0,0 +1,37 @@ +// File: subset_sum_i_naive.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { + // Если сумма подмножества равна target, записать решение + if target == total { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // Перебор всех вариантов выбора + for i := 0; i < len(*choices); i++ { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if total+(*choices)[i] > target { + continue + } + // Попытка: сделать выбор и обновить элемент и total + *state = append(*state, (*choices)[i]) + // Перейти к следующему выбору + backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) + // Откат: отменить выбор и восстановить предыдущее состояние + *state = (*state)[:len(*state)-1] + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +func subsetSumINaive(nums []int, target int) [][]int { + state := make([]int, 0) // Состояние (подмножество) + total := 0 // Сумма подмножеств + res := make([][]int, 0) // Список результатов (список подмножеств) + backtrackSubsetSumINaive(total, target, &state, &nums, &res) + return res +} diff --git a/ru/codes/go/chapter_backtracking/subset_sum_ii.go b/ru/codes/go/chapter_backtracking/subset_sum_ii.go new file mode 100644 index 000000000..ed644fcff --- /dev/null +++ b/ru/codes/go/chapter_backtracking/subset_sum_ii.go @@ -0,0 +1,47 @@ +// File: subset_sum_ii.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { + // Если сумма подмножества равна target, записать решение + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for i := start; i < len(*choices); i++ { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target-(*choices)[i] < 0 { + break + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if i > start && (*choices)[i] == (*choices)[i-1] { + continue + } + // Попытка: сделать выбор и обновить target и start + *state = append(*state, (*choices)[i]) + // Перейти к следующему выбору + backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) + // Откат: отменить выбор и восстановить предыдущее состояние + *state = (*state)[:len(*state)-1] + } +} + +/* Решить задачу суммы подмножеств II */ +func subsetSumII(nums []int, target int) [][]int { + state := make([]int, 0) // Состояние (подмножество) + sort.Ints(nums) // Отсортировать nums + start := 0 // Стартовая вершина обхода + res := make([][]int, 0) // Список результатов (список подмножеств) + backtrackSubsetSumII(start, target, &state, &nums, &res) + return res +} diff --git a/ru/codes/go/chapter_backtracking/subset_sum_test.go b/ru/codes/go/chapter_backtracking/subset_sum_test.go new file mode 100644 index 000000000..b8b2652be --- /dev/null +++ b/ru/codes/go/chapter_backtracking/subset_sum_test.go @@ -0,0 +1,56 @@ +// File: subset_sum_test.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSubsetSumINaive(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumINaive(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", входной массив nums = ") + PrintSlice(nums) + + fmt.Println("Все подмножества с суммой " + strconv.Itoa(target) + ": res = ") + for i := range res { + PrintSlice(res[i]) + } + fmt.Println("Обратите внимание: результат этого метода содержит повторяющиеся множества") +} + +func TestSubsetSumI(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumI(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", входной массив nums = ") + PrintSlice(nums) + + fmt.Println("Все подмножества с суммой " + strconv.Itoa(target) + ": res = ") + for i := range res { + PrintSlice(res[i]) + } +} + +func TestSubsetSumII(t *testing.T) { + nums := []int{4, 4, 5} + target := 9 + res := subsetSumII(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", входной массив nums = ") + PrintSlice(nums) + + fmt.Println("Все подмножества с суммой " + strconv.Itoa(target) + ": res = ") + for i := range res { + PrintSlice(res[i]) + } +} diff --git a/ru/codes/go/chapter_computational_complexity/iteration.go b/ru/codes/go/chapter_computational_complexity/iteration.go new file mode 100644 index 000000000..157e6eb59 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/iteration.go @@ -0,0 +1,59 @@ +// File: iteration.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "fmt" + +/* Цикл for */ +func forLoop(n int) int { + res := 0 + // Циклическое суммирование 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + res += i + } + return res +} + +/* Цикл while */ +func whileLoop(n int) int { + res := 0 + // Инициализация условной переменной + i := 1 + // Циклическое суммирование 1, 2, ..., n-1, n + for i <= n { + res += i + // Обновить условную переменную + i++ + } + return res +} + +/* Цикл while (двойное обновление) */ +func whileLoopII(n int) int { + res := 0 + // Инициализация условной переменной + i := 1 + // Циклическое суммирование 1, 4, 10, ... + for i <= n { + res += i + // Обновить условную переменную + i++ + i *= 2 + } + return res +} + +/* Двойной цикл for */ +func nestedForLoop(n int) string { + res := "" + // Цикл по i = 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + for j := 1; j <= n; j++ { + // Цикл по j = 1, 2, ..., n-1, n + res += fmt.Sprintf("(%d, %d), ", i, j) + } + } + return res +} diff --git a/ru/codes/go/chapter_computational_complexity/iteration_test.go b/ru/codes/go/chapter_computational_complexity/iteration_test.go new file mode 100644 index 000000000..901418d2a --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/iteration_test.go @@ -0,0 +1,26 @@ +// File: iteration_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestIteration(t *testing.T) { + n := 5 + res := forLoop(n) + fmt.Println("\nРезультат суммирования в цикле for res = ", res) + + res = whileLoop(n) + fmt.Println("\nРезультат суммирования в цикле while res = ", res) + + res = whileLoopII(n) + fmt.Println("\nРезультат суммирования в цикле while (двойное обновление) res = ", res) + + resStr := nestedForLoop(n) + fmt.Println("\nРезультат обхода в двойном цикле for ", resStr) +} diff --git a/ru/codes/go/chapter_computational_complexity/recursion.go b/ru/codes/go/chapter_computational_complexity/recursion.go new file mode 100644 index 000000000..4a9bdac39 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/recursion.go @@ -0,0 +1,61 @@ +// File: recursion.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "container/list" + +/* Рекурсия */ +func recur(n int) int { + // Условие завершения + if n == 1 { + return 1 + } + // Рекурсия: рекурсивный вызов + res := recur(n - 1) + // Возврат: вернуть результат + return n + res +} + +/* Имитация рекурсии итерацией */ +func forLoopRecur(n int) int { + // Использовать явный стек для имитации системного стека вызовов + stack := list.New() + res := 0 + // Рекурсия: рекурсивный вызов + for i := n; i > 0; i-- { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.PushBack(i) + } + // Возврат: вернуть результат + for stack.Len() != 0 { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.Back().Value.(int) + stack.Remove(stack.Back()) + } + // res = 1+2+3+...+n + return res +} + +/* Хвостовая рекурсия */ +func tailRecur(n int, res int) int { + // Условие завершения + if n == 0 { + return res + } + // Хвостовой рекурсивный вызов + return tailRecur(n-1, res+n) +} + +/* Последовательность Фибоначчи: рекурсия */ +func fib(n int) int { + // Условие завершения: f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + res := fib(n-1) + fib(n-2) + // Вернуть результат f(n) + return res +} diff --git a/ru/codes/go/chapter_computational_complexity/recursion_test.go b/ru/codes/go/chapter_computational_complexity/recursion_test.go new file mode 100644 index 000000000..6ac15f4a7 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/recursion_test.go @@ -0,0 +1,26 @@ +// File: recursion_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestRecursion(t *testing.T) { + n := 5 + res := recur(n) + fmt.Println("\nРезультат суммирования в рекурсивной функции res = ", res) + + res = forLoopRecur(n) + fmt.Println("\nРезультат суммирования при имитации рекурсии итерацией res = ", res) + + res = tailRecur(n, 0) + fmt.Println("\nРезультат суммирования в хвостовой рекурсии res = ", res) + + res = fib(n) + fmt.Println("\nЧлен последовательности Фибоначчи с номером", n, "=", res) +} diff --git a/ru/codes/go/chapter_computational_complexity/space_complexity.go b/ru/codes/go/chapter_computational_complexity/space_complexity.go new file mode 100644 index 000000000..bd6e24fca --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/space_complexity.go @@ -0,0 +1,106 @@ +// File: space_complexity.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "strconv" + + . "github.com/krahets/hello-algo/pkg" +) + +/* Структура */ +type node struct { + val int + next *node +} + +/* Создать структуру node */ +func newNode(val int) *node { + return &node{val: val} +} + +/* Функция */ +func function() int { + // Выполнить некоторые операции... + return 0 +} + +/* Постоянная сложность */ +func spaceConstant(n int) { + // Константы, переменные и объекты занимают O(1) памяти + const a = 0 + b := 0 + nums := make([]int, 10000) + node := newNode(0) + // Переменные в цикле занимают O(1) памяти + var c int + for i := 0; i < n; i++ { + c = 0 + } + // Функции в цикле занимают O(1) памяти + for i := 0; i < n; i++ { + function() + } + b += 0 + c += 0 + nums[0] = 0 + node.val = 0 +} + +/* Линейная сложность */ +func spaceLinear(n int) { + // Массив длины n занимает O(n) памяти + _ = make([]int, n) + // Список длины n занимает O(n) памяти + var nodes []*node + for i := 0; i < n; i++ { + nodes = append(nodes, newNode(i)) + } + // Хеш-таблица длины n занимает O(n) памяти + m := make(map[int]string, n) + for i := 0; i < n; i++ { + m[i] = strconv.Itoa(i) + } +} + +/* Линейная сложность (рекурсивная реализация) */ +func spaceLinearRecur(n int) { + fmt.Println("Рекурсия n =", n) + if n == 1 { + return + } + spaceLinearRecur(n - 1) +} + +/* Квадратичная сложность */ +func spaceQuadratic(n int) { + // Матрица занимает O(n^2) памяти + numMatrix := make([][]int, n) + for i := 0; i < n; i++ { + numMatrix[i] = make([]int, n) + } +} + +/* Квадратичная сложность (рекурсивная реализация) */ +func spaceQuadraticRecur(n int) int { + if n <= 0 { + return 0 + } + nums := make([]int, n) + fmt.Printf("В рекурсии n = %d, длина nums = %d\n", n, len(nums)) + return spaceQuadraticRecur(n - 1) +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +func buildTree(n int) *TreeNode { + if n == 0 { + return nil + } + root := NewTreeNode(0) + root.Left = buildTree(n - 1) + root.Right = buildTree(n - 1) + return root +} diff --git a/ru/codes/go/chapter_computational_complexity/space_complexity_test.go b/ru/codes/go/chapter_computational_complexity/space_complexity_test.go new file mode 100644 index 000000000..351aec6e6 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/space_complexity_test.go @@ -0,0 +1,26 @@ +// File: space_complexity_test.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSpaceComplexity(t *testing.T) { + n := 5 + // Постоянная сложность + spaceConstant(n) + // Линейная сложность + spaceLinear(n) + spaceLinearRecur(n) + // Квадратичная сложность + spaceQuadratic(n) + spaceQuadraticRecur(n) + // Экспоненциальная сложность + root := buildTree(n) + PrintTree(root) +} diff --git a/ru/codes/go/chapter_computational_complexity/time_complexity.go b/ru/codes/go/chapter_computational_complexity/time_complexity.go new file mode 100644 index 000000000..4f4cd790c --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/time_complexity.go @@ -0,0 +1,130 @@ +// File: time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +/* Постоянная сложность */ +func constant(n int) int { + count := 0 + size := 100000 + for i := 0; i < size; i++ { + count++ + } + return count +} + +/* Линейная сложность */ +func linear(n int) int { + count := 0 + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* Линейная сложность (обход массива) */ +func arrayTraversal(nums []int) int { + count := 0 + // Число итераций пропорционально длине массива + for range nums { + count++ + } + return count +} + +/* Квадратичная сложность */ +func quadratic(n int) int { + count := 0 + // Число итераций квадратично зависит от размера данных n + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + count++ + } + } + return count +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +func bubbleSort(nums []int) int { + count := 0 // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for i := len(nums) - 1; i > 0; i-- { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // Поменять местами nums[j] и nums[j + 1] + tmp := nums[j] + nums[j] = nums[j+1] + nums[j+1] = tmp + count += 3 // Обмен элементов включает 3 элементарные операции + } + } + } + return count +} + +/* Экспоненциальная сложность (итеративная реализация) */ +func exponential(n int) int { + count, base := 0, 1 + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for i := 0; i < n; i++ { + for j := 0; j < base; j++ { + count++ + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +func expRecur(n int) int { + if n == 1 { + return 1 + } + return expRecur(n-1) + expRecur(n-1) + 1 +} + +/* Логарифмическая сложность (итеративная реализация) */ +func logarithmic(n int) int { + count := 0 + for n > 1 { + n = n / 2 + count++ + } + return count +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +func logRecur(n int) int { + if n <= 1 { + return 0 + } + return logRecur(n/2) + 1 +} + +/* Линейно-логарифмическая сложность */ +func linearLogRecur(n int) int { + if n <= 1 { + return 1 + } + count := linearLogRecur(n/2) + linearLogRecur(n/2) + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* Факториальная сложность (рекурсивная реализация) */ +func factorialRecur(n int) int { + if n == 0 { + return 1 + } + count := 0 + // Из одного получается n + for i := 0; i < n; i++ { + count += factorialRecur(n - 1) + } + return count +} diff --git a/ru/codes/go/chapter_computational_complexity/time_complexity_test.go b/ru/codes/go/chapter_computational_complexity/time_complexity_test.go new file mode 100644 index 000000000..261f32e49 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/time_complexity_test.go @@ -0,0 +1,48 @@ +// File: time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestTimeComplexity(t *testing.T) { + n := 8 + fmt.Println("Размер входных данных n =", n) + + count := constant(n) + fmt.Println("Число операций константной сложности =", count) + + count = linear(n) + fmt.Println("Число операций линейной сложности =", count) + count = arrayTraversal(make([]int, n)) + fmt.Println("Число операций линейной сложности (обход массива) =", count) + + count = quadratic(n) + fmt.Println("Число операций квадратичной сложности =", count) + nums := make([]int, n) + for i := 0; i < n; i++ { + nums[i] = n - i + } + count = bubbleSort(nums) + fmt.Println("Число операций квадратичной сложности (пузырьковая сортировка) =", count) + + count = exponential(n) + fmt.Println("Число операций экспоненциальной сложности (итеративная реализация) =", count) + count = expRecur(n) + fmt.Println("Число операций экспоненциальной сложности (рекурсивная реализация) =", count) + + count = logarithmic(n) + fmt.Println("Число операций логарифмической сложности (итеративная реализация) =", count) + count = logRecur(n) + fmt.Println("Число операций логарифмической сложности (рекурсивная реализация) =", count) + + count = linearLogRecur(n) + fmt.Println("Число операций линейно-логарифмической сложности (рекурсивная реализация) =", count) + + count = factorialRecur(n) + fmt.Println("Число операций факториальной сложности (рекурсивная реализация) =", count) +} diff --git a/ru/codes/go/chapter_computational_complexity/worst_best_time_complexity.go b/ru/codes/go/chapter_computational_complexity/worst_best_time_complexity.go new file mode 100644 index 000000000..4f5d3a8e8 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/worst_best_time_complexity.go @@ -0,0 +1,35 @@ +// File: worst_best_time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "math/rand" +) + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +func randomNumbers(n int) []int { + nums := make([]int, n) + // Создать массив nums = { 1, 2, 3, ..., n } + for i := 0; i < n; i++ { + nums[i] = i + 1 + } + // Случайно перемешать элементы массива + rand.Shuffle(len(nums), func(i, j int) { + nums[i], nums[j] = nums[j], nums[i] + }) + return nums +} + +/* Найти индекс числа 1 в массиве nums */ +func findOne(nums []int) int { + for i := 0; i < len(nums); i++ { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if nums[i] == 1 { + return i + } + } + return -1 +} diff --git a/ru/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go b/ru/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go new file mode 100644 index 000000000..d6965e6e3 --- /dev/null +++ b/ru/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go @@ -0,0 +1,20 @@ +// File: worst_best_time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestWorstBestTimeComplexity(t *testing.T) { + for i := 0; i < 10; i++ { + n := 100 + nums := randomNumbers(n) + index := findOne(nums) + fmt.Println("\nМассив [1, 2, ..., n] после перемешивания =", nums) + fmt.Println("Индекс числа 1 =", index) + } +} diff --git a/ru/codes/go/chapter_divide_and_conquer/binary_search_recur.go b/ru/codes/go/chapter_divide_and_conquer/binary_search_recur.go new file mode 100644 index 000000000..4e1daa6a8 --- /dev/null +++ b/ru/codes/go/chapter_divide_and_conquer/binary_search_recur.go @@ -0,0 +1,34 @@ +// File: binary_search_recur.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +/* Бинарный поиск: задача f(i, j) */ +func dfs(nums []int, target, i, j int) int { + // Если интервал пуст, это означает отсутствие целевого элемента, вернуть -1 + if i > j { + return -1 + } + // Вычислить средний индекс + m := i + ((j - i) >> 1) + // Сравнить середину и целевой элемент + if nums[m] < target { + // Если меньше, рекурсивно обрабатывать правую половину массива + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m+1, j) + } else if nums[m] > target { + // Если больше, рекурсивно обработать левую половину массива + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m-1) + } else { + // Целевой элемент найден, вернуть его индекс + return m + } +} + +/* Бинарный поиск */ +func binarySearch(nums []int, target int) int { + n := len(nums) + return dfs(nums, target, 0, n-1) +} diff --git a/ru/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go b/ru/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go new file mode 100644 index 000000000..68586d576 --- /dev/null +++ b/ru/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go @@ -0,0 +1,20 @@ +// File: binary_search_recur_test.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + target := 6 + noTarget := 99 + targetIndex := binarySearch(nums, target) + fmt.Println("Индекс целевого элемента 6 = ", targetIndex) + noTargetIndex := binarySearch(nums, noTarget) + fmt.Println("Индекс отсутствующего целевого элемента =", noTargetIndex) +} diff --git a/ru/codes/go/chapter_divide_and_conquer/build_tree.go b/ru/codes/go/chapter_divide_and_conquer/build_tree.go new file mode 100644 index 000000000..c372bf03c --- /dev/null +++ b/ru/codes/go/chapter_divide_and_conquer/build_tree.go @@ -0,0 +1,37 @@ +// File: build_tree.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import . "github.com/krahets/hello-algo/pkg" + +/* Построить двоичное дерево: разделяй и властвуй */ +func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { + // Завершить при пустом диапазоне поддерева + if r-l < 0 { + return nil + } + // Инициализировать корневой узел + root := NewTreeNode(preorder[i]) + // Найти m, чтобы разделить левое и правое поддеревья + m := inorderMap[preorder[i]] + // Подзадача: построить левое поддерево + root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) + // Подзадача: построить правое поддерево + root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) + // Вернуть корневой узел + return root +} + +/* Построить двоичное дерево */ +func buildTree(preorder, inorder []int) *TreeNode { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + inorderMap := make(map[int]int, len(inorder)) + for i := 0; i < len(inorder); i++ { + inorderMap[inorder[i]] = i + } + + root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) + return root +} diff --git a/ru/codes/go/chapter_divide_and_conquer/build_tree_test.go b/ru/codes/go/chapter_divide_and_conquer/build_tree_test.go new file mode 100644 index 000000000..3d7a890c6 --- /dev/null +++ b/ru/codes/go/chapter_divide_and_conquer/build_tree_test.go @@ -0,0 +1,25 @@ +// File: build_tree_test.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBuildTree(t *testing.T) { + preorder := []int{3, 9, 2, 1, 7} + inorder := []int{9, 3, 1, 2, 7} + fmt.Print("Предварительный обход = ") + PrintSlice(preorder) + fmt.Print("Симметричный обход = ") + PrintSlice(inorder) + + root := buildTree(preorder, inorder) + fmt.Println("Построенное двоичное дерево:") + PrintTree(root) +} diff --git a/ru/codes/go/chapter_divide_and_conquer/hanota.go b/ru/codes/go/chapter_divide_and_conquer/hanota.go new file mode 100644 index 000000000..1361041af --- /dev/null +++ b/ru/codes/go/chapter_divide_and_conquer/hanota.go @@ -0,0 +1,39 @@ +// File: hanota.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import "container/list" + +/* Переместить один диск */ +func move(src, tar *list.List) { + // Снять диск с вершины src + pan := src.Back() + // Положить диск на вершину tar + tar.PushBack(pan.Value) + // Убрать верхний диск из src + src.Remove(pan) +} + +/* Решить задачу Ханойской башни f(i) */ +func dfsHanota(i int, src, buf, tar *list.List) { + // Если в src остался только один диск, сразу переместить его в tar + if i == 1 { + move(src, tar) + return + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfsHanota(i-1, src, tar, buf) + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar) + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfsHanota(i-1, buf, src, tar) +} + +/* Решить задачу Ханойской башни */ +func solveHanota(A, B, C *list.List) { + n := A.Len() + // Переместить верхние n дисков из A в C с помощью B + dfsHanota(n, A, B, C) +} diff --git a/ru/codes/go/chapter_divide_and_conquer/hanota_test.go b/ru/codes/go/chapter_divide_and_conquer/hanota_test.go new file mode 100644 index 000000000..3f65c9e5a --- /dev/null +++ b/ru/codes/go/chapter_divide_and_conquer/hanota_test.go @@ -0,0 +1,40 @@ +// File: hanota_test.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHanota(t *testing.T) { + // Хвост списка соответствует вершине столбца + A := list.New() + for i := 5; i > 0; i-- { + A.PushBack(i) + } + B := list.New() + C := list.New() + fmt.Println("Исходное состояние:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) + + solveHanota(A, B, C) + + fmt.Println("После завершения перемещения дисков:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) +} diff --git a/ru/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go new file mode 100644 index 000000000..6f4f17210 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go @@ -0,0 +1,36 @@ +// File: climbing_stairs_backtrack.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Бэктрекинг */ +func backtrack(choices []int, state, n int, res []int) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if state == n { + res[0] = res[0] + 1 + } + // Перебор всех вариантов выбора + for _, choice := range choices { + // Отсечение: нельзя выходить за n-ю ступень + if state+choice > n { + continue + } + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state+choice, n, res) + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +func climbingStairsBacktrack(n int) int { + // Можно подняться на 1 или 2 ступени + choices := []int{1, 2} + // Начать подъем с 0-й ступени + state := 0 + res := make([]int, 1) + // Использовать res[0] для хранения числа решений + res[0] = 0 + backtrack(choices, state, n, res) + return res[0] +} diff --git a/ru/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go new file mode 100644 index 000000000..de0b59780 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go @@ -0,0 +1,25 @@ +// File: climbing_stairs_constraint_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +func climbingStairsConstraintDP(n int) int { + if n == 1 || n == 2 { + return 1 + } + // Инициализация таблицы dp для хранения решений подзадач + dp := make([][3]int, n+1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // Переход состояний: постепенное решение больших подзадач через меньшие + for i := 3; i <= n; i++ { + dp[i][1] = dp[i-1][2] + dp[i][2] = dp[i-2][1] + dp[i-2][2] + } + return dp[n][1] + dp[n][2] +} diff --git a/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go new file mode 100644 index 000000000..a4a4b3ad3 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go @@ -0,0 +1,21 @@ +// File: climbing_stairs_dfs.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Поиск */ +func dfs(i int) int { + // dp[1] и dp[2] уже известны, вернуть их + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfs(i-1) + dfs(i-2) + return count +} + +/* Подъем по лестнице: поиск */ +func climbingStairsDFS(n int) int { + return dfs(n) +} diff --git a/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go new file mode 100644 index 000000000..ba3e55d77 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go @@ -0,0 +1,32 @@ +// File: climbing_stairs_dfs_mem.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Поиск с мемоизацией */ +func dfsMem(i int, mem []int) int { + // dp[1] и dp[2] уже известны, вернуть их + if i == 1 || i == 2 { + return i + } + // Если запись dp[i] существует, сразу вернуть ее + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfsMem(i-1, mem) + dfsMem(i-2, mem) + // Сохранить dp[i] + mem[i] = count + return count +} + +/* Подъем по лестнице: поиск с мемоизацией */ +func climbingStairsDFSMem(n int) int { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + mem := make([]int, n+1) + for i := range mem { + mem[i] = -1 + } + return dfsMem(n, mem) +} diff --git a/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go new file mode 100644 index 000000000..1a41fa1e0 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go @@ -0,0 +1,35 @@ +// File: climbing_stairs_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Подъем по лестнице: динамическое программирование */ +func climbingStairsDP(n int) int { + if n == 1 || n == 2 { + return n + } + // Инициализация таблицы dp для хранения решений подзадач + dp := make([]int, n+1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1 + dp[2] = 2 + // Переход состояний: постепенное решение больших подзадач через меньшие + for i := 3; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +func climbingStairsDPComp(n int) int { + if n == 1 || n == 2 { + return n + } + a, b := 1, 2 + // Переход состояний: постепенное решение больших подзадач через меньшие + for i := 3; i <= n; i++ { + a, b = b, a+b + } + return b +} diff --git a/ru/codes/go/chapter_dynamic_programming/climbing_stairs_test.go b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_test.go new file mode 100644 index 000000000..e1dd0d05a --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/climbing_stairs_test.go @@ -0,0 +1,57 @@ +// File: climbing_stairs_test.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestClimbingStairsBacktrack(t *testing.T) { + n := 9 + res := climbingStairsBacktrack(n) + fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) +} + +func TestClimbingStairsDFS(t *testing.T) { + n := 9 + res := climbingStairsDFS(n) + fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) +} + +func TestClimbingStairsDFSMem(t *testing.T) { + n := 9 + res := climbingStairsDFSMem(n) + fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) +} + +func TestClimbingStairsDP(t *testing.T) { + n := 9 + res := climbingStairsDP(n) + fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) +} + +func TestClimbingStairsDPComp(t *testing.T) { + n := 9 + res := climbingStairsDPComp(n) + fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) +} + +func TestClimbingStairsConstraintDP(t *testing.T) { + n := 9 + res := climbingStairsConstraintDP(n) + fmt.Printf("Количество способов подняться по лестнице из %d ступеней: %d\n", n, res) +} + +func TestMinCostClimbingStairsDPComp(t *testing.T) { + cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} + fmt.Printf("Список стоимостей ступеней = %v\n", cost) + + res := minCostClimbingStairsDP(cost) + fmt.Printf("Минимальная стоимость подъема по лестнице = %d\n", res) + + res = minCostClimbingStairsDPComp(cost) + fmt.Printf("Минимальная стоимость подъема по лестнице = %d\n", res) +} diff --git a/ru/codes/go/chapter_dynamic_programming/coin_change.go b/ru/codes/go/chapter_dynamic_programming/coin_change.go new file mode 100644 index 000000000..5135a6de4 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/coin_change.go @@ -0,0 +1,66 @@ +// File: coin_change.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* Размен монет: динамическое программирование */ +func coinChangeDP(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // Инициализация таблицы dp + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // Переход состояний: первая строка и первый столбец + for a := 1; a <= amt; a++ { + dp[0][a] = max + } + // Переход состояний: остальные строки и столбцы + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i-1][a] + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) + } + } + } + if dp[n][amt] != max { + return dp[n][amt] + } + return -1 +} + +/* Размен монет: динамическое программирование */ +func coinChangeDPComp(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // Инициализация таблицы dp + dp := make([]int, amt+1) + for i := 1; i <= amt; i++ { + dp[i] = max + } + // Переход состояний + for i := 1; i <= n; i++ { + // Прямой обход + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) + } + } + } + if dp[amt] != max { + return dp[amt] + } + return -1 +} diff --git a/ru/codes/go/chapter_dynamic_programming/coin_change_ii.go b/ru/codes/go/chapter_dynamic_programming/coin_change_ii.go new file mode 100644 index 000000000..d56def0c5 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/coin_change_ii.go @@ -0,0 +1,54 @@ +// File: coin_change_ii.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Размен монет II: динамическое программирование */ +func coinChangeIIDP(coins []int, amt int) int { + n := len(coins) + // Инициализация таблицы dp + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // Инициализация первого столбца + for i := 0; i <= n; i++ { + dp[i][0] = 1 + } + // Переход состояний: остальные строки и столбцы + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i-1][a] + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] + } + } + } + return dp[n][amt] +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +func coinChangeIIDPComp(coins []int, amt int) int { + n := len(coins) + // Инициализация таблицы dp + dp := make([]int, amt+1) + dp[0] = 1 + // Переход состояний + for i := 1; i <= n; i++ { + // Прямой обход + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a-coins[i-1]] + } + } + } + return dp[amt] +} diff --git a/ru/codes/go/chapter_dynamic_programming/coin_change_test.go b/ru/codes/go/chapter_dynamic_programming/coin_change_test.go new file mode 100644 index 000000000..9a04a0983 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/coin_change_test.go @@ -0,0 +1,23 @@ +// File: coin_change_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestCoinChange(t *testing.T) { + coins := []int{1, 2, 5} + amt := 4 + + // Динамическое программирование + res := coinChangeDP(coins, amt) + fmt.Printf("Минимальное число монет для набора целевой суммы = %d\n", res) + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins, amt) + fmt.Printf("Минимальное число монет для набора целевой суммы = %d\n", res) +} diff --git a/ru/codes/go/chapter_dynamic_programming/edit_distance.go b/ru/codes/go/chapter_dynamic_programming/edit_distance.go new file mode 100644 index 000000000..54e89a57d --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/edit_distance.go @@ -0,0 +1,129 @@ +// File: edit_distance.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Редакционное расстояние: полный перебор */ +func editDistanceDFS(s string, t string, i int, j int) int { + // Если s и t пусты, вернуть 0 + if i == 0 && j == 0 { + return 0 + } + // Если s пусто, вернуть длину t + if i == 0 { + return j + } + // Если t пусто, вернуть длину s + if j == 0 { + return i + } + // Если два символа равны, сразу пропустить их + if s[i-1] == t[j-1] { + return editDistanceDFS(s, t, i-1, j-1) + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + insert := editDistanceDFS(s, t, i, j-1) + deleted := editDistanceDFS(s, t, i-1, j) + replace := editDistanceDFS(s, t, i-1, j-1) + // Вернуть минимальное число шагов редактирования + return MinInt(MinInt(insert, deleted), replace) + 1 +} + +/* Редакционное расстояние: поиск с мемоизацией */ +func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { + // Если s и t пусты, вернуть 0 + if i == 0 && j == 0 { + return 0 + } + // Если s пусто, вернуть длину t + if i == 0 { + return j + } + // Если t пусто, вернуть длину s + if j == 0 { + return i + } + // Если запись уже есть, сразу вернуть ее + if mem[i][j] != -1 { + return mem[i][j] + } + // Если два символа равны, сразу пропустить их + if s[i-1] == t[j-1] { + return editDistanceDFSMem(s, t, mem, i-1, j-1) + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + insert := editDistanceDFSMem(s, t, mem, i, j-1) + deleted := editDistanceDFSMem(s, t, mem, i-1, j) + replace := editDistanceDFSMem(s, t, mem, i-1, j-1) + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 + return mem[i][j] +} + +/* Редакционное расстояние: динамическое программирование */ +func editDistanceDP(s string, t string) int { + n := len(s) + m := len(t) + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, m+1) + } + // Переход состояний: первая строка и первый столбец + for i := 1; i <= n; i++ { + dp[i][0] = i + } + for j := 1; j <= m; j++ { + dp[0][j] = j + } + // Переход состояний: остальные строки и столбцы + for i := 1; i <= n; i++ { + for j := 1; j <= m; j++ { + if s[i-1] == t[j-1] { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i-1][j-1] + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 + } + } + } + return dp[n][m] +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +func editDistanceDPComp(s string, t string) int { + n := len(s) + m := len(t) + dp := make([]int, m+1) + // Переход состояний: первая строка + for j := 1; j <= m; j++ { + dp[j] = j + } + // Переход состояний: остальные строки + for i := 1; i <= n; i++ { + // Переход состояний: первый столбец + leftUp := dp[0] // Временно сохранить dp[i-1, j-1] + dp[0] = i + // Переход состояний: остальные столбцы + for j := 1; j <= m; j++ { + temp := dp[j] + if s[i-1] == t[j-1] { + // Если два символа равны, сразу пропустить их + dp[j] = leftUp + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 + } + leftUp = temp // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m] +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/ru/codes/go/chapter_dynamic_programming/edit_distance_test.go b/ru/codes/go/chapter_dynamic_programming/edit_distance_test.go new file mode 100644 index 000000000..efe94ef23 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/edit_distance_test.go @@ -0,0 +1,40 @@ +// File: edit_distance_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestEditDistanceDFS(test *testing.T) { + s := "bag" + t := "pack" + n := len(s) + m := len(t) + + // Полный перебор + res := editDistanceDFS(s, t, n, m) + fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) + + // Поиск с мемоизацией + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, m+1) + for j := 0; j <= m; j++ { + mem[i][j] = -1 + } + } + res = editDistanceDFSMem(s, t, mem, n, m) + fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) + + // Динамическое программирование + res = editDistanceDP(s, t) + fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t) + fmt.Printf("Чтобы преобразовать %s в %s, нужно минимум %d шагов\n", s, t, res) +} diff --git a/ru/codes/go/chapter_dynamic_programming/knapsack.go b/ru/codes/go/chapter_dynamic_programming/knapsack.go new file mode 100644 index 000000000..83590fbaf --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/knapsack.go @@ -0,0 +1,87 @@ +// File: knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* Рюкзак 0-1: полный перебор */ +func knapsackDFS(wgt, val []int, i, c int) int { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 || c == 0 { + return 0 + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i-1] > c { + return knapsackDFS(wgt, val, i-1, c) + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + no := knapsackDFS(wgt, val, i-1, c) + yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] + // Вернуть вариант с большей стоимостью из двух возможных + return int(math.Max(float64(no), float64(yes))) +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 || c == 0 { + return 0 + } + // Если запись уже есть, вернуть сразу + if mem[i][c] != -1 { + return mem[i][c] + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i-1] > c { + return knapsackDFSMem(wgt, val, mem, i-1, c) + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + no := knapsackDFSMem(wgt, val, mem, i-1, c) + yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] + // Вернуть вариант с большей стоимостью из двух возможных + mem[i][c] = int(math.Max(float64(no), float64(yes))) + return mem[i][c] +} + +/* Рюкзак 0-1: динамическое программирование */ +func knapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // Инициализация таблицы dp + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // Переход состояний + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i-1][c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +func knapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // Инициализация таблицы dp + dp := make([]int, cap+1) + // Переход состояний + for i := 1; i <= n; i++ { + // Обход в обратном порядке + for c := cap; c >= 1; c-- { + if wgt[i-1] <= c { + // Большее из двух решений: не брать или взять предмет i + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/ru/codes/go/chapter_dynamic_programming/knapsack_test.go b/ru/codes/go/chapter_dynamic_programming/knapsack_test.go new file mode 100644 index 000000000..8f6c07354 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/knapsack_test.go @@ -0,0 +1,54 @@ +// File: knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + c := 50 + n := len(wgt) + + // Полный перебор + res := knapsackDFS(wgt, val, n, c) + fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) + + // Поиск с мемоизацией + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, c+1) + for j := 0; j <= c; j++ { + mem[i][j] = -1 + } + } + res = knapsackDFSMem(wgt, val, mem, n, c) + fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) + + // Динамическое программирование + res = knapsackDP(wgt, val, c) + fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt, val, c) + fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) +} + +func TestUnboundedKnapsack(t *testing.T) { + wgt := []int{1, 2, 3} + val := []int{5, 11, 15} + c := 4 + + // Динамическое программирование + res := unboundedKnapsackDP(wgt, val, c) + fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(wgt, val, c) + fmt.Printf("Максимальная стоимость предметов без превышения вместимости рюкзака = %d\n", res) +} diff --git a/ru/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go b/ru/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go new file mode 100644 index 000000000..e401597ef --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go @@ -0,0 +1,52 @@ +// File: min_cost_climbing_stairs_dp.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +func minCostClimbingStairsDP(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // Инициализация таблицы dp для хранения решений подзадач + dp := make([]int, n+1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1] + dp[2] = cost[2] + // Переход состояний: постепенное решение больших подзадач через меньшие + for i := 3; i <= n; i++ { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i] + } + return dp[n] +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +func minCostClimbingStairsDPComp(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // Начальное состояние: заранее задать решения наименьших подзадач + a, b := cost[1], cost[2] + // Переход состояний: постепенное решение больших подзадач через меньшие + for i := 3; i <= n; i++ { + tmp := b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} diff --git a/ru/codes/go/chapter_dynamic_programming/min_path_sum.go b/ru/codes/go/chapter_dynamic_programming/min_path_sum.go new file mode 100644 index 000000000..b0d7c2812 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/min_path_sum.go @@ -0,0 +1,94 @@ +// File: min_path_sum.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* Минимальная сумма пути: полный перебор */ +func minPathSumDFS(grid [][]int, i, j int) int { + // Если это верхняя левая ячейка, завершить поиск + if i == 0 && j == 0 { + return grid[0][0] + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 || j < 0 { + return math.MaxInt + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + up := minPathSumDFS(grid, i-1, j) + left := minPathSumDFS(grid, i, j-1) + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return int(math.Min(float64(left), float64(up))) + grid[i][j] +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +func minPathSumDFSMem(grid, mem [][]int, i, j int) int { + // Если это верхняя левая ячейка, завершить поиск + if i == 0 && j == 0 { + return grid[0][0] + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 || j < 0 { + return math.MaxInt + } + // Если запись уже есть, вернуть сразу + if mem[i][j] != -1 { + return mem[i][j] + } + // Минимальная стоимость пути для левой и верхней ячеек + up := minPathSumDFSMem(grid, mem, i-1, j) + left := minPathSumDFSMem(grid, mem, i, j-1) + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] + return mem[i][j] +} + +/* Минимальная сумма пути: динамическое программирование */ +func minPathSumDP(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // Инициализация таблицы dp + dp := make([][]int, n) + for i := 0; i < n; i++ { + dp[i] = make([]int, m) + } + dp[0][0] = grid[0][0] + // Переход состояний: первая строка + for j := 1; j < m; j++ { + dp[0][j] = dp[0][j-1] + grid[0][j] + } + // Переход состояний: первый столбец + for i := 1; i < n; i++ { + dp[i][0] = dp[i-1][0] + grid[i][0] + } + // Переход состояний: остальные строки и столбцы + for i := 1; i < n; i++ { + for j := 1; j < m; j++ { + dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] + } + } + return dp[n-1][m-1] +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +func minPathSumDPComp(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // Инициализация таблицы dp + dp := make([]int, m) + // Переход состояний: первая строка + dp[0] = grid[0][0] + for j := 1; j < m; j++ { + dp[j] = dp[j-1] + grid[0][j] + } + // Переход состояний: остальные строки и столбцы + for i := 1; i < n; i++ { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0] + // Переход состояний: остальные столбцы + for j := 1; j < m; j++ { + dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] + } + } + return dp[m-1] +} diff --git a/ru/codes/go/chapter_dynamic_programming/min_path_sum_test.go b/ru/codes/go/chapter_dynamic_programming/min_path_sum_test.go new file mode 100644 index 000000000..c3f8f8c99 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/min_path_sum_test.go @@ -0,0 +1,43 @@ +// File: min_path_sum_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestMinPathSum(t *testing.T) { + grid := [][]int{ + {1, 3, 1, 5}, + {2, 2, 4, 2}, + {5, 3, 2, 1}, + {4, 3, 5, 2}, + } + n, m := len(grid), len(grid[0]) + + // Полный перебор + res := minPathSumDFS(grid, n-1, m-1) + fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) + + // Поиск с мемоизацией + mem := make([][]int, n) + for i := 0; i < n; i++ { + mem[i] = make([]int, m) + for j := 0; j < m; j++ { + mem[i][j] = -1 + } + } + res = minPathSumDFSMem(grid, mem, n-1, m-1) + fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) + + // Динамическое программирование + res = minPathSumDP(grid) + fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) + + // Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(grid) + fmt.Printf("Минимальная сумма пути из левого верхнего угла в правый нижний = %d\n", res) +} diff --git a/ru/codes/go/chapter_dynamic_programming/unbounded_knapsack.go b/ru/codes/go/chapter_dynamic_programming/unbounded_knapsack.go new file mode 100644 index 000000000..4f88abe63 --- /dev/null +++ b/ru/codes/go/chapter_dynamic_programming/unbounded_knapsack.go @@ -0,0 +1,50 @@ +// File: unbounded_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* Полный рюкзак: динамическое программирование */ +func unboundedKnapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // Инициализация таблицы dp + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // Переход состояний + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i-1][c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +func unboundedKnapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // Инициализация таблицы dp + dp := make([]int, cap+1) + // Переход состояний + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/ru/codes/go/chapter_graph/graph_adjacency_list.go b/ru/codes/go/chapter_graph/graph_adjacency_list.go new file mode 100644 index 000000000..e80a656df --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_adjacency_list.go @@ -0,0 +1,100 @@ +// File: graph_adjacency_list.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "strconv" + "strings" + + . "github.com/krahets/hello-algo/pkg" +) + +/* Класс неориентированного графа на основе списка смежности */ +type graphAdjList struct { + // Список смежности, где key — вершина, а value — все смежные ей вершины + adjList map[Vertex][]Vertex +} + +/* Конструктор */ +func newGraphAdjList(edges [][]Vertex) *graphAdjList { + g := &graphAdjList{ + adjList: make(map[Vertex][]Vertex), + } + // Добавить все вершины и ребра + for _, edge := range edges { + g.addVertex(edge[0]) + g.addVertex(edge[1]) + g.addEdge(edge[0], edge[1]) + } + return g +} + +/* Получить число вершин */ +func (g *graphAdjList) size() int { + return len(g.adjList) +} + +/* Добавление ребра */ +func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // Добавить ребро vet1 - vet2, добавив анонимную struct{} + g.adjList[vet1] = append(g.adjList[vet1], vet2) + g.adjList[vet2] = append(g.adjList[vet2], vet1) +} + +/* Удаление ребра */ +func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // Удалить ребро vet1 - vet2 + g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) + g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) +} + +/* Добавление вершины */ +func (g *graphAdjList) addVertex(vet Vertex) { + _, ok := g.adjList[vet] + if ok { + return + } + // Добавить новый список в список смежности + g.adjList[vet] = make([]Vertex, 0) +} + +/* Удаление вершины */ +func (g *graphAdjList) removeVertex(vet Vertex) { + _, ok := g.adjList[vet] + if !ok { + panic("error") + } + // Удалить из списка смежности список, соответствующий вершине vet + delete(g.adjList, vet) + // Обойти списки других вершин и удалить все ребра, содержащие vet + for v, list := range g.adjList { + g.adjList[v] = DeleteSliceElms(list, vet) + } +} + +/* Вывести список смежности */ +func (g *graphAdjList) print() { + var builder strings.Builder + fmt.Printf("Список смежности = \n") + for k, v := range g.adjList { + builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") + for _, vet := range v { + builder.WriteString(strconv.Itoa(vet.Val) + " ") + } + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/ru/codes/go/chapter_graph/graph_adjacency_list_test.go b/ru/codes/go/chapter_graph/graph_adjacency_list_test.go new file mode 100644 index 000000000..372d28e2b --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_adjacency_list_test.go @@ -0,0 +1,45 @@ +// File: graph_adjacency_list_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphAdjList(t *testing.T) { + /* Инициализация неориентированного графа */ + v := ValsToVets([]int{1, 3, 2, 5, 4}) + edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} + graph := newGraphAdjList(edges) + fmt.Println("Граф после инициализации:") + graph.print() + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(v[0], v[2]) + fmt.Println("\nГраф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(v[0], v[1]) + fmt.Println("\nГраф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + v5 := NewVertex(6) + graph.addVertex(v5) + fmt.Println("\nГраф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(v[1]) + fmt.Println("\nГраф после удаления вершины 3") + graph.print() +} diff --git a/ru/codes/go/chapter_graph/graph_adjacency_matrix.go b/ru/codes/go/chapter_graph/graph_adjacency_matrix.go new file mode 100644 index 000000000..c550f3401 --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_adjacency_matrix.go @@ -0,0 +1,102 @@ +// File: graph_adjacency_matrix.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import "fmt" + +/* Класс неориентированного графа на основе матрицы смежности */ +type graphAdjMat struct { + // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + vertices []int + // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + adjMat [][]int +} + +/* Конструктор */ +func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { + // Добавление вершины + n := len(vertices) + adjMat := make([][]int, n) + for i := range adjMat { + adjMat[i] = make([]int, n) + } + // Инициализировать граф + g := &graphAdjMat{ + vertices: vertices, + adjMat: adjMat, + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for i := range edges { + g.addEdge(edges[i][0], edges[i][1]) + } + return g +} + +/* Получить число вершин */ +func (g *graphAdjMat) size() int { + return len(g.vertices) +} + +/* Добавление вершины */ +func (g *graphAdjMat) addVertex(val int) { + n := g.size() + // Добавить значение новой вершины в список вершин + g.vertices = append(g.vertices, val) + // Добавить строку в матрицу смежности + newRow := make([]int, n) + g.adjMat = append(g.adjMat, newRow) + // Добавить столбец в матрицу смежности + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i], 0) + } +} + +/* Удаление вершины */ +func (g *graphAdjMat) removeVertex(index int) { + if index >= g.size() { + return + } + // Удалить вершину с индексом index из списка вершин + g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) + // Удалить строку с индексом index из матрицы смежности + g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) + // Удалить столбец с индексом index из матрицы смежности + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) + } +} + +/* Добавление ребра */ +// Параметры i и j соответствуют индексам элементов vertices +func (g *graphAdjMat) addEdge(i, j int) { + // Обработка выхода индекса за границы и случая равенства + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + g.adjMat[i][j] = 1 + g.adjMat[j][i] = 1 +} + +/* Удаление ребра */ +// Параметры i и j соответствуют индексам элементов vertices +func (g *graphAdjMat) removeEdge(i, j int) { + // Обработка выхода индекса за границы и случая равенства + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + g.adjMat[i][j] = 0 + g.adjMat[j][i] = 0 +} + +/* Вывести матрицу смежности */ +func (g *graphAdjMat) print() { + fmt.Printf("\tСписок вершин = %v\n", g.vertices) + fmt.Printf("\tМатрица смежности = \n") + for i := range g.adjMat { + fmt.Printf("\t\t\t%v\n", g.adjMat[i]) + } +} diff --git a/ru/codes/go/chapter_graph/graph_adjacency_matrix_test.go b/ru/codes/go/chapter_graph/graph_adjacency_matrix_test.go new file mode 100644 index 000000000..a9916d402 --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_adjacency_matrix_test.go @@ -0,0 +1,43 @@ +// File: graph_adjacency_matrix_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" +) + +func TestGraphAdjMat(t *testing.T) { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + vertices := []int{1, 3, 2, 5, 4} + edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} + graph := newGraphAdjMat(vertices, edges) + fmt.Println("Граф после инициализации:") + graph.print() + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.addEdge(0, 2) + fmt.Println("Граф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.removeEdge(0, 1) + fmt.Println("Граф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + graph.addVertex(6) + fmt.Println("Граф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.removeVertex(1) + fmt.Println("Граф после удаления вершины 3") + graph.print() +} diff --git a/ru/codes/go/chapter_graph/graph_bfs.go b/ru/codes/go/chapter_graph/graph_bfs.go new file mode 100644 index 000000000..4d39b9f01 --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_bfs.go @@ -0,0 +1,41 @@ +// File: graph_bfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { + // Последовательность обхода вершин + res := make([]Vertex, 0) + // Хеш-множество для хранения уже посещенных вершин + visited := make(map[Vertex]struct{}) + visited[startVet] = struct{}{} + // Очередь используется для реализации BFS, срез используется для имитации очереди + queue := make([]Vertex, 0) + queue = append(queue, startVet) + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + for len(queue) > 0 { + // Извлечь головную вершину из очереди + vet := queue[0] + queue = queue[1:] + // Отметить посещенную вершину + res = append(res, vet) + // Обойти все смежные вершины данной вершины + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // Помещать в очередь только непосещенные вершины + if !isExist { + queue = append(queue, adjVet) + visited[adjVet] = struct{}{} + } + } + } + // Вернуть последовательность обхода вершин + return res +} diff --git a/ru/codes/go/chapter_graph/graph_bfs_test.go b/ru/codes/go/chapter_graph/graph_bfs_test.go new file mode 100644 index 000000000..276fc51c6 --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_bfs_test.go @@ -0,0 +1,29 @@ +// File: graph_bfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphBFS(t *testing.T) { + /* Инициализация неориентированного графа */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, + {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, + {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} + graph := newGraphAdjList(edges) + fmt.Println("Граф после инициализации:") + graph.print() + + /* Обход в ширину */ + res := graphBFS(graph, vets[0]) + fmt.Println("Последовательность вершин при обходе в ширину (BFS):") + PrintSlice(VetsToVals(res)) +} diff --git a/ru/codes/go/chapter_graph/graph_dfs.go b/ru/codes/go/chapter_graph/graph_dfs.go new file mode 100644 index 000000000..4a3dd8d53 --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_dfs.go @@ -0,0 +1,36 @@ +// File: graph_dfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Вспомогательная функция обхода в глубину */ +func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { + // Операция append возвращает новую ссылку, поэтому исходную ссылку нужно заново присвоить новому срезу + *res = append(*res, vet) + visited[vet] = struct{}{} + // Обойти все смежные вершины данной вершины + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // Рекурсивно обходить смежные вершины + if !isExist { + dfs(g, visited, res, adjVet) + } + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { + // Последовательность обхода вершин + res := make([]Vertex, 0) + // Хеш-множество для хранения уже посещенных вершин + visited := make(map[Vertex]struct{}) + dfs(g, visited, &res, startVet) + // Вернуть последовательность обхода вершин + return res +} diff --git a/ru/codes/go/chapter_graph/graph_dfs_test.go b/ru/codes/go/chapter_graph/graph_dfs_test.go new file mode 100644 index 000000000..1c52162d1 --- /dev/null +++ b/ru/codes/go/chapter_graph/graph_dfs_test.go @@ -0,0 +1,28 @@ +// File: graph_dfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphDFS(t *testing.T) { + /* Инициализация неориентированного графа */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, + {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} + graph := newGraphAdjList(edges) + fmt.Println("Граф после инициализации:") + graph.print() + + /* Обход в глубину */ + res := graphDFS(graph, vets[0]) + fmt.Println("Последовательность вершин при обходе в глубину (DFS):") + PrintSlice(VetsToVals(res)) +} diff --git a/ru/codes/go/chapter_greedy/coin_change_greedy.go b/ru/codes/go/chapter_greedy/coin_change_greedy.go new file mode 100644 index 000000000..2eb023681 --- /dev/null +++ b/ru/codes/go/chapter_greedy/coin_change_greedy.go @@ -0,0 +1,27 @@ +// File: coin_change_greedy.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +/* Размен монет: жадный алгоритм */ +func coinChangeGreedy(coins []int, amt int) int { + // Предположить, что список coins упорядочен + i := len(coins) - 1 + count := 0 + // Циклически выполнять жадный выбор, пока не останется суммы + for amt > 0 { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + for i > 0 && coins[i] > amt { + i-- + } + // Выбрать coins[i] + amt -= coins[i] + count++ + } + // Если допустимое решение не найдено, вернуть -1 + if amt != 0 { + return -1 + } + return count +} diff --git a/ru/codes/go/chapter_greedy/coin_change_greedy_test.go b/ru/codes/go/chapter_greedy/coin_change_greedy_test.go new file mode 100644 index 000000000..813eb700d --- /dev/null +++ b/ru/codes/go/chapter_greedy/coin_change_greedy_test.go @@ -0,0 +1,35 @@ +// File: coin_change_greedy_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestCoinChangeGreedy(t *testing.T) { + // Жадный подход: гарантирует нахождение глобально оптимального решения + coins := []int{1, 5, 10, 20, 50, 100} + amt := 186 + res := coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("Минимальное число монет для набора суммы %d = %d\n", amt, res) + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = []int{1, 20, 50} + amt = 60 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("Минимальное число монет для набора суммы %d = %d\n", amt, res) + fmt.Println("На самом деле минимум равен 3: 20 + 20 + 20") + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = []int{1, 49, 50} + amt = 98 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("Минимальное число монет для набора суммы %d = %d\n", amt, res) + fmt.Println("На самом деле минимум равен 2: 49 + 49") +} diff --git a/ru/codes/go/chapter_greedy/fractional_knapsack.go b/ru/codes/go/chapter_greedy/fractional_knapsack.go new file mode 100644 index 000000000..3612acbdc --- /dev/null +++ b/ru/codes/go/chapter_greedy/fractional_knapsack.go @@ -0,0 +1,41 @@ +// File: fractional_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "sort" + +/* Предмет */ +type Item struct { + w int // Вес предмета + v int // Стоимость предмета +} + +/* Дробный рюкзак: жадный алгоритм */ +func fractionalKnapsack(wgt []int, val []int, cap int) float64 { + // Создать список предметов с двумя свойствами: вес и стоимость + items := make([]Item, len(wgt)) + for i := 0; i < len(wgt); i++ { + items[i] = Item{wgt[i], val[i]} + } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + sort.Slice(items, func(i, j int) bool { + return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) + }) + // Циклический жадный выбор + res := 0.0 + for _, item := range items { + if item.w <= cap { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += float64(item.v) + cap -= item.w + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += float64(item.v) / float64(item.w) * float64(cap) + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break + } + } + return res +} diff --git a/ru/codes/go/chapter_greedy/fractional_knapsack_test.go b/ru/codes/go/chapter_greedy/fractional_knapsack_test.go new file mode 100644 index 000000000..cbe052592 --- /dev/null +++ b/ru/codes/go/chapter_greedy/fractional_knapsack_test.go @@ -0,0 +1,20 @@ +// File: fractional_knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestFractionalKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + capacity := 50 + + // Жадный алгоритм + res := fractionalKnapsack(wgt, val, capacity) + fmt.Println("Максимальная стоимость предметов без превышения вместимости рюкзака =", res) +} diff --git a/ru/codes/go/chapter_greedy/max_capacity.go b/ru/codes/go/chapter_greedy/max_capacity.go new file mode 100644 index 000000000..7319ce81f --- /dev/null +++ b/ru/codes/go/chapter_greedy/max_capacity.go @@ -0,0 +1,28 @@ +// File: max_capacity.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* Максимальная вместимость: жадный алгоритм */ +func maxCapacity(ht []int) int { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + i, j := 0, len(ht)-1 + // Начальная максимальная вместимость равна 0 + res := 0 + // Выполнять жадный выбор в цикле, пока две доски не встретятся + for i < j { + // Обновить максимальную вместимость + capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) + res = int(math.Max(float64(res), float64(capacity))) + // Сдвигать внутрь более короткую сторону + if ht[i] < ht[j] { + i++ + } else { + j-- + } + } + return res +} diff --git a/ru/codes/go/chapter_greedy/max_capacity_test.go b/ru/codes/go/chapter_greedy/max_capacity_test.go new file mode 100644 index 000000000..c4b700990 --- /dev/null +++ b/ru/codes/go/chapter_greedy/max_capacity_test.go @@ -0,0 +1,18 @@ +// File: max_capacity_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxCapacity(t *testing.T) { + ht := []int{3, 8, 5, 2, 7, 7, 3, 4} + + // Жадный алгоритм + res := maxCapacity(ht) + fmt.Println("Максимальная вместимость =", res) +} diff --git a/ru/codes/go/chapter_greedy/max_product_cutting.go b/ru/codes/go/chapter_greedy/max_product_cutting.go new file mode 100644 index 000000000..5762c2efb --- /dev/null +++ b/ru/codes/go/chapter_greedy/max_product_cutting.go @@ -0,0 +1,28 @@ +// File: max_product_cutting.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* Максимальное произведение разрезания: жадный алгоритм */ +func maxProductCutting(n int) int { + // Когда n <= 3, обязательно нужно выделить одну 1 + if n <= 3 { + return 1 * (n - 1) + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + a := n / 3 + b := n % 3 + if b == 1 { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return int(math.Pow(3, float64(a-1))) * 2 * 2 + } + if b == 2 { + // Если остаток равен 2, ничего не делать + return int(math.Pow(3, float64(a))) * 2 + } + // Если остаток равен 0, ничего не делать + return int(math.Pow(3, float64(a))) +} diff --git a/ru/codes/go/chapter_greedy/max_product_cutting_test.go b/ru/codes/go/chapter_greedy/max_product_cutting_test.go new file mode 100644 index 000000000..cbe5bbb64 --- /dev/null +++ b/ru/codes/go/chapter_greedy/max_product_cutting_test.go @@ -0,0 +1,17 @@ +// File: max_product_cutting_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxProductCutting(t *testing.T) { + n := 58 + // Жадный алгоритм + res := maxProductCutting(n) + fmt.Println("Максимальное произведение после разрезания =", res) +} diff --git a/ru/codes/go/chapter_hashing/array_hash_map.go b/ru/codes/go/chapter_hashing/array_hash_map.go new file mode 100644 index 000000000..66b109ca4 --- /dev/null +++ b/ru/codes/go/chapter_hashing/array_hash_map.go @@ -0,0 +1,97 @@ +// File: array_hash_map.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import "fmt" + +/* Пара ключ-значение */ +type pair struct { + key int + val string +} + +/* Хеш-таблица на основе массива */ +type arrayHashMap struct { + buckets []*pair +} + +/* Инициализация хеш-таблицы */ +func newArrayHashMap() *arrayHashMap { + // Инициализировать массив, содержащий 100 корзин + buckets := make([]*pair, 100) + return &arrayHashMap{buckets: buckets} +} + +/* Хеш-функция */ +func (a *arrayHashMap) hashFunc(key int) int { + index := key % 100 + return index +} + +/* Операция поиска */ +func (a *arrayHashMap) get(key int) string { + index := a.hashFunc(key) + pair := a.buckets[index] + if pair == nil { + return "Not Found" + } + return pair.val +} + +/* Операция добавления */ +func (a *arrayHashMap) put(key int, val string) { + pair := &pair{key: key, val: val} + index := a.hashFunc(key) + a.buckets[index] = pair +} + +/* Операция удаления */ +func (a *arrayHashMap) remove(key int) { + index := a.hashFunc(key) + // Присвоить nil, что означает удаление + a.buckets[index] = nil +} + +/* Получить все ключи */ +func (a *arrayHashMap) pairSet() []*pair { + var pairs []*pair + for _, pair := range a.buckets { + if pair != nil { + pairs = append(pairs, pair) + } + } + return pairs +} + +/* Получить все ключи */ +func (a *arrayHashMap) keySet() []int { + var keys []int + for _, pair := range a.buckets { + if pair != nil { + keys = append(keys, pair.key) + } + } + return keys +} + +/* Получить все значения */ +func (a *arrayHashMap) valueSet() []string { + var values []string + for _, pair := range a.buckets { + if pair != nil { + values = append(values, pair.val) + } + } + return values +} + +/* Вывести хеш-таблицу */ +func (a *arrayHashMap) print() { + for _, pair := range a.buckets { + if pair != nil { + fmt.Println(pair.key, "->", pair.val) + } + } +} diff --git a/ru/codes/go/chapter_hashing/array_hash_map_test.go b/ru/codes/go/chapter_hashing/array_hash_map_test.go new file mode 100644 index 000000000..88cd3e5a2 --- /dev/null +++ b/ru/codes/go/chapter_hashing/array_hash_map_test.go @@ -0,0 +1,52 @@ +// File: array_hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestArrayHashMap(t *testing.T) { + /* Инициализация хеш-таблицы */ + hmap := newArrayHashMap() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + hmap.put(12836, "Сяо Ха") + hmap.put(15937, "Сяо Ло") + hmap.put(16750, "Сяо Суань") + hmap.put(13276, "Сяо Фа") + hmap.put(10583, "Сяо Я") + fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + name := hmap.get(15937) + fmt.Println("\nДля номера 15937 найдено имя " + name) + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + hmap.remove(10583) + fmt.Println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() + + /* Обход хеш-таблицы */ + fmt.Println("\nОтдельный обход пар ключ-значение") + for _, kv := range hmap.pairSet() { + fmt.Println(kv.key, " -> ", kv.val) + } + + fmt.Println("\nОтдельный обход ключей") + for _, key := range hmap.keySet() { + fmt.Println(key) + } + + fmt.Println("\nОтдельный обход значений") + for _, val := range hmap.valueSet() { + fmt.Println(val) + } +} diff --git a/ru/codes/go/chapter_hashing/hash_collision_test.go b/ru/codes/go/chapter_hashing/hash_collision_test.go new file mode 100644 index 000000000..156f85e31 --- /dev/null +++ b/ru/codes/go/chapter_hashing/hash_collision_test.go @@ -0,0 +1,62 @@ +// File: hash_collision_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestHashMapChaining(t *testing.T) { + /* Инициализация хеш-таблицы */ + hmap := newHashMapChaining() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + hmap.put(12836, "Сяо Ха") + hmap.put(15937, "Сяо Ло") + hmap.put(16750, "Сяо Суань") + hmap.put(13276, "Сяо Фа") + hmap.put(10583, "Сяо Я") + fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + name := hmap.get(15937) + fmt.Println("\nДля номера 15937 найдено имя", name) + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + hmap.remove(12836) + fmt.Println("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() +} + +func TestHashMapOpenAddressing(t *testing.T) { + /* Инициализация хеш-таблицы */ + hmap := newHashMapOpenAddressing() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + hmap.put(12836, "Сяо Ха") + hmap.put(15937, "Сяо Ло") + hmap.put(16750, "Сяо Суань") + hmap.put(13276, "Сяо Фа") + hmap.put(10583, "Сяо Я") + fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + name := hmap.get(13276) + fmt.Println("\nДля номера 13276 найдено имя", name) + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + hmap.remove(16750) + fmt.Println("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() +} diff --git a/ru/codes/go/chapter_hashing/hash_map_chaining.go b/ru/codes/go/chapter_hashing/hash_map_chaining.go new file mode 100644 index 000000000..ebfcff19f --- /dev/null +++ b/ru/codes/go/chapter_hashing/hash_map_chaining.go @@ -0,0 +1,134 @@ +// File: hash_map_chaining.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "strings" +) + +/* Хеш-таблица с цепочками */ +type hashMapChaining struct { + size int // Число пар ключ-значение + capacity int // Вместимость хеш-таблицы + loadThres float64 // Порог коэффициента загрузки для запуска расширения + extendRatio int // Коэффициент расширения + buckets [][]pair // Массив корзин +} + +/* Конструктор */ +func newHashMapChaining() *hashMapChaining { + buckets := make([][]pair, 4) + for i := 0; i < 4; i++ { + buckets[i] = make([]pair, 0) + } + return &hashMapChaining{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: buckets, + } +} + +/* Хеш-функция */ +func (m *hashMapChaining) hashFunc(key int) int { + return key % m.capacity +} + +/* Коэффициент загрузки */ +func (m *hashMapChaining) loadFactor() float64 { + return float64(m.size) / float64(m.capacity) +} + +/* Операция поиска */ +func (m *hashMapChaining) get(key int) string { + idx := m.hashFunc(key) + bucket := m.buckets[idx] + // Обойти корзину; если найден key, вернуть соответствующее val + for _, p := range bucket { + if p.key == key { + return p.val + } + } + // Если key не найден, вернуть пустую строку + return "" +} + +/* Операция добавления */ +func (m *hashMapChaining) put(key int, val string) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if m.loadFactor() > m.loadThres { + m.extend() + } + idx := m.hashFunc(key) + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for i := range m.buckets[idx] { + if m.buckets[idx][i].key == key { + m.buckets[idx][i].val = val + return + } + } + // Если такого key нет, добавить пару ключ-значение в конец + p := pair{ + key: key, + val: val, + } + m.buckets[idx] = append(m.buckets[idx], p) + m.size += 1 +} + +/* Операция удаления */ +func (m *hashMapChaining) remove(key int) { + idx := m.hashFunc(key) + // Обойти корзину и удалить из нее пару ключ-значение + for i, p := range m.buckets[idx] { + if p.key == key { + // Удаление из среза + m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) + m.size -= 1 + break + } + } +} + +/* Расширить хеш-таблицу */ +func (m *hashMapChaining) extend() { + // Временно сохранить исходную хеш-таблицу + tmpBuckets := make([][]pair, len(m.buckets)) + for i := 0; i < len(m.buckets); i++ { + tmpBuckets[i] = make([]pair, len(m.buckets[i])) + copy(tmpBuckets[i], m.buckets[i]) + } + // Инициализация новой хеш-таблицы после расширения + m.capacity *= m.extendRatio + m.buckets = make([][]pair, m.capacity) + for i := 0; i < m.capacity; i++ { + m.buckets[i] = make([]pair, 0) + } + m.size = 0 + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for _, bucket := range tmpBuckets { + for _, p := range bucket { + m.put(p.key, p.val) + } + } +} + +/* Вывести хеш-таблицу */ +func (m *hashMapChaining) print() { + var builder strings.Builder + + for _, bucket := range m.buckets { + builder.WriteString("[") + for _, p := range bucket { + builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") + } + builder.WriteString("]") + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/ru/codes/go/chapter_hashing/hash_map_open_addressing.go b/ru/codes/go/chapter_hashing/hash_map_open_addressing.go new file mode 100644 index 000000000..e1c078d3a --- /dev/null +++ b/ru/codes/go/chapter_hashing/hash_map_open_addressing.go @@ -0,0 +1,126 @@ +// File: hash_map_open_addressing.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" +) + +/* Хеш-таблица с открытой адресацией */ +type hashMapOpenAddressing struct { + size int // Число пар ключ-значение + capacity int // Вместимость хеш-таблицы + loadThres float64 // Порог коэффициента загрузки для запуска расширения + extendRatio int // Коэффициент расширения + buckets []*pair // Массив корзин + TOMBSTONE *pair // Удалить метку +} + +/* Конструктор */ +func newHashMapOpenAddressing() *hashMapOpenAddressing { + return &hashMapOpenAddressing{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: make([]*pair, 4), + TOMBSTONE: &pair{-1, "-1"}, + } +} + +/* Хеш-функция */ +func (h *hashMapOpenAddressing) hashFunc(key int) int { + return key % h.capacity // Вычислить хеш-значение по ключу +} + +/* Коэффициент загрузки */ +func (h *hashMapOpenAddressing) loadFactor() float64 { + return float64(h.size) / float64(h.capacity) // Вычислить текущий коэффициент загрузки +} + +/* Найти индекс корзины, соответствующий key */ +func (h *hashMapOpenAddressing) findBucket(key int) int { + index := h.hashFunc(key) // Получить начальный индекс + firstTombstone := -1 // Запомнить положение первого TOMBSTONE + for h.buckets[index] != nil { + if h.buckets[index].key == key { + if firstTombstone != -1 { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + h.buckets[firstTombstone] = h.buckets[index] + h.buckets[index] = h.TOMBSTONE + return firstTombstone // Вернуть индекс корзины после перемещения + } + return index // Вернуть найденный индекс + } + if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { + firstTombstone = index // Запомнить положение первой метки удаления + } + index = (index + 1) % h.capacity // Линейное пробирование: при выходе за хвост вернуться к началу + } + // Если key не существует, вернуть индекс точки добавления + if firstTombstone != -1 { + return firstTombstone + } + return index +} + +/* Операция поиска */ +func (h *hashMapOpenAddressing) get(key int) string { + index := h.findBucket(key) // Найти индекс корзины, соответствующий key + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + return h.buckets[index].val // Если пара ключ-значение найдена, вернуть соответствующее val + } + return "" // Если пара ключ-значение не существует, вернуть "" +} + +/* Операция добавления */ +func (h *hashMapOpenAddressing) put(key int, val string) { + if h.loadFactor() > h.loadThres { + h.extend() // Когда коэффициент загрузки превышает порог, выполнить расширение + } + index := h.findBucket(key) // Найти индекс корзины, соответствующий key + if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { + h.buckets[index] = &pair{key, val} // Если пары ключ-значение нет, добавить ее + h.size++ + } else { + h.buckets[index].val = val // Если пара ключ-значение найдена, перезаписать val + } +} + +/* Операция удаления */ +func (h *hashMapOpenAddressing) remove(key int) { + index := h.findBucket(key) // Найти индекс корзины, соответствующий key + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + h.buckets[index] = h.TOMBSTONE // Если пара ключ-значение найдена, заменить ее меткой удаления + h.size-- + } +} + +/* Расширить хеш-таблицу */ +func (h *hashMapOpenAddressing) extend() { + oldBuckets := h.buckets // Временно сохранить исходную хеш-таблицу + h.capacity *= h.extendRatio // Обновить емкость + h.buckets = make([]*pair, h.capacity) // Инициализация новой хеш-таблицы после расширения + h.size = 0 // Сбросить размер + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for _, pair := range oldBuckets { + if pair != nil && pair != h.TOMBSTONE { + h.put(pair.key, pair.val) + } + } +} + +/* Вывести хеш-таблицу */ +func (h *hashMapOpenAddressing) print() { + for _, pair := range h.buckets { + if pair == nil { + fmt.Println("nil") + } else if pair == h.TOMBSTONE { + fmt.Println("TOMBSTONE") + } else { + fmt.Printf("%d -> %s\n", pair.key, pair.val) + } + } +} diff --git a/ru/codes/go/chapter_hashing/hash_map_test.go b/ru/codes/go/chapter_hashing/hash_map_test.go new file mode 100644 index 000000000..7cbb56c7e --- /dev/null +++ b/ru/codes/go/chapter_hashing/hash_map_test.go @@ -0,0 +1,74 @@ +// File: hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashMap(t *testing.T) { + /* Инициализация хеш-таблицы */ + hmap := make(map[int]string) + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + hmap[12836] = "Сяо Ха" + hmap[15937] = "Сяо Ло" + hmap[16750] = "Сяо Суань" + hmap[13276] = "Сяо Фа" + hmap[10583] = "Сяо Я" + fmt.Println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + PrintMap(hmap) + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + name := hmap[15937] + fmt.Println("\nДля номера 15937 найдено имя", name) + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + delete(hmap, 10583) + fmt.Println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + PrintMap(hmap) + + /* Обход хеш-таблицы */ + // Перебрать пары ключ-значение key->value + fmt.Println("\nОтдельный обход пар ключ-значение") + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // Отдельно обходить ключи key + fmt.Println("\nОтдельный обход ключей") + for key := range hmap { + fmt.Println(key) + } + // Отдельно обходить значения value + fmt.Println("\nОтдельный обход значений") + for _, value := range hmap { + fmt.Println(value) + } +} + +func TestSimpleHash(t *testing.T) { + var hash int + + key := "Hello Algo" + + hash = addHash(key) + fmt.Println("Хеш-сумма сложением = " + strconv.Itoa(hash)) + + hash = mulHash(key) + fmt.Println("Хеш-сумма умножением = " + strconv.Itoa(hash)) + + hash = xorHash(key) + fmt.Println("Хеш-сумма XOR = " + strconv.Itoa(hash)) + + hash = rotHash(key) + fmt.Println("Хеш-сумма с циклическим сдвигом = " + strconv.Itoa(hash)) +} diff --git a/ru/codes/go/chapter_hashing/simple_hash.go b/ru/codes/go/chapter_hashing/simple_hash.go new file mode 100644 index 000000000..e9567ccb4 --- /dev/null +++ b/ru/codes/go/chapter_hashing/simple_hash.go @@ -0,0 +1,55 @@ +// File: simple_hash.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import "fmt" + +/* Аддитивное хеширование */ +func addHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (hash + int64(b)) % modulus + } + return int(hash) +} + +/* Мультипликативное хеширование */ +func mulHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (31*hash + int64(b)) % modulus + } + return int(hash) +} + +/* XOR-хеширование */ +func xorHash(key string) int { + hash := 0 + modulus := 1000000007 + for _, b := range []byte(key) { + fmt.Println(int(b)) + hash ^= int(b) + hash = (31*hash + int(b)) % modulus + } + return hash & modulus +} + +/* Хеширование с циклическим сдвигом */ +func rotHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus + } + return int(hash) +} diff --git a/ru/codes/go/chapter_heap/heap.go b/ru/codes/go/chapter_heap/heap.go new file mode 100644 index 000000000..b5ef628e0 --- /dev/null +++ b/ru/codes/go/chapter_heap/heap.go @@ -0,0 +1,45 @@ +// File: heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +// В Go можно построить максимальную кучу для целых чисел, реализовав heap.Interface +// Для реализации heap.Interface одновременно требуется реализовать sort.Interface +type intHeap []any + +// Функция Push интерфейса heap.Interface, реализующая добавление элемента в кучу +func (h *intHeap) Push(x any) { + // Push и Pop используют pointer receiver в качестве параметра + // Потому что они не только изменяют содержимое среза, но и меняют его длину. + *h = append(*h, x.(int)) +} + +// Функция Pop интерфейса heap.Interface, реализующая извлечение элемента с вершины кучи +func (h *intHeap) Pop() any { + // Элемент, который нужно удалить из кучи, хранится в конце + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Функция Len интерфейса sort.Interface +func (h *intHeap) Len() int { + return len(*h) +} + +// Функция Less интерфейса sort.Interface +func (h *intHeap) Less(i, j int) bool { + // При реализации минимальной кучи нужно изменить знак сравнения на «меньше» + return (*h)[i].(int) > (*h)[j].(int) +} + +// Функция Swap интерфейса sort.Interface +func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] +} + +// Top: получить элемент на вершине кучи +func (h *intHeap) Top() any { + return (*h)[0] +} diff --git a/ru/codes/go/chapter_heap/heap_test.go b/ru/codes/go/chapter_heap/heap_test.go new file mode 100644 index 000000000..e96783a74 --- /dev/null +++ b/ru/codes/go/chapter_heap/heap_test.go @@ -0,0 +1,101 @@ +// File: heap_test.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "container/heap" + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func testPush(h *intHeap, val int) { + // Вызвать функции heap.Interface для добавления элемента + heap.Push(h, val) + fmt.Printf("\nПосле добавления элемента %d в кучу\n", val) + PrintHeap(*h) +} + +func testPop(h *intHeap) { + // Вызвать функции heap.Interface для удаления элемента + val := heap.Pop(h) + fmt.Printf("\nПосле извлечения элемента вершины кучи %d\n", val) + PrintHeap(*h) +} + +func TestHeap(t *testing.T) { + /* Инициализация кучи */ + // Инициализация максимальной кучи + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* Добавление элемента в кучу */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* Получение элемента с вершины кучи */ + top := maxHeap.Top() + fmt.Printf("Элемент на вершине кучи = %d\n", top) + + /* Извлечение элемента с вершины кучи */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* Получение размера кучи */ + size := len(*maxHeap) + fmt.Printf("Количество элементов в куче = %d\n", size) + + /* Проверка, пуста ли куча */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("Пуста ли куча: %t\n", isEmpty) +} + +func TestMyHeap(t *testing.T) { + /* Инициализация кучи */ + // Инициализация максимальной кучи + maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) + fmt.Printf("После построения кучи из входного массива\n") + maxHeap.print() + + /* Получение элемента с вершины кучи */ + peek := maxHeap.peek() + fmt.Printf("\nЭлемент на вершине кучи = %d\n", peek) + + /* Добавление элемента в кучу */ + val := 7 + maxHeap.push(val) + fmt.Printf("\nПосле добавления элемента %d в кучу\n", val) + maxHeap.print() + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop() + fmt.Printf("\nПосле извлечения элемента вершины кучи %d\n", peek) + maxHeap.print() + + /* Получение размера кучи */ + size := maxHeap.size() + fmt.Printf("\nКоличество элементов в куче = %d\n", size) + + /* Проверка, пуста ли куча */ + isEmpty := maxHeap.isEmpty() + fmt.Printf("\nПуста ли куча: %t\n", isEmpty) +} + +func TestTopKHeap(t *testing.T) { + /* Инициализация кучи */ + // Инициализация максимальной кучи + nums := []int{1, 7, 6, 3, 2} + k := 3 + res := topKHeap(nums, k) + fmt.Printf("Наибольшие " + strconv.Itoa(k) + " элементов") + PrintHeap(*res) +} diff --git a/ru/codes/go/chapter_heap/my_heap.go b/ru/codes/go/chapter_heap/my_heap.go new file mode 100644 index 000000000..739afed55 --- /dev/null +++ b/ru/codes/go/chapter_heap/my_heap.go @@ -0,0 +1,140 @@ +// File: my_heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "fmt" + + . "github.com/krahets/hello-algo/pkg" +) + +type maxHeap struct { + // Использовать срез вместо массива, чтобы не учитывать проблему расширения + data []any +} + +/* Конструктор, создающий пустую кучу */ +func newHeap() *maxHeap { + return &maxHeap{ + data: make([]any, 0), + } +} + +/* Конструктор, строящий кучу по срезу */ +func newMaxHeap(nums []any) *maxHeap { + // Добавить элементы списка в кучу без изменений + h := &maxHeap{data: nums} + for i := h.parent(len(h.data) - 1); i >= 0; i-- { + // Выполнить heapify для всех узлов, кроме листовых + h.siftDown(i) + } + return h +} + +/* Получить индекс левого дочернего узла */ +func (h *maxHeap) left(i int) int { + return 2*i + 1 +} + +/* Получить индекс правого дочернего узла */ +func (h *maxHeap) right(i int) int { + return 2*i + 2 +} + +/* Получить индекс родительского узла */ +func (h *maxHeap) parent(i int) int { + // Округление вниз при делении + return (i - 1) / 2 +} + +/* Поменять элементы местами */ +func (h *maxHeap) swap(i, j int) { + h.data[i], h.data[j] = h.data[j], h.data[i] +} + +/* Получение размера кучи */ +func (h *maxHeap) size() int { + return len(h.data) +} + +/* Проверка, пуста ли куча */ +func (h *maxHeap) isEmpty() bool { + return len(h.data) == 0 +} + +/* Доступ к элементу на вершине кучи */ +func (h *maxHeap) peek() any { + return h.data[0] +} + +/* Добавление элемента в кучу */ +func (h *maxHeap) push(val any) { + // Добавление узла + h.data = append(h.data, val) + // Просеивание снизу вверх + h.siftUp(len(h.data) - 1) +} + +/* Начиная с узла i, выполнить просеивание снизу вверх */ +func (h *maxHeap) siftUp(i int) { + for true { + // Получение родительского узла для узла i + p := h.parent(i) + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if p < 0 || h.data[i].(int) <= h.data[p].(int) { + break + } + // Поменять два узла местами + h.swap(i, p) + // Циклическое просеивание вверх + i = p + } +} + +/* Извлечение элемента из кучи */ +func (h *maxHeap) pop() any { + // Обработка пустого случая + if h.isEmpty() { + fmt.Println("error") + return nil + } + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + h.swap(0, h.size()-1) + // Удаление узла + val := h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + // Просеивание сверху вниз + h.siftDown(0) + + // Вернуть элемент с вершины кучи + return val +} + +/* Начиная с узла i, выполнить просеивание сверху вниз */ +func (h *maxHeap) siftDown(i int) { + for true { + // Определить узел с максимальным значением среди i, l и r и обозначить его как max + l, r, max := h.left(i), h.right(i), i + if l < h.size() && h.data[l].(int) > h.data[max].(int) { + max = l + } + if r < h.size() && h.data[r].(int) > h.data[max].(int) { + max = r + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if max == i { + break + } + // Поменять два узла местами + h.swap(i, max) + // Циклическое просеивание вниз + i = max + } +} + +/* Вывести кучу (двоичное дерево) */ +func (h *maxHeap) print() { + PrintHeap(h.data) +} diff --git a/ru/codes/go/chapter_heap/top_k.go b/ru/codes/go/chapter_heap/top_k.go new file mode 100644 index 000000000..d1cb01dd1 --- /dev/null +++ b/ru/codes/go/chapter_heap/top_k.go @@ -0,0 +1,51 @@ +// File: top_k.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import "container/heap" + +type minHeap []any + +func (h *minHeap) Len() int { return len(*h) } +func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } +func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } + +// Метод Push интерфейса heap.Interface, реализующий добавление элемента в кучу +func (h *minHeap) Push(x any) { + *h = append(*h, x.(int)) +} + +// Метод Pop интерфейса heap.Interface, реализующий извлечение элемента с вершины кучи +func (h *minHeap) Pop() any { + // Элемент, который нужно удалить из кучи, хранится в конце + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Top: получить элемент на вершине кучи +func (h *minHeap) Top() any { + return (*h)[0] +} + +/* Найти k наибольших элементов массива с помощью кучи */ +func topKHeap(nums []int, k int) *minHeap { + // Инициализация минимальной кучи + h := &minHeap{} + heap.Init(h) + // Поместить первые k элементов массива в кучу + for i := 0; i < k; i++ { + heap.Push(h, nums[i]) + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for i := k; i < len(nums); i++ { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if nums[i] > h.Top().(int) { + heap.Pop(h) + heap.Push(h, nums[i]) + } + } + return h +} diff --git a/ru/codes/go/chapter_searching/binary_search.go b/ru/codes/go/chapter_searching/binary_search.go new file mode 100644 index 000000000..37672f9f0 --- /dev/null +++ b/ru/codes/go/chapter_searching/binary_search.go @@ -0,0 +1,43 @@ +// File: binary_search.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +func binarySearch(nums []int, target int) int { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + i, j := 0, len(nums)-1 + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + for i <= j { + m := i + (j-i)/2 // Вычислить индекс середины m + if nums[m] < target { // Это означает, что target находится в интервале [m+1, j] + i = m + 1 + } else if nums[m] > target { // Это означает, что target находится в интервале [i, m-1] + j = m - 1 + } else { // Целевой элемент найден, вернуть его индекс + return m + } + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +func binarySearchLCRO(nums []int, target int) int { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + i, j := 0, len(nums) + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + for i < j { + m := i + (j-i)/2 // Вычислить индекс середины m + if nums[m] < target { // Это означает, что target находится в интервале [m+1, j) + i = m + 1 + } else if nums[m] > target { // Это означает, что target находится в интервале [i, m) + j = m + } else { // Целевой элемент найден, вернуть его индекс + return m + } + } + // Целевой элемент не найден, вернуть -1 + return -1 +} diff --git a/ru/codes/go/chapter_searching/binary_search_edge.go b/ru/codes/go/chapter_searching/binary_search_edge.go new file mode 100644 index 000000000..d532c5859 --- /dev/null +++ b/ru/codes/go/chapter_searching/binary_search_edge.go @@ -0,0 +1,31 @@ +// File: binary_search_edge.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* Бинарный поиск самого левого target */ +func binarySearchLeftEdge(nums []int, target int) int { + // Эквивалентно поиску точки вставки target + i := binarySearchInsertion(nums, target) + // target не найден, вернуть -1 + if i == len(nums) || nums[i] != target { + return -1 + } + // Найти target и вернуть индекс i + return i +} + +/* Бинарный поиск самого правого target */ +func binarySearchRightEdge(nums []int, target int) int { + // Преобразовать задачу в поиск самого левого target + 1 + i := binarySearchInsertion(nums, target+1) + // j указывает на самый правый target, а i — на первый элемент больше target + j := i - 1 + // target не найден, вернуть -1 + if j == -1 || nums[j] != target { + return -1 + } + // Найти target и вернуть индекс j + return j +} diff --git a/ru/codes/go/chapter_searching/binary_search_insertion.go b/ru/codes/go/chapter_searching/binary_search_insertion.go new file mode 100644 index 000000000..e5722dc4b --- /dev/null +++ b/ru/codes/go/chapter_searching/binary_search_insertion.go @@ -0,0 +1,49 @@ +// File: binary_search_insertion.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +func binarySearchInsertionSimple(nums []int, target int) int { + // Инициализировать двусторонне замкнутый интервал [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // Вычислить индекс середины m + m := i + (j-i)/2 + if nums[m] < target { + // target находится в интервале [m+1, j] + i = m + 1 + } else if nums[m] > target { + // target находится в интервале [i, m-1] + j = m - 1 + } else { + // Найти target и вернуть точку вставки m + return m + } + } + // target не найден, вернуть точку вставки i + return i +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +func binarySearchInsertion(nums []int, target int) int { + // Инициализировать двусторонне замкнутый интервал [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // Вычислить индекс середины m + m := i + (j-i)/2 + if nums[m] < target { + // target находится в интервале [m+1, j] + i = m + 1 + } else if nums[m] > target { + // target находится в интервале [i, m-1] + j = m - 1 + } else { + // Первый элемент меньше target находится в интервале [i, m-1] + j = m - 1 + } + } + // Вернуть точку вставки i + return i +} diff --git a/ru/codes/go/chapter_searching/binary_search_test.go b/ru/codes/go/chapter_searching/binary_search_test.go new file mode 100644 index 000000000..a523d2732 --- /dev/null +++ b/ru/codes/go/chapter_searching/binary_search_test.go @@ -0,0 +1,61 @@ +// File: binary_search_test.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + var ( + target = 6 + nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + expected = 2 + ) + // Выполнить бинарный поиск в массиве + actual := binarySearch(nums, target) + fmt.Println("Индекс целевого элемента 6 =", actual) + if actual != expected { + t.Errorf("Индекс целевого элемента 6 = %d, должно быть %d", actual, expected) + } +} + +func TestBinarySearchEdge(t *testing.T) { + // Массив с повторяющимися элементами + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("\nМассив nums =", nums) + + // Бинарный поиск левой и правой границы + for _, target := range []int{6, 7} { + index := binarySearchLeftEdge(nums, target) + fmt.Println("Индекс самого левого элемента", target, "равен", index) + + index = binarySearchRightEdge(nums, target) + fmt.Println("Индекс самого правого элемента", target, "равен", index) + } +} + +func TestBinarySearchInsertion(t *testing.T) { + // Массив без повторяющихся элементов + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("Массив nums =", nums) + + // Бинарный поиск точки вставки + for _, target := range []int{6, 9} { + index := binarySearchInsertionSimple(nums, target) + fmt.Println("Индекс позиции вставки элемента", target, "равен", index) + } + + // Массив с повторяющимися элементами + nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} + fmt.Println("\nМассив nums =", nums) + + // Бинарный поиск точки вставки + for _, target := range []int{2, 6, 20} { + index := binarySearchInsertion(nums, target) + fmt.Println("Индекс позиции вставки элемента", target, "равен", index) + } +} diff --git a/ru/codes/go/chapter_searching/hashing_search.go b/ru/codes/go/chapter_searching/hashing_search.go new file mode 100644 index 000000000..e64ccc2ae --- /dev/null +++ b/ru/codes/go/chapter_searching/hashing_search.go @@ -0,0 +1,29 @@ +// File: hashing_search.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import . "github.com/krahets/hello-algo/pkg" + +/* Хеш-поиск (массив) */ +func hashingSearchArray(m map[int]int, target int) int { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + if index, ok := m[target]; ok { + return index + } else { + return -1 + } +} + +/* Хеш-поиск (связный список) */ +func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть nil + if node, ok := m[target]; ok { + return node + } else { + return nil + } +} diff --git a/ru/codes/go/chapter_searching/hashing_search_test.go b/ru/codes/go/chapter_searching/hashing_search_test.go new file mode 100644 index 000000000..d9bd5fef3 --- /dev/null +++ b/ru/codes/go/chapter_searching/hashing_search_test.go @@ -0,0 +1,36 @@ +// File: hashing_search_test.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashingSearch(t *testing.T) { + target := 3 + /* Хеш-поиск (массив) */ + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + // Инициализация хеш-таблицы + m := make(map[int]int) + for i := 0; i < len(nums); i++ { + m[nums[i]] = i + } + index := hashingSearchArray(m, target) + fmt.Println("Индекс целевого элемента 3 = ", index) + + /* Хеш-поиск (связный список) */ + head := ArrayToLinkedList(nums) + // Инициализация хеш-таблицы + m1 := make(map[int]*ListNode) + for head != nil { + m1[head.Val] = head + head = head.Next + } + node := hashingSearchLinkedList(m1, target) + fmt.Println("Объект узла со значением 3 =", node) +} diff --git a/ru/codes/go/chapter_searching/linear_search.go b/ru/codes/go/chapter_searching/linear_search.go new file mode 100644 index 000000000..83b22fa70 --- /dev/null +++ b/ru/codes/go/chapter_searching/linear_search.go @@ -0,0 +1,36 @@ +// File: linear_search.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* Линейный поиск (массив) */ +func linearSearchArray(nums []int, target int) int { + // Обход массива + for i := 0; i < len(nums); i++ { + // Целевой элемент найден, вернуть его индекс + if nums[i] == target { + return i + } + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Линейный поиск (связный список) */ +func linearSearchLinkedList(node *ListNode, target int) *ListNode { + // Обойти связный список + for node != nil { + // Найти целевой узел и вернуть его + if node.Val == target { + return node + } + node = node.Next + } + // Целевой элемент не найден, вернуть nil + return nil +} diff --git a/ru/codes/go/chapter_searching/linear_search_test.go b/ru/codes/go/chapter_searching/linear_search_test.go new file mode 100644 index 000000000..1ec55702f --- /dev/null +++ b/ru/codes/go/chapter_searching/linear_search_test.go @@ -0,0 +1,26 @@ +// File: linear_search_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinearSearch(t *testing.T) { + target := 3 + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + + // Выполнить линейный поиск в массиве + index := linearSearchArray(nums, target) + fmt.Println("Индекс целевого элемента 3 =", index) + + // Выполнить линейный поиск в связном списке + head := ArrayToLinkedList(nums) + node := linearSearchLinkedList(head, target) + fmt.Println("Объект узла со значением 3 =", node) +} diff --git a/ru/codes/go/chapter_searching/two_sum.go b/ru/codes/go/chapter_searching/two_sum.go new file mode 100644 index 000000000..795de6fda --- /dev/null +++ b/ru/codes/go/chapter_searching/two_sum.go @@ -0,0 +1,33 @@ +// File: two_sum.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +/* Метод 1: полный перебор */ +func twoSumBruteForce(nums []int, target int) []int { + size := len(nums) + // Два вложенных цикла, временная сложность O(n^2) + for i := 0; i < size-1; i++ { + for j := i + 1; j < size; j++ { + if nums[i]+nums[j] == target { + return []int{i, j} + } + } + } + return nil +} + +/* Метод 2: вспомогательная хеш-таблица */ +func twoSumHashTable(nums []int, target int) []int { + // Вспомогательная хеш-таблица, пространственная сложность O(n) + hashTable := map[int]int{} + // Один цикл, временная сложность O(n) + for idx, val := range nums { + if preIdx, ok := hashTable[target-val]; ok { + return []int{preIdx, idx} + } + hashTable[val] = idx + } + return nil +} diff --git a/ru/codes/go/chapter_searching/two_sum_test.go b/ru/codes/go/chapter_searching/two_sum_test.go new file mode 100644 index 000000000..95eba1e2b --- /dev/null +++ b/ru/codes/go/chapter_searching/two_sum_test.go @@ -0,0 +1,24 @@ +// File: two_sum_test.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestTwoSum(t *testing.T) { + // ======= Test Case ======= + nums := []int{2, 7, 11, 15} + target := 13 + + // ====== Основной код ====== + // Метод 1: решение полным перебором + res := twoSumBruteForce(nums, target) + fmt.Println("Результат метода 1 res =", res) + // Способ 2: хеш-таблица + res = twoSumHashTable(nums, target) + fmt.Println("Результат метода 2 res =", res) +} diff --git a/ru/codes/go/chapter_sorting/bubble_sort.go b/ru/codes/go/chapter_sorting/bubble_sort.go new file mode 100644 index 000000000..eaf536da9 --- /dev/null +++ b/ru/codes/go/chapter_sorting/bubble_sort.go @@ -0,0 +1,38 @@ +// File: bubble_sort.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +/* Пузырьковая сортировка */ +func bubbleSort(nums []int) { + // Внешний цикл: неотсортированный диапазон [0, i] + for i := len(nums) - 1; i > 0; i-- { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // Поменять местами nums[j] и nums[j + 1] + nums[j], nums[j+1] = nums[j+1], nums[j] + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +func bubbleSortWithFlag(nums []int) { + // Внешний цикл: неотсортированный диапазон [0, i] + for i := len(nums) - 1; i > 0; i-- { + flag := false // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // Поменять местами nums[j] и nums[j + 1] + nums[j], nums[j+1] = nums[j+1], nums[j] + flag = true // Записать обмен элементов + } + } + if flag == false { // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + break + } + } +} diff --git a/ru/codes/go/chapter_sorting/bubble_sort_test.go b/ru/codes/go/chapter_sorting/bubble_sort_test.go new file mode 100644 index 000000000..3c5f2ed93 --- /dev/null +++ b/ru/codes/go/chapter_sorting/bubble_sort_test.go @@ -0,0 +1,20 @@ +// File: bubble_sort_test.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBubbleSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + bubbleSort(nums) + fmt.Println("После пузырьковой сортировки nums =", nums) + + nums1 := []int{4, 1, 3, 1, 5, 2} + bubbleSortWithFlag(nums1) + fmt.Println("После пузырьковой сортировки nums1 =", nums1) +} diff --git a/ru/codes/go/chapter_sorting/bucket_sort.go b/ru/codes/go/chapter_sorting/bucket_sort.go new file mode 100644 index 000000000..8b1396183 --- /dev/null +++ b/ru/codes/go/chapter_sorting/bucket_sort.go @@ -0,0 +1,37 @@ +// File: bucket_sort.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "sort" + +/* Сортировка корзинами */ +func bucketSort(nums []float64) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + k := len(nums) / 2 + buckets := make([][]float64, k) + for i := 0; i < k; i++ { + buckets[i] = make([]float64, 0) + } + // 1. Распределить элементы массива по корзинам + for _, num := range nums { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + i := int(num * float64(k)) + // Добавить num в корзину i + buckets[i] = append(buckets[i], num) + } + // 2. Выполнить сортировку внутри каждой корзины + for i := 0; i < k; i++ { + // Использовать встроенную функцию сортировки среза; ее также можно заменить другим алгоритмом сортировки + sort.Float64s(buckets[i]) + } + // 3. Обойти корзины и объединить результаты + i := 0 + for _, bucket := range buckets { + for _, num := range bucket { + nums[i] = num + i++ + } + } +} diff --git a/ru/codes/go/chapter_sorting/bucket_sort_test.go b/ru/codes/go/chapter_sorting/bucket_sort_test.go new file mode 100644 index 000000000..18dcdb144 --- /dev/null +++ b/ru/codes/go/chapter_sorting/bucket_sort_test.go @@ -0,0 +1,17 @@ +// File: bucket_sort_test.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBucketSort(t *testing.T) { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} + bucketSort(nums) + fmt.Println("После сортировки корзинами nums =", nums) +} diff --git a/ru/codes/go/chapter_sorting/counting_sort.go b/ru/codes/go/chapter_sorting/counting_sort.go new file mode 100644 index 000000000..b9ceee1c6 --- /dev/null +++ b/ru/codes/go/chapter_sorting/counting_sort.go @@ -0,0 +1,68 @@ +// File: counting_sort.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +type CountingSort struct{} + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +func countingSortNaive(nums []int) { + // 1. Найти максимальный элемент массива m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. Обойти counter и заполнить исходный массив nums элементами + for i, num := 0, 0; num < m+1; num++ { + for j := 0; j < counter[num]; j++ { + nums[i] = num + i++ + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +func countingSort(nums []int) { + // 1. Найти максимальный элемент массива m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for i := 0; i < m; i++ { + counter[i+1] += counter[i] + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + n := len(nums) + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + num := nums[i] + // Поместить num по соответствующему индексу + res[counter[num]-1] = num + // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + counter[num]-- + } + // Перезаписать исходный массив nums массивом результата res + copy(nums, res) +} diff --git a/ru/codes/go/chapter_sorting/counting_sort_test.go b/ru/codes/go/chapter_sorting/counting_sort_test.go new file mode 100644 index 000000000..c34192d9b --- /dev/null +++ b/ru/codes/go/chapter_sorting/counting_sort_test.go @@ -0,0 +1,20 @@ +// File: counting_sort_test.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestCountingSort(t *testing.T) { + nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSortNaive(nums) + fmt.Println("После сортировки подсчетом (объекты не поддерживаются) nums =", nums) + + nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSort(nums1) + fmt.Println("После сортировки подсчетом nums1 =", nums1) +} diff --git a/ru/codes/go/chapter_sorting/heap_sort.go b/ru/codes/go/chapter_sorting/heap_sort.go new file mode 100644 index 000000000..d1811376d --- /dev/null +++ b/ru/codes/go/chapter_sorting/heap_sort.go @@ -0,0 +1,44 @@ +// File: heap_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +func siftDown(nums *[]int, n, i int) { + for true { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + l := 2*i + 1 + r := 2*i + 2 + ma := i + if l < n && (*nums)[l] > (*nums)[ma] { + ma = l + } + if r < n && (*nums)[r] > (*nums)[ma] { + ma = r + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i { + break + } + // Поменять два узла местами + (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] + // Циклическое просеивание вниз + i = ma + } +} + +/* Сортировка кучей */ +func heapSort(nums *[]int) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for i := len(*nums)/2 - 1; i >= 0; i-- { + siftDown(nums, len(*nums), i) + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for i := len(*nums) - 1; i > 0; i-- { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0) + } +} diff --git a/ru/codes/go/chapter_sorting/heap_sort_test.go b/ru/codes/go/chapter_sorting/heap_sort_test.go new file mode 100644 index 000000000..6ea18ea66 --- /dev/null +++ b/ru/codes/go/chapter_sorting/heap_sort_test.go @@ -0,0 +1,16 @@ +// File: heap_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestHeapSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + heapSort(&nums) + fmt.Println("После сортировки кучей nums =", nums) +} diff --git a/ru/codes/go/chapter_sorting/insertion_sort.go b/ru/codes/go/chapter_sorting/insertion_sort.go new file mode 100644 index 000000000..9885fa3c7 --- /dev/null +++ b/ru/codes/go/chapter_sorting/insertion_sort.go @@ -0,0 +1,20 @@ +// File: insertion_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* Сортировка вставками */ +func insertionSort(nums []int) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for i := 1; i < len(nums); i++ { + base := nums[i] + j := i - 1 + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + for j >= 0 && nums[j] > base { + nums[j+1] = nums[j] // Сдвинуть nums[j] на одну позицию вправо + j-- + } + nums[j+1] = base // Поместить base в правильную позицию + } +} diff --git a/ru/codes/go/chapter_sorting/insertion_sort_test.go b/ru/codes/go/chapter_sorting/insertion_sort_test.go new file mode 100644 index 000000000..bd02b42b0 --- /dev/null +++ b/ru/codes/go/chapter_sorting/insertion_sort_test.go @@ -0,0 +1,16 @@ +// File: insertion_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestInsertionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + insertionSort(nums) + fmt.Println("После сортировки вставками nums =", nums) +} diff --git a/ru/codes/go/chapter_sorting/merge_sort.go b/ru/codes/go/chapter_sorting/merge_sort.go new file mode 100644 index 000000000..79aacb062 --- /dev/null +++ b/ru/codes/go/chapter_sorting/merge_sort.go @@ -0,0 +1,54 @@ +// File: merge_sort.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* Объединить левый и правый подмассивы */ +func merge(nums []int, left, mid, right int) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + tmp := make([]int, right-left+1) + // Инициализировать начальные индексы левого и правого подмассивов + i, j, k := left, mid+1, 0 + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + for i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i++ + } else { + tmp[k] = nums[j] + j++ + } + k++ + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + for i <= mid { + tmp[k] = nums[i] + i++ + k++ + } + for j <= right { + tmp[k] = nums[j] + j++ + k++ + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for k := 0; k < len(tmp); k++ { + nums[left+k] = tmp[k] + } +} + +/* Сортировка слиянием */ +func mergeSort(nums []int, left, right int) { + // Условие завершения + if left >= right { + return + } + // Этап разбиения + mid := left + (right - left) / 2 + mergeSort(nums, left, mid) + mergeSort(nums, mid+1, right) + // Этап слияния + merge(nums, left, mid, right) +} diff --git a/ru/codes/go/chapter_sorting/merge_sort_test.go b/ru/codes/go/chapter_sorting/merge_sort_test.go new file mode 100644 index 000000000..c5b8e19c4 --- /dev/null +++ b/ru/codes/go/chapter_sorting/merge_sort_test.go @@ -0,0 +1,16 @@ +// File: merge_sort_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestMergeSort(t *testing.T) { + nums := []int{7, 3, 2, 6, 0, 1, 5, 4} + mergeSort(nums, 0, len(nums)-1) + fmt.Println("После сортировки слиянием nums =", nums) +} diff --git a/ru/codes/go/chapter_sorting/quick_sort.go b/ru/codes/go/chapter_sorting/quick_sort.go new file mode 100644 index 000000000..73384679f --- /dev/null +++ b/ru/codes/go/chapter_sorting/quick_sort.go @@ -0,0 +1,130 @@ +// File: quick_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +// Быстрая сортировка +type quickSort struct{} + +// Быстрая сортировка (оптимизация медианным опорным элементом) +type quickSortMedian struct{} + +// Быстрая сортировка (оптимизация глубины рекурсии) +type quickSortTailCall struct{} + +/* Разбиение с опорными указателями */ +func (q *quickSort) partition(nums []int, left, right int) int { + // Взять nums[left] в качестве опорного элемента + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // Идти справа налево в поисках первого элемента меньше опорного + } + for i < j && nums[i] <= nums[left] { + i++ // Идти слева направо в поисках первого элемента больше опорного + } + // Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + } + // Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + return i // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка */ +func (q *quickSort) quickSort(nums []int, left, right int) { + // Завершить рекурсию, когда длина подмассива равна 1 + if left >= right { + return + } + // Разбиение с опорными указателями + pivot := q.partition(nums, left, right) + // Рекурсивно обработать левый и правый подмассивы + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* Выбрать медиану из трех кандидатов */ +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { + l, m, r := nums[left], nums[mid], nums[right] + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid // m находится между l и r + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left // l находится между m и r + } + return right +} + +/* Разбиение с опорными указателями (медиана трех) */ +func (q *quickSortMedian) partition(nums []int, left, right int) int { + // Взять nums[left] в качестве опорного элемента + med := q.medianThree(nums, left, (left+right)/2, right) + // Переместить медиану в крайний левый элемент массива + nums[left], nums[med] = nums[med], nums[left] + // Взять nums[left] в качестве опорного элемента + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // Идти справа налево в поисках первого элемента меньше опорного + } + for i < j && nums[i] <= nums[left] { + i++ // Идти слева направо в поисках первого элемента больше опорного + } + // Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + } + // Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + return i // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка */ +func (q *quickSortMedian) quickSort(nums []int, left, right int) { + // Завершить рекурсию, когда длина подмассива равна 1 + if left >= right { + return + } + // Разбиение с опорными указателями + pivot := q.partition(nums, left, right) + // Рекурсивно обработать левый и правый подмассивы + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* Разбиение с опорными указателями */ +func (q *quickSortTailCall) partition(nums []int, left, right int) int { + // Взять nums[left] в качестве опорного элемента + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // Идти справа налево в поисках первого элемента меньше опорного + } + for i < j && nums[i] <= nums[left] { + i++ // Идти слева направо в поисках первого элемента больше опорного + } + // Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + } + // Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + return i // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +func (q *quickSortTailCall) quickSort(nums []int, left, right int) { + // Завершить, когда длина подмассива равна 1 + for left < right { + // Операция разбиения с опорными указателями + pivot := q.partition(nums, left, right) + // Выполнить быструю сортировку для более короткого из двух подмассивов + if pivot-left < right-pivot { + q.quickSort(nums, left, pivot-1) // Рекурсивно отсортировать левый подмассив + left = pivot + 1 // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + q.quickSort(nums, pivot+1, right) // Рекурсивно отсортировать правый подмассив + right = pivot - 1 // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } +} diff --git a/ru/codes/go/chapter_sorting/quick_sort_test.go b/ru/codes/go/chapter_sorting/quick_sort_test.go new file mode 100644 index 000000000..31610ece7 --- /dev/null +++ b/ru/codes/go/chapter_sorting/quick_sort_test.go @@ -0,0 +1,34 @@ +// File: quick_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +// Быстрая сортировка +func TestQuickSort(t *testing.T) { + q := quickSort{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("После быстрой сортировки nums =", nums) +} + +// Быстрая сортировка (оптимизация медианным опорным элементом) +func TestQuickSortMedian(t *testing.T) { + q := quickSortMedian{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("После быстрой сортировки (оптимизация медианным опорным элементом) nums =", nums) +} + +// Быстрая сортировка (оптимизация глубины рекурсии) +func TestQuickSortTailCall(t *testing.T) { + q := quickSortTailCall{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("После быстрой сортировки (оптимизация глубины рекурсии) nums =", nums) +} diff --git a/ru/codes/go/chapter_sorting/radix_sort.go b/ru/codes/go/chapter_sorting/radix_sort.go new file mode 100644 index 000000000..a408d3a61 --- /dev/null +++ b/ru/codes/go/chapter_sorting/radix_sort.go @@ -0,0 +1,60 @@ +// File: radix_sort.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "math" + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +func digit(num, exp int) int { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num / exp) % 10 +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +func countingSortDigit(nums []int, exp int) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + counter := make([]int, 10) + n := len(nums) + // Подсчитать число появлений каждой цифры от 0 до 9 + for i := 0; i < n; i++ { + d := digit(nums[i], exp) // Получить k-й разряд nums[i], обозначив его как d + counter[d]++ // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for i := 1; i < 10; i++ { + counter[i] += counter[i-1] + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + d := digit(nums[i], exp) + j := counter[d] - 1 // Получить индекс j цифры d в массиве + res[j] = nums[i] // Поместить текущий элемент по индексу j + counter[d]-- // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for i := 0; i < n; i++ { + nums[i] = res[i] + } +} + +/* Поразрядная сортировка */ +func radixSort(nums []int) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + max := math.MinInt + for _, num := range nums { + if num > max { + max = num + } + } + // Проходить разряды от младшего к старшему + for exp := 1; max >= exp; exp *= 10 { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp) + } +} diff --git a/ru/codes/go/chapter_sorting/radix_sort_test.go b/ru/codes/go/chapter_sorting/radix_sort_test.go new file mode 100644 index 000000000..8dffc02dd --- /dev/null +++ b/ru/codes/go/chapter_sorting/radix_sort_test.go @@ -0,0 +1,18 @@ +// File: radix_sort_test.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestRadixSort(t *testing.T) { + /* Поразрядная сортировка */ + nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996} + radixSort(nums) + fmt.Println("После поразрядной сортировки nums =", nums) +} diff --git a/ru/codes/go/chapter_sorting/selection_sort.go b/ru/codes/go/chapter_sorting/selection_sort.go new file mode 100644 index 000000000..322ee52d5 --- /dev/null +++ b/ru/codes/go/chapter_sorting/selection_sort.go @@ -0,0 +1,24 @@ +// File: selection_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* Сортировка выбором */ +func selectionSort(nums []int) { + n := len(nums) + // Внешний цикл: неотсортированный диапазон [i, n-1] + for i := 0; i < n-1; i++ { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + k := i + for j := i + 1; j < n; j++ { + if nums[j] < nums[k] { + // Записать индекс минимального элемента + k = j + } + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + nums[i], nums[k] = nums[k], nums[i] + + } +} diff --git a/ru/codes/go/chapter_sorting/selection_sort_test.go b/ru/codes/go/chapter_sorting/selection_sort_test.go new file mode 100644 index 000000000..b09333cec --- /dev/null +++ b/ru/codes/go/chapter_sorting/selection_sort_test.go @@ -0,0 +1,16 @@ +// File: selection_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestSelectionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + selectionSort(nums) + fmt.Println("После сортировки выбором nums =", nums) +} diff --git a/ru/codes/go/chapter_stack_and_queue/array_deque.go b/ru/codes/go/chapter_stack_and_queue/array_deque.go new file mode 100644 index 000000000..7ccf3950e --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/array_deque.go @@ -0,0 +1,121 @@ +// File: array_deque.go +// Created Time: 2023-03-13 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import "fmt" + +/* Двусторонняя очередь на основе кольцевого массива */ +type arrayDeque struct { + nums []int // Массив для хранения элементов двусторонней очереди + front int // Указатель head, указывающий на первый элемент очереди + queSize int // Длина двусторонней очереди + queCapacity int // Вместимость очереди (то есть максимальное число элементов) +} + +/* Инициализация очереди */ +func newArrayDeque(queCapacity int) *arrayDeque { + return &arrayDeque{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* Получение длины двусторонней очереди */ +func (q *arrayDeque) size() int { + return q.queSize +} + +/* Проверка, пуста ли двусторонняя очередь */ +func (q *arrayDeque) isEmpty() bool { + return q.queSize == 0 +} + +/* Вычислить индекс в кольцевом массиве */ +func (q *arrayDeque) index(i int) int { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + q.queCapacity) % q.queCapacity +} + +/* Добавление в голову очереди */ +func (q *arrayDeque) pushFirst(num int) { + if q.queSize == q.queCapacity { + fmt.Println("Двусторонняя очередь заполнена") + return + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + q.front = q.index(q.front - 1) + // Добавить num в голову очереди + q.nums[q.front] = num + q.queSize++ +} + +/* Добавление в хвост очереди */ +func (q *arrayDeque) pushLast(num int) { + if q.queSize == q.queCapacity { + fmt.Println("Двусторонняя очередь заполнена") + return + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + rear := q.index(q.front + q.queSize) + // Добавить num в хвост очереди + q.nums[rear] = num + q.queSize++ +} + +/* Извлечение из головы очереди */ +func (q *arrayDeque) popFirst() any { + num := q.peekFirst() + if num == nil { + return nil + } + // Указатель головы сдвигается на одну позицию назад + q.front = q.index(q.front + 1) + q.queSize-- + return num +} + +/* Извлечение из хвоста очереди */ +func (q *arrayDeque) popLast() any { + num := q.peekLast() + if num == nil { + return nil + } + q.queSize-- + return num +} + +/* Доступ к элементу в начале очереди */ +func (q *arrayDeque) peekFirst() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* Доступ к элементу в конце очереди */ +func (q *arrayDeque) peekLast() any { + if q.isEmpty() { + return nil + } + // Вычислить индекс хвостового элемента + last := q.index(q.front + q.queSize - 1) + return q.nums[last] +} + +/* Получить Slice для вывода */ +func (q *arrayDeque) toSlice() []int { + // Преобразовывать только элементы списка в пределах фактической длины + res := make([]int, q.queSize) + for i, j := 0, q.front; i < q.queSize; i++ { + res[i] = q.nums[q.index(j)] + j++ + } + return res +} diff --git a/ru/codes/go/chapter_stack_and_queue/array_queue.go b/ru/codes/go/chapter_stack_and_queue/array_queue.go new file mode 100644 index 000000000..2ae70a16c --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/array_queue.go @@ -0,0 +1,78 @@ +// File: array_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* Очередь на основе кольцевого массива */ +type arrayQueue struct { + nums []int // Массив для хранения элементов очереди + front int // Указатель head, указывающий на первый элемент очереди + queSize int // Длина очереди + queCapacity int // Вместимость очереди (то есть максимальное число элементов) +} + +/* Инициализация очереди */ +func newArrayQueue(queCapacity int) *arrayQueue { + return &arrayQueue{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* Получение длины очереди */ +func (q *arrayQueue) size() int { + return q.queSize +} + +/* Проверка, пуста ли очередь */ +func (q *arrayQueue) isEmpty() bool { + return q.queSize == 0 +} + +/* Поместить в очередь */ +func (q *arrayQueue) push(num int) { + // Когда rear == queCapacity, очередь заполнена + if q.queSize == q.queCapacity { + return + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + rear := (q.front + q.queSize) % q.queCapacity + // Добавить num в хвост очереди + q.nums[rear] = num + q.queSize++ +} + +/* Извлечь из очереди */ +func (q *arrayQueue) pop() any { + num := q.peek() + if num == nil { + return nil + } + + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + q.front = (q.front + 1) % q.queCapacity + q.queSize-- + return num +} + +/* Доступ к элементу в начале очереди */ +func (q *arrayQueue) peek() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* Получить Slice для вывода */ +func (q *arrayQueue) toSlice() []int { + rear := (q.front + q.queSize) + if rear >= q.queCapacity { + rear %= q.queCapacity + return append(q.nums[q.front:], q.nums[:rear]...) + } + return q.nums[q.front:rear] +} diff --git a/ru/codes/go/chapter_stack_and_queue/array_stack.go b/ru/codes/go/chapter_stack_and_queue/array_stack.go new file mode 100644 index 000000000..9b0ef6c70 --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/array_stack.go @@ -0,0 +1,55 @@ +// File: array_stack.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* Стек на основе массива */ +type arrayStack struct { + data []int // Данные +} + +/* Инициализация стека */ +func newArrayStack() *arrayStack { + return &arrayStack{ + // Установить длину стека равной 0, а емкость равной 16 + data: make([]int, 0, 16), + } +} + +/* Длина стека */ +func (s *arrayStack) size() int { + return len(s.data) +} + +/* Пуст ли стек */ +func (s *arrayStack) isEmpty() bool { + return s.size() == 0 +} + +/* Поместить в стек */ +func (s *arrayStack) push(v int) { + // Срез автоматически расширяется + s.data = append(s.data, v) +} + +/* Извлечь из стека */ +func (s *arrayStack) pop() any { + val := s.peek() + s.data = s.data[:len(s.data)-1] + return val +} + +/* Получить элемент на вершине стека */ +func (s *arrayStack) peek() any { + if s.isEmpty() { + return nil + } + val := s.data[len(s.data)-1] + return val +} + +/* Получить Slice для вывода */ +func (s *arrayStack) toSlice() []int { + return s.data +} diff --git a/ru/codes/go/chapter_stack_and_queue/deque_test.go b/ru/codes/go/chapter_stack_and_queue/deque_test.go new file mode 100644 index 000000000..0b0be8681 --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/deque_test.go @@ -0,0 +1,141 @@ +// File: deque_test.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestDeque(t *testing.T) { + /* Инициализация двусторонней очереди */ + // В Go list используется как двусторонняя очередь + deque := list.New() + + /* Добавление элемента в очередь */ + deque.PushBack(2) + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) + deque.PushFront(1) + fmt.Print("Двусторонняя очередь deque = ") + PrintList(deque) + + /* Доступ к элементу */ + front := deque.Front() + fmt.Println("Первый элемент front =", front.Value) + rear := deque.Back() + fmt.Println("Последний элемент rear =", rear.Value) + + /* Извлечение элемента из очереди */ + deque.Remove(front) + fmt.Print("Извлеченный из головы элемент front = ", front.Value, ", deque после извлечения из головы = ") + PrintList(deque) + deque.Remove(rear) + fmt.Print("Извлеченный из хвоста элемент rear = ", rear.Value, ", deque после извлечения из хвоста = ") + PrintList(deque) + + /* Получение длины двусторонней очереди */ + size := deque.Len() + fmt.Println("Длина двусторонней очереди size =", size) + + /* Проверка, пуста ли двусторонняя очередь */ + isEmpty := deque.Len() == 0 + fmt.Println("Пуста ли двусторонняя очередь =", isEmpty) +} + +func TestArrayDeque(t *testing.T) { + /* Инициализация двусторонней очереди */ + // В Go list используется как двусторонняя очередь + deque := newArrayDeque(16) + + /* Добавление элемента в очередь */ + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + fmt.Print("Двусторонняя очередь deque = ") + PrintSlice(deque.toSlice()) + + /* Доступ к элементу */ + peekFirst := deque.peekFirst() + fmt.Println("Первый элемент peekFirst =", peekFirst) + peekLast := deque.peekLast() + fmt.Println("Последний элемент peekLast =", peekLast) + + /* Добавление элемента в очередь */ + deque.pushLast(4) + fmt.Print("После добавления элемента 4 в хвост deque = ") + PrintSlice(deque.toSlice()) + deque.pushFirst(1) + fmt.Print("После добавления элемента 1 в голову deque = ") + PrintSlice(deque.toSlice()) + + /* Извлечение элемента из очереди */ + popFirst := deque.popFirst() + fmt.Print("Извлеченный из головы элемент popFirst = ", popFirst, ", deque после извлечения из головы = ") + PrintSlice(deque.toSlice()) + popLast := deque.popLast() + fmt.Print("Извлеченный из хвоста элемент popLast = ", popLast, ", deque после извлечения из хвоста = ") + PrintSlice(deque.toSlice()) + + /* Получение длины двусторонней очереди */ + size := deque.size() + fmt.Println("Длина двусторонней очереди size =", size) + + /* Проверка, пуста ли двусторонняя очередь */ + isEmpty := deque.isEmpty() + fmt.Println("Пуста ли двусторонняя очередь =", isEmpty) +} + +func TestLinkedListDeque(t *testing.T) { + // Инициализация очереди + deque := newLinkedListDeque() + + // Добавление элемента в очередь + deque.pushLast(2) + deque.pushLast(5) + deque.pushLast(4) + deque.pushFirst(3) + deque.pushFirst(1) + fmt.Print("Очередь deque = ") + PrintList(deque.toList()) + + // Доступ к элементу в начале очереди + front := deque.peekFirst() + fmt.Println("Первый элемент front =", front) + rear := deque.peekLast() + fmt.Println("Последний элемент rear =", rear) + + // Извлечение элемента из очереди + popFirst := deque.popFirst() + fmt.Print("Извлеченный из головы элемент popFirst = ", popFirst, ", deque после извлечения из головы = ") + PrintList(deque.toList()) + popLast := deque.popLast() + fmt.Print("Извлеченный из хвоста элемент popLast = ", popLast, ", deque после извлечения из хвоста = ") + PrintList(deque.toList()) + + // Получить длину очереди + size := deque.size() + fmt.Println("Длина очереди size =", size) + + // Проверка на пустоту + isEmpty := deque.isEmpty() + fmt.Println("Пуста ли очередь =", isEmpty) +} + +// BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro +func BenchmarkLinkedListDeque(b *testing.B) { + deque := newLinkedListDeque() + // use b.N for looping + for i := 0; i < b.N; i++ { + deque.pushLast(777) + } + for i := 0; i < b.N; i++ { + deque.popFirst() + } +} diff --git a/ru/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/ru/codes/go/chapter_stack_and_queue/linkedlist_deque.go new file mode 100644 index 000000000..5a3e7a7d5 --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -0,0 +1,85 @@ +// File: linkedlist_deque.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* Двусторонняя очередь на основе двусвязного списка */ +type linkedListDeque struct { + // Использовать встроенный пакет list + data *list.List +} + +/* Инициализировать двустороннюю очередь */ +func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ + data: list.New(), + } +} + +/* Поместить элемент в голову очереди */ +func (s *linkedListDeque) pushFirst(value any) { + s.data.PushFront(value) +} + +/* Поместить элемент в хвост очереди */ +func (s *linkedListDeque) pushLast(value any) { + s.data.PushBack(value) +} + +/* Извлечь элемент из головы очереди */ +func (s *linkedListDeque) popFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* Извлечь элемент из хвоста очереди */ +func (s *linkedListDeque) popLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* Доступ к элементу в начале очереди */ +func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* Доступ к элементу в конце очереди */ +func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* Получение длины очереди */ +func (s *linkedListDeque) size() int { + return s.data.Len() +} + +/* Проверка, пуста ли очередь */ +func (s *linkedListDeque) isEmpty() bool { + return s.data.Len() == 0 +} + +/* Получить List для вывода */ +func (s *linkedListDeque) toList() *list.List { + return s.data +} diff --git a/ru/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/ru/codes/go/chapter_stack_and_queue/linkedlist_queue.go new file mode 100644 index 000000000..279f62b82 --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -0,0 +1,61 @@ +// File: linkedlist_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* Очередь на основе связного списка */ +type linkedListQueue struct { + // Использовать встроенный пакет list для реализации очереди + data *list.List +} + +/* Инициализация очереди */ +func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ + data: list.New(), + } +} + +/* Поместить в очередь */ +func (s *linkedListQueue) push(value any) { + s.data.PushBack(value) +} + +/* Извлечь из очереди */ +func (s *linkedListQueue) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* Доступ к элементу в начале очереди */ +func (s *linkedListQueue) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* Получение длины очереди */ +func (s *linkedListQueue) size() int { + return s.data.Len() +} + +/* Проверка, пуста ли очередь */ +func (s *linkedListQueue) isEmpty() bool { + return s.data.Len() == 0 +} + +/* Получить List для вывода */ +func (s *linkedListQueue) toList() *list.List { + return s.data +} diff --git a/ru/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/ru/codes/go/chapter_stack_and_queue/linkedlist_stack.go new file mode 100644 index 000000000..00c2737a1 --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -0,0 +1,61 @@ +// File: linkedlist_stack.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* Стек на основе связного списка */ +type linkedListStack struct { + // Использовать встроенный пакет list для реализации стека + data *list.List +} + +/* Инициализация стека */ +func newLinkedListStack() *linkedListStack { + return &linkedListStack{ + data: list.New(), + } +} + +/* Поместить в стек */ +func (s *linkedListStack) push(value int) { + s.data.PushBack(value) +} + +/* Извлечь из стека */ +func (s *linkedListStack) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* Доступ к верхнему элементу стека */ +func (s *linkedListStack) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* Получение длины стека */ +func (s *linkedListStack) size() int { + return s.data.Len() +} + +/* Проверка, пуст ли стек */ +func (s *linkedListStack) isEmpty() bool { + return s.data.Len() == 0 +} + +/* Получить List для вывода */ +func (s *linkedListStack) toList() *list.List { + return s.data +} diff --git a/ru/codes/go/chapter_stack_and_queue/queue_test.go b/ru/codes/go/chapter_stack_and_queue/queue_test.go new file mode 100644 index 000000000..526c9a2e6 --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/queue_test.go @@ -0,0 +1,146 @@ +// File: queue_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestQueue(t *testing.T) { + /* Инициализация очереди */ + // В Go list используется как очередь + queue := list.New() + + /* Добавление элемента в очередь */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + fmt.Print("Очередь queue = ") + PrintList(queue) + + /* Доступ к элементу в начале очереди */ + peek := queue.Front() + fmt.Println("Первый элемент peek =", peek.Value) + + /* Извлечение элемента из очереди */ + pop := queue.Front() + queue.Remove(pop) + fmt.Print("Извлеченный элемент pop = ", pop.Value, ", queue после извлечения = ") + PrintList(queue) + + /* Получение длины очереди */ + size := queue.Len() + fmt.Println("Длина очереди size =", size) + + /* Проверка, пуста ли очередь */ + isEmpty := queue.Len() == 0 + fmt.Println("Пуста ли очередь =", isEmpty) +} + +func TestArrayQueue(t *testing.T) { + + // Инициализировать очередь, используя общий интерфейс очереди + capacity := 10 + queue := newArrayQueue(capacity) + if queue.pop() != nil { + t.Errorf("want:%v,got:%v", nil, queue.pop()) + } + + // Добавление элемента в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("Очередь queue = ") + PrintSlice(queue.toSlice()) + + // Доступ к элементу в начале очереди + peek := queue.peek() + fmt.Println("Первый элемент peek =", peek) + + // Извлечение элемента из очереди + pop := queue.pop() + fmt.Print("Извлеченный элемент pop = ", pop, ", queue после извлечения = ") + PrintSlice(queue.toSlice()) + + // Получить длину очереди + size := queue.size() + fmt.Println("Длина очереди size =", size) + + // Проверка на пустоту + isEmpty := queue.isEmpty() + fmt.Println("Пуста ли очередь =", isEmpty) + + /* Проверка кольцевого массива */ + for i := 0; i < 10; i++ { + queue.push(i) + queue.pop() + fmt.Print("После ", i, "-го раунда операций enqueue и dequeue queue =") + PrintSlice(queue.toSlice()) + } +} + +func TestLinkedListQueue(t *testing.T) { + // Инициализировать очередь + queue := newLinkedListQueue() + + // Добавление элемента в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("Очередь queue = ") + PrintList(queue.toList()) + + // Доступ к элементу в начале очереди + peek := queue.peek() + fmt.Println("Первый элемент peek =", peek) + + // Извлечение элемента из очереди + pop := queue.pop() + fmt.Print("Извлеченный элемент pop = ", pop, ", queue после извлечения = ") + PrintList(queue.toList()) + + // Получить длину очереди + size := queue.size() + fmt.Println("Длина очереди size =", size) + + // Проверка на пустоту + isEmpty := queue.isEmpty() + fmt.Println("Пуста ли очередь =", isEmpty) +} + +// BenchmarkArrayQueue 8 ns/op in Mac M1 Pro +func BenchmarkArrayQueue(b *testing.B) { + capacity := 1000 + queue := newArrayQueue(capacity) + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} + +// BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro +func BenchmarkLinkedQueue(b *testing.B) { + queue := newLinkedListQueue() + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} diff --git a/ru/codes/go/chapter_stack_and_queue/stack_test.go b/ru/codes/go/chapter_stack_and_queue/stack_test.go new file mode 100644 index 000000000..d170ef05f --- /dev/null +++ b/ru/codes/go/chapter_stack_and_queue/stack_test.go @@ -0,0 +1,130 @@ +// File: stack_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestStack(t *testing.T) { + /* Инициализация стека */ + // В Go рекомендуется использовать Slice как стек + var stack []int + + /* Помещение элемента в стек */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + fmt.Print("Стек stack = ") + PrintSlice(stack) + + /* Доступ к верхнему элементу стека */ + peek := stack[len(stack)-1] + fmt.Println("Верхний элемент peek =", peek) + + /* Извлечение элемента из стека */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + fmt.Print("Извлеченный элемент pop = ", pop, ", stack после извлечения = ") + PrintSlice(stack) + + /* Получение длины стека */ + size := len(stack) + fmt.Println("Длина стека size =", size) + + /* Проверка на пустоту */ + isEmpty := len(stack) == 0 + fmt.Println("Пуст ли стек =", isEmpty) +} + +func TestArrayStack(t *testing.T) { + // Инициализировать стек, используя интерфейс + stack := newArrayStack() + + // Помещение элемента в стек + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("Стек stack = ") + PrintSlice(stack.toSlice()) + + // Доступ к верхнему элементу стека + peek := stack.peek() + fmt.Println("Верхний элемент peek =", peek) + + // Извлечение элемента из стека + pop := stack.pop() + fmt.Print("Извлеченный элемент pop = ", pop, ", stack после извлечения = ") + PrintSlice(stack.toSlice()) + + // Получение длины стека + size := stack.size() + fmt.Println("Длина стека size =", size) + + // Проверка на пустоту + isEmpty := stack.isEmpty() + fmt.Println("Пуст ли стек =", isEmpty) +} + +func TestLinkedListStack(t *testing.T) { + // Инициализация стека + stack := newLinkedListStack() + // Помещение элемента в стек + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("Стек stack = ") + PrintList(stack.toList()) + + // Доступ к верхнему элементу стека + peek := stack.peek() + fmt.Println("Верхний элемент peek =", peek) + + // Извлечение элемента из стека + pop := stack.pop() + fmt.Print("Извлеченный элемент pop = ", pop, ", stack после извлечения = ") + PrintList(stack.toList()) + + // Получение длины стека + size := stack.size() + fmt.Println("Длина стека size =", size) + + // Проверка на пустоту + isEmpty := stack.isEmpty() + fmt.Println("Пуст ли стек =", isEmpty) +} + +// BenchmarkArrayStack 8 ns/op in Mac M1 Pro +func BenchmarkArrayStack(b *testing.B) { + stack := newArrayStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} + +// BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro +func BenchmarkLinkedListStack(b *testing.B) { + stack := newLinkedListStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} diff --git a/ru/codes/go/chapter_tree/array_binary_tree.go b/ru/codes/go/chapter_tree/array_binary_tree.go new file mode 100644 index 000000000..43d324964 --- /dev/null +++ b/ru/codes/go/chapter_tree/array_binary_tree.go @@ -0,0 +1,101 @@ +// File: array_binary_tree.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +/* Класс двоичного дерева в массивном представлении */ +type arrayBinaryTree struct { + tree []any +} + +/* Конструктор */ +func newArrayBinaryTree(arr []any) *arrayBinaryTree { + return &arrayBinaryTree{ + tree: arr, + } +} + +/* Вместимость списка */ +func (abt *arrayBinaryTree) size() int { + return len(abt.tree) +} + +/* Получить значение узла с индексом i */ +func (abt *arrayBinaryTree) val(i int) any { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if i < 0 || i >= abt.size() { + return nil + } + return abt.tree[i] +} + +/* Получить индекс левого дочернего узла узла с индексом i */ +func (abt *arrayBinaryTree) left(i int) int { + return 2*i + 1 +} + +/* Получить индекс правого дочернего узла узла с индексом i */ +func (abt *arrayBinaryTree) right(i int) int { + return 2*i + 2 +} + +/* Получить индекс родительского узла узла с индексом i */ +func (abt *arrayBinaryTree) parent(i int) int { + return (i - 1) / 2 +} + +/* Обход в ширину */ +func (abt *arrayBinaryTree) levelOrder() []any { + var res []any + // Непосредственно обходить массив + for i := 0; i < abt.size(); i++ { + if abt.val(i) != nil { + res = append(res, abt.val(i)) + } + } + return res +} + +/* Обход в глубину */ +func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { + // Если это пустая позиция, вернуть + if abt.val(i) == nil { + return + } + // Предварительный обход + if order == "pre" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.left(i), order, res) + // Симметричный обход + if order == "in" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.right(i), order, res) + // Обратный обход + if order == "post" { + *res = append(*res, abt.val(i)) + } +} + +/* Предварительный обход */ +func (abt *arrayBinaryTree) preOrder() []any { + var res []any + abt.dfs(0, "pre", &res) + return res +} + +/* Симметричный обход */ +func (abt *arrayBinaryTree) inOrder() []any { + var res []any + abt.dfs(0, "in", &res) + return res +} + +/* Обратный обход */ +func (abt *arrayBinaryTree) postOrder() []any { + var res []any + abt.dfs(0, "post", &res) + return res +} diff --git a/ru/codes/go/chapter_tree/array_binary_tree_test.go b/ru/codes/go/chapter_tree/array_binary_tree_test.go new file mode 100644 index 000000000..cbe4c0134 --- /dev/null +++ b/ru/codes/go/chapter_tree/array_binary_tree_test.go @@ -0,0 +1,47 @@ +// File: array_binary_tree_test.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestArrayBinaryTree(t *testing.T) { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + root := SliceToTree(arr) + fmt.Println("\nИнициализация двоичного дерева") + fmt.Println("Массивное представление двоичного дерева:") + fmt.Println(arr) + fmt.Println("Связное представление двоичного дерева:") + PrintTree(root) + + // Класс двоичного дерева в массивном представлении + abt := newArrayBinaryTree(arr) + + // Доступ к узлу + i := 1 + l := abt.left(i) + r := abt.right(i) + p := abt.parent(i) + fmt.Println("\nТекущий узел: индекс =", i, ", значение =", abt.val(i)) + fmt.Println("Индекс левого дочернего узла =", l, ", значение =", abt.val(l)) + fmt.Println("Индекс правого дочернего узла =", r, ", значение =", abt.val(r)) + fmt.Println("Индекс родительского узла =", p, ", значение =", abt.val(p)) + + // Обходить дерево + res := abt.levelOrder() + fmt.Println("\nОбход в ширину =", res) + res = abt.preOrder() + fmt.Println("Предварительный обход =", res) + res = abt.inOrder() + fmt.Println("Симметричный обход =", res) + res = abt.postOrder() + fmt.Println("Обратный обход =", res) +} diff --git a/ru/codes/go/chapter_tree/avl_tree.go b/ru/codes/go/chapter_tree/avl_tree.go new file mode 100644 index 000000000..72237b0e7 --- /dev/null +++ b/ru/codes/go/chapter_tree/avl_tree.go @@ -0,0 +1,200 @@ +// File: avl_tree.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import . "github.com/krahets/hello-algo/pkg" + +/* AVL-дерево */ +type aVLTree struct { + // Корневой узел + root *TreeNode +} + +func newAVLTree() *aVLTree { + return &aVLTree{root: nil} +} + +/* Получить высоту узла */ +func (t *aVLTree) height(node *TreeNode) int { + // Высота пустого узла равна -1, высота листового узла равна 0 + if node != nil { + return node.Height + } + return -1 +} + +/* Обновить высоту узла */ +func (t *aVLTree) updateHeight(node *TreeNode) { + lh := t.height(node.Left) + rh := t.height(node.Right) + // Высота узла равна высоте более высокого поддерева + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } +} + +/* Получить коэффициент баланса */ +func (t *aVLTree) balanceFactor(node *TreeNode) int { + // Коэффициент баланса пустого узла равен 0 + if node == nil { + return 0 + } + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return t.height(node.Left) - t.height(node.Right) +} + +/* Операция правого вращения */ +func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // Выполнить правое вращение узла node вокруг child + child.Right = node + node.Left = grandChild + // Обновить высоту узла + t.updateHeight(node) + t.updateHeight(child) + // Вернуть корневой узел поддерева после вращения + return child +} + +/* Операция левого вращения */ +func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // Выполнить левое вращение узла node вокруг child + child.Left = node + node.Right = grandChild + // Обновить высоту узла + t.updateHeight(node) + t.updateHeight(child) + // Вернуть корневой узел поддерева после вращения + return child +} + +/* Выполнить вращение, чтобы снова сбалансировать поддерево */ +func (t *aVLTree) rotate(node *TreeNode) *TreeNode { + // Получить коэффициент баланса узла node + // В Go рекомендуется использовать короткие имена переменных, здесь bf обозначает t.balanceFactor + bf := t.balanceFactor(node) + // Левосторонне перекошенное дерево + if bf > 1 { + if t.balanceFactor(node.Left) >= 0 { + // Правое вращение + return t.rightRotate(node) + } else { + // Сначала левое вращение, затем правое + node.Left = t.leftRotate(node.Left) + return t.rightRotate(node) + } + } + // Правосторонне перекошенное дерево + if bf < -1 { + if t.balanceFactor(node.Right) <= 0 { + // Левое вращение + return t.leftRotate(node) + } else { + // Сначала правое вращение, затем левое + node.Right = t.rightRotate(node.Right) + return t.leftRotate(node) + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node +} + +/* Вставка узла */ +func (t *aVLTree) insert(val int) { + t.root = t.insertHelper(t.root, val) +} + +/* Рекурсивная вставка узла (вспомогательная функция) */ +func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. Найти позицию вставки и вставить узел */ + if val < node.Val.(int) { + node.Left = t.insertHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.insertHelper(node.Right, val) + } else { + // Повторяющийся узел не вставлять, сразу вернуть + return node + } + // Обновить высоту узла + t.updateHeight(node) + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = t.rotate(node) + // Вернуть корневой узел поддерева + return node +} + +/* Удаление узла */ +func (t *aVLTree) remove(val int) { + t.root = t.removeHelper(t.root, val) +} + +/* Рекурсивное удаление узла (вспомогательная функция) */ +func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. Найти узел и удалить его */ + if val < node.Val.(int) { + node.Left = t.removeHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + if child == nil { + // Число дочерних узлов = 0, удалить node и сразу вернуть + return nil + } else { + // Число дочерних узлов = 1, удалить node напрямую + node = child + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + temp := node.Right + for temp.Left != nil { + temp = temp.Left + } + node.Right = t.removeHelper(node.Right, temp.Val.(int)) + node.Val = temp.Val + } + } + // Обновить высоту узла + t.updateHeight(node) + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = t.rotate(node) + // Вернуть корневой узел поддерева + return node +} + +/* Поиск узла */ +func (t *aVLTree) search(val int) *TreeNode { + cur := t.root + // Искать в цикле и выйти после прохода за листовой узел + for cur != nil { + if cur.Val.(int) < val { + // Целевой узел находится в правом поддереве cur + cur = cur.Right + } else if cur.Val.(int) > val { + // Целевой узел находится в левом поддереве cur + cur = cur.Left + } else { + // Найти целевой узел и выйти из цикла + break + } + } + // Вернуть целевой узел + return cur +} diff --git a/ru/codes/go/chapter_tree/avl_tree_test.go b/ru/codes/go/chapter_tree/avl_tree_test.go new file mode 100644 index 000000000..4df4a66ea --- /dev/null +++ b/ru/codes/go/chapter_tree/avl_tree_test.go @@ -0,0 +1,54 @@ +// File: avl_tree_test.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestAVLTree(t *testing.T) { + /* Инициализация пустого AVL-дерева */ + tree := newAVLTree() + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(tree, 1) + testInsert(tree, 2) + testInsert(tree, 3) + testInsert(tree, 4) + testInsert(tree, 5) + testInsert(tree, 8) + testInsert(tree, 7) + testInsert(tree, 9) + testInsert(tree, 10) + testInsert(tree, 6) + + /* Вставка повторяющегося узла */ + testInsert(tree, 7) + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(tree, 8) // Удаление узла степени 0 + testRemove(tree, 5) // Удаление узла степени 1 + testRemove(tree, 4) // Удаление узла степени 2 + + /* Поиск узла */ + node := tree.search(7) + fmt.Printf("\nНайденный объект узла = %#v, значение узла = %d\n", node, node.Val) +} + +func testInsert(tree *aVLTree, val int) { + tree.insert(val) + fmt.Printf("\nПосле вставки узла %d AVL-дерево имеет вид\n", val) + PrintTree(tree.root) +} + +func testRemove(tree *aVLTree, val int) { + tree.remove(val) + fmt.Printf("\nПосле удаления узла %d AVL-дерево имеет вид\n", val) + PrintTree(tree.root) +} diff --git a/ru/codes/go/chapter_tree/binary_search_tree.go b/ru/codes/go/chapter_tree/binary_search_tree.go new file mode 100644 index 000000000..55b924419 --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_search_tree.go @@ -0,0 +1,142 @@ +// File: binary_search_tree.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +type binarySearchTree struct { + root *TreeNode +} + +func newBinarySearchTree() *binarySearchTree { + bst := &binarySearchTree{} + // Инициализировать пустое дерево + bst.root = nil + return bst +} + +/* Получить корневой узел */ +func (bst *binarySearchTree) getRoot() *TreeNode { + return bst.root +} + +/* Поиск узла */ +func (bst *binarySearchTree) search(num int) *TreeNode { + node := bst.root + // Искать в цикле и выйти после прохода за листовой узел + for node != nil { + if node.Val.(int) < num { + // Целевой узел находится в правом поддереве cur + node = node.Right + } else if node.Val.(int) > num { + // Целевой узел находится в левом поддереве cur + node = node.Left + } else { + // Найти целевой узел и выйти из цикла + break + } + } + // Вернуть целевой узел + return node +} + +/* Вставка узла */ +func (bst *binarySearchTree) insert(num int) { + cur := bst.root + // Если дерево пусто, инициализировать корневой узел + if cur == nil { + bst.root = NewTreeNode(num) + return + } + // Позиция узла, предшествующего вставляемому + var pre *TreeNode = nil + // Искать в цикле и выйти после прохода за листовой узел + for cur != nil { + if cur.Val == num { + return + } + pre = cur + if cur.Val.(int) < num { + cur = cur.Right + } else { + cur = cur.Left + } + } + // Вставка узла + node := NewTreeNode(num) + if pre.Val.(int) < num { + pre.Right = node + } else { + pre.Left = node + } +} + +/* Удаление узла */ +func (bst *binarySearchTree) remove(num int) { + cur := bst.root + // Если дерево пусто, сразу вернуть + if cur == nil { + return + } + // Позиция узла, предшествующего удаляемому + var pre *TreeNode = nil + // Искать в цикле и выйти после прохода за листовой узел + for cur != nil { + if cur.Val == num { + break + } + pre = cur + if cur.Val.(int) < num { + // Удаляемый узел находится в правом поддереве + cur = cur.Right + } else { + // Удаляемый узел находится в левом поддереве + cur = cur.Left + } + } + // Если узел для удаления отсутствует, сразу вернуть + if cur == nil { + return + } + // Число дочерних узлов равно 0 или 1 + if cur.Left == nil || cur.Right == nil { + var child *TreeNode = nil + // Извлечь дочерний узел удаляемого узла + if cur.Left != nil { + child = cur.Left + } else { + child = cur.Right + } + // Удалить узел cur + if cur != bst.root { + if pre.Left == cur { + pre.Left = child + } else { + pre.Right = child + } + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + bst.root = child + } + // Число дочерних узлов равно 2 + } else { + // Получить следующий после cur узел в симметричном обходе для удаляемого узла + tmp := cur.Right + for tmp.Left != nil { + tmp = tmp.Left + } + // Рекурсивно удалить узел tmp + bst.remove(tmp.Val.(int)) + // Перезаписать cur значением tmp + cur.Val = tmp.Val + } +} + +/* Вывести двоичное дерево поиска */ +func (bst *binarySearchTree) print() { + PrintTree(bst.root) +} diff --git a/ru/codes/go/chapter_tree/binary_search_tree_test.go b/ru/codes/go/chapter_tree/binary_search_tree_test.go new file mode 100644 index 000000000..529c24e79 --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_search_tree_test.go @@ -0,0 +1,45 @@ +// File: binary_search_tree_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" +) + +func TestBinarySearchTree(t *testing.T) { + bst := newBinarySearchTree() + nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + for _, num := range nums { + bst.insert(num) + } + fmt.Println("\nИсходное двоичное дерево:") + bst.print() + + // Получить корневой узел + node := bst.getRoot() + fmt.Println("\nКорневой узел двоичного дерева:", node.Val) + + // Поиск узла + node = bst.search(7) + fmt.Println("Найденный объект узла =", node, ", значение узла =", node.Val) + + // Вставка узла + bst.insert(16) + fmt.Println("\nПосле вставки узла 16 двоичное дерево имеет вид:") + bst.print() + + // Удаление узла + bst.remove(1) + fmt.Println("\nПосле удаления узла 1 двоичное дерево имеет вид:") + bst.print() + bst.remove(2) + fmt.Println("\nПосле удаления узла 2 двоичное дерево имеет вид:") + bst.print() + bst.remove(4) + fmt.Println("\nПосле удаления узла 4 двоичное дерево имеет вид:") + bst.print() +} diff --git a/ru/codes/go/chapter_tree/binary_tree_bfs.go b/ru/codes/go/chapter_tree/binary_tree_bfs.go new file mode 100644 index 000000000..0df49c9ee --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_tree_bfs.go @@ -0,0 +1,35 @@ +// File: binary_tree_bfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "container/list" + + . "github.com/krahets/hello-algo/pkg" +) + +/* Обход в ширину */ +func levelOrder(root *TreeNode) []any { + // Инициализировать очередь и добавить корневой узел + queue := list.New() + queue.PushBack(root) + // Инициализировать срез для хранения последовательности обхода + nums := make([]any, 0) + for queue.Len() > 0 { + // Извлечение из очереди + node := queue.Remove(queue.Front()).(*TreeNode) + // Сохранить значение узла + nums = append(nums, node.Val) + if node.Left != nil { + // Поместить левый дочерний узел в очередь + queue.PushBack(node.Left) + } + if node.Right != nil { + // Поместить правый дочерний узел в очередь + queue.PushBack(node.Right) + } + } + return nums +} diff --git a/ru/codes/go/chapter_tree/binary_tree_bfs_test.go b/ru/codes/go/chapter_tree/binary_tree_bfs_test.go new file mode 100644 index 000000000..00798be41 --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -0,0 +1,24 @@ +// File: binary_tree_bfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLevelOrder(t *testing.T) { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\nИнициализация двоичного дерева:") + PrintTree(root) + + // Обход в ширину + nums := levelOrder(root) + fmt.Println("\nПоследовательность печати узлов при обходе в ширину =", nums) +} diff --git a/ru/codes/go/chapter_tree/binary_tree_dfs.go b/ru/codes/go/chapter_tree/binary_tree_dfs.go new file mode 100644 index 000000000..044c901ea --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_tree_dfs.go @@ -0,0 +1,44 @@ +// File: binary_tree_dfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +var nums []any + +/* Предварительный обход */ +func preOrder(node *TreeNode) { + if node == nil { + return + } + // Порядок обхода: корень -> левое поддерево -> правое поддерево + nums = append(nums, node.Val) + preOrder(node.Left) + preOrder(node.Right) +} + +/* Симметричный обход */ +func inOrder(node *TreeNode) { + if node == nil { + return + } + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(node.Left) + nums = append(nums, node.Val) + inOrder(node.Right) +} + +/* Обратный обход */ +func postOrder(node *TreeNode) { + if node == nil { + return + } + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(node.Left) + postOrder(node.Right) + nums = append(nums, node.Val) +} diff --git a/ru/codes/go/chapter_tree/binary_tree_dfs_test.go b/ru/codes/go/chapter_tree/binary_tree_dfs_test.go new file mode 100644 index 000000000..ccf9a61e2 --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -0,0 +1,35 @@ +// File: binary_tree_dfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreInPostOrderTraversal(t *testing.T) { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\nИнициализация двоичного дерева:") + PrintTree(root) + + // Предварительный обход + nums = nil + preOrder(root) + fmt.Println("\nПоследовательность печати узлов при предварительном обходе =", nums) + + // Симметричный обход + nums = nil + inOrder(root) + fmt.Println("\nПоследовательность печати узлов при симметричном обходе =", nums) + + // Обратный обход + nums = nil + postOrder(root) + fmt.Println("\nПоследовательность печати узлов при обратном обходе =", nums) +} diff --git a/ru/codes/go/chapter_tree/binary_tree_test.go b/ru/codes/go/chapter_tree/binary_tree_test.go new file mode 100644 index 000000000..7a545c431 --- /dev/null +++ b/ru/codes/go/chapter_tree/binary_tree_test.go @@ -0,0 +1,41 @@ +// File: binary_tree_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBinaryTree(t *testing.T) { + /* Инициализация двоичного дерева */ + // Инициализация узла + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // Построить связи между узлами (указатели) + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + fmt.Println("Инициализация двоичного дерева") + PrintTree(n1) + + /* Вставка и удаление узлов */ + // Вставка узла + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + fmt.Println("После вставки узла P") + PrintTree(n1) + // Удаление узла + n1.Left = n2 + fmt.Println("После удаления узла P") + PrintTree(n1) +} diff --git a/ru/codes/go/go.mod b/ru/codes/go/go.mod new file mode 100644 index 000000000..34f5dac20 --- /dev/null +++ b/ru/codes/go/go.mod @@ -0,0 +1,3 @@ +module github.com/krahets/hello-algo + +go 1.19 diff --git a/ru/codes/go/pkg/list_node.go b/ru/codes/go/pkg/list_node.go new file mode 100644 index 000000000..3964b166e --- /dev/null +++ b/ru/codes/go/pkg/list_node.go @@ -0,0 +1,31 @@ +// File: list_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// ListNode: узел связного списка +type ListNode struct { + Next *ListNode + Val int +} + +// NewListNode: конструктор узла связного списка +func NewListNode(v int) *ListNode { + return &ListNode{ + Next: nil, + Val: v, + } +} + +// ArrayToLinkedList десериализует массив в связный список +func ArrayToLinkedList(arr []int) *ListNode { + // dummy header of linked list + dummy := NewListNode(0) + node := dummy + for _, val := range arr { + node.Next = NewListNode(val) + node = node.Next + } + return dummy.Next +} diff --git a/ru/codes/go/pkg/list_node_test.go b/ru/codes/go/pkg/list_node_test.go new file mode 100644 index 000000000..e61d8d5bf --- /dev/null +++ b/ru/codes/go/pkg/list_node_test.go @@ -0,0 +1,16 @@ +// File: list_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "testing" +) + +func TestListNode(t *testing.T) { + arr := []int{2, 3, 5, 6, 7} + head := ArrayToLinkedList(arr) + + PrintLinkedList(head) +} diff --git a/ru/codes/go/pkg/print_utils.go b/ru/codes/go/pkg/print_utils.go new file mode 100644 index 000000000..46d933a52 --- /dev/null +++ b/ru/codes/go/pkg/print_utils.go @@ -0,0 +1,118 @@ +// File: print_utils.go +// Created Time: 2022-12-03 +// Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) + +package pkg + +import ( + "container/list" + "fmt" + "strconv" + "strings" +) + +// PrintSlice: вывести срез +func PrintSlice[T any](nums []T) { + fmt.Printf("%v", nums) + fmt.Println() +} + +// PrintList: вывести список +func PrintList(list *list.List) { + if list.Len() == 0 { + fmt.Print("[]\n") + return + } + e := list.Front() + // Преобразование к string повлияет на эффективность + fmt.Print("[") + for e.Next() != nil { + fmt.Print(e.Value, " ") + e = e.Next() + } + fmt.Print(e.Value, "]\n") +} + +// PrintMap: вывести хеш-таблицу +func PrintMap[K comparable, V any](m map[K]V) { + for key, value := range m { + fmt.Println(key, "->", value) + } +} + +// PrintHeap: вывести кучу +func PrintHeap(h []any) { + fmt.Printf("Массивное представление кучи:") + fmt.Printf("%v", h) + fmt.Printf("\nДревовидное представление кучи:\n") + root := SliceToTree(h) + PrintTree(root) +} + +// PrintLinkedList: вывести связный список +func PrintLinkedList(node *ListNode) { + if node == nil { + return + } + var builder strings.Builder + for node.Next != nil { + builder.WriteString(strconv.Itoa(node.Val) + " -> ") + node = node.Next + } + builder.WriteString(strconv.Itoa(node.Val)) + fmt.Println(builder.String()) +} + +// PrintTree: вывести двоичное дерево +func PrintTree(root *TreeNode) { + printTreeHelper(root, nil, false) +} + +// printTreeHelper: вывести двоичное дерево +// Этот вывод дерева заимствован из TECHIE DELIGHT +// https://www.techiedelight.com/c-program-print-binary-tree/ +func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { + if root == nil { + return + } + prevStr := " " + trunk := newTrunk(prev, prevStr) + printTreeHelper(root.Right, trunk, true) + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + showTrunk(trunk) + fmt.Println(root.Val) + if prev != nil { + prev.str = prevStr + } + trunk.str = " |" + printTreeHelper(root.Left, trunk, false) +} + +type trunk struct { + prev *trunk + str string +} + +func newTrunk(prev *trunk, str string) *trunk { + return &trunk{ + prev: prev, + str: str, + } +} + +func showTrunk(t *trunk) { + if t == nil { + return + } + + showTrunk(t.prev) + fmt.Print(t.str) +} diff --git a/ru/codes/go/pkg/tree_node.go b/ru/codes/go/pkg/tree_node.go new file mode 100644 index 000000000..877bdf546 --- /dev/null +++ b/ru/codes/go/pkg/tree_node.go @@ -0,0 +1,78 @@ +// File: tree_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// TreeNode: узел двоичного дерева +type TreeNode struct { + Val any // Значение узла + Height int // Высота узла + Left *TreeNode // Ссылка на левый дочерний узел + Right *TreeNode // Ссылка на правый дочерний узел +} + +// NewTreeNode: конструктор узла двоичного дерева +func NewTreeNode(v any) *TreeNode { + return &TreeNode{ + Val: v, + Height: 0, + Left: nil, + Right: nil, + } +} + +// Правила кодирования сериализации см.: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// Представление двоичного дерева массивом: +// [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] +// Представление двоичного дерева связным списком: +// +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// +// ——— 1 +// +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +// SliceToTreeDFS десериализует список в двоичное дерево: рекурсия +func SliceToTreeDFS(arr []any, i int) *TreeNode { + if i < 0 || i >= len(arr) || arr[i] == nil { + return nil + } + root := NewTreeNode(arr[i]) + root.Left = SliceToTreeDFS(arr, 2*i+1) + root.Right = SliceToTreeDFS(arr, 2*i+2) + return root +} + +// SliceToTree десериализует срез в двоичное дерево +func SliceToTree(arr []any) *TreeNode { + return SliceToTreeDFS(arr, 0) +} + +// TreeToSliceDFS сериализует двоичное дерево в срез: рекурсия +func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { + if root == nil { + return + } + for i >= len(*res) { + *res = append(*res, nil) + } + (*res)[i] = root.Val + TreeToSliceDFS(root.Left, 2*i+1, res) + TreeToSliceDFS(root.Right, 2*i+2, res) +} + +// TreeToSlice сериализует двоичное дерево в срез +func TreeToSlice(root *TreeNode) []any { + var res []any + TreeToSliceDFS(root, 0, &res) + return res +} diff --git a/ru/codes/go/pkg/tree_node_test.go b/ru/codes/go/pkg/tree_node_test.go new file mode 100644 index 000000000..043aab099 --- /dev/null +++ b/ru/codes/go/pkg/tree_node_test.go @@ -0,0 +1,21 @@ +// File: tree_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "fmt" + "testing" +) + +func TestTreeNode(t *testing.T) { + arr := []any{1, 2, 3, nil, 5, 6, nil} + node := SliceToTree(arr) + + // print tree + PrintTree(node) + + // tree to arr + fmt.Println(TreeToSlice(node)) +} diff --git a/ru/codes/go/pkg/vertex.go b/ru/codes/go/pkg/vertex.go new file mode 100644 index 000000000..a065f20a9 --- /dev/null +++ b/ru/codes/go/pkg/vertex.go @@ -0,0 +1,55 @@ +// File: vertex.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package pkg + +// Vertex: класс вершины +type Vertex struct { + Val int +} + +// NewVertex: конструктор вершины +func NewVertex(val int) Vertex { + return Vertex{ + Val: val, + } +} + +// ValsToVets десериализует список значений в список вершин +func ValsToVets(vals []int) []Vertex { + vets := make([]Vertex, len(vals)) + for i := 0; i < len(vals); i++ { + vets[i] = NewVertex(vals[i]) + } + return vets +} + +// VetsToVals сериализует список вершин в список значений +func VetsToVals(vets []Vertex) []int { + vals := make([]int, len(vets)) + for i := range vets { + vals[i] = vets[i].Val + } + return vals +} + +// DeleteSliceElms удаляет указанный элемент из среза +func DeleteSliceElms[T any](a []T, elms ...T) []T { + if len(a) == 0 || len(elms) == 0 { + return a + } + // Сначала преобразовать элементы в set + m := make(map[any]struct{}) + for _, v := range elms { + m[v] = struct{}{} + } + // Отфильтровать указанный элемент + res := make([]T, 0, len(a)) + for _, v := range a { + if _, ok := m[v]; !ok { + res = append(res, v) + } + } + return res +} diff --git a/ru/codes/java/.gitignore b/ru/codes/java/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/ru/codes/java/.gitignore @@ -0,0 +1 @@ +build diff --git a/ru/codes/java/chapter_array_and_linkedlist/array.java b/ru/codes/java/chapter_array_and_linkedlist/array.java new file mode 100644 index 000000000..a23338b00 --- /dev/null +++ b/ru/codes/java/chapter_array_and_linkedlist/array.java @@ -0,0 +1,105 @@ +/** + * File: array.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class array { + /* Случайный доступ к элементу */ + static int randomAccess(int[] nums) { + // Случайным образом выбрать число из интервала [0, nums.length) + int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); + // Получить и вернуть случайный элемент + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* Увеличить длину массива */ + static int[] extend(int[] nums, int enlarge) { + // Инициализировать массив увеличенной длины + int[] res = new int[nums.length + enlarge]; + // Скопировать все элементы исходного массива в новый массив + for (int i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // Вернуть новый массив после расширения + return res; + } + + /* Вставить элемент num по индексу index в массив */ + static void insert(int[] nums, int num, int index) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (int i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; + } + + /* Удалить элемент по индексу index */ + static void remove(int[] nums, int index) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* Обход массива */ + static void traverse(int[] nums) { + int count = 0; + // Обход массива по индексам + for (int i = 0; i < nums.length; i++) { + count += nums[i]; + } + // Непосредственно обходить элементы массива + for (int num : nums) { + count += num; + } + } + + /* Найти заданный элемент в массиве */ + static int find(int[] nums, int target) { + for (int i = 0; i < nums.length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* Инициализация массива */ + int[] arr = new int[5]; + System.out.println("Массив arr = " + Arrays.toString(arr)); + int[] nums = { 1, 3, 2, 5, 4 }; + System.out.println("Массив nums = " + Arrays.toString(nums)); + + /* Случайный доступ */ + int randomNum = randomAccess(nums); + System.out.println("Случайный элемент из nums = " + randomNum); + + /* Расширение длины */ + nums = extend(nums, 3); + System.out.println("После увеличения длины массива до 8 nums = " + Arrays.toString(nums)); + + /* Вставка элемента */ + insert(nums, 6, 3); + System.out.println("После вставки числа 6 по индексу 3 nums = " + Arrays.toString(nums)); + + /* Удаление элемента */ + remove(nums, 2); + System.out.println("После удаления элемента по индексу 2 nums = " + Arrays.toString(nums)); + + /* Обход массива */ + traverse(nums); + + /* Поиск элемента */ + int index = find(nums, 3); + System.out.println("Поиск элемента 3 в nums: индекс = " + index); + } +} diff --git a/ru/codes/java/chapter_array_and_linkedlist/linked_list.java b/ru/codes/java/chapter_array_and_linkedlist/linked_list.java new file mode 100644 index 000000000..7b74e5e7b --- /dev/null +++ b/ru/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -0,0 +1,86 @@ +/** + * File: linked_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import utils.*; + +public class linked_list { + /* Вставить узел P после узла n0 в связном списке */ + static void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* Удалить первый узел после узла n0 в связном списке */ + static void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode n1 = P.next; + n0.next = n1; + } + + /* Доступ к узлу связного списка по индексу index */ + static ListNode access(ListNode head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* Найти в связном списке первый узел со значением target */ + static int find(ListNode head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* Инициализация связного списка */ + // Инициализация всех узлов + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // Построить ссылки между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + System.out.println("Исходный связный список"); + PrintUtil.printLinkedList(n0); + + /* Вставка узла */ + insert(n0, new ListNode(0)); + System.out.println("Связный список после вставки узла"); + PrintUtil.printLinkedList(n0); + + /* Удаление узла */ + remove(n0); + System.out.println("Связный список после удаления узла"); + PrintUtil.printLinkedList(n0); + + /* Доступ к узлу */ + ListNode node = access(n0, 3); + System.out.println("Значение узла по индексу 3 в связном списке = " + node.val); + + /* Поиск узла */ + int index = find(n0, 2); + System.out.println("Индекс узла со значением 2 в связном списке = " + index); + } +} diff --git a/ru/codes/java/chapter_array_and_linkedlist/list.java b/ru/codes/java/chapter_array_and_linkedlist/list.java new file mode 100644 index 000000000..cbb5c4beb --- /dev/null +++ b/ru/codes/java/chapter_array_and_linkedlist/list.java @@ -0,0 +1,66 @@ +/** + * File: list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +public class list { + public static void main(String[] args) { + /* Инициализация списка */ + // Обратите внимание: тип элементов массива int[] — это обертка Integer[] + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + System.out.println("Список nums = " + nums); + + /* Доступ к элементу */ + int num = nums.get(1); + System.out.println("Элемент по индексу 1: num = " + num); + + /* Обновление элемента */ + nums.set(1, 0); + System.out.println("После обновления элемента по индексу 1 до 0 nums = " + nums); + + /* Очистить список */ + nums.clear(); + System.out.println("После очистки списка nums = " + nums); + + /* Добавление элемента в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("После добавления элементов nums = " + nums); + + /* Вставка элемента в середину */ + nums.add(3, 6); + System.out.println("После вставки числа 6 по индексу 3 nums = " + nums); + + /* Удаление элемента */ + nums.remove(3); + System.out.println("После удаления элемента по индексу 3 nums = " + nums); + + /* Обходить список по индексам */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + /* Непосредственно обходить элементы списка */ + for (int x : nums) { + count += x; + } + + /* Объединить два списка */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); + System.out.println("После конкатенации списка nums1 к nums nums = " + nums); + + /* Отсортировать список */ + Collections.sort(nums); + System.out.println("После сортировки списка nums = " + nums); + } +} diff --git a/ru/codes/java/chapter_array_and_linkedlist/my_list.java b/ru/codes/java/chapter_array_and_linkedlist/my_list.java new file mode 100644 index 000000000..db42a96da --- /dev/null +++ b/ru/codes/java/chapter_array_and_linkedlist/my_list.java @@ -0,0 +1,147 @@ +/** + * File: my_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +/* Класс списка */ +class MyList { + private int[] arr; // Массив (для хранения элементов списка) + private int capacity = 10; // Вместимость списка + private int size = 0; // Длина списка (текущее число элементов) + private int extendRatio = 2; // Коэффициент увеличения списка при каждом расширении + + /* Конструктор */ + public MyList() { + arr = new int[capacity]; + } + + /* Получить длину списка (текущее число элементов) */ + public int size() { + return size; + } + + /* Получить вместимость списка */ + public int capacity() { + return capacity; + } + + /* Доступ к элементу */ + public int get(int index) { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("индекс выходит за границы"); + return arr[index]; + } + + /* Обновление элемента */ + public void set(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("индекс выходит за границы"); + arr[index] = num; + } + + /* Добавление элемента в конец */ + public void add(int num) { + // При превышении вместимости по числу элементов запускается расширение + if (size == capacity()) + extendCapacity(); + arr[size] = num; + // Обновить число элементов + size++; + } + + /* Вставка элемента в середину */ + public void insert(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("индекс выходит за границы"); + // При превышении вместимости по числу элементов запускается расширение + if (size == capacity()) + extendCapacity(); + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (int j = size - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // Обновить число элементов + size++; + } + + /* Удаление элемента */ + public int remove(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("индекс выходит за границы"); + int num = arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (int j = index; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + // Обновить число элементов + size--; + // Вернуть удаленный элемент + return num; + } + + /* Расширение списка */ + public void extendCapacity() { + // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив + arr = Arrays.copyOf(arr, capacity() * extendRatio); + // Обновить вместимость списка + capacity = arr.length; + } + + /* Преобразовать список в массив */ + public int[] toArray() { + int size = size(); + // Преобразовывать только элементы списка в пределах фактической длины + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = get(i); + } + return arr; + } +} + +public class my_list { + /* Driver Code */ + public static void main(String[] args) { + /* Инициализация списка */ + MyList nums = new MyList(); + /* Добавление элемента в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("Список nums = " + Arrays.toString(nums.toArray()) + + ", вместимость = " + nums.capacity() + " , длина = " + nums.size()); + + /* Вставка элемента в середину */ + nums.insert(3, 6); + System.out.println("После вставки числа 6 по индексу 3 nums = " + Arrays.toString(nums.toArray())); + + /* Удаление элемента */ + nums.remove(3); + System.out.println("После удаления элемента по индексу 3 nums = " + Arrays.toString(nums.toArray())); + + /* Доступ к элементу */ + int num = nums.get(1); + System.out.println("Элемент по индексу 1: num = " + num); + + /* Обновление элемента */ + nums.set(1, 0); + System.out.println("После обновления элемента по индексу 1 до 0 nums = " + Arrays.toString(nums.toArray())); + + /* Проверка механизма расширения */ + for (int i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i); + } + System.out.println("Список nums после увеличения вместимости = " + Arrays.toString(nums.toArray()) + + ", вместимость = " + nums.capacity() + " , длина = " + nums.size()); + } +} diff --git a/ru/codes/java/chapter_backtracking/n_queens.java b/ru/codes/java/chapter_backtracking/n_queens.java new file mode 100644 index 000000000..0f1aaedf0 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/n_queens.java @@ -0,0 +1,77 @@ +/** + * File: n_queens.java + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class n_queens { + /* Алгоритм бэктрекинга: n ферзей */ + public static void backtrack(int row, int n, List> state, List>> res, + boolean[] cols, boolean[] diags1, boolean[] diags2) { + // Когда все строки уже обработаны, записать решение + if (row == n) { + List> copyState = new ArrayList<>(); + for (List sRow : state) { + copyState.add(new ArrayList<>(sRow)); + } + res.add(copyState); + return; + } + // Обойти все столбцы + for (int col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + int diag1 = row - col + n - 1; + int diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state.get(row).set(col, "Q"); + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state.get(row).set(col, "#"); + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* Решить задачу о n ферзях */ + public static List>> nQueens(int n) { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + List> state = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List row = new ArrayList<>(); + for (int j = 0; j < n; j++) { + row.add("#"); + } + state.add(row); + } + boolean[] cols = new boolean[n]; // Отмечать, есть ли ферзь в столбце + boolean[] diags1 = new boolean[2 * n - 1]; // Отмечать наличие ферзя на главной диагонали + boolean[] diags2 = new boolean[2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали + List>> res = new ArrayList<>(); + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + public static void main(String[] args) { + int n = 4; + List>> res = nQueens(n); + + System.out.println("Размер входной доски = " + n); + System.out.println("Количество способов расстановки ферзей: " + res.size()); + for (List> state : res) { + System.out.println("--------------------"); + for (List row : state) { + System.out.println(row); + } + } + } +} diff --git a/ru/codes/java/chapter_backtracking/permutations_i.java b/ru/codes/java/chapter_backtracking/permutations_i.java new file mode 100644 index 000000000..9d799a949 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/permutations_i.java @@ -0,0 +1,51 @@ +/** + * File: permutations_i.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_i { + /* Алгоритм бэктрекинга: все перестановки I */ + public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.add(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* Все перестановки I */ + static List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3 }; + + List> res = permutationsI(nums); + + System.out.println("Входной массив nums = " + Arrays.toString(nums)); + System.out.println("Все перестановки res = " + res); + } +} diff --git a/ru/codes/java/chapter_backtracking/permutations_ii.java b/ru/codes/java/chapter_backtracking/permutations_ii.java new file mode 100644 index 000000000..dd3c898e7 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/permutations_ii.java @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_ii { + /* Алгоритм бэктрекинга: все перестановки II */ + static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // Перебор всех вариантов выбора + Set duplicated = new HashSet(); + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated.contains(choice)) { + // Попытка: сделать выбор и обновить состояние + duplicated.add(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.add(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* Все перестановки II */ + static List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 2 }; + + List> res = permutationsII(nums); + + System.out.println("Входной массив nums = " + Arrays.toString(nums)); + System.out.println("Все перестановки res = " + res); + } +} diff --git a/ru/codes/java/chapter_backtracking/preorder_traversal_i_compact.java b/ru/codes/java/chapter_backtracking/preorder_traversal_i_compact.java new file mode 100644 index 000000000..9f8067756 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_i_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_i_compact { + static List res; + + /* Предварительный обход: пример 1 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // Записать решение + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nИнициализация двоичного дерева"); + PrintUtil.printTree(root); + + // Предварительный обход + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nВсе узлы со значением 7"); + List vals = new ArrayList<>(); + for (TreeNode node : res) { + vals.add(node.val); + } + System.out.println(vals); + } +} diff --git a/ru/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java b/ru/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java new file mode 100644 index 000000000..4b8796b5c --- /dev/null +++ b/ru/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_ii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_ii_compact { + static List path; + static List> res; + + /* Предварительный обход: пример 2 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + // Попытка + path.add(root); + if (root.val == 7) { + // Записать решение + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // Откат + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nИнициализация двоичного дерева"); + PrintUtil.printTree(root); + + // Предварительный обход + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nВсе пути от корня к узлу 7"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/ru/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/ru/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java new file mode 100644 index 000000000..1a517e235 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -0,0 +1,53 @@ +/** + * File: preorder_traversal_iii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_compact { + static List path; + static List> res; + + /* Предварительный обход: пример 3 */ + static void preOrder(TreeNode root) { + // Отсечение + if (root == null || root.val == 3) { + return; + } + // Попытка + path.add(root); + if (root.val == 7) { + // Записать решение + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // Откат + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nИнициализация двоичного дерева"); + PrintUtil.printTree(root); + + // Предварительный обход + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/ru/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/ru/codes/java/chapter_backtracking/preorder_traversal_iii_template.java new file mode 100644 index 000000000..596aee259 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -0,0 +1,77 @@ +/** + * File: preorder_traversal_iii_template.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_template { + /* Проверить, является ли текущее состояние решением */ + static boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } + + /* Записать решение */ + static void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } + + /* Проверить, допустим ли этот выбор в текущем состоянии */ + static boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* Обновить состояние */ + static void makeChoice(List state, TreeNode choice) { + state.add(choice); + } + + /* Восстановить состояние */ + static void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } + + /* Алгоритм бэктрекинга: пример 3 */ + static void backtrack(List state, List choices, List> res) { + // Проверить, является ли текущее состояние решением + if (isSolution(state)) { + // Записать решение + recordSolution(state, res); + } + // Перебор всех вариантов выбора + for (TreeNode choice : choices) { + // Отсечение: проверить допустимость выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + // Перейти к следующему выбору + backtrack(state, Arrays.asList(choice.left, choice.right), res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\nИнициализация двоичного дерева"); + PrintUtil.printTree(root); + + // Алгоритм бэктрекинга + List> res = new ArrayList<>(); + backtrack(new ArrayList<>(), Arrays.asList(root), res); + + System.out.println("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/ru/codes/java/chapter_backtracking/subset_sum_i.java b/ru/codes/java/chapter_backtracking/subset_sum_i.java new file mode 100644 index 000000000..b9fe28ff0 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/subset_sum_i.java @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i { + /* Алгоритм бэктрекинга: сумма подмножеств I */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (int i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state.add(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.remove(state.size() - 1); + } + } + + /* Решить задачу суммы подмножеств I */ + static List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // Состояние (подмножество) + Arrays.sort(nums); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + List> res = new ArrayList<>(); // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumI(nums, target); + + System.out.println("Входной массив nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("Все подмножества с суммой " + target + ": res = " + res); + } +} diff --git a/ru/codes/java/chapter_backtracking/subset_sum_i_naive.java b/ru/codes/java/chapter_backtracking/subset_sum_i_naive.java new file mode 100644 index 000000000..f08962a77 --- /dev/null +++ b/ru/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i_naive.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i_naive { + /* Алгоритм бэктрекинга: сумма подмножеств I */ + static void backtrack(List state, int target, int total, int[] choices, List> res) { + // Если сумма подмножества равна target, записать решение + if (total == target) { + res.add(new ArrayList<>(state)); + return; + } + // Перебор всех вариантов выбора + for (int i = 0; i < choices.length; i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.add(choices[i]); + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.remove(state.size() - 1); + } + } + + /* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ + static List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // Состояние (подмножество) + int total = 0; // Сумма подмножеств + List> res = new ArrayList<>(); // Список результатов (список подмножеств) + backtrack(state, target, total, nums, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + System.out.println("Входной массив nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("Все подмножества с суммой " + target + ": res = " + res); + System.out.println("Обратите внимание: результат этого метода содержит повторяющиеся множества"); + } +} diff --git a/ru/codes/java/chapter_backtracking/subset_sum_ii.java b/ru/codes/java/chapter_backtracking/subset_sum_ii.java new file mode 100644 index 000000000..78cf9d1ef --- /dev/null +++ b/ru/codes/java/chapter_backtracking/subset_sum_ii.java @@ -0,0 +1,60 @@ +/** + * File: subset_sum_ii.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_ii { + /* Алгоритм бэктрекинга: сумма подмножеств II */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (int i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.add(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.remove(state.size() - 1); + } + } + + /* Решить задачу суммы подмножеств II */ + static List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // Состояние (подмножество) + Arrays.sort(nums); // Отсортировать nums + int start = 0; // Стартовая вершина обхода + List> res = new ArrayList<>(); // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 4, 4, 5 }; + int target = 9; + + List> res = subsetSumII(nums, target); + + System.out.println("Входной массив nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("Все подмножества с суммой " + target + ": res = " + res); + } +} diff --git a/ru/codes/java/chapter_computational_complexity/iteration.java b/ru/codes/java/chapter_computational_complexity/iteration.java new file mode 100644 index 000000000..4abf6a735 --- /dev/null +++ b/ru/codes/java/chapter_computational_complexity/iteration.java @@ -0,0 +1,76 @@ +/** + * File: iteration.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class iteration { + /* Цикл for */ + static int forLoop(int n) { + int res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* Цикл while */ + static int whileLoop(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Обновить условную переменную + } + return res; + } + + /* Цикл while (двойное обновление) */ + static int whileLoopII(int n) { + int res = 0; + int i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i++; + i *= 2; + } + return res; + } + + /* Двойной цикл for */ + static String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // Цикл по i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // Цикл по j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.append("(" + i + ", " + j + "), "); + } + } + return res.toString(); + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = forLoop(n); + System.out.println("\nРезультат суммирования в цикле for res = " + res); + + res = whileLoop(n); + System.out.println("\nРезультат суммирования в цикле while res = " + res); + + res = whileLoopII(n); + System.out.println("\nРезультат суммирования в цикле while (двойное обновление) res = " + res); + + String resStr = nestedForLoop(n); + System.out.println("\nРезультат обхода в двойном цикле for " + resStr); + } +} diff --git a/ru/codes/java/chapter_computational_complexity/recursion.java b/ru/codes/java/chapter_computational_complexity/recursion.java new file mode 100644 index 000000000..050a6fe10 --- /dev/null +++ b/ru/codes/java/chapter_computational_complexity/recursion.java @@ -0,0 +1,79 @@ +/** + * File: recursion.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.Stack; + +public class recursion { + /* Рекурсия */ + static int recur(int n) { + // Условие завершения + if (n == 1) + return 1; + // Рекурсия: рекурсивный вызов + int res = recur(n - 1); + // Возврат: вернуть результат + return n + res; + } + + /* Имитация рекурсии итерацией */ + static int forLoopRecur(int n) { + // Использовать явный стек для имитации системного стека вызовов + Stack stack = new Stack<>(); + int res = 0; + // Рекурсия: рекурсивный вызов + for (int i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.push(i); + } + // Возврат: вернуть результат + while (!stack.isEmpty()) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* Хвостовая рекурсия */ + static int tailRecur(int n, int res) { + // Условие завершения + if (n == 0) + return res; + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); + } + + /* Последовательность Фибоначчи: рекурсия */ + static int fib(int n) { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = recur(n); + System.out.println("\nРезультат суммирования в рекурсивной функции res = " + res); + + res = forLoopRecur(n); + System.out.println("\nРезультат суммирования при имитации рекурсии итерацией res = " + res); + + res = tailRecur(n, 0); + System.out.println("\nРезультат суммирования в хвостовой рекурсии res = " + res); + + res = fib(n); + System.out.println("\nЧлен последовательности Фибоначчи с номером " + n + " = " + res); + } +} diff --git a/ru/codes/java/chapter_computational_complexity/space_complexity.java b/ru/codes/java/chapter_computational_complexity/space_complexity.java new file mode 100644 index 000000000..347bcc64e --- /dev/null +++ b/ru/codes/java/chapter_computational_complexity/space_complexity.java @@ -0,0 +1,110 @@ +/** + * File: space_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import utils.*; +import java.util.*; + +public class space_complexity { + /* Функция */ + static int function() { + // Выполнить некоторые операции + return 0; + } + + /* Постоянная сложность */ + static void constant(int n) { + // Константы, переменные и объекты занимают O(1) памяти + final int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // Переменные в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + int c = 0; + } + // Функции в цикле занимают O(1) памяти + for (int i = 0; i < n; i++) { + function(); + } + } + + /* Линейная сложность */ + static void linear(int n) { + // Массив длины n занимает O(n) памяти + int[] nums = new int[n]; + // Список длины n занимает O(n) памяти + List nodes = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nodes.add(new ListNode(i)); + } + // Хеш-таблица длины n занимает O(n) памяти + Map map = new HashMap<>(); + for (int i = 0; i < n; i++) { + map.put(i, String.valueOf(i)); + } + } + + /* Линейная сложность (рекурсивная реализация) */ + static void linearRecur(int n) { + System.out.println("Рекурсия n = " + n); + if (n == 1) + return; + linearRecur(n - 1); + } + + /* Квадратичная сложность */ + static void quadratic(int n) { + // Матрица занимает O(n^2) памяти + int[][] numMatrix = new int[n][n]; + // Двумерный список занимает O(n^2) памяти + List> numList = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List tmp = new ArrayList<>(); + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } + } + + /* Квадратичная сложность (рекурсивная реализация) */ + static int quadraticRecur(int n) { + if (n <= 0) + return 0; + // Длина массива nums равна n, n-1, ..., 2, 1 + int[] nums = new int[n]; + System.out.println("В рекурсии n = " + n + ", длина nums = " + nums.length); + return quadraticRecur(n - 1); + } + + /* Экспоненциальная сложность (построение полного двоичного дерева) */ + static TreeNode buildTree(int n) { + if (n == 0) + return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + // Постоянная сложность + constant(n); + // Линейная сложность + linear(n); + linearRecur(n); + // Квадратичная сложность + quadratic(n); + quadraticRecur(n); + // Экспоненциальная сложность + TreeNode root = buildTree(n); + PrintUtil.printTree(root); + } +} diff --git a/ru/codes/java/chapter_computational_complexity/time_complexity.java b/ru/codes/java/chapter_computational_complexity/time_complexity.java new file mode 100644 index 000000000..e9707ccd8 --- /dev/null +++ b/ru/codes/java/chapter_computational_complexity/time_complexity.java @@ -0,0 +1,167 @@ +/** + * File: time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class time_complexity { + /* Постоянная сложность */ + static int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* Линейная сложность */ + static int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* Линейная сложность (обход массива) */ + static int arrayTraversal(int[] nums) { + int count = 0; + // Число итераций пропорционально длине массива + for (int num : nums) { + count++; + } + return count; + } + + /* Квадратичная сложность */ + static int quadratic(int n) { + int count = 0; + // Число итераций квадратично зависит от размера данных n + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* Квадратичная сложность (пузырьковая сортировка) */ + static int bubbleSort(int[] nums) { + int count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; + } + + /* Экспоненциальная сложность (итеративная реализация) */ + static int exponential(int n) { + int count = 0, base = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* Экспоненциальная сложность (рекурсивная реализация) */ + static int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } + + /* Логарифмическая сложность (итеративная реализация) */ + static int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } + + /* Логарифмическая сложность (рекурсивная реализация) */ + static int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; + } + + /* Линейно-логарифмическая сложность */ + static int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; + } + + /* Факториальная сложность (рекурсивная реализация) */ + static int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // Из одного получается n + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; + } + + /* Driver Code */ + public static void main(String[] args) { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + int n = 8; + System.out.println("Размер входных данных n = " + n); + + int count = constant(n); + System.out.println("Число операций константной сложности = " + count); + + count = linear(n); + System.out.println("Число операций линейной сложности = " + count); + count = arrayTraversal(new int[n]); + System.out.println("Число операций линейной сложности (обход массива) = " + count); + + count = quadratic(n); + System.out.println("Число операций квадратичной сложности = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + System.out.println("Число операций квадратичной сложности (пузырьковая сортировка) = " + count); + + count = exponential(n); + System.out.println("Число операций экспоненциальной сложности (итеративная реализация) = " + count); + count = expRecur(n); + System.out.println("Число операций экспоненциальной сложности (рекурсивная реализация) = " + count); + + count = logarithmic(n); + System.out.println("Число операций логарифмической сложности (итеративная реализация) = " + count); + count = logRecur(n); + System.out.println("Число операций логарифмической сложности (рекурсивная реализация) = " + count); + + count = linearLogRecur(n); + System.out.println("Число операций линейно-логарифмической сложности (рекурсивная реализация) = " + count); + + count = factorialRecur(n); + System.out.println("Число операций факториальной сложности (рекурсивная реализация) = " + count); + } +} diff --git a/ru/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/ru/codes/java/chapter_computational_complexity/worst_best_time_complexity.java new file mode 100644 index 000000000..3dff2614b --- /dev/null +++ b/ru/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -0,0 +1,50 @@ +/** + * File: worst_best_time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.*; + +public class worst_best_time_complexity { + /* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ + static int[] randomNumbers(int n) { + Integer[] nums = new Integer[n]; + // Создать массив nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Случайно перемешать элементы массива + Collections.shuffle(Arrays.asList(nums)); + // Integer[] -> int[] + int[] res = new int[n]; + for (int i = 0; i < n; i++) { + res[i] = nums[i]; + } + return res; + } + + /* Найти индекс числа 1 в массиве nums */ + static int findOne(int[] nums) { + for (int i = 0; i < nums.length; i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = randomNumbers(n); + int index = findOne(nums); + System.out.println("\nМассив [1, 2, ..., n] после перемешивания = " + Arrays.toString(nums)); + System.out.println("Индекс числа 1 = " + index); + } + } +} diff --git a/ru/codes/java/chapter_divide_and_conquer/binary_search_recur.java b/ru/codes/java/chapter_divide_and_conquer/binary_search_recur.java new file mode 100644 index 000000000..55a2640b9 --- /dev/null +++ b/ru/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -0,0 +1,45 @@ +/** + * File: binary_search_recur.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +public class binary_search_recur { + /* Бинарный поиск: задача f(i, j) */ + static int dfs(int[] nums, int target, int i, int j) { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + int m = (i + j) / 2; + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + + /* Бинарный поиск */ + static int binarySearch(int[] nums, int target) { + int n = nums.length; + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1); + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + // Бинарный поиск (двусторонне замкнутый интервал) + int index = binarySearch(nums, target); + System.out.println("Индекс целевого элемента 6 = " + index); + } +} diff --git a/ru/codes/java/chapter_divide_and_conquer/build_tree.java b/ru/codes/java/chapter_divide_and_conquer/build_tree.java new file mode 100644 index 000000000..a0faeab3b --- /dev/null +++ b/ru/codes/java/chapter_divide_and_conquer/build_tree.java @@ -0,0 +1,51 @@ +/** + * File: build_tree.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import utils.*; +import java.util.*; + +public class build_tree { + /* Построить двоичное дерево: разделяй и властвуй */ + static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) + return null; + // Инициализировать корневой узел + TreeNode root = new TreeNode(preorder[i]); + // Найти m, чтобы разделить левое и правое поддеревья + int m = inorderMap.get(preorder[i]); + // Подзадача: построить левое поддерево + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + return root; + } + + /* Построить двоичное дерево */ + static TreeNode buildTree(int[] preorder, int[] inorder) { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + Map inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; + } + + public static void main(String[] args) { + int[] preorder = { 3, 9, 2, 1, 7 }; + int[] inorder = { 9, 3, 1, 2, 7 }; + System.out.println("Предварительный обход = " + Arrays.toString(preorder)); + System.out.println("Симметричный обход = " + Arrays.toString(inorder)); + + TreeNode root = buildTree(preorder, inorder); + System.out.println("Построенное двоичное дерево:"); + PrintUtil.printTree(root); + } +} diff --git a/ru/codes/java/chapter_divide_and_conquer/hanota.java b/ru/codes/java/chapter_divide_and_conquer/hanota.java new file mode 100644 index 000000000..34b425ce4 --- /dev/null +++ b/ru/codes/java/chapter_divide_and_conquer/hanota.java @@ -0,0 +1,59 @@ +/** + * File: hanota.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import java.util.*; + +public class hanota { + /* Переместить один диск */ + static void move(List src, List tar) { + // Снять диск с вершины src + Integer pan = src.remove(src.size() - 1); + // Положить диск на вершину tar + tar.add(pan); + } + + /* Решить задачу Ханойской башни f(i) */ + static void dfs(int i, List src, List buf, List tar) { + // Если в src остался только один диск, сразу переместить его в tar + if (i == 1) { + move(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar); + } + + /* Решить задачу Ханойской башни */ + static void solveHanota(List A, List B, List C) { + int n = A.size(); + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C); + } + + public static void main(String[] args) { + // Хвост списка соответствует вершине столбца + List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); + List B = new ArrayList<>(); + List C = new ArrayList<>(); + System.out.println("Исходное состояние:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + + solveHanota(A, B, C); + + System.out.println("После завершения перемещения дисков:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java new file mode 100644 index 000000000..8fad40129 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.*; + +public class climbing_stairs_backtrack { + /* Бэктрекинг */ + public static void backtrack(List choices, int state, int n, List res) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) + res.set(0, res.get(0) + 1); + // Перебор всех вариантов выбора + for (Integer choice : choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) + continue; + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } + } + + /* Подъем по лестнице: бэктрекинг */ + public static int climbingStairsBacktrack(int n) { + List choices = Arrays.asList(1, 2); // Можно подняться на 1 или 2 ступени + int state = 0; // Начать подъем с 0-й ступени + List res = new ArrayList<>(); + res.add(0); // Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res); + return res.get(0); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsBacktrack(n); + System.out.println(String.format("Количество способов подняться по лестнице из %d ступеней: %d", n, res)); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java new file mode 100644 index 000000000..c39b9ed9b --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.java + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* Подъем по лестнице с ограничениями: динамическое программирование */ + static int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + int[][] dp = new int[n + 1][3]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsConstraintDP(n); + System.out.println(String.format("Количество способов подняться по лестнице из %d ступеней: %d", n, res)); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java new file mode 100644 index 000000000..c0c1cda07 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -0,0 +1,31 @@ +/** + * File: climbing_stairs_dfs.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* Поиск */ + public static int dfs(int i) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; + } + + /* Подъем по лестнице: поиск */ + public static int climbingStairsDFS(int n) { + return dfs(n); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFS(n); + System.out.println(String.format("Количество способов подняться по лестнице из %d ступеней: %d", n, res)); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java new file mode 100644 index 000000000..3b1f69816 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_dfs_mem.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class climbing_stairs_dfs_mem { + /* Поиск с мемоизацией */ + public static int dfs(int i, int[] mem) { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) + return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; + } + + /* Подъем по лестнице: поиск с мемоизацией */ + public static int climbingStairsDFSMem(int n) { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFSMem(n); + System.out.println(String.format("Количество способов подняться по лестнице из %d ступеней: %d", n, res)); + } +} \ No newline at end of file diff --git a/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java new file mode 100644 index 000000000..3e11a2110 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -0,0 +1,48 @@ +/** + * File: climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* Подъем по лестнице: динамическое программирование */ + public static int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // Инициализация таблицы dp для хранения решений подзадач + int[] dp = new int[n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ + public static int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDP(n); + System.out.println(String.format("Количество способов подняться по лестнице из %d ступеней: %d", n, res)); + + res = climbingStairsDPComp(n); + System.out.println(String.format("Количество способов подняться по лестнице из %d ступеней: %d", n, res)); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/coin_change.java b/ru/codes/java/chapter_dynamic_programming/coin_change.java new file mode 100644 index 000000000..ce8f3d4dd --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/coin_change.java @@ -0,0 +1,72 @@ +/** + * File: coin_change.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class coin_change { + /* Размен монет: динамическое программирование */ + static int coinChangeDP(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // Инициализация таблицы dp + int[][] dp = new int[n + 1][amt + 1]; + // Переход состояний: первая строка и первый столбец + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; + } + + /* Размен монет: динамическое программирование с оптимизацией памяти */ + static int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // Инициализация таблицы dp + int[] dp = new int[amt + 1]; + Arrays.fill(dp, MAX); + dp[0] = 0; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 4; + + // Динамическое программирование + int res = coinChangeDP(coins, amt); + System.out.println("Минимальное число монет для набора целевой суммы = " + res); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins, amt); + System.out.println("Минимальное число монет для набора целевой суммы = " + res); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/coin_change_ii.java b/ru/codes/java/chapter_dynamic_programming/coin_change_ii.java new file mode 100644 index 000000000..b772cb770 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class coin_change_ii { + /* Размен монет II: динамическое программирование */ + static int coinChangeIIDP(int[] coins, int amt) { + int n = coins.length; + // Инициализация таблицы dp + int[][] dp = new int[n + 1][amt + 1]; + // Инициализация первого столбца + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; + } + + /* Размен монет II: динамическое программирование с оптимизацией памяти */ + static int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.length; + // Инициализация таблицы dp + int[] dp = new int[amt + 1]; + dp[0] = 1; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 5; + + // Динамическое программирование + int res = coinChangeIIDP(coins, amt); + System.out.println("Количество комбинаций монет для набора целевой суммы = " + res); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(coins, amt); + System.out.println("Количество комбинаций монет для набора целевой суммы = " + res); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/edit_distance.java b/ru/codes/java/chapter_dynamic_programming/edit_distance.java new file mode 100644 index 000000000..fb665218c --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/edit_distance.java @@ -0,0 +1,139 @@ +/** + * File: edit_distance.java + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class edit_distance { + /* Редакционное расстояние: полный перебор */ + static int editDistanceDFS(String s, String t, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если два символа равны, сразу пропустить их + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return Math.min(Math.min(insert, delete), replace) + 1; + } + + /* Редакционное расстояние: поиск с мемоизацией */ + static int editDistanceDFSMem(String s, String t, int[][] mem, int i, int j) { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) + return 0; + // Если s пусто, вернуть длину t + if (i == 0) + return j; + // Если t пусто, вернуть длину s + if (j == 0) + return i; + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) + return mem[i][j]; + // Если два символа равны, сразу пропустить их + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* Редакционное расстояние: динамическое программирование */ + static int editDistanceDP(String s, String t) { + int n = s.length(), m = t.length(); + int[][] dp = new int[n + 1][m + 1]; + // Переход состояний: первая строка и первый столбец + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + + /* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ + static int editDistanceDPComp(String s, String t) { + int n = s.length(), m = t.length(); + int[] dp = new int[m + 1]; + // Переход состояний: первая строка + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (int i = 1; i <= n; i++) { + // Переход состояний: первый столбец + int leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; + } + + public static void main(String[] args) { + String s = "bag"; + String t = "pack"; + int n = s.length(), m = t.length(); + + // Полный перебор + int res = editDistanceDFS(s, t, n, m); + System.out.println("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов"); + + // Поиск с мемоизацией + int[][] mem = new int[n + 1][m + 1]; + for (int[] row : mem) + Arrays.fill(row, -1); + res = editDistanceDFSMem(s, t, mem, n, m); + System.out.println("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов"); + + // Динамическое программирование + res = editDistanceDP(s, t); + System.out.println("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов"); + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t); + System.out.println("Чтобы преобразовать " + s + " в " + t + ", нужно минимум " + res + " шагов"); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/knapsack.java b/ru/codes/java/chapter_dynamic_programming/knapsack.java new file mode 100644 index 000000000..b76b2e015 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/knapsack.java @@ -0,0 +1,116 @@ +/** + * File: knapsack.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class knapsack { + + /* Рюкзак 0-1: полный перебор */ + static int knapsackDFS(int[] wgt, int[] val, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return Math.max(no, yes); + } + + /* Рюкзак 0-1: поиск с мемоизацией */ + static int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = Math.max(no, yes); + return mem[i][c]; + } + + /* Рюкзак 0-1: динамическое программирование */ + static int knapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + int[][] dp = new int[n + 1][cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ + static int knapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + int[] dp = new int[cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + // Обход в обратном порядке + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + int n = wgt.length; + + // Полный перебор + int res = knapsackDFS(wgt, val, n, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Поиск с мемоизацией + int[][] mem = new int[n + 1][cap + 1]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = knapsackDFSMem(wgt, val, mem, n, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Динамическое программирование + res = knapsackDP(wgt, val, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt, val, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java b/ru/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java new file mode 100644 index 000000000..8354c0916 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_cost_climbing_stairs_dp { + /* Минимальная стоимость подъема по лестнице: динамическое программирование */ + public static int minCostClimbingStairsDP(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + // Инициализация таблицы dp для хранения решений подзадач + int[] dp = new int[n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (int i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ + public static int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int[] cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + System.out.println(String.format("Список стоимостей ступеней = %s", Arrays.toString(cost))); + + int res = minCostClimbingStairsDP(cost); + System.out.println(String.format("Минимальная стоимость подъема по лестнице = %d", res)); + + res = minCostClimbingStairsDPComp(cost); + System.out.println(String.format("Минимальная стоимость подъема по лестнице = %d", res)); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/min_path_sum.java b/ru/codes/java/chapter_dynamic_programming/min_path_sum.java new file mode 100644 index 000000000..e2ecdee32 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -0,0 +1,125 @@ +/** + * File: min_path_sum.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_path_sum { + /* Минимальная сумма пути: полный перебор */ + static int minPathSumDFS(int[][] grid, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return Math.min(left, up) + grid[i][j]; + } + + /* Минимальная сумма пути: поиск с мемоизацией */ + static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* Минимальная сумма пути: динамическое программирование */ + static int minPathSumDP(int[][] grid) { + int n = grid.length, m = grid[0].length; + // Инициализация таблицы dp + int[][] dp = new int[n][m]; + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + + /* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ + static int minPathSumDPComp(int[][] grid) { + int n = grid.length, m = grid[0].length; + // Инициализация таблицы dp + int[] dp = new int[m]; + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (int i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (int j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + public static void main(String[] args) { + int[][] grid = { + { 1, 3, 1, 5 }, + { 2, 2, 4, 2 }, + { 5, 3, 2, 1 }, + { 4, 3, 5, 2 } + }; + int n = grid.length, m = grid[0].length; + + // Полный перебор + int res = minPathSumDFS(grid, n - 1, m - 1); + System.out.println("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + + // Поиск с мемоизацией + int[][] mem = new int[n][m]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + System.out.println("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + + // Динамическое программирование + res = minPathSumDP(grid); + System.out.println("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + + // Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(grid); + System.out.println("Минимальная сумма пути из левого верхнего угла в правый нижний = " + res); + } +} diff --git a/ru/codes/java/chapter_dynamic_programming/unbounded_knapsack.java b/ru/codes/java/chapter_dynamic_programming/unbounded_knapsack.java new file mode 100644 index 000000000..d926e7f79 --- /dev/null +++ b/ru/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class unbounded_knapsack { + /* Полный рюкзак: динамическое программирование */ + static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + int[][] dp = new int[n + 1][cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* Полный рюкзак: динамическое программирование с оптимизацией памяти */ + static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // Инициализация таблицы dp + int[] dp = new int[cap + 1]; + // Переход состояний + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 1, 2, 3 }; + int[] val = { 5, 11, 15 }; + int cap = 4; + + // Динамическое программирование + int res = unboundedKnapsackDP(wgt, val, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(wgt, val, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + } +} diff --git a/ru/codes/java/chapter_graph/graph_adjacency_list.java b/ru/codes/java/chapter_graph/graph_adjacency_list.java new file mode 100644 index 000000000..622f64a29 --- /dev/null +++ b/ru/codes/java/chapter_graph/graph_adjacency_list.java @@ -0,0 +1,117 @@ +/** + * File: graph_adjacency_list.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +/* Класс неориентированного графа на основе списка смежности */ +class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + Map> adjList; + + /* Конструктор */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // Добавить все вершины и ребра + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + public int size() { + return adjList.size(); + } + + /* Добавление ребра */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // Добавить ребро vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* Удаление ребра */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // Удалить ребро vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* Добавление вершины */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // Добавить новый список в список смежности + adjList.put(vet, new ArrayList<>()); + } + + /* Удаление вершины */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // Удалить из списка смежности список, соответствующий вершине vet + adjList.remove(vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* Вывести список смежности */ + public void print() { + System.out.println("Список смежности ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } +} + +public class graph_adjacency_list { + public static void main(String[] args) { + /* Инициализация неориентированного графа */ + Vertex[] v = Vertex.valsToVets(new int[] { 1, 3, 2, 5, 4 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\nГраф после инициализации"); + graph.print(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(v[0], v[2]); + System.out.println("\nГраф после добавления ребра 1-2"); + graph.print(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(v[0], v[1]); + System.out.println("\nГраф после удаления ребра 1-3"); + graph.print(); + + /* Добавление вершины */ + Vertex v5 = new Vertex(6); + graph.addVertex(v5); + System.out.println("\nГраф после добавления вершины 6"); + graph.print(); + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(v[1]); + System.out.println("\nГраф после удаления вершины 3"); + graph.print(); + } +} diff --git a/ru/codes/java/chapter_graph/graph_adjacency_matrix.java b/ru/codes/java/chapter_graph/graph_adjacency_matrix.java new file mode 100644 index 000000000..aefaafc71 --- /dev/null +++ b/ru/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import utils.*; +import java.util.*; + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + List vertices; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + List> adjMat; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // Добавление вершины + for (int val : vertices) { + addVertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* Получить число вершин */ + public int size() { + return vertices.size(); + } + + /* Добавление вершины */ + public void addVertex(int val) { + int n = size(); + // Добавить значение новой вершины в список вершин + vertices.add(val); + // Добавить строку в матрицу смежности + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // Добавить столбец в матрицу смежности + for (List row : adjMat) { + row.add(0); + } + } + + /* Удаление вершины */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // Удалить вершину с индексом index из списка вершин + vertices.remove(index); + // Удалить строку с индексом index из матрицы смежности + adjMat.remove(index); + // Удалить столбец с индексом index из матрицы смежности + for (List row : adjMat) { + row.remove(index); + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + public void addEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + public void removeEdge(int i, int j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + adjMat.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + + /* Вывести матрицу смежности */ + public void print() { + System.out.print("Список вершин = "); + System.out.println(vertices); + System.out.println("Матрица смежности ="); + PrintUtil.printMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + public static void main(String[] args) { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + int[] vertices = { 1, 3, 2, 5, 4 }; + int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + GraphAdjMat graph = new GraphAdjMat(vertices, edges); + System.out.println("\nГраф после инициализации"); + graph.print(); + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.addEdge(0, 2); + System.out.println("\nГраф после добавления ребра 1-2"); + graph.print(); + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.removeEdge(0, 1); + System.out.println("\nГраф после удаления ребра 1-3"); + graph.print(); + + /* Добавление вершины */ + graph.addVertex(6); + System.out.println("\nГраф после добавления вершины 6"); + graph.print(); + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.removeVertex(1); + System.out.println("\nГраф после удаления вершины 3"); + graph.print(); + } +} diff --git a/ru/codes/java/chapter_graph/graph_bfs.java b/ru/codes/java/chapter_graph/graph_bfs.java new file mode 100644 index 000000000..f06b29ed9 --- /dev/null +++ b/ru/codes/java/chapter_graph/graph_bfs.java @@ -0,0 +1,55 @@ +/** + * File: graph_bfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_bfs { + /* Обход в ширину */ + // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины + static List graphBFS(GraphAdjList graph, Vertex startVet) { + // Последовательность обхода вершин + List res = new ArrayList<>(); + // Хеш-множество для хранения уже посещенных вершин + Set visited = new HashSet<>(); + visited.add(startVet); + // Очередь используется для реализации BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (!que.isEmpty()) { + Vertex vet = que.poll(); // Извлечь головную вершину из очереди + res.add(vet); // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // Пропустить уже посещенную вершину + que.offer(adjVet); // Помещать в очередь только непосещенные вершины + visited.add(adjVet); // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res; + } + + public static void main(String[] args) { + /* Инициализация неориентированного графа */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, { v[1], v[4] }, + { v[2], v[5] }, { v[3], v[4] }, { v[3], v[6] }, { v[4], v[5] }, + { v[4], v[7] }, { v[5], v[8] }, { v[6], v[7] }, { v[7], v[8] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\nГраф после инициализации"); + graph.print(); + + /* Обход в ширину */ + List res = graphBFS(graph, v[0]); + System.out.println("\nПоследовательность вершин при обходе в ширину (BFS)"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/ru/codes/java/chapter_graph/graph_dfs.java b/ru/codes/java/chapter_graph/graph_dfs.java new file mode 100644 index 000000000..4ac3ba949 --- /dev/null +++ b/ru/codes/java/chapter_graph/graph_dfs.java @@ -0,0 +1,51 @@ +/** + * File: graph_dfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_dfs { + /* Вспомогательная функция обхода в глубину */ + static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // Отметить посещенную вершину + visited.add(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // Пропустить уже посещенную вершину + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet); + } + } + + /* Обход в глубину */ + // Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины + static List graphDFS(GraphAdjList graph, Vertex startVet) { + // Последовательность обхода вершин + List res = new ArrayList<>(); + // Хеш-множество для хранения уже посещенных вершин + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + + public static void main(String[] args) { + /* Инициализация неориентированного графа */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6 }); + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[5] }, { v[4], v[5] }, { v[5], v[6] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\nГраф после инициализации"); + graph.print(); + + /* Обход в глубину */ + List res = graphDFS(graph, v[0]); + System.out.println("\nПоследовательность вершин при обходе в глубину (DFS)"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/ru/codes/java/chapter_greedy/coin_change_greedy.java b/ru/codes/java/chapter_greedy/coin_change_greedy.java new file mode 100644 index 000000000..97d3e2e85 --- /dev/null +++ b/ru/codes/java/chapter_greedy/coin_change_greedy.java @@ -0,0 +1,55 @@ +/** + * File: coin_change_greedy.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; + +public class coin_change_greedy { + /* Размен монет: жадный алгоритм */ + static int coinChangeGreedy(int[] coins, int amt) { + // Предположить, что список coins упорядочен + int i = coins.length - 1; + int count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt == 0 ? count : -1; + } + + public static void main(String[] args) { + // Жадный подход: гарантирует нахождение глобально оптимального решения + int[] coins = { 1, 5, 10, 20, 50, 100 }; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("Минимальное число монет для набора суммы " + amt + " = " + res); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = new int[] { 1, 20, 50 }; + amt = 60; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("Минимальное число монет для набора суммы " + amt + " = " + res); + System.out.println("На самом деле минимум равен 3: 20 + 20 + 20"); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = new int[] { 1, 49, 50 }; + amt = 98; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("Минимальное число монет для набора суммы " + amt + " = " + res); + System.out.println("На самом деле минимум равен 2: 49 + 49"); + } +} diff --git a/ru/codes/java/chapter_greedy/fractional_knapsack.java b/ru/codes/java/chapter_greedy/fractional_knapsack.java new file mode 100644 index 000000000..ef1278c21 --- /dev/null +++ b/ru/codes/java/chapter_greedy/fractional_knapsack.java @@ -0,0 +1,59 @@ +/** + * File: fractional_knapsack.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; +import java.util.Comparator; + +/* Предмет */ +class Item { + int w; // Вес предмета + int v; // Стоимость предмета + + public Item(int w, int v) { + this.w = w; + this.v = v; + } +} + +public class fractional_knapsack { + /* Дробный рюкзак: жадный алгоритм */ + static double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // Создать список предметов с двумя свойствами: вес и стоимость + Item[] items = new Item[wgt.length]; + for (int i = 0; i < wgt.length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); + // Циклический жадный выбор + double res = 0; + for (Item item : items) { + if (item.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (double) item.v / item.w * cap; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + return res; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + + // Жадный алгоритм + double res = fractionalKnapsack(wgt, val, cap); + System.out.println("Максимальная стоимость предметов без превышения вместимости рюкзака = " + res); + } +} diff --git a/ru/codes/java/chapter_greedy/max_capacity.java b/ru/codes/java/chapter_greedy/max_capacity.java new file mode 100644 index 000000000..ba49a1405 --- /dev/null +++ b/ru/codes/java/chapter_greedy/max_capacity.java @@ -0,0 +1,38 @@ +/** + * File: max_capacity.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +public class max_capacity { + /* Максимальная вместимость: жадный алгоритм */ + static int maxCapacity(int[] ht) { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + int i = 0, j = ht.length - 1; + // Начальная максимальная вместимость равна 0 + int res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + int cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + public static void main(String[] args) { + int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; + + // Жадный алгоритм + int res = maxCapacity(ht); + System.out.println("Максимальная вместимость = " + res); + } +} diff --git a/ru/codes/java/chapter_greedy/max_product_cutting.java b/ru/codes/java/chapter_greedy/max_product_cutting.java new file mode 100644 index 000000000..e4cb1deb1 --- /dev/null +++ b/ru/codes/java/chapter_greedy/max_product_cutting.java @@ -0,0 +1,40 @@ +/** + * File: max_product_cutting.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.lang.Math; + +public class max_product_cutting { + /* Максимальное произведение разрезания: жадный алгоритм */ + public static int maxProductCutting(int n) { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + int a = n / 3; + int b = n % 3; + if (b == 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return (int) Math.pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // Если остаток равен 2, ничего не делать + return (int) Math.pow(3, a) * 2; + } + // Если остаток равен 0, ничего не делать + return (int) Math.pow(3, a); + } + + public static void main(String[] args) { + int n = 58; + + // Жадный алгоритм + int res = maxProductCutting(n); + System.out.println("Максимальное произведение после разрезания = " + res); + } +} diff --git a/ru/codes/java/chapter_hashing/array_hash_map.java b/ru/codes/java/chapter_hashing/array_hash_map.java new file mode 100644 index 000000000..3596c2e14 --- /dev/null +++ b/ru/codes/java/chapter_hashing/array_hash_map.java @@ -0,0 +1,141 @@ +/** + * File: array_hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; + +/* Пара ключ-значение */ +class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + private List buckets; + + public ArrayHashMap() { + // Инициализировать массив, содержащий 100 корзин + buckets = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + buckets.add(null); + } + } + + /* Хеш-функция */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* Операция поиска */ + public String get(int key) { + int index = hashFunc(key); + Pair pair = buckets.get(index); + if (pair == null) + return null; + return pair.val; + } + + /* Операция добавления */ + public void put(int key, String val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets.set(index, pair); + } + + /* Операция удаления */ + public void remove(int key) { + int index = hashFunc(key); + // Присвоить null, что означает удаление + buckets.set(index, null); + } + + /* Получить все пары ключ-значение */ + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + pairSet.add(pair); + } + return pairSet; + } + + /* Получить все ключи */ + public List keySet() { + List keySet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + keySet.add(pair.key); + } + return keySet; + } + + /* Получить все значения */ + public List valueSet() { + List valueSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + valueSet.add(pair.val); + } + return valueSet; + } + + /* Вывести хеш-таблицу */ + public void print() { + for (Pair kv : pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + } +} + +public class array_hash_map { + public static void main(String[] args) { + /* Инициализация хеш-таблицы */ + ArrayHashMap map = new ArrayHashMap(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + String name = map.get(15937); + System.out.println("\nДля номера 15937 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583); + System.out.println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + + /* Обход хеш-таблицы */ + System.out.println("\nОтдельный обход пар ключ-значение"); + for (Pair kv : map.pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + System.out.println("\nОтдельный обход ключей"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\nОтдельный обход значений"); + for (String val : map.valueSet()) { + System.out.println(val); + } + } +} diff --git a/ru/codes/java/chapter_hashing/built_in_hash.java b/ru/codes/java/chapter_hashing/built_in_hash.java new file mode 100644 index 000000000..efa17ef98 --- /dev/null +++ b/ru/codes/java/chapter_hashing/built_in_hash.java @@ -0,0 +1,38 @@ +/** + * File: built_in_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import utils.*; +import java.util.*; + +public class built_in_hash { + public static void main(String[] args) { + int num = 3; + int hashNum = Integer.hashCode(num); + System.out.println("Хеш-значение целого числа " + num + " = " + hashNum); + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + System.out.println("Хеш-значение булева значения " + bol + " = " + hashBol); + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + System.out.println("Хеш-значение десятичного числа " + dec + " = " + hashDec); + + String str = "Hello Algo"; + int hashStr = str.hashCode(); + System.out.println("Хеш-значение строки " + str + " = " + hashStr); + + Object[] arr = { 12836, "Сяо Ха" }; + int hashTup = Arrays.hashCode(arr); + System.out.println("Хеш-значение массива " + Arrays.toString(arr) + " = " + hashTup); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + System.out.println("Хеш-значение объекта узла " + obj + " = " + hashObj); + } +} diff --git a/ru/codes/java/chapter_hashing/hash_map.java b/ru/codes/java/chapter_hashing/hash_map.java new file mode 100644 index 000000000..cec11d78f --- /dev/null +++ b/ru/codes/java/chapter_hashing/hash_map.java @@ -0,0 +1,52 @@ +/** + * File: hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; +import utils.*; + +public class hash_map { + public static void main(String[] args) { + /* Инициализация хеш-таблицы */ + Map map = new HashMap<>(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + PrintUtil.printHashMap(map); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + String name = map.get(15937); + System.out.println("\nДля номера 15937 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583); + System.out.println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + PrintUtil.printHashMap(map); + + /* Обход хеш-таблицы */ + System.out.println("\nОтдельный обход пар ключ-значение"); + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + System.out.println("\nОтдельный обход ключей"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\nОтдельный обход значений"); + for (String val : map.values()) { + System.out.println(val); + } + } +} diff --git a/ru/codes/java/chapter_hashing/hash_map_chaining.java b/ru/codes/java/chapter_hashing/hash_map_chaining.java new file mode 100644 index 000000000..579c59c5a --- /dev/null +++ b/ru/codes/java/chapter_hashing/hash_map_chaining.java @@ -0,0 +1,148 @@ +/** + * File: hash_map_chaining.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.ArrayList; +import java.util.List; + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + int size; // Число пар ключ-значение + int capacity; // Вместимость хеш-таблицы + double loadThres; // Порог коэффициента загрузки для запуска расширения + int extendRatio; // Коэффициент расширения + List> buckets; // Массив корзин + + /* Конструктор */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* Хеш-функция */ + int hashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + double loadFactor() { + return (double) size / capacity; + } + + /* Операция поиска */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // Обойти корзину; если найден key, вернуть соответствующее val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // Если key не найден, вернуть null + return null; + } + + /* Операция добавления */ + void put(int key, String val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // Если такого key нет, добавить пару ключ-значение в конец + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* Операция удаления */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // Обойти корзину и удалить из нее пару ключ-значение + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* Расширить хеш-таблицу */ + void extend() { + // Временно сохранить исходную хеш-таблицу + List> bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } +} + +public class hash_map_chaining { + public static void main(String[] args) { + /* Инициализация хеш-таблицы */ + HashMapChaining map = new HashMapChaining(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + String name = map.get(13276); + System.out.println("\nДля номера 13276 найдено имя " + name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(12836); + System.out.println("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + } +} diff --git a/ru/codes/java/chapter_hashing/hash_map_open_addressing.java b/ru/codes/java/chapter_hashing/hash_map_open_addressing.java new file mode 100644 index 000000000..ab2ad5dad --- /dev/null +++ b/ru/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -0,0 +1,158 @@ +/** + * File: hash_map_open_addressing.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + private int size; // Число пар ключ-значение + private int capacity = 4; // Вместимость хеш-таблицы + private final double loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения + private final int extendRatio = 2; // Коэффициент расширения + private Pair[] buckets; // Массив корзин + private final Pair TOMBSTONE = new Pair(-1, "-1"); // Удалить метку + + /* Конструктор */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* Хеш-функция */ + private int hashFunc(int key) { + return key % capacity; + } + + /* Коэффициент загрузки */ + private double loadFactor() { + return (double) size / capacity; + } + + /* Найти индекс корзины, соответствующий key */ + private int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (buckets[index] != null) { + // Если встретился key, вернуть соответствующий индекс корзины + if (buckets[index].key == key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone == -1 ? index : firstTombstone; + } + + /* Операция поиска */ + public String get(int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // Если пары ключ-значение не существует, вернуть null + return null; + } + + /* Операция добавления */ + public void put(int key, String val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > loadThres) { + extend(); + } + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + buckets[index] = new Pair(key, val); + size++; + } + + /* Операция удаления */ + public void remove(int key) { + // Найти индекс корзины, соответствующий key + int index = findBucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* Расширить хеш-таблицу */ + private void extend() { + // Временно сохранить исходную хеш-таблицу + Pair[] bucketsTmp = buckets; + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (Pair pair : bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + public void print() { + for (Pair pair : buckets) { + if (pair == null) { + System.out.println("null"); + } else if (pair == TOMBSTONE) { + System.out.println("TOMBSTONE"); + } else { + System.out.println(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + public static void main(String[] args) { + // Инициализация хеш-таблицы + HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); + + // Операция добавления + // Добавить пару (key, val) в хеш-таблицу + hashmap.put(12836, "Сяо Ха"); + hashmap.put(15937, "Сяо Ло"); + hashmap.put(16750, "Сяо Суань"); + hashmap.put(13276, "Сяо Фа"); + hashmap.put(10583, "Сяо Я"); + System.out.println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + hashmap.print(); + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение val + String name = hashmap.get(13276); + System.out.println("\nДля номера 13276 найдено имя " + name); + + // Операция удаления + // Удалить пару (key, val) из хеш-таблицы + hashmap.remove(16750); + System.out.println("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); + hashmap.print(); + } +} diff --git a/ru/codes/java/chapter_hashing/simple_hash.java b/ru/codes/java/chapter_hashing/simple_hash.java new file mode 100644 index 000000000..2e5e5c906 --- /dev/null +++ b/ru/codes/java/chapter_hashing/simple_hash.java @@ -0,0 +1,65 @@ +/** + * File: simple_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +public class simple_hash { + /* Аддитивное хеширование */ + static int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* Мультипликативное хеширование */ + static int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* XOR-хеширование */ + static int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } + + /* Хеширование с циклическим сдвигом */ + static int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + + public static void main(String[] args) { + String key = "Hello Algo"; + + int hash = addHash(key); + System.out.println("Хеш-сумма сложением = " + hash); + + hash = mulHash(key); + System.out.println("Хеш-сумма умножением = " + hash); + + hash = xorHash(key); + System.out.println("Хеш-сумма XOR = " + hash); + + hash = rotHash(key); + System.out.println("Хеш-сумма с циклическим сдвигом = " + hash); + } +} diff --git a/ru/codes/java/chapter_heap/heap.java b/ru/codes/java/chapter_heap/heap.java new file mode 100644 index 000000000..a20409d64 --- /dev/null +++ b/ru/codes/java/chapter_heap/heap.java @@ -0,0 +1,66 @@ +/** + * File: heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class heap { + public static void testPush(Queue heap, int val) { + heap.offer(val); // Добавление элемента в кучу + System.out.format("\nПосле добавления элемента %d в кучу\n", val); + PrintUtil.printHeap(heap); + } + + public static void testPop(Queue heap) { + int val = heap.poll(); // Извлечение элемента с вершины кучи + System.out.format("\nПосле удаления элемента %d с вершины кучи\n", val); + PrintUtil.printHeap(heap); + } + + public static void main(String[] args) { + /* Инициализация кучи */ + // Инициализация минимальной кучи + Queue minHeap = new PriorityQueue<>(); + // Инициализация максимальной кучи (достаточно изменить Comparator с помощью lambda-выражения) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + System.out.println("\nНиже приведен тестовый пример для max-heap"); + + /* Добавление элемента в кучу */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.peek(); + System.out.format("\nЭлемент на вершине кучи = %d\n", peek); + + /* Извлечение элемента с вершины кучи */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* Получение размера кучи */ + int size = maxHeap.size(); + System.out.format("\nКоличество элементов в куче = %d\n", size); + + /* Проверка, пуста ли куча */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\nПуста ли куча: %b\n", isEmpty); + + /* Построить кучу по входному списку */ + // Временная сложность равна O(n), а не O(nlogn) + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + System.out.println("\nПосле построения min-heap из входного списка"); + PrintUtil.printHeap(minHeap); + } +} diff --git a/ru/codes/java/chapter_heap/my_heap.java b/ru/codes/java/chapter_heap/my_heap.java new file mode 100644 index 000000000..b844e1d74 --- /dev/null +++ b/ru/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,159 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +/* Максимальная куча */ +class MaxHeap { + // Использовать список вместо массива, чтобы не учитывать проблему расширения + private List maxHeap; + + /* Конструктор, строящий кучу по входному списку */ + public MaxHeap(List nums) { + // Добавить элементы списка в кучу без изменений + maxHeap = new ArrayList<>(nums); + // Выполнить heapify для всех узлов, кроме листовых + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* Получить индекс левого дочернего узла */ + private int left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + private int right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + private int parent(int i) { + return (i - 1) / 2; // Округление вниз при делении + } + + /* Поменять элементы местами */ + private void swap(int i, int j) { + int tmp = maxHeap.get(i); + maxHeap.set(i, maxHeap.get(j)); + maxHeap.set(j, tmp); + } + + /* Получение размера кучи */ + public int size() { + return maxHeap.size(); + } + + /* Проверка, пуста ли куча */ + public boolean isEmpty() { + return size() == 0; + } + + /* Доступ к элементу на вершине кучи */ + public int peek() { + return maxHeap.get(0); + } + + /* Добавление элемента в кучу */ + public void push(int val) { + // Добавление узла + maxHeap.add(val); + // Просеивание снизу вверх + siftUp(size() - 1); + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + private void siftUp(int i) { + while (true) { + // Получение родительского узла для узла i + int p = parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + // Поменять два узла местами + swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + public int pop() { + // Обработка пустого случая + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(0, size() - 1); + // Удаление узла + int val = maxHeap.remove(size() - 1); + // Просеивание сверху вниз + siftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + private void siftDown(int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) + ma = l; + if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) + ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) + break; + // Поменять два узла местами + swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Вывести кучу (двоичное дерево) */ + public void print() { + Queue queue = new PriorityQueue<>((a, b) -> { return b - a; }); + queue.addAll(maxHeap); + PrintUtil.printHeap(queue); + } +} + +public class my_heap { + public static void main(String[] args) { + /* Инициализация максимальной кучи */ + MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); + System.out.println("\nПосле построения кучи из входного списка"); + maxHeap.print(); + + /* Получение элемента с вершины кучи */ + int peek = maxHeap.peek(); + System.out.format("\nЭлемент на вершине кучи = %d\n", peek); + + /* Добавление элемента в кучу */ + int val = 7; + maxHeap.push(val); + System.out.format("\nПосле добавления элемента %d в кучу\n", val); + maxHeap.print(); + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop(); + System.out.format("\nПосле удаления элемента %d с вершины кучи\n", peek); + maxHeap.print(); + + /* Получение размера кучи */ + int size = maxHeap.size(); + System.out.format("\nКоличество элементов в куче = %d\n", size); + + /* Проверка, пуста ли куча */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\nПуста ли куча: %b\n", isEmpty); + } +} diff --git a/ru/codes/java/chapter_heap/top_k.java b/ru/codes/java/chapter_heap/top_k.java new file mode 100644 index 000000000..33d022958 --- /dev/null +++ b/ru/codes/java/chapter_heap/top_k.java @@ -0,0 +1,40 @@ +/** + * File: top_k.java + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class top_k { + /* Найти k наибольших элементов массива с помощью кучи */ + static Queue topKHeap(int[] nums, int k) { + // Инициализация минимальной кучи + Queue heap = new PriorityQueue(); + // Поместить первые k элементов массива в кучу + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (int i = k; i < nums.length; i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + + public static void main(String[] args) { + int[] nums = { 1, 7, 6, 3, 2 }; + int k = 3; + + Queue res = topKHeap(nums, k); + System.out.println("Наибольшие " + k + " элементов"); + PrintUtil.printHeap(res); + } +} diff --git a/ru/codes/java/chapter_searching/binary_search.java b/ru/codes/java/chapter_searching/binary_search.java new file mode 100644 index 000000000..dbf2d2bd0 --- /dev/null +++ b/ru/codes/java/chapter_searching/binary_search.java @@ -0,0 +1,58 @@ +/** + * File: binary_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search { + /* Бинарный поиск (двусторонне замкнутый интервал) */ + static int binarySearch(int[] nums, int target) { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + int i = 0, j = nums.length - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; + } + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + static int binarySearchLCRO(int[] nums, int target) { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + int i = 0, j = nums.length; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) + j = m; + else // Целевой элемент найден, вернуть его индекс + return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + int index = binarySearch(nums, target); + System.out.println("Индекс целевого элемента 6 = " + index); + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = binarySearchLCRO(nums, target); + System.out.println("Индекс целевого элемента 6 = " + index); + } +} diff --git a/ru/codes/java/chapter_searching/binary_search_edge.java b/ru/codes/java/chapter_searching/binary_search_edge.java new file mode 100644 index 000000000..5faec635b --- /dev/null +++ b/ru/codes/java/chapter_searching/binary_search_edge.java @@ -0,0 +1,49 @@ +/** + * File: binary_search_edge.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search_edge { + /* Бинарный поиск самого левого target */ + static int binarySearchLeftEdge(int[] nums, int target) { + // Эквивалентно поиску точки вставки target + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // target не найден, вернуть -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // Найти target и вернуть индекс i + return i; + } + + /* Бинарный поиск самого правого target */ + static int binarySearchRightEdge(int[] nums, int target) { + // Преобразовать задачу в поиск самого левого target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + int j = i - 1; + // target не найден, вернуть -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // Найти target и вернуть индекс j + return j; + } + + public static void main(String[] args) { + // Массив с повторяющимися элементами + int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\nМассив nums = " + java.util.Arrays.toString(nums)); + + // Бинарный поиск левой и правой границы + for (int target : new int[] { 6, 7 }) { + int index = binarySearchLeftEdge(nums, target); + System.out.println("Индекс самого левого элемента " + target + " равен " + index); + index = binarySearchRightEdge(nums, target); + System.out.println("Индекс самого правого элемента " + target + " равен " + index); + } + } +} diff --git a/ru/codes/java/chapter_searching/binary_search_insertion.java b/ru/codes/java/chapter_searching/binary_search_insertion.java new file mode 100644 index 000000000..e358c8c06 --- /dev/null +++ b/ru/codes/java/chapter_searching/binary_search_insertion.java @@ -0,0 +1,63 @@ +/** + * File: binary_search_insertion.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +class binary_search_insertion { + /* Бинарный поиск точки вставки (без повторяющихся элементов) */ + static int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; + } + + /* Бинарный поиск точки вставки (с повторяющимися элементами) */ + static int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; + } + + public static void main(String[] args) { + // Массив без повторяющихся элементов + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + System.out.println("\nМассив nums = " + java.util.Arrays.toString(nums)); + // Бинарный поиск точки вставки + for (int target : new int[] { 6, 9 }) { + int index = binarySearchInsertionSimple(nums, target); + System.out.println("Индекс позиции вставки элемента " + target + " равен " + index); + } + + // Массив с повторяющимися элементами + nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\nМассив nums = " + java.util.Arrays.toString(nums)); + // Бинарный поиск точки вставки + for (int target : new int[] { 2, 6, 20 }) { + int index = binarySearchInsertion(nums, target); + System.out.println("Индекс позиции вставки элемента " + target + " равен " + index); + } + } +} diff --git a/ru/codes/java/chapter_searching/hashing_search.java b/ru/codes/java/chapter_searching/hashing_search.java new file mode 100644 index 000000000..9362cf12c --- /dev/null +++ b/ru/codes/java/chapter_searching/hashing_search.java @@ -0,0 +1,51 @@ +/** + * File: hashing_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; +import java.util.*; + +public class hashing_search { + /* Хеш-поиск (массив) */ + static int hashingSearchArray(Map map, int target) { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + return map.getOrDefault(target, -1); + } + + /* Хеш-поиск (связный список) */ + static ListNode hashingSearchLinkedList(Map map, int target) { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + return map.getOrDefault(target, null); + } + + public static void main(String[] args) { + int target = 3; + + /* Хеш-поиск (массив) */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // Инициализация хеш-таблицы + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], i); // key: элемент, value: индекс + } + int index = hashingSearchArray(map, target); + System.out.println("Индекс целевого элемента 3 = " + index); + + /* Хеш-поиск (связный список) */ + ListNode head = ListNode.arrToLinkedList(nums); + // Инициализация хеш-таблицы + Map map1 = new HashMap<>(); + while (head != null) { + map1.put(head.val, head); // key: значение узла, value: узел + head = head.next; + } + ListNode node = hashingSearchLinkedList(map1, target); + System.out.println("Объект узла со значением 3 = " + node); + } +} diff --git a/ru/codes/java/chapter_searching/linear_search.java b/ru/codes/java/chapter_searching/linear_search.java new file mode 100644 index 000000000..59b6c0778 --- /dev/null +++ b/ru/codes/java/chapter_searching/linear_search.java @@ -0,0 +1,50 @@ +/** + * File: linear_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; + +public class linear_search { + /* Линейный поиск (массив) */ + static int linearSearchArray(int[] nums, int target) { + // Обход массива + for (int i = 0; i < nums.length; i++) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] == target) + return i; + } + // Целевой элемент не найден, вернуть -1 + return -1; + } + + /* Линейный поиск (связный список) */ + static ListNode linearSearchLinkedList(ListNode head, int target) { + // Обойти связный список + while (head != null) { + // Найти целевой узел и вернуть его + if (head.val == target) + return head; + head = head.next; + } + // Целевой узел не найден, вернуть null + return null; + } + + public static void main(String[] args) { + int target = 3; + + /* Выполнить линейный поиск в массиве */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + int index = linearSearchArray(nums, target); + System.out.println("Индекс целевого элемента 3 = " + index); + + /* Выполнить линейный поиск в связном списке */ + ListNode head = ListNode.arrToLinkedList(nums); + ListNode node = linearSearchLinkedList(head, target); + System.out.println("Объект узла со значением 3 = " + node); + } +} diff --git a/ru/codes/java/chapter_searching/two_sum.java b/ru/codes/java/chapter_searching/two_sum.java new file mode 100644 index 000000000..5a8df3ad9 --- /dev/null +++ b/ru/codes/java/chapter_searching/two_sum.java @@ -0,0 +1,53 @@ +/** + * File: two_sum.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import java.util.*; + +public class two_sum { + /* Метод 1: полный перебор */ + static int[] twoSumBruteForce(int[] nums, int target) { + int size = nums.length; + // Два вложенных цикла, временная сложность O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + + /* Метод 2: вспомогательная хеш-таблица */ + static int[] twoSumHashTable(int[] nums, int target) { + int size = nums.length; + // Вспомогательная хеш-таблица, пространственная сложность O(n) + Map dic = new HashMap<>(); + // Один цикл, временная сложность O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); + } + return new int[0]; + } + + public static void main(String[] args) { + // ======= Test Case ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 13; + + // ====== Основной код ====== + // Метод 1 + int[] res = twoSumBruteForce(nums, target); + System.out.println("Результат метода 1 res = " + Arrays.toString(res)); + // Метод 2 + res = twoSumHashTable(nums, target); + System.out.println("Результат метода 2 res = " + Arrays.toString(res)); + } +} diff --git a/ru/codes/java/chapter_sorting/bubble_sort.java b/ru/codes/java/chapter_sorting/bubble_sort.java new file mode 100644 index 000000000..f4668af94 --- /dev/null +++ b/ru/codes/java/chapter_sorting/bubble_sort.java @@ -0,0 +1,57 @@ +/** + * File: bubble_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bubble_sort { + /* Пузырьковая сортировка */ + static void bubbleSort(int[] nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } + } + + /* Пузырьковая сортировка (оптимизация флагом) */ + static void bubbleSortWithFlag(int[] nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (int i = nums.length - 1; i > 0; i--) { + boolean flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // Записать обмен элементов + } + } + if (!flag) + break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + bubbleSort(nums); + System.out.println("После пузырьковой сортировки nums = " + Arrays.toString(nums)); + + int[] nums1 = { 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(nums1); + System.out.println("После пузырьковой сортировки nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/ru/codes/java/chapter_sorting/bucket_sort.java b/ru/codes/java/chapter_sorting/bucket_sort.java new file mode 100644 index 000000000..10e70f654 --- /dev/null +++ b/ru/codes/java/chapter_sorting/bucket_sort.java @@ -0,0 +1,47 @@ +/** + * File: bucket_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bucket_sort { + /* Сортировка корзинами */ + static void bucketSort(float[] nums) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + int k = nums.length / 2; + List> buckets = new ArrayList<>(); + for (int i = 0; i < k; i++) { + buckets.add(new ArrayList<>()); + } + // 1. Распределить элементы массива по корзинам + for (float num : nums) { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + int i = (int) (num * k); + // Добавить num в корзину i + buckets.get(i).add(num); + } + // 2. Выполнить сортировку внутри каждой корзины + for (List bucket : buckets) { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + Collections.sort(bucket); + } + // 3. Обойти корзины и объединить результаты + int i = 0; + for (List bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + + public static void main(String[] args) { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + float[] nums = { 0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f }; + bucketSort(nums); + System.out.println("После сортировки корзинами nums = " + Arrays.toString(nums)); + } +} diff --git a/ru/codes/java/chapter_sorting/counting_sort.java b/ru/codes/java/chapter_sorting/counting_sort.java new file mode 100644 index 000000000..dc60f3159 --- /dev/null +++ b/ru/codes/java/chapter_sorting/counting_sort.java @@ -0,0 +1,78 @@ +/** + * File: counting_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class counting_sort { + /* Сортировка подсчетом */ + // Простая реализация, не подходит для сортировки объектов + static void countingSortNaive(int[] nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + + /* Сортировка подсчетом */ + // Полная реализация, позволяет сортировать объекты и является стабильной сортировкой + static void countingSort(int[] nums) { + // 1. Найти максимальный элемент массива m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + int n = nums.length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // Поместить num по соответствующему индексу + counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + public static void main(String[] args) { + int[] nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSortNaive(nums); + System.out.println("После сортировки подсчетом (объекты не поддерживаются) nums = " + Arrays.toString(nums)); + + int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSort(nums1); + System.out.println("После сортировки подсчетом nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/ru/codes/java/chapter_sorting/heap_sort.java b/ru/codes/java/chapter_sorting/heap_sort.java new file mode 100644 index 000000000..b3518e554 --- /dev/null +++ b/ru/codes/java/chapter_sorting/heap_sort.java @@ -0,0 +1,57 @@ +/** + * File: heap_sort.java + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class heap_sort { + /* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ + public static void siftDown(int[] nums, int n, int i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) + break; + // Поменять два узла местами + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // Циклическое просеивание вниз + i = ma; + } + } + + /* Сортировка кучей */ + public static void heapSort(int[] nums) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (int i = nums.length / 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (int i = nums.length - 1; i > 0; i--) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0); + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + heapSort(nums); + System.out.println("После сортировки кучей nums = " + Arrays.toString(nums)); + } +} diff --git a/ru/codes/java/chapter_sorting/insertion_sort.java b/ru/codes/java/chapter_sorting/insertion_sort.java new file mode 100644 index 000000000..0886a05bd --- /dev/null +++ b/ru/codes/java/chapter_sorting/insertion_sort.java @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class insertion_sort { + /* Сортировка вставками */ + static void insertionSort(int[] nums) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо + j--; + } + nums[j + 1] = base; // Поместить base в правильную позицию + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + insertionSort(nums); + System.out.println("После сортировки вставками nums = " + Arrays.toString(nums)); + } +} diff --git a/ru/codes/java/chapter_sorting/merge_sort.java b/ru/codes/java/chapter_sorting/merge_sort.java new file mode 100644 index 000000000..6bccd5629 --- /dev/null +++ b/ru/codes/java/chapter_sorting/merge_sort.java @@ -0,0 +1,58 @@ +/** + * File: merge_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class merge_sort { + /* Объединить левый и правый подмассивы */ + static void merge(int[] nums, int left, int mid, int right) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + int[] tmp = new int[right - left + 1]; + // Инициализировать начальные индексы левого и правого подмассивов + int i = left, j = mid + 1, k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } + } + + /* Сортировка слиянием */ + static void mergeSort(int[] nums, int left, int right) { + // Условие завершения + if (left >= right) + return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + int mid = left + (right - left) / 2; // Вычислить середину + mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right); + } + + public static void main(String[] args) { + /* Сортировка слиянием */ + int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + mergeSort(nums, 0, nums.length - 1); + System.out.println("После сортировки слиянием nums = " + Arrays.toString(nums)); + } +} diff --git a/ru/codes/java/chapter_sorting/quick_sort.java b/ru/codes/java/chapter_sorting/quick_sort.java new file mode 100644 index 000000000..70ad5e3f7 --- /dev/null +++ b/ru/codes/java/chapter_sorting/quick_sort.java @@ -0,0 +1,158 @@ +/** + * File: quick_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +/* Класс быстрой сортировки */ +class QuickSort { + /* Обмен элементов */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + static int partition(int[] nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + public static void quickSort(int[] nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +class QuickSortMedian { + /* Обмен элементов */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Выбрать медиану из трех кандидатов */ + static int medianThree(int[] nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m находится между l и r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l находится между m и r + return right; + } + + /* Разбиение с опорными указателями (медиана трех) */ + static int partition(int[] nums, int left, int right) { + // Выбрать медиану из трех кандидатов + int med = medianThree(nums, left, (left + right) / 2, right); + // Переместить медиану в крайний левый элемент массива + swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + public static void quickSort(int[] nums, int left, int right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) + return; + // Разбиение с опорными указателями + int pivot = partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация глубины рекурсии) */ +class QuickSortTailCall { + /* Обмен элементов */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + static int partition(int[] nums, int left, int right) { + // Взять nums[left] в качестве опорного элемента + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + public static void quickSort(int[] nums, int left, int right) { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + int pivot = partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +} + +public class quick_sort { + public static void main(String[] args) { + /* Быстрая сортировка */ + int[] nums = { 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(nums, 0, nums.length - 1); + System.out.println("После быстрой сортировки nums = " + Arrays.toString(nums)); + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + int[] nums1 = { 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + System.out.println("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = " + Arrays.toString(nums1)); + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + int[] nums2 = { 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + System.out.println("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = " + Arrays.toString(nums2)); + } +} diff --git a/ru/codes/java/chapter_sorting/radix_sort.java b/ru/codes/java/chapter_sorting/radix_sort.java new file mode 100644 index 000000000..ac1dbfe6d --- /dev/null +++ b/ru/codes/java/chapter_sorting/radix_sort.java @@ -0,0 +1,69 @@ +/** + * File: radix_sort.java + * Created Time: 2023-01-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class radix_sort { + /* Получить k-й разряд элемента num, где exp = 10^(k-1) */ + static int digit(int num, int exp) { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num / exp) % 10; + } + + /* Сортировка подсчетом (сортировка по k-му разряду nums) */ + static void countingSortDigit(int[] nums, int exp) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + int[] counter = new int[10]; + int n = nums.length; + // Подсчитать число появлений каждой цифры от 0 до 9 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d]++; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (int i = 0; i < n; i++) + nums[i] = res[i]; + } + + /* Поразрядная сортировка */ + static void radixSort(int[] nums) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + int m = Integer.MIN_VALUE; + for (int num : nums) + if (num > m) + m = num; + // Проходить разряды от младшего к старшему + for (int exp = 1; exp <= m; exp *= 10) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp); + } + } + + public static void main(String[] args) { + // Поразрядная сортировка + int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 }; + radixSort(nums); + System.out.println("После поразрядной сортировки nums = " + Arrays.toString(nums)); + } +} diff --git a/ru/codes/java/chapter_sorting/selection_sort.java b/ru/codes/java/chapter_sorting/selection_sort.java new file mode 100644 index 000000000..b6e8be197 --- /dev/null +++ b/ru/codes/java/chapter_sorting/selection_sort.java @@ -0,0 +1,35 @@ +/** + * File: selection_sort.java + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class selection_sort { + /* Сортировка выбором */ + public static void selectionSort(int[] nums) { + int n = nums.length; + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (int i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // Записать индекс минимального элемента + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + selectionSort(nums); + System.out.println("После сортировки выбором nums = " + Arrays.toString(nums)); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/array_deque.java b/ru/codes/java/chapter_stack_and_queue/array_deque.java new file mode 100644 index 000000000..f8443bcd4 --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/array_deque.java @@ -0,0 +1,151 @@ +/** + * File: array_deque.java + * Created Time: 2023-02-16 + * Author: krahets (krahets@163.com), FangYuan33 (374072213@qq.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Двусторонняя очередь на основе кольцевого массива */ +class ArrayDeque { + private int[] nums; // Массив для хранения элементов двусторонней очереди + private int front; // Указатель head, указывающий на первый элемент очереди + private int queSize; // Длина двусторонней очереди + + /* Конструктор */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* Получить вместимость двусторонней очереди */ + public int capacity() { + return nums.length; + } + + /* Получение длины двусторонней очереди */ + public int size() { + return queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + public boolean isEmpty() { + return queSize == 0; + } + + /* Вычислить индекс в кольцевом массиве */ + private int index(int i) { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + capacity()) % capacity(); + } + + /* Добавление в голову очереди */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("Двусторонняя очередь заполнена"); + return; + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + front = index(front - 1); + // Добавить num в голову очереди + nums[front] = num; + queSize++; + } + + /* Добавление в хвост очереди */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("Двусторонняя очередь заполнена"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + int rear = index(front + queSize); + // Добавить num в хвост очереди + nums[rear] = num; + queSize++; + } + + /* Извлечение из головы очереди */ + public int popFirst() { + int num = peekFirst(); + // Указатель головы сдвигается на одну позицию назад + front = index(front + 1); + queSize--; + return num; + } + + /* Извлечение из хвоста очереди */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* Доступ к элементу в конце очереди */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // Вычислить индекс хвостового элемента + int last = index(front + queSize - 1); + return nums[last]; + } + + /* Вернуть массив для вывода */ + public int[] toArray() { + // Преобразовывать только элементы списка в пределах фактической длины + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +} + +public class array_deque { + public static void main(String[] args) { + /* Инициализация двусторонней очереди */ + ArrayDeque deque = new ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("Двусторонняя очередь deque = " + Arrays.toString(deque.toArray())); + + /* Доступ к элементу */ + int peekFirst = deque.peekFirst(); + System.out.println("Первый элемент peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("Последний элемент peekLast = " + peekLast); + + /* Добавление элемента в очередь */ + deque.pushLast(4); + System.out.println("После добавления элемента 4 в хвост deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("После добавления элемента 1 в голову deque = " + Arrays.toString(deque.toArray())); + + /* Извлечение элемента из очереди */ + int popLast = deque.popLast(); + System.out.println("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + Arrays.toString(deque.toArray())); + + /* Получение длины двусторонней очереди */ + int size = deque.size(); + System.out.println("Длина двусторонней очереди size = " + size); + + /* Проверка, пуста ли двусторонняя очередь */ + boolean isEmpty = deque.isEmpty(); + System.out.println("Пуста ли двусторонняя очередь = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/array_queue.java b/ru/codes/java/chapter_stack_and_queue/array_queue.java new file mode 100644 index 000000000..f13148abf --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/array_queue.java @@ -0,0 +1,115 @@ +/** + * File: array_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + private int[] nums; // Массив для хранения элементов очереди + private int front; // Указатель head, указывающий на первый элемент очереди + private int queSize; // Длина очереди + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* Получить вместимость очереди */ + public int capacity() { + return nums.length; + } + + /* Получение длины очереди */ + public int size() { + return queSize; + } + + /* Проверка, пуста ли очередь */ + public boolean isEmpty() { + return queSize == 0; + } + + /* Поместить в очередь */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("Очередь заполнена"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + int rear = (front + queSize) % capacity(); + // Добавить num в хвост очереди + nums[rear] = num; + queSize++; + } + + /* Извлечь из очереди */ + public int pop() { + int num = peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + front = (front + 1) % capacity(); + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* Вернуть массив */ + public int[] toArray() { + // Преобразовывать только элементы списка в пределах фактической длины + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % capacity()]; + } + return res; + } +} + +public class array_queue { + public static void main(String[] args) { + /* Инициализация очереди */ + int capacity = 10; + ArrayQueue queue = new ArrayQueue(capacity); + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("Очередь queue = " + Arrays.toString(queue.toArray())); + + /* Доступ к элементу в начале очереди */ + int peek = queue.peek(); + System.out.println("Первый элемент peek = " + peek); + + /* Извлечение элемента из очереди */ + int pop = queue.pop(); + System.out.println("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + Arrays.toString(queue.toArray())); + + /* Получение длины очереди */ + int size = queue.size(); + System.out.println("Длина очереди size = " + size); + + /* Проверка, пуста ли очередь */ + boolean isEmpty = queue.isEmpty(); + System.out.println("Пуста ли очередь = " + isEmpty); + + /* Проверка кольцевого массива */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + System.out.println("После " + i + "-го раунда операций enqueue и dequeue queue = " + Arrays.toString(queue.toArray())); + } + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/array_stack.java b/ru/codes/java/chapter_stack_and_queue/array_stack.java new file mode 100644 index 000000000..8d0071aec --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/array_stack.java @@ -0,0 +1,84 @@ +/** + * File: array_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Стек на основе массива */ +class ArrayStack { + private ArrayList stack; + + public ArrayStack() { + // Инициализация списка (динамического массива) + stack = new ArrayList<>(); + } + + /* Получение длины стека */ + public int size() { + return stack.size(); + } + + /* Проверка, пуст ли стек */ + public boolean isEmpty() { + return size() == 0; + } + + /* Поместить в стек */ + public void push(int num) { + stack.add(num); + } + + /* Извлечь из стека */ + public int pop() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.remove(size() - 1); + } + + /* Доступ к верхнему элементу стека */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.get(size() - 1); + } + + /* Преобразовать List в Array и вернуть */ + public Object[] toArray() { + return stack.toArray(); + } +} + +public class array_stack { + public static void main(String[] args) { + /* Инициализация стека */ + ArrayStack stack = new ArrayStack(); + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("Стек stack = " + Arrays.toString(stack.toArray())); + + /* Доступ к верхнему элементу стека */ + int peek = stack.peek(); + System.out.println("Верхний элемент peek = " + peek); + + /* Извлечение элемента из стека */ + int pop = stack.pop(); + System.out.println("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + Arrays.toString(stack.toArray())); + + /* Получение длины стека */ + int size = stack.size(); + System.out.println("Длина стека size = " + size); + + /* Проверка на пустоту */ + boolean isEmpty = stack.isEmpty(); + System.out.println("Пуст ли стек = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/deque.java b/ru/codes/java/chapter_stack_and_queue/deque.java new file mode 100644 index 000000000..707961e6c --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/deque.java @@ -0,0 +1,46 @@ +/** + * File: deque.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class deque { + public static void main(String[] args) { + /* Инициализация двусторонней очереди */ + Deque deque = new LinkedList<>(); + deque.offerLast(3); + deque.offerLast(2); + deque.offerLast(5); + System.out.println("Двусторонняя очередь deque = " + deque); + + /* Доступ к элементу */ + int peekFirst = deque.peekFirst(); + System.out.println("Первый элемент peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("Последний элемент peekLast = " + peekLast); + + /* Добавление элемента в очередь */ + deque.offerLast(4); + System.out.println("После добавления элемента 4 в хвост deque = " + deque); + deque.offerFirst(1); + System.out.println("После добавления элемента 1 в голову deque = " + deque); + + /* Извлечение элемента из очереди */ + int popLast = deque.pollLast(); + System.out.println("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + deque); + int popFirst = deque.pollFirst(); + System.out.println("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + deque); + + /* Получение длины двусторонней очереди */ + int size = deque.size(); + System.out.println("Длина двусторонней очереди size = " + size); + + /* Проверка, пуста ли двусторонняя очередь */ + boolean isEmpty = deque.isEmpty(); + System.out.println("Пуста ли двусторонняя очередь = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/ru/codes/java/chapter_stack_and_queue/linkedlist_deque.java new file mode 100644 index 000000000..f16d01034 --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -0,0 +1,175 @@ +/** + * File: linkedlist_deque.java + * Created Time: 2023-01-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Узел двусвязного списка */ +class ListNode { + int val; // Значение узла + ListNode next; // Ссылка на узел-преемник + ListNode prev; // Ссылка на узел-предшественник + + ListNode(int val) { + this.val = val; + prev = next = null; + } +} + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + private ListNode front, rear; // Головной узел front, хвостовой узел rear + private int queSize = 0; // Длина двусторонней очереди + + public LinkedListDeque() { + front = rear = null; + } + + /* Получение длины двусторонней очереди */ + public int size() { + return queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + public boolean isEmpty() { + return size() == 0; + } + + /* Операция добавления в очередь */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (isEmpty()) + front = rear = node; + // Операция добавления в голову очереди + else if (isFront) { + // Добавить node в голову списка + front.prev = node; + node.next = front; + front = node; // Обновить головной узел + // Операция добавления в хвост очереди + } else { + // Добавить node в хвост списка + rear.next = node; + node.prev = rear; + rear = node; // Обновить хвостовой узел + } + queSize++; // Обновить длину очереди + } + + /* Добавление в голову очереди */ + public void pushFirst(int num) { + push(num, true); + } + + /* Добавление в хвост очереди */ + public void pushLast(int num) { + push(num, false); + } + + /* Операция извлечения из очереди */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // Операция извлечения из головы очереди + if (isFront) { + val = front.val; // Временно сохранить значение головного узла + // Удалить головной узел + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // Обновить головной узел + // Операция извлечения из хвоста очереди + } else { + val = rear.val; // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // Обновить хвостовой узел + } + queSize--; // Обновить длину очереди + return val; + } + + /* Извлечение из головы очереди */ + public int popFirst() { + return pop(true); + } + + /* Извлечение из хвоста очереди */ + public int popLast() { + return pop(false); + } + + /* Доступ к элементу в начале очереди */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* Доступ к элементу в конце очереди */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; + } + + /* Вернуть массив для вывода */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_deque { + public static void main(String[] args) { + /* Инициализация двусторонней очереди */ + LinkedListDeque deque = new LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("Двусторонняя очередь deque = " + Arrays.toString(deque.toArray())); + + /* Доступ к элементу */ + int peekFirst = deque.peekFirst(); + System.out.println("Первый элемент peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("Последний элемент peekLast = " + peekLast); + + /* Добавление элемента в очередь */ + deque.pushLast(4); + System.out.println("После добавления элемента 4 в хвост deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("После добавления элемента 1 в голову deque = " + Arrays.toString(deque.toArray())); + + /* Извлечение элемента из очереди */ + int popLast = deque.popLast(); + System.out.println("Извлеченный из хвоста элемент = " + popLast + ", deque после извлечения из хвоста = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("Извлеченный из головы элемент = " + popFirst + ", deque после извлечения из головы = " + Arrays.toString(deque.toArray())); + + /* Получение длины двусторонней очереди */ + int size = deque.size(); + System.out.println("Длина двусторонней очереди size = " + size); + + /* Проверка, пуста ли двусторонняя очередь */ + boolean isEmpty = deque.isEmpty(); + System.out.println("Пуста ли двусторонняя очередь = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/ru/codes/java/chapter_stack_and_queue/linkedlist_queue.java new file mode 100644 index 000000000..8f4b76b0e --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -0,0 +1,104 @@ +/** + * File: linkedlist_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* Очередь на основе связного списка */ +class LinkedListQueue { + private ListNode front, rear; // Головной узел front, хвостовой узел rear + private int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* Получение длины очереди */ + public int size() { + return queSize; + } + + /* Проверка, пуста ли очередь */ + public boolean isEmpty() { + return size() == 0; + } + + /* Поместить в очередь */ + public void push(int num) { + // Добавить num после хвостового узла + ListNode node = new ListNode(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (front == null) { + front = node; + rear = node; + // Если очередь не пуста, добавить этот узел после хвостового узла + } else { + rear.next = node; + rear = node; + } + queSize++; + } + + /* Извлечь из очереди */ + public int pop() { + int num = peek(); + // Удалить головной узел + front = front.next; + queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* Преобразовать связный список в Array и вернуть */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + public static void main(String[] args) { + /* Инициализация очереди */ + LinkedListQueue queue = new LinkedListQueue(); + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("Очередь queue = " + Arrays.toString(queue.toArray())); + + /* Доступ к элементу в начале очереди */ + int peek = queue.peek(); + System.out.println("Первый элемент peek = " + peek); + + /* Извлечение элемента из очереди */ + int pop = queue.pop(); + System.out.println("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + Arrays.toString(queue.toArray())); + + /* Получение длины очереди */ + int size = queue.size(); + System.out.println("Длина очереди size = " + size); + + /* Проверка, пуста ли очередь */ + boolean isEmpty = queue.isEmpty(); + System.out.println("Пуста ли очередь = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/ru/codes/java/chapter_stack_and_queue/linkedlist_stack.java new file mode 100644 index 000000000..08a912539 --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -0,0 +1,95 @@ +/** + * File: linkedlist_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; +import utils.*; + +/* Стек на основе связного списка */ +class LinkedListStack { + private ListNode stackPeek; // Использовать головной узел как вершину стека + private int stkSize = 0; // Длина стека + + public LinkedListStack() { + stackPeek = null; + } + + /* Получение длины стека */ + public int size() { + return stkSize; + } + + /* Проверка, пуст ли стек */ + public boolean isEmpty() { + return size() == 0; + } + + /* Поместить в стек */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* Извлечь из стека */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* Доступ к верхнему элементу стека */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } + + /* Преобразовать List в Array и вернуть */ + public int[] toArray() { + ListNode node = stackPeek; + int[] res = new int[size()]; + for (int i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + public static void main(String[] args) { + /* Инициализация стека */ + LinkedListStack stack = new LinkedListStack(); + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("Стек stack = " + Arrays.toString(stack.toArray())); + + /* Доступ к верхнему элементу стека */ + int peek = stack.peek(); + System.out.println("Верхний элемент peek = " + peek); + + /* Извлечение элемента из стека */ + int pop = stack.pop(); + System.out.println("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + Arrays.toString(stack.toArray())); + + /* Получение длины стека */ + int size = stack.size(); + System.out.println("Длина стека size = " + size); + + /* Проверка на пустоту */ + boolean isEmpty = stack.isEmpty(); + System.out.println("Пуст ли стек = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/queue.java b/ru/codes/java/chapter_stack_and_queue/queue.java new file mode 100644 index 000000000..867ca8acf --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/queue.java @@ -0,0 +1,40 @@ +/** + * File: queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class queue { + public static void main(String[] args) { + /* Инициализация очереди */ + Queue queue = new LinkedList<>(); + + /* Добавление элемента в очередь */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + System.out.println("Очередь queue = " + queue); + + /* Доступ к элементу в начале очереди */ + int peek = queue.peek(); + System.out.println("Первый элемент peek = " + peek); + + /* Извлечение элемента из очереди */ + int pop = queue.poll(); + System.out.println("Извлеченный элемент pop = " + pop + ", queue после извлечения = " + queue); + + /* Получение длины очереди */ + int size = queue.size(); + System.out.println("Длина очереди size = " + size); + + /* Проверка, пуста ли очередь */ + boolean isEmpty = queue.isEmpty(); + System.out.println("Пуста ли очередь = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_stack_and_queue/stack.java b/ru/codes/java/chapter_stack_and_queue/stack.java new file mode 100644 index 000000000..5247613b5 --- /dev/null +++ b/ru/codes/java/chapter_stack_and_queue/stack.java @@ -0,0 +1,40 @@ +/** + * File: stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class stack { + public static void main(String[] args) { + /* Инициализация стека */ + Stack stack = new Stack<>(); + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("Стек stack = " + stack); + + /* Доступ к верхнему элементу стека */ + int peek = stack.peek(); + System.out.println("Верхний элемент peek = " + peek); + + /* Извлечение элемента из стека */ + int pop = stack.pop(); + System.out.println("Извлеченный элемент pop = " + pop + ", stack после извлечения = " + stack); + + /* Получение длины стека */ + int size = stack.size(); + System.out.println("Длина стека size = " + size); + + /* Проверка на пустоту */ + boolean isEmpty = stack.isEmpty(); + System.out.println("Пуст ли стек = " + isEmpty); + } +} diff --git a/ru/codes/java/chapter_tree/array_binary_tree.java b/ru/codes/java/chapter_tree/array_binary_tree.java new file mode 100644 index 000000000..b32a46a8d --- /dev/null +++ b/ru/codes/java/chapter_tree/array_binary_tree.java @@ -0,0 +1,136 @@ +/** + * File: array_binary_tree.java + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree { + private List tree; + + /* Конструктор */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* Вместимость списка */ + public int size() { + return tree.size(); + } + + /* Получить значение узла с индексом i */ + public Integer val(int i) { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла узла с индексом i */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* Обход в ширину */ + public List levelOrder() { + List res = new ArrayList<>(); + // Непосредственно обходить массив + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } + + /* Обход в глубину */ + private void dfs(Integer i, String order, List res) { + // Если это пустая позиция, вернуть + if (val(i) == null) + return; + // Предварительный обход + if ("pre".equals(order)) + res.add(val(i)); + dfs(left(i), order, res); + // Симметричный обход + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // Обратный обход + if ("post".equals(order)) + res.add(val(i)); + } + + /* Предварительный обход */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* Симметричный обход */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* Обратный обход */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } +} + +public class array_binary_tree { + public static void main(String[] args) { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + List arr = Arrays.asList(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15); + + TreeNode root = TreeNode.listToTree(arr); + System.out.println("\nИнициализация двоичного дерева\n"); + System.out.println("Массивное представление двоичного дерева:"); + System.out.println(arr); + System.out.println("Связное представление двоичного дерева:"); + PrintUtil.printTree(root); + + // Класс двоичного дерева в массивном представлении + ArrayBinaryTree abt = new ArrayBinaryTree(arr); + + // Доступ к узлу + int i = 1; + Integer l = abt.left(i); + Integer r = abt.right(i); + Integer p = abt.parent(i); + System.out.println("\nТекущий узел: индекс = " + i + " , значение = " + abt.val(i)); + System.out.println("Индекс левого дочернего узла = " + l + " , значение = " + (l == null ? "null" : abt.val(l))); + System.out.println("Индекс правого дочернего узла = " + r + " , значение = " + (r == null ? "null" : abt.val(r))); + System.out.println("Индекс родительского узла = " + p + " , значение = " + (p == null ? "null" : abt.val(p))); + + // Обходить дерево + List res = abt.levelOrder(); + System.out.println("\nОбход в ширину =" + res); + res = abt.preOrder(); + System.out.println("Предварительный обход =" + res); + res = abt.inOrder(); + System.out.println("Симметричный обход =" + res); + res = abt.postOrder(); + System.out.println("Обратный обход =" + res); + } +} diff --git a/ru/codes/java/chapter_tree/avl_tree.java b/ru/codes/java/chapter_tree/avl_tree.java new file mode 100644 index 000000000..7eb0780f1 --- /dev/null +++ b/ru/codes/java/chapter_tree/avl_tree.java @@ -0,0 +1,220 @@ +/** + * File: avl_tree.java + * Created Time: 2022-12-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* AVL-дерево */ +class AVLTree { + TreeNode root; // Корневой узел + + /* Получить высоту узла */ + public int height(TreeNode node) { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node == null ? -1 : node.height; + } + + /* Обновить высоту узла */ + private void updateHeight(TreeNode node) { + // Высота узла равна высоте более высокого поддерева + 1 + node.height = Math.max(height(node.left), height(node.right)) + 1; + } + + /* Получить коэффициент баланса */ + public int balanceFactor(TreeNode node) { + // Коэффициент баланса пустого узла равен 0 + if (node == null) + return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return height(node.left) - height(node.right); + } + + /* Операция правого вращения */ + private TreeNode rightRotate(TreeNode node) { + TreeNode child = node.left; + TreeNode grandChild = child.right; + // Выполнить правое вращение узла node вокруг child + child.right = node; + node.left = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Операция левого вращения */ + private TreeNode leftRotate(TreeNode node) { + TreeNode child = node.right; + TreeNode grandChild = child.left; + // Выполнить левое вращение узла node вокруг child + child.left = node; + node.right = grandChild; + // Обновить высоту узла + updateHeight(node); + updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + private TreeNode rotate(TreeNode node) { + // Получить коэффициент баланса узла node + int balanceFactor = balanceFactor(node); + // Левосторонне перекошенное дерево + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // Правое вращение + return rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // Левое вращение + return leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + /* Вставка узла */ + public void insert(int val) { + root = insertHelper(root, val); + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + private TreeNode insertHelper(TreeNode node, int val) { + if (node == null) + return new TreeNode(val); + /* 1. Найти позицию вставки и вставить узел */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // Повторяющийся узел не вставлять, сразу вернуть + updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Удаление узла */ + public void remove(int val) { + root = removeHelper(root, val); + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + private TreeNode removeHelper(TreeNode node, int val) { + if (node == null) + return null; + /* 1. Найти узел и удалить его */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode child = node.left != null ? node.left : node.right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == null) + return null; + // Число дочерних узлов = 1, удалить node напрямую + else + node = child; + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + TreeNode temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Поиск узла */ + public TreeNode search(int val) { + TreeNode cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < val) + cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > val) + cur = cur.left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } +} + +public class avl_tree { + static void testInsert(AVLTree tree, int val) { + tree.insert(val); + System.out.println("\nПосле вставки узла " + val + " AVL-дерево имеет вид"); + PrintUtil.printTree(tree.root); + } + + static void testRemove(AVLTree tree, int val) { + tree.remove(val); + System.out.println("\nПосле удаления узла " + val + " AVL-дерево имеет вид"); + PrintUtil.printTree(tree.root); + } + + public static void main(String[] args) { + /* Инициализация пустого AVL-дерева */ + AVLTree avlTree = new AVLTree(); + + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* Вставка повторяющегося узла */ + testInsert(avlTree, 7); + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(avlTree, 8); // Удаление узла степени 0 + testRemove(avlTree, 5); // Удаление узла степени 1 + testRemove(avlTree, 4); // Удаление узла степени 2 + + /* Поиск узла */ + TreeNode node = avlTree.search(7); + System.out.println("\nНайденный объект узла = " + node + ", значение узла = " + node.val); + } +} diff --git a/ru/codes/java/chapter_tree/binary_search_tree.java b/ru/codes/java/chapter_tree/binary_search_tree.java new file mode 100644 index 000000000..1a8513ea6 --- /dev/null +++ b/ru/codes/java/chapter_tree/binary_search_tree.java @@ -0,0 +1,158 @@ +/** + * File: binary_search_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* Двоичное дерево поиска */ +class BinarySearchTree { + private TreeNode root; + + /* Конструктор */ + public BinarySearchTree() { + // Инициализировать пустое дерево + root = null; + } + + /* Получить корневой узел двоичного дерева */ + public TreeNode getRoot() { + return root; + } + + /* Поиск узла */ + public TreeNode search(int num) { + TreeNode cur = root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < num) + cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > num) + cur = cur.left; + // Найти целевой узел и выйти из цикла + else + break; + } + // Вернуть целевой узел + return cur; + } + + /* Вставка узла */ + public void insert(int num) { + // Если дерево пусто, инициализировать корневой узел + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode cur = root, pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти повторяющийся узел и сразу вернуть + if (cur.val == num) + return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur.val < num) + cur = cur.right; + // Позиция вставки находится в левом поддереве cur + else + cur = cur.left; + } + // Вставка узла + TreeNode node = new TreeNode(num); + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + + /* Удаление узла */ + public void remove(int num) { + // Если дерево пусто, сразу вернуть + if (root == null) + return; + TreeNode cur = root, pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти узел для удаления и выйти из цикла + if (cur.val == num) + break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur.val < num) + cur = cur.right; + // Узел для удаления находится в левом поддереве cur + else + cur = cur.left; + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur == null) + return; + // Число дочерних узлов = 0 или 1 + if (cur.left == null || cur.right == null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + TreeNode child = cur.left != null ? cur.left : cur.right; + // Удалить узел cur + if (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + root = child; + } + } + // Число дочерних узлов = 2 + else { + // Получить следующий узел после cur в симметричном обходе + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // Рекурсивно удалить узел tmp + remove(tmp.val); + // Перезаписать cur значением tmp + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + public static void main(String[] args) { + /* Инициализация двоичного дерева поиска */ + BinarySearchTree bst = new BinarySearchTree(); + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; + for (int num : nums) { + bst.insert(num); + } + System.out.println("\nИсходное двоичное дерево\n"); + PrintUtil.printTree(bst.getRoot()); + + /* Поиск узла */ + TreeNode node = bst.search(7); + System.out.println("\nНайденный объект узла = " + node + ", значение узла = " + node.val); + + /* Вставка узла */ + bst.insert(16); + System.out.println("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); + PrintUtil.printTree(bst.getRoot()); + + /* Удаление узла */ + bst.remove(1); + System.out.println("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(2); + System.out.println("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(4); + System.out.println("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); + PrintUtil.printTree(bst.getRoot()); + } +} diff --git a/ru/codes/java/chapter_tree/binary_tree.java b/ru/codes/java/chapter_tree/binary_tree.java new file mode 100644 index 000000000..76e8a2cc7 --- /dev/null +++ b/ru/codes/java/chapter_tree/binary_tree.java @@ -0,0 +1,40 @@ +/** + * File: binary_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +public class binary_tree { + public static void main(String[] args) { + /* Инициализация двоичного дерева */ + // Инициализация узла + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Построить связи между узлами (указатели) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + System.out.println("\nИнициализация двоичного дерева\n"); + PrintUtil.printTree(n1); + + /* Вставка и удаление узлов */ + TreeNode P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + System.out.println("\nПосле вставки узла P\n"); + PrintUtil.printTree(n1); + // Удалить узел P + n1.left = n2; + System.out.println("\nПосле удаления узла P\n"); + PrintUtil.printTree(n1); + } +} diff --git a/ru/codes/java/chapter_tree/binary_tree_bfs.java b/ru/codes/java/chapter_tree/binary_tree_bfs.java new file mode 100644 index 000000000..8fd21442a --- /dev/null +++ b/ru/codes/java/chapter_tree/binary_tree_bfs.java @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_bfs { + /* Обход в ширину */ + static List levelOrder(TreeNode root) { + // Инициализировать очередь и добавить корневой узел + Queue queue = new LinkedList<>(); + queue.add(root); + // Инициализировать список для хранения последовательности обхода + List list = new ArrayList<>(); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // Извлечение из очереди + list.add(node.val); // Сохранить значение узла + if (node.left != null) + queue.offer(node.left); // Поместить левый дочерний узел в очередь + if (node.right != null) + queue.offer(node.right); // Поместить правый дочерний узел в очередь + } + return list; + } + + public static void main(String[] args) { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\nИнициализация двоичного дерева\n"); + PrintUtil.printTree(root); + + /* Обход в ширину */ + List list = levelOrder(root); + System.out.println("\nПоследовательность печати узлов при обходе в ширину = " + list); + } +} diff --git a/ru/codes/java/chapter_tree/binary_tree_dfs.java b/ru/codes/java/chapter_tree/binary_tree_dfs.java new file mode 100644 index 000000000..f3ba8ddc8 --- /dev/null +++ b/ru/codes/java/chapter_tree/binary_tree_dfs.java @@ -0,0 +1,68 @@ +/** + * File: binary_tree_dfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_dfs { + // Инициализировать список для хранения последовательности обхода + static ArrayList list = new ArrayList<>(); + + /* Предварительный обход */ + static void preOrder(TreeNode root) { + if (root == null) + return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* Симметричный обход */ + static void inOrder(TreeNode root) { + if (root == null) + return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } + + /* Обратный обход */ + static void postOrder(TreeNode root) { + if (root == null) + return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + + public static void main(String[] args) { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\nИнициализация двоичного дерева\n"); + PrintUtil.printTree(root); + + /* Предварительный обход */ + list.clear(); + preOrder(root); + System.out.println("\nПоследовательность печати узлов при предварительном обходе = " + list); + + /* Симметричный обход */ + list.clear(); + inOrder(root); + System.out.println("\nПоследовательность печати узлов при симметричном обходе = " + list); + + /* Обратный обход */ + list.clear(); + postOrder(root); + System.out.println("\nПоследовательность печати узлов при обратном обходе = " + list); + } +} diff --git a/ru/codes/java/utils/ListNode.java b/ru/codes/java/utils/ListNode.java new file mode 100644 index 000000000..b960fdf8f --- /dev/null +++ b/ru/codes/java/utils/ListNode.java @@ -0,0 +1,28 @@ +/** + * File: ListNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +/* Узел связного списка */ +public class ListNode { + public int val; + public ListNode next; + + public ListNode(int x) { + val = x; + } + + /* Десериализовать список в связный список */ + public static ListNode arrToLinkedList(int[] arr) { + ListNode dum = new ListNode(0); + ListNode head = dum; + for (int val : arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } +} diff --git a/ru/codes/java/utils/PrintUtil.java b/ru/codes/java/utils/PrintUtil.java new file mode 100644 index 000000000..325b5f160 --- /dev/null +++ b/ru/codes/java/utils/PrintUtil.java @@ -0,0 +1,116 @@ +/** + * File: PrintUtil.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +class Trunk { + Trunk prev; + String str; + + Trunk(Trunk prev, String str) { + this.prev = prev; + this.str = str; + } +}; + +public class PrintUtil { + /* Вывести матрицу (Array) */ + public static void printMatrix(T[][] matrix) { + System.out.println("["); + for (T[] row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* Вывести матрицу (List) */ + public static void printMatrix(List> matrix) { + System.out.println("["); + for (List row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* Вывести связный список */ + public static void printLinkedList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(String.valueOf(head.val)); + head = head.next; + } + System.out.println(String.join(" -> ", list)); + } + + /* Вывести двоичное дерево */ + public static void printTree(TreeNode root) { + printTree(root, null, false); + } + + /** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void printTree(TreeNode root, Trunk prev, boolean isRight) { + if (root == null) { + return; + } + + String prev_str = " "; + Trunk trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + showTrunks(trunk); + System.out.println(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + printTree(root.left, trunk, false); + } + + public static void showTrunks(Trunk p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + System.out.print(p.str); + } + + /* Вывести хеш-таблицу */ + public static void printHashMap(Map map) { + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + } + + /* Вывести кучу (приоритетную очередь) */ + public static void printHeap(Queue queue) { + List list = new ArrayList<>(queue); + System.out.print("Массивное представление кучи:"); + System.out.println(list); + System.out.println("Древовидное представление кучи:"); + TreeNode root = TreeNode.listToTree(list); + printTree(root); + } +} diff --git a/ru/codes/java/utils/TreeNode.java b/ru/codes/java/utils/TreeNode.java new file mode 100644 index 000000000..0f341a9c7 --- /dev/null +++ b/ru/codes/java/utils/TreeNode.java @@ -0,0 +1,73 @@ +/** + * File: TreeNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* Класс узла двоичного дерева */ +public class TreeNode { + public int val; // Значение узла + public int height; // Высота узла + public TreeNode left; // Ссылка на левый дочерний узел + public TreeNode right; // Ссылка на правый дочерний узел + + /* Конструктор */ + public TreeNode(int x) { + val = x; + } + + // Правила кодирования сериализации см.: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // Массивное представление двоичного дерева: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // Связное представление двоичного дерева: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* Десериализовать список в двоичное дерево: рекурсия */ + private static TreeNode listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.size() || arr.get(i) == null) { + return null; + } + TreeNode root = new TreeNode(arr.get(i)); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; + } + + /* Десериализовать список в двоичное дерево */ + public static TreeNode listToTree(List arr) { + return listToTreeDFS(arr, 0); + } + + /* Сериализовать двоичное дерево в список: рекурсия */ + private static void treeToListDFS(TreeNode root, int i, List res) { + if (root == null) + return; + while (i >= res.size()) { + res.add(null); + } + res.set(i, root.val); + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); + } + + /* Сериализовать двоичное дерево в список */ + public static List treeToList(TreeNode root) { + List res = new ArrayList<>(); + treeToListDFS(root, 0, res); + return res; + } +} diff --git a/ru/codes/java/utils/Vertex.java b/ru/codes/java/utils/Vertex.java new file mode 100644 index 000000000..266b2d83c --- /dev/null +++ b/ru/codes/java/utils/Vertex.java @@ -0,0 +1,36 @@ +/** + * File: Vertex.java + * Created Time: 2023-02-15 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* Класс вершины */ +public class Vertex { + public int val; + + public Vertex(int val) { + this.val = val; + } + + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + public static Vertex[] valsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.length]; + for (int i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + public static List vetsToVals(List vets) { + List vals = new ArrayList<>(); + for (Vertex vet : vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/ru/codes/javascript/.prettierrc b/ru/codes/javascript/.prettierrc new file mode 100644 index 000000000..3f4aa8cb6 --- /dev/null +++ b/ru/codes/javascript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/ru/codes/javascript/chapter_array_and_linkedlist/array.js b/ru/codes/javascript/chapter_array_and_linkedlist/array.js new file mode 100644 index 000000000..2fcc45e7d --- /dev/null +++ b/ru/codes/javascript/chapter_array_and_linkedlist/array.js @@ -0,0 +1,97 @@ +/** + * File: array.js + * Created Time: 2022-11-27 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Случайный доступ к элементу */ +function randomAccess(nums) { + // Случайным образом выбрать число из интервала [0, nums.length) + const random_index = Math.floor(Math.random() * nums.length); + // Получить и вернуть случайный элемент + const random_num = nums[random_index]; + return random_num; +} + +/* Увеличить длину массива */ +// Обратите внимание: Array в JavaScript — это динамический массив, его можно расширять напрямую +// Для удобства обучения в этой функции Array рассматривается как массив неизменяемой длины +function extend(nums, enlarge) { + // Инициализировать массив увеличенной длины + const res = new Array(nums.length + enlarge).fill(0); + // Скопировать все элементы исходного массива в новый массив + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // Вернуть новый массив после расширения + return res; +} + +/* Вставить элемент num по индексу index в массив */ +function insert(nums, num, index) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; +} + +/* Удалить элемент по индексу index */ +function remove(nums, index) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* Обход массива */ +function traverse(nums) { + let count = 0; + // Обход массива по индексам + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // Непосредственно обходить элементы массива + for (const num of nums) { + count += num; + } +} + +/* Найти заданный элемент в массиве */ +function find(nums, target) { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) return i; + } + return -1; +} + +/* Driver Code */ +/* Инициализация массива */ +const arr = new Array(5).fill(0); +console.log('Массив arr =', arr); +let nums = [1, 3, 2, 5, 4]; +console.log('Массив nums =', nums); + +/* Случайный доступ */ +let random_num = randomAccess(nums); +console.log('Случайный элемент из nums =', random_num); + +/* Расширение длины */ +nums = extend(nums, 3); +console.log('После увеличения длины массива до 8 nums =', nums); + +/* Вставка элемента */ +insert(nums, 6, 3); +console.log('После вставки числа 6 по индексу 3 nums =', nums); + +/* Удаление элемента */ +remove(nums, 2); +console.log('После удаления элемента по индексу 2 nums =', nums); + +/* Обход массива */ +traverse(nums); + +/* Поиск элемента */ +let index = find(nums, 3); +console.log('Поиск элемента 3 в nums: индекс =', index); diff --git a/ru/codes/javascript/chapter_array_and_linkedlist/linked_list.js b/ru/codes/javascript/chapter_array_and_linkedlist/linked_list.js new file mode 100644 index 000000000..c31c77b5d --- /dev/null +++ b/ru/codes/javascript/chapter_array_and_linkedlist/linked_list.js @@ -0,0 +1,82 @@ +/** + * File: linked_list.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) + */ + +const { printLinkedList } = require('../modules/PrintUtil'); +const { ListNode } = require('../modules/ListNode'); + +/* Вставить узел P после узла n0 в связном списке */ +function insert(n0, P) { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* Удалить первый узел после узла n0 в связном списке */ +function remove(n0) { + if (!n0.next) return; + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* Доступ к узлу связного списка по индексу index */ +function access(head, index) { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* Найти в связном списке первый узел со значением target */ +function find(head, target) { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* Инициализация связного списка */ +// Инициализация всех узлов +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// Построить ссылки между узлами +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('Исходный связный список'); +printLinkedList(n0); + +/* Вставка узла */ +insert(n0, new ListNode(0)); +console.log('Связный список после вставки узла'); +printLinkedList(n0); + +/* Удаление узла */ +remove(n0); +console.log('Связный список после удаления узла'); +printLinkedList(n0); + +/* Доступ к узлу */ +const node = access(n0, 3); +console.log('Значение узла по индексу 3 в связном списке = ' + node.val); + +/* Поиск узла */ +const index = find(n0, 2); +console.log('Индекс узла со значением 2 в связном списке = ' + index); diff --git a/ru/codes/javascript/chapter_array_and_linkedlist/list.js b/ru/codes/javascript/chapter_array_and_linkedlist/list.js new file mode 100644 index 000000000..0f76e6e89 --- /dev/null +++ b/ru/codes/javascript/chapter_array_and_linkedlist/list.js @@ -0,0 +1,57 @@ +/** + * File: list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Инициализация списка */ +const nums = [1, 3, 2, 5, 4]; +console.log(`Список nums = ${nums}`); + +/* Доступ к элементу */ +const num = nums[1]; +console.log(`Элемент по индексу 1: num = ${num}`); + +/* Обновление элемента */ +nums[1] = 0; +console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums}`); + +/* Очистить список */ +nums.length = 0; +console.log(`После очистки списка nums = ${nums}`); + +/* Добавление элемента в конец */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`После добавления элементов nums = ${nums}`); + +/* Вставка элемента в середину */ +nums.splice(3, 0, 6); +console.log(`После вставки числа 6 по индексу 3 nums = ${nums}`); + +/* Удаление элемента */ +nums.splice(3, 1); +console.log(`После удаления элемента по индексу 3 nums = ${nums}`); + +/* Обходить список по индексам */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* Непосредственно обходить элементы списка */ +count = 0; +for (const x of nums) { + count += x; +} + +/* Объединить два списка */ +const nums1 = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`После конкатенации списка nums1 к nums nums = ${nums}`); + +/* Отсортировать список */ +nums.sort((a, b) => a - b); +console.log(`После сортировки списка nums = ${nums}`); diff --git a/ru/codes/javascript/chapter_array_and_linkedlist/my_list.js b/ru/codes/javascript/chapter_array_and_linkedlist/my_list.js new file mode 100644 index 000000000..a75ff76bc --- /dev/null +++ b/ru/codes/javascript/chapter_array_and_linkedlist/my_list.js @@ -0,0 +1,141 @@ +/** + * File: my_list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Класс списка */ +class MyList { + #arr = new Array(); // Массив (для хранения элементов списка) + #capacity = 10; // Вместимость списка + #size = 0; // Длина списка (текущее число элементов) + #extendRatio = 2; // Коэффициент увеличения списка при каждом расширении + + /* Конструктор */ + constructor() { + this.#arr = new Array(this.#capacity); + } + + /* Получить длину списка (текущее число элементов) */ + size() { + return this.#size; + } + + /* Получить вместимость списка */ + capacity() { + return this.#capacity; + } + + /* Доступ к элементу */ + get(index) { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); + return this.#arr[index]; + } + + /* Обновление элемента */ + set(index, num) { + if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); + this.#arr[index] = num; + } + + /* Добавление элемента в конец */ + add(num) { + // Если длина равна вместимости, требуется расширение + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // Добавить новый элемент в конец списка + this.#arr[this.#size] = num; + this.#size++; + } + + /* Вставка элемента в середину */ + insert(index, num) { + if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); + // При превышении вместимости по числу элементов запускается расширение + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (let j = this.#size - 1; j >= index; j--) { + this.#arr[j + 1] = this.#arr[j]; + } + // Обновить число элементов + this.#arr[index] = num; + this.#size++; + } + + /* Удаление элемента */ + remove(index) { + if (index < 0 || index >= this.#size) throw new Error('индекс выходит за границы'); + let num = this.#arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (let j = index; j < this.#size - 1; j++) { + this.#arr[j] = this.#arr[j + 1]; + } + // Обновить число элементов + this.#size--; + // Вернуть удаленный элемент + return num; + } + + /* Расширение списка */ + extendCapacity() { + // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив + this.#arr = this.#arr.concat( + new Array(this.capacity() * (this.#extendRatio - 1)) + ); + // Обновить вместимость списка + this.#capacity = this.#arr.length; + } + + /* Преобразовать список в массив */ + toArray() { + let size = this.size(); + // Преобразовывать только элементы списка в пределах фактической длины + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* Инициализация списка */ +const nums = new MyList(); +/* Добавление элемента в конец */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `Список nums = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` +); + +/* Вставка элемента в середину */ +nums.insert(3, 6); +console.log(`После вставки числа 6 по индексу 3 nums = ${nums.toArray()}`); + +/* Удаление элемента */ +nums.remove(3); +console.log(`После удаления элемента по индексу 3 nums = ${nums.toArray()}`); + +/* Доступ к элементу */ +const num = nums.get(1); +console.log(`Элемент по индексу 1: num = ${num}`); + +/* Обновление элемента */ +nums.set(1, 0); +console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums.toArray()}`); + +/* Проверка механизма расширения */ +for (let i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i); +} +console.log( + `Список nums после увеличения вместимости = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` +); diff --git a/ru/codes/javascript/chapter_backtracking/n_queens.js b/ru/codes/javascript/chapter_backtracking/n_queens.js new file mode 100644 index 000000000..373c7c470 --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/n_queens.js @@ -0,0 +1,55 @@ +/** + * File: n_queens.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Алгоритм бэктрекинга: n ферзей */ +function backtrack(row, n, state, res, cols, diags1, diags2) { + // Когда все строки уже обработаны, записать решение + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // Обойти все столбцы + for (let col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + const diag1 = row - col + n - 1; + const diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* Решить задачу о n ферзях */ +function nQueens(n) { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // Отмечать, есть ли ферзь в столбце + const diags1 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на главной диагонали + const diags2 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на побочной диагонали + const res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`Размер входной доски = ${n}`); +console.log(`Количество способов расстановки ферзей: ${res.length}`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); diff --git a/ru/codes/javascript/chapter_backtracking/permutations_i.js b/ru/codes/javascript/chapter_backtracking/permutations_i.js new file mode 100644 index 000000000..d88f64cd3 --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/permutations_i.js @@ -0,0 +1,42 @@ +/** + * File: permutations_i.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки I */ +function backtrack(state, choices, selected, res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.length === choices.length) { + res.push([...state]); + return; + } + // Перебор всех вариантов выбора + choices.forEach((choice, i) => { + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.push(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop(); + } + }); +} + +/* Все перестановки I */ +function permutationsI(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 3]; +const res = permutationsI(nums); + +console.log(`Входной массив nums = ${JSON.stringify(nums)}`); +console.log(`Все перестановки res = ${JSON.stringify(res)}`); diff --git a/ru/codes/javascript/chapter_backtracking/permutations_ii.js b/ru/codes/javascript/chapter_backtracking/permutations_ii.js new file mode 100644 index 000000000..e76e5dbab --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/permutations_ii.js @@ -0,0 +1,44 @@ +/** + * File: permutations_ii.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки II */ +function backtrack(state, choices, selected, res) { + // Когда длина состояния равна числу элементов, записать решение + if (state.length === choices.length) { + res.push([...state]); + return; + } + // Перебор всех вариантов выбора + const duplicated = new Set(); + choices.forEach((choice, i) => { + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated.has(choice)) { + // Попытка: сделать выбор и обновить состояние + duplicated.add(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.push(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop(); + } + }); +} + +/* Все перестановки II */ +function permutationsII(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 2]; +const res = permutationsII(nums); + +console.log(`Входной массив nums = ${JSON.stringify(nums)}`); +console.log(`Все перестановки res = ${JSON.stringify(res)}`); diff --git a/ru/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js b/ru/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js new file mode 100644 index 000000000..2eb4db267 --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js @@ -0,0 +1,33 @@ +/** + * File: preorder_traversal_i_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Предварительный обход: пример 1 */ +function preOrder(root, res) { + if (root === null) { + return; + } + if (root.val === 7) { + // Записать решение + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Предварительный обход +const res = []; +preOrder(root, res); + +console.log('\nВсе узлы со значением 7'); +console.log(res.map((node) => node.val)); diff --git a/ru/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js b/ru/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js new file mode 100644 index 000000000..fec859dab --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js @@ -0,0 +1,40 @@ +/** + * File: preorder_traversal_ii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Предварительный обход: пример 2 */ +function preOrder(root, path, res) { + if (root === null) { + return; + } + // Попытка + path.push(root); + if (root.val === 7) { + // Записать решение + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // Откат + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Предварительный обход +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\nВсе пути от корня к узлу 7'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js b/ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js new file mode 100644 index 000000000..3f59d80c9 --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js @@ -0,0 +1,41 @@ +/** + * File: preorder_traversal_iii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Предварительный обход: пример 3 */ +function preOrder(root, path, res) { + // Отсечение + if (root === null || root.val === 3) { + return; + } + // Попытка + path.push(root); + if (root.val === 7) { + // Записать решение + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // Откат + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Предварительный обход +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js b/ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js new file mode 100644 index 000000000..9a64962f0 --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js @@ -0,0 +1,68 @@ +/** + * File: preorder_traversal_iii_template.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Проверить, является ли текущее состояние решением */ +function isSolution(state) { + return state && state[state.length - 1]?.val === 7; +} + +/* Записать решение */ +function recordSolution(state, res) { + res.push([...state]); +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +function isValid(state, choice) { + return choice !== null && choice.val !== 3; +} + +/* Обновить состояние */ +function makeChoice(state, choice) { + state.push(choice); +} + +/* Восстановить состояние */ +function undoChoice(state) { + state.pop(); +} + +/* Алгоритм бэктрекинга: пример 3 */ +function backtrack(state, choices, res) { + // Проверить, является ли текущее состояние решением + if (isSolution(state)) { + // Записать решение + recordSolution(state, res); + } + // Перебор всех вариантов выбора + for (const choice of choices) { + // Отсечение: проверить допустимость выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + // Перейти к следующему выбору + backtrack(state, [choice.left, choice.right], res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Алгоритм бэктрекинга +const res = []; +backtrack([], [root], res); + +console.log('\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/ru/codes/javascript/chapter_backtracking/subset_sum_i.js b/ru/codes/javascript/chapter_backtracking/subset_sum_i.js new file mode 100644 index 000000000..db4ad898b --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/subset_sum_i.js @@ -0,0 +1,46 @@ +/** + * File: subset_sum_i.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +function backtrack(state, target, choices, start, res) { + // Если сумма подмножества равна target, записать решение + if (target === 0) { + res.push([...state]); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (let i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств I */ +function subsetSumI(nums, target) { + const state = []; // Состояние (подмножество) + nums.sort((a, b) => a - b); // Отсортировать nums + const start = 0; // Стартовая вершина обхода + const res = []; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); diff --git a/ru/codes/javascript/chapter_backtracking/subset_sum_i_naive.js b/ru/codes/javascript/chapter_backtracking/subset_sum_i_naive.js new file mode 100644 index 000000000..a50d8c1ee --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/subset_sum_i_naive.js @@ -0,0 +1,44 @@ +/** + * File: subset_sum_i_naive.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +function backtrack(state, target, total, choices, res) { + // Если сумма подмножества равна target, записать решение + if (total === target) { + res.push([...state]); + return; + } + // Перебор всех вариантов выбора + for (let i = 0; i < choices.length; i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +function subsetSumINaive(nums, target) { + const state = []; // Состояние (подмножество) + const total = 0; // Сумма подмножеств + const res = []; // Список результатов (список подмножеств) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); +console.log('Обратите внимание: результат этого метода содержит повторяющиеся множества'); diff --git a/ru/codes/javascript/chapter_backtracking/subset_sum_ii.js b/ru/codes/javascript/chapter_backtracking/subset_sum_ii.js new file mode 100644 index 000000000..b6dc15177 --- /dev/null +++ b/ru/codes/javascript/chapter_backtracking/subset_sum_ii.js @@ -0,0 +1,51 @@ +/** + * File: subset_sum_ii.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +function backtrack(state, target, choices, start, res) { + // Если сумма подмножества равна target, записать решение + if (target === 0) { + res.push([...state]); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (let i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств II */ +function subsetSumII(nums, target) { + const state = []; // Состояние (подмножество) + nums.sort((a, b) => a - b); // Отсортировать nums + const start = 0; // Стартовая вершина обхода + const res = []; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); diff --git a/ru/codes/javascript/chapter_computational_complexity/iteration.js b/ru/codes/javascript/chapter_computational_complexity/iteration.js new file mode 100644 index 000000000..3bae20b95 --- /dev/null +++ b/ru/codes/javascript/chapter_computational_complexity/iteration.js @@ -0,0 +1,70 @@ +/** + * File: iteration.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Цикл for */ +function forLoop(n) { + let res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* Цикл while */ +function whileLoop(n) { + let res = 0; + let i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Обновить условную переменную + } + return res; +} + +/* Цикл while (двойное обновление) */ +function whileLoopII(n) { + let res = 0; + let i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i++; + i *= 2; + } + return res; +} + +/* Двойной цикл for */ +function nestedForLoop(n) { + let res = ''; + // Цикл по i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // Цикл по j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = forLoop(n); +console.log(`Результат суммирования в цикле for res = ${res}`); + +res = whileLoop(n); +console.log(`Результат суммирования в цикле while res = ${res}`); + +res = whileLoopII(n); +console.log(`Результат суммирования в цикле while (двойное обновление) res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`Результат обхода в двойном цикле for ${resStr}`); diff --git a/ru/codes/javascript/chapter_computational_complexity/recursion.js b/ru/codes/javascript/chapter_computational_complexity/recursion.js new file mode 100644 index 000000000..0c9493b37 --- /dev/null +++ b/ru/codes/javascript/chapter_computational_complexity/recursion.js @@ -0,0 +1,69 @@ +/** + * File: recursion.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Рекурсия */ +function recur(n) { + // Условие завершения + if (n === 1) return 1; + // Рекурсия: рекурсивный вызов + const res = recur(n - 1); + // Возврат: вернуть результат + return n + res; +} + +/* Имитация рекурсии итерацией */ +function forLoopRecur(n) { + // Использовать явный стек для имитации системного стека вызовов + const stack = []; + let res = 0; + // Рекурсия: рекурсивный вызов + for (let i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.push(i); + } + // Возврат: вернуть результат + while (stack.length) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* Хвостовая рекурсия */ +function tailRecur(n, res) { + // Условие завершения + if (n === 0) return res; + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); +} + +/* Последовательность Фибоначчи: рекурсия */ +function fib(n) { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = recur(n); +console.log(`Результат суммирования в рекурсивной функции res = ${res}`); + +res = forLoopRecur(n); +console.log(`Результат суммирования при имитации рекурсии итерацией res = ${res}`); + +res = tailRecur(n, 0); +console.log(`Результат суммирования в хвостовой рекурсии res = ${res}`); + +res = fib(n); +console.log(`Член последовательности Фибоначчи с номером ${n} = ${res}`); + diff --git a/ru/codes/javascript/chapter_computational_complexity/space_complexity.js b/ru/codes/javascript/chapter_computational_complexity/space_complexity.js new file mode 100644 index 000000000..66e1f739d --- /dev/null +++ b/ru/codes/javascript/chapter_computational_complexity/space_complexity.js @@ -0,0 +1,103 @@ +/** + * File: space_complexity.js + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +const { ListNode } = require('../modules/ListNode'); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Функция */ +function constFunc() { + // Выполнить некоторые операции + return 0; +} + +/* Постоянная сложность */ +function constant(n) { + // Константы, переменные и объекты занимают O(1) памяти + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // Переменные в цикле занимают O(1) памяти + for (let i = 0; i < n; i++) { + const c = 0; + } + // Функции в цикле занимают O(1) памяти + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* Линейная сложность */ +function linear(n) { + // Массив длины n занимает O(n) памяти + const nums = new Array(n); + // Список длины n занимает O(n) памяти + const nodes = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // Хеш-таблица длины n занимает O(n) памяти + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* Линейная сложность (рекурсивная реализация) */ +function linearRecur(n) { + console.log(`Рекурсия n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* Квадратичная сложность */ +function quadratic(n) { + // Матрица занимает O(n^2) памяти + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // Двумерный список занимает O(n^2) памяти + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* Квадратичная сложность (рекурсивная реализация) */ +function quadraticRecur(n) { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`В рекурсии n = ${n} длина nums = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +function buildTree(n) { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// Постоянная сложность +constant(n); +// Линейная сложность +linear(n); +linearRecur(n); +// Квадратичная сложность +quadratic(n); +quadraticRecur(n); +// Экспоненциальная сложность +const root = buildTree(n); +printTree(root); diff --git a/ru/codes/javascript/chapter_computational_complexity/time_complexity.js b/ru/codes/javascript/chapter_computational_complexity/time_complexity.js new file mode 100644 index 000000000..e2c6ef6ba --- /dev/null +++ b/ru/codes/javascript/chapter_computational_complexity/time_complexity.js @@ -0,0 +1,155 @@ +/** + * File: time_complexity.js + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* Постоянная сложность */ +function constant(n) { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* Линейная сложность */ +function linear(n) { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* Линейная сложность (обход массива) */ +function arrayTraversal(nums) { + let count = 0; + // Число итераций пропорционально длине массива + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* Квадратичная сложность */ +function quadratic(n) { + let count = 0; + // Число итераций квадратично зависит от размера данных n + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +function bubbleSort(nums) { + let count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; +} + +/* Экспоненциальная сложность (итеративная реализация) */ +function exponential(n) { + let count = 0, + base = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +function expRecur(n) { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* Логарифмическая сложность (итеративная реализация) */ +function logarithmic(n) { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +function logRecur(n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* Линейно-логарифмическая сложность */ +function linearLogRecur(n) { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Факториальная сложность (рекурсивная реализация) */ +function factorialRecur(n) { + if (n === 0) return 1; + let count = 0; + // Из одного получается n + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях +const n = 8; +console.log('Размер входных данных n = ' + n); + +let count = constant(n); +console.log('Число операций константной сложности = ' + count); + +count = linear(n); +console.log('Число операций линейной сложности = ' + count); +count = arrayTraversal(new Array(n)); +console.log('Число операций линейной сложности (обход массива) = ' + count); + +count = quadratic(n); +console.log('Число операций квадратичной сложности = ' + count); +let nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('Число операций квадратичной сложности (пузырьковая сортировка) = ' + count); + +count = exponential(n); +console.log('Число операций экспоненциальной сложности (итеративная реализация) = ' + count); +count = expRecur(n); +console.log('Число операций экспоненциальной сложности (рекурсивная реализация) = ' + count); + +count = logarithmic(n); +console.log('Число операций логарифмической сложности (итеративная реализация) = ' + count); +count = logRecur(n); +console.log('Число операций логарифмической сложности (рекурсивная реализация) = ' + count); + +count = linearLogRecur(n); +console.log('Число операций линейно-логарифмической сложности (рекурсивная реализация) = ' + count); + +count = factorialRecur(n); +console.log('Число операций факториальной сложности (рекурсивная реализация) = ' + count); diff --git a/ru/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js b/ru/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js new file mode 100644 index 000000000..216aa9025 --- /dev/null +++ b/ru/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js @@ -0,0 +1,43 @@ +/** + * File: worst_best_time_complexity.js + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +function randomNumbers(n) { + const nums = Array(n); + // Создать массив nums = { 1, 2, 3, ..., n } + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Случайно перемешать элементы массива + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* Найти индекс числа 1 в массиве nums */ +function findOne(nums) { + for (let i = 0; i < nums.length; i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\nМассив [1, 2, ..., n] после перемешивания = [' + nums.join(', ') + ']'); + console.log('Индекс числа 1 = ' + index); +} diff --git a/ru/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js b/ru/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js new file mode 100644 index 000000000..5913fcafc --- /dev/null +++ b/ru/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js @@ -0,0 +1,39 @@ +/** + * File: binary_search_recur.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Бинарный поиск: задача f(i, j) */ +function dfs(nums, target, i, j) { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + const m = i + ((j - i) >> 1); + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } +} + +/* Бинарный поиск */ +function binarySearch(nums, target) { + const n = nums.length; + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// Бинарный поиск (двусторонне замкнутый интервал) +const index = binarySearch(nums, target); +console.log(`Индекс целевого элемента 6 = ${index}`); diff --git a/ru/codes/javascript/chapter_divide_and_conquer/build_tree.js b/ru/codes/javascript/chapter_divide_and_conquer/build_tree.js new file mode 100644 index 000000000..71122ed6f --- /dev/null +++ b/ru/codes/javascript/chapter_divide_and_conquer/build_tree.js @@ -0,0 +1,44 @@ +/** + * File: build_tree.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { printTree } = require('../modules/PrintUtil'); +const { TreeNode } = require('../modules/TreeNode'); + +/* Построить двоичное дерево: разделяй и властвуй */ +function dfs(preorder, inorderMap, i, l, r) { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) return null; + // Инициализировать корневой узел + const root = new TreeNode(preorder[i]); + // Найти m, чтобы разделить левое и правое поддеревья + const m = inorderMap.get(preorder[i]); + // Подзадача: построить левое поддерево + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + return root; +} + +/* Построить двоичное дерево */ +function buildTree(preorder, inorder) { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('Предварительный обход = ' + JSON.stringify(preorder)); +console.log('Симметричный обход = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('Построенное двоичное дерево:'); +printTree(root); diff --git a/ru/codes/javascript/chapter_divide_and_conquer/hanota.js b/ru/codes/javascript/chapter_divide_and_conquer/hanota.js new file mode 100644 index 000000000..672aad719 --- /dev/null +++ b/ru/codes/javascript/chapter_divide_and_conquer/hanota.js @@ -0,0 +1,52 @@ +/** + * File: hanota.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Переместить один диск */ +function move(src, tar) { + // Снять диск с вершины src + const pan = src.pop(); + // Положить диск на вершину tar + tar.push(pan); +} + +/* Решить задачу Ханойской башни f(i) */ +function dfs(i, src, buf, tar) { + // Если в src остался только один диск, сразу переместить его в tar + if (i === 1) { + move(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar); +} + +/* Решить задачу Ханойской башни */ +function solveHanota(A, B, C) { + const n = A.length; + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C); +} + +/* Driver Code */ +// Хвост списка соответствует вершине столбца +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('Исходное состояние:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('После завершения перемещения дисков:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js new file mode 100644 index 000000000..dcba9f613 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js @@ -0,0 +1,34 @@ +/** + * File: climbing_stairs_backtrack.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Бэктрекинг */ +function backtrack(choices, state, n, res) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state === n) res.set(0, res.get(0) + 1); + // Перебор всех вариантов выбора + for (const choice of choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) continue; + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +function climbingStairsBacktrack(n) { + const choices = [1, 2]; // Можно подняться на 1 или 2 ступени + const state = 0; // Начать подъем с 0-й ступени + const res = new Map(); + res.set(0, 0); // Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js new file mode 100644 index 000000000..7d075e9fb --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_constraint_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +function climbingStairsConstraintDP(n) { + if (n === 1 || n === 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + const dp = Array.from(new Array(n + 1), () => new Array(3)); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js new file mode 100644 index 000000000..7644adc52 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js @@ -0,0 +1,24 @@ +/** + * File: climbing_stairs_dfs.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Поиск */ +function dfs(i) { + // dp[1] и dp[2] уже известны, вернуть их + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* Подъем по лестнице: поиск */ +function climbingStairsDFS(n) { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js new file mode 100644 index 000000000..6a7fb0f14 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_dfs_mem.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Поиск с мемоизацией */ +function dfs(i, mem) { + // dp[1] и dp[2] уже известны, вернуть их + if (i === 1 || i === 2) return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; +} + +/* Подъем по лестнице: поиск с мемоизацией */ +function climbingStairsDFSMem(n) { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js new file mode 100644 index 000000000..547908cb4 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dp.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Подъем по лестнице: динамическое программирование */ +function climbingStairsDP(n) { + if (n === 1 || n === 2) return n; + // Инициализация таблицы dp для хранения решений подзадач + const dp = new Array(n + 1).fill(-1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +function climbingStairsDPComp(n) { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); +res = climbingStairsDPComp(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/coin_change.js b/ru/codes/javascript/chapter_dynamic_programming/coin_change.js new file mode 100644 index 000000000..cc915ed7e --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -0,0 +1,66 @@ +/** + * File: coin_change.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Размен монет: динамическое программирование */ +function coinChangeDP(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // Переход состояний: первая строка и первый столбец + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +function coinChangeDPComp(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // Инициализация таблицы dp + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// Динамическое программирование +let res = coinChangeDP(coins, amt); +console.log(`Минимальное число монет для набора целевой суммы = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = coinChangeDPComp(coins, amt); +console.log(`Минимальное число монет для набора целевой суммы = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/ru/codes/javascript/chapter_dynamic_programming/coin_change_ii.js new file mode 100644 index 000000000..24256f0b7 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Размен монет II: динамическое программирование */ +function coinChangeIIDP(coins, amt) { + const n = coins.length; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // Инициализация первого столбца + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +function coinChangeIIDPComp(coins, amt) { + const n = coins.length; + // Инициализация таблицы dp + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// Динамическое программирование +let res = coinChangeIIDP(coins, amt); +console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = coinChangeIIDPComp(coins, amt); +console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/edit_distance.js b/ru/codes/javascript/chapter_dynamic_programming/edit_distance.js new file mode 100644 index 000000000..4f0de16da --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -0,0 +1,135 @@ +/** + * File: edit_distance.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Редакционное расстояние: полный перебор */ +function editDistanceDFS(s, t, i, j) { + // Если s и t пусты, вернуть 0 + if (i === 0 && j === 0) return 0; + + // Если s пусто, вернуть длину t + if (i === 0) return j; + + // Если t пусто, вернуть длину s + if (j === 0) return i; + + // Если два символа равны, сразу пропустить их + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return Math.min(insert, del, replace) + 1; +} + +/* Редакционное расстояние: поиск с мемоизацией */ +function editDistanceDFSMem(s, t, mem, i, j) { + // Если s и t пусты, вернуть 0 + if (i === 0 && j === 0) return 0; + + // Если s пусто, вернуть длину t + if (i === 0) return j; + + // Если t пусто, вернуть длину s + if (j === 0) return i; + + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] !== -1) return mem[i][j]; + + // Если два символа равны, сразу пропустить их + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* Редакционное расстояние: динамическое программирование */ +function editDistanceDP(s, t) { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + // Переход состояний: первая строка и первый столбец + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // Переход состояний: остальные строки и столбцы + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +function editDistanceDPComp(s, t) { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // Переход состояний: первая строка + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (let i = 1; i <= n; i++) { + // Переход состояний: первый столбец + let leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; +} + +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// Полный перебор +let res = editDistanceDFS(s, t, n, m); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +// Поиск с мемоизацией +const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +// Динамическое программирование +res = editDistanceDP(s, t); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +// Динамическое программирование с оптимизацией памяти +res = editDistanceDPComp(s, t); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/knapsack.js b/ru/codes/javascript/chapter_dynamic_programming/knapsack.js new file mode 100644 index 000000000..1a5508cd4 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -0,0 +1,113 @@ +/** + * File: knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Рюкзак 0-1: полный перебор */ +function knapsackDFS(wgt, val, i, c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i === 0 || c === 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return Math.max(no, yes); +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +function knapsackDFSMem(wgt, val, mem, i, c) { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i === 0 || c === 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* Рюкзак 0-1: динамическое программирование */ +function knapsackDP(wgt, val, cap) { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array(n + 1) + .fill(0) + .map(() => Array(cap + 1).fill(0)); + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +function knapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array(cap + 1).fill(0); + // Переход состояний + for (let i = 1; i <= n; i++) { + // Обход в обратном порядке + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// Полный перебор +let res = knapsackDFS(wgt, val, n, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Поиск с мемоизацией +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Динамическое программирование +res = knapsackDP(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = knapsackDPComp(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/ru/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js new file mode 100644 index 000000000..0f45f71e1 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -0,0 +1,49 @@ +/** + * File: min_cost_climbing_stairs_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +function minCostClimbingStairsDP(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // Инициализация таблицы dp для хранения решений подзадач + const dp = new Array(n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +function minCostClimbingStairsDPComp(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log('Список стоимостей ступеней =', cost); + +let res = minCostClimbingStairsDP(cost); +console.log(`Минимальная стоимость подъема по лестнице = ${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`Минимальная стоимость подъема по лестнице = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/ru/codes/javascript/chapter_dynamic_programming/min_path_sum.js new file mode 100644 index 000000000..1b5629149 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -0,0 +1,121 @@ +/** + * File: min_path_sum.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Минимальная сумма пути: полный перебор */ +function minPathSumDFS(grid, i, j) { + // Если это верхняя левая ячейка, завершить поиск + if (i === 0 && j === 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Infinity; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return Math.min(left, up) + grid[i][j]; +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +function minPathSumDFSMem(grid, mem, i, j) { + // Если это верхняя левая ячейка, завершить поиск + if (i === 0 && j === 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Infinity; + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] !== -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* Минимальная сумма пути: динамическое программирование */ +function minPathSumDP(grid) { + const n = grid.length, + m = grid[0].length; + // Инициализация таблицы dp + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +function minPathSumDPComp(grid) { + const n = grid.length, + m = grid[0].length; + // Инициализация таблицы dp + const dp = new Array(m); + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (let i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// Полный перебор +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +// Поиск с мемоизацией +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +// Динамическое программирование +res = minPathSumDP(grid); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = minPathSumDPComp(grid); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); diff --git a/ru/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/ru/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js new file mode 100644 index 000000000..dbd623260 --- /dev/null +++ b/ru/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Полный рюкзак: динамическое программирование */ +function unboundedKnapsackDP(wgt, val, cap) { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +function unboundedKnapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array.from({ length: cap + 1 }, () => 0); + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// Динамическое программирование +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); diff --git a/ru/codes/javascript/chapter_graph/graph_adjacency_list.js b/ru/codes/javascript/chapter_graph/graph_adjacency_list.js new file mode 100644 index 000000000..0e0598979 --- /dev/null +++ b/ru/codes/javascript/chapter_graph/graph_adjacency_list.js @@ -0,0 +1,142 @@ +/** + * File: graph_adjacency_list.js + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { Vertex } = require('../modules/Vertex'); + +/* Класс неориентированного графа на основе списка смежности */ +class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + adjList; + + /* Конструктор */ + constructor(edges) { + this.adjList = new Map(); + // Добавить все вершины и ребра + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + size() { + return this.adjList.size; + } + + /* Добавление ребра */ + addEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // Добавить ребро vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* Удаление ребра */ + removeEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 || + this.adjList.get(vet1).indexOf(vet2) === -1 + ) { + throw new Error('Illegal Argument Exception'); + } + // Удалить ребро vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* Добавление вершины */ + addVertex(vet) { + if (this.adjList.has(vet)) return; + // Добавить новый список в список смежности + this.adjList.set(vet, []); + } + + /* Удаление вершины */ + removeVertex(vet) { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // Удалить из списка смежности список, соответствующий вершине vet + this.adjList.delete(vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + for (const set of this.adjList.values()) { + const index = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* Вывести список смежности */ + print() { + console.log('Список смежности ='); + for (const [key, value] of this.adjList) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +if (require.main === module) { + /* Driver Code */ + /* Инициализация неориентированного графа */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\nГраф после инициализации'); + graph.print(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v0 и v2 + graph.addEdge(v0, v2); + console.log('\nГраф после добавления ребра 1-2'); + graph.print(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v0 и v1 + graph.removeEdge(v0, v1); + console.log('\nГраф после удаления ребра 1-3'); + graph.print(); + + /* Добавление вершины */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\nГраф после добавления вершины 6'); + graph.print(); + + /* Удаление вершины */ + // Вершина 3 соответствует v1 + graph.removeVertex(v1); + console.log('\nГраф после удаления вершины 3'); + graph.print(); +} + +module.exports = { + GraphAdjList, +}; diff --git a/ru/codes/javascript/chapter_graph/graph_adjacency_matrix.js b/ru/codes/javascript/chapter_graph/graph_adjacency_matrix.js new file mode 100644 index 000000000..2d18e6549 --- /dev/null +++ b/ru/codes/javascript/chapter_graph/graph_adjacency_matrix.js @@ -0,0 +1,132 @@ +/** + * File: graph_adjacency_matrix.js + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + vertices; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + adjMat; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + constructor(vertices, edges) { + this.vertices = []; + this.adjMat = []; + // Добавление вершины + for (const val of vertices) { + this.addVertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* Получить число вершин */ + size() { + return this.vertices.length; + } + + /* Добавление вершины */ + addVertex(val) { + const n = this.size(); + // Добавить значение новой вершины в список вершин + this.vertices.push(val); + // Добавить строку в матрицу смежности + const newRow = []; + for (let j = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // Добавить столбец в матрицу смежности + for (const row of this.adjMat) { + row.push(0); + } + } + + /* Удаление вершины */ + removeVertex(index) { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // Удалить вершину с индексом index из списка вершин + this.vertices.splice(index, 1); + + // Удалить строку с индексом index из матрицы смежности + this.adjMat.splice(index, 1); + // Удалить столбец с индексом index из матрицы смежности + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + addEdge(i, j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + removeEdge(i, j) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* Вывести матрицу смежности */ + print() { + console.log('Список вершин = ', this.vertices); + console.log('Матрица смежности =', this.adjMat); + } +} + +/* Driver Code */ +/* Инициализация неориентированного графа */ +// Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices +const vertices = [1, 3, 2, 5, 4]; +const edges = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph = new GraphAdjMat(vertices, edges); +console.log('\nГраф после инициализации'); +graph.print(); + +/* Добавление ребра */ +// Индексы вершин 1 и 2 равны 0 и 2 соответственно +graph.addEdge(0, 2); +console.log('\nГраф после добавления ребра 1-2'); +graph.print(); + +/* Удаление ребра */ +// Индексы вершин 1 и 3 равны 0 и 1 соответственно +graph.removeEdge(0, 1); +console.log('\nГраф после удаления ребра 1-3'); +graph.print(); + +/* Добавление вершины */ +graph.addVertex(6); +console.log('\nГраф после добавления вершины 6'); +graph.print(); + +/* Удаление вершины */ +// Индекс вершины 3 равен 1 +graph.removeVertex(1); +console.log('\nГраф после удаления вершины 3'); +graph.print(); diff --git a/ru/codes/javascript/chapter_graph/graph_bfs.js b/ru/codes/javascript/chapter_graph/graph_bfs.js new file mode 100644 index 000000000..51770146d --- /dev/null +++ b/ru/codes/javascript/chapter_graph/graph_bfs.js @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { GraphAdjList } = require('./graph_adjacency_list'); +const { Vertex } = require('../modules/Vertex'); + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +function graphBFS(graph, startVet) { + // Последовательность обхода вершин + const res = []; + // Хеш-множество для хранения уже посещенных вершин + const visited = new Set(); + visited.add(startVet); + // Очередь используется для реализации BFS + const que = [startVet]; + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (que.length) { + const vet = que.shift(); // Извлечь головную вершину из очереди + res.push(vet); // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + que.push(adjVet); // Помещать в очередь только непосещенные вершины + visited.add(adjVet); // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res; +} + +/* Driver Code */ +/* Инициализация неориентированного графа */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], +]; +const graph = new GraphAdjList(edges); +console.log('\nГраф после инициализации'); +graph.print(); + +/* Обход в ширину */ +const res = graphBFS(graph, v[0]); +console.log('\nПоследовательность вершин при обходе в ширину (BFS)'); +console.log(Vertex.vetsToVals(res)); diff --git a/ru/codes/javascript/chapter_graph/graph_dfs.js b/ru/codes/javascript/chapter_graph/graph_dfs.js new file mode 100644 index 000000000..38f4314d9 --- /dev/null +++ b/ru/codes/javascript/chapter_graph/graph_dfs.js @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { Vertex } = require('../modules/Vertex'); +const { GraphAdjList } = require('./graph_adjacency_list'); + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +function dfs(graph, visited, res, vet) { + res.push(vet); // Отметить посещенную вершину + visited.add(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet); + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +function graphDFS(graph, startVet) { + // Последовательность обхода вершин + const res = []; + // Хеш-множество для хранения уже посещенных вершин + const visited = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* Инициализация неориентированного графа */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\nГраф после инициализации'); +graph.print(); + +/* Обход в глубину */ +const res = graphDFS(graph, v[0]); +console.log('\nПоследовательность вершин при обходе в глубину (DFS)'); +console.log(Vertex.vetsToVals(res)); diff --git a/ru/codes/javascript/chapter_greedy/coin_change_greedy.js b/ru/codes/javascript/chapter_greedy/coin_change_greedy.js new file mode 100644 index 000000000..783e137cc --- /dev/null +++ b/ru/codes/javascript/chapter_greedy/coin_change_greedy.js @@ -0,0 +1,48 @@ +/** + * File: coin_change_greedy.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Размен монет: жадный алгоритм */ +function coinChangeGreedy(coins, amt) { + // Предположить, что массив coins упорядочен + let i = coins.length - 1; + let count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// Жадный подход: гарантирует нахождение глобально оптимального решения +let coins = [1, 5, 10, 20, 50, 100]; +let amt = 186; +let res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); + +// Жадный подход: не гарантирует нахождение глобально оптимального решения +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); +console.log('На самом деле минимум равен 3: 20 + 20 + 20'); + +// Жадный подход: не гарантирует нахождение глобально оптимального решения +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); +console.log('На самом деле минимум равен 2: 49 + 49'); diff --git a/ru/codes/javascript/chapter_greedy/fractional_knapsack.js b/ru/codes/javascript/chapter_greedy/fractional_knapsack.js new file mode 100644 index 000000000..4a27077d0 --- /dev/null +++ b/ru/codes/javascript/chapter_greedy/fractional_knapsack.js @@ -0,0 +1,46 @@ +/** + * File: fractional_knapsack.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Предмет */ +class Item { + constructor(w, v) { + this.w = w; // Вес предмета + this.v = v; // Стоимость предмета + } +} + +/* Дробный рюкзак: жадный алгоритм */ +function fractionalKnapsack(wgt, val, cap) { + // Создать список предметов с двумя свойствами: вес и стоимость + const items = wgt.map((w, i) => new Item(w, val[i])); + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort((a, b) => b.v / b.w - a.v / a.w); + // Циклический жадный выбор + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (item.v / item.w) * cap; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + return res; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// Жадный алгоритм +const res = fractionalKnapsack(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); diff --git a/ru/codes/javascript/chapter_greedy/max_capacity.js b/ru/codes/javascript/chapter_greedy/max_capacity.js new file mode 100644 index 000000000..0de17158b --- /dev/null +++ b/ru/codes/javascript/chapter_greedy/max_capacity.js @@ -0,0 +1,34 @@ +/** + * File: max_capacity.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Максимальная вместимость: жадный алгоритм */ +function maxCapacity(ht) { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + let i = 0, + j = ht.length - 1; + // Начальная максимальная вместимость равна 0 + let res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + const cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht = [3, 8, 5, 2, 7, 7, 3, 4]; + +// Жадный алгоритм +const res = maxCapacity(ht); +console.log(`Максимальная вместимость = ${res}`); diff --git a/ru/codes/javascript/chapter_greedy/max_product_cutting.js b/ru/codes/javascript/chapter_greedy/max_product_cutting.js new file mode 100644 index 000000000..a3eda2849 --- /dev/null +++ b/ru/codes/javascript/chapter_greedy/max_product_cutting.js @@ -0,0 +1,33 @@ +/** + * File: max_product_cutting.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Максимальное произведение разрезания: жадный алгоритм */ +function maxProductCutting(n) { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + let a = Math.floor(n / 3); + let b = n % 3; + if (b === 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // Если остаток равен 2, ничего не делать + return Math.pow(3, a) * 2; + } + // Если остаток равен 0, ничего не делать + return Math.pow(3, a); +} + +/* Driver Code */ +let n = 58; + +// Жадный алгоритм +let res = maxProductCutting(n); +console.log(`Максимальное произведение после разрезания = ${res}`); diff --git a/ru/codes/javascript/chapter_hashing/array_hash_map.js b/ru/codes/javascript/chapter_hashing/array_hash_map.js new file mode 100644 index 000000000..0b09773c9 --- /dev/null +++ b/ru/codes/javascript/chapter_hashing/array_hash_map.js @@ -0,0 +1,128 @@ +/** + * File: array_hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Пара ключ-значение Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + #buckets; + constructor() { + // Инициализировать массив, содержащий 100 корзин + this.#buckets = new Array(100).fill(null); + } + + /* Хеш-функция */ + #hashFunc(key) { + return key % 100; + } + + /* Операция поиска */ + get(key) { + let index = this.#hashFunc(key); + let pair = this.#buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* Операция добавления */ + set(key, val) { + let index = this.#hashFunc(key); + this.#buckets[index] = new Pair(key, val); + } + + /* Операция удаления */ + delete(key) { + let index = this.#hashFunc(key); + // Присвоить null, что означает удаление + this.#buckets[index] = null; + } + + /* Получить все пары ключ-значение */ + entries() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i]); + } + } + return arr; + } + + /* Получить все ключи */ + keys() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].key); + } + } + return arr; + } + + /* Получить все значения */ + values() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].val); + } + } + return arr; + } + + /* Вывести хеш-таблицу */ + print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* Инициализация хеш-таблицы */ +const map = new ArrayHashMap(); +/* Операция добавления */ +// Добавить пару (key, value) в хеш-таблицу +map.set(12836, 'Сяо Ха'); +map.set(15937, 'Сяо Ло'); +map.set(16750, 'Сяо Суань'); +map.set(13276, 'Сяо Фа'); +map.set(10583, 'Сяо Я'); +console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +/* Операция поиска */ +// Ввести в хеш-таблицу ключ key и получить значение value +let name = map.get(15937); +console.info('\nПо номеру 15937 найдено имя ' + name); + +/* Операция удаления */ +// Удалить пару (key, value) из хеш-таблицы +map.delete(10583); +console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +/* Обход хеш-таблицы */ +console.info('\nОтдельный обход пар ключ-значение'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\nОтдельный обход ключей'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\nОтдельный обход значений'); +for (const val of map.values()) { + console.info(val); +} diff --git a/ru/codes/javascript/chapter_hashing/hash_map.js b/ru/codes/javascript/chapter_hashing/hash_map.js new file mode 100644 index 000000000..d4896ee6a --- /dev/null +++ b/ru/codes/javascript/chapter_hashing/hash_map.js @@ -0,0 +1,44 @@ +/** + * File: hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Driver Code */ +/* Инициализация хеш-таблицы */ +const map = new Map(); + +/* Операция добавления */ +// Добавить пару (key, value) в хеш-таблицу +map.set(12836, 'Сяо Ха'); +map.set(15937, 'Сяо Ло'); +map.set(16750, 'Сяо Суань'); +map.set(13276, 'Сяо Фа'); +map.set(10583, 'Сяо Я'); +console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +console.info(map); + +/* Операция поиска */ +// Ввести в хеш-таблицу ключ key и получить значение value +let name = map.get(15937); +console.info('\nПо номеру 15937 найдено имя ' + name); + +/* Операция удаления */ +// Удалить пару (key, value) из хеш-таблицы +map.delete(10583); +console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); +console.info(map); + +/* Обход хеш-таблицы */ +console.info('\nОтдельный обход пар ключ-значение'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\nОтдельный обход ключей'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\nОтдельный обход значений'); +for (const v of map.values()) { + console.info(v); +} diff --git a/ru/codes/javascript/chapter_hashing/hash_map_chaining.js b/ru/codes/javascript/chapter_hashing/hash_map_chaining.js new file mode 100644 index 000000000..dad6c5978 --- /dev/null +++ b/ru/codes/javascript/chapter_hashing/hash_map_chaining.js @@ -0,0 +1,142 @@ +/** + * File: hash_map_chaining.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Пара ключ-значение Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + #size; // Число пар ключ-значение + #capacity; // Вместимость хеш-таблицы + #loadThres; // Порог коэффициента загрузки для запуска расширения + #extendRatio; // Коэффициент расширения + #buckets; // Массив корзин + + /* Конструктор */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* Хеш-функция */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* Коэффициент загрузки */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* Операция поиска */ + get(key) { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // Обойти корзину; если найден key, вернуть соответствующее val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // Если key не найден, вернуть null + return null; + } + + /* Операция добавления */ + put(key, val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // Если такого key нет, добавить пару ключ-значение в конец + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* Операция удаления */ + remove(key) { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // Обойти корзину и удалить из нее пару ключ-значение + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* Расширить хеш-таблицу */ + #extend() { + // Временно сохранить исходную хеш-таблицу + const bucketsTmp = this.#buckets; + // Инициализация новой хеш-таблицы после расширения + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + print() { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* Инициализация хеш-таблицы */ +const map = new HashMapChaining(); + +/* Операция добавления */ +// Добавить пару (key, value) в хеш-таблицу +map.put(12836, 'Сяо Ха'); +map.put(15937, 'Сяо Ло'); +map.put(16750, 'Сяо Суань'); +map.put(13276, 'Сяо Фа'); +map.put(10583, 'Сяо Я'); +console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +/* Операция поиска */ +// Ввести в хеш-таблицу ключ key и получить значение value +const name = map.get(13276); +console.log('\nДля номера 13276 найдено имя ' + name); + +/* Операция удаления */ +// Удалить пару (key, value) из хеш-таблицы +map.remove(12836); +console.log('\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); diff --git a/ru/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/ru/codes/javascript/chapter_hashing/hash_map_open_addressing.js new file mode 100644 index 000000000..073fbfb76 --- /dev/null +++ b/ru/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -0,0 +1,177 @@ +/** + * File: hashMapOpenAddressing.js + * Created Time: 2023-06-13 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* Пара ключ-значение Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + #size; // Число пар ключ-значение + #capacity; // Вместимость хеш-таблицы + #loadThres; // Порог коэффициента загрузки для запуска расширения + #extendRatio; // Коэффициент расширения + #buckets; // Массив корзин + #TOMBSTONE; // Удалить метку + + /* Конструктор */ + constructor() { + this.#size = 0; // Число пар ключ-значение + this.#capacity = 4; // Вместимость хеш-таблицы + this.#loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения + this.#extendRatio = 2; // Коэффициент расширения + this.#buckets = Array(this.#capacity).fill(null); // Массив корзин + this.#TOMBSTONE = new Pair(-1, '-1'); // Удалить метку + } + + /* Хеш-функция */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* Коэффициент загрузки */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* Найти индекс корзины, соответствующий key */ + #findBucket(key) { + let index = this.#hashFunc(key); + let firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (this.#buckets[index] !== null) { + // Если встретился key, вернуть соответствующий индекс корзины + if (this.#buckets[index].key === key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone !== -1) { + this.#buckets[firstTombstone] = this.#buckets[index]; + this.#buckets[index] = this.#TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if ( + firstTombstone === -1 && + this.#buckets[index] === this.#TOMBSTONE + ) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % this.#capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone === -1 ? index : firstTombstone; + } + + /* Операция поиска */ + get(key) { + // Найти индекс корзины, соответствующий key + const index = this.#findBucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + return this.#buckets[index].val; + } + // Если пары ключ-значение не существует, вернуть null + return null; + } + + /* Операция добавления */ + put(key, val) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + // Найти индекс корзины, соответствующий key + const index = this.#findBucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index].val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + this.#buckets[index] = new Pair(key, val); + this.#size++; + } + + /* Операция удаления */ + remove(key) { + // Найти индекс корзины, соответствующий key + const index = this.#findBucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index] = this.#TOMBSTONE; + this.#size--; + } + } + + /* Расширить хеш-таблицу */ + #extend() { + // Временно сохранить исходную хеш-таблицу + const bucketsTmp = this.#buckets; + // Инициализация новой хеш-таблицы после расширения + this.#capacity *= this.#extendRatio; + this.#buckets = Array(this.#capacity).fill(null); + this.#size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.#TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + print() { + for (const pair of this.#buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.#TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// Инициализация хеш-таблицы +const hashmap = new HashMapOpenAddressing(); + +// Операция добавления +// Добавить пару (key, val) в хеш-таблицу +hashmap.put(12836, 'Сяо Ха'); +hashmap.put(15937, 'Сяо Ло'); +hashmap.put(16750, 'Сяо Суань'); +hashmap.put(13276, 'Сяо Фа'); +hashmap.put(10583, 'Сяо Я'); +console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +hashmap.print(); + +// Операция поиска +// Передать ключ key в хеш-таблицу и получить значение val +const name = hashmap.get(13276); +console.log('\nДля номера 13276 найдено имя ' + name); + +// Операция удаления +// Удалить пару (key, val) из хеш-таблицы +hashmap.remove(16750); +console.log('\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение'); +hashmap.print(); diff --git a/ru/codes/javascript/chapter_hashing/simple_hash.js b/ru/codes/javascript/chapter_hashing/simple_hash.js new file mode 100644 index 000000000..86248321d --- /dev/null +++ b/ru/codes/javascript/chapter_hashing/simple_hash.js @@ -0,0 +1,60 @@ +/** + * File: simple_hash.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Аддитивное хеширование */ +function addHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Мультипликативное хеширование */ +function mulHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* XOR-хеширование */ +function xorHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* Хеширование с циклическим сдвигом */ +function rotHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello Algo'; + +let hash = addHash(key); +console.log('Хеш-сумма сложением = ' + hash); + +hash = mulHash(key); +console.log('Хеш-сумма умножением = ' + hash); + +hash = xorHash(key); +console.log('Хеш-сумма XOR = ' + hash); + +hash = rotHash(key); +console.log('Хеш-сумма с циклическим сдвигом = ' + hash); diff --git a/ru/codes/javascript/chapter_heap/my_heap.js b/ru/codes/javascript/chapter_heap/my_heap.js new file mode 100644 index 000000000..717f4e38c --- /dev/null +++ b/ru/codes/javascript/chapter_heap/my_heap.js @@ -0,0 +1,158 @@ +/** + * File: my_heap.js + * Created Time: 2023-02-06 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { printHeap } = require('../modules/PrintUtil'); + +/* Класс максимальной кучи */ +class MaxHeap { + #maxHeap; + + /* Конструктор, создающий пустую кучу или строящий кучу по входному списку */ + constructor(nums) { + // Добавить элементы списка в кучу без изменений + this.#maxHeap = nums === undefined ? [] : [...nums]; + // Выполнить heapify для всех узлов, кроме листовых + for (let i = this.#parent(this.size() - 1); i >= 0; i--) { + this.#siftDown(i); + } + } + + /* Получить индекс левого дочернего узла */ + #left(i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + #right(i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + #parent(i) { + return Math.floor((i - 1) / 2); // Округление вниз при делении + } + + /* Поменять элементы местами */ + #swap(i, j) { + const tmp = this.#maxHeap[i]; + this.#maxHeap[i] = this.#maxHeap[j]; + this.#maxHeap[j] = tmp; + } + + /* Получение размера кучи */ + size() { + return this.#maxHeap.length; + } + + /* Проверка, пуста ли куча */ + isEmpty() { + return this.size() === 0; + } + + /* Доступ к элементу на вершине кучи */ + peek() { + return this.#maxHeap[0]; + } + + /* Добавление элемента в кучу */ + push(val) { + // Добавление узла + this.#maxHeap.push(val); + // Просеивание снизу вверх + this.#siftUp(this.size() - 1); + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + #siftUp(i) { + while (true) { + // Получение родительского узла для узла i + const p = this.#parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; + // Поменять два узла местами + this.#swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + pop() { + // Обработка пустого случая + if (this.isEmpty()) throw new Error('куча пуста'); + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + this.#swap(0, this.size() - 1); + // Удаление узла + const val = this.#maxHeap.pop(); + // Просеивание сверху вниз + this.#siftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + #siftDown(i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + const l = this.#left(i), + r = this.#right(i); + let ma = i; + if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; + if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma === i) break; + // Поменять два узла местами + this.#swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Вывести кучу (двоичное дерево) */ + print() { + printHeap(this.#maxHeap); + } + + /* Извлечь элементы из кучи */ + getMaxHeap() { + return this.#maxHeap; + } +} + +/* Driver Code */ +if (require.main === module) { + /* Инициализация максимальной кучи */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\nПосле построения кучи из входного списка'); + maxHeap.print(); + + /* Получение элемента с вершины кучи */ + let peek = maxHeap.peek(); + console.log(`\nЭлемент на вершине кучи = ${peek}`); + + /* Добавление элемента в кучу */ + let val = 7; + maxHeap.push(val); + console.log(`\nПосле добавления элемента ${val} в кучу`); + maxHeap.print(); + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop(); + console.log(`\nПосле извлечения элемента вершины кучи ${peek}`); + maxHeap.print(); + + /* Получение размера кучи */ + let size = maxHeap.size(); + console.log(`\nКоличество элементов в куче = ${size}`); + + /* Проверка, пуста ли куча */ + let isEmpty = maxHeap.isEmpty(); + console.log(`\nПуста ли куча: ${isEmpty}`); +} + +module.exports = { + MaxHeap, +}; diff --git a/ru/codes/javascript/chapter_heap/top_k.js b/ru/codes/javascript/chapter_heap/top_k.js new file mode 100644 index 000000000..55e3e8031 --- /dev/null +++ b/ru/codes/javascript/chapter_heap/top_k.js @@ -0,0 +1,58 @@ +/** + * File: top_k.js + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +const { MaxHeap } = require('./my_heap'); + +/* Добавление элемента в кучу */ +function pushMinHeap(maxHeap, val) { + // Инвертировать знак элемента + maxHeap.push(-val); +} + +/* Извлечение элемента из кучи */ +function popMinHeap(maxHeap) { + // Инвертировать знак элемента + return -maxHeap.pop(); +} + +/* Доступ к элементу на вершине кучи */ +function peekMinHeap(maxHeap) { + // Инвертировать знак элемента + return -maxHeap.peek(); +} + +/* Извлечь элементы из кучи */ +function getMinHeap(maxHeap) { + // Инвертировать знак элемента + return maxHeap.getMaxHeap().map((num) => -num); +} + +/* Найти k наибольших элементов массива с помощью кучи */ +function topKHeap(nums, k) { + // Инициализация минимальной кучи + // Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную + const maxHeap = new MaxHeap([]); + // Поместить первые k элементов массива в кучу + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (let i = k; i < nums.length; i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // Вернуть элементы кучи + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`Наибольшие ${k} элементов`, res); diff --git a/ru/codes/javascript/chapter_searching/binary_search.js b/ru/codes/javascript/chapter_searching/binary_search.js new file mode 100644 index 000000000..826289256 --- /dev/null +++ b/ru/codes/javascript/chapter_searching/binary_search.js @@ -0,0 +1,60 @@ +/** + * File: binary_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +function binarySearch(nums, target) { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + let i = 0, + j = nums.length - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + // Вычислить индекс середины m, используя parseInt() для округления вниз + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + else if (nums[m] > target) + // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + else return m; // Целевой элемент найден, вернуть его индекс + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +function binarySearchLCRO(nums, target) { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + let i = 0, + j = nums.length; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + // Вычислить индекс середины m, используя parseInt() для округления вниз + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + else if (nums[m] > target) + // Это означает, что target находится в интервале [i, m) + j = m; + // Целевой элемент найден, вернуть его индекс + else return m; + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +let index = binarySearch(nums, target); +console.log('Индекс целевого элемента 6 = ' + index); + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +index = binarySearchLCRO(nums, target); +console.log('Индекс целевого элемента 6 = ' + index); diff --git a/ru/codes/javascript/chapter_searching/binary_search_edge.js b/ru/codes/javascript/chapter_searching/binary_search_edge.js new file mode 100644 index 000000000..2e8718aa3 --- /dev/null +++ b/ru/codes/javascript/chapter_searching/binary_search_edge.js @@ -0,0 +1,45 @@ +/** + * File: binary_search_edge.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +const { binarySearchInsertion } = require('./binary_search_insertion.js'); + +/* Бинарный поиск самого левого target */ +function binarySearchLeftEdge(nums, target) { + // Эквивалентно поиску точки вставки target + const i = binarySearchInsertion(nums, target); + // target не найден, вернуть -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // Найти target и вернуть индекс i + return i; +} + +/* Бинарный поиск самого правого target */ +function binarySearchRightEdge(nums, target) { + // Преобразовать задачу в поиск самого левого target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + const j = i - 1; + // target не найден, вернуть -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // Найти target и вернуть индекс j + return j; +} + +/* Driver Code */ +// Массив с повторяющимися элементами +const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\nМассив nums = ' + nums); +// Бинарный поиск левой и правой границы +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('Индекс самого левого элемента ' + target + ' равен ' + index); + index = binarySearchRightEdge(nums, target); + console.log('Индекс самого правого элемента ' + target + ' равен ' + index); +} diff --git a/ru/codes/javascript/chapter_searching/binary_search_insertion.js b/ru/codes/javascript/chapter_searching/binary_search_insertion.js new file mode 100644 index 000000000..d877ec081 --- /dev/null +++ b/ru/codes/javascript/chapter_searching/binary_search_insertion.js @@ -0,0 +1,64 @@ +/** + * File: binary_search_insertion.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +function binarySearchInsertionSimple(nums, target) { + let i = 0, + j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +function binarySearchInsertion(nums, target) { + let i = 0, + j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Driver Code */ +// Массив без повторяющихся элементов +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\nМассив nums = ' + nums); +// Бинарный поиск точки вставки +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('Индекс позиции вставки элемента ' + target + ' равен ' + index); +} + +// Массив с повторяющимися элементами +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\nМассив nums = ' + nums); +// Бинарный поиск точки вставки +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('Индекс позиции вставки элемента ' + target + ' равен ' + index); +} + +module.exports = { + binarySearchInsertion, +}; diff --git a/ru/codes/javascript/chapter_searching/hashing_search.js b/ru/codes/javascript/chapter_searching/hashing_search.js new file mode 100644 index 000000000..3e4bc7466 --- /dev/null +++ b/ru/codes/javascript/chapter_searching/hashing_search.js @@ -0,0 +1,45 @@ +/** + * File: hashing_search.js + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { arrToLinkedList } = require('../modules/ListNode'); + +/* Хеш-поиск (массив) */ +function hashingSearchArray(map, target) { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + return map.has(target) ? map.get(target) : -1; +} + +/* Хеш-поиск (связный список) */ +function hashingSearchLinkedList(map, target) { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + return map.has(target) ? map.get(target) : null; +} + +/* Driver Code */ +const target = 3; + +/* Хеш-поиск (массив) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// Инициализация хеш-таблицы +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: элемент, value: индекс +} +const index = hashingSearchArray(map, target); +console.log('Индекс целевого элемента 3 = ' + index); + +/* Хеш-поиск (связный список) */ +let head = arrToLinkedList(nums); +// Инициализация хеш-таблицы +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: значение узла, value: узел + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('Объект узла со значением 3 =', node); diff --git a/ru/codes/javascript/chapter_searching/linear_search.js b/ru/codes/javascript/chapter_searching/linear_search.js new file mode 100644 index 000000000..abbc6bdfd --- /dev/null +++ b/ru/codes/javascript/chapter_searching/linear_search.js @@ -0,0 +1,47 @@ +/** + * File: linear_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +const { ListNode, arrToLinkedList } = require('../modules/ListNode'); + +/* Линейный поиск (массив) */ +function linearSearchArray(nums, target) { + // Обход массива + for (let i = 0; i < nums.length; i++) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] === target) { + return i; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Линейный поиск (связный список) */ +function linearSearchLinkedList(head, target) { + // Обойти связный список + while (head) { + // Найти целевой узел и вернуть его + if (head.val === target) { + return head; + } + head = head.next; + } + // Целевой узел не найден, вернуть null + return null; +} + +/* Driver Code */ +const target = 3; + +/* Выполнить линейный поиск в массиве */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('Индекс целевого элемента 3 = ' + index); + +/* Выполнить линейный поиск в связном списке */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('Объект узла со значением 3 = ', node); diff --git a/ru/codes/javascript/chapter_searching/two_sum.js b/ru/codes/javascript/chapter_searching/two_sum.js new file mode 100644 index 000000000..74a3ea9fe --- /dev/null +++ b/ru/codes/javascript/chapter_searching/two_sum.js @@ -0,0 +1,46 @@ +/** + * File: two_sum.js + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* Метод 1: полный перебор */ +function twoSumBruteForce(nums, target) { + const n = nums.length; + // Два вложенных цикла, временная сложность O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* Метод 2: вспомогательная хеш-таблица */ +function twoSumHashTable(nums, target) { + // Вспомогательная хеш-таблица, пространственная сложность O(n) + let m = {}; + // Один цикл, временная сложность O(n) + for (let i = 0; i < nums.length; i++) { + if (m[target - nums[i]] !== undefined) { + return [m[target - nums[i]], i]; + } else { + m[nums[i]] = i; + } + } + return []; +} + +/* Driver Code */ +// Метод 1 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('Результат метода 1 res = ', res); + +// Метод 2 +res = twoSumHashTable(nums, target); +console.log('Результат метода 2 res = ', res); diff --git a/ru/codes/javascript/chapter_sorting/bubble_sort.js b/ru/codes/javascript/chapter_sorting/bubble_sort.js new file mode 100644 index 000000000..21984de06 --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/bubble_sort.js @@ -0,0 +1,49 @@ +/** + * File: bubble_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Пузырьковая сортировка */ +function bubbleSort(nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +function bubbleSortWithFlag(nums) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // Записать обмен элементов + } + } + if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('После пузырьковой сортировки nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('После пузырьковой сортировки nums =', nums1); diff --git a/ru/codes/javascript/chapter_sorting/bucket_sort.js b/ru/codes/javascript/chapter_sorting/bucket_sort.js new file mode 100644 index 000000000..facbd63e3 --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/bucket_sort.js @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка корзинами */ +function bucketSort(nums) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + const k = nums.length / 2; + const buckets = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. Распределить элементы массива по корзинам + for (const num of nums) { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + const i = Math.floor(num * k); + // Добавить num в корзину i + buckets[i].push(num); + } + // 2. Выполнить сортировку внутри каждой корзины + for (const bucket of buckets) { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + bucket.sort((a, b) => a - b); + } + // 3. Обойти корзины и объединить результаты + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('После сортировки корзинами nums =', nums); diff --git a/ru/codes/javascript/chapter_sorting/counting_sort.js b/ru/codes/javascript/chapter_sorting/counting_sort.js new file mode 100644 index 000000000..a59f191dc --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/counting_sort.js @@ -0,0 +1,65 @@ +/** + * File: counting_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +function countingSortNaive(nums) { + // 1. Найти максимальный элемент массива m + let m = Math.max(...nums); + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +function countingSort(nums) { + // 1. Найти максимальный элемент массива m + let m = Math.max(...nums); + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + const n = nums.length; + const res = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // Поместить num по соответствующему индексу + counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('После сортировки подсчетом (объекты не поддерживаются) nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('После сортировки подсчетом nums1 =', nums1); diff --git a/ru/codes/javascript/chapter_sorting/heap_sort.js b/ru/codes/javascript/chapter_sorting/heap_sort.js new file mode 100644 index 000000000..189d2eac4 --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/heap_sort.js @@ -0,0 +1,49 @@ +/** + * File: heap_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +function siftDown(nums, n, i) { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let ma = i; + if (l < n && nums[l] > nums[ma]) { + ma = l; + } + if (r < n && nums[r] > nums[ma]) { + ma = r; + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma === i) { + break; + } + // Поменять два узла местами + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // Циклическое просеивание вниз + i = ma; + } +} + +/* Сортировка кучей */ +function heapSort(nums) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (let i = nums.length - 1; i > 0; i--) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('После сортировки кучей nums =', nums); diff --git a/ru/codes/javascript/chapter_sorting/insertion_sort.js b/ru/codes/javascript/chapter_sorting/insertion_sort.js new file mode 100644 index 000000000..cd86e2abb --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/insertion_sort.js @@ -0,0 +1,25 @@ +/** + * File: insertion_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Сортировка вставками */ +function insertionSort(nums) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (let i = 1; i < nums.length; i++) { + let base = nums[i], + j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо + j--; + } + nums[j + 1] = base; // Поместить base в правильную позицию + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('После сортировки вставками nums =', nums); diff --git a/ru/codes/javascript/chapter_sorting/merge_sort.js b/ru/codes/javascript/chapter_sorting/merge_sort.js new file mode 100644 index 000000000..1e6c16f88 --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/merge_sort.js @@ -0,0 +1,52 @@ +/** + * File: merge_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Объединить левый и правый подмассивы */ +function merge(nums, left, mid, right) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + const tmp = new Array(right - left + 1); + // Инициализировать начальные индексы левого и правого подмассивов + let i = left, + j = mid + 1, + k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* Сортировка слиянием */ +function mergeSort(nums, left, right) { + // Условие завершения + if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + let mid = Math.floor(left + (right - left) / 2); // Вычислить середину + mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('После сортировки слиянием nums =', nums); diff --git a/ru/codes/javascript/chapter_sorting/quick_sort.js b/ru/codes/javascript/chapter_sorting/quick_sort.js new file mode 100644 index 000000000..35361deb2 --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/quick_sort.js @@ -0,0 +1,161 @@ +/** + * File: quick_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Класс быстрой сортировки */ +class QuickSort { + /* Обмен элементов */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + partition(nums, left, right) { + // Взять nums[left] в качестве опорного элемента + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // Идти слева направо в поисках первого элемента больше опорного + } + // Обмен элементов + this.swap(nums, i, j); // Поменять эти два элемента местами + } + this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + quickSort(nums, left, right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return; + // Разбиение с опорными указателями + const pivot = this.partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +class QuickSortMedian { + /* Обмен элементов */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Выбрать медиану из трех кандидатов */ + medianThree(nums, left, mid, right) { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m находится между l и r + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l находится между m и r + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* Разбиение с опорными указателями (медиана трех) */ + partition(nums, left, right) { + // Выбрать медиану из трех кандидатов + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // Переместить медиану в крайний левый элемент массива + this.swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного + this.swap(nums, i, j); // Поменять эти два элемента местами + } + this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + quickSort(nums, left, right) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return; + // Разбиение с опорными указателями + const pivot = this.partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация глубины рекурсии) */ +class QuickSortTailCall { + /* Обмен элементов */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + partition(nums, left, right) { + // Взять nums[left] в качестве опорного элемента + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) i++; // Идти слева направо в поисках первого элемента больше опорного + this.swap(nums, i, j); // Поменять эти два элемента местами + } + this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + quickSort(nums, left, right) { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + let pivot = this.partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* Быстрая сортировка */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('После быстрой сортировки nums =', nums); + +/* Быстрая сортировка (оптимизация медианным опорным элементом) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('После быстрой сортировки (оптимизация медианным опорным элементом) nums =', nums1); + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('После быстрой сортировки (оптимизация глубины рекурсии) nums =', nums2); diff --git a/ru/codes/javascript/chapter_sorting/radix_sort.js b/ru/codes/javascript/chapter_sorting/radix_sort.js new file mode 100644 index 000000000..068fa4334 --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/radix_sort.js @@ -0,0 +1,61 @@ +/** + * File: radix_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +function digit(num, exp) { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return Math.floor(num / exp) % 10; +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +function countingSortDigit(nums, exp) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + const counter = new Array(10).fill(0); + const n = nums.length; + // Подсчитать число появлений каждой цифры от 0 до 9 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d]++; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Поразрядная сортировка */ +function radixSort(nums) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + let m = Math.max(... nums); + // Проходить разряды от младшего к старшему + for (let exp = 1; exp <= m; exp *= 10) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('После поразрядной сортировки nums =', nums); diff --git a/ru/codes/javascript/chapter_sorting/selection_sort.js b/ru/codes/javascript/chapter_sorting/selection_sort.js new file mode 100644 index 000000000..1ee743a5d --- /dev/null +++ b/ru/codes/javascript/chapter_sorting/selection_sort.js @@ -0,0 +1,27 @@ +/** + * File: selection_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка выбором */ +function selectionSort(nums) { + let n = nums.length; + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (let i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // Записать индекс минимального элемента + } + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('После сортировки выбором nums =', nums); diff --git a/ru/codes/javascript/chapter_stack_and_queue/array_deque.js b/ru/codes/javascript/chapter_stack_and_queue/array_deque.js new file mode 100644 index 000000000..796b2c2c6 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/array_deque.js @@ -0,0 +1,156 @@ +/** + * File: array_deque.js + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Двусторонняя очередь на основе кольцевого массива */ +class ArrayDeque { + #nums; // Массив для хранения элементов двусторонней очереди + #front; // Указатель head, указывающий на первый элемент очереди + #queSize; // Длина двусторонней очереди + + /* Конструктор */ + constructor(capacity) { + this.#nums = new Array(capacity); + this.#front = 0; + this.#queSize = 0; + } + + /* Получить вместимость двусторонней очереди */ + capacity() { + return this.#nums.length; + } + + /* Получение длины двусторонней очереди */ + size() { + return this.#queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + isEmpty() { + return this.#queSize === 0; + } + + /* Вычислить индекс в кольцевом массиве */ + index(i) { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + this.capacity()) % this.capacity(); + } + + /* Добавление в голову очереди */ + pushFirst(num) { + if (this.#queSize === this.capacity()) { + console.log('Двусторонняя очередь заполнена'); + return; + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + this.#front = this.index(this.#front - 1); + // Добавить num в голову очереди + this.#nums[this.#front] = num; + this.#queSize++; + } + + /* Добавление в хвост очереди */ + pushLast(num) { + if (this.#queSize === this.capacity()) { + console.log('Двусторонняя очередь заполнена'); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + const rear = this.index(this.#front + this.#queSize); + // Добавить num в хвост очереди + this.#nums[rear] = num; + this.#queSize++; + } + + /* Извлечение из головы очереди */ + popFirst() { + const num = this.peekFirst(); + // Указатель головы сдвигается на одну позицию назад + this.#front = this.index(this.#front + 1); + this.#queSize--; + return num; + } + + /* Извлечение из хвоста очереди */ + popLast() { + const num = this.peekLast(); + this.#queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + peekFirst() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.#nums[this.#front]; + } + + /* Доступ к элементу в конце очереди */ + peekLast() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // Вычислить индекс хвостового элемента + const last = this.index(this.#front + this.#queSize - 1); + return this.#nums[last]; + } + + /* Вернуть массив для вывода */ + toArray() { + // Преобразовывать только элементы списка в пределах фактической длины + const res = []; + for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { + res[i] = this.#nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* Инициализация двусторонней очереди */ +const capacity = 5; +const deque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('Двусторонняя очередь deque = [' + deque.toArray() + ']'); + +/* Доступ к элементу */ +const peekFirst = deque.peekFirst(); +console.log('Первый элемент peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('Последний элемент peekLast = ' + peekLast); + +/* Добавление элемента в очередь */ +deque.pushLast(4); +console.log('После добавления элемента 4 в хвост deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('После добавления элемента 1 в голову deque = [' + deque.toArray() + ']'); + +/* Извлечение элемента из очереди */ +const popLast = deque.popLast(); +console.log( + 'Извлеченный из хвоста элемент = ' + + popLast + + ', deque после извлечения из хвоста = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + 'Извлеченный из головы элемент = ' + + popFirst + + ', deque после извлечения из головы = [' + + deque.toArray() + + ']' +); + +/* Получение длины двусторонней очереди */ +const size = deque.size(); +console.log('Длина двусторонней очереди size = ' + size); + +/* Проверка, пуста ли двусторонняя очередь */ +const isEmpty = deque.isEmpty(); +console.log('Пуста ли двусторонняя очередь = ' + isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/array_queue.js b/ru/codes/javascript/chapter_stack_and_queue/array_queue.js new file mode 100644 index 000000000..cc83d5e13 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/array_queue.js @@ -0,0 +1,106 @@ +/** + * File: array_queue.js + * Created Time: 2022-12-13 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + #nums; // Массив для хранения элементов очереди + #front = 0; // Указатель head, указывающий на первый элемент очереди + #queSize = 0; // Длина очереди + + constructor(capacity) { + this.#nums = new Array(capacity); + } + + /* Получить вместимость очереди */ + get capacity() { + return this.#nums.length; + } + + /* Получение длины очереди */ + get size() { + return this.#queSize; + } + + /* Проверка, пуста ли очередь */ + isEmpty() { + return this.#queSize === 0; + } + + /* Поместить в очередь */ + push(num) { + if (this.size === this.capacity) { + console.log('Очередь заполнена'); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + const rear = (this.#front + this.size) % this.capacity; + // Добавить num в хвост очереди + this.#nums[rear] = num; + this.#queSize++; + } + + /* Извлечь из очереди */ + pop() { + const num = this.peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + this.#front = (this.#front + 1) % this.capacity; + this.#queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + peek() { + if (this.isEmpty()) throw new Error('очередь пуста'); + return this.#nums[this.#front]; + } + + /* Вернуть Array */ + toArray() { + // Преобразовывать только элементы списка в пределах фактической длины + const arr = new Array(this.size); + for (let i = 0, j = this.#front; i < this.size; i++, j++) { + arr[i] = this.#nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* Инициализация очереди */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* Добавление элемента в очередь */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('Очередь queue =', queue.toArray()); + +/* Доступ к элементу в начале очереди */ +const peek = queue.peek(); +console.log('Первый элемент peek = ' + peek); + +/* Извлечение элемента из очереди */ +const pop = queue.pop(); +console.log('Извлеченный элемент pop = ' + pop + ', queue после извлечения =', queue.toArray()); + +/* Получение длины очереди */ +const size = queue.size; +console.log('Длина очереди size = ' + size); + +/* Проверка, пуста ли очередь */ +const isEmpty = queue.isEmpty(); +console.log('Пуста ли очередь = ' + isEmpty); + +/* Проверка кольцевого массива */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('После ' + i + '-го раунда операций enqueue и dequeue queue =', queue.toArray()); +} diff --git a/ru/codes/javascript/chapter_stack_and_queue/array_stack.js b/ru/codes/javascript/chapter_stack_and_queue/array_stack.js new file mode 100644 index 000000000..f133ddcb2 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/array_stack.js @@ -0,0 +1,75 @@ +/** + * File: array_stack.js + * Created Time: 2022-12-09 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Стек на основе массива */ +class ArrayStack { + #stack; + constructor() { + this.#stack = []; + } + + /* Получение длины стека */ + get size() { + return this.#stack.length; + } + + /* Проверка, пуст ли стек */ + isEmpty() { + return this.#stack.length === 0; + } + + /* Поместить в стек */ + push(num) { + this.#stack.push(num); + } + + /* Извлечь из стека */ + pop() { + if (this.isEmpty()) throw new Error('стек пуст'); + return this.#stack.pop(); + } + + /* Доступ к верхнему элементу стека */ + top() { + if (this.isEmpty()) throw new Error('стек пуст'); + return this.#stack[this.#stack.length - 1]; + } + + /* Вернуть Array */ + toArray() { + return this.#stack; + } +} + +/* Driver Code */ +/* Инициализация стека */ +const stack = new ArrayStack(); + +/* Помещение элемента в стек */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('Стек stack = '); +console.log(stack.toArray()); + +/* Доступ к верхнему элементу стека */ +const top = stack.top(); +console.log('Верхний элемент top = ' + top); + +/* Извлечение элемента из стека */ +const pop = stack.pop(); +console.log('Извлеченный элемент pop = ' + pop + ', stack после извлечения = '); +console.log(stack.toArray()); + +/* Получение длины стека */ +const size = stack.size; +console.log('Длина стека size = ' + size); + +/* Проверка на пустоту */ +const isEmpty = stack.isEmpty(); +console.log('Пуст ли стек = ' + isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/deque.js b/ru/codes/javascript/chapter_stack_and_queue/deque.js new file mode 100644 index 000000000..95ff3ce84 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/deque.js @@ -0,0 +1,44 @@ +/** + * File: deque.js + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* Инициализация двусторонней очереди */ +// В JavaScript нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь +const deque = []; + +/* Добавление элемента в очередь */ +deque.push(2); +deque.push(5); +deque.push(4); +// Обратите внимание: поскольку используется массив, временная сложность метода unshift() равна O(n) +deque.unshift(3); +deque.unshift(1); +console.log('Двусторонняя очередь deque = ', deque); + +/* Доступ к элементу */ +const peekFirst = deque[0]; +console.log('Первый элемент peekFirst = ' + peekFirst); +const peekLast = deque[deque.length - 1]; +console.log('Последний элемент peekLast = ' + peekLast); + +/* Извлечение элемента из очереди */ +// Обратите внимание: поскольку используется массив, временная сложность метода shift() равна O(n) +const popFront = deque.shift(); +console.log( + 'Извлеченный из головы элемент popFront = ' + popFront + ', deque после извлечения из головы = ' + deque +); +const popBack = deque.pop(); +console.log( + 'Извлеченный из хвоста элемент popBack = ' + popBack + ', deque после извлечения из хвоста = ' + deque +); + +/* Получение длины двусторонней очереди */ +const size = deque.length; +console.log('Длина двусторонней очереди size = ' + size); + +/* Проверка, пуста ли двусторонняя очередь */ +const isEmpty = size === 0; +console.log('Пуста ли двусторонняя очередь = ' + isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js b/ru/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js new file mode 100644 index 000000000..d2293aa66 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.js + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Узел двусвязного списка */ +class ListNode { + prev; // Ссылка на узел-предшественник (указатель) + next; // Ссылка на узел-преемник (указатель) + val; // Значение узла + + constructor(val) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + #front; // Головной узел front + #rear; // Хвостовой узел rear + #queSize; // Длина двусторонней очереди + + constructor() { + this.#front = null; + this.#rear = null; + this.#queSize = 0; + } + + /* Операция добавления в хвост очереди */ + pushLast(val) { + const node = new ListNode(val); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // Добавить node в хвост списка + this.#rear.next = node; + node.prev = this.#rear; + this.#rear = node; // Обновить хвостовой узел + } + this.#queSize++; + } + + /* Операция добавления в голову очереди */ + pushFirst(val) { + const node = new ListNode(val); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // Добавить node в голову списка + this.#front.prev = node; + node.next = this.#front; + this.#front = node; // Обновить головной узел + } + this.#queSize++; + } + + /* Операция извлечения из хвоста очереди */ + popLast() { + if (this.#queSize === 0) { + return null; + } + const value = this.#rear.val; // Сохранить значение хвостового узла + // Удалить хвостовой узел + let temp = this.#rear.prev; + if (temp !== null) { + temp.next = null; + this.#rear.prev = null; + } + this.#rear = temp; // Обновить хвостовой узел + this.#queSize--; + return value; + } + + /* Операция извлечения из головы очереди */ + popFirst() { + if (this.#queSize === 0) { + return null; + } + const value = this.#front.val; // Сохранить значение хвостового узла + // Удалить головной узел + let temp = this.#front.next; + if (temp !== null) { + temp.prev = null; + this.#front.next = null; + } + this.#front = temp; // Обновить головной узел + this.#queSize--; + return value; + } + + /* Доступ к элементу в конце очереди */ + peekLast() { + return this.#queSize === 0 ? null : this.#rear.val; + } + + /* Доступ к элементу в начале очереди */ + peekFirst() { + return this.#queSize === 0 ? null : this.#front.val; + } + + /* Получение длины двусторонней очереди */ + size() { + return this.#queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + isEmpty() { + return this.#queSize === 0; + } + + /* Вывести двустороннюю очередь */ + print() { + const arr = []; + let temp = this.#front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* Инициализация двусторонней очереди */ +const linkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('Двусторонняя очередь linkedListDeque = '); +linkedListDeque.print(); + +/* Доступ к элементу */ +const peekFirst = linkedListDeque.peekFirst(); +console.log('Первый элемент peekFirst = ' + peekFirst); +const peekLast = linkedListDeque.peekLast(); +console.log('Последний элемент peekLast = ' + peekLast); + +/* Добавление элемента в очередь */ +linkedListDeque.pushLast(4); +console.log('После добавления элемента 4 в хвост linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('После добавления элемента 1 в голову linkedListDeque = '); +linkedListDeque.print(); + +/* Извлечение элемента из очереди */ +const popLast = linkedListDeque.popLast(); +console.log('Извлеченный из хвоста элемент = ' + popLast + ', linkedListDeque после извлечения из хвоста = '); +linkedListDeque.print(); +const popFirst = linkedListDeque.popFirst(); +console.log('Извлеченный из головы элемент = ' + popFirst + ', linkedListDeque после извлечения из головы = '); +linkedListDeque.print(); + +/* Получение длины двусторонней очереди */ +const size = linkedListDeque.size(); +console.log('Длина двусторонней очереди size = ' + size); + +/* Проверка, пуста ли двусторонняя очередь */ +const isEmpty = linkedListDeque.isEmpty(); +console.log('Пуста ли двусторонняя очередь = ' + isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js b/ru/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js new file mode 100644 index 000000000..083c095aa --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js @@ -0,0 +1,99 @@ +/** + * File: linkedlist_queue.js + * Created Time: 2022-12-20 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* Очередь на основе связного списка */ +class LinkedListQueue { + #front; // Головной узел #front + #rear; // Хвостовой узел #rear + #queSize = 0; + + constructor() { + this.#front = null; + this.#rear = null; + } + + /* Получение длины очереди */ + get size() { + return this.#queSize; + } + + /* Проверка, пуста ли очередь */ + isEmpty() { + return this.size === 0; + } + + /* Поместить в очередь */ + push(num) { + // Добавить num после хвостового узла + const node = new ListNode(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (!this.#front) { + this.#front = node; + this.#rear = node; + // Если очередь не пуста, добавить этот узел после хвостового узла + } else { + this.#rear.next = node; + this.#rear = node; + } + this.#queSize++; + } + + /* Извлечь из очереди */ + pop() { + const num = this.peek(); + // Удалить головной узел + this.#front = this.#front.next; + this.#queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + peek() { + if (this.size === 0) throw new Error('очередь пуста'); + return this.#front.val; + } + + /* Преобразовать связный список в Array и вернуть */ + toArray() { + let node = this.#front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* Инициализация очереди */ +const queue = new LinkedListQueue(); + +/* Добавление элемента в очередь */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('Очередь queue = ' + queue.toArray()); + +/* Доступ к элементу в начале очереди */ +const peek = queue.peek(); +console.log('Первый элемент peek = ' + peek); + +/* Извлечение элемента из очереди */ +const pop = queue.pop(); +console.log('Извлеченный элемент pop = ' + pop + ', queue после извлечения = ' + queue.toArray()); + +/* Получение длины очереди */ +const size = queue.size; +console.log('Длина очереди size = ' + size); + +/* Проверка, пуста ли очередь */ +const isEmpty = queue.isEmpty(); +console.log('Пуста ли очередь = ' + isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js b/ru/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js new file mode 100644 index 000000000..d4c3c15cb --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js @@ -0,0 +1,88 @@ +/** + * File: linkedlist_stack.js + * Created Time: 2022-12-22 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* Стек на основе связного списка */ +class LinkedListStack { + #stackPeek; // Использовать головной узел как вершину стека + #stkSize = 0; // Длина стека + + constructor() { + this.#stackPeek = null; + } + + /* Получение длины стека */ + get size() { + return this.#stkSize; + } + + /* Проверка, пуст ли стек */ + isEmpty() { + return this.size === 0; + } + + /* Поместить в стек */ + push(num) { + const node = new ListNode(num); + node.next = this.#stackPeek; + this.#stackPeek = node; + this.#stkSize++; + } + + /* Извлечь из стека */ + pop() { + const num = this.peek(); + this.#stackPeek = this.#stackPeek.next; + this.#stkSize--; + return num; + } + + /* Доступ к верхнему элементу стека */ + peek() { + if (!this.#stackPeek) throw new Error('стек пуст'); + return this.#stackPeek.val; + } + + /* Преобразовать связный список в Array и вернуть */ + toArray() { + let node = this.#stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* Инициализация стека */ +const stack = new LinkedListStack(); + +/* Помещение элемента в стек */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('Стек stack = ' + stack.toArray()); + +/* Доступ к верхнему элементу стека */ +const peek = stack.peek(); +console.log('Верхний элемент peek = ' + peek); + +/* Извлечение элемента из стека */ +const pop = stack.pop(); +console.log('Извлеченный элемент pop = ' + pop + ', stack после извлечения = ' + stack.toArray()); + +/* Получение длины стека */ +const size = stack.size; +console.log('Длина стека size = ' + size); + +/* Проверка на пустоту */ +const isEmpty = stack.isEmpty(); +console.log('Пуст ли стек = ' + isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/queue.js b/ru/codes/javascript/chapter_stack_and_queue/queue.js new file mode 100644 index 000000000..765de1c22 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/queue.js @@ -0,0 +1,35 @@ +/** + * File: queue.js + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* Инициализация очереди */ +// В JavaScript нет встроенной очереди, поэтому Array можно использовать как очередь +const queue = []; + +/* Добавление элемента в очередь */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('Очередь queue =', queue); + +/* Доступ к элементу в начале очереди */ +const peek = queue[0]; +console.log('Первый элемент peek =', peek); + +/* Извлечение элемента из очереди */ +// В основе лежит массив, поэтому временная сложность метода shift() равна O(n) +const pop = queue.shift(); +console.log('Извлеченный элемент pop =', pop, ', queue после извлечения = ', queue); + +/* Получение длины очереди */ +const size = queue.length; +console.log('Длина очереди size =', size); + +/* Проверка, пуста ли очередь */ +const isEmpty = queue.length === 0; +console.log('Пуста ли очередь = ', isEmpty); diff --git a/ru/codes/javascript/chapter_stack_and_queue/stack.js b/ru/codes/javascript/chapter_stack_and_queue/stack.js new file mode 100644 index 000000000..537f864b5 --- /dev/null +++ b/ru/codes/javascript/chapter_stack_and_queue/stack.js @@ -0,0 +1,35 @@ +/** + * File: stack.js + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* Инициализация стека */ +// В JavaScript нет встроенного класса стека, поэтому Array можно использовать как стек +const stack = []; + +/* Помещение элемента в стек */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('Стек stack =', stack); + +/* Доступ к верхнему элементу стека */ +const peek = stack[stack.length - 1]; +console.log('Верхний элемент peek =', peek); + +/* Извлечение элемента из стека */ +const pop = stack.pop(); +console.log('Извлеченный элемент pop =', pop); +console.log('stack после извлечения =', stack); + +/* Получение длины стека */ +const size = stack.length; +console.log('Длина стека size =', size); + +/* Проверка на пустоту */ +const isEmpty = stack.length === 0; +console.log('Пуст ли стек =', isEmpty); diff --git a/ru/codes/javascript/chapter_tree/array_binary_tree.js b/ru/codes/javascript/chapter_tree/array_binary_tree.js new file mode 100644 index 000000000..b65468c54 --- /dev/null +++ b/ru/codes/javascript/chapter_tree/array_binary_tree.js @@ -0,0 +1,147 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree { + #tree; + + /* Конструктор */ + constructor(arr) { + this.#tree = arr; + } + + /* Вместимость списка */ + size() { + return this.#tree.length; + } + + /* Получить значение узла с индексом i */ + val(i) { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + left(i) { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + right(i) { + return 2 * i + 2; + } + + /* Получить индекс родительского узла узла с индексом i */ + parent(i) { + return Math.floor((i - 1) / 2); // Округление вниз при делении + } + + /* Обход в ширину */ + levelOrder() { + let res = []; + // Непосредственно обходить массив + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* Обход в глубину */ + #dfs(i, order, res) { + // Если это пустая позиция, вернуть + if (this.val(i) === null) return; + // Предварительный обход + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // Симметричный обход + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // Обратный обход + if (order === 'post') res.push(this.val(i)); + } + + /* Предварительный обход */ + preOrder() { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* Симметричный обход */ + inOrder() { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* Обратный обход */ + postOrder() { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// Инициализировать двоичное дерево +// Здесь используется функция, напрямую строящая двоичное дерево из массива +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\nИнициализация двоичного дерева\n'); +console.log('Массивное представление двоичного дерева:'); +console.log(arr); +console.log('Связное представление двоичного дерева:'); +printTree(root); + +// Класс двоичного дерева в массивном представлении +const abt = new ArrayBinaryTree(arr); + +// Доступ к узлу +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\nТекущий узел: индекс = ' + i + ', значение = ' + abt.val(i)); +console.log( + 'Индекс левого дочернего узла = ' + l + ', значение = ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + 'Индекс правого дочернего узла = ' + r + ', значение = ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + 'Индекс родительского узла = ' + p + ', значение = ' + (p === null ? 'null' : abt.val(p)) +); + +// Обходить дерево +let res = abt.levelOrder(); +console.log('\nОбход в ширину: ' + res); +res = abt.preOrder(); +console.log('Предварительный обход: ' + res); +res = abt.inOrder(); +console.log('Симметричный обход: ' + res); +res = abt.postOrder(); +console.log('Обратный обход: ' + res); diff --git a/ru/codes/javascript/chapter_tree/avl_tree.js b/ru/codes/javascript/chapter_tree/avl_tree.js new file mode 100644 index 000000000..050b1d507 --- /dev/null +++ b/ru/codes/javascript/chapter_tree/avl_tree.js @@ -0,0 +1,208 @@ +/** + * File: avl_tree.js + * Created Time: 2023-02-05 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* AVL-дерево */ +class AVLTree { + /* Конструктор */ + constructor() { + this.root = null; // Корневой узел + } + + /* Получить высоту узла */ + height(node) { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node === null ? -1 : node.height; + } + + /* Обновить высоту узла */ + #updateHeight(node) { + // Высота узла равна высоте более высокого поддерева + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* Получить коэффициент баланса */ + balanceFactor(node) { + // Коэффициент баланса пустого узла равен 0 + if (node === null) return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return this.height(node.left) - this.height(node.right); + } + + /* Операция правого вращения */ + #rightRotate(node) { + const child = node.left; + const grandChild = child.right; + // Выполнить правое вращение узла node вокруг child + child.right = node; + node.left = grandChild; + // Обновить высоту узла + this.#updateHeight(node); + this.#updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Операция левого вращения */ + #leftRotate(node) { + const child = node.right; + const grandChild = child.left; + // Выполнить левое вращение узла node вокруг child + child.left = node; + node.right = grandChild; + // Обновить высоту узла + this.#updateHeight(node); + this.#updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + #rotate(node) { + // Получить коэффициент баланса узла node + const balanceFactor = this.balanceFactor(node); + // Левосторонне перекошенное дерево + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // Правое вращение + return this.#rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node.left = this.#leftRotate(node.left); + return this.#rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // Левое вращение + return this.#leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node.right = this.#rightRotate(node.right); + return this.#leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + /* Вставка узла */ + insert(val) { + this.root = this.#insertHelper(this.root, val); + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + #insertHelper(node, val) { + if (node === null) return new TreeNode(val); + /* 1. Найти позицию вставки и вставить узел */ + if (val < node.val) node.left = this.#insertHelper(node.left, val); + else if (val > node.val) + node.right = this.#insertHelper(node.right, val); + else return node; // Повторяющийся узел не вставлять, сразу вернуть + this.#updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = this.#rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Удаление узла */ + remove(val) { + this.root = this.#removeHelper(this.root, val); + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + #removeHelper(node, val) { + if (node === null) return null; + /* 1. Найти узел и удалить его */ + if (val < node.val) node.left = this.#removeHelper(node.left, val); + else if (val > node.val) + node.right = this.#removeHelper(node.right, val); + else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child === null) return null; + // Число дочерних узлов = 1, удалить node напрямую + else node = child; + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.#removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.#updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = this.#rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Поиск узла */ + search(val) { + let cur = this.root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < val) cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > val) cur = cur.left; + // Найти целевой узел и выйти из цикла + else break; + } + // Вернуть целевой узел + return cur; + } +} + +function testInsert(tree, val) { + tree.insert(val); + console.log('\nПосле вставки узла ' + val + ' AVL-дерево имеет вид'); + printTree(tree.root); +} + +function testRemove(tree, val) { + tree.remove(val); + console.log('\nПосле удаления узла ' + val + ' AVL-дерево имеет вид'); + printTree(tree.root); +} + +/* Driver Code */ +/* Инициализация пустого AVL-дерева */ +const avlTree = new AVLTree(); +/* Вставка узла */ +// Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* Вставка повторяющегося узла */ +testInsert(avlTree, 7); + +/* Удаление узла */ +// Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла +testRemove(avlTree, 8); // Удаление узла степени 0 +testRemove(avlTree, 5); // Удаление узла степени 1 +testRemove(avlTree, 4); // Удаление узла степени 2 + +/* Поиск узла */ +const node = avlTree.search(7); +console.log('\nНайденный объект узла =', node, ', значение узла = ' + node.val); diff --git a/ru/codes/javascript/chapter_tree/binary_search_tree.js b/ru/codes/javascript/chapter_tree/binary_search_tree.js new file mode 100644 index 000000000..8b428fb68 --- /dev/null +++ b/ru/codes/javascript/chapter_tree/binary_search_tree.js @@ -0,0 +1,139 @@ +/** + * File: binary_search_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Двоичное дерево поиска */ +class BinarySearchTree { + /* Конструктор */ + constructor() { + // Инициализировать пустое дерево + this.root = null; + } + + /* Получить корневой узел двоичного дерева */ + getRoot() { + return this.root; + } + + /* Поиск узла */ + search(num) { + let cur = this.root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < num) cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > num) cur = cur.left; + // Найти целевой узел и выйти из цикла + else break; + } + // Вернуть целевой узел + return cur; + } + + /* Вставка узла */ + insert(num) { + // Если дерево пусто, инициализировать корневой узел + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur = this.root, + pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Найти повторяющийся узел и сразу вернуть + if (cur.val === num) return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur.val < num) cur = cur.right; + // Позиция вставки находится в левом поддереве cur + else cur = cur.left; + } + // Вставка узла + const node = new TreeNode(num); + if (pre.val < num) pre.right = node; + else pre.left = node; + } + + /* Удаление узла */ + remove(num) { + // Если дерево пусто, сразу вернуть + if (this.root === null) return; + let cur = this.root, + pre = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Найти узел для удаления и выйти из цикла + if (cur.val === num) break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur.val < num) cur = cur.right; + // Узел для удаления находится в левом поддереве cur + else cur = cur.left; + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur === null) return; + // Число дочерних узлов = 0 или 1 + if (cur.left === null || cur.right === null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + const child = cur.left !== null ? cur.left : cur.right; + // Удалить узел cur + if (cur !== this.root) { + if (pre.left === cur) pre.left = child; + else pre.right = child; + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + this.root = child; + } + } + // Число дочерних узлов = 2 + else { + // Получить следующий узел после cur в симметричном обходе + let tmp = cur.right; + while (tmp.left !== null) { + tmp = tmp.left; + } + // Рекурсивно удалить узел tmp + this.remove(tmp.val); + // Перезаписать cur значением tmp + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +/* Инициализация двоичного дерева поиска */ +const bst = new BinarySearchTree(); +// Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\nИсходное двоичное дерево\n'); +printTree(bst.getRoot()); + +/* Поиск узла */ +const node = bst.search(7); +console.log('\nНайденный объект узла = ' + node + ', значение узла = ' + node.val); + +/* Вставка узла */ +bst.insert(16); +console.log('\nПосле вставки узла 16 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); + +/* Удаление узла */ +bst.remove(1); +console.log('\nПосле удаления узла 1 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\nПосле удаления узла 2 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\nПосле удаления узла 4 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); diff --git a/ru/codes/javascript/chapter_tree/binary_tree.js b/ru/codes/javascript/chapter_tree/binary_tree.js new file mode 100644 index 000000000..d38bfd90a --- /dev/null +++ b/ru/codes/javascript/chapter_tree/binary_tree.js @@ -0,0 +1,35 @@ +/** + * File: binary_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Инициализация двоичного дерева */ +// Инициализация узла +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// Построить связи между узлами (указатели) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\nИнициализация двоичного дерева\n'); +printTree(n1); + +/* Вставка и удаление узлов */ +const P = new TreeNode(0); +// Вставить узел P между n1 -> n2 +n1.left = P; +P.left = n2; +console.log('\nПосле вставки узла P\n'); +printTree(n1); +// Удалить узел P +n1.left = n2; +console.log('\nПосле удаления узла P\n'); +printTree(n1); diff --git a/ru/codes/javascript/chapter_tree/binary_tree_bfs.js b/ru/codes/javascript/chapter_tree/binary_tree_bfs.js new file mode 100644 index 000000000..3432be0f5 --- /dev/null +++ b/ru/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -0,0 +1,34 @@ +/** + * File: binary_tree_bfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* Обход в ширину */ +function levelOrder(root) { + // Инициализировать очередь и добавить корневой узел + const queue = [root]; + // Инициализировать список для хранения последовательности обхода + const list = []; + while (queue.length) { + let node = queue.shift(); // Извлечение из очереди + list.push(node.val); // Сохранить значение узла + if (node.left) queue.push(node.left); // Поместить левый дочерний узел в очередь + if (node.right) queue.push(node.right); // Поместить правый дочерний узел в очередь + } + return list; +} + +/* Driver Code */ +/* Инициализация двоичного дерева */ +// Здесь используется функция, напрямую строящая двоичное дерево из массива +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева\n'); +printTree(root); + +/* Обход в ширину */ +const list = levelOrder(root); +console.log('\nПоследовательность печати узлов при обходе в ширину = ' + list); diff --git a/ru/codes/javascript/chapter_tree/binary_tree_dfs.js b/ru/codes/javascript/chapter_tree/binary_tree_dfs.js new file mode 100644 index 000000000..247655543 --- /dev/null +++ b/ru/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -0,0 +1,60 @@ +/** + * File: binary_tree_dfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +// Инициализировать список для хранения последовательности обхода +const list = []; + +/* Предварительный обход */ +function preOrder(root) { + if (root === null) return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* Симметричный обход */ +function inOrder(root) { + if (root === null) return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* Обратный обход */ +function postOrder(root) { + if (root === null) return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* Инициализация двоичного дерева */ +// Здесь используется функция, напрямую строящая двоичное дерево из массива +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева\n'); +printTree(root); + +/* Предварительный обход */ +list.length = 0; +preOrder(root); +console.log('\nПоследовательность печати узлов при предварительном обходе = ' + list); + +/* Симметричный обход */ +list.length = 0; +inOrder(root); +console.log('\nПоследовательность печати узлов при симметричном обходе = ' + list); + +/* Обратный обход */ +list.length = 0; +postOrder(root); +console.log('\nПоследовательность печати узлов при обратном обходе = ' + list); diff --git a/ru/codes/javascript/modules/ListNode.js b/ru/codes/javascript/modules/ListNode.js new file mode 100644 index 000000000..5944e9d2e --- /dev/null +++ b/ru/codes/javascript/modules/ListNode.js @@ -0,0 +1,31 @@ +/** + * File: ListNode.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Узел связного списка */ +class ListNode { + val; // Значение узла + next; // Ссылка (указатель) на следующий узел + constructor(val, next) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* Десериализовать список в связный список */ +function arrToLinkedList(arr) { + const dum = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +module.exports = { + ListNode, + arrToLinkedList, +}; diff --git a/ru/codes/javascript/modules/PrintUtil.js b/ru/codes/javascript/modules/PrintUtil.js new file mode 100644 index 000000000..9d92d9d32 --- /dev/null +++ b/ru/codes/javascript/modules/PrintUtil.js @@ -0,0 +1,86 @@ +/** + * File: PrintUtil.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('./TreeNode'); + +/* Вывести связный список */ +function printLinkedList(head) { + let list = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +function Trunk(prev, str) { + this.prev = prev; + this.str = str; +} + +/** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root) { + printTree(root, null, false); +} + +/* Вывести двоичное дерево */ +function printTree(root, prev, isRight) { + if (root === null) { + return; + } + + let prev_str = ' '; + let trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (!prev) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +function showTrunks(p) { + if (!p) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* Вывести кучу */ +function printHeap(arr) { + console.log('Массивное представление кучи:'); + console.log(arr); + console.log('Древовидное представление кучи:'); + printTree(arrToTree(arr)); +} + +module.exports = { + printLinkedList, + printTree, + printHeap, +}; diff --git a/ru/codes/javascript/modules/TreeNode.js b/ru/codes/javascript/modules/TreeNode.js new file mode 100644 index 000000000..28e2747f5 --- /dev/null +++ b/ru/codes/javascript/modules/TreeNode.js @@ -0,0 +1,35 @@ +/** + * File: TreeNode.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* Узел двоичного дерева */ +class TreeNode { + val; // Значение узла + left; // Указатель на левый дочерний узел + right; // Указатель на правый дочерний узел + height; // Высота узла + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + this.height = height === undefined ? 0 : height; + } +} + +/* Десериализовать массив в двоичное дерево */ +function arrToTree(arr, i = 0) { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +module.exports = { + TreeNode, + arrToTree, +}; diff --git a/ru/codes/javascript/modules/Vertex.js b/ru/codes/javascript/modules/Vertex.js new file mode 100644 index 000000000..3a9e17f16 --- /dev/null +++ b/ru/codes/javascript/modules/Vertex.js @@ -0,0 +1,35 @@ +/** + * File: Vertex.js + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Класс вершины */ +class Vertex { + val; + constructor(val) { + this.val = val; + } + + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + static valsToVets(vals) { + const vets = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + static vetsToVals(vets) { + const vals = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +module.exports = { + Vertex, +}; diff --git a/ru/codes/javascript/test_all.js b/ru/codes/javascript/test_all.js new file mode 100644 index 000000000..b43ef49f5 --- /dev/null +++ b/ru/codes/javascript/test_all.js @@ -0,0 +1,63 @@ +import { bold, brightRed } from 'jsr:@std/fmt/colors'; +import { expandGlob } from 'jsr:@std/fs'; +import { relative, resolve } from 'jsr:@std/path'; + +/** + * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry + * @type {WalkEntry[]} + */ +const entries = []; + +for await (const entry of expandGlob( + resolve(import.meta.dirname, './chapter_*/*.js') +)) { + entries.push(entry); +} + +/** @type {{ status: Promise; stderr: ReadableStream; }[]} */ +const processes = []; + +for (const file of entries) { + const execute = new Deno.Command('node', { + args: [relative(import.meta.dirname, file.path)], + cwd: import.meta.dirname, + stdin: 'piped', + stdout: 'piped', + stderr: 'piped', + }); + + const process = execute.spawn(); + processes.push({ status: process.status, stderr: process.stderr }); +} + +const results = await Promise.all( + processes.map(async (item) => { + const status = await item.status; + return { status, stderr: item.stderr }; + }) +); + +/** @type {ReadableStream[]} */ +const errors = []; + +for (const result of results) { + if (!result.status.success) { + errors.push(result.stderr); + } +} + +console.log(`Tested ${entries.length} files`); +console.log(`Found exception in ${errors.length} files`); + +if (errors.length) { + console.log(); + + for (const error of errors) { + const reader = error.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); + } + + throw new Error('Test failed'); +} diff --git a/ru/codes/kotlin/chapter_array_and_linkedlist/array.kt b/ru/codes/kotlin/chapter_array_and_linkedlist/array.kt new file mode 100644 index 000000000..4031bf16b --- /dev/null +++ b/ru/codes/kotlin/chapter_array_and_linkedlist/array.kt @@ -0,0 +1,102 @@ +/** + * File: array.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_array_and_linkedlist + +import java.util.concurrent.ThreadLocalRandom + +/* Случайный доступ к элементу */ +fun randomAccess(nums: IntArray): Int { + // Случайным образом выбрать число из интервала [0, nums.size) + val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) + // Получить и вернуть случайный элемент + val randomNum = nums[randomIndex] + return randomNum +} + +/* Увеличить длину массива */ +fun extend(nums: IntArray, enlarge: Int): IntArray { + // Инициализировать массив увеличенной длины + val res = IntArray(nums.size + enlarge) + // Скопировать все элементы исходного массива в новый массив + for (i in nums.indices) { + res[i] = nums[i] + } + // Вернуть новый массив после расширения + return res +} + +/* Вставить элемент num по индексу index в массив */ +fun insert(nums: IntArray, num: Int, index: Int) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (i in nums.size - 1 downTo index + 1) { + nums[i] = nums[i - 1] + } + // Присвоить num элементу по индексу index + nums[index] = num +} + +/* Удалить элемент по индексу index */ +fun remove(nums: IntArray, index: Int) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (i in index.. P -> n1 + val p = n0.next + val n1 = p?.next + n0.next = n1 +} + +/* Доступ к узлу связного списка по индексу index */ +fun access(head: ListNode?, index: Int): ListNode? { + var h = head + for (i in 0..= size) + throw IndexOutOfBoundsException("индекс выходит за границы") + return arr[index] + } + + /* Обновление элемента */ + fun set(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("индекс выходит за границы") + arr[index] = num + } + + /* Добавление элемента в конец */ + fun add(num: Int) { + // При превышении вместимости по числу элементов запускается расширение + if (size == capacity()) + extendCapacity() + arr[size] = num + // Обновить число элементов + size++ + } + + /* Вставка элемента в середину */ + fun insert(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("индекс выходит за границы") + // При превышении вместимости по числу элементов запускается расширение + if (size == capacity()) + extendCapacity() + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (j in size - 1 downTo index) + arr[j + 1] = arr[j] + arr[index] = num + // Обновить число элементов + size++ + } + + /* Удаление элемента */ + fun remove(index: Int): Int { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("индекс выходит за границы") + val num = arr[index] + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (j in index..>, + res: MutableList>?>, + cols: BooleanArray, + diags1: BooleanArray, + diags2: BooleanArray +) { + // Когда все строки уже обработаны, записать решение + if (row == n) { + val copyState = mutableListOf>() + for (sRow in state) { + copyState.add(sRow.toMutableList()) + } + res.add(copyState) + return + } + // Обойти все столбцы + for (col in 0..>?> { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + val state = mutableListOf>() + for (i in 0..() + for (j in 0..>?>() + + backtrack(0, n, state, res, cols, diags1, diags2) + + return res +} + +/* Driver Code */ +fun main() { + val n = 4 + val res = nQueens(n) + + println("Размер входной доски = $n") + println("Количество способов расстановки ферзей: ${res.size}") + for (state in res) { + println("--------------------") + for (row in state!!) { + println(row) + } + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/permutations_i.kt b/ru/codes/kotlin/chapter_backtracking/permutations_i.kt new file mode 100644 index 000000000..4901f4932 --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/permutations_i.kt @@ -0,0 +1,53 @@ +/** + * File: permutations_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_i + +/* Алгоритм бэктрекинга: все перестановки I */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // Когда длина состояния равна числу элементов, записать решение + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // Перебор всех вариантов выбора + for (i in choices.indices) { + val choice = choices[i] + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true + state.add(choice) + // Перейти к следующему выбору + backtrack(state, choices, selected, res) + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* Все перестановки I */ +fun permutationsI(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 3) + + val res = permutationsI(nums) + + println("Входной массив nums = ${nums.contentToString()}") + println("Все перестановки res = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/permutations_ii.kt b/ru/codes/kotlin/chapter_backtracking/permutations_ii.kt new file mode 100644 index 000000000..d44a8307c --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/permutations_ii.kt @@ -0,0 +1,54 @@ +/** + * File: permutations_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_ii + +/* Алгоритм бэктрекинга: все перестановки II */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // Когда длина состояния равна числу элементов, записать решение + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // Перебор всех вариантов выбора + val duplicated = HashSet() + for (i in choices.indices) { + val choice = choices[i] + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated.contains(choice)) { + // Попытка: сделать выбор и обновить состояние + duplicated.add(choice) // Записать значения уже выбранных элементов + selected[i] = true + state.add(choice) + // Перейти к следующему выбору + backtrack(state, choices, selected, res) + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* Все перестановки II */ +fun permutationsII(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 2) + val res = permutationsII(nums) + + println("Входной массив nums = ${nums.contentToString()}") + println("Все перестановки res = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt new file mode 100644 index 000000000..97e7c7d06 --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_i_compact + +import utils.TreeNode +import utils.printTree + +var res: MutableList? = null + +/* Предварительный обход: пример 1 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + if (root._val == 7) { + // Записать решение + res!!.add(root) + } + preOrder(root.left) + preOrder(root.right) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\nИнициализация двоичного дерева") + printTree(root) + + // Предварительный обход + res = mutableListOf() + preOrder(root) + + println("\nВсе узлы со значением 7") + val vals = mutableListOf() + for (node in res!!) { + vals.add(node._val) + } + println(vals) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt new file mode 100644 index 000000000..0771a7533 --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_ii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* Предварительный обход: пример 2 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + // Попытка + path!!.add(root) + if (root._val == 7) { + // Записать решение + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // Откат + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\nИнициализация двоичного дерева") + printTree(root) + + // Предварительный обход + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\nВсе пути от корня к узлу 7") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt new file mode 100644 index 000000000..e1a3e315f --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* Предварительный обход: пример 3 */ +fun preOrder(root: TreeNode?) { + // Отсечение + if (root == null || root._val == 3) { + return + } + // Попытка + path!!.add(root) + if (root._val == 7) { + // Записать решение + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // Откат + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\nИнициализация двоичного дерева") + printTree(root) + + // Предварительный обход + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt new file mode 100644 index 000000000..59bad00ba --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt @@ -0,0 +1,82 @@ +/** + * File: preorder_traversal_iii_template.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_template + +import utils.TreeNode +import utils.printTree + +/* Проверить, является ли текущее состояние решением */ +fun isSolution(state: MutableList): Boolean { + return state.isNotEmpty() && state[state.size - 1]?._val == 7 +} + +/* Записать решение */ +fun recordSolution(state: MutableList?, res: MutableList?>) { + res.add(state!!.toMutableList()) +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +fun isValid(state: MutableList?, choice: TreeNode?): Boolean { + return choice != null && choice._val != 3 +} + +/* Обновить состояние */ +fun makeChoice(state: MutableList, choice: TreeNode?) { + state.add(choice) +} + +/* Восстановить состояние */ +fun undoChoice(state: MutableList, choice: TreeNode?) { + state.removeLast() +} + +/* Алгоритм бэктрекинга: пример 3 */ +fun backtrack( + state: MutableList, + choices: MutableList, + res: MutableList?> +) { + // Проверить, является ли текущее состояние решением + if (isSolution(state)) { + // Записать решение + recordSolution(state, res) + } + // Перебор всех вариантов выбора + for (choice in choices) { + // Отсечение: проверить допустимость выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice) + // Перейти к следующему выбору + backtrack(state, mutableListOf(choice!!.left, choice.right), res) + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice) + } + } +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\nИнициализация двоичного дерева") + printTree(root) + + // Алгоритм бэктрекинга + val res = mutableListOf?>() + backtrack(mutableListOf(), mutableListOf(root), res) + + println("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3") + for (path in res) { + val vals = mutableListOf() + for (node in path!!) { + if (node != null) { + vals.add(node._val) + } + } + println(vals) + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/subset_sum_i.kt b/ru/codes/kotlin/chapter_backtracking/subset_sum_i.kt new file mode 100644 index 000000000..9d21737f0 --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/subset_sum_i.kt @@ -0,0 +1,58 @@ +/** + * File: subset_sum_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.add(state.toMutableList()) + return + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (i in start..?> { + val state = mutableListOf() // Состояние (подмножество) + nums.sort() // Отсортировать nums + val start = 0 // Стартовая вершина обхода + val res = mutableListOf?>() // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + + val res = subsetSumI(nums, target) + + println("Входной массив nums = ${nums.contentToString()}, target = $target") + println("Все подмножества с суммой $target: res = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt b/ru/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt new file mode 100644 index 000000000..98f1e6eb4 --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i_native.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i_naive + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +fun backtrack( + state: MutableList, + target: Int, + total: Int, + choices: IntArray, + res: MutableList?> +) { + // Если сумма подмножества равна target, записать решение + if (total == target) { + res.add(state.toMutableList()) + return + } + // Перебор всех вариантов выбора + for (i in choices.indices) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue + } + // Попытка: сделать выбор и обновить элемент и total + state.add(choices[i]) + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res) + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeAt(state.size - 1) + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // Состояние (подмножество) + val total = 0 // Сумма подмножеств + val res = mutableListOf?>() // Список результатов (список подмножеств) + backtrack(state, target, total, nums, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + val res = subsetSumINaive(nums, target) + + println("Входной массив nums = ${nums.contentToString()}, target = $target") + println("Все подмножества с суммой $target: res = $res") + println("Обратите внимание: результат этого метода содержит повторяющиеся множества") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_backtracking/subset_sum_ii.kt b/ru/codes/kotlin/chapter_backtracking/subset_sum_ii.kt new file mode 100644 index 000000000..57eef2baf --- /dev/null +++ b/ru/codes/kotlin/chapter_backtracking/subset_sum_ii.kt @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_ii + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // Если сумма подмножества равна target, записать решение + if (target == 0) { + res.add(state.toMutableList()) + return + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (i in start.. start && choices[i] == choices[i - 1]) { + continue + } + // Попытка: сделать выбор и обновить target и start + state.add(choices[i]) + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res) + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeAt(state.size - 1) + } +} + +/* Решить задачу суммы подмножеств II */ +fun subsetSumII(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // Состояние (подмножество) + nums.sort() // Отсортировать nums + val start = 0 // Стартовая вершина обхода + val res = mutableListOf?>() // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 4, 5) + val target = 9 + val res = subsetSumII(nums, target) + + println("Входной массив nums = ${nums.contentToString()}, target = $target") + println("Все подмножества с суммой $target: res = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_computational_complexity/iteration.kt b/ru/codes/kotlin/chapter_computational_complexity/iteration.kt new file mode 100644 index 000000000..972db258a --- /dev/null +++ b/ru/codes/kotlin/chapter_computational_complexity/iteration.kt @@ -0,0 +1,74 @@ +/** + * File: iteration.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.iteration + +/* Цикл for */ +fun forLoop(n: Int): Int { + var res = 0 + // Циклическое суммирование 1, 2, ..., n-1, n + for (i in 1..n) { + res += i + } + return res +} + +/* Цикл while */ +fun whileLoop(n: Int): Int { + var res = 0 + var i = 1 // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i + i++ // Обновить условную переменную + } + return res +} + +/* Цикл while (двойное обновление) */ +fun whileLoopII(n: Int): Int { + var res = 0 + var i = 1 // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i + // Обновить условную переменную + i++ + i *= 2 + } + return res +} + +/* Двойной цикл for */ +fun nestedForLoop(n: Int): String { + val res = StringBuilder() + // Цикл по i = 1, 2, ..., n-1, n + for (i in 1..n) { + // Цикл по j = 1, 2, ..., n-1, n + for (j in 1..n) { + res.append(" ($i, $j), ") + } + } + return res.toString() +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = forLoop(n) + println("\nРезультат суммирования в цикле for res = $res") + + res = whileLoop(n) + println("\nРезультат суммирования в цикле while res = $res") + + res = whileLoopII(n) + println("\nРезультат суммирования в цикле while (двойное обновление) res = $res") + + val resStr = nestedForLoop(n) + println("\nРезультат обхода в двойном цикле for $resStr") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_computational_complexity/recursion.kt b/ru/codes/kotlin/chapter_computational_complexity/recursion.kt new file mode 100644 index 000000000..babf945d2 --- /dev/null +++ b/ru/codes/kotlin/chapter_computational_complexity/recursion.kt @@ -0,0 +1,78 @@ +/** + * File: recursion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.recursion + +import java.util.* + +/* Рекурсия */ +fun recur(n: Int): Int { + // Условие завершения + if (n == 1) + return 1 + // Рекурсивный шаг: рекурсивный вызов + val res = recur(n - 1) + // Возврат: вернуть результат + return n + res +} + +/* Имитация рекурсии итерацией */ +fun forLoopRecur(n: Int): Int { + // Использовать явный стек для имитации системного стека вызовов + val stack = Stack() + var res = 0 + // Рекурсивный шаг: рекурсивный вызов + for (i in n downTo 0) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.push(i) + } + // Возврат: вернуть результат + while (stack.isNotEmpty()) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.pop() + } + // res = 1+2+3+...+n + return res +} + +/* Хвостовая рекурсия */ +tailrec fun tailRecur(n: Int, res: Int): Int { + // Добавить ключевое слово tailrec, чтобы включить оптимизацию хвостовой рекурсии + // Условие завершения + if (n == 0) + return res + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n) +} + +/* Последовательность Фибоначчи: рекурсия */ +fun fib(n: Int): Int { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1 + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + val res = fib(n - 1) + fib(n - 2) + // Вернуть результат f(n) + return res +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = recur(n) + println("\nРезультат суммирования в рекурсивной функции res = $res") + + res = forLoopRecur(n) + println("\nРезультат суммирования при имитации рекурсии итерацией res = $res") + + res = tailRecur(n, 0) + println("\nРезультат суммирования в хвостовой рекурсии res = $res") + + res = fib(n) + println("\nЧлен последовательности Фибоначчи с номером $n = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_computational_complexity/space_complexity.kt b/ru/codes/kotlin/chapter_computational_complexity/space_complexity.kt new file mode 100644 index 000000000..309934910 --- /dev/null +++ b/ru/codes/kotlin/chapter_computational_complexity/space_complexity.kt @@ -0,0 +1,109 @@ +/** + * File: space_complexity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.space_complexity + +import utils.ListNode +import utils.TreeNode +import utils.printTree + +/* Функция */ +fun function(): Int { + // Выполнить некоторые операции + return 0 +} + +/* Постоянная сложность */ +fun constant(n: Int) { + // Константы, переменные и объекты занимают O(1) памяти + val a = 0 + var b = 0 + val nums = Array(10000) { 0 } + val node = ListNode(0) + // Переменные в цикле занимают O(1) памяти + for (i in 0..() + for (i in 0..() + for (i in 0..?>(n) + // Двумерный список занимает O(n^2) памяти + val numList = mutableListOf>() + for (i in 0..() + for (j in 0.. nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + count += 3 // Обмен элементов включает 3 элементарные операции + } + } + } + return count +} + +/* Экспоненциальная сложность (итеративная реализация) */ +fun exponential(n: Int): Int { + var count = 0 + var base = 1 + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (i in 0.. 1) { + n1 /= 2 + count++ + } + return count +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +fun logRecur(n: Int): Int { + if (n <= 1) + return 0 + return logRecur(n / 2) + 1 +} + +/* Линейно-логарифмическая сложность */ +fun linearLogRecur(n: Int): Int { + if (n <= 1) + return 1 + var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) + for (i in 0.. { + val nums = IntArray(n) + // Создать массив nums = { 1, 2, 3, ..., n } + for (i in 0..(n) + for (i in 0..): Int { + for (i in nums.indices) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] == 1) + return i + } + return -1 +} + +/* Driver Code */ +fun main() { + for (i in 0..9) { + val n = 100 + val nums = randomNumbers(n) + val index = findOne(nums) + println("\nМассив [1, 2, ..., n] после перемешивания = ${nums.contentToString()}") + println("Индекс числа 1 = $index") + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt b/ru/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt new file mode 100644 index 000000000..a4e3d0d6f --- /dev/null +++ b/ru/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt @@ -0,0 +1,49 @@ +/** + * File: binary_search_recur.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.binary_search_recur + +/* Бинарный поиск: задача f(i, j) */ +fun dfs( + nums: IntArray, + target: Int, + i: Int, + j: Int +): Int { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1 + } + // Вычислить индекс середины m + val m = (i + j) / 2 + return if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + dfs(nums, target, m + 1, j) + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + dfs(nums, target, i, m - 1) + } else { + // Целевой элемент найден, вернуть его индекс + m + } +} + +/* Бинарный поиск */ +fun binarySearch(nums: IntArray, target: Int): Int { + val n = nums.size + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + // Бинарный поиск (двусторонне замкнутый интервал) + val index = binarySearch(nums, target) + println("Индекс целевого элемента 6 = $index") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_divide_and_conquer/build_tree.kt b/ru/codes/kotlin/chapter_divide_and_conquer/build_tree.kt new file mode 100644 index 000000000..8695ce7fc --- /dev/null +++ b/ru/codes/kotlin/chapter_divide_and_conquer/build_tree.kt @@ -0,0 +1,55 @@ +/** + * File: build_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.build_tree + +import utils.TreeNode +import utils.printTree + +/* Построить двоичное дерево: разделяй и властвуй */ +fun dfs( + preorder: IntArray, + inorderMap: Map, + i: Int, + l: Int, + r: Int +): TreeNode? { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) return null + // Инициализировать корневой узел + val root = TreeNode(preorder[i]) + // Найти m, чтобы разделить левое и правое поддеревья + val m = inorderMap[preorder[i]]!! + // Подзадача: построить левое поддерево + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) + // Подзадача: построить правое поддерево + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) + // Вернуть корневой узел + return root +} + +/* Построить двоичное дерево */ +fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + val inorderMap = HashMap() + for (i in inorder.indices) { + inorderMap[inorder[i]] = i + } + val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) + return root +} + +/* Driver Code */ +fun main() { + val preorder = intArrayOf(3, 9, 2, 1, 7) + val inorder = intArrayOf(9, 3, 1, 2, 7) + println("Предварительный обход = ${preorder.contentToString()}") + println("Симметричный обход = ${inorder.contentToString()}") + + val root = buildTree(preorder, inorder) + println("Построенное двоичное дерево:") + printTree(root) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_divide_and_conquer/hanota.kt b/ru/codes/kotlin/chapter_divide_and_conquer/hanota.kt new file mode 100644 index 000000000..dd1fdcc05 --- /dev/null +++ b/ru/codes/kotlin/chapter_divide_and_conquer/hanota.kt @@ -0,0 +1,56 @@ +/** + * File: hanota.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.hanota + +/* Переместить один диск */ +fun move(src: MutableList, tar: MutableList) { + // Снять диск с вершины src + val pan = src.removeAt(src.size - 1) + // Положить диск на вершину tar + tar.add(pan) +} + +/* Решить задачу Ханойской башни f(i) */ +fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { + // Если в src остался только один диск, сразу переместить его в tar + if (i == 1) { + move(src, tar) + return + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf) + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar) + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar) +} + +/* Решить задачу Ханойской башни */ +fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { + val n = A.size + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C) +} + +/* Driver Code */ +fun main() { + // Хвост списка соответствует вершине столбца + val A = mutableListOf(5, 4, 3, 2, 1) + val B = mutableListOf() + val C = mutableListOf() + println("Исходное состояние:") + println("A = $A") + println("B = $B") + println("C = $C") + + solveHanota(A, B, C) + + println("После завершения перемещения дисков:") + println("A = $A") + println("B = $B") + println("C = $C") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt new file mode 100644 index 000000000..e124e20c3 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt @@ -0,0 +1,45 @@ +/** + * File: climbing_stairs_backtrack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* Бэктрекинг */ +fun backtrack( + choices: MutableList, + state: Int, + n: Int, + res: MutableList +) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) + res[0] = res[0] + 1 + // Перебор всех вариантов выбора + for (choice in choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) continue + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res) + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +fun climbingStairsBacktrack(n: Int): Int { + val choices = mutableListOf(1, 2) // Можно подняться на 1 или 2 ступени + val state = 0 // Начать подъем с 0-й ступени + val res = mutableListOf() + res.add(0) // Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res) + return res[0] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsBacktrack(n) + println("Количество способов подняться по лестнице из $n ступеней = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt new file mode 100644 index 000000000..4892c0e0a --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt @@ -0,0 +1,35 @@ +/** + * File: climbing_stairs_constraint_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +fun climbingStairsConstraintDP(n: Int): Int { + if (n == 1 || n == 2) { + return 1 + } + // Инициализация таблицы dp для хранения решений подзадач + val dp = Array(n + 1) { IntArray(3) } + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // Переход состояний: постепенное решение больших подзадач через меньшие + for (i in 3..n) { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsConstraintDP(n) + println("Количество способов подняться по лестнице из $n ступеней = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt new file mode 100644 index 000000000..53805209d --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt @@ -0,0 +1,29 @@ +/** + * File: climbing_stairs_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* Поиск */ +fun dfs(i: Int): Int { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) return i + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1) + dfs(i - 2) + return count +} + +/* Подъем по лестнице: поиск */ +fun climbingStairsDFS(n: Int): Int { + return dfs(n) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFS(n) + println("Количество способов подняться по лестнице из $n ступеней = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt new file mode 100644 index 000000000..91895b683 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_dfs_mem.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* Поиск с мемоизацией */ +fun dfs(i: Int, mem: IntArray): Int { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 || i == 2) return i + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) return mem[i] + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1, mem) + dfs(i - 2, mem) + // Сохранить dp[i] + mem[i] = count + return count +} + +/* Подъем по лестнице: поиск с мемоизацией */ +fun climbingStairsDFSMem(n: Int): Int { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + val mem = IntArray(n + 1) + mem.fill(-1) + return dfs(n, mem) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFSMem(n) + println("Количество способов подняться по лестнице из $n ступеней = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt new file mode 100644 index 000000000..a2587b975 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* Подъем по лестнице: динамическое программирование */ +fun climbingStairsDP(n: Int): Int { + if (n == 1 || n == 2) return n + // Инициализация таблицы dp для хранения решений подзадач + val dp = IntArray(n + 1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1 + dp[2] = 2 + // Переход состояний: постепенное решение больших подзадач через меньшие + for (i in 3..n) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +fun climbingStairsDPComp(n: Int): Int { + if (n == 1 || n == 2) return n + var a = 1 + var b = 2 + for (i in 3..n) { + val temp = b + b += a + a = temp + } + return b +} + +/* Driver Code */ +fun main() { + val n = 9 + + var res = climbingStairsDP(n) + println("Количество способов подняться по лестнице из $n ступеней = $res") + + res = climbingStairsDPComp(n) + println("Количество способов подняться по лестнице из $n ступеней = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/coin_change.kt b/ru/codes/kotlin/chapter_dynamic_programming/coin_change.kt new file mode 100644 index 000000000..c192027fa --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/coin_change.kt @@ -0,0 +1,71 @@ +/** + * File: coin_change.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* Размен монет: динамическое программирование */ +fun coinChangeDP(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // Инициализация таблицы dp + val dp = Array(n + 1) { IntArray(amt + 1) } + // Переход состояний: первая строка и первый столбец + for (a in 1..amt) { + dp[0][a] = MAX + } + // Переход состояний: остальные строки и столбцы + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return if (dp[n][amt] != MAX) dp[n][amt] else -1 +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +fun coinChangeDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // Инициализация таблицы dp + val dp = IntArray(amt + 1) + dp.fill(MAX) + dp[0] = 0 + // Переход состояний + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return if (dp[amt] != MAX) dp[amt] else -1 +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 4 + + // Динамическое программирование + var res = coinChangeDP(coins, amt) + println("Минимальное число монет для набора целевой суммы = $res") + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins, amt) + println("Минимальное число монет для набора целевой суммы = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt b/ru/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt new file mode 100644 index 000000000..010381607 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* Размен монет II: динамическое программирование */ +fun coinChangeIIDP(coins: IntArray, amt: Int): Int { + val n = coins.size + // Инициализация таблицы dp + val dp = Array(n + 1) { IntArray(amt + 1) } + // Инициализация первого столбца + for (i in 0..n) { + dp[i][0] = 1 + } + // Переход состояний + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + // Инициализация таблицы dp + val dp = IntArray(amt + 1) + dp[0] = 1 + // Переход состояний + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 5 + + // Динамическое программирование + var res = coinChangeIIDP(coins, amt) + println("Количество комбинаций монет для набора целевой суммы = $res") + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(coins, amt) + println("Количество комбинаций монет для набора целевой суммы = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/edit_distance.kt b/ru/codes/kotlin/chapter_dynamic_programming/edit_distance.kt new file mode 100644 index 000000000..3f3752f54 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/edit_distance.kt @@ -0,0 +1,143 @@ +/** + * File: edit_distance.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* Редакционное расстояние: полный перебор */ +fun editDistanceDFS( + s: String, + t: String, + i: Int, + j: Int +): Int { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) return 0 + // Если s пусто, вернуть длину t + if (i == 0) return j + // Если t пусто, вернуть длину s + if (j == 0) return i + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + val insert = editDistanceDFS(s, t, i, j - 1) + val delete = editDistanceDFS(s, t, i - 1, j) + val replace = editDistanceDFS(s, t, i - 1, j - 1) + // Вернуть минимальное число шагов редактирования + return min(min(insert, delete), replace) + 1 +} + +/* Редакционное расстояние: поиск с мемоизацией */ +fun editDistanceDFSMem( + s: String, + t: String, + mem: Array, + i: Int, + j: Int +): Int { + // Если s и t пусты, вернуть 0 + if (i == 0 && j == 0) return 0 + // Если s пусто, вернуть длину t + if (i == 0) return j + // Если t пусто, вернуть длину s + if (j == 0) return i + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) return mem[i][j] + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + val insert = editDistanceDFSMem(s, t, mem, i, j - 1) + val delete = editDistanceDFSMem(s, t, mem, i - 1, j) + val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* Редакционное расстояние: динамическое программирование */ +fun editDistanceDP(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = Array(n + 1) { IntArray(m + 1) } + // Переход состояний: первая строка и первый столбец + for (i in 1..n) { + dp[i][0] = i + } + for (j in 1..m) { + dp[0][j] = j + } + // Переход состояний: остальные строки и столбцы + for (i in 1..n) { + for (j in 1..m) { + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1] + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +fun editDistanceDPComp(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = IntArray(m + 1) + // Переход состояний: первая строка + for (j in 1..m) { + dp[j] = j + } + // Переход состояний: остальные строки + for (i in 1..n) { + // Переход состояний: первый столбец + var leftup = dp[0] // Временно сохранить dp[i-1, j-1] + dp[0] = i + // Переход состояний: остальные столбцы + for (j in 1..m) { + val temp = dp[j] + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m] +} + +/* Driver Code */ +fun main() { + val s = "bag" + val t = "pack" + val n = s.length + val m = t.length + + // Полный перебор + var res = editDistanceDFS(s, t, n, m) + println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") + + // Поиск с мемоизацией + val mem = Array(n + 1) { IntArray(m + 1) } + for (row in mem) + row.fill(-1) + res = editDistanceDFSMem(s, t, mem, n, m) + println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") + + // Динамическое программирование + res = editDistanceDP(s, t) + println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t) + println("Чтобы преобразовать $s в $t, нужно минимум $res шагов") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/knapsack.kt b/ru/codes/kotlin/chapter_dynamic_programming/knapsack.kt new file mode 100644 index 000000000..0ac960621 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/knapsack.kt @@ -0,0 +1,125 @@ +/** + * File: knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.max + +/* Рюкзак 0-1: полный перебор */ +fun knapsackDFS( + wgt: IntArray, + _val: IntArray, + i: Int, + c: Int +): Int { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0 + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, _val, i - 1, c) + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + val no = knapsackDFS(wgt, _val, i - 1, c) + val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] + // Вернуть вариант с большей стоимостью из двух возможных + return max(no, yes) +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +fun knapsackDFSMem( + wgt: IntArray, + _val: IntArray, + mem: Array, + i: Int, + c: Int +): Int { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 || c == 0) { + return 0 + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c] + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, _val, mem, i - 1, c) + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) + val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* Рюкзак 0-1: динамическое программирование */ +fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // Инициализация таблицы dp + val dp = Array(n + 1) { IntArray(cap + 1) } + // Переход состояний + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // Инициализация таблицы dp + val dp = IntArray(cap + 1) + // Переход состояний + for (i in 1..n) { + // Обход в обратном порядке + for (c in cap downTo 1) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + val n = wgt.size + + // Полный перебор + var res = knapsackDFS(wgt, _val, n, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") + + // Поиск с мемоизацией + val mem = Array(n + 1) { IntArray(cap + 1) } + for (row in mem) { + row.fill(-1) + } + res = knapsackDFSMem(wgt, _val, mem, n, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") + + // Динамическое программирование + res = knapsackDP(wgt, _val, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt, _val, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt b/ru/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt new file mode 100644 index 000000000..11f039e9e --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +fun minCostClimbingStairsDP(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + // Инициализация таблицы dp для хранения решений подзадач + val dp = IntArray(n + 1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1] + dp[2] = cost[2] + // Переход состояний: постепенное решение больших подзадач через меньшие + for (i in 3..n) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +fun minCostClimbingStairsDPComp(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + var a = cost[1] + var b = cost[2] + for (i in 3..n) { + val tmp = b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} + +/* Driver Code */ +fun main() { + val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) + println("Список стоимостей ступеней = ${cost.contentToString()}") + + var res = minCostClimbingStairsDP(cost) + println("Минимальная стоимость подъема по лестнице = $res") + + res = minCostClimbingStairsDPComp(cost) + println("Минимальная стоимость подъема по лестнице = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt b/ru/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt new file mode 100644 index 000000000..2e512f115 --- /dev/null +++ b/ru/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* Минимальная сумма пути: полный перебор */ +fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0] + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + val up = minPathSumDFS(grid, i - 1, j) + val left = minPathSumDFS(grid, i, j - 1) + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return min(left, up) + grid[i][j] +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +fun minPathSumDFSMem( + grid: Array, + mem: Array, + i: Int, + j: Int +): Int { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 && j == 0) { + return grid[0][0] + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j] + } + // Минимальная стоимость пути для левой и верхней ячеек + val up = minPathSumDFSMem(grid, mem, i - 1, j) + val left = minPathSumDFSMem(grid, mem, i, j - 1) + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* Минимальная сумма пути: динамическое программирование */ +fun minPathSumDP(grid: Array): Int { + val n = grid.size + val m = grid[0].size + // Инициализация таблицы dp + val dp = Array(n) { IntArray(m) } + dp[0][0] = grid[0][0] + // Переход состояний: первая строка + for (j in 1..): Int { + val n = grid.size + val m = grid[0].size + // Инициализация таблицы dp + val dp = IntArray(m) + // Переход состояний: первая строка + dp[0] = grid[0][0] + for (j in 1.. c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +fun unboundedKnapsackDPComp( + wgt: IntArray, + _val: IntArray, + cap: Int +): Int { + val n = wgt.size + // Инициализация таблицы dp + val dp = IntArray(cap + 1) + // Переход состояний + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(1, 2, 3) + val _val = intArrayOf(5, 11, 15) + val cap = 4 + + // Динамическое программирование + var res = unboundedKnapsackDP(wgt, _val, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(wgt, _val, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_graph/graph_adjacency_list.kt b/ru/codes/kotlin/chapter_graph/graph_adjacency_list.kt new file mode 100644 index 000000000..5276b4936 --- /dev/null +++ b/ru/codes/kotlin/chapter_graph/graph_adjacency_list.kt @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* Класс неориентированного графа на основе списка смежности */ +class GraphAdjList(edges: Array>) { + // Список смежности, где key — вершина, а value — все смежные ей вершины + val adjList = HashMap>() + + /* Конструктор */ + init { + // Добавить все вершины и ребра + for (edge in edges) { + addVertex(edge[0]!!) + addVertex(edge[1]!!) + addEdge(edge[0]!!, edge[1]!!) + } + } + + /* Получить число вершин */ + fun size(): Int { + return adjList.size + } + + /* Добавление ребра */ + fun addEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // Добавить ребро vet1 - vet2 + adjList[vet1]?.add(vet2) + adjList[vet2]?.add(vet1) + } + + /* Удаление ребра */ + fun removeEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // Удалить ребро vet1 - vet2 + adjList[vet1]?.remove(vet2) + adjList[vet2]?.remove(vet1) + } + + /* Добавление вершины */ + fun addVertex(vet: Vertex) { + if (adjList.containsKey(vet)) + return + // Добавить новый список в список смежности + adjList[vet] = mutableListOf() + } + + /* Удаление вершины */ + fun removeVertex(vet: Vertex) { + if (!adjList.containsKey(vet)) + throw IllegalArgumentException() + // Удалить из списка смежности список, соответствующий вершине vet + adjList.remove(vet) + // Обойти списки других вершин и удалить все ребра, содержащие vet + for (list in adjList.values) { + list.remove(vet) + } + } + + /* Вывести список смежности */ + fun print() { + println("Список смежности =") + for (pair in adjList.entries) { + val tmp = mutableListOf() + for (vertex in pair.value) { + tmp.add(vertex._val) + } + println("${pair.key._val}: $tmp,") + } + } +} + +/* Driver Code */ +fun main() { + /* Инициализация неориентированного графа */ + val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[3]), + arrayOf(v[2], v[4]), + arrayOf(v[3], v[4]) + ) + val graph = GraphAdjList(edges) + println("\nГраф после инициализации") + graph.print() + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(v[0]!!, v[2]!!) + println("\nГраф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(v[0]!!, v[1]!!) + println("\nГраф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + val v5 = Vertex(6) + graph.addVertex(v5) + println("\nГраф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(v[1]!!) + println("\nГраф после удаления вершины 3") + graph.print() +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt b/ru/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt new file mode 100644 index 000000000..80df8b20c --- /dev/null +++ b/ru/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.printMatrix + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat(vertices: IntArray, edges: Array) { + val vertices = mutableListOf() // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + val adjMat = mutableListOf>() // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + init { + // Добавление вершины + for (vertex in vertices) { + addVertex(vertex) + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for (edge in edges) { + addEdge(edge[0], edge[1]) + } + } + + /* Получить число вершин */ + fun size(): Int { + return vertices.size + } + + /* Добавление вершины */ + fun addVertex(_val: Int) { + val n = size() + // Добавить значение новой вершины в список вершин + vertices.add(_val) + // Добавить строку в матрицу смежности + val newRow = mutableListOf() + for (j in 0..= size()) + throw IndexOutOfBoundsException() + // Удалить вершину с индексом index из списка вершин + vertices.removeAt(index) + // Удалить строку с индексом index из матрицы смежности + adjMat.removeAt(index) + // Удалить столбец с индексом index из матрицы смежности + for (row in adjMat) { + row.removeAt(index) + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + fun addEdge(i: Int, j: Int) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + fun removeEdge(i: Int, j: Int) { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* Вывести матрицу смежности */ + fun print() { + print("Список вершин = ") + println(vertices) + println("Матрица смежности =") + printMatrix(adjMat) + } +} + +/* Driver Code */ +fun main() { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + val vertices = intArrayOf(1, 3, 2, 5, 4) + val edges = arrayOf( + intArrayOf(0, 1), + intArrayOf(0, 3), + intArrayOf(1, 2), + intArrayOf(2, 3), + intArrayOf(2, 4), + intArrayOf(3, 4) + ) + val graph = GraphAdjMat(vertices, edges) + println("\nГраф после инициализации") + graph.print() + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.addEdge(0, 2) + println("\nГраф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.removeEdge(0, 1) + println("\nГраф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + graph.addVertex(6) + println("\nГраф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.removeVertex(1) + println("\nГраф после удаления вершины 3") + graph.print() +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_graph/graph_bfs.kt b/ru/codes/kotlin/chapter_graph/graph_bfs.kt new file mode 100644 index 000000000..29cd1f1ef --- /dev/null +++ b/ru/codes/kotlin/chapter_graph/graph_bfs.kt @@ -0,0 +1,65 @@ +/** + * File: graph_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex +import java.util.* + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { + // Последовательность обхода вершин + val res = mutableListOf() + // Хеш-множество для хранения уже посещенных вершин + val visited = HashSet() + visited.add(startVet) + // Очередь используется для реализации BFS + val que = LinkedList() + que.offer(startVet) + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (!que.isEmpty()) { + val vet = que.poll() // Извлечь головную вершину из очереди + res.add(vet) // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // Пропустить уже посещенную вершину + que.offer(adjVet) // Помещать в очередь только непосещенные вершины + visited.add(adjVet) // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res +} + +/* Driver Code */ +fun main() { + /* Инициализация неориентированного графа */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[1], v[4]), + arrayOf(v[2], v[5]), + arrayOf(v[3], v[4]), + arrayOf(v[3], v[6]), + arrayOf(v[4], v[5]), + arrayOf(v[4], v[7]), + arrayOf(v[5], v[8]), + arrayOf(v[6], v[7]), + arrayOf(v[7], v[8]) + ) + val graph = GraphAdjList(edges) + println("\nГраф после инициализации") + graph.print() + + /* Обход в ширину */ + val res = graphBFS(graph, v[0]!!) + println("\nПоследовательность вершин при обходе в ширину (BFS)") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_graph/graph_dfs.kt b/ru/codes/kotlin/chapter_graph/graph_dfs.kt new file mode 100644 index 000000000..9010cbef0 --- /dev/null +++ b/ru/codes/kotlin/chapter_graph/graph_dfs.kt @@ -0,0 +1,60 @@ +/** + * File: graph_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* Вспомогательная функция обхода в глубину */ +fun dfs( + graph: GraphAdjList, + visited: MutableSet, + res: MutableList, + vet: Vertex? +) { + res.add(vet) // Отметить посещенную вершину + visited.add(vet) // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // Пропустить уже посещенную вершину + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet) + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { + // Последовательность обхода вершин + val res = mutableListOf() + // Хеш-множество для хранения уже посещенных вершин + val visited = HashSet() + dfs(graph, visited, res, startVet) + return res +} + +/* Driver Code */ +fun main() { + /* Инициализация неориентированного графа */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[5]), + arrayOf(v[4], v[5]), + arrayOf(v[5], v[6]) + ) + val graph = GraphAdjList(edges) + println("\nГраф после инициализации") + graph.print() + + /* Обход в глубину */ + val res = graphDFS(graph, v[0]) + println("\nПоследовательность вершин при обходе в глубину (DFS)") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_greedy/coin_change_greedy.kt b/ru/codes/kotlin/chapter_greedy/coin_change_greedy.kt new file mode 100644 index 000000000..b8bd4cd8c --- /dev/null +++ b/ru/codes/kotlin/chapter_greedy/coin_change_greedy.kt @@ -0,0 +1,53 @@ +/** + * File: coin_change_greedy.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* Размен монет: жадный алгоритм */ +fun coinChangeGreedy(coins: IntArray, amt: Int): Int { + // Предположить, что список coins упорядочен + var am = amt + var i = coins.size - 1 + var count = 0 + // Циклически выполнять жадный выбор, пока не останется суммы + while (am > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > am) { + i-- + } + // Выбрать coins[i] + am -= coins[i] + count++ + } + // Если допустимое решение не найдено, вернуть -1 + return if (am == 0) count else -1 +} + +/* Driver Code */ +fun main() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + var coins = intArrayOf(1, 5, 10, 20, 50, 100) + var amt = 186 + var res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("Минимальное число монет для набора суммы $amt = $res") + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = intArrayOf(1, 20, 50) + amt = 60 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("Минимальное число монет для набора суммы $amt = $res") + println("На самом деле минимум равен 3: 20 + 20 + 20") + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = intArrayOf(1, 49, 50) + amt = 98 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("Минимальное число монет для набора суммы $amt = $res") + println("На самом деле минимум равен 2: 49 + 49") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_greedy/fractional_knapsack.kt b/ru/codes/kotlin/chapter_greedy/fractional_knapsack.kt new file mode 100644 index 000000000..392a5d6f3 --- /dev/null +++ b/ru/codes/kotlin/chapter_greedy/fractional_knapsack.kt @@ -0,0 +1,51 @@ +/** + * File: fractional_knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* Предмет */ +class Item( + val w: Int, // Предмет + val v: Int // Стоимость предмета +) + +/* Дробный рюкзак: жадный алгоритм */ +fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { + // Создать список предметов с двумя свойствами: вес и стоимость + var cap = c + val items = arrayOfNulls(wgt.size) + for (i in wgt.indices) { + items[i] = Item(wgt[i], _val[i]) + } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } + // Циклический жадный выбор + var res = 0.0 + for (item in items) { + if (item!!.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v + cap -= item.w + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += item.v.toDouble() / item.w * cap + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break + } + } + return res +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + + // Жадный алгоритм + val res = fractionalKnapsack(wgt, _val, cap) + println("Максимальная стоимость предметов без превышения вместимости рюкзака = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_greedy/max_capacity.kt b/ru/codes/kotlin/chapter_greedy/max_capacity.kt new file mode 100644 index 000000000..6691163c6 --- /dev/null +++ b/ru/codes/kotlin/chapter_greedy/max_capacity.kt @@ -0,0 +1,41 @@ +/** + * File: max_capacity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.max +import kotlin.math.min + +/* Максимальная вместимость: жадный алгоритм */ +fun maxCapacity(ht: IntArray): Int { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + var i = 0 + var j = ht.size - 1 + // Начальная максимальная вместимость равна 0 + var res = 0 + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + val cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i++ + } else { + j-- + } + } + return res +} + +/* Driver Code */ +fun main() { + val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) + + // Жадный алгоритм + val res = maxCapacity(ht) + println("Максимальная вместимость = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_greedy/max_product_cutting.kt b/ru/codes/kotlin/chapter_greedy/max_product_cutting.kt new file mode 100644 index 000000000..8292abaf9 --- /dev/null +++ b/ru/codes/kotlin/chapter_greedy/max_product_cutting.kt @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.pow + +/* Максимальное произведение разрезания: жадный алгоритм */ +fun maxProductCutting(n: Int): Int { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1) + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + val a = n / 3 + val b = n % 3 + if (b == 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return 3.0.pow((a - 1)).toInt() * 2 * 2 + } + if (b == 2) { + // Если остаток равен 2, ничего не делать + return 3.0.pow(a).toInt() * 2 * 2 + } + // Если остаток равен 0, ничего не делать + return 3.0.pow(a).toInt() +} + +/* Driver Code */ +fun main() { + val n = 58 + + // Жадный алгоритм + val res = maxProductCutting(n) + println("Максимальное произведение после разрезания = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_hashing/array_hash_map.kt b/ru/codes/kotlin/chapter_hashing/array_hash_map.kt new file mode 100644 index 000000000..f0a0d638e --- /dev/null +++ b/ru/codes/kotlin/chapter_hashing/array_hash_map.kt @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* Пара ключ-значение */ +class Pair( + var key: Int, + var _val: String +) + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + // Инициализировать массив, содержащий 100 корзин + private val buckets = arrayOfNulls(100) + + /* Хеш-функция */ + fun hashFunc(key: Int): Int { + val index = key % 100 + return index + } + + /* Операция поиска */ + fun get(key: Int): String? { + val index = hashFunc(key) + val pair = buckets[index] ?: return null + return pair._val + } + + /* Операция добавления */ + fun put(key: Int, _val: String) { + val pair = Pair(key, _val) + val index = hashFunc(key) + buckets[index] = pair + } + + /* Операция удаления */ + fun remove(key: Int) { + val index = hashFunc(key) + // Присвоить null, что означает удаление + buckets[index] = null + } + + /* Получить все пары ключ-значение */ + fun pairSet(): MutableList { + val pairSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + pairSet.add(pair) + } + return pairSet + } + + /* Получить все ключи */ + fun keySet(): MutableList { + val keySet = mutableListOf() + for (pair in buckets) { + if (pair != null) + keySet.add(pair.key) + } + return keySet + } + + /* Получить все значения */ + fun valueSet(): MutableList { + val valueSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + valueSet.add(pair._val) + } + return valueSet + } + + /* Вывести хеш-таблицу */ + fun print() { + for (kv in pairSet()) { + val key = kv.key + val _val = kv._val + println("$key -> $_val") + } + } +} + +/* Driver Code */ +fun main() { + /* Инициализация хеш-таблицы */ + val map = ArrayHashMap() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха") + map.put(15937, "Сяо Ло") + map.put(16750, "Сяо Суань") + map.put(13276, "Сяо Фа") + map.put(10583, "Сяо Я") + println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + val name = map.get(15937) + println("\nДля номера 15937 найдено имя $name") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583) + println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Обход хеш-таблицы */ + println("\nОтдельный обход пар ключ-значение") + for (kv in map.pairSet()) { + println("${kv.key} -> ${kv._val}") + } + println("\nОтдельный обход ключей") + for (key in map.keySet()) { + println(key) + } + println("\nОтдельный обход значений") + for (_val in map.valueSet()) { + println(_val) + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_hashing/built_in_hash.kt b/ru/codes/kotlin/chapter_hashing/built_in_hash.kt new file mode 100644 index 000000000..ce05bed1f --- /dev/null +++ b/ru/codes/kotlin/chapter_hashing/built_in_hash.kt @@ -0,0 +1,36 @@ +/** + * File: built_in_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.ListNode + +/* Driver Code */ +fun main() { + val num = 3 + val hashNum = num.hashCode() + println("Хеш-значение целого числа $num = $hashNum") + + val bol = true + val hashBol = bol.hashCode() + println("Хеш-значение булева значения $bol = $hashBol") + + val dec = 3.14159 + val hashDec = dec.hashCode() + println("Хеш-значение десятичного числа $dec = $hashDec") + + val str = "Hello Algo" + val hashStr = str.hashCode() + println("Хеш-значение строки $str = $hashStr") + + val arr = arrayOf(12836, "Сяо Ха") + val hashTup = arr.contentHashCode() + println("Хеш-значение массива ${arr.contentToString()} = $hashTup") + + val obj = ListNode(0) + val hashObj = obj.hashCode() + println("Хеш-значение объекта узла $obj = $hashObj") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_hashing/hash_map.kt b/ru/codes/kotlin/chapter_hashing/hash_map.kt new file mode 100644 index 000000000..b5bd934be --- /dev/null +++ b/ru/codes/kotlin/chapter_hashing/hash_map.kt @@ -0,0 +1,50 @@ +/** + * File: hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.printHashMap + +/* Driver Code */ +fun main() { + /* Инициализация хеш-таблицы */ + val map = HashMap() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map[12836] = "Сяо Ха" + map[15937] = "Сяо Ло" + map[16750] = "Сяо Суань" + map[13276] = "Сяо Фа" + map[10583] = "Сяо Я" + println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + printHashMap(map) + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + val name = map[15937] + println("\nДля номера 15937 найдено имя $name") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583) + println("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + printHashMap(map) + + /* Обход хеш-таблицы */ + println("\nОтдельный обход пар ключ-значение") + for ((key, value) in map) { + println("$key -> $value") + } + println("\nОтдельный обход ключей") + for (key in map.keys) { + println(key) + } + println("\nОтдельный обход значений") + for (_val in map.values) { + println(_val) + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_hashing/hash_map_chaining.kt b/ru/codes/kotlin/chapter_hashing/hash_map_chaining.kt new file mode 100644 index 000000000..a1141fc8b --- /dev/null +++ b/ru/codes/kotlin/chapter_hashing/hash_map_chaining.kt @@ -0,0 +1,145 @@ +/** + * File: hash_map_chaining.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + var size: Int // Число пар ключ-значение + var capacity: Int // Вместимость хеш-таблицы + val loadThres: Double // Порог коэффициента загрузки для запуска расширения + val extendRatio: Int // Коэффициент расширения + var buckets: MutableList> // Массив корзин + + /* Конструктор */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = mutableListOf() + for (i in 0.. loadThres) { + extend() + } + val index = hashFunc(key) + val bucket = buckets[index] + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for (pair in bucket) { + if (pair.key == key) { + pair._val = _val + return + } + } + // Если такого key нет, добавить пару ключ-значение в конец + val pair = Pair(key, _val) + bucket.add(pair) + size++ + } + + /* Операция удаления */ + fun remove(key: Int) { + val index = hashFunc(key) + val bucket = buckets[index] + // Обойти корзину и удалить из нее пару ключ-значение + for (pair in bucket) { + if (pair.key == key) { + bucket.remove(pair) + size-- + break + } + } + } + + /* Расширить хеш-таблицу */ + fun extend() { + // Временно сохранить исходную хеш-таблицу + val bucketsTmp = buckets + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio + // mutablelist не имеет фиксированного размера + buckets = mutableListOf() + for (i in 0..() + for (pair in bucket) { + val k = pair.key + val v = pair._val + res.add("$k -> $v") + } + println(res) + } + } +} + +/* Driver Code */ +fun main() { + /* Инициализация хеш-таблицы */ + val map = HashMapChaining() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха") + map.put(15937, "Сяо Ло") + map.put(16750, "Сяо Суань") + map.put(13276, "Сяо Фа") + map.put(10583, "Сяо Я") + println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + val name = map.get(13276) + println("\nДля номера 13276 найдено имя $name") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(12836) + println("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение") + map.print() +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt b/ru/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt new file mode 100644 index 000000000..f9f73a9aa --- /dev/null +++ b/ru/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt @@ -0,0 +1,161 @@ +/** + * File: hash_map_open_addressing.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + private var size: Int // Число пар ключ-значение + private var capacity: Int // Вместимость хеш-таблицы + private val loadThres: Double // Порог коэффициента загрузки для запуска расширения + private val extendRatio: Int // Коэффициент расширения + private var buckets: Array // Массив корзин + private val TOMBSTONE: Pair // Удалить метку + + /* Конструктор */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = arrayOfNulls(capacity) + TOMBSTONE = Pair(-1, "-1") + } + + /* Хеш-функция */ + fun hashFunc(key: Int): Int { + return key % capacity + } + + /* Коэффициент загрузки */ + fun loadFactor(): Double { + return (size / capacity).toDouble() + } + + /* Найти индекс корзины, соответствующий key */ + fun findBucket(key: Int): Int { + var index = hashFunc(key) + var firstTombstone = -1 + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (buckets[index] != null) { + // Если встретился key, вернуть соответствующий индекс корзины + if (buckets[index]?.key == key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // Вернуть индекс корзины после перемещения + } + return index // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % capacity + } + // Если key не существует, вернуть индекс точки добавления + return if (firstTombstone == -1) index else firstTombstone + } + + /* Операция поиска */ + fun get(key: Int): String? { + // Найти индекс корзины, соответствующий key + val index = findBucket(key) + // Если пара ключ-значение найдена, вернуть соответствующее val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index]?._val + } + // Если пары ключ-значение не существует, вернуть null + return null + } + + /* Операция добавления */ + fun put(key: Int, _val: String) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (loadFactor() > loadThres) { + extend() + } + // Найти индекс корзины, соответствующий key + val index = findBucket(key) + // Если пара ключ-значение найдена, перезаписать val и вернуть + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index]!!._val = _val + return + } + // Если пары ключ-значение нет, добавить ее + buckets[index] = Pair(key, _val) + size++ + } + + /* Операция удаления */ + fun remove(key: Int) { + // Найти индекс корзины, соответствующий key + val index = findBucket(key) + // Если пара ключ-значение найдена, заменить ее меткой удаления + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE + size-- + } + } + + /* Расширить хеш-таблицу */ + fun extend() { + // Временно сохранить исходную хеш-таблицу + val bucketsTmp = buckets + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio + buckets = arrayOfNulls(capacity) + size = 0 + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair._val) + } + } + } + + /* Вывести хеш-таблицу */ + fun print() { + for (pair in buckets) { + if (pair == null) { + println("null") + } else if (pair == TOMBSTONE) { + println("TOMESTOME") + } else { + println("${pair.key} -> ${pair._val}") + } + } + } +} + +/* Driver Code */ +fun main() { + // Инициализация хеш-таблицы + val hashmap = HashMapOpenAddressing() + + // Операция добавления + // Добавить пару (key, val) в хеш-таблицу + hashmap.put(12836, "Сяо Ха") + hashmap.put(15937, "Сяо Ло") + hashmap.put(16750, "Сяо Суань") + hashmap.put(13276, "Сяо Фа") + hashmap.put(10583, "Сяо Я") + println("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + hashmap.print() + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение val + val name = hashmap.get(13276) + println("\nДля номера 13276 найдено имя $name") + + // Операция удаления + // Удалить пару (key, val) из хеш-таблицы + hashmap.remove(16750) + println("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") + hashmap.print() +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_hashing/simple_hash.kt b/ru/codes/kotlin/chapter_hashing/simple_hash.kt new file mode 100644 index 000000000..1c00107ec --- /dev/null +++ b/ru/codes/kotlin/chapter_hashing/simple_hash.kt @@ -0,0 +1,64 @@ +/** + * File: simple_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* Аддитивное хеширование */ +fun addHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* Мультипликативное хеширование */ +fun mulHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (31 * hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* XOR-хеширование */ +fun xorHash(key: String): Int { + var hash = 0 + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = hash xor c.code + } + return hash and MODULUS +} + +/* Хеширование с циклическим сдвигом */ +fun rotHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS + } + return hash.toInt() +} + +/* Driver Code */ +fun main() { + val key = "Hello Algo" + + var hash = addHash(key) + println("Хеш-сумма сложением = $hash") + + hash = mulHash(key) + println("Хеш-сумма умножением = $hash") + + hash = xorHash(key) + println("Хеш-сумма XOR = $hash") + + hash = rotHash(key) + println("Хеш-сумма с циклическим сдвигом = $hash") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_heap/heap.kt b/ru/codes/kotlin/chapter_heap/heap.kt new file mode 100644 index 000000000..418b63912 --- /dev/null +++ b/ru/codes/kotlin/chapter_heap/heap.kt @@ -0,0 +1,66 @@ +/** + * File: heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +fun testPush(heap: Queue, _val: Int) { + heap.offer(_val) // Добавление элемента в кучу + print("\nПосле добавления элемента $_val в кучу\n") + printHeap(heap) +} + +fun testPop(heap: Queue) { + val _val = heap.poll() // Извлечение элемента с вершины кучи + print("\nПосле извлечения элемента вершины кучи $_val\n") + printHeap(heap) +} + +/* Driver Code */ +fun main() { + /* Инициализация кучи */ + // Инициализация минимальной кучи + var minHeap = PriorityQueue() + + // Инициализация максимальной кучи (достаточно изменить Comparator с помощью lambda-выражения) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + println("\nНиже приведен тестовый пример для max-heap") + + /* Добавление элемента в кучу */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* Получение элемента с вершины кучи */ + val peek = maxHeap.peek() + print("\nЭлемент на вершине кучи = $peek\n") + + /* Извлечение элемента с вершины кучи */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* Получение размера кучи */ + val size = maxHeap.size + print("\nКоличество элементов в куче = $size\n") + + /* Проверка, пуста ли куча */ + val isEmpty = maxHeap.isEmpty() + print("\nПуста ли куча: $isEmpty\n") + + /* Построить кучу по входному списку */ + // Временная сложность равна O(n), а не O(nlogn) + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + println("\nПосле построения min-heap из входного списка") + printHeap(minHeap) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_heap/my_heap.kt b/ru/codes/kotlin/chapter_heap/my_heap.kt new file mode 100644 index 000000000..f349f2671 --- /dev/null +++ b/ru/codes/kotlin/chapter_heap/my_heap.kt @@ -0,0 +1,160 @@ +/** + * File: my_heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* Максимальная куча */ +class MaxHeap(nums: MutableList?) { + // Использовать список вместо массива, чтобы не учитывать проблему расширения + private val maxHeap = mutableListOf() + + /* Конструктор, строящий кучу по входному списку */ + init { + // Добавить элементы списка в кучу без изменений + maxHeap.addAll(nums!!) + // Выполнить heapify для всех узлов, кроме листовых + for (i in parent(size() - 1) downTo 0) { + siftDown(i) + } + } + + /* Получить индекс левого дочернего узла */ + private fun left(i: Int): Int { + return 2 * i + 1 + } + + /* Получить индекс правого дочернего узла */ + private fun right(i: Int): Int { + return 2 * i + 2 + } + + /* Получить индекс родительского узла */ + private fun parent(i: Int): Int { + return (i - 1) / 2 // Округление вниз при делении + } + + /* Поменять элементы местами */ + private fun swap(i: Int, j: Int) { + val temp = maxHeap[i] + maxHeap[i] = maxHeap[j] + maxHeap[j] = temp + } + + /* Получение размера кучи */ + fun size(): Int { + return maxHeap.size + } + + /* Проверка, пуста ли куча */ + fun isEmpty(): Boolean { + /* Проверка, пуста ли куча */ + return size() == 0 + } + + /* Доступ к элементу на вершине кучи */ + fun peek(): Int { + return maxHeap[0] + } + + /* Добавление элемента в кучу */ + fun push(_val: Int) { + // Добавление узла + maxHeap.add(_val) + // Просеивание снизу вверх + siftUp(size() - 1) + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + private fun siftUp(it: Int) { + // Параметры функций в Kotlin неизменяемы, поэтому создается временная переменная + var i = it + while (true) { + // Получение родительского узла для узла i + val p = parent(i) + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // Поменять два узла местами + swap(i, p) + // Циклическое просеивание вверх + i = p + } + } + + /* Извлечение элемента из кучи */ + fun pop(): Int { + // Обработка пустого случая + if (isEmpty()) throw IndexOutOfBoundsException() + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(0, size() - 1) + // Удаление узла + val _val = maxHeap.removeAt(size() - 1) + // Просеивание сверху вниз + siftDown(0) + // Вернуть элемент с вершины кучи + return _val + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + private fun siftDown(it: Int) { + // Параметры функций в Kotlin неизменяемы, поэтому создается временная переменная + var i = it + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) break + // Поменять два узла местами + swap(i, ma) + // Циклическое просеивание вниз + i = ma + } + } + + /* Вывести кучу (двоичное дерево) */ + fun print() { + val queue = PriorityQueue { a: Int, b: Int -> b - a } + queue.addAll(maxHeap) + printHeap(queue) + } +} + +/* Driver Code */ +fun main() { + /* Инициализация максимальной кучи */ + val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) + println("\nПосле построения кучи из входного списка") + maxHeap.print() + + /* Получение элемента с вершины кучи */ + var peek = maxHeap.peek() + print("\nЭлемент на вершине кучи = $peek\n") + + /* Добавление элемента в кучу */ + val _val = 7 + maxHeap.push(_val) + print("\nПосле добавления элемента $_val в кучу\n") + maxHeap.print() + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop() + print("\nПосле извлечения элемента вершины кучи $peek\n") + maxHeap.print() + + /* Получение размера кучи */ + val size = maxHeap.size() + print("\nКоличество элементов в куче = $size\n") + + /* Проверка, пуста ли куча */ + val isEmpty = maxHeap.isEmpty() + print("\nПуста ли куча: $isEmpty\n") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_heap/top_k.kt b/ru/codes/kotlin/chapter_heap/top_k.kt new file mode 100644 index 000000000..df82d7084 --- /dev/null +++ b/ru/codes/kotlin/chapter_heap/top_k.kt @@ -0,0 +1,38 @@ +/** + * File: top_k.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* Найти k наибольших элементов массива с помощью кучи */ +fun topKHeap(nums: IntArray, k: Int): Queue { + // Инициализация минимальной кучи + val heap = PriorityQueue() + // Поместить первые k элементов массива в кучу + for (i in 0.. heap.peek()) { + heap.poll() + heap.offer(nums[i]) + } + } + return heap +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 7, 6, 3, 2) + val k = 3 + val res = topKHeap(nums, k) + println("Наибольшие $k элементов") + printHeap(res) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_searching/binary_search.kt b/ru/codes/kotlin/chapter_searching/binary_search.kt new file mode 100644 index 000000000..8883f7053 --- /dev/null +++ b/ru/codes/kotlin/chapter_searching/binary_search.kt @@ -0,0 +1,59 @@ +/** + * File: binary_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +fun binarySearch(nums: IntArray, target: Int): Int { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + var i = 0 + var j = nums.size - 1 + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + val m = i + (j - i) / 2 // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j] + i = m + 1 + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m-1] + j = m - 1 + else // Целевой элемент найден, вернуть его индекс + return m + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +fun binarySearchLCRO(nums: IntArray, target: Int): Int { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + var i = 0 + var j = nums.size + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + val m = i + (j - i) / 2 // Вычислить индекс середины m + if (nums[m] < target) // Это означает, что target находится в интервале [m+1, j) + i = m + 1 + else if (nums[m] > target) // Это означает, что target находится в интервале [i, m) + j = m + else // Целевой элемент найден, вернуть его индекс + return m + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + var index = binarySearch(nums, target) + println("Индекс целевого элемента 6 = $index") + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = binarySearchLCRO(nums, target) + println("Индекс целевого элемента 6 = $index") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_searching/binary_search_edge.kt b/ru/codes/kotlin/chapter_searching/binary_search_edge.kt new file mode 100644 index 000000000..8d18bc8e5 --- /dev/null +++ b/ru/codes/kotlin/chapter_searching/binary_search_edge.kt @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* Бинарный поиск самого левого target */ +fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { + // Эквивалентно поиску точки вставки target + val i = binarySearchInsertion(nums, target) + // target не найден, вернуть -1 + if (i == nums.size || nums[i] != target) { + return -1 + } + // Найти target и вернуть индекс i + return i +} + +/* Бинарный поиск самого правого target */ +fun binarySearchRightEdge(nums: IntArray, target: Int): Int { + // Преобразовать задачу в поиск самого левого target + 1 + val i = binarySearchInsertion(nums, target + 1) + // j указывает на самый правый target, а i — на первый элемент больше target + val j = i - 1 + // target не найден, вернуть -1 + if (j == -1 || nums[j] != target) { + return -1 + } + // Найти target и вернуть индекс j + return j +} + +/* Driver Code */ +fun main() { + // Массив с повторяющимися элементами + val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\nМассив nums = ${nums.contentToString()}") + + // Бинарный поиск левой и правой границы + for (target in intArrayOf(6, 7)) { + var index = binarySearchLeftEdge(nums, target) + println("Индекс самого левого элемента $target равен $index") + index = binarySearchRightEdge(nums, target) + println("Индекс самого правого элемента $target равен $index") + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_searching/binary_search_insertion.kt b/ru/codes/kotlin/chapter_searching/binary_search_insertion.kt new file mode 100644 index 000000000..6e5710701 --- /dev/null +++ b/ru/codes/kotlin/chapter_searching/binary_search_insertion.kt @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + val m = i + (j - i) / 2 // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1 // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1 // target находится в интервале [i, m-1] + } else { + return m // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +fun binarySearchInsertion(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + val m = i + (j - i) / 2 // Вычислить индекс середины m + if (nums[m] < target) { + i = m + 1 // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1 // target находится в интервале [i, m-1] + } else { + j = m - 1 // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i +} + +/* Driver Code */ +fun main() { + // Массив без повторяющихся элементов + var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + println("\nМассив nums = ${nums.contentToString()}") + // Бинарный поиск точки вставки + for (target in intArrayOf(6, 9)) { + val index = binarySearchInsertionSimple(nums, target) + println("Индекс позиции вставки элемента $target равен $index") + } + + // Массив с повторяющимися элементами + nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\nМассив nums = ${nums.contentToString()}") + + // Бинарный поиск точки вставки + for (target in intArrayOf(2, 6, 20)) { + val index = binarySearchInsertion(nums, target) + println("Индекс позиции вставки элемента $target равен $index") + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_searching/hashing_search.kt b/ru/codes/kotlin/chapter_searching/hashing_search.kt new file mode 100644 index 000000000..604d322dd --- /dev/null +++ b/ru/codes/kotlin/chapter_searching/hashing_search.kt @@ -0,0 +1,49 @@ +/** + * File: hashing_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* Хеш-поиск (массив) */ +fun hashingSearchArray(map: Map, target: Int): Int { + // key хеш-таблицы: целевой элемент, _val: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + return map.getOrDefault(target, -1) +} + +/* Хеш-поиск (связный список) */ +fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { + // key хеш-таблицы: значение целевого узла, _val: объект узла + // Если такого key нет в хеш-таблице, вернуть null + return map.getOrDefault(target, null) +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* Хеш-поиск (массив) */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + // Инициализация хеш-таблицы + val map = HashMap() + for (i in nums.indices) { + map[nums[i]] = i // key: элемент, _val: индекс + } + val index = hashingSearchArray(map, target) + println("Индекс целевого элемента 3 = $index") + + /* Хеш-поиск (связный список) */ + var head = ListNode.arrToLinkedList(nums) + // Инициализация хеш-таблицы + val map1 = HashMap() + while (head != null) { + map1[head._val] = head // key: значение узла, _val: узел + head = head.next + } + val node = hashingSearchLinkedList(map1, target) + println("Объект узла со значением 3 = $node") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_searching/linear_search.kt b/ru/codes/kotlin/chapter_searching/linear_search.kt new file mode 100644 index 000000000..b522e5d08 --- /dev/null +++ b/ru/codes/kotlin/chapter_searching/linear_search.kt @@ -0,0 +1,50 @@ +/** + * File: linear_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* Линейный поиск (массив) */ +fun linearSearchArray(nums: IntArray, target: Int): Int { + // Обход массива + for (i in nums.indices) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] == target) + return i + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Линейный поиск (связный список) */ +fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { + // Обойти связный список + var head = h + while (head != null) { + // Найти целевой узел и вернуть его + if (head._val == target) + return head + head = head.next + } + // Целевой узел не найден, вернуть null + return null +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* Выполнить линейный поиск в массиве */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + val index = linearSearchArray(nums, target) + println("Индекс целевого элемента 3 = $index") + + /* Выполнить линейный поиск в связном списке */ + val head = ListNode.arrToLinkedList(nums) + val node = linearSearchLinkedList(head, target) + println("Объект узла со значением 3 = $node") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_searching/two_sum.kt b/ru/codes/kotlin/chapter_searching/two_sum.kt new file mode 100644 index 000000000..2fcbbe3fa --- /dev/null +++ b/ru/codes/kotlin/chapter_searching/two_sum.kt @@ -0,0 +1,49 @@ +/** + * File: two_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* Метод 1: полный перебор */ +fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { + val size = nums.size + // Два вложенных цикла, временная сложность O(n^2) + for (i in 0..() + // Один цикл, временная сложность O(n) + for (i in 0.. nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +fun bubbleSortWithFlag(nums: IntArray) { + // Внешний цикл: неотсортированный диапазон [0, i] + for (i in nums.size - 1 downTo 1) { + var flag = false // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (j in 0.. nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + flag = true // Записать обмен элементов + } + } + if (!flag) break // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSort(nums) + println("После пузырьковой сортировки nums = ${nums.contentToString()}") + + val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSortWithFlag(nums1) + println("После пузырьковой сортировки nums1 = ${nums1.contentToString()}") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_sorting/bucket_sort.kt b/ru/codes/kotlin/chapter_sorting/bucket_sort.kt new file mode 100644 index 000000000..e06d5ba5a --- /dev/null +++ b/ru/codes/kotlin/chapter_sorting/bucket_sort.kt @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* Сортировка корзинами */ +fun bucketSort(nums: FloatArray) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + val k = nums.size / 2 + val buckets = mutableListOf>() + for (i in 0.. nums[ma]) + ma = l + if (r < n && nums[r] > nums[ma]) + ma = r + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) + break + // Поменять два узла местами + val temp = nums[i] + nums[i] = nums[ma] + nums[ma] = temp + // Циклическое просеивание вниз + i = ma + } +} + +/* Сортировка кучей */ +fun heapSort(nums: IntArray) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (i in nums.size / 2 - 1 downTo 0) { + siftDown(nums, nums.size, i) + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (i in nums.size - 1 downTo 1) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + val temp = nums[0] + nums[0] = nums[i] + nums[i] = temp + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0) + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + heapSort(nums) + println("После сортировки кучей nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_sorting/insertion_sort.kt b/ru/codes/kotlin/chapter_sorting/insertion_sort.kt new file mode 100644 index 000000000..33bcb1d0f --- /dev/null +++ b/ru/codes/kotlin/chapter_sorting/insertion_sort.kt @@ -0,0 +1,29 @@ +/** + * File: insertion_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* Сортировка вставками */ +fun insertionSort(nums: IntArray) { + // Внешний цикл: отсортированные элементы равны 1, 2, ..., n + for (i in nums.indices) { + val base = nums[i] + var j = i - 1 + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j] // Сдвинуть nums[j] на одну позицию вправо + j-- + } + nums[j + 1] = base // Поместить base в правильную позицию + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + insertionSort(nums) + println("После сортировки вставками nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_sorting/merge_sort.kt b/ru/codes/kotlin/chapter_sorting/merge_sort.kt new file mode 100644 index 000000000..3a0d3b64b --- /dev/null +++ b/ru/codes/kotlin/chapter_sorting/merge_sort.kt @@ -0,0 +1,56 @@ +/** + * File: merge_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* Объединить левый и правый подмассивы */ +fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + val tmp = IntArray(right - left + 1) + // Инициализировать начальные индексы левого и правого подмассивов + var i = left + var j = mid + 1 + var k = 0 + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++] + else + tmp[k++] = nums[j++] + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++] + } + while (j <= right) { + tmp[k++] = nums[j++] + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (l in tmp.indices) { + nums[left + l] = tmp[l] + } +} + +/* Сортировка слиянием */ +fun mergeSort(nums: IntArray, left: Int, right: Int) { + // Условие завершения + if (left >= right) return // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + val mid = left + (right - left) / 2 // Вычислить середину + mergeSort(nums, left, mid) // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right) // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right) +} + +/* Driver Code */ +fun main() { + /* Сортировка слиянием */ + val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) + mergeSort(nums, 0, nums.size - 1) + println("После сортировки слиянием nums = ${nums.contentToString()}") +} diff --git a/ru/codes/kotlin/chapter_sorting/quick_sort.kt b/ru/codes/kotlin/chapter_sorting/quick_sort.kt new file mode 100644 index 000000000..16df5080a --- /dev/null +++ b/ru/codes/kotlin/chapter_sorting/quick_sort.kt @@ -0,0 +1,121 @@ +/** + * File: quick_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* Обмен элементов */ +fun swap(nums: IntArray, i: Int, j: Int) { + val temp = nums[i] + nums[i] = nums[j] + nums[j] = temp +} + +/* Разбиение с опорными указателями */ +fun partition(nums: IntArray, left: Int, right: Int): Int { + // Взять nums[left] в качестве опорного элемента + var i = left + var j = right + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j-- // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++ // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j) // Поменять эти два элемента местами + } + swap(nums, i, left) // Переместить опорный элемент на границу двух подмассивов + return i // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка */ +fun quickSort(nums: IntArray, left: Int, right: Int) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return + // Разбиение с опорными указателями + val pivot = partition(nums, left, right) + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* Выбрать медиану из трех кандидатов */ +fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { + val l = nums[left] + val m = nums[mid] + val r = nums[right] + if ((m in l..r) || (m in r..l)) + return mid // m находится между l и r + if ((l in m..r) || (l in r..m)) + return left // l находится между m и r + return right +} + +/* Разбиение с опорными указателями (медиана трех) */ +fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { + // Выбрать медиану из трех кандидатов + val med = medianThree(nums, left, (left + right) / 2, right) + // Переместить медиану в крайний левый элемент массива + swap(nums, left, med) + // Взять nums[left] в качестве опорного элемента + var i = left + var j = right + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j-- // Идти справа налево в поисках первого элемента меньше опорного + while (i < j && nums[i] <= nums[left]) + i++ // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j) // Поменять эти два элемента местами + } + swap(nums, i, left) // Переместить опорный элемент на границу двух подмассивов + return i // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка */ +fun quickSortMedian(nums: IntArray, left: Int, right: Int) { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return + // Разбиение с опорными указателями + val pivot = partitionMedian(nums, left, right) + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { + // Завершить, когда длина подмассива равна 1 + var l = left + var r = right + while (l < r) { + // Операция разбиения с опорными указателями + val pivot = partition(nums, l, r) + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - l < r - pivot) { + quickSort(nums, l, pivot - 1) // Рекурсивно отсортировать левый подмассив + l = pivot + 1 // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, r) // Рекурсивно отсортировать правый подмассив + r = pivot - 1 // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } +} + +/* Driver Code */ +fun main() { + /* Быстрая сортировка */ + val nums = intArrayOf(2, 4, 1, 0, 3, 5) + quickSort(nums, 0, nums.size - 1) + println("После быстрой сортировки nums = ${nums.contentToString()}") + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortMedian(nums1, 0, nums1.size - 1) + println("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = ${nums1.contentToString()}") + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortTailCall(nums2, 0, nums2.size - 1) + println("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = ${nums2.contentToString()}") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_sorting/radix_sort.kt b/ru/codes/kotlin/chapter_sorting/radix_sort.kt new file mode 100644 index 000000000..e5219d589 --- /dev/null +++ b/ru/codes/kotlin/chapter_sorting/radix_sort.kt @@ -0,0 +1,68 @@ +/** + * File: radix_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +fun digit(num: Int, exp: Int): Int { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num / exp) % 10 +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +fun countingSortDigit(nums: IntArray, exp: Int) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + val counter = IntArray(10) + val n = nums.size + // Подсчитать число появлений каждой цифры от 0 до 9 + for (i in 0.. m) m = num + var exp = 1 + // Проходить разряды от младшего к старшему + while (exp <= m) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp) + exp *= 10 + } +} + +/* Driver Code */ +fun main() { + // Поразрядная сортировка + val nums = intArrayOf( + 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 + ) + radixSort(nums) + println("После поразрядной сортировки nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_sorting/selection_sort.kt b/ru/codes/kotlin/chapter_sorting/selection_sort.kt new file mode 100644 index 000000000..a85478cda --- /dev/null +++ b/ru/codes/kotlin/chapter_sorting/selection_sort.kt @@ -0,0 +1,32 @@ +/** + * File: selection_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* Сортировка выбором */ +fun selectionSort(nums: IntArray) { + val n = nums.size + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (i in 0..() + + /* Получение длины стека */ + fun size(): Int { + return stack.size + } + + /* Проверка, пуст ли стек */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* Поместить в стек */ + fun push(num: Int) { + stack.add(num) + } + + /* Извлечь из стека */ + fun pop(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack.removeAt(size() - 1) + } + + /* Доступ к верхнему элементу стека */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack[size() - 1] + } + + /* Преобразовать List в Array и вернуть */ + fun toArray(): Array { + return stack.toTypedArray() + } +} + +/* Driver Code */ +fun main() { + /* Инициализация стека */ + val stack = ArrayStack() + + /* Помещение элемента в стек */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("Стек stack = ${stack.toArray().contentToString()}") + + /* Доступ к верхнему элементу стека */ + val peek = stack.peek() + println("Верхний элемент peek = $peek") + + /* Извлечение элемента из стека */ + val pop = stack.pop() + println("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toArray().contentToString()}") + + /* Получение длины стека */ + val size = stack.size() + println("Длина стека size = $size") + + /* Проверка на пустоту */ + val isEmpty = stack.isEmpty() + println("Пуст ли стек = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_stack_and_queue/deque.kt b/ru/codes/kotlin/chapter_stack_and_queue/deque.kt new file mode 100644 index 000000000..7ca3d51cb --- /dev/null +++ b/ru/codes/kotlin/chapter_stack_and_queue/deque.kt @@ -0,0 +1,45 @@ +/** + * File: deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* Инициализация двусторонней очереди */ + val deque = LinkedList() + deque.offerLast(3) + deque.offerLast(2) + deque.offerLast(5) + println("Двусторонняя очередь deque = $deque") + + /* Доступ к элементу */ + val peekFirst = deque.peekFirst() + println("Первый элемент peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("Последний элемент peekLast = $peekLast") + + /* Добавление элемента в очередь */ + deque.offerLast(4) + println("После добавления элемента 4 в хвост deque = $deque") + deque.offerFirst(1) + println("После добавления элемента 1 в голову deque = $deque") + + /* Извлечение элемента из очереди */ + val popLast = deque.pollLast() + println("Извлеченный из хвоста элемент = $popLast, deque после извлечения из хвоста = $deque") + val popFirst = deque.pollFirst() + println("Извлеченный из головы элемент = $popFirst, deque после извлечения из головы = $deque") + + /* Получение длины двусторонней очереди */ + val size = deque.size + println("Длина двусторонней очереди size = $size") + + /* Проверка, пуста ли двусторонняя очередь */ + val isEmpty = deque.isEmpty() + println("Пуста ли двусторонняя очередь = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt b/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt new file mode 100644 index 000000000..60ee5381d --- /dev/null +++ b/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt @@ -0,0 +1,163 @@ +/** + * File: linkedlist_deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* Узел двусвязного списка */ +class ListNode(var _val: Int) { + // Значение узла + var next: ListNode? = null // Ссылка на узел-преемник + var prev: ListNode? = null // Ссылка на узел-предшественник +} + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + private var front: ListNode? = null // Головной узел front + private var rear: ListNode? = null // Хвостовой узел rear + private var queSize: Int = 0 // Длина двусторонней очереди + + /* Получение длины двусторонней очереди */ + fun size(): Int { + return queSize + } + + /* Проверка, пуста ли двусторонняя очередь */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* Операция добавления в очередь */ + fun push(num: Int, isFront: Boolean) { + val node = ListNode(num) + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (isEmpty()) { + rear = node + front = rear + // Операция добавления в голову очереди + } else if (isFront) { + // Добавить node в голову списка + front?.prev = node + node.next = front + front = node // Обновить головной узел + // Операция добавления в хвост очереди + } else { + // Добавить node в хвост списка + rear?.next = node + node.prev = rear + rear = node // Обновить хвостовой узел + } + queSize++ // Обновить длину очереди + } + + /* Добавление в голову очереди */ + fun pushFirst(num: Int) { + push(num, true) + } + + /* Добавление в хвост очереди */ + fun pushLast(num: Int) { + push(num, false) + } + + /* Операция извлечения из очереди */ + fun pop(isFront: Boolean): Int { + if (isEmpty()) + throw IndexOutOfBoundsException() + val _val: Int + // Операция извлечения из головы очереди + if (isFront) { + _val = front!!._val // Временно сохранить значение головного узла + // Удалить головной узел + val fNext = front!!.next + if (fNext != null) { + fNext.prev = null + front!!.next = null + } + front = fNext // Обновить головной узел + // Операция извлечения из хвоста очереди + } else { + _val = rear!!._val // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + val rPrev = rear!!.prev + if (rPrev != null) { + rPrev.next = null + rear!!.prev = null + } + rear = rPrev // Обновить хвостовой узел + } + queSize-- // Обновить длину очереди + return _val + } + + /* Извлечение из головы очереди */ + fun popFirst(): Int { + return pop(true) + } + + /* Извлечение из хвоста очереди */ + fun popLast(): Int { + return pop(false) + } + + /* Доступ к элементу в начале очереди */ + fun peekFirst(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* Доступ к элементу в конце очереди */ + fun peekLast(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return rear!!._val + } + + /* Вернуть массив для вывода */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* Инициализация двусторонней очереди */ + val deque = LinkedListDeque() + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + println("Двусторонняя очередь deque = ${deque.toArray().contentToString()}") + + /* Доступ к элементу */ + val peekFirst = deque.peekFirst() + println("Первый элемент peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("Последний элемент peekLast = $peekLast") + + /* Добавление элемента в очередь */ + deque.pushLast(4) + println("После добавления элемента 4 в хвост deque = ${deque.toArray().contentToString()}") + deque.pushFirst(1) + println("После добавления элемента 1 в голову deque = ${deque.toArray().contentToString()}") + + /* Извлечение элемента из очереди */ + val popLast = deque.popLast() + println("Извлеченный из хвоста элемент = ${popLast}, deque после извлечения из хвоста = ${deque.toArray().contentToString()}") + val popFirst = deque.popFirst() + println("Извлеченный из головы элемент = ${popFirst}, deque после извлечения из головы = ${deque.toArray().contentToString()}") + + /* Получение длины двусторонней очереди */ + val size = deque.size() + println("Длина двусторонней очереди size = $size") + + /* Проверка, пуста ли двусторонняя очередь */ + val isEmpty = deque.isEmpty() + println("Пуста ли двусторонняя очередь = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt b/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt new file mode 100644 index 000000000..cfa2ad477 --- /dev/null +++ b/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt @@ -0,0 +1,98 @@ +/** + * File: linkedlist_queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* Очередь на основе связного списка */ +class LinkedListQueue( + // Головной узел front, хвостовой узел rear + private var front: ListNode? = null, + private var rear: ListNode? = null, + private var queSize: Int = 0 +) { + + /* Получение длины очереди */ + fun size(): Int { + return queSize + } + + /* Проверка, пуста ли очередь */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* Поместить в очередь */ + fun push(num: Int) { + // Добавить num после хвостового узла + val node = ListNode(num) + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (front == null) { + front = node + rear = node + // Если очередь не пуста, добавить этот узел после хвостового узла + } else { + rear?.next = node + rear = node + } + queSize++ + } + + /* Извлечь из очереди */ + fun pop(): Int { + val num = peek() + // Удалить головной узел + front = front?.next + queSize-- + return num + } + + /* Доступ к элементу в начале очереди */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* Преобразовать связный список в Array и вернуть */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* Инициализация очереди */ + val queue = LinkedListQueue() + + /* Добавление элемента в очередь */ + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + println("Очередь queue = ${queue.toArray().contentToString()}") + + /* Доступ к элементу в начале очереди */ + val peek = queue.peek() + println("Первый элемент peek = $peek") + + /* Извлечение элемента из очереди */ + val pop = queue.pop() + println("Извлеченный элемент pop = $pop, queue после извлечения = ${queue.toArray().contentToString()}") + + /* Получение длины очереди */ + val size = queue.size() + println("Длина очереди size = $size") + + /* Проверка, пуста ли очередь */ + val isEmpty = queue.isEmpty() + println("Пуста ли очередь = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt b/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt new file mode 100644 index 000000000..ab4e41a73 --- /dev/null +++ b/ru/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt @@ -0,0 +1,87 @@ +/** + * File: linkedlist_stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* Стек на основе связного списка */ +class LinkedListStack( + private var stackPeek: ListNode? = null, // Использовать головной узел как вершину стека + private var stkSize: Int = 0 // Длина стека +) { + + /* Получение длины стека */ + fun size(): Int { + return stkSize + } + + /* Проверка, пуст ли стек */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* Поместить в стек */ + fun push(num: Int) { + val node = ListNode(num) + node.next = stackPeek + stackPeek = node + stkSize++ + } + + /* Извлечь из стека */ + fun pop(): Int? { + val num = peek() + stackPeek = stackPeek?.next + stkSize-- + return num + } + + /* Доступ к верхнему элементу стека */ + fun peek(): Int? { + if (isEmpty()) throw IndexOutOfBoundsException() + return stackPeek?._val + } + + /* Преобразовать List в Array и вернуть */ + fun toArray(): IntArray { + var node = stackPeek + val res = IntArray(size()) + for (i in res.size - 1 downTo 0) { + res[i] = node?._val!! + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* Инициализация стека */ + val stack = LinkedListStack() + + /* Помещение элемента в стек */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("Стек stack = ${stack.toArray().contentToString()}") + + /* Доступ к верхнему элементу стека */ + val peek = stack.peek()!! + println("Верхний элемент peek = $peek") + + /* Извлечение элемента из стека */ + val pop = stack.pop()!! + println("Извлеченный элемент pop = $pop, stack после извлечения = ${stack.toArray().contentToString()}") + + /* Получение длины стека */ + val size = stack.size() + println("Длина стека size = $size") + + /* Проверка на пустоту */ + val isEmpty = stack.isEmpty() + println("Пуст ли стек = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_stack_and_queue/queue.kt b/ru/codes/kotlin/chapter_stack_and_queue/queue.kt new file mode 100644 index 000000000..08013d55c --- /dev/null +++ b/ru/codes/kotlin/chapter_stack_and_queue/queue.kt @@ -0,0 +1,39 @@ +/** + * File: queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* Инициализация очереди */ + val queue = LinkedList() + + /* Добавление элемента в очередь */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + println("Очередь queue = $queue") + + /* Доступ к элементу в начале очереди */ + val peek = queue.peek() + println("Первый элемент peek = $peek") + + /* Извлечение элемента из очереди */ + val pop = queue.poll() + println("Извлеченный элемент pop = $pop, queue после извлечения = $queue") + + /* Получение длины очереди */ + val size = queue.size + println("Длина очереди size = $size") + + /* Проверка, пуста ли очередь */ + val isEmpty = queue.isEmpty() + println("Пуста ли очередь = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_stack_and_queue/stack.kt b/ru/codes/kotlin/chapter_stack_and_queue/stack.kt new file mode 100644 index 000000000..0acd3db8c --- /dev/null +++ b/ru/codes/kotlin/chapter_stack_and_queue/stack.kt @@ -0,0 +1,39 @@ +/** + * File: stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* Инициализация стека */ + val stack = Stack() + + /* Помещение элемента в стек */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("Стек stack = $stack") + + /* Доступ к верхнему элементу стека */ + val peek = stack.peek() + println("Верхний элемент peek = $peek") + + /* Извлечение элемента из стека */ + val pop = stack.pop() + println("Извлеченный элемент pop = $pop, stack после извлечения = $stack") + + /* Получение длины стека */ + val size = stack.size + println("Длина стека size = $size") + + /* Проверка на пустоту */ + val isEmpty = stack.isEmpty() + println("Пуст ли стек = $isEmpty") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_tree/array_binary_tree.kt b/ru/codes/kotlin/chapter_tree/array_binary_tree.kt new file mode 100644 index 000000000..11a5e33af --- /dev/null +++ b/ru/codes/kotlin/chapter_tree/array_binary_tree.kt @@ -0,0 +1,127 @@ +/** + * File: array_binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree(private val tree: MutableList) { + /* Вместимость списка */ + fun size(): Int { + return tree.size + } + + /* Получить значение узла с индексом i */ + fun _val(i: Int): Int? { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if (i < 0 || i >= size()) return null + return tree[i] + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* Получить индекс родительского узла узла с индексом i */ + fun parent(i: Int): Int { + return (i - 1) / 2 + } + + /* Обход в ширину */ + fun levelOrder(): MutableList { + val res = mutableListOf() + // Непосредственно обходить массив + for (i in 0..) { + // Если это пустая позиция, вернуть + if (_val(i) == null) + return + // Предварительный обход + if ("pre" == order) + res.add(_val(i)) + dfs(left(i), order, res) + // Симметричный обход + if ("in" == order) + res.add(_val(i)) + dfs(right(i), order, res) + // Обратный обход + if ("post" == order) + res.add(_val(i)) + } + + /* Предварительный обход */ + fun preOrder(): MutableList { + val res = mutableListOf() + dfs(0, "pre", res) + return res + } + + /* Симметричный обход */ + fun inOrder(): MutableList { + val res = mutableListOf() + dfs(0, "in", res) + return res + } + + /* Обратный обход */ + fun postOrder(): MutableList { + val res = mutableListOf() + dfs(0, "post", res) + return res + } +} + +/* Driver Code */ +fun main() { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из списка + val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) + + val root = TreeNode.listToTree(arr) + println("\nИнициализация двоичного дерева\n") + println("Массивное представление двоичного дерева:") + println(arr) + println("Связное представление двоичного дерева:") + printTree(root) + + // Класс двоичного дерева в массивном представлении + val abt = ArrayBinaryTree(arr) + + // Доступ к узлу + val i = 1 + val l = abt.left(i) + val r = abt.right(i) + val p = abt.parent(i) + println("Текущий узел: индекс = $i, значение = ${abt._val(i)}") + println("Индекс левого дочернего узла = $l, значение = ${abt._val(l)}") + println("Индекс правого дочернего узла = $r, значение = ${abt._val(r)}") + println("Индекс родительского узла = $p, значение = ${abt._val(p)}") + + // Обходить дерево + var res = abt.levelOrder() + println("\nОбход в ширину = $res") + res = abt.preOrder() + println("Предварительный обход = $res") + res = abt.inOrder() + println("Симметричный обход = $res") + res = abt.postOrder() + println("Обратный обход = $res") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_tree/avl_tree.kt b/ru/codes/kotlin/chapter_tree/avl_tree.kt new file mode 100644 index 000000000..5394734b7 --- /dev/null +++ b/ru/codes/kotlin/chapter_tree/avl_tree.kt @@ -0,0 +1,223 @@ +/** + * File: avl_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import kotlin.math.max + +/* AVL-дерево */ +class AVLTree { + var root: TreeNode? = null // Корневой узел + + /* Получить высоту узла */ + fun height(node: TreeNode?): Int { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node?.height ?: -1 + } + + /* Обновить высоту узла */ + private fun updateHeight(node: TreeNode?) { + // Высота узла равна высоте более высокого поддерева + 1 + node?.height = max(height(node?.left), height(node?.right)) + 1 + } + + /* Получить коэффициент баланса */ + fun balanceFactor(node: TreeNode?): Int { + // Коэффициент баланса пустого узла равен 0 + if (node == null) return 0 + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return height(node.left) - height(node.right) + } + + /* Операция правого вращения */ + private fun rightRotate(node: TreeNode?): TreeNode { + val child = node!!.left + val grandChild = child!!.right + // Выполнить правое вращение узла node вокруг child + child.right = node + node.left = grandChild + // Обновить высоту узла + updateHeight(node) + updateHeight(child) + // Вернуть корневой узел поддерева после вращения + return child + } + + /* Операция левого вращения */ + private fun leftRotate(node: TreeNode?): TreeNode { + val child = node!!.right + val grandChild = child!!.left + // Выполнить левое вращение узла node вокруг child + child.left = node + node.right = grandChild + // Обновить высоту узла + updateHeight(node) + updateHeight(child) + // Вернуть корневой узел поддерева после вращения + return child + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + private fun rotate(node: TreeNode): TreeNode { + // Получить коэффициент баланса узла node + val balanceFactor = balanceFactor(node) + // Левосторонне перекошенное дерево + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // Правое вращение + return rightRotate(node) + } else { + // Сначала левое вращение, затем правое + node.left = leftRotate(node.left) + return rightRotate(node) + } + } + // Правосторонне перекошенное дерево + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // Левое вращение + return leftRotate(node) + } else { + // Сначала правое вращение, затем левое + node.right = rightRotate(node.right) + return leftRotate(node) + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node + } + + /* Вставка узла */ + fun insert(_val: Int) { + root = insertHelper(root, _val) + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { + if (n == null) + return TreeNode(_val) + var node = n + /* 1. Найти позицию вставки и вставить узел */ + if (_val < node._val) + node.left = insertHelper(node.left, _val) + else if (_val > node._val) + node.right = insertHelper(node.right, _val) + else + return node // Повторяющийся узел не вставлять, сразу вернуть + updateHeight(node) // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node) + // Вернуть корневой узел поддерева + return node + } + + /* Удаление узла */ + fun remove(_val: Int) { + root = removeHelper(root, _val) + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { + var node = n ?: return null + /* 1. Найти узел и удалить его */ + if (_val < node._val) + node.left = removeHelper(node.left, _val) + else if (_val > node._val) + node.right = removeHelper(node.right, _val) + else { + if (node.left == null || node.right == null) { + val child = if (node.left != null) + node.left + else + node.right + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == null) + return null + // Число дочерних узлов = 1, удалить node напрямую + else + node = child + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + var temp = node.right + while (temp!!.left != null) { + temp = temp.left + } + node.right = removeHelper(node.right, temp._val) + node._val = temp._val + } + } + updateHeight(node) // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node) + // Вернуть корневой узел поддерева + return node + } + + /* Поиск узла */ + fun search(_val: Int): TreeNode? { + var cur = root + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + cur = if (cur._val < _val) + cur.right!! + // Целевой узел находится в левом поддереве cur + else if (cur._val > _val) + cur.left + // Найти целевой узел и выйти из цикла + else + break + } + // Вернуть целевой узел + return cur + } +} + +fun testInsert(tree: AVLTree, _val: Int) { + tree.insert(_val) + println("\nПосле вставки узла $_val AVL-дерево имеет вид") + printTree(tree.root) +} + +fun testRemove(tree: AVLTree, _val: Int) { + tree.remove(_val) + println("\nПосле удаления узла $_val AVL-дерево имеет вид") + printTree(tree.root) +} + +/* Driver Code */ +fun main() { + /* Инициализация пустого AVL-дерева */ + val avlTree = AVLTree() + + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(avlTree, 1) + testInsert(avlTree, 2) + testInsert(avlTree, 3) + testInsert(avlTree, 4) + testInsert(avlTree, 5) + testInsert(avlTree, 8) + testInsert(avlTree, 7) + testInsert(avlTree, 9) + testInsert(avlTree, 10) + testInsert(avlTree, 6) + + /* Вставка повторяющегося узла */ + testInsert(avlTree, 7) + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(avlTree, 8) // Удаление узла степени 0 + testRemove(avlTree, 5) // Удаление узла степени 1 + testRemove(avlTree, 4) // Удаление узла степени 2 + + /* Поиск узла */ + val node = avlTree.search(7) + println("\nНайденный объект узла = $node, значение узла = ${node?._val}") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_tree/binary_search_tree.kt b/ru/codes/kotlin/chapter_tree/binary_search_tree.kt new file mode 100644 index 000000000..e4f62f43a --- /dev/null +++ b/ru/codes/kotlin/chapter_tree/binary_search_tree.kt @@ -0,0 +1,157 @@ +/** + * File: binary_search_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Двоичное дерево поиска */ +class BinarySearchTree { + // Инициализировать пустое дерево + private var root: TreeNode? = null + + /* Получить корневой узел двоичного дерева */ + fun getRoot(): TreeNode? { + return root + } + + /* Поиск узла */ + fun search(num: Int): TreeNode? { + var cur = root + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + cur = if (cur._val < num) + cur.right + // Целевой узел находится в левом поддереве cur + else if (cur._val > num) + cur.left + // Найти целевой узел и выйти из цикла + else + break + } + // Вернуть целевой узел + return cur + } + + /* Вставка узла */ + fun insert(num: Int) { + // Если дерево пусто, инициализировать корневой узел + if (root == null) { + root = TreeNode(num) + return + } + var cur = root + var pre: TreeNode? = null + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти повторяющийся узел и сразу вернуть + if (cur._val == num) + return + pre = cur + // Позиция вставки находится в правом поддереве cur + cur = if (cur._val < num) + cur.right + // Позиция вставки находится в левом поддереве cur + else + cur.left + } + // Вставка узла + val node = TreeNode(num) + if (pre?._val!! < num) + pre.right = node + else + pre.left = node + } + + /* Удаление узла */ + fun remove(num: Int) { + // Если дерево пусто, сразу вернуть + if (root == null) + return + var cur = root + var pre: TreeNode? = null + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти узел для удаления и выйти из цикла + if (cur._val == num) + break + pre = cur + // Узел для удаления находится в правом поддереве cur + cur = if (cur._val < num) + cur.right + // Узел для удаления находится в левом поддереве cur + else + cur.left + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur == null) + return + // Число дочерних узлов = 0 или 1 + if (cur.left == null || cur.right == null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + val child = if (cur.left != null) + cur.left + else + cur.right + // Удалить узел cur + if (cur != root) { + if (pre!!.left == cur) + pre.left = child + else + pre.right = child + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + root = child + } + // Число дочерних узлов = 2 + } else { + // Получить следующий узел после cur в симметричном обходе + var tmp = cur.right + while (tmp!!.left != null) { + tmp = tmp.left + } + // Рекурсивно удалить узел tmp + remove(tmp._val) + // Перезаписать cur значением tmp + cur._val = tmp._val + } + } +} + +/* Driver Code */ +fun main() { + /* Инициализация двоичного дерева поиска */ + val bst = BinarySearchTree() + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) + for (num in nums) { + bst.insert(num) + } + println("\nИсходное двоичное дерево\n") + printTree(bst.getRoot()) + + /* Поиск узла */ + val node = bst.search(7) + println("Найденный объект узла = $node, значение узла = ${node?._val}") + + /* Вставка узла */ + bst.insert(16) + println("\nПосле вставки узла 16 двоичное дерево имеет вид\n") + printTree(bst.getRoot()) + + /* Удаление узла */ + bst.remove(1) + println("\nПосле удаления узла 1 двоичное дерево имеет вид\n") + printTree(bst.getRoot()) + bst.remove(2) + println("\nПосле удаления узла 2 двоичное дерево имеет вид\n") + printTree(bst.getRoot()) + bst.remove(4) + println("\nПосле удаления узла 4 двоичное дерево имеет вид\n") + printTree(bst.getRoot()) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_tree/binary_tree.kt b/ru/codes/kotlin/chapter_tree/binary_tree.kt new file mode 100644 index 000000000..d97ce3846 --- /dev/null +++ b/ru/codes/kotlin/chapter_tree/binary_tree.kt @@ -0,0 +1,40 @@ +/** + * File: binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Driver Code */ +fun main() { + /* Инициализация двоичного дерева */ + // Инициализация узла + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // Построить связи между узлами (указатели) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + println("\nИнициализация двоичного дерева\n") + printTree(n1) + + /* Вставка и удаление узлов */ + val P = TreeNode(0) + // Вставить узел P между n1 -> n2 + n1.left = P + P.left = n2 + println("\nПосле вставки узла P\n") + printTree(n1) + // Удалить узел P + n1.left = n2 + println("\nПосле удаления узла P\n") + printTree(n1) +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_tree/binary_tree_bfs.kt b/ru/codes/kotlin/chapter_tree/binary_tree_bfs.kt new file mode 100644 index 000000000..f8afdf7a0 --- /dev/null +++ b/ru/codes/kotlin/chapter_tree/binary_tree_bfs.kt @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import java.util.* + +/* Обход в ширину */ +fun levelOrder(root: TreeNode?): MutableList { + // Инициализировать очередь и добавить корневой узел + val queue = LinkedList() + queue.add(root) + // Инициализировать список для хранения последовательности обхода + val list = mutableListOf() + while (queue.isNotEmpty()) { + val node = queue.poll() // Извлечение из очереди + list.add(node?._val!!) // Сохранить значение узла + if (node.left != null) + queue.offer(node.left) // Поместить левый дочерний узел в очередь + if (node.right != null) + queue.offer(node.right) // Поместить правый дочерний узел в очередь + } + return list +} + +/* Driver Code */ +fun main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из списка + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\nИнициализация двоичного дерева\n") + printTree(root) + + /* Обход в ширину */ + val list = levelOrder(root) + println("\nПоследовательность печати узлов при обходе в ширину = $list") +} \ No newline at end of file diff --git a/ru/codes/kotlin/chapter_tree/binary_tree_dfs.kt b/ru/codes/kotlin/chapter_tree/binary_tree_dfs.kt new file mode 100644 index 000000000..aee0f1383 --- /dev/null +++ b/ru/codes/kotlin/chapter_tree/binary_tree_dfs.kt @@ -0,0 +1,64 @@ +/** + * File: binary_tree_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +// Инициализировать список для хранения последовательности обхода +var list = mutableListOf() + +/* Предварительный обход */ +fun preOrder(root: TreeNode?) { + if (root == null) return + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.add(root._val) + preOrder(root.left) + preOrder(root.right) +} + +/* Симметричный обход */ +fun inOrder(root: TreeNode?) { + if (root == null) return + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root.left) + list.add(root._val) + inOrder(root.right) +} + +/* Обратный обход */ +fun postOrder(root: TreeNode?) { + if (root == null) return + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root.left) + postOrder(root.right) + list.add(root._val) +} + +/* Driver Code */ +fun main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из списка + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\nИнициализация двоичного дерева\n") + printTree(root) + + /* Предварительный обход */ + list.clear() + preOrder(root) + println("\nПоследовательность печати узлов при предварительном обходе = $list") + + /* Симметричный обход */ + list.clear() + inOrder(root) + println("\nПоследовательность печати узлов при симметричном обходе = $list") + + /* Обратный обход */ + list.clear() + postOrder(root) + println("\nПоследовательность печати узлов при обратном обходе = $list") +} \ No newline at end of file diff --git a/ru/codes/kotlin/utils/ListNode.kt b/ru/codes/kotlin/utils/ListNode.kt new file mode 100644 index 000000000..c19397faf --- /dev/null +++ b/ru/codes/kotlin/utils/ListNode.kt @@ -0,0 +1,25 @@ +/** + * File: ListNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* Узел связного списка */ +class ListNode(var _val: Int) { + var next: ListNode? = null + + companion object { + /* Десериализовать список в связный список */ + fun arrToLinkedList(arr: IntArray): ListNode? { + val dum = ListNode(0) + var head = dum + for (_val in arr) { + head.next = ListNode(_val) + head = head.next!! + } + return dum.next + } + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/utils/PrintUtil.kt b/ru/codes/kotlin/utils/PrintUtil.kt new file mode 100644 index 000000000..69ccc1dd1 --- /dev/null +++ b/ru/codes/kotlin/utils/PrintUtil.kt @@ -0,0 +1,107 @@ +/** + * File: PrintUtil.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +import java.util.* + +class Trunk(var prev: Trunk?, var str: String) + +/* Вывести матрицу (Array) */ +fun printMatrix(matrix: Array>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* Вывести матрицу (List) */ +fun printMatrix(matrix: MutableList>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* Вывести связный список */ +fun printLinkedList(h: ListNode?) { + var head = h + val list = mutableListOf() + while (head != null) { + list.add(head._val.toString()) + head = head.next + } + println(list.joinToString(separator = " -> ")) +} + +/* Вывести двоичное дерево */ +fun printTree(root: TreeNode?) { + printTree(root, null, false) +} + +/** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { + if (root == null) { + return + } + + var prevStr = " " + val trunk = Trunk(prev, prevStr) + + printTree(root.right, trunk, true) + + if (prev == null) { + trunk.str = "———" + } else if (isRight) { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + + showTrunks(trunk) + println(" ${root._val}") + + if (prev != null) { + prev.str = prevStr + } + trunk.str = " |" + + printTree(root.left, trunk, false) +} + +fun showTrunks(p: Trunk?) { + if (p == null) { + return + } + showTrunks(p.prev) + print(p.str) +} + +/* Вывести хеш-таблицу */ +fun printHashMap(map: Map) { + for ((key, value) in map) { + println("${key.toString()} -> $value") + } +} + +/* Вывести кучу */ +fun printHeap(queue: Queue?) { + val list = mutableListOf() + queue?.let { list.addAll(it) } + print("Массивное представление кучи:") + println(list) + println("Древовидное представление кучи:") + val root = TreeNode.listToTree(list) + printTree(root) +} \ No newline at end of file diff --git a/ru/codes/kotlin/utils/TreeNode.kt b/ru/codes/kotlin/utils/TreeNode.kt new file mode 100644 index 000000000..86c86f098 --- /dev/null +++ b/ru/codes/kotlin/utils/TreeNode.kt @@ -0,0 +1,69 @@ +/** + * File: TreeNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* Класс узла двоичного дерева */ +/* Конструктор */ +class TreeNode( + var _val: Int // Значение узла +) { + var height: Int = 0 // Высота узла + var left: TreeNode? = null // Ссылка на левый дочерний узел + var right: TreeNode? = null // Ссылка на правый дочерний узел + + // Правила кодирования сериализации см.: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // Массивное представление двоичного дерева: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // Связное представление двоичного дерева: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* Десериализовать список в двоичное дерево: рекурсия */ + companion object { + private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { + if (i < 0 || i >= arr.size || arr[i] == null) { + return null + } + val root = TreeNode(arr[i]!!) + root.left = listToTreeDFS(arr, 2 * i + 1) + root.right = listToTreeDFS(arr, 2 * i + 2) + return root + } + + /* Десериализовать список в двоичное дерево */ + fun listToTree(arr: MutableList): TreeNode? { + return listToTreeDFS(arr, 0) + } + + /* Сериализовать двоичное дерево в список: рекурсия */ + private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { + if (root == null) return + while (i >= res.size) { + res.add(null) + } + res[i] = root._val + treeToListDFS(root.left, 2 * i + 1, res) + treeToListDFS(root.right, 2 * i + 2, res) + } + + /* Сериализовать двоичное дерево в список */ + fun treeToList(root: TreeNode?): MutableList { + val res = mutableListOf() + treeToListDFS(root, 0, res) + return res + } + } +} \ No newline at end of file diff --git a/ru/codes/kotlin/utils/Vertex.kt b/ru/codes/kotlin/utils/Vertex.kt new file mode 100644 index 000000000..3134bd276 --- /dev/null +++ b/ru/codes/kotlin/utils/Vertex.kt @@ -0,0 +1,30 @@ +/** + * File: Vertex.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* Класс вершины */ +class Vertex(val _val: Int) { + companion object { + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + fun valsToVets(vals: IntArray): Array { + val vets = arrayOfNulls(vals.size) + for (i in vals.indices) { + vets[i] = Vertex(vals[i]) + } + return vets + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + fun vetsToVals(vets: MutableList): MutableList { + val vals = mutableListOf() + for (vet in vets) { + vals.add(vet!!._val) + } + return vals + } + } +} \ No newline at end of file diff --git a/ru/codes/python/.gitignore b/ru/codes/python/.gitignore new file mode 100644 index 000000000..bee8a64b7 --- /dev/null +++ b/ru/codes/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/ru/codes/python/chapter_array_and_linkedlist/array.py b/ru/codes/python/chapter_array_and_linkedlist/array.py new file mode 100644 index 000000000..15955c67f --- /dev/null +++ b/ru/codes/python/chapter_array_and_linkedlist/array.py @@ -0,0 +1,100 @@ +""" +File: array.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_access(nums: list[int]) -> int: + """Случайный доступ к элементу""" + # Случайным образом выбрать число из интервала [0, len(nums)-1] + random_index = random.randint(0, len(nums) - 1) + # Получить и вернуть случайный элемент + random_num = nums[random_index] + return random_num + + +# Обратите внимание: list в Python — это динамический массив, его можно расширять напрямую +# Для удобства обучения в этой функции list рассматривается как массив неизменяемой длины +def extend(nums: list[int], enlarge: int) -> list[int]: + """Увеличить длину массива""" + # Инициализировать массив увеличенной длины + res = [0] * (len(nums) + enlarge) + # Скопировать все элементы исходного массива в новый массив + for i in range(len(nums)): + res[i] = nums[i] + # Вернуть новый массив после расширения + return res + + +def insert(nums: list[int], num: int, index: int): + """Вставить элемент num по индексу index в массив""" + # Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for i in range(len(nums) - 1, index, -1): + nums[i] = nums[i - 1] + # Присвоить num элементу по индексу index + nums[index] = num + + +def remove(nums: list[int], index: int): + """Удалить элемент по индексу index""" + # Сдвинуть все элементы после индекса index на одну позицию вперед + for i in range(index, len(nums) - 1): + nums[i] = nums[i + 1] + + +def traverse(nums: list[int]): + """Обход массива""" + count = 0 + # Обход массива по индексам + for i in range(len(nums)): + count += nums[i] + # Непосредственно обходить элементы массива + for num in nums: + count += num + # Одновременно обходить индексы и элементы данных + for i, num in enumerate(nums): + count += nums[i] + count += num + + +def find(nums: list[int], target: int) -> int: + """Найти заданный элемент в массиве""" + for i in range(len(nums)): + if nums[i] == target: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация массива + arr = [0] * 5 + print("Массив arr =", arr) + nums = [1, 3, 2, 5, 4] + print("Массив nums =", nums) + + # Случайный доступ + random_num: int = random_access(nums) + print("Случайный элемент из nums =", random_num) + + # Расширение длины + nums: list[int] = extend(nums, 3) + print("После увеличения длины массива до 8 nums =", nums) + + # Вставка элемента + insert(nums, 6, 3) + print("После вставки числа 6 по индексу 3 nums =", nums) + + # Удаление элемента + remove(nums, 2) + print("После удаления элемента по индексу 2 nums =", nums) + + # Обход массива + traverse(nums) + + # Поиск элемента + index: int = find(nums, 3) + print("Поиск элемента 3 в nums: индекс =", index) diff --git a/ru/codes/python/chapter_array_and_linkedlist/linked_list.py b/ru/codes/python/chapter_array_and_linkedlist/linked_list.py new file mode 100644 index 000000000..f10174edc --- /dev/null +++ b/ru/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -0,0 +1,85 @@ +""" +File: linked_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, print_linked_list + + +def insert(n0: ListNode, P: ListNode): + """Вставить узел P после узла n0 в связном списке""" + n1 = n0.next + P.next = n1 + n0.next = P + + +def remove(n0: ListNode): + """Удалить первый узел после узла n0 в связном списке""" + if not n0.next: + return + # n0 -> P -> n1 + P = n0.next + n1 = P.next + n0.next = n1 + + +def access(head: ListNode, index: int) -> ListNode | None: + """Доступ к узлу связного списка по индексу index""" + for _ in range(index): + if not head: + return None + head = head.next + return head + + +def find(head: ListNode, target: int) -> int: + """Найти в связном списке первый узел со значением target""" + index = 0 + while head: + if head.val == target: + return index + head = head.next + index += 1 + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация связного списка + # Инициализация всех узлов + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # Построить ссылки между узлами + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("Исходный связный список") + print_linked_list(n0) + + # Вставка узла + p = ListNode(0) + insert(n0, p) + print("Связный список после вставки узла") + print_linked_list(n0) + + # Удаление узла + remove(n0) + print("Связный список после удаления узла") + print_linked_list(n0) + + # Доступ к узлу + node: ListNode = access(n0, 3) + print("Значение узла по индексу 3 в связном списке = {}".format(node.val)) + + # Поиск узла + index: int = find(n0, 2) + print("Индекс узла со значением 2 в связном списке = {}".format(index)) diff --git a/ru/codes/python/chapter_array_and_linkedlist/list.py b/ru/codes/python/chapter_array_and_linkedlist/list.py new file mode 100644 index 000000000..0a77fadd5 --- /dev/null +++ b/ru/codes/python/chapter_array_and_linkedlist/list.py @@ -0,0 +1,56 @@ +""" +File: list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация списка + nums: list[int] = [1, 3, 2, 5, 4] + print("\nСписок nums =", nums) + + # Доступ к элементу + x: int = nums[1] + print("\nЭлемент по индексу 1: x =", x) + + # Обновление элемента + nums[1] = 0 + print("\nПосле обновления элемента по индексу 1 до 0 nums =", nums) + + # Очистить список + nums.clear() + print("\nПосле очистки списка nums =", nums) + + # Добавление элемента в конец + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("\nПосле добавления элементов nums =", nums) + + # Вставка элемента в середину + nums.insert(3, 6) + print("\nПосле вставки числа 6 по индексу 3 nums =", nums) + + # Удаление элемента + nums.pop(3) + print("\nПосле удаления элемента по индексу 3 nums =", nums) + + # Обходить список по индексам + count = 0 + for i in range(len(nums)): + count += nums[i] + # Непосредственно обходить элементы списка + for num in nums: + count += num + + # Объединить два списка + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + print("\nПосле конкатенации списка nums1 к nums nums =", nums) + + # Отсортировать список + nums.sort() + print("\nПосле сортировки списка nums =", nums) diff --git a/ru/codes/python/chapter_array_and_linkedlist/my_list.py b/ru/codes/python/chapter_array_and_linkedlist/my_list.py new file mode 100644 index 000000000..08450f713 --- /dev/null +++ b/ru/codes/python/chapter_array_and_linkedlist/my_list.py @@ -0,0 +1,118 @@ +""" +File: my_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +class MyList: + """Класс списка""" + + def __init__(self): + """Конструктор""" + self._capacity: int = 10 # Вместимость списка + self._arr: list[int] = [0] * self._capacity # Массив (для хранения элементов списка) + self._size: int = 0 # Длина списка (текущее число элементов) + self._extend_ratio: int = 2 # Коэффициент увеличения списка при каждом расширении + + def size(self) -> int: + """Получить длину списка (текущее число элементов)""" + return self._size + + def capacity(self) -> int: + """Получить вместимость списка""" + return self._capacity + + def get(self, index: int) -> int: + """Доступ к элементу""" + # Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if index < 0 or index >= self._size: + raise IndexError("индекс выходит за границы") + return self._arr[index] + + def set(self, num: int, index: int): + """Обновление элемента""" + if index < 0 or index >= self._size: + raise IndexError("индекс выходит за границы") + self._arr[index] = num + + def add(self, num: int): + """Добавление элемента в конец""" + # При превышении вместимости по числу элементов запускается расширение + if self.size() == self.capacity(): + self.extend_capacity() + self._arr[self._size] = num + self._size += 1 + + def insert(self, num: int, index: int): + """Вставка элемента в середину""" + if index < 0 or index >= self._size: + raise IndexError("индекс выходит за границы") + # При превышении вместимости по числу элементов запускается расширение + if self._size == self.capacity(): + self.extend_capacity() + # Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for j in range(self._size - 1, index - 1, -1): + self._arr[j + 1] = self._arr[j] + self._arr[index] = num + # Обновить число элементов + self._size += 1 + + def remove(self, index: int) -> int: + """Удаление элемента""" + if index < 0 or index >= self._size: + raise IndexError("индекс выходит за границы") + num = self._arr[index] + # Сдвинуть все элементы после индекса index на одну позицию вперед + for j in range(index, self._size - 1): + self._arr[j] = self._arr[j + 1] + # Обновить число элементов + self._size -= 1 + # Вернуть удаленный элемент + return num + + def extend_capacity(self): + """Расширение списка""" + # Создать новый массив длиной в _extend_ratio раз больше исходного массива и скопировать в него исходный массив + self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1) + # Обновить вместимость списка + self._capacity = len(self._arr) + + def to_array(self) -> list[int]: + """Вернуть список фактической длины""" + return self._arr[: self._size] + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация списка + nums = MyList() + # Добавление элемента в конец + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + print(f"Список nums = {nums.to_array()}, вместимость = {nums.capacity()}, длина = {nums.size()}") + + # Вставка элемента в середину + nums.insert(6, index=3) + print("После вставки числа 6 по индексу 3 nums =", nums.to_array()) + + # Удаление элемента + nums.remove(3) + print("После удаления элемента по индексу 3 nums =", nums.to_array()) + + # Доступ к элементу + num = nums.get(1) + print("Элемент по индексу 1: num =", num) + + # Обновление элемента + nums.set(0, 1) + print("После обновления элемента по индексу 1 до 0 nums =", nums.to_array()) + + # Проверка механизма расширения + for i in range(10): + # При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i) + print(f"Список nums после увеличения вместимости = {nums.to_array()}, вместимость = {nums.capacity()}, длина = {nums.size()}") diff --git a/ru/codes/python/chapter_backtracking/n_queens.py b/ru/codes/python/chapter_backtracking/n_queens.py new file mode 100644 index 000000000..4b33c9ccb --- /dev/null +++ b/ru/codes/python/chapter_backtracking/n_queens.py @@ -0,0 +1,62 @@ +""" +File: n_queens.py +Created Time: 2023-04-26 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], +): + """Алгоритм бэктрекинга: n ферзей""" + # Когда все строки уже обработаны, записать решение + if row == n: + res.append([list(row) for row in state]) + return + # Обойти все столбцы + for col in range(n): + # Вычислить главную и побочную диагонали, соответствующие этой клетке + diag1 = row - col + n - 1 + diag2 = row + col + # Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if not cols[col] and not diags1[diag1] and not diags2[diag2]: + # Попытка: поставить ферзя в эту клетку + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = True + # Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # Откат: восстановить эту клетку как пустую + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = False + + +def n_queens(n: int) -> list[list[list[str]]]: + """Решить задачу о n ферзях""" + # Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # Отмечать, есть ли ферзь в столбце + diags1 = [False] * (2 * n - 1) # Отмечать наличие ферзя на главной диагонали + diags2 = [False] * (2 * n - 1) # Отмечать наличие ферзя на побочной диагонали + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 4 + res = n_queens(n) + + print(f"Размер входной доски = {n}") + print(f"Количество способов расстановки ферзей: {len(res)}") + for state in res: + print("--------------------") + for row in state: + print(row) diff --git a/ru/codes/python/chapter_backtracking/permutations_i.py b/ru/codes/python/chapter_backtracking/permutations_i.py new file mode 100644 index 000000000..b0d8391dd --- /dev/null +++ b/ru/codes/python/chapter_backtracking/permutations_i.py @@ -0,0 +1,44 @@ +""" +File: permutations_i.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """Алгоритм бэктрекинга: все перестановки I""" + # Когда длина состояния равна числу элементов, записать решение + if len(state) == len(choices): + res.append(list(state)) + return + # Перебор всех вариантов выбора + for i, choice in enumerate(choices): + # Отсечение: нельзя выбирать один и тот же элемент повторно + if not selected[i]: + # Попытка: сделать выбор и обновить состояние + selected[i] = True + state.append(choice) + # Перейти к следующему выбору + backtrack(state, choices, selected, res) + # Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = False + state.pop() + + +def permutations_i(nums: list[int]) -> list[list[int]]: + """Все перестановки I""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 3] + + res = permutations_i(nums) + + print(f"Входной массив nums = {nums}") + print(f"Все перестановки res = {res}") diff --git a/ru/codes/python/chapter_backtracking/permutations_ii.py b/ru/codes/python/chapter_backtracking/permutations_ii.py new file mode 100644 index 000000000..0bd9399c0 --- /dev/null +++ b/ru/codes/python/chapter_backtracking/permutations_ii.py @@ -0,0 +1,46 @@ +""" +File: permutations_ii.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """Алгоритм бэктрекинга: все перестановки II""" + # Когда длина состояния равна числу элементов, записать решение + if len(state) == len(choices): + res.append(list(state)) + return + # Перебор всех вариантов выбора + duplicated = set[int]() + for i, choice in enumerate(choices): + # Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if not selected[i] and choice not in duplicated: + # Попытка: сделать выбор и обновить состояние + duplicated.add(choice) # Записать значения уже выбранных элементов + selected[i] = True + state.append(choice) + # Перейти к следующему выбору + backtrack(state, choices, selected, res) + # Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = False + state.pop() + + +def permutations_ii(nums: list[int]) -> list[list[int]]: + """Все перестановки II""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 2, 2] + + res = permutations_ii(nums) + + print(f"Входной массив nums = {nums}") + print(f"Все перестановки res = {res}") diff --git a/ru/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/ru/codes/python/chapter_backtracking/preorder_traversal_i_compact.py new file mode 100644 index 000000000..1319cb3f0 --- /dev/null +++ b/ru/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -0,0 +1,36 @@ +""" +File: preorder_traversal_i_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """Предварительный обход: пример 1""" + if root is None: + return + if root.val == 7: + # Записать решение + res.append(root) + pre_order(root.left) + pre_order(root.right) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + print_tree(root) + + # Предварительный обход + res = list[TreeNode]() + pre_order(root) + + print("\nВсе узлы со значением 7") + print([node.val for node in res]) diff --git a/ru/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/ru/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py new file mode 100644 index 000000000..cffb4506d --- /dev/null +++ b/ru/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -0,0 +1,42 @@ +""" +File: preorder_traversal_ii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """Предварительный обход: пример 2""" + if root is None: + return + # Попытка + path.append(root) + if root.val == 7: + # Записать решение + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # Откат + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + print_tree(root) + + # Предварительный обход + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\nВсе пути от корня к узлу 7") + for path in res: + print([node.val for node in path]) diff --git a/ru/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/ru/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py new file mode 100644 index 000000000..c9ea1135f --- /dev/null +++ b/ru/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -0,0 +1,43 @@ +""" +File: preorder_traversal_iii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """Предварительный обход: пример 3""" + # Отсечение + if root is None or root.val == 3: + return + # Попытка + path.append(root) + if root.val == 7: + # Записать решение + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # Откат + path.pop() + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + print_tree(root) + + # Предварительный обход + path = list[TreeNode]() + res = list[list[TreeNode]]() + pre_order(root) + + print("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") + for path in res: + print([node.val for node in path]) diff --git a/ru/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/ru/codes/python/chapter_backtracking/preorder_traversal_iii_template.py new file mode 100644 index 000000000..6c510b93c --- /dev/null +++ b/ru/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -0,0 +1,71 @@ +""" +File: preorder_traversal_iii_template.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def is_solution(state: list[TreeNode]) -> bool: + """Проверить, является ли текущее состояние решением""" + return state and state[-1].val == 7 + + +def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """Записать решение""" + res.append(list(state)) + + +def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """Проверить, допустим ли этот выбор в текущем состоянии""" + return choice is not None and choice.val != 3 + + +def make_choice(state: list[TreeNode], choice: TreeNode): + """Обновить состояние""" + state.append(choice) + + +def undo_choice(state: list[TreeNode], choice: TreeNode): + """Восстановить состояние""" + state.pop() + + +def backtrack( + state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] +): + """Алгоритм бэктрекинга: пример 3""" + # Проверить, является ли текущее состояние решением + if is_solution(state): + # Записать решение + record_solution(state, res) + # Перебор всех вариантов выбора + for choice in choices: + # Отсечение: проверить допустимость выбора + if is_valid(state, choice): + # Попытка: сделать выбор и обновить состояние + make_choice(state, choice) + # Перейти к следующему выбору + backtrack(state, [choice.left, choice.right], res) + # Откат: отменить выбор и восстановить предыдущее состояние + undo_choice(state, choice) + + +"""Driver Code""" +if __name__ == "__main__": + root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + print_tree(root) + + # Алгоритм бэктрекинга + res = [] + backtrack(state=[], choices=[root], res=res) + + print("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3") + for path in res: + print([node.val for node in path]) diff --git a/ru/codes/python/chapter_backtracking/subset_sum_i.py b/ru/codes/python/chapter_backtracking/subset_sum_i.py new file mode 100644 index 000000000..74539ecff --- /dev/null +++ b/ru/codes/python/chapter_backtracking/subset_sum_i.py @@ -0,0 +1,48 @@ +""" +File: subset_sum_i.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """Алгоритм бэктрекинга: сумма подмножеств I""" + # Если сумма подмножества равна target, записать решение + if target == 0: + res.append(list(state)) + return + # Обойти все варианты выбора + # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for i in range(start, len(choices)): + # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target - choices[i] < 0: + break + # Попытка: сделать выбор и обновить target и start + state.append(choices[i]) + # Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res) + # Откат: отменить выбор и восстановить предыдущее состояние + state.pop() + + +def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: + """Решить задачу суммы подмножеств I""" + state = [] # Состояние (подмножество) + nums.sort() # Отсортировать nums + start = 0 # Стартовая вершина обхода + res = [] # Список результатов (список подмножеств) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + print(f"Входной массив nums = {nums}, target = {target}") + print(f"Все подмножества с суммой {target}: res = {res}") diff --git a/ru/codes/python/chapter_backtracking/subset_sum_i_naive.py b/ru/codes/python/chapter_backtracking/subset_sum_i_naive.py new file mode 100644 index 000000000..1a993f900 --- /dev/null +++ b/ru/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -0,0 +1,50 @@ +""" +File: subset_sum_i_naive.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], + target: int, + total: int, + choices: list[int], + res: list[list[int]], +): + """Алгоритм бэктрекинга: сумма подмножеств I""" + # Если сумма подмножества равна target, записать решение + if total == target: + res.append(list(state)) + return + # Перебор всех вариантов выбора + for i in range(len(choices)): + # Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if total + choices[i] > target: + continue + # Попытка: сделать выбор и обновить элемент и total + state.append(choices[i]) + # Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res) + # Откат: отменить выбор и восстановить предыдущее состояние + state.pop() + + +def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: + """Решить задачу суммы подмножеств I (с повторяющимися подмножествами)""" + state = [] # Состояние (подмножество) + total = 0 # Сумма подмножеств + res = [] # Список результатов (список подмножеств) + backtrack(state, target, total, nums, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + print(f"Входной массив nums = {nums}, target = {target}") + print(f"Все подмножества с суммой {target}: res = {res}") + print(f"Обратите внимание: результат этого метода содержит повторяющиеся множества") diff --git a/ru/codes/python/chapter_backtracking/subset_sum_ii.py b/ru/codes/python/chapter_backtracking/subset_sum_ii.py new file mode 100644 index 000000000..017c12a0f --- /dev/null +++ b/ru/codes/python/chapter_backtracking/subset_sum_ii.py @@ -0,0 +1,52 @@ +""" +File: subset_sum_ii.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """Алгоритм бэктрекинга: сумма подмножеств II""" + # Если сумма подмножества равна target, записать решение + if target == 0: + res.append(list(state)) + return + # Обойти все варианты выбора + # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + # Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for i in range(start, len(choices)): + # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target - choices[i] < 0: + break + # Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if i > start and choices[i] == choices[i - 1]: + continue + # Попытка: сделать выбор и обновить target и start + state.append(choices[i]) + # Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res) + # Откат: отменить выбор и восстановить предыдущее состояние + state.pop() + + +def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: + """Решить задачу суммы подмножеств II""" + state = [] # Состояние (подмножество) + nums.sort() # Отсортировать nums + start = 0 # Стартовая вершина обхода + res = [] # Список результатов (список подмножеств) + backtrack(state, target, nums, start, res) + return res + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + print(f"Входной массив nums = {nums}, target = {target}") + print(f"Все подмножества с суммой {target}: res = {res}") diff --git a/ru/codes/python/chapter_computational_complexity/iteration.py b/ru/codes/python/chapter_computational_complexity/iteration.py new file mode 100644 index 000000000..e8aea5535 --- /dev/null +++ b/ru/codes/python/chapter_computational_complexity/iteration.py @@ -0,0 +1,65 @@ +""" +File: iteration.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def for_loop(n: int) -> int: + """Цикл for""" + res = 0 + # Циклическое суммирование 1, 2, ..., n-1, n + for i in range(1, n + 1): + res += i + return res + + +def while_loop(n: int) -> int: + """Цикл while""" + res = 0 + i = 1 # Инициализация условной переменной + # Циклическое суммирование 1, 2, ..., n-1, n + while i <= n: + res += i + i += 1 # Обновить условную переменную + return res + + +def while_loop_ii(n: int) -> int: + """Цикл while (двойное обновление)""" + res = 0 + i = 1 # Инициализация условной переменной + # Циклическое суммирование 1, 4, 10, ... + while i <= n: + res += i + # Обновить условную переменную + i += 1 + i *= 2 + return res + + +def nested_for_loop(n: int) -> str: + """Двойной цикл for""" + res = "" + # Цикл по i = 1, 2, ..., n-1, n + for i in range(1, n + 1): + # Цикл по j = 1, 2, ..., n-1, n + for j in range(1, n + 1): + res += f"({i}, {j}), " + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = for_loop(n) + print(f"\nРезультат суммирования в цикле for res = {res}") + + res = while_loop(n) + print(f"\nРезультат суммирования в цикле while res = {res}") + + res = while_loop_ii(n) + print(f"\nРезультат суммирования в цикле while (двойное обновление) res = {res}") + + res = nested_for_loop(n) + print(f"\nРезультат обхода в двойном цикле for {res}") diff --git a/ru/codes/python/chapter_computational_complexity/recursion.py b/ru/codes/python/chapter_computational_complexity/recursion.py new file mode 100644 index 000000000..242457a2c --- /dev/null +++ b/ru/codes/python/chapter_computational_complexity/recursion.py @@ -0,0 +1,69 @@ +""" +File: recursion.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def recur(n: int) -> int: + """Рекурсия""" + # Условие завершения + if n == 1: + return 1 + # Рекурсия: рекурсивный вызов + res = recur(n - 1) + # Возврат: вернуть результат + return n + res + + +def for_loop_recur(n: int) -> int: + """Имитация рекурсии итерацией""" + # Использовать явный стек для имитации системного стека вызовов + stack = [] + res = 0 + # Рекурсия: рекурсивный вызов + for i in range(n, 0, -1): + # Имитировать «рекурсию» с помощью операции помещения в стек + stack.append(i) + # Возврат: вернуть результат + while stack: + # Имитировать «возврат» с помощью операции извлечения из стека + res += stack.pop() + # res = 1+2+3+...+n + return res + + +def tail_recur(n, res): + """Хвостовая рекурсия""" + # Условие завершения + if n == 0: + return res + # Хвостовой рекурсивный вызов + return tail_recur(n - 1, res + n) + + +def fib(n: int) -> int: + """Последовательность Фибоначчи: рекурсия""" + # Условие завершения: f(1) = 0, f(2) = 1 + if n == 1 or n == 2: + return n - 1 + # Рекурсивный вызов f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # Вернуть результат f(n) + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = recur(n) + print(f"\nРезультат суммирования в рекурсивной функции res = {res}") + + res = for_loop_recur(n) + print(f"\nРезультат суммирования при имитации рекурсии res = {res}") + + res = tail_recur(n, 0) + print(f"\nРезультат суммирования в хвостовой рекурсии res = {res}") + + res = fib(n) + print(f"\nЧлен последовательности Фибоначчи с номером {n} = {res}") diff --git a/ru/codes/python/chapter_computational_complexity/space_complexity.py b/ru/codes/python/chapter_computational_complexity/space_complexity.py new file mode 100644 index 000000000..00b488898 --- /dev/null +++ b/ru/codes/python/chapter_computational_complexity/space_complexity.py @@ -0,0 +1,90 @@ +""" +File: space_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, TreeNode, print_tree + + +def function() -> int: + """Функция""" + # Выполнить некоторые операции + return 0 + + +def constant(n: int): + """Постоянная сложность""" + # Константы, переменные и объекты занимают O(1) памяти + a = 0 + nums = [0] * 10000 + node = ListNode(0) + # Переменные в цикле занимают O(1) памяти + for _ in range(n): + c = 0 + # Функции в цикле занимают O(1) памяти + for _ in range(n): + function() + + +def linear(n: int): + """Линейная сложность""" + # Список длины n занимает O(n) памяти + nums = [0] * n + # Хеш-таблица длины n занимает O(n) памяти + hmap = dict[int, str]() + for i in range(n): + hmap[i] = str(i) + + +def linear_recur(n: int): + """Линейная сложность (рекурсивная реализация)""" + print("Рекурсия n =", n) + if n == 1: + return + linear_recur(n - 1) + + +def quadratic(n: int): + """Квадратичная сложность""" + # Двумерный список занимает O(n^2) памяти + num_matrix = [[0] * n for _ in range(n)] + + +def quadratic_recur(n: int) -> int: + """Квадратичная сложность (рекурсивная реализация)""" + if n <= 0: + return 0 + # Длина массива nums равна n, n-1, ..., 2, 1 + nums = [0] * n + return quadratic_recur(n - 1) + + +def build_tree(n: int) -> TreeNode | None: + """Экспоненциальная сложность (построение полного двоичного дерева)""" + if n == 0: + return None + root = TreeNode(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + # Постоянная сложность + constant(n) + # Линейная сложность + linear(n) + linear_recur(n) + # Квадратичная сложность + quadratic(n) + quadratic_recur(n) + # Экспоненциальная сложность + root = build_tree(n) + print_tree(root) diff --git a/ru/codes/python/chapter_computational_complexity/time_complexity.py b/ru/codes/python/chapter_computational_complexity/time_complexity.py new file mode 100644 index 000000000..8cbc62191 --- /dev/null +++ b/ru/codes/python/chapter_computational_complexity/time_complexity.py @@ -0,0 +1,153 @@ +""" +File: time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def constant(n: int) -> int: + """Постоянная сложность""" + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count + + +def linear(n: int) -> int: + """Линейная сложность""" + count = 0 + for _ in range(n): + count += 1 + return count + + +def array_traversal(nums: list[int]) -> int: + """Линейная сложность (обход массива)""" + count = 0 + # Число итераций пропорционально длине массива + for num in nums: + count += 1 + return count + + +def quadratic(n: int) -> int: + """Квадратичная сложность""" + count = 0 + # Число итераций квадратично зависит от размера данных n + for i in range(n): + for j in range(n): + count += 1 + return count + + +def bubble_sort(nums: list[int]) -> int: + """Квадратичная сложность (пузырьковая сортировка)""" + count = 0 # Счетчик + # Внешний цикл: неотсортированный диапазон [0, i] + for i in range(len(nums) - 1, 0, -1): + # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in range(i): + if nums[j] > nums[j + 1]: + # Поменять местами nums[j] и nums[j + 1] + tmp: int = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # Обмен элементов включает 3 элементарные операции + return count + + +def exponential(n: int) -> int: + """Экспоненциальная сложность (итеративная реализация)""" + count = 0 + base = 1 + # На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for _ in range(n): + for _ in range(base): + count += 1 + base *= 2 + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count + + +def exp_recur(n: int) -> int: + """Экспоненциальная сложность (рекурсивная реализация)""" + if n == 1: + return 1 + return exp_recur(n - 1) + exp_recur(n - 1) + 1 + + +def logarithmic(n: int) -> int: + """Логарифмическая сложность (итеративная реализация)""" + count = 0 + while n > 1: + n = n / 2 + count += 1 + return count + + +def log_recur(n: int) -> int: + """Логарифмическая сложность (рекурсивная реализация)""" + if n <= 1: + return 0 + return log_recur(n / 2) + 1 + + +def linear_log_recur(n: int) -> int: + """Линейно-логарифмическая сложность""" + if n <= 1: + return 1 + # Разделение надвое: размер подзадачи уменьшается вдвое + count = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # Текущая подзадача содержит n операций + for _ in range(n): + count += 1 + return count + + +def factorial_recur(n: int) -> int: + """Факториальная сложность (рекурсивная реализация)""" + if n == 0: + return 1 + count = 0 + # Из одного получается n + for _ in range(n): + count += factorial_recur(n - 1) + return count + + +"""Driver Code""" +if __name__ == "__main__": + # Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + n = 8 + print("Размер входных данных n =", n) + + count = constant(n) + print("Число операций константной сложности =", count) + + count = linear(n) + print("Число операций линейной сложности =", count) + count = array_traversal([0] * n) + print("Число операций линейной сложности (обход массива) =", count) + + count = quadratic(n) + print("Число операций квадратичной сложности =", count) + nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] + count = bubble_sort(nums) + print("Число операций квадратичной сложности (пузырьковая сортировка) =", count) + + count = exponential(n) + print("Число операций экспоненциальной сложности (итеративная реализация) =", count) + count = exp_recur(n) + print("Число операций экспоненциальной сложности (рекурсивная реализация) =", count) + + count = logarithmic(n) + print("Число операций логарифмической сложности (итеративная реализация) =", count) + count = log_recur(n) + print("Число операций логарифмической сложности (рекурсивная реализация) =", count) + + count = linear_log_recur(n) + print("Число операций линейно-логарифмической сложности (рекурсивная реализация) =", count) + + count = factorial_recur(n) + print("Число операций факториальной сложности (рекурсивная реализация) =", count) diff --git a/ru/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/ru/codes/python/chapter_computational_complexity/worst_best_time_complexity.py new file mode 100644 index 000000000..ccf307d25 --- /dev/null +++ b/ru/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -0,0 +1,36 @@ +""" +File: worst_best_time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_numbers(n: int) -> list[int]: + """Сгенерировать массив с элементами 1, 2, ..., n в случайном порядке""" + # Создать массив nums =: 1, 2, 3, ..., n + nums = [i for i in range(1, n + 1)] + # Случайно перемешать элементы массива + random.shuffle(nums) + return nums + + +def find_one(nums: list[int]) -> int: + """Найти индекс числа 1 в массиве nums""" + for i in range(len(nums)): + # Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + # Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if nums[i] == 1: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + for i in range(10): + n = 100 + nums: list[int] = random_numbers(n) + index: int = find_one(nums) + print("\nМассив [1, 2, ..., n] после перемешивания =", nums) + print("Индекс числа 1 =", index) diff --git a/ru/codes/python/chapter_divide_and_conquer/binary_search_recur.py b/ru/codes/python/chapter_divide_and_conquer/binary_search_recur.py new file mode 100644 index 000000000..c556e5994 --- /dev/null +++ b/ru/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -0,0 +1,40 @@ +""" +File: binary_search_recur.py +Created Time: 2023-07-17 +Author: krahets (krahets@163.com) +""" + + +def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """Бинарный поиск: задача f(i, j)""" + # Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if i > j: + return -1 + # Вычислить индекс середины m + m = (i + j) // 2 + if nums[m] < target: + # Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # Целевой элемент найден, вернуть его индекс + return m + + +def binary_search(nums: list[int], target: int) -> int: + """Бинарный поиск""" + n = len(nums) + # Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1) + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # Бинарный поиск (двусторонне замкнутый интервал) + index = binary_search(nums, target) + print("Индекс целевого элемента 6 = ", index) diff --git a/ru/codes/python/chapter_divide_and_conquer/build_tree.py b/ru/codes/python/chapter_divide_and_conquer/build_tree.py new file mode 100644 index 000000000..94c1d1e46 --- /dev/null +++ b/ru/codes/python/chapter_divide_and_conquer/build_tree.py @@ -0,0 +1,54 @@ +""" +File: build_tree.py +Created Time: 2023-07-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +def dfs( + preorder: list[int], + inorder_map: dict[int, int], + i: int, + l: int, + r: int, +) -> TreeNode | None: + """Построить двоичное дерево: разделяй и властвуй""" + # Завершить при пустом диапазоне поддерева + if r - l < 0: + return None + # Инициализировать корневой узел + root = TreeNode(preorder[i]) + # Найти m, чтобы разделить левое и правое поддеревья + m = inorder_map[preorder[i]] + # Подзадача: построить левое поддерево + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # Подзадача: построить правое поддерево + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + # Вернуть корневой узел + return root + + +def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: + """Построить двоичное дерево""" + # Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + inorder_map = {val: i for i, val in enumerate(inorder)} + root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) + return root + + +"""Driver Code""" +if __name__ == "__main__": + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + print(f"Предварительный обход = {preorder}") + print(f"Симметричный обход = {inorder}") + + root = build_tree(preorder, inorder) + print("Построенное двоичное дерево:") + print_tree(root) diff --git a/ru/codes/python/chapter_divide_and_conquer/hanota.py b/ru/codes/python/chapter_divide_and_conquer/hanota.py new file mode 100644 index 000000000..74699ef41 --- /dev/null +++ b/ru/codes/python/chapter_divide_and_conquer/hanota.py @@ -0,0 +1,53 @@ +""" +File: hanota.py +Created Time: 2023-07-16 +Author: krahets (krahets@163.com) +""" + + +def move(src: list[int], tar: list[int]): + """Переместить один диск""" + # Снять диск с вершины src + pan = src.pop() + # Положить диск на вершину tar + tar.append(pan) + + +def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): + """Решить задачу Ханойской башни f(i)""" + # Если в src остался только один диск, сразу переместить его в tar + if i == 1: + move(src, tar) + return + # Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf) + # Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar) + # Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar) + + +def solve_hanota(A: list[int], B: list[int], C: list[int]): + """Решить задачу Ханойской башни""" + n = len(A) + # Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C) + + +"""Driver Code""" +if __name__ == "__main__": + # Хвост списка соответствует вершине столбца + A = [5, 4, 3, 2, 1] + B = [] + C = [] + print("Исходное состояние:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") + + solve_hanota(A, B, C) + + print("После завершения перемещения дисков:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") diff --git a/ru/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py new file mode 100644 index 000000000..7ef5f7f69 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -0,0 +1,37 @@ +""" +File: climbing_stairs_backtrack.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """Бэктрекинг""" + # Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if state == n: + res[0] += 1 + # Перебор всех вариантов выбора + for choice in choices: + # Отсечение: нельзя выходить за n-ю ступень + if state + choice > n: + continue + # Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res) + # Откат + + +def climbing_stairs_backtrack(n: int) -> int: + """Подъем по лестнице: бэктрекинг""" + choices = [1, 2] # Можно подняться на 1 или 2 ступени + state = 0 # Начать подъем с 0-й ступени + res = [0] # Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res) + return res[0] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_backtrack(n) + print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py new file mode 100644 index 000000000..ead4b32a2 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -0,0 +1,29 @@ +""" +File: climbing_stairs_constraint_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_constraint_dp(n: int) -> int: + """Подъем по лестнице с ограничениями: динамическое программирование""" + if n == 1 or n == 2: + return 1 + # Инициализация таблицы dp для хранения решений подзадач + dp = [[0] * 3 for _ in range(n + 1)] + # Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # Переход состояний: постепенное решение больших подзадач через меньшие + for i in range(3, n + 1): + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + return dp[n][1] + dp[n][2] + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_constraint_dp(n) + print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py new file mode 100644 index 000000000..ae5a6c8e6 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -0,0 +1,28 @@ +""" +File: climbing_stairs_dfs.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int) -> int: + """Поиск""" + # dp[1] и dp[2] уже известны, вернуть их + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count + + +def climbing_stairs_dfs(n: int) -> int: + """Подъем по лестнице: поиск""" + return dfs(n) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs(n) + print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py new file mode 100644 index 000000000..bf8dd88a3 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -0,0 +1,35 @@ +""" +File: climbing_stairs_dfs_mem.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int, mem: list[int]) -> int: + """Поиск с мемоизацией""" + # dp[1] и dp[2] уже известны, вернуть их + if i == 1 or i == 2: + return i + # Если запись dp[i] существует, сразу вернуть ее + if mem[i] != -1: + return mem[i] + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # Сохранить dp[i] + mem[i] = count + return count + + +def climbing_stairs_dfs_mem(n: int) -> int: + """Подъем по лестнице: поиск с мемоизацией""" + # mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + mem = [-1] * (n + 1) + return dfs(n, mem) + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs_mem(n) + print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py new file mode 100644 index 000000000..2d8153dba --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -0,0 +1,40 @@ +""" +File: climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_dp(n: int) -> int: + """Подъем по лестнице: динамическое программирование""" + if n == 1 or n == 2: + return n + # Инициализация таблицы dp для хранения решений подзадач + dp = [0] * (n + 1) + # Начальное состояние: заранее задать решения наименьших подзадач + dp[1], dp[2] = 1, 2 + # Переход состояний: постепенное решение больших подзадач через меньшие + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] + + +def climbing_stairs_dp_comp(n: int) -> int: + """Подъем по лестнице: динамическое программирование с оптимизацией памяти""" + if n == 1 or n == 2: + return n + a, b = 1, 2 + for _ in range(3, n + 1): + a, b = b, a + b + return b + + +"""Driver Code""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dp(n) + print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") + + res = climbing_stairs_dp_comp(n) + print(f"Количество способов подняться по лестнице из {n} ступеней = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/coin_change.py b/ru/codes/python/chapter_dynamic_programming/coin_change.py new file mode 100644 index 000000000..6fc9873d5 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/coin_change.py @@ -0,0 +1,60 @@ +""" +File: coin_change.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_dp(coins: list[int], amt: int) -> int: + """Размен монет: динамическое программирование""" + n = len(coins) + MAX = amt + 1 + # Инициализация таблицы dp + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # Переход состояний: первая строка и первый столбец + for a in range(1, amt + 1): + dp[0][a] = MAX + # Переход состояний: остальные строки и столбцы + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + else: + # Меньшее из двух решений: не брать или взять монету i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + return dp[n][amt] if dp[n][amt] != MAX else -1 + + +def coin_change_dp_comp(coins: list[int], amt: int) -> int: + """Размен монет: динамическое программирование с оптимизацией памяти""" + n = len(coins) + MAX = amt + 1 + # Инициализация таблицы dp + dp = [MAX] * (amt + 1) + dp[0] = 0 + # Переход состояний + for i in range(1, n + 1): + # Прямой обход + for a in range(1, amt + 1): + if coins[i - 1] > a: + # Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + else: + # Меньшее из двух решений: не брать или взять монету i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + return dp[amt] if dp[amt] != MAX else -1 + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 4 + + # Динамическое программирование + res = coin_change_dp(coins, amt) + print(f"Минимальное число монет для набора целевой суммы = {res}") + + # Динамическое программирование с оптимизацией памяти + res = coin_change_dp_comp(coins, amt) + print(f"Минимальное число монет для набора целевой суммы = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/coin_change_ii.py b/ru/codes/python/chapter_dynamic_programming/coin_change_ii.py new file mode 100644 index 000000000..85aa6c29b --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -0,0 +1,58 @@ +""" +File: coin_change_ii.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """Размен монет II: динамическое программирование""" + n = len(coins) + # Инициализация таблицы dp + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # Инициализация первого столбца + for i in range(n + 1): + dp[i][0] = 1 + # Переход состояний + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + else: + # Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + return dp[n][amt] + + +def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """Размен монет II: динамическое программирование с оптимизацией памяти""" + n = len(coins) + # Инициализация таблицы dp + dp = [0] * (amt + 1) + dp[0] = 1 + # Переход состояний + for i in range(1, n + 1): + # Прямой обход + for a in range(1, amt + 1): + if coins[i - 1] > a: + # Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + else: + # Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] + + +"""Driver Code""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 5 + + # Динамическое программирование + res = coin_change_ii_dp(coins, amt) + print(f"Количество комбинаций монет для набора целевой суммы = {res}") + + # Динамическое программирование с оптимизацией памяти + res = coin_change_ii_dp_comp(coins, amt) + print(f"Количество комбинаций монет для набора целевой суммы = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/edit_distance.py b/ru/codes/python/chapter_dynamic_programming/edit_distance.py new file mode 100644 index 000000000..522b4ba6e --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/edit_distance.py @@ -0,0 +1,123 @@ +""" +File: edit_distancde.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + + +def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: + """Редакционное расстояние: полный перебор""" + # Если s и t пусты, вернуть 0 + if i == 0 and j == 0: + return 0 + # Если s пусто, вернуть длину t + if i == 0: + return j + # Если t пусто, вернуть длину s + if j == 0: + return i + # Если два символа равны, сразу пропустить их + if s[i - 1] == t[j - 1]: + return edit_distance_dfs(s, t, i - 1, j - 1) + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # Вернуть минимальное число шагов редактирования + return min(insert, delete, replace) + 1 + + +def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: + """Редакционное расстояние: поиск с мемоизацией""" + # Если s и t пусты, вернуть 0 + if i == 0 and j == 0: + return 0 + # Если s пусто, вернуть длину t + if i == 0: + return j + # Если t пусто, вернуть длину s + if j == 0: + return i + # Если запись уже есть, сразу вернуть ее + if mem[i][j] != -1: + return mem[i][j] + # Если два символа равны, сразу пропустить их + if s[i - 1] == t[j - 1]: + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = min(insert, delete, replace) + 1 + return mem[i][j] + + +def edit_distance_dp(s: str, t: str) -> int: + """Редакционное расстояние: динамическое программирование""" + n, m = len(s), len(t) + dp = [[0] * (m + 1) for _ in range(n + 1)] + # Переход состояний: первая строка и первый столбец + for i in range(1, n + 1): + dp[i][0] = i + for j in range(1, m + 1): + dp[0][j] = j + # Переход состояний: остальные строки и столбцы + for i in range(1, n + 1): + for j in range(1, m + 1): + if s[i - 1] == t[j - 1]: + # Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1] + else: + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + return dp[n][m] + + +def edit_distance_dp_comp(s: str, t: str) -> int: + """Редакционное расстояние: динамическое программирование с оптимизацией памяти""" + n, m = len(s), len(t) + dp = [0] * (m + 1) + # Переход состояний: первая строка + for j in range(1, m + 1): + dp[j] = j + # Переход состояний: остальные строки + for i in range(1, n + 1): + # Переход состояний: первый столбец + leftup = dp[0] # Временно сохранить dp[i-1, j-1] + dp[0] += 1 + # Переход состояний: остальные столбцы + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # Если два символа равны, сразу пропустить их + dp[j] = leftup + else: + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # Обновить до значения dp[i-1, j-1] для следующей итерации + return dp[m] + + +"""Driver Code""" +if __name__ == "__main__": + s = "bag" + t = "pack" + n, m = len(s), len(t) + + # Полный перебор + res = edit_distance_dfs(s, t, n, m) + print(f"Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов") + + # Поиск с мемоизацией + mem = [[-1] * (m + 1) for _ in range(n + 1)] + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(f"Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов") + + # Динамическое программирование + res = edit_distance_dp(s, t) + print(f"Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов") + + # Динамическое программирование с оптимизацией памяти + res = edit_distance_dp_comp(s, t) + print(f"Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов") diff --git a/ru/codes/python/chapter_dynamic_programming/knapsack.py b/ru/codes/python/chapter_dynamic_programming/knapsack.py new file mode 100644 index 000000000..fd78f4fc6 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/knapsack.py @@ -0,0 +1,101 @@ +""" +File: knapsack.py +Created Time: 2023-07-03 +Author: krahets (krahets@163.com) +""" + + +def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """Рюкзак 0-1: полный перебор""" + # Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 or c == 0: + return 0 + # Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i - 1] > c: + return knapsack_dfs(wgt, val, i - 1, c) + # Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # Вернуть вариант с большей стоимостью из двух возможных + return max(no, yes) + + +def knapsack_dfs_mem( + wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int +) -> int: + """Рюкзак 0-1: поиск с мемоизацией""" + # Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 or c == 0: + return 0 + # Если запись уже есть, вернуть сразу + if mem[i][c] != -1: + return mem[i][c] + # Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i - 1] > c: + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) + # Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = max(no, yes) + return mem[i][c] + + +def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """Рюкзак 0-1: динамическое программирование""" + n = len(wgt) + # Инициализация таблицы dp + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # Переход состояний + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + else: + # Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """Рюкзак 0-1: динамическое программирование с оптимизацией памяти""" + n = len(wgt) + # Инициализация таблицы dp + dp = [0] * (cap + 1) + # Переход состояний + for i in range(1, n + 1): + # Обход в обратном порядке + for c in range(cap, 0, -1): + if wgt[i - 1] > c: + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + else: + # Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # Полный перебор + res = knapsack_dfs(wgt, val, n, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") + + # Поиск с мемоизацией + mem = [[-1] * (cap + 1) for _ in range(n + 1)] + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") + + # Динамическое программирование + res = knapsack_dp(wgt, val, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") + + # Динамическое программирование с оптимизацией памяти + res = knapsack_dp_comp(wgt, val, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py b/ru/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py new file mode 100644 index 000000000..41da56fa6 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -0,0 +1,43 @@ +""" +File: min_cost_climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def min_cost_climbing_stairs_dp(cost: list[int]) -> int: + """Минимальная стоимость подъема по лестнице: динамическое программирование""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + # Инициализация таблицы dp для хранения решений подзадач + dp = [0] * (n + 1) + # Начальное состояние: заранее задать решения наименьших подзадач + dp[1], dp[2] = cost[1], cost[2] + # Переход состояний: постепенное решение больших подзадач через меньшие + for i in range(3, n + 1): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return dp[n] + + +def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: + """Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b + + +"""Driver Code""" +if __name__ == "__main__": + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print(f"Список стоимостей ступеней = {cost}") + + res = min_cost_climbing_stairs_dp(cost) + print(f"Минимальная стоимость подъема по лестнице = {res}") + + res = min_cost_climbing_stairs_dp_comp(cost) + print(f"Минимальная стоимость подъема по лестнице = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/min_path_sum.py b/ru/codes/python/chapter_dynamic_programming/min_path_sum.py new file mode 100644 index 000000000..006631cec --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -0,0 +1,104 @@ +""" +File: min_path_sum.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + +from math import inf + + +def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: + """Минимальная сумма пути: полный перебор""" + # Если это верхняя левая ячейка, завершить поиск + if i == 0 and j == 0: + return grid[0][0] + # Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 or j < 0: + return inf + # Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return min(left, up) + grid[i][j] + + +def min_path_sum_dfs_mem( + grid: list[list[int]], mem: list[list[int]], i: int, j: int +) -> int: + """Минимальная сумма пути: поиск с мемоизацией""" + # Если это верхняя левая ячейка, завершить поиск + if i == 0 and j == 0: + return grid[0][0] + # Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 or j < 0: + return inf + # Если запись уже есть, вернуть сразу + if mem[i][j] != -1: + return mem[i][j] + # Минимальная стоимость пути для левой и верхней ячеек + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + + +def min_path_sum_dp(grid: list[list[int]]) -> int: + """Минимальная сумма пути: динамическое программирование""" + n, m = len(grid), len(grid[0]) + # Инициализация таблицы dp + dp = [[0] * m for _ in range(n)] + dp[0][0] = grid[0][0] + # Переход состояний: первая строка + for j in range(1, m): + dp[0][j] = dp[0][j - 1] + grid[0][j] + # Переход состояний: первый столбец + for i in range(1, n): + dp[i][0] = dp[i - 1][0] + grid[i][0] + # Переход состояний: остальные строки и столбцы + for i in range(1, n): + for j in range(1, m): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + return dp[n - 1][m - 1] + + +def min_path_sum_dp_comp(grid: list[list[int]]) -> int: + """Минимальная сумма пути: динамическое программирование с оптимизацией памяти""" + n, m = len(grid), len(grid[0]) + # Инициализация таблицы dp + dp = [0] * m + # Переход состояний: первая строка + dp[0] = grid[0][0] + for j in range(1, m): + dp[j] = dp[j - 1] + grid[0][j] + # Переход состояний: остальные строки + for i in range(1, n): + # Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0] + # Переход состояний: остальные столбцы + for j in range(1, m): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] + + +"""Driver Code""" +if __name__ == "__main__": + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = len(grid), len(grid[0]) + + # Полный перебор + res = min_path_sum_dfs(grid, n - 1, m - 1) + print(f"Минимальная сумма пути из левого верхнего угла в правый нижний = {res}") + + # Поиск с мемоизацией + mem = [[-1] * m for _ in range(n)] + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(f"Минимальная сумма пути из левого верхнего угла в правый нижний = {res}") + + # Динамическое программирование + res = min_path_sum_dp(grid) + print(f"Минимальная сумма пути из левого верхнего угла в правый нижний = {res}") + + # Динамическое программирование с оптимизацией памяти + res = min_path_sum_dp_comp(grid) + print(f"Минимальная сумма пути из левого верхнего угла в правый нижний = {res}") diff --git a/ru/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/ru/codes/python/chapter_dynamic_programming/unbounded_knapsack.py new file mode 100644 index 000000000..2db554835 --- /dev/null +++ b/ru/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -0,0 +1,55 @@ +""" +File: unbounded_knapsack.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """Полный рюкзак: динамическое программирование""" + n = len(wgt) + # Инициализация таблицы dp + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # Переход состояний + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + else: + # Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] + + +def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """Полный рюкзак: динамическое программирование с оптимизацией памяти""" + n = len(wgt) + # Инициализация таблицы dp + dp = [0] * (cap + 1) + # Переход состояний + for i in range(1, n + 1): + # Прямой обход + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + else: + # Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # Динамическое программирование + res = unbounded_knapsack_dp(wgt, val, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") + + # Динамическое программирование с оптимизацией памяти + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") diff --git a/ru/codes/python/chapter_graph/graph_adjacency_list.py b/ru/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 000000000..a38fbb940 --- /dev/null +++ b/ru/codes/python/chapter_graph/graph_adjacency_list.py @@ -0,0 +1,111 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets + + +class GraphAdjList: + """Класс неориентированного графа на основе списка смежности""" + + def __init__(self, edges: list[list[Vertex]]): + """Конструктор""" + # Список смежности, где key — вершина, а value — все смежные ей вершины + self.adj_list = dict[Vertex, list[Vertex]]() + # Добавить все вершины и ребра + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """Получить число вершин""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """Добавление ребра""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # Добавить ребро vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """Удаление ребра""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # Удалить ребро vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """Добавление вершины""" + if vet in self.adj_list: + return + # Добавить новый список в список смежности + self.adj_list[vet] = [] + + def remove_vertex(self, vet: Vertex): + """Удаление вершины""" + if vet not in self.adj_list: + raise ValueError() + # Удалить из списка смежности список, соответствующий вершине vet + self.adj_list.pop(vet) + # Обойти списки других вершин и удалить все ребра, содержащие vet + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """Вывести список смежности""" + print("Список смежности =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация неориентированного графа + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList(edges) + print("\nГраф после инициализации") + graph.print() + + # Добавить ребро + # Вершины 1 и 2, то есть v[0] и v[2] + graph.add_edge(v[0], v[2]) + print("\nГраф после добавления ребра 1-2") + graph.print() + + # Удалить ребро + # Вершины 1 и 3 соответствуют v[0] и v[1] + graph.remove_edge(v[0], v[1]) + print("\nГраф после удаления ребра 1-3") + graph.print() + + # Добавление вершины + v5 = Vertex(6) + graph.add_vertex(v5) + print("\nГраф после добавления вершины 6") + graph.print() + + # Удаление вершины + # Вершина 3 соответствует v[1] + graph.remove_vertex(v[1]) + print("\nГраф после удаления вершины 3") + graph.print() diff --git a/ru/codes/python/chapter_graph/graph_adjacency_matrix.py b/ru/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 000000000..90bd374de --- /dev/null +++ b/ru/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -0,0 +1,116 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, print_matrix + + +class GraphAdjMat: + """Класс неориентированного графа на основе матрицы смежности""" + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """Конструктор""" + # Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + self.vertices: list[int] = [] + # Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + self.adj_mat: list[list[int]] = [] + # Добавление вершины + for val in vertices: + self.add_vertex(val) + # Добавить ребра + # Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """Получить число вершин""" + return len(self.vertices) + + def add_vertex(self, val: int): + """Добавление вершины""" + n = self.size() + # Добавить значение новой вершины в список вершин + self.vertices.append(val) + # Добавить строку в матрицу смежности + new_row = [0] * n + self.adj_mat.append(new_row) + # Добавить столбец в матрицу смежности + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """Удаление вершины""" + if index >= self.size(): + raise IndexError() + # Удалить вершину с индексом index из списка вершин + self.vertices.pop(index) + # Удалить строку с индексом index из матрицы смежности + self.adj_mat.pop(index) + # Удалить столбец с индексом index из матрицы смежности + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """Добавление ребра""" + # Параметры i и j соответствуют индексам элементов vertices + # Обработка выхода индекса за границы и случая равенства + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """Удаление ребра""" + # Параметры i и j соответствуют индексам элементов vertices + # Обработка выхода индекса за границы и случая равенства + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """Вывести матрицу смежности""" + print("Список вершин =", self.vertices) + print("Матрица смежности =") + print_matrix(self.adj_mat) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация неориентированного графа + # Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat(vertices, edges) + print("\nГраф после инициализации") + graph.print() + + # Добавление ребра + # Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.add_edge(0, 2) + print("\nГраф после добавления ребра 1-2") + graph.print() + + # Удалить ребро + # Индексы вершин 1 и 3 равны 0 и 1 + graph.remove_edge(0, 1) + print("\nГраф после удаления ребра 1-3") + graph.print() + + # Добавление вершины + graph.add_vertex(6) + print("\nГраф после добавления вершины 6") + graph.print() + + # Удаление вершины + # Индекс вершины 3 равен 1 + graph.remove_vertex(1) + print("\nГраф после удаления вершины 3") + graph.print() diff --git a/ru/codes/python/chapter_graph/graph_bfs.py b/ru/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 000000000..92ece63eb --- /dev/null +++ b/ru/codes/python/chapter_graph/graph_bfs.py @@ -0,0 +1,64 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets, vets_to_vals +from collections import deque +from graph_adjacency_list import GraphAdjList + + +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """Обход в ширину""" + # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины + # Последовательность обхода вершин + res = [] + # Хеш-множество для хранения уже посещенных вершин + visited = set[Vertex]([start_vet]) + # Очередь используется для реализации BFS + que = deque[Vertex]([start_vet]) + # Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while len(que) > 0: + vet = que.popleft() # Извлечь головную вершину из очереди + res.append(vet) # Отметить посещенную вершину + # Обойти все смежные вершины данной вершины + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # Пропустить уже посещенную вершину + que.append(adj_vet) # Помещать в очередь только непосещенные вершины + visited.add(adj_vet) # Отметить эту вершину как посещенную + # Вернуть последовательность обхода вершин + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация неориентированного графа + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ] + graph = GraphAdjList(edges) + print("\nГраф после инициализации") + graph.print() + + # Обход в ширину + res = graph_bfs(graph, v[0]) + print("\nПоследовательность вершин при обходе в ширину (BFS)") + print(vets_to_vals(res)) diff --git a/ru/codes/python/chapter_graph/graph_dfs.py b/ru/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 000000000..86fb28446 --- /dev/null +++ b/ru/codes/python/chapter_graph/graph_dfs.py @@ -0,0 +1,57 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vets_to_vals, vals_to_vets +from graph_adjacency_list import GraphAdjList + + +def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """Вспомогательная функция обхода в глубину""" + res.append(vet) # Отметить посещенную вершину + visited.add(vet) # Отметить эту вершину как посещенную + # Обойти все смежные вершины данной вершины + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # Пропустить уже посещенную вершину + # Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet) + + +def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """Обход в глубину""" + # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины + # Последовательность обхода вершин + res = [] + # Хеш-множество для хранения уже посещенных вершин + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация неориентированного графа + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList(edges) + print("\nГраф после инициализации") + graph.print() + + # Обход в глубину + res = graph_dfs(graph, v[0]) + print("\nПоследовательность вершин при обходе в глубину (DFS)") + print(vets_to_vals(res)) diff --git a/ru/codes/python/chapter_greedy/coin_change_greedy.py b/ru/codes/python/chapter_greedy/coin_change_greedy.py new file mode 100644 index 000000000..9f82fdc49 --- /dev/null +++ b/ru/codes/python/chapter_greedy/coin_change_greedy.py @@ -0,0 +1,48 @@ +""" +File: coin_change_greedy.py +Created Time: 2023-07-18 +Author: krahets (krahets@163.com) +""" + + +def coin_change_greedy(coins: list[int], amt: int) -> int: + """Размен монет: жадный алгоритм""" + # Предположить, что список coins упорядочен + i = len(coins) - 1 + count = 0 + # Циклически выполнять жадный выбор, пока не останется суммы + while amt > 0: + # Найти монету, которая меньше остатка суммы и наиболее к нему близка + while i > 0 and coins[i] > amt: + i -= 1 + # Выбрать coins[i] + amt -= coins[i] + count += 1 + # Если допустимое решение не найдено, вернуть -1 + return count if amt == 0 else -1 + + +"""Driver Code""" +if __name__ == "__main__": + # Жадный подход: гарантирует нахождение глобально оптимального решения + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"Минимальное число монет для набора суммы {amt} = {res}") + + # Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"Минимальное число монет для набора суммы {amt} = {res}") + print(f"На самом деле минимум равен 3: 20 + 20 + 20") + + # Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + print(f"\ncoins = {coins}, amt = {amt}") + print(f"Минимальное число монет для набора суммы {amt} = {res}") + print(f"На самом деле минимум равен 2: 49 + 49") diff --git a/ru/codes/python/chapter_greedy/fractional_knapsack.py b/ru/codes/python/chapter_greedy/fractional_knapsack.py new file mode 100644 index 000000000..c91416aec --- /dev/null +++ b/ru/codes/python/chapter_greedy/fractional_knapsack.py @@ -0,0 +1,46 @@ +""" +File: fractional_knapsack.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + + +class Item: + """Предмет""" + + def __init__(self, w: int, v: int): + self.w = w # Вес предмета + self.v = v # Стоимость предмета + + +def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """Дробный рюкзак: жадный алгоритм""" + # Создать список предметов с двумя свойствами: вес и стоимость + items = [Item(w, v) for w, v in zip(wgt, val)] + # Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort(key=lambda item: item.v / item.w, reverse=True) + # Циклический жадный выбор + res = 0 + for item in items: + if item.w <= cap: + # Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v + cap -= item.w + else: + # Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (item.v / item.w) * cap + # Свободной вместимости больше не осталось, поэтому выйти из цикла + break + return res + + +"""Driver Code""" +if __name__ == "__main__": + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = len(wgt) + + # Жадный алгоритм + res = fractional_knapsack(wgt, val, cap) + print(f"Максимальная стоимость предметов без превышения вместимости рюкзака = {res}") diff --git a/ru/codes/python/chapter_greedy/max_capacity.py b/ru/codes/python/chapter_greedy/max_capacity.py new file mode 100644 index 000000000..d94a3df4d --- /dev/null +++ b/ru/codes/python/chapter_greedy/max_capacity.py @@ -0,0 +1,33 @@ +""" +File: max_capacity.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + + +def max_capacity(ht: list[int]) -> int: + """Максимальная вместимость: жадный алгоритм""" + # Инициализировать i и j так, чтобы они располагались по двум концам массива + i, j = 0, len(ht) - 1 + # Начальная максимальная вместимость равна 0 + res = 0 + # Выполнять жадный выбор в цикле, пока две доски не встретятся + while i < j: + # Обновить максимальную вместимость + cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + # Сдвигать внутрь более короткую сторону + if ht[i] < ht[j]: + i += 1 + else: + j -= 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # Жадный алгоритм + res = max_capacity(ht) + print(f"Максимальная вместимость = {res}") diff --git a/ru/codes/python/chapter_greedy/max_product_cutting.py b/ru/codes/python/chapter_greedy/max_product_cutting.py new file mode 100644 index 000000000..f51ac647f --- /dev/null +++ b/ru/codes/python/chapter_greedy/max_product_cutting.py @@ -0,0 +1,33 @@ +""" +File: max_product_cutting.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + +import math + + +def max_product_cutting(n: int) -> int: + """Максимальное произведение разрезания: жадный алгоритм""" + # Когда n <= 3, обязательно нужно выделить одну 1 + if n <= 3: + return 1 * (n - 1) + # Жадно выделить множители 3, где a — число троек, а b — остаток + a, b = n // 3, n % 3 + if b == 1: + # Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return int(math.pow(3, a - 1)) * 2 * 2 + if b == 2: + # Если остаток равен 2, ничего не делать + return int(math.pow(3, a)) * 2 + # Если остаток равен 0, ничего не делать + return int(math.pow(3, a)) + + +"""Driver Code""" +if __name__ == "__main__": + n = 58 + + # Жадный алгоритм + res = max_product_cutting(n) + print(f"Максимальное произведение после разрезания = {res}") diff --git a/ru/codes/python/chapter_hashing/array_hash_map.py b/ru/codes/python/chapter_hashing/array_hash_map.py new file mode 100644 index 000000000..1cfb9176f --- /dev/null +++ b/ru/codes/python/chapter_hashing/array_hash_map.py @@ -0,0 +1,117 @@ +""" +File: array_hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + + +class Pair: + """Пара ключ-значение""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + +class ArrayHashMap: + """Хеш-таблица на основе массива""" + + def __init__(self): + """Конструктор""" + # Инициализировать массив, содержащий 100 корзин + self.buckets: list[Pair | None] = [None] * 100 + + def hash_func(self, key: int) -> int: + """Хеш-функция""" + index = key % 100 + return index + + def get(self, key: int) -> str | None: + """Операция поиска""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] + if pair is None: + return None + return pair.val + + def put(self, key: int, val: str): + """Операции добавления и обновления""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair + + def remove(self, key: int): + """Операция удаления""" + index: int = self.hash_func(key) + # Присвоить None, что означает удаление + self.buckets[index] = None + + def entry_set(self) -> list[Pair]: + """Получить все пары ключ-значение""" + result: list[Pair] = [] + for pair in self.buckets: + if pair is not None: + result.append(pair) + return result + + def key_set(self) -> list[int]: + """Получить все ключи""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.key) + return result + + def value_set(self) -> list[str]: + """Получить все значения""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.val) + return result + + def print(self): + """Вывести хеш-таблицу""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация хеш-таблицы + hmap = ArrayHashMap() + + # Операция добавления + # Добавить пару (key, value) в хеш-таблицу + hmap.put(12836, "Сяо Ха") + hmap.put(15937, "Сяо Ло") + hmap.put(16750, "Сяо Суань") + hmap.put(13276, "Сяо Фа") + hmap.put(10583, "Сяо Я") + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение value + name = hmap.get(15937) + print("\nДля номера 15937 найдено имя " + name) + + # Операция удаления + # Удалить пару (key, value) из хеш-таблицы + hmap.remove(10583) + print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + hmap.print() + + # Обход хеш-таблицы + print("\nОтдельный обход пар ключ-значение") + for pair in hmap.entry_set(): + print(pair.key, "->", pair.val) + + print("\nОтдельный обход ключей") + for key in hmap.key_set(): + print(key) + + print("\nОтдельный обход значений") + for val in hmap.value_set(): + print(val) diff --git a/ru/codes/python/chapter_hashing/built_in_hash.py b/ru/codes/python/chapter_hashing/built_in_hash.py new file mode 100644 index 000000000..85dc95db4 --- /dev/null +++ b/ru/codes/python/chapter_hashing/built_in_hash.py @@ -0,0 +1,37 @@ +""" +File: built_in_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + +"""Driver Code""" +if __name__ == "__main__": + num = 3 + hash_num = hash(num) + print(f"Хеш-значение целого числа {num} = {hash_num}") + + bol = True + hash_bol = hash(bol) + print(f"Хеш-значение булева значения {bol} = {hash_bol}") + + dec = 3.14159 + hash_dec = hash(dec) + print(f"Хеш-значение десятичного числа {dec} = {hash_dec}") + + str = "Hello Algo" + hash_str = hash(str) + print(f"Хеш-значение строки {str} = {hash_str}") + + tup = (12836, "Сяо Ха") + hash_tup = hash(tup) + print(f"Хеш-значение кортежа {tup} = {hash(hash_tup)}") + + obj = ListNode(0) + hash_obj = hash(obj) + print(f"Хеш-значение объекта узла {obj} = {hash_obj}") diff --git a/ru/codes/python/chapter_hashing/hash_map.py b/ru/codes/python/chapter_hashing/hash_map.py new file mode 100644 index 000000000..48ebc1f89 --- /dev/null +++ b/ru/codes/python/chapter_hashing/hash_map.py @@ -0,0 +1,50 @@ +""" +File: hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_dict + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация хеш-таблицы + hmap = dict[int, str]() + + # Операция добавления + # Добавить пару (key, value) в хеш-таблицу + hmap[12836] = "Сяо Ха" + hmap[15937] = "Сяо Ло" + hmap[16750] = "Сяо Суань" + hmap[13276] = "Сяо Фа" + hmap[10583] = "Сяо Я" + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + print_dict(hmap) + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение value + name: str = hmap[15937] + print("\nДля номера 15937 найдено имя " + name) + + # Операция удаления + # Удалить пару (key, value) из хеш-таблицы + hmap.pop(10583) + print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + print_dict(hmap) + + # Обход хеш-таблицы + print("\nОтдельный обход пар ключ-значение") + for key, value in hmap.items(): + print(key, "->", value) + + print("\nОтдельный обход ключей") + for key in hmap.keys(): + print(key) + + print("\nОтдельный обход значений") + for val in hmap.values(): + print(val) diff --git a/ru/codes/python/chapter_hashing/hash_map_chaining.py b/ru/codes/python/chapter_hashing/hash_map_chaining.py new file mode 100644 index 000000000..32a653833 --- /dev/null +++ b/ru/codes/python/chapter_hashing/hash_map_chaining.py @@ -0,0 +1,118 @@ +""" +File: hash_map_chaining.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapChaining: + """Хеш-таблица с цепочками""" + + def __init__(self): + """Конструктор""" + self.size = 0 # Число пар ключ-значение + self.capacity = 4 # Вместимость хеш-таблицы + self.load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения + self.extend_ratio = 2 # Коэффициент расширения + self.buckets = [[] for _ in range(self.capacity)] # Массив корзин + + def hash_func(self, key: int) -> int: + """Хеш-функция""" + return key % self.capacity + + def load_factor(self) -> float: + """Коэффициент загрузки""" + return self.size / self.capacity + + def get(self, key: int) -> str | None: + """Операция поиска""" + index = self.hash_func(key) + bucket = self.buckets[index] + # Обойти корзину; если найден key, вернуть соответствующее val + for pair in bucket: + if pair.key == key: + return pair.val + # Если key не найден, вернуть None + return None + + def put(self, key: int, val: str): + """Операция добавления""" + # Когда коэффициент загрузки превышает порог, выполнить расширение + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for pair in bucket: + if pair.key == key: + pair.val = val + return + # Если такого key нет, добавить пару ключ-значение в конец + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """Операция удаления""" + index = self.hash_func(key) + bucket = self.buckets[index] + # Обойти корзину и удалить из нее пару ключ-значение + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break + + def extend(self): + """Расширить хеш-таблицу""" + # Временно сохранить исходную хеш-таблицу + buckets = self.buckets + # Инициализация новой хеш-таблицы после расширения + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # Перенести пары ключ-значение из исходной хеш-таблицы в новую + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) + + def print(self): + """Вывести хеш-таблицу""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация хеш-таблицы + hashmap = HashMapChaining() + + # Операция добавления + # Добавить пару (key, value) в хеш-таблицу + hashmap.put(12836, "Сяо Ха") + hashmap.put(15937, "Сяо Ло") + hashmap.put(16750, "Сяо Суань") + hashmap.put(13276, "Сяо Фа") + hashmap.put(10583, "Сяо Я") + print("\nПосле завершения добавления хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение value + name = hashmap.get(13276) + print("\nДля номера 13276 найдено имя " + name) + + # Операция удаления + # Удалить пару (key, value) из хеш-таблицы + hashmap.remove(12836) + print("\nПосле удаления 12836 хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() diff --git a/ru/codes/python/chapter_hashing/hash_map_open_addressing.py b/ru/codes/python/chapter_hashing/hash_map_open_addressing.py new file mode 100644 index 000000000..1f9bf3da7 --- /dev/null +++ b/ru/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -0,0 +1,138 @@ +""" +File: hash_map_open_addressing.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapOpenAddressing: + """Хеш-таблица с открытой адресацией""" + + def __init__(self): + """Конструктор""" + self.size = 0 # Число пар ключ-значение + self.capacity = 4 # Вместимость хеш-таблицы + self.load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения + self.extend_ratio = 2 # Коэффициент расширения + self.buckets: list[Pair | None] = [None] * self.capacity # Массив корзин + self.TOMBSTONE = Pair(-1, "-1") # Удалить метку + + def hash_func(self, key: int) -> int: + """Хеш-функция""" + return key % self.capacity + + def load_factor(self) -> float: + """Коэффициент загрузки""" + return self.size / self.capacity + + def find_bucket(self, key: int) -> int: + """Найти индекс корзины, соответствующий key""" + index = self.hash_func(key) + first_tombstone = -1 + # Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while self.buckets[index] is not None: + # Если встретился key, вернуть соответствующий индекс корзины + if self.buckets[index].key == key: + # Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if first_tombstone != -1: + self.buckets[first_tombstone] = self.buckets[index] + self.buckets[index] = self.TOMBSTONE + return first_tombstone # Вернуть индекс корзины после перемещения + return index # Вернуть индекс корзины + # Записать первую встретившуюся метку удаления + if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: + first_tombstone = index + # Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % self.capacity + # Если key не существует, вернуть индекс точки добавления + return index if first_tombstone == -1 else first_tombstone + + def get(self, key: int) -> str: + """Операция поиска""" + # Найти индекс корзины, соответствующий key + index = self.find_bucket(key) + # Если пара ключ-значение найдена, вернуть соответствующее val + if self.buckets[index] not in [None, self.TOMBSTONE]: + return self.buckets[index].val + # Если пара ключ-значение не существует, вернуть None + return None + + def put(self, key: int, val: str): + """Операция добавления""" + # Когда коэффициент загрузки превышает порог, выполнить расширение + if self.load_factor() > self.load_thres: + self.extend() + # Найти индекс корзины, соответствующий key + index = self.find_bucket(key) + # Если пара ключ-значение найдена, перезаписать val и вернуть + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index].val = val + return + # Если пары ключ-значение нет, добавить ее + self.buckets[index] = Pair(key, val) + self.size += 1 + + def remove(self, key: int): + """Операция удаления""" + # Найти индекс корзины, соответствующий key + index = self.find_bucket(key) + # Если пара ключ-значение найдена, заменить ее меткой удаления + if self.buckets[index] not in [None, self.TOMBSTONE]: + self.buckets[index] = self.TOMBSTONE + self.size -= 1 + + def extend(self): + """Расширить хеш-таблицу""" + # Временно сохранить исходную хеш-таблицу + buckets_tmp = self.buckets + # Инициализация новой хеш-таблицы после расширения + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # Перенести пары ключ-значение из исходной хеш-таблицы в новую + for pair in buckets_tmp: + if pair not in [None, self.TOMBSTONE]: + self.put(pair.key, pair.val) + + def print(self): + """Вывести хеш-таблицу""" + for pair in self.buckets: + if pair is None: + print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация хеш-таблицы + hashmap = HashMapOpenAddressing() + + # Операция добавления + # Добавить пару (key, val) в хеш-таблицу + hashmap.put(12836, "Сяо Ха") + hashmap.put(15937, "Сяо Ло") + hashmap.put(16750, "Сяо Суань") + hashmap.put(13276, "Сяо Фа") + hashmap.put(10583, "Сяо Я") + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + hashmap.print() + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение val + name = hashmap.get(13276) + print("\nДля номера 13276 найдено имя " + name) + + # Операция удаления + # Удалить пару (key, val) из хеш-таблицы + hashmap.remove(16750) + print("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") + hashmap.print() diff --git a/ru/codes/python/chapter_hashing/simple_hash.py b/ru/codes/python/chapter_hashing/simple_hash.py new file mode 100644 index 000000000..dedfebe0b --- /dev/null +++ b/ru/codes/python/chapter_hashing/simple_hash.py @@ -0,0 +1,58 @@ +""" +File: simple_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + + +def add_hash(key: str) -> int: + """Аддитивное хеширование""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus + + +def mul_hash(key: str) -> int: + """Мультипликативное хеширование""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus + + +def xor_hash(key: str) -> int: + """XOR-хеширование""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus + + +def rot_hash(key: str) -> int: + """Хеширование с циклическим сдвигом""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus + + +"""Driver Code""" +if __name__ == "__main__": + key = "Hello Algo" + + hash = add_hash(key) + print(f"Хеш-сумма сложением = {hash}") + + hash = mul_hash(key) + print(f"Хеш-сумма умножением = {hash}") + + hash = xor_hash(key) + print(f"Хеш-сумма XOR = {hash}") + + hash = rot_hash(key) + print(f"Хеш-сумма с циклическим сдвигом = {hash}") diff --git a/ru/codes/python/chapter_heap/heap.py b/ru/codes/python/chapter_heap/heap.py new file mode 100644 index 000000000..bf3a1d2f2 --- /dev/null +++ b/ru/codes/python/chapter_heap/heap.py @@ -0,0 +1,71 @@ +""" +File: heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def test_push(heap: list, val: int, flag: int = 1): + heapq.heappush(heap, flag * val) # Добавление элемента в кучу + print(f"\nПосле добавления элемента {val} в кучу") + print_heap([flag * val for val in heap]) + + +def test_pop(heap: list, flag: int = 1): + val = flag * heapq.heappop(heap) # Извлечение элемента с вершины кучи + print(f"\nПосле извлечения элемента вершины кучи {val}") + print_heap([flag * val for val in heap]) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация минимальной кучи + min_heap, flag = [], 1 + # Инициализация максимальной кучи + max_heap, flag = [], -1 + + print("\nНиже приведен тестовый пример для max-heap") + # Модуль heapq в Python по умолчанию реализует минимальную кучу + # Можно помещать в кучу отрицательные элементы, чтобы инвертировать отношение порядка и таким образом реализовать максимальную кучу + # В этом примере flag = 1 соответствует минимальной куче, а flag = -1 — максимальной куче + + # Добавление элемента в кучу + test_push(max_heap, 1, flag) + test_push(max_heap, 3, flag) + test_push(max_heap, 2, flag) + test_push(max_heap, 5, flag) + test_push(max_heap, 4, flag) + + # Получение элемента с вершины кучи + peek: int = flag * max_heap[0] + print(f"\nЭлемент на вершине кучи = {peek}") + + # Извлечение элемента с вершины кучи + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + # Получение размера кучи + size: int = len(max_heap) + print(f"\nКоличество элементов в куче = {size}") + + # Проверка, пуста ли куча + is_empty: bool = not max_heap + print(f"\nПуста ли куча: {is_empty}") + + # Создать кучу по входному списку + # Временная сложность равна O(n), а не O(nlogn) + min_heap = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + print("\nПосле построения min-heap из входного списка") + print_heap(min_heap) diff --git a/ru/codes/python/chapter_heap/my_heap.py b/ru/codes/python/chapter_heap/my_heap.py new file mode 100644 index 000000000..05252b0de --- /dev/null +++ b/ru/codes/python/chapter_heap/my_heap.py @@ -0,0 +1,137 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + + +class MaxHeap: + """Максимальная куча""" + + def __init__(self, nums: list[int]): + """Конструктор, строящий кучу по входному списку""" + # Добавить элементы списка в кучу без изменений + self.max_heap = nums + # Выполнить heapify для всех узлов, кроме листовых + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) + + def left(self, i: int) -> int: + """Получить индекс левого дочернего узла""" + return 2 * i + 1 + + def right(self, i: int) -> int: + """Получить индекс правого дочернего узла""" + return 2 * i + 2 + + def parent(self, i: int) -> int: + """Получить индекс родительского узла""" + return (i - 1) // 2 # Округление вниз при делении + + def swap(self, i: int, j: int): + """Поменять элементы местами""" + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] + + def size(self) -> int: + """Получение размера кучи""" + return len(self.max_heap) + + def is_empty(self) -> bool: + """Проверка, пуста ли куча""" + return self.size() == 0 + + def peek(self) -> int: + """Доступ к элементу на вершине кучи""" + return self.max_heap[0] + + def push(self, val: int): + """Добавление элемента в кучу""" + # Добавление узла + self.max_heap.append(val) + # Просеивание снизу вверх + self.sift_up(self.size() - 1) + + def sift_up(self, i: int): + """Начиная с узла i, выполнить просеивание снизу вверх""" + while True: + # Получение родительского узла для узла i + p = self.parent(i) + # Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # Поменять два узла местами + self.swap(i, p) + # Циклическое просеивание вверх + i = p + + def pop(self) -> int: + """Извлечение элемента из кучи""" + # Обработка пустого случая + if self.is_empty(): + raise IndexError("куча пуста") + # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + self.swap(0, self.size() - 1) + # Удаление узла + val = self.max_heap.pop() + # Просеивание сверху вниз + self.sift_down(0) + # Вернуть элемент с вершины кучи + return val + + def sift_down(self, i: int): + """Начиная с узла i, выполнить просеивание сверху вниз""" + while True: + # Определить узел с максимальным значением среди i, l и r и обозначить его как ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i: + break + # Поменять два узла местами + self.swap(i, ma) + # Циклическое просеивание вниз + i = ma + + def print(self): + """Вывести кучу (двоичное дерево)""" + print_heap(self.max_heap) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация максимальной кучи + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\nПосле построения кучи из входного списка") + max_heap.print() + + # Получение элемента с вершины кучи + peek = max_heap.peek() + print(f"\nЭлемент на вершине кучи = {peek}") + + # Добавление элемента в кучу + val = 7 + max_heap.push(val) + print(f"\nПосле добавления элемента {val} в кучу") + max_heap.print() + + # Извлечение элемента с вершины кучи + peek = max_heap.pop() + print(f"\nПосле извлечения элемента вершины кучи {peek}") + max_heap.print() + + # Получение размера кучи + size = max_heap.size() + print(f"\nКоличество элементов в куче = {size}") + + # Проверка, пуста ли куча + is_empty = max_heap.is_empty() + print(f"\nПуста ли куча: {is_empty}") diff --git a/ru/codes/python/chapter_heap/top_k.py b/ru/codes/python/chapter_heap/top_k.py new file mode 100644 index 000000000..e8d36ad0d --- /dev/null +++ b/ru/codes/python/chapter_heap/top_k.py @@ -0,0 +1,39 @@ +""" +File: top_k.py +Created Time: 2023-06-10 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def top_k_heap(nums: list[int], k: int) -> list[int]: + """Найти k наибольших элементов массива с помощью кучи""" + # Инициализация минимальной кучи + heap = [] + # Поместить первые k элементов массива в кучу + for i in range(k): + heapq.heappush(heap, nums[i]) + # Начиная с элемента k+1, поддерживать длину кучи равной k + for i in range(k, len(nums)): + # Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + print(f"Наибольшие {k} элементов") + print_heap(res) diff --git a/ru/codes/python/chapter_searching/binary_search.py b/ru/codes/python/chapter_searching/binary_search.py new file mode 100644 index 000000000..2a7177656 --- /dev/null +++ b/ru/codes/python/chapter_searching/binary_search.py @@ -0,0 +1,52 @@ +""" +File: binary_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + + +def binary_search(nums: list[int], target: int) -> int: + """Бинарный поиск (двусторонне замкнутый интервал)""" + # Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + i, j = 0, len(nums) - 1 + # Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while i <= j: + # Теоретически числа в Python могут быть сколь угодно большими (ограничены только объемом памяти), поэтому не нужно учитывать переполнение больших чисел + m = (i + j) // 2 # Вычислить индекс середины m + if nums[m] < target: + i = m + 1 # Это означает, что target находится в интервале [m+1, j] + elif nums[m] > target: + j = m - 1 # Это означает, что target находится в интервале [i, m-1] + else: + return m # Целевой элемент найден, вернуть его индекс + return -1 # Целевой элемент не найден, вернуть -1 + + +def binary_search_lcro(nums: list[int], target: int) -> int: + """Бинарный поиск (лево замкнутый, право открытый интервал)""" + # Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + i, j = 0, len(nums) + # Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while i < j: + m = (i + j) // 2 # Вычислить индекс середины m + if nums[m] < target: + i = m + 1 # Это означает, что target находится в интервале [m+1, j) + elif nums[m] > target: + j = m # Это означает, что target находится в интервале [i, m) + else: + return m # Целевой элемент найден, вернуть его индекс + return -1 # Целевой элемент не найден, вернуть -1 + + +"""Driver Code""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # Бинарный поиск (двусторонне замкнутый интервал) + index = binary_search(nums, target) + print("Индекс целевого элемента 6 = ", index) + + # Бинарный поиск (лево замкнутый, право открытый интервал) + index = binary_search_lcro(nums, target) + print("Индекс целевого элемента 6 = ", index) diff --git a/ru/codes/python/chapter_searching/binary_search_edge.py b/ru/codes/python/chapter_searching/binary_search_edge.py new file mode 100644 index 000000000..05f1684bb --- /dev/null +++ b/ru/codes/python/chapter_searching/binary_search_edge.py @@ -0,0 +1,49 @@ +""" +File: binary_search_edge.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from binary_search_insertion import binary_search_insertion + + +def binary_search_left_edge(nums: list[int], target: int) -> int: + """Бинарный поиск самого левого target""" + # Эквивалентно поиску точки вставки target + i = binary_search_insertion(nums, target) + # target не найден, вернуть -1 + if i == len(nums) or nums[i] != target: + return -1 + # Найти target и вернуть индекс i + return i + + +def binary_search_right_edge(nums: list[int], target: int) -> int: + """Бинарный поиск самого правого target""" + # Преобразовать задачу в поиск самого левого target + 1 + i = binary_search_insertion(nums, target + 1) + # j указывает на самый правый target, а i — на первый элемент больше target + j = i - 1 + # target не найден, вернуть -1 + if j == -1 or nums[j] != target: + return -1 + # Найти target и вернуть индекс j + return j + + +"""Driver Code""" +if __name__ == "__main__": + # Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\nМассив nums = {nums}") + + # Бинарный поиск левой и правой границы + for target in [6, 7]: + index = binary_search_left_edge(nums, target) + print(f"Индекс самого левого элемента {target} равен {index}") + index = binary_search_right_edge(nums, target) + print(f"Индекс самого правого элемента {target} равен {index}") diff --git a/ru/codes/python/chapter_searching/binary_search_insertion.py b/ru/codes/python/chapter_searching/binary_search_insertion.py new file mode 100644 index 000000000..971a4c337 --- /dev/null +++ b/ru/codes/python/chapter_searching/binary_search_insertion.py @@ -0,0 +1,54 @@ +""" +File: binary_search_insertion.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + + +def binary_search_insertion_simple(nums: list[int], target: int) -> int: + """Бинарный поиск точки вставки (без повторяющихся элементов)""" + i, j = 0, len(nums) - 1 # Инициализировать двусторонне замкнутый интервал [0, n-1] + while i <= j: + m = (i + j) // 2 # Вычислить индекс середины m + if nums[m] < target: + i = m + 1 # target находится в интервале [m+1, j] + elif nums[m] > target: + j = m - 1 # target находится в интервале [i, m-1] + else: + return m # Найти target и вернуть точку вставки m + # target не найден, вернуть точку вставки i + return i + + +def binary_search_insertion(nums: list[int], target: int) -> int: + """Бинарный поиск точки вставки (с повторяющимися элементами)""" + i, j = 0, len(nums) - 1 # Инициализировать двусторонне замкнутый интервал [0, n-1] + while i <= j: + m = (i + j) // 2 # Вычислить индекс середины m + if nums[m] < target: + i = m + 1 # target находится в интервале [m+1, j] + elif nums[m] > target: + j = m - 1 # target находится в интервале [i, m-1] + else: + j = m - 1 # Первый элемент меньше target находится в интервале [i, m-1] + # Вернуть точку вставки i + return i + + +"""Driver Code""" +if __name__ == "__main__": + # Массив без повторяющихся элементов + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print(f"\nМассив nums = {nums}") + # Бинарный поиск точки вставки + for target in [6, 9]: + index = binary_search_insertion_simple(nums, target) + print(f"Индекс позиции вставки элемента {target} равен {index}") + + # Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print(f"\nМассив nums = {nums}") + # Бинарный поиск точки вставки + for target in [2, 6, 20]: + index = binary_search_insertion(nums, target) + print(f"Индекс позиции вставки элемента {target} равен {index}") diff --git a/ru/codes/python/chapter_searching/hashing_search.py b/ru/codes/python/chapter_searching/hashing_search.py new file mode 100644 index 000000000..1dbe0fe73 --- /dev/null +++ b/ru/codes/python/chapter_searching/hashing_search.py @@ -0,0 +1,51 @@ +""" +File: hashing_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def hashing_search_array(hmap: dict[int, int], target: int) -> int: + """Хеш-поиск (массив)""" + # key хеш-таблицы: целевой элемент, value: индекс + # Если такого key нет в хеш-таблице, вернуть -1 + return hmap.get(target, -1) + + +def hashing_search_linkedlist( + hmap: dict[int, ListNode], target: int +) -> ListNode | None: + """Хеш-поиск (связный список)""" + # key хеш-таблицы: целевой элемент, value: объект узла + # Если такого key нет в хеш-таблице, вернуть None + return hmap.get(target, None) + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # Хеш-поиск (массив) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # Инициализация хеш-таблицы + map0 = dict[int, int]() + for i in range(len(nums)): + map0[nums[i]] = i # key: элемент, value: индекс + index: int = hashing_search_array(map0, target) + print("Индекс целевого элемента 3 =", index) + + # Хеш-поиск (связный список) + head: ListNode = list_to_linked_list(nums) + # Инициализация хеш-таблицы + map1 = dict[int, ListNode]() + while head: + map1[head.val] = head # key: значение узла, value: узел + head = head.next + node: ListNode = hashing_search_linkedlist(map1, target) + print("Объект узла со значением 3 =", node) diff --git a/ru/codes/python/chapter_searching/linear_search.py b/ru/codes/python/chapter_searching/linear_search.py new file mode 100644 index 000000000..397fe1d7d --- /dev/null +++ b/ru/codes/python/chapter_searching/linear_search.py @@ -0,0 +1,45 @@ +""" +File: linear_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def linear_search_array(nums: list[int], target: int) -> int: + """Линейный поиск (массив)""" + # Обход массива + for i in range(len(nums)): + if nums[i] == target: # Целевой элемент найден, вернуть его индекс + return i + return -1 # Целевой элемент не найден, вернуть -1 + + +def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: + """Линейный поиск (связный список)""" + # Обойти связный список + while head: + if head.val == target: # Найти целевой узел и вернуть его + return head + head = head.next + return None # Целевой узел не найден, вернуть None + + +"""Driver Code""" +if __name__ == "__main__": + target = 3 + + # Выполнить линейный поиск в массиве + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index: int = linear_search_array(nums, target) + print("Индекс целевого элемента 3 =", index) + + # Выполнить линейный поиск в связном списке + head: ListNode = list_to_linked_list(nums) + node: ListNode | None = linear_search_linkedlist(head, target) + print("Объект узла со значением 3 =", node) diff --git a/ru/codes/python/chapter_searching/two_sum.py b/ru/codes/python/chapter_searching/two_sum.py new file mode 100644 index 000000000..3e7a583ec --- /dev/null +++ b/ru/codes/python/chapter_searching/two_sum.py @@ -0,0 +1,42 @@ +""" +File: two_sum.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def two_sum_brute_force(nums: list[int], target: int) -> list[int]: + """Метод 1: полный перебор""" + # Два вложенных цикла, временная сложность O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i, j] + return [] + + +def two_sum_hash_table(nums: list[int], target: int) -> list[int]: + """Метод 2: вспомогательная хеш-таблица""" + # Вспомогательная хеш-таблица, пространственная сложность O(n) + dic = {} + # Один цикл, временная сложность O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return [dic[target - nums[i]], i] + dic[nums[i]] = i + return [] + + +"""Driver Code""" +if __name__ == "__main__": + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Основной код ====== + # Метод 1 + res: list[int] = two_sum_brute_force(nums, target) + print("Результат метода 1 res =", res) + # Метод 2 + res: list[int] = two_sum_hash_table(nums, target) + print("Результат метода 2 res =", res) diff --git a/ru/codes/python/chapter_sorting/bubble_sort.py b/ru/codes/python/chapter_sorting/bubble_sort.py new file mode 100644 index 000000000..6412448bc --- /dev/null +++ b/ru/codes/python/chapter_sorting/bubble_sort.py @@ -0,0 +1,44 @@ +""" +File: bubble_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def bubble_sort(nums: list[int]): + """Пузырьковая сортировка""" + n = len(nums) + # Внешний цикл: неотсортированный диапазон [0, i] + for i in range(n - 1, 0, -1): + # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in range(i): + if nums[j] > nums[j + 1]: + # Поменять местами nums[j] и nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + + +def bubble_sort_with_flag(nums: list[int]): + """Пузырьковая сортировка (оптимизация флагом)""" + n = len(nums) + # Внешний цикл: неотсортированный диапазон [0, i] + for i in range(n - 1, 0, -1): + flag = False # Инициализировать флаг + # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in range(i): + if nums[j] > nums[j + 1]: + # Поменять местами nums[j] и nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = True # Записать обмен элементов + if not flag: + break # На этой итерации «всплытия» не было ни одного обмена, сразу выйти + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + print("После пузырьковой сортировки nums =", nums) + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + print("После пузырьковой сортировки nums =", nums1) diff --git a/ru/codes/python/chapter_sorting/bucket_sort.py b/ru/codes/python/chapter_sorting/bucket_sort.py new file mode 100644 index 000000000..2f77de939 --- /dev/null +++ b/ru/codes/python/chapter_sorting/bucket_sort.py @@ -0,0 +1,35 @@ +""" +File: bucket_sort.py +Created Time: 2023-03-30 +Author: krahets (krahets@163.com) +""" + + +def bucket_sort(nums: list[float]): + """Сортировка корзинами""" + # Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. Распределить элементы массива по корзинам + for num in nums: + # Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + i = int(num * k) + # Добавить num в корзину i + buckets[i].append(num) + # 2. Выполнить сортировку внутри каждой корзины + for bucket in buckets: + # Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + bucket.sort() + # 3. Обойти корзины и объединить результаты + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + + +if __name__ == "__main__": + # Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + print("После сортировки корзинами nums =", nums) diff --git a/ru/codes/python/chapter_sorting/counting_sort.py b/ru/codes/python/chapter_sorting/counting_sort.py new file mode 100644 index 000000000..ca4d9972f --- /dev/null +++ b/ru/codes/python/chapter_sorting/counting_sort.py @@ -0,0 +1,62 @@ +""" +File: counting_sort.py +Created Time: 2023-03-21 +Author: krahets (krahets@163.com) +""" + + +def counting_sort_naive(nums: list[int]): + """Сортировка подсчетом""" + # Простая реализация, не подходит для сортировки объектов + # 1. Найти максимальный элемент массива m + m = max(nums) + # 2. Подсчитать число появлений каждой цифры + # counter[num] обозначает число появлений num + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. Обойти counter и заполнить исходный массив nums элементами + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 + + +def counting_sort(nums: list[int]): + """Сортировка подсчетом""" + # Полная реализация, позволяет сортировать объекты и является стабильной сортировкой + # 1. Найти максимальный элемент массива m + m = max(nums) + # 2. Подсчитать число появлений каждой цифры + # counter[num] обозначает число появлений num + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + # То есть counter[num]-1 — это индекс последнего появления num в res + for i in range(m): + counter[i + 1] += counter[i] + # 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + # Инициализировать массив res для хранения результата + n = len(nums) + res = [0] * n + for i in range(n - 1, -1, -1): + num = nums[i] + res[counter[num] - 1] = num # Поместить num по соответствующему индексу + counter[num] -= 1 # Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + # Перезаписать исходный массив nums массивом результата res + for i in range(n): + nums[i] = res[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + print(f"После сортировки подсчетом (объекты не поддерживаются) nums = {nums}") + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + print(f"После сортировки подсчетом nums1 = {nums1}") diff --git a/ru/codes/python/chapter_sorting/heap_sort.py b/ru/codes/python/chapter_sorting/heap_sort.py new file mode 100644 index 000000000..9f1816b40 --- /dev/null +++ b/ru/codes/python/chapter_sorting/heap_sort.py @@ -0,0 +1,45 @@ +""" +File: heap_sort.py +Created Time: 2023-05-24 +Author: krahets (krahets@163.com) +""" + + +def sift_down(nums: list[int], n: int, i: int): + """Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз""" + while True: + # Определить узел с максимальным значением среди i, l и r и обозначить его как ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i: + break + # Поменять два узла местами + nums[i], nums[ma] = nums[ma], nums[i] + # Циклическое просеивание вниз + i = ma + + +def heap_sort(nums: list[int]): + """Сортировка кучей""" + # Построение кучи: выполнить heapify для всех узлов, кроме листовых + for i in range(len(nums) // 2 - 1, -1, -1): + sift_down(nums, len(nums), i) + # Извлекать максимальный элемент из кучи в течение n-1 итераций + for i in range(len(nums) - 1, 0, -1): + # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + nums[0], nums[i] = nums[i], nums[0] + # Начиная с корневого узла, выполнить просеивание сверху вниз + sift_down(nums, i, 0) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + print("После сортировки кучей nums =", nums) diff --git a/ru/codes/python/chapter_sorting/insertion_sort.py b/ru/codes/python/chapter_sorting/insertion_sort.py new file mode 100644 index 000000000..9828f1324 --- /dev/null +++ b/ru/codes/python/chapter_sorting/insertion_sort.py @@ -0,0 +1,25 @@ +""" +File: insertion_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def insertion_sort(nums: list[int]): + """Сортировка вставками""" + # Внешний цикл: отсортированный диапазон [0, i-1] + for i in range(1, len(nums)): + base = nums[i] + j = i - 1 + # Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while j >= 0 and nums[j] > base: + nums[j + 1] = nums[j] # Сдвинуть nums[j] на одну позицию вправо + j -= 1 + nums[j + 1] = base # Поместить base в правильную позицию + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + insertion_sort(nums) + print("После сортировки вставками nums =", nums) diff --git a/ru/codes/python/chapter_sorting/merge_sort.py b/ru/codes/python/chapter_sorting/merge_sort.py new file mode 100644 index 000000000..36fa4c36d --- /dev/null +++ b/ru/codes/python/chapter_sorting/merge_sort.py @@ -0,0 +1,55 @@ +""" +File: merge_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com), krahets (krahets@163.com) +""" + + +def merge(nums: list[int], left: int, mid: int, right: int): + """Объединить левый и правый подмассивы""" + # Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + # Создать временный массив tmp для хранения результата слияния + tmp = [0] * (right - left + 1) + # Инициализировать начальные индексы левого и правого подмассивов + i, j, k = left, mid + 1, 0 + # Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while i <= mid and j <= right: + if nums[i] <= nums[j]: + tmp[k] = nums[i] + i += 1 + else: + tmp[k] = nums[j] + j += 1 + k += 1 + # Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while i <= mid: + tmp[k] = nums[i] + i += 1 + k += 1 + while j <= right: + tmp[k] = nums[j] + j += 1 + k += 1 + # Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for k in range(0, len(tmp)): + nums[left + k] = tmp[k] + + +def merge_sort(nums: list[int], left: int, right: int): + """Сортировка слиянием""" + # Условие завершения + if left >= right: + return # Завершить рекурсию, когда длина подмассива равна 1 + # Этап разбиения + mid = (left + right) // 2 # Вычислить середину + merge_sort(nums, left, mid) # Рекурсивно обработать левый подмассив + merge_sort(nums, mid + 1, right) # Рекурсивно обработать правый подмассив + # Этап слияния + merge(nums, left, mid, right) + + +"""Driver Code""" +if __name__ == "__main__": + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, len(nums) - 1) + print("После сортировки слиянием nums =", nums) diff --git a/ru/codes/python/chapter_sorting/quick_sort.py b/ru/codes/python/chapter_sorting/quick_sort.py new file mode 100644 index 000000000..e32b3dc27 --- /dev/null +++ b/ru/codes/python/chapter_sorting/quick_sort.py @@ -0,0 +1,129 @@ +""" +File: quick_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +class QuickSort: + """Класс быстрой сортировки""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """Разбиение с опорными указателями""" + # Взять nums[left] в качестве опорного элемента + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # Идти справа налево в поисках первого элемента меньше опорного + while i < j and nums[i] <= nums[left]: + i += 1 # Идти слева направо в поисках первого элемента больше опорного + # Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + # Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + return i # Вернуть индекс опорного элемента + + def quick_sort(self, nums: list[int], left: int, right: int): + """Быстрая сортировка""" + # Завершить рекурсию, когда длина подмассива равна 1 + if left >= right: + return + # Разбиение с опорными указателями + pivot = self.partition(nums, left, right) + # Рекурсивно обработать левый и правый подмассивы + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortMedian: + """Класс быстрой сортировки (оптимизация медианным опорным элементом)""" + + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """Выбрать медиану из трех кандидатов""" + l, m, r = nums[left], nums[mid], nums[right] + if (l <= m <= r) or (r <= m <= l): + return mid # m находится между l и r + if (m <= l <= r) or (r <= l <= m): + return left # l находится между m и r + return right + + def partition(self, nums: list[int], left: int, right: int) -> int: + """Разбиение с опорными указателями (медиана трех)""" + # Взять nums[left] в качестве опорного элемента + med = self.median_three(nums, left, (left + right) // 2, right) + # Переместить медиану в крайний левый элемент массива + nums[left], nums[med] = nums[med], nums[left] + # Взять nums[left] в качестве опорного элемента + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # Идти справа налево в поисках первого элемента меньше опорного + while i < j and nums[i] <= nums[left]: + i += 1 # Идти слева направо в поисках первого элемента больше опорного + # Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + # Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + return i # Вернуть индекс опорного элемента + + def quick_sort(self, nums: list[int], left: int, right: int): + """Быстрая сортировка""" + # Завершить рекурсию, когда длина подмассива равна 1 + if left >= right: + return + # Разбиение с опорными указателями + pivot = self.partition(nums, left, right) + # Рекурсивно обработать левый и правый подмассивы + self.quick_sort(nums, left, pivot - 1) + self.quick_sort(nums, pivot + 1, right) + + +class QuickSortTailCall: + """Класс быстрой сортировки (оптимизация глубины рекурсии)""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """Разбиение с опорными указателями""" + # Взять nums[left] в качестве опорного элемента + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # Идти справа налево в поисках первого элемента меньше опорного + while i < j and nums[i] <= nums[left]: + i += 1 # Идти слева направо в поисках первого элемента больше опорного + # Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + # Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + return i # Вернуть индекс опорного элемента + + def quick_sort(self, nums: list[int], left: int, right: int): + """Быстрая сортировка (оптимизация глубины рекурсии)""" + # Завершить, когда длина подмассива равна 1 + while left < right: + # Операция разбиения с опорными указателями + pivot = self.partition(nums, left, right) + # Выполнить быструю сортировку для более короткого из двух подмассивов + if pivot - left < right - pivot: + self.quick_sort(nums, left, pivot - 1) # Рекурсивно отсортировать левый подмассив + left = pivot + 1 # Оставшийся неотсортированный диапазон: [pivot + 1, right] + else: + self.quick_sort(nums, pivot + 1, right) # Рекурсивно отсортировать правый подмассив + right = pivot - 1 # Оставшийся неотсортированный диапазон: [left, pivot - 1] + + +"""Driver Code""" +if __name__ == "__main__": + # Быстрая сортировка + nums = [2, 4, 1, 0, 3, 5] + QuickSort().quick_sort(nums, 0, len(nums) - 1) + print("После быстрой сортировки nums =", nums) + + # Быстрая сортировка (оптимизация медианным опорным элементом) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) + print("После быстрой сортировки (оптимизация медианным опорным элементом) nums =", nums1) + + # Быстрая сортировка (оптимизация глубины рекурсии) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) + print("После быстрой сортировки (оптимизация глубины рекурсии) nums =", nums2) diff --git a/ru/codes/python/chapter_sorting/radix_sort.py b/ru/codes/python/chapter_sorting/radix_sort.py new file mode 100644 index 000000000..c7f4d000a --- /dev/null +++ b/ru/codes/python/chapter_sorting/radix_sort.py @@ -0,0 +1,69 @@ +""" +File: radix_sort.py +Created Time: 2023-03-26 +Author: krahets (krahets@163.com) +""" + + +def digit(num: int, exp: int) -> int: + """Получить k-й разряд элемента num, где exp = 10^(k-1)""" + # Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return (num // exp) % 10 + + +def counting_sort_digit(nums: list[int], exp: int): + """Сортировка подсчетом (сортировка по k-му разряду nums)""" + # Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + counter = [0] * 10 + n = len(nums) + # Подсчитать число появлений каждой цифры от 0 до 9 + for i in range(n): + d = digit(nums[i], exp) # Получить k-й разряд nums[i], обозначив его как d + counter[d] += 1 # Подсчитать число появлений цифры d + # Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for i in range(1, 10): + counter[i] += counter[i - 1] + # Выполняя обратный проход, заполнить res элементами по статистике в корзинах + res = [0] * n + for i in range(n - 1, -1, -1): + d = digit(nums[i], exp) + j = counter[d] - 1 # Получить индекс j цифры d в массиве + res[j] = nums[i] # Поместить текущий элемент по индексу j + counter[d] -= 1 # Уменьшить количество d на 1 + # Перезаписать исходный массив nums результатом + for i in range(n): + nums[i] = res[i] + + +def radix_sort(nums: list[int]): + """Поразрядная сортировка""" + # Получить максимальный элемент массива, чтобы определить максимальное число разрядов + m = max(nums) + # Проходить разряды от младшего к старшему + exp = 1 + while exp <= m: + # Выполнить сортировку подсчетом по k-му разряду элементов массива + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # то есть exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + + +"""Driver Code""" +if __name__ == "__main__": + # Поразрядная сортировка + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + print("После поразрядной сортировки nums =", nums) diff --git a/ru/codes/python/chapter_sorting/selection_sort.py b/ru/codes/python/chapter_sorting/selection_sort.py new file mode 100644 index 000000000..7562a5486 --- /dev/null +++ b/ru/codes/python/chapter_sorting/selection_sort.py @@ -0,0 +1,26 @@ +""" +File: selection_sort.py +Created Time: 2023-05-22 +Author: krahets (krahets@163.com) +""" + + +def selection_sort(nums: list[int]): + """Сортировка выбором""" + n = len(nums) + # Внешний цикл: неотсортированный диапазон [i, n-1] + for i in range(n - 1): + # Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + k = i + for j in range(i + 1, n): + if nums[j] < nums[k]: + k = j # Записать индекс минимального элемента + # Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + nums[i], nums[k] = nums[k], nums[i] + + +"""Driver Code""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + print("После сортировки выбором nums =", nums) diff --git a/ru/codes/python/chapter_stack_and_queue/array_deque.py b/ru/codes/python/chapter_stack_and_queue/array_deque.py new file mode 100644 index 000000000..9eb4630fb --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/array_deque.py @@ -0,0 +1,129 @@ +""" +File: array_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ArrayDeque: + """Двусторонняя очередь на основе кольцевого массива""" + + def __init__(self, capacity: int): + """Конструктор""" + self._nums: list[int] = [0] * capacity + self._front: int = 0 + self._size: int = 0 + + def capacity(self) -> int: + """Получить вместимость двусторонней очереди""" + return len(self._nums) + + def size(self) -> int: + """Получение длины двусторонней очереди""" + return self._size + + def is_empty(self) -> bool: + """Проверка, пуста ли двусторонняя очередь""" + return self._size == 0 + + def index(self, i: int) -> int: + """Вычислить индекс в кольцевом массиве""" + # С помощью операции взятия по модулю соединить начало и конец массива + # Когда i выходит за конец массива, он возвращается в начало + # Когда i выходит за начало массива, он возвращается в конец + return (i + self.capacity()) % self.capacity() + + def push_first(self, num: int): + """Добавление в голову очереди""" + if self._size == self.capacity(): + print("Двусторонняя очередь заполнена") + return + # Указатель головы сдвигается на одну позицию влево + # С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + self._front = self.index(self._front - 1) + # Добавить num в голову очереди + self._nums[self._front] = num + self._size += 1 + + def push_last(self, num: int): + """Добавление в хвост очереди""" + if self._size == self.capacity(): + print("Двусторонняя очередь заполнена") + return + # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + rear = self.index(self._front + self._size) + # Добавить num в хвост очереди + self._nums[rear] = num + self._size += 1 + + def pop_first(self) -> int: + """Извлечение из головы очереди""" + num = self.peek_first() + # Указатель головы сдвигается на одну позицию назад + self._front = self.index(self._front + 1) + self._size -= 1 + return num + + def pop_last(self) -> int: + """Извлечение из хвоста очереди""" + num = self.peek_last() + self._size -= 1 + return num + + def peek_first(self) -> int: + """Доступ к элементу в начале очереди""" + if self.is_empty(): + raise IndexError("двусторонняя очередь пуста") + return self._nums[self._front] + + def peek_last(self) -> int: + """Доступ к элементу в конце очереди""" + if self.is_empty(): + raise IndexError("двусторонняя очередь пуста") + # Вычислить индекс хвостового элемента + last = self.index(self._front + self._size - 1) + return self._nums[last] + + def to_array(self) -> list[int]: + """Вернуть массив для вывода""" + # Преобразовывать только элементы списка в пределах фактической длины + res = [] + for i in range(self._size): + res.append(self._nums[self.index(self._front + i)]) + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация двусторонней очереди + deque = ArrayDeque(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("Двусторонняя очередь deque =", deque.to_array()) + + # Доступ к элементу + peek_first: int = deque.peek_first() + print("Первый элемент peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("Последний элемент peek_last =", peek_last) + + # Добавление элемента в очередь + deque.push_last(4) + print("После добавления элемента 4 в хвост deque =", deque.to_array()) + deque.push_first(1) + print("После добавления элемента 1 в голову deque =", deque.to_array()) + + # Извлечение элемента из очереди + pop_last: int = deque.pop_last() + print("Извлеченный из хвоста элемент =", pop_last, ", deque после извлечения из хвоста =", deque.to_array()) + pop_first: int = deque.pop_first() + print("Извлеченный из головы элемент =", pop_first, ", deque после извлечения из головы =", deque.to_array()) + + # Получение длины двусторонней очереди + size: int = deque.size() + print("Длина двусторонней очереди size =", size) + + # Проверка, пуста ли двусторонняя очередь + is_empty: bool = deque.is_empty() + print("Пуста ли двусторонняя очередь =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/array_queue.py b/ru/codes/python/chapter_stack_and_queue/array_queue.py new file mode 100644 index 000000000..2164c5c26 --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/array_queue.py @@ -0,0 +1,98 @@ +""" +File: array_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayQueue: + """Очередь на основе кольцевого массива""" + + def __init__(self, size: int): + """Конструктор""" + self._nums: list[int] = [0] * size # Массив для хранения элементов очереди + self._front: int = 0 # Указатель head, указывающий на первый элемент очереди + self._size: int = 0 # Длина очереди + + def capacity(self) -> int: + """Получить вместимость очереди""" + return len(self._nums) + + def size(self) -> int: + """Получение длины очереди""" + return self._size + + def is_empty(self) -> bool: + """Проверка, пуста ли очередь""" + return self._size == 0 + + def push(self, num: int): + """Поместить в очередь""" + if self._size == self.capacity(): + raise IndexError("очередь заполнена") + # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + # С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + rear: int = (self._front + self._size) % self.capacity() + # Добавить num в хвост очереди + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """Извлечь из очереди""" + num: int = self.peek() + # Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + self._front = (self._front + 1) % self.capacity() + self._size -= 1 + return num + + def peek(self) -> int: + """Доступ к элементу в начале очереди""" + if self.is_empty(): + raise IndexError("очередь пуста") + return self._nums[self._front] + + def to_list(self) -> list[int]: + """Вернуть список для вывода""" + res = [0] * self.size() + j: int = self._front + for i in range(self.size()): + res[i] = self._nums[(j % self.capacity())] + j += 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация очереди + queue = ArrayQueue(10) + + # Добавление элемента в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("Очередь queue =", queue.to_list()) + + # Доступ к элементу в начале очереди + peek: int = queue.peek() + print("Первый элемент peek =", peek) + + # Извлечение элемента из очереди + pop: int = queue.pop() + print("Извлеченный элемент pop =", pop) + print("queue после извлечения =", queue.to_list()) + + # Получение длины очереди + size: int = queue.size() + print("Длина очереди size =", size) + + # Проверка, пуста ли очередь + is_empty: bool = queue.is_empty() + print("Пуста ли очередь =", is_empty) + + # Проверка кольцевого массива + for i in range(10): + queue.push(i) + queue.pop() + print("После", i, "-го раунда операций enqueue и dequeue queue =", queue.to_list()) diff --git a/ru/codes/python/chapter_stack_and_queue/array_stack.py b/ru/codes/python/chapter_stack_and_queue/array_stack.py new file mode 100644 index 000000000..b04d3cf9c --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/array_stack.py @@ -0,0 +1,72 @@ +""" +File: array_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayStack: + """Стек на основе массива""" + + def __init__(self): + """Конструктор""" + self._stack: list[int] = [] + + def size(self) -> int: + """Получение длины стека""" + return len(self._stack) + + def is_empty(self) -> bool: + """Проверка, пуст ли стек""" + return self.size() == 0 + + def push(self, item: int): + """Поместить в стек""" + self._stack.append(item) + + def pop(self) -> int: + """Извлечь из стека""" + if self.is_empty(): + raise IndexError("стек пуст") + return self._stack.pop() + + def peek(self) -> int: + """Доступ к верхнему элементу стека""" + if self.is_empty(): + raise IndexError("стек пуст") + return self._stack[-1] + + def to_list(self) -> list[int]: + """Вернуть список для вывода""" + return self._stack + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация стека + stack = ArrayStack() + + # Помещение элемента в стек + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("Стек stack =", stack.to_list()) + + # Доступ к верхнему элементу стека + peek: int = stack.peek() + print("Верхний элемент peek =", peek) + + # Извлечение элемента из стека + pop: int = stack.pop() + print("Извлеченный элемент pop =", pop) + print("stack после извлечения =", stack.to_list()) + + # Получение длины стека + size: int = stack.size() + print("Длина стека size =", size) + + # Проверка на пустоту + is_empty: bool = stack.is_empty() + print("Пуст ли стек =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/deque.py b/ru/codes/python/chapter_stack_and_queue/deque.py new file mode 100644 index 000000000..d8993189a --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/deque.py @@ -0,0 +1,42 @@ +""" +File: deque.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация двусторонней очереди + deq: deque[int] = deque() + + # Добавление элемента в очередь + deq.append(2) # Добавить в хвост очереди + deq.append(5) + deq.append(4) + deq.appendleft(3) # Добавить в голову очереди + deq.appendleft(1) + print("Двусторонняя очередь deque =", deq) + + # Доступ к элементу + front: int = deq[0] # Элемент в голове очереди + print("Первый элемент front =", front) + rear: int = deq[-1] # Элемент в хвосте очереди + print("Последний элемент rear =", rear) + + # Извлечение элемента из очереди + pop_front: int = deq.popleft() # Извлечь элемент из головы очереди + print("Извлеченный из головы элемент pop_front =", pop_front) + print("deque после извлечения из головы =", deq) + pop_rear: int = deq.pop() # Извлечь элемент из хвоста очереди + print("Извлеченный из хвоста элемент pop_rear =", pop_rear) + print("deque после извлечения из хвоста =", deq) + + # Получение длины двусторонней очереди + size: int = len(deq) + print("Длина двусторонней очереди size =", size) + + # Проверка, пуста ли двусторонняя очередь + is_empty: bool = len(deq) == 0 + print("Пуста ли двусторонняя очередь =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/ru/codes/python/chapter_stack_and_queue/linkedlist_deque.py new file mode 100644 index 000000000..481ce94c7 --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -0,0 +1,151 @@ +""" +File: linkedlist_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """Узел двусвязного списка""" + + def __init__(self, val: int): + """Конструктор""" + self.val: int = val + self.next: ListNode | None = None # Ссылка на узел-преемник + self.prev: ListNode | None = None # Ссылка на узел-предшественник + + +class LinkedListDeque: + """Двусторонняя очередь на основе двусвязного списка""" + + def __init__(self): + """Конструктор""" + self._front: ListNode | None = None # Головной узел front + self._rear: ListNode | None = None # Хвостовой узел rear + self._size: int = 0 # Длина двусторонней очереди + + def size(self) -> int: + """Получение длины двусторонней очереди""" + return self._size + + def is_empty(self) -> bool: + """Проверка, пуста ли двусторонняя очередь""" + return self._size == 0 + + def push(self, num: int, is_front: bool): + """Операция добавления в очередь""" + node = ListNode(num) + # Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if self.is_empty(): + self._front = self._rear = node + # Операция добавления в голову очереди + elif is_front: + # Добавить node в голову списка + self._front.prev = node + node.next = self._front + self._front = node # Обновить головной узел + # Операция добавления в хвост очереди + else: + # Добавить node в хвост списка + self._rear.next = node + node.prev = self._rear + self._rear = node # Обновить хвостовой узел + self._size += 1 # Обновить длину очереди + + def push_first(self, num: int): + """Добавление в голову очереди""" + self.push(num, True) + + def push_last(self, num: int): + """Добавление в хвост очереди""" + self.push(num, False) + + def pop(self, is_front: bool) -> int: + """Операция извлечения из очереди""" + if self.is_empty(): + raise IndexError("двусторонняя очередь пуста") + # Операция извлечения из головы очереди + if is_front: + val: int = self._front.val # Временно сохранить значение головного узла + # Удалить головной узел + fnext: ListNode | None = self._front.next + if fnext is not None: + fnext.prev = None + self._front.next = None + self._front = fnext # Обновить головной узел + # Операция извлечения из хвоста очереди + else: + val: int = self._rear.val # Временно сохранить значение хвостового узла + # Удалить хвостовой узел + rprev: ListNode | None = self._rear.prev + if rprev is not None: + rprev.next = None + self._rear.prev = None + self._rear = rprev # Обновить хвостовой узел + self._size -= 1 # Обновить длину очереди + return val + + def pop_first(self) -> int: + """Извлечение из головы очереди""" + return self.pop(True) + + def pop_last(self) -> int: + """Извлечение из хвоста очереди""" + return self.pop(False) + + def peek_first(self) -> int: + """Доступ к элементу в начале очереди""" + if self.is_empty(): + raise IndexError("двусторонняя очередь пуста") + return self._front.val + + def peek_last(self) -> int: + """Доступ к элементу в конце очереди""" + if self.is_empty(): + raise IndexError("двусторонняя очередь пуста") + return self._rear.val + + def to_array(self) -> list[int]: + """Вернуть массив для вывода""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация двусторонней очереди + deque = LinkedListDeque() + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("Двусторонняя очередь deque =", deque.to_array()) + + # Доступ к элементу + peek_first: int = deque.peek_first() + print("Первый элемент peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("Последний элемент peek_last =", peek_last) + + # Добавление элемента в очередь + deque.push_last(4) + print("После добавления элемента 4 в хвост deque =", deque.to_array()) + deque.push_first(1) + print("После добавления элемента 1 в голову deque =", deque.to_array()) + + # Извлечение элемента из очереди + pop_last: int = deque.pop_last() + print("Извлеченный из хвоста элемент =", pop_last, ", deque после извлечения из хвоста =", deque.to_array()) + pop_first: int = deque.pop_first() + print("Извлеченный из головы элемент =", pop_first, ", deque после извлечения из головы =", deque.to_array()) + + # Получение длины двусторонней очереди + size: int = deque.size() + print("Длина двусторонней очереди size =", size) + + # Проверка, пуста ли двусторонняя очередь + is_empty: bool = deque.is_empty() + print("Пуста ли двусторонняя очередь =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/ru/codes/python/chapter_stack_and_queue/linkedlist_queue.py new file mode 100644 index 000000000..98e63ae60 --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -0,0 +1,97 @@ +""" +File: linkedlist_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListQueue: + """Очередь на основе связного списка""" + + def __init__(self): + """Конструктор""" + self._front: ListNode | None = None # Головной узел front + self._rear: ListNode | None = None # Хвостовой узел rear + self._size: int = 0 + + def size(self) -> int: + """Получение длины очереди""" + return self._size + + def is_empty(self) -> bool: + """Проверка, пуста ли очередь""" + return self._size == 0 + + def push(self, num: int): + """Поместить в очередь""" + # Добавить num после хвостового узла + node = ListNode(num) + # Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if self._front is None: + self._front = node + self._rear = node + # Если очередь не пуста, добавить этот узел после хвостового узла + else: + self._rear.next = node + self._rear = node + self._size += 1 + + def pop(self) -> int: + """Извлечь из очереди""" + num = self.peek() + # Удалить головной узел + self._front = self._front.next + self._size -= 1 + return num + + def peek(self) -> int: + """Доступ к элементу в начале очереди""" + if self.is_empty(): + raise IndexError("очередь пуста") + return self._front.val + + def to_list(self) -> list[int]: + """Преобразовать в список для вывода""" + queue = [] + temp = self._front + while temp: + queue.append(temp.val) + temp = temp.next + return queue + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация очереди + queue = LinkedListQueue() + + # Добавление элемента в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("Очередь queue =", queue.to_list()) + + # Доступ к элементу в начале очереди + peek: int = queue.peek() + print("Первый элемент front =", peek) + + # Извлечение элемента из очереди + pop_front: int = queue.pop() + print("Извлеченный элемент pop =", pop_front) + print("queue после извлечения =", queue.to_list()) + + # Получение длины очереди + size: int = queue.size() + print("Длина очереди size =", size) + + # Проверка, пуста ли очередь + is_empty: bool = queue.is_empty() + print("Пуста ли очередь =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/ru/codes/python/chapter_stack_and_queue/linkedlist_stack.py new file mode 100644 index 000000000..8ac670d34 --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -0,0 +1,89 @@ +""" +File: linkedlist_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListStack: + """Стек на основе связного списка""" + + def __init__(self): + """Конструктор""" + self._peek: ListNode | None = None + self._size: int = 0 + + def size(self) -> int: + """Получение длины стека""" + return self._size + + def is_empty(self) -> bool: + """Проверка, пуст ли стек""" + return self._size == 0 + + def push(self, val: int): + """Поместить в стек""" + node = ListNode(val) + node.next = self._peek + self._peek = node + self._size += 1 + + def pop(self) -> int: + """Извлечь из стека""" + num = self.peek() + self._peek = self._peek.next + self._size -= 1 + return num + + def peek(self) -> int: + """Доступ к верхнему элементу стека""" + if self.is_empty(): + raise IndexError("стек пуст") + return self._peek.val + + def to_list(self) -> list[int]: + """Преобразовать в список для вывода""" + arr = [] + node = self._peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация стека + stack = LinkedListStack() + + # Помещение элемента в стек + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + print("Стек stack =", stack.to_list()) + + # Доступ к верхнему элементу стека + peek: int = stack.peek() + print("Верхний элемент peek =", peek) + + # Извлечение элемента из стека + pop: int = stack.pop() + print("Извлеченный элемент pop =", pop) + print("stack после извлечения =", stack.to_list()) + + # Получение длины стека + size: int = stack.size() + print("Длина стека size =", size) + + # Проверка на пустоту + is_empty: bool = stack.is_empty() + print("Пуст ли стек =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/queue.py b/ru/codes/python/chapter_stack_and_queue/queue.py new file mode 100644 index 000000000..d68864d96 --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/queue.py @@ -0,0 +1,39 @@ +""" +File: queue.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # Инициализировать очередь + # В Python мы обычно рассматриваем двустороннюю очередь deque как очередь + # Хотя queue.Queue() — это «настоящий» класс очереди, им не очень удобно пользоваться + que: deque[int] = deque() + + # Добавление элемента в очередь + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + print("Очередь que =", que) + + # Доступ к элементу в начале очереди + front: int = que[0] + print("Первый элемент front =", front) + + # Извлечение элемента из очереди + pop: int = que.popleft() + print("Извлеченный элемент pop =", pop) + print("que после извлечения =", que) + + # Получение длины очереди + size: int = len(que) + print("Длина очереди size =", size) + + # Проверка, пуста ли очередь + is_empty: bool = len(que) == 0 + print("Пуста ли очередь =", is_empty) diff --git a/ru/codes/python/chapter_stack_and_queue/stack.py b/ru/codes/python/chapter_stack_and_queue/stack.py new file mode 100644 index 000000000..cb8c42f82 --- /dev/null +++ b/ru/codes/python/chapter_stack_and_queue/stack.py @@ -0,0 +1,36 @@ +""" +File: stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # Инициализировать стек + # В Python нет встроенного класса стека, поэтому list можно использовать как стек + stack: list[int] = [] + + # Помещение элемента в стек + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("Стек stack =", stack) + + # Доступ к верхнему элементу стека + peek: int = stack[-1] + print("Верхний элемент peek =", peek) + + # Извлечение элемента из стека + pop: int = stack.pop() + print("Извлеченный элемент pop =", pop) + print("stack после извлечения =", stack) + + # Получение длины стека + size: int = len(stack) + print("Длина стека size =", size) + + # Проверка на пустоту + is_empty: bool = len(stack) == 0 + print("Пуст ли стек =", is_empty) diff --git a/ru/codes/python/chapter_tree/array_binary_tree.py b/ru/codes/python/chapter_tree/array_binary_tree.py new file mode 100644 index 000000000..42bdf5d23 --- /dev/null +++ b/ru/codes/python/chapter_tree/array_binary_tree.py @@ -0,0 +1,119 @@ +""" +File: array_binary_tree.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +class ArrayBinaryTree: + """Класс двоичного дерева в массивном представлении""" + + def __init__(self, arr: list[int | None]): + """Конструктор""" + self._tree = list(arr) + + def size(self): + """Вместимость списка""" + return len(self._tree) + + def val(self, i: int) -> int | None: + """Получить значение узла с индексом i""" + # Если индекс выходит за границы, вернуть None, обозначающий пустую позицию + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """Получить индекс левого дочернего узла узла с индексом i""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """Получить индекс правого дочернего узла узла с индексом i""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """Получить индекс родительского узла узла с индексом i""" + return (i - 1) // 2 + + def level_order(self) -> list[int]: + """Обход в ширину""" + self.res = [] + # Непосредственно обходить массив + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res + + def dfs(self, i: int, order: str): + """Обход в глубину""" + if self.val(i) is None: + return + # Предварительный обход + if order == "pre": + self.res.append(self.val(i)) + self.dfs(self.left(i), order) + # Симметричный обход + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # Обратный обход + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """Предварительный обход""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """Симметричный обход""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """Обратный обход""" + self.res = [] + self.dfs(0, order="post") + return self.res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализировать двоичное дерево + # Здесь используется функция, напрямую строящая двоичное дерево из массива + arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + root = list_to_tree(arr) + print("\nИнициализация двоичного дерева\n") + print("Массивное представление двоичного дерева:") + print(arr) + print("Связное представление двоичного дерева:") + print_tree(root) + + # Класс двоичного дерева в массивном представлении + abt = ArrayBinaryTree(arr) + + # Доступ к узлу + i = 1 + l, r, p = abt.left(i), abt.right(i), abt.parent(i) + print(f"\nТекущий узел: индекс = {i}, значение = {abt.val(i)}") + print(f"Индекс левого дочернего узла = {l}, значение = {abt.val(l)}") + print(f"Индекс правого дочернего узла = {r}, значение = {abt.val(r)}") + print(f"Индекс родительского узла = {p}, значение = {abt.val(p)}") + + # Обходить дерево + res = abt.level_order() + print("\nОбход в ширину:", res) + res = abt.pre_order() + print("Предварительный обход:", res) + res = abt.in_order() + print("Симметричный обход:", res) + res = abt.post_order() + print("Обратный обход:", res) diff --git a/ru/codes/python/chapter_tree/avl_tree.py b/ru/codes/python/chapter_tree/avl_tree.py new file mode 100644 index 000000000..b8ac7a966 --- /dev/null +++ b/ru/codes/python/chapter_tree/avl_tree.py @@ -0,0 +1,200 @@ +""" +File: avl_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class AVLTree: + """AVL-дерево""" + + def __init__(self): + """Конструктор""" + self._root = None + + def get_root(self) -> TreeNode | None: + """Получить корневой узел двоичного дерева""" + return self._root + + def height(self, node: TreeNode | None) -> int: + """Получить высоту узла""" + # Высота пустого узла равна -1, высота листового узла равна 0 + if node is not None: + return node.height + return -1 + + def update_height(self, node: TreeNode | None): + """Обновить высоту узла""" + # Высота узла равна высоте более высокого поддерева + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 + + def balance_factor(self, node: TreeNode | None) -> int: + """Получить коэффициент баланса""" + # Коэффициент баланса пустого узла равен 0 + if node is None: + return 0 + # Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return self.height(node.left) - self.height(node.right) + + def right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """Операция правого вращения""" + child = node.left + grand_child = child.right + # Выполнить правое вращение узла node вокруг child + child.right = node + node.left = grand_child + # Обновить высоту узла + self.update_height(node) + self.update_height(child) + # Вернуть корневой узел поддерева после вращения + return child + + def left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """Операция левого вращения""" + child = node.right + grand_child = child.left + # Выполнить левое вращение узла node вокруг child + child.left = node + node.right = grand_child + # Обновить высоту узла + self.update_height(node) + self.update_height(child) + # Вернуть корневой узел поддерева после вращения + return child + + def rotate(self, node: TreeNode | None) -> TreeNode | None: + """Выполнить вращение, чтобы снова сбалансировать поддерево""" + # Получить коэффициент баланса узла node + balance_factor = self.balance_factor(node) + # Левосторонне перекошенное дерево + if balance_factor > 1: + if self.balance_factor(node.left) >= 0: + # Правое вращение + return self.right_rotate(node) + else: + # Сначала левое вращение, затем правое + node.left = self.left_rotate(node.left) + return self.right_rotate(node) + # Правосторонне перекошенное дерево + elif balance_factor < -1: + if self.balance_factor(node.right) <= 0: + # Левое вращение + return self.left_rotate(node) + else: + # Сначала правое вращение, затем левое + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # Дерево сбалансировано, вращение не требуется, вернуть сразу + return node + + def insert(self, val): + """Вставка узла""" + self._root = self.insert_helper(self._root, val) + + def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: + """Рекурсивная вставка узла (вспомогательный метод)""" + if node is None: + return TreeNode(val) + # 1. Найти позицию вставки и вставить узел + if val < node.val: + node.left = self.insert_helper(node.left, val) + elif val > node.val: + node.right = self.insert_helper(node.right, val) + else: + # Повторяющийся узел не вставлять, сразу вернуть + return node + # Обновить высоту узла + self.update_height(node) + # 2. Выполнить вращение, чтобы снова сбалансировать поддерево + return self.rotate(node) + + def remove(self, val: int): + """Удаление узла""" + self._root = self.remove_helper(self._root, val) + + def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """Рекурсивное удаление узла (вспомогательный метод)""" + if node is None: + return None + # 1. Найти узел и удалить его + if val < node.val: + node.left = self.remove_helper(node.left, val) + elif val > node.val: + node.right = self.remove_helper(node.right, val) + else: + if node.left is None or node.right is None: + child = node.left or node.right + # Число дочерних узлов = 0, удалить node и сразу вернуть + if child is None: + return None + # Число дочерних узлов = 1, удалить node напрямую + else: + node = child + else: + # Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + temp = node.right + while temp.left is not None: + temp = temp.left + node.right = self.remove_helper(node.right, temp.val) + node.val = temp.val + # Обновить высоту узла + self.update_height(node) + # 2. Выполнить вращение, чтобы снова сбалансировать поддерево + return self.rotate(node) + + def search(self, val: int) -> TreeNode | None: + """Поиск узла""" + cur = self._root + # Искать в цикле и выйти после прохода за листовой узел + while cur is not None: + # Целевой узел находится в правом поддереве cur + if cur.val < val: + cur = cur.right + # Целевой узел находится в левом поддереве cur + elif cur.val > val: + cur = cur.left + # Найти целевой узел и выйти из цикла + else: + break + # Вернуть целевой узел + return cur + + +"""Driver Code""" +if __name__ == "__main__": + + def test_insert(tree: AVLTree, val: int): + tree.insert(val) + print("\nПосле вставки узла {} AVL-дерево имеет вид".format(val)) + print_tree(tree.get_root()) + + def test_remove(tree: AVLTree, val: int): + tree.remove(val) + print("\nПосле удаления узла {} AVL-дерево имеет вид".format(val)) + print_tree(tree.get_root()) + + # Инициализация пустого AVL-дерева + avl_tree = AVLTree() + + # Вставка узла + # Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: + test_insert(avl_tree, val) + + # Вставка повторяющегося узла + test_insert(avl_tree, 7) + + # Удаление узла + # Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + test_remove(avl_tree, 8) # Удаление узла степени 0 + test_remove(avl_tree, 5) # Удаление узла степени 1 + test_remove(avl_tree, 4) # Удаление узла степени 2 + + result_node = avl_tree.search(7) + print("\nНайденный объект узла = {}, значение узла = {}".format(result_node, result_node.val)) diff --git a/ru/codes/python/chapter_tree/binary_search_tree.py b/ru/codes/python/chapter_tree/binary_search_tree.py new file mode 100644 index 000000000..3453bfd35 --- /dev/null +++ b/ru/codes/python/chapter_tree/binary_search_tree.py @@ -0,0 +1,146 @@ +""" +File: binary_search_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class BinarySearchTree: + """Двоичное дерево поиска""" + + def __init__(self): + """Конструктор""" + # Инициализировать пустое дерево + self._root = None + + def get_root(self) -> TreeNode | None: + """Получить корневой узел двоичного дерева""" + return self._root + + def search(self, num: int) -> TreeNode | None: + """Поиск узла""" + cur = self._root + # Искать в цикле и выйти после прохода за листовой узел + while cur is not None: + # Целевой узел находится в правом поддереве cur + if cur.val < num: + cur = cur.right + # Целевой узел находится в левом поддереве cur + elif cur.val > num: + cur = cur.left + # Найти целевой узел и выйти из цикла + else: + break + return cur + + def insert(self, num: int): + """Вставка узла""" + # Если дерево пусто, инициализировать корневой узел + if self._root is None: + self._root = TreeNode(num) + return + # Искать в цикле и выйти после прохода за листовой узел + cur, pre = self._root, None + while cur is not None: + # Найти повторяющийся узел и сразу вернуть + if cur.val == num: + return + pre = cur + # Позиция вставки находится в правом поддереве cur + if cur.val < num: + cur = cur.right + # Позиция вставки находится в левом поддереве cur + else: + cur = cur.left + # Вставка узла + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node + + def remove(self, num: int): + """Удаление узла""" + # Если дерево пусто, сразу вернуть + if self._root is None: + return + # Искать в цикле и выйти после прохода за листовой узел + cur, pre = self._root, None + while cur is not None: + # Найти узел для удаления и выйти из цикла + if cur.val == num: + break + pre = cur + # Узел для удаления находится в правом поддереве cur + if cur.val < num: + cur = cur.right + # Узел для удаления находится в левом поддереве cur + else: + cur = cur.left + # Если узел для удаления отсутствует, сразу вернуть + if cur is None: + return + + # Число дочерних узлов = 0 или 1 + if cur.left is None or cur.right is None: + # Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + child = cur.left or cur.right + # Удалить узел cur + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # Если удаляемый узел является корнем, заново назначить корневой узел + self._root = child + # Число дочерних узлов = 2 + else: + # Получить следующий узел после cur в симметричном обходе + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # Рекурсивно удалить узел tmp + self.remove(tmp.val) + # Перезаписать cur значением tmp + cur.val = tmp.val + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация двоичного дерева поиска + bst = BinarySearchTree() + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + for num in nums: + bst.insert(num) + print("\nИсходное двоичное дерево\n") + print_tree(bst.get_root()) + + # Поиск узла + node = bst.search(7) + print("\nНайденный объект узла = {}, значение узла = {}".format(node, node.val)) + + # Вставка узла + bst.insert(16) + print("\nПосле вставки узла 16 двоичное дерево имеет вид\n") + print_tree(bst.get_root()) + + # Удаление узла + bst.remove(1) + print("\nПосле удаления узла 1 двоичное дерево имеет вид\n") + print_tree(bst.get_root()) + + bst.remove(2) + print("\nПосле удаления узла 2 двоичное дерево имеет вид\n") + print_tree(bst.get_root()) + + bst.remove(4) + print("\nПосле удаления узла 4 двоичное дерево имеет вид\n") + print_tree(bst.get_root()) diff --git a/ru/codes/python/chapter_tree/binary_tree.py b/ru/codes/python/chapter_tree/binary_tree.py new file mode 100644 index 000000000..fe4ff8cff --- /dev/null +++ b/ru/codes/python/chapter_tree/binary_tree.py @@ -0,0 +1,41 @@ +""" +File: binary_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализация двоичного дерева + # Инициализация узлов + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # Построить связи между узлами (указатели) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\nИнициализация двоичного дерева\n") + print_tree(n1) + + # Вставка и удаление узлов + P = TreeNode(0) + # Вставить узел P между n1 -> n2 + n1.left = P + P.left = n2 + print("\nПосле вставки узла P\n") + print_tree(n1) + # Удаление узла + n1.left = n2 + print("\nПосле удаления узла P\n") + print_tree(n1) diff --git a/ru/codes/python/chapter_tree/binary_tree_bfs.py b/ru/codes/python/chapter_tree/binary_tree_bfs.py new file mode 100644 index 000000000..b7f09ee46 --- /dev/null +++ b/ru/codes/python/chapter_tree/binary_tree_bfs.py @@ -0,0 +1,42 @@ +""" +File: binary_tree_bfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree +from collections import deque + + +def level_order(root: TreeNode | None) -> list[int]: + """Обход в ширину""" + # Инициализировать очередь и добавить корневой узел + queue: deque[TreeNode] = deque() + queue.append(root) + # Инициализировать список для хранения последовательности обхода + res = [] + while queue: + node: TreeNode = queue.popleft() # Извлечение из очереди + res.append(node.val) # Сохранить значение узла + if node.left is not None: + queue.append(node.left) # Поместить левый дочерний узел в очередь + if node.right is not None: + queue.append(node.right) # Поместить правый дочерний узел в очередь + return res + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализировать двоичное дерево + # Здесь используется функция, напрямую строящая двоичное дерево из массива + root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева\n") + print_tree(root) + + # Обход в ширину + res: list[int] = level_order(root) + print("\nПоследовательность печати узлов при обходе в ширину = ", res) diff --git a/ru/codes/python/chapter_tree/binary_tree_dfs.py b/ru/codes/python/chapter_tree/binary_tree_dfs.py new file mode 100644 index 000000000..aad2ffd34 --- /dev/null +++ b/ru/codes/python/chapter_tree/binary_tree_dfs.py @@ -0,0 +1,65 @@ +""" +File: binary_tree_dfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree + + +def pre_order(root: TreeNode | None): + """Предварительный обход""" + if root is None: + return + # Порядок обхода: корень -> левое поддерево -> правое поддерево + res.append(root.val) + pre_order(root=root.left) + pre_order(root=root.right) + + +def in_order(root: TreeNode | None): + """Симметричный обход""" + if root is None: + return + # Порядок обхода: левое поддерево -> корень -> правое поддерево + in_order(root=root.left) + res.append(root.val) + in_order(root=root.right) + + +def post_order(root: TreeNode | None): + """Обратный обход""" + if root is None: + return + # Порядок обхода: левое поддерево -> правое поддерево -> корень + post_order(root=root.left) + post_order(root=root.right) + res.append(root.val) + + +"""Driver Code""" +if __name__ == "__main__": + # Инициализировать двоичное дерево + # Здесь используется функция, напрямую строящая двоичное дерево из массива + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева\n") + print_tree(root) + + # Предварительный обход + res = [] + pre_order(root) + print("\nПоследовательность печати узлов при предварительном обходе = ", res) + + # Симметричный обход + res.clear() + in_order(root) + print("\nПоследовательность печати узлов при симметричном обходе = ", res) + + # Обратный обход + res.clear() + post_order(root) + print("\nПоследовательность печати узлов при обратном обходе = ", res) diff --git a/ru/codes/python/modules/__init__.py b/ru/codes/python/modules/__init__.py new file mode 100644 index 000000000..b10799e3c --- /dev/null +++ b/ru/codes/python/modules/__init__.py @@ -0,0 +1,19 @@ +# Follow the PEP 585 - Type Hinting Generics In Standard Collections +# https://peps.python.org/pep-0585/ +from __future__ import annotations + +# Import common libs here to simplify the code by `from module import *` +from .list_node import ( + ListNode, + list_to_linked_list, + linked_list_to_list, +) +from .tree_node import TreeNode, list_to_tree, tree_to_list +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import ( + print_matrix, + print_linked_list, + print_tree, + print_dict, + print_heap, +) diff --git a/ru/codes/python/modules/list_node.py b/ru/codes/python/modules/list_node.py new file mode 100644 index 000000000..252e5b250 --- /dev/null +++ b/ru/codes/python/modules/list_node.py @@ -0,0 +1,32 @@ +""" +File: list_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """Класс узла связного списка""" + + def __init__(self, val: int): + self.val: int = val # Значение узла + self.next: ListNode | None = None # Ссылка на узел-преемник + + +def list_to_linked_list(arr: list[int]) -> ListNode | None: + """Десериализовать список в связный список""" + dum = head = ListNode(0) + for a in arr: + node = ListNode(a) + head.next = node + head = head.next + return dum.next + + +def linked_list_to_list(head: ListNode | None) -> list[int]: + """Сериализовать связный список в список""" + arr: list[int] = [] + while head: + arr.append(head.val) + head = head.next + return arr diff --git a/ru/codes/python/modules/print_util.py b/ru/codes/python/modules/print_util.py new file mode 100644 index 000000000..d50c63ba8 --- /dev/null +++ b/ru/codes/python/modules/print_util.py @@ -0,0 +1,81 @@ +""" +File: print_util.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com) +""" + +from .tree_node import TreeNode, list_to_tree +from .list_node import ListNode, linked_list_to_list + + +def print_matrix(mat: list[list[int]]): + """Вывести матрицу""" + s = [] + for arr in mat: + s.append(" " + str(arr)) + print("[\n" + ",\n".join(s) + "\n]") + + +def print_linked_list(head: ListNode | None): + """Вывести связный список""" + arr: list[int] = linked_list_to_list(head) + print(" -> ".join([str(a) for a in arr])) + + +class Trunk: + def __init__(self, prev, string: str | None = None): + self.prev = prev + self.str = string + + +def show_trunks(p: Trunk | None): + if p is None: + return + show_trunks(p.prev) + print(p.str, end="") + + +def print_tree( + root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False +): + """ +Вывести двоичное дерево +Этот вывод дерева заимствован из TECHIE DELIGHT +https://www.techiedelight.com/c-program-print-binary-tree/ +""" + if root is None: + return + + prev_str = " " + trunk = Trunk(prev, prev_str) + print_tree(root.right, trunk, True) + + if prev is None: + trunk.str = "———" + elif is_right: + trunk.str = "/———" + prev_str = " |" + else: + trunk.str = "\———" + prev.str = prev_str + + show_trunks(trunk) + print(" " + str(root.val)) + if prev: + prev.str = prev_str + trunk.str = " |" + print_tree(root.left, trunk, False) + + +def print_dict(hmap: dict): + """Вывести словарь""" + for key, value in hmap.items(): + print(key, "->", value) + + +def print_heap(heap: list[int]): + """Вывести кучу""" + print("Массивное представление кучи:", heap) + print("Древовидное представление кучи:") + root: TreeNode | None = list_to_tree(heap) + print_tree(root) diff --git a/ru/codes/python/modules/tree_node.py b/ru/codes/python/modules/tree_node.py new file mode 100644 index 000000000..23b396ef2 --- /dev/null +++ b/ru/codes/python/modules/tree_node.py @@ -0,0 +1,69 @@ +""" +File: tree_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + +from collections import deque + + +class TreeNode: + """Класс узла двоичного дерева""" + + def __init__(self, val: int = 0): + self.val: int = val # Значение узла + self.height: int = 0 # Высота узла + self.left: TreeNode | None = None # Ссылка на левый дочерний узел + self.right: TreeNode | None = None # Ссылка на правый дочерний узел + + # Правила кодирования сериализации см.: + # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + # Массивное представление двоичного дерева: + # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + # Связное представление двоичного дерева: + # /——— 15 + # /——— 7 + # /——— 3 + # | \——— 6 + # | \——— 12 + # ——— 1 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 + + +def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: + """Десериализовать список в двоичное дерево: рекурсия""" + # Если индекс выходит за длину массива или соответствующий элемент равен None, вернуть None + if i < 0 or i >= len(arr) or arr[i] is None: + return None + # Построить текущий узел + root = TreeNode(arr[i]) + # Рекурсивно построить левое и правое поддеревья + root.left = list_to_tree_dfs(arr, 2 * i + 1) + root.right = list_to_tree_dfs(arr, 2 * i + 2) + return root + + +def list_to_tree(arr: list[int]) -> TreeNode | None: + """Десериализовать список в двоичное дерево""" + return list_to_tree_dfs(arr, 0) + + +def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: + """Сериализовать двоичное дерево в список: рекурсия""" + if root is None: + return + if i >= len(res): + res += [None] * (i - len(res) + 1) + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i + 1, res) + tree_to_list_dfs(root.right, 2 * i + 2, res) + + +def tree_to_list(root: TreeNode | None) -> list[int]: + """Сериализовать двоичное дерево в список""" + res = [] + tree_to_list_dfs(root, 0, res) + return res diff --git a/ru/codes/python/modules/vertex.py b/ru/codes/python/modules/vertex.py new file mode 100644 index 000000000..29e96f479 --- /dev/null +++ b/ru/codes/python/modules/vertex.py @@ -0,0 +1,20 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# Author: krahets (krahets@163.com) + + +class Vertex: + """Класс вершины""" + + def __init__(self, val: int): + self.val = val + + +def vals_to_vets(vals: list[int]) -> list["Vertex"]: + """На вход подается список значений vals, на выходе возвращается список вершин vets""" + return [Vertex(val) for val in vals] + + +def vets_to_vals(vets: list["Vertex"]) -> list[int]: + """На вход подается список вершин vets, на выходе возвращается список значений vals""" + return [vet.val for vet in vets] diff --git a/ru/codes/python/test_all.py b/ru/codes/python/test_all.py new file mode 100644 index 000000000..7c585de87 --- /dev/null +++ b/ru/codes/python/test_all.py @@ -0,0 +1,33 @@ +import os +import glob +import subprocess + +env = os.environ.copy() +env["PYTHONIOENCODING"] = "utf-8" + +if __name__ == "__main__": + # find source code files + src_paths = sorted(glob.glob("chapter_*/*.py")) + errors = [] + + # run python code + for src_path in src_paths: + process = subprocess.Popen( + ["python", src_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + encoding='utf-8' + ) + # Wait for the process to complete, and get the output and error messages + stdout, stderr = process.communicate() + # Check the exit status + exit_status = process.returncode + if exit_status != 0: + errors.append(stderr) + + print(f"Tested {len(src_paths)} files") + print(f"Found exception in {len(errors)} files") + if len(errors) > 0: + raise RuntimeError("\n\n".join(errors)) diff --git a/ru/codes/pythontutor/chapter_array_and_linkedlist/array.md b/ru/codes/pythontutor/chapter_array_and_linkedlist/array.md new file mode 100644 index 000000000..d6aa1ae0b --- /dev/null +++ b/ru/codes/pythontutor/chapter_array_and_linkedlist/array.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%BC%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%BC%20%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B8%D0%B7%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B0%20%5B0%2C%20len%28nums%29-1%5D%0A%20%20%20%20random_index%20%3D%20random.randint%280%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20nums%20%3D%22%2C%20random_num%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20num%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%20%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%BE%D0%BC%20index%20%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%BD%D0%B0%D0%B7%D0%B0%D0%B4%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%20index%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B8%D1%81%D0%B2%D0%BE%D0%B8%D1%82%D1%8C%20num%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20insert%28nums%2C%206%2C%203%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%206%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D1%8C%20%D0%B2%D1%81%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%20index%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%B2%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%0A%20%20%20%20for%20i%20in%20range%28index%2C%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20remove%28nums%2C%202%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%202%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D0%BC%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9D%D0%B5%D0%BF%D0%BE%D1%81%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%D0%9E%D0%B4%D0%BD%D0%BE%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%0A%20%20%20%20for%20i%2C%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B5%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums%2C%203%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%203%20%D0%B2%20nums%3A%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%3D%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=%23%20%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20list%20%D0%B2%20Python%20%E2%80%94%20%D1%8D%D1%82%D0%BE%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%2C%20%D0%B5%D0%B3%D0%BE%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D1%8F%D1%82%D1%8C%20%D0%BD%D0%B0%D0%BF%D1%80%D1%8F%D0%BC%D1%83%D1%8E%0A%23%20%D0%94%D0%BB%D1%8F%20%D1%83%D0%B4%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B0%20%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%8D%D1%82%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20list%20%D1%80%D0%B0%D1%81%D1%81%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%B0%D0%BA%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D0%BD%D0%B5%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D1%8F%D0%B5%D0%BC%D0%BE%D0%B9%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%0Adef%20extend%28nums%3A%20list%5Bint%5D%2C%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%A3%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%D0%A1%D0%BA%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B2%D1%81%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B2%20%D0%BD%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BD%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%D0%A0%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%0A%20%20%20%20nums%20%3D%20extend%28nums%2C%203%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B4%D0%BE%208%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md b/ru/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md new file mode 100644 index 000000000..f70ba6c57 --- /dev/null +++ b/ru/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20insert%28n0%3A%20ListNode%2C%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20n0%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0%2C%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20n0%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20access%28head%3A%20ListNode%2C%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%20index%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%0A%20%20%20%20node%20%3D%20access%28n0%2C%203%29%0A%20%20%20%20print%28%22%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20find%28head%3A%20ListNode%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20target%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20index%20%3D%20find%28n0%2C%202%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%202%20%D0%B2%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%BC%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_array_and_linkedlist/my_list.md b/ru/codes/pythontutor/chapter_array_and_linkedlist/my_list.md new file mode 100644 index 000000000..65213d968 --- /dev/null +++ b/ru/codes/pythontutor/chapter_array_and_linkedlist/my_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20MyList%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201%2C%20index%20-%201%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%27%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index%2C%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20%2A%20self.capacity%28%29%20%2A%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%20%20%20%20nums.insert%286%2C%20index%3D3%29%0A%20%20%20%20nums.remove%283%29%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%20%20%20%20nums.set%280%2C%201%29%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/n_queens.md b/ru/codes/pythontutor/chapter_backtracking/n_queens.md new file mode 100644 index 000000000..cc5b87227 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/n_queens.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28row%3A%20int%2C%20n%3A%20int%2C%20state%3A%20list%5Blist%5Bstr%5D%5D%2C%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D%2C%20cols%3A%20list%5Bbool%5D%2C%20diags1%3A%20list%5Bbool%5D%2C%20diags2%3A%20list%5Bbool%5D%29%3A%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20%28not%20diags1%5Bdiag1%5D%29%20and%20%28not%20diags2%5Bdiag2%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%27Q%27%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%27%23%27%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20state%20%3D%20%5B%5B%27%23%27%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20%2A%20n%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%20%20%20%20print%28f%27%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%B4%D0%BE%D1%81%D0%BA%D0%B8%20%3D%20%7Bn%7D%27%29%0A%20%20%20%20print%28f%27%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20%D1%84%D0%B5%D1%80%D0%B7%D0%B5%D0%B9%3A%20%7Blen%28res%29%7D%27%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%27--------------------%27%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/permutations_i.md b/ru/codes/pythontutor/chapter_backtracking/permutations_i.md new file mode 100644 index 000000000..fa2931d92 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/permutations_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20I%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20%D1%87%D0%B8%D1%81%D0%BB%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B8%20%D1%82%D0%BE%D1%82%20%D0%B6%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/permutations_ii.md b/ru/codes/pythontutor/chapter_backtracking/permutations_ii.md new file mode 100644 index 000000000..c7af6e540 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/permutations_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20%D1%87%D0%B8%D1%81%D0%BB%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B8%20%D1%82%D0%BE%D1%82%20%D0%B6%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B8%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%83%D0%B6%D0%B5%20%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md new file mode 100644 index 000000000..0d0fb0462 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%D0%BB%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20None%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%3A%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%201%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%D0%92%D1%81%D0%B5%20%D1%83%D0%B7%D0%BB%D1%8B%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%207%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md new file mode 100644 index 000000000..13360bed8 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%D0%BB%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20None%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%3A%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%202%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%D0%92%D1%81%D0%B5%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%BE%D1%82%20%D0%BA%D0%BE%D1%80%D0%BD%D1%8F%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%207%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md new file mode 100644 index 000000000..3bfaedab9 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B7%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%D0%BB%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20None%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B5%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%3A%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%203%22%22%22%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%D0%92%D1%81%D0%B5%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%BE%D1%82%20%D0%BA%D0%BE%D1%80%D0%BD%D1%8F%20%D0%BA%20%D1%83%D0%B7%D0%BB%D1%83%207%2C%20%D0%BD%D0%B5%20%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%89%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D1%81%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%203%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md new file mode 100644 index 000000000..1d485838b --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%3D0%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20%21%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28state%3A%20list%5BTreeNode%5D%2C%20choices%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20record_solution%28state%2C%20res%29%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20if%20is_valid%28state%2C%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state%2C%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20%5Bchoice.left%2C%20choice.right%5D%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state%2C%20choice%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3D%5Broot%5D%2C%20res%3Dres%29%0A%20%20%20%20print%28%27%5Cn%D0%92%D1%8B%D0%B2%D0%B5%D1%81%D1%82%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D1%83%D1%82%D0%B8%27%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/subset_sum_i.md b/ru/codes/pythontutor/chapter_backtracking/subset_sum_i.md new file mode 100644 index 000000000..c72cb3c38 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/subset_sum_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20target%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D1%8B%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%202%3A%20%D0%BD%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D1%81%20start%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%B8%D0%B7%D0%B1%D0%B5%D0%B6%D0%B0%D1%82%D1%8C%20%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D1%85%D1%81%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%201%3A%20%D0%B5%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B0%D0%B5%D1%82%20target%2C%20%D0%BD%D0%B5%D0%BC%D0%B5%D0%B4%D0%BB%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D1%86%D0%B8%D0%BA%D0%BB%0A%20%20%20%20%20%20%20%20%23%20%D0%AD%D1%82%D0%BE%20%D1%81%D0%B2%D1%8F%D0%B7%D0%B0%D0%BD%D0%BE%20%D1%81%20%D1%82%D0%B5%D0%BC%2C%20%D1%87%D1%82%D0%BE%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B6%D0%B5%20%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%2C%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%2C%20%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%82%D0%BE%D1%87%D0%BD%D0%BE%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%81%D0%B8%D1%82%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20target%20%D0%B8%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%20%28%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE%29%0A%20%20%20%20nums.sort%28%29%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20nums%0A%20%20%20%20start%20%3D%200%20%20%23%20%D0%A1%D1%82%D0%B0%D1%80%D1%82%D0%BE%D0%B2%D0%B0%D1%8F%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B0%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B0%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2%20%28%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%29%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%81%20%D1%81%D1%83%D0%BC%D0%BC%D0%BE%D0%B9%20%7Btarget%7D%3A%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md b/ru/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md new file mode 100644 index 000000000..6b5ab1bd5 --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%0A%20%20%20%20target%3A%20int%2C%0A%20%20%20%20total%3A%20int%2C%0A%20%20%20%20choices%3A%20list%5Bint%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20target%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%B5%D1%81%D0%BB%D0%B8%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B0%D0%B5%D1%82%20target%2C%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D1%82%D0%BE%D1%82%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%2C%20total%20%2B%20choices%5Bi%5D%2C%20choices%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%20I%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%20%28%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE%29%0A%20%20%20%20total%20%3D%200%20%20%23%20%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2%20%28%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%29%0A%20%20%20%20backtrack%28state%2C%20target%2C%20total%2C%20nums%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%81%20%D1%81%D1%83%D0%BC%D0%BC%D0%BE%D0%B9%20%7Btarget%7D%3A%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%8D%D1%82%D0%BE%D0%B3%D0%BE%20%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%B0%20%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%B5%D1%81%D1%8F%20%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_backtracking/subset_sum_ii.md b/ru/codes/pythontutor/chapter_backtracking/subset_sum_ii.md new file mode 100644 index 000000000..634040f9f --- /dev/null +++ b/ru/codes/pythontutor/chapter_backtracking/subset_sum_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%20%2B%201%2C%20res%29%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20state%20%3D%20%5B%5D%0A%20%20%20%20nums.sort%28%29%0A%20%20%20%20start%20%3D%200%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums%2C%20target%29%0A%20%20%20%20print%28f%27%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%27%29%0A%20%20%20%20print%28f%27%D0%92%D1%81%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0%20%D1%81%20%D1%81%D1%83%D0%BC%D0%BC%D0%BE%D0%B9%20%7Btarget%7D%3A%20res%20%3D%20%7Bres%7D%27%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_computational_complexity/iteration.md b/ru/codes/pythontutor/chapter_computational_complexity/iteration.md new file mode 100644 index 000000000..f69849e66 --- /dev/null +++ b/ru/codes/pythontutor/chapter_computational_complexity/iteration.md @@ -0,0 +1,29 @@ + + + +https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A6%D0%B8%D0%BA%D0%BB%20for%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20for%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& + + +https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A6%D0%B8%D0%BA%D0%BB%20while%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D0%BE%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D1%83%D1%8E%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%83%D1%8E%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20while%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A6%D0%B8%D0%BA%D0%BB%20while%20%28%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B5%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%29%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D0%BE%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%201%2C%204%2C%2010%2C%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D1%83%D1%8E%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%83%D1%8E%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20%2A%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20while%20%28%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B5%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%29%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%20for%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%BF%D0%BE%20i%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%BF%D0%BE%20j%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D%2C%20%7Bj%7D%29%2C%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B0%20%D0%B2%20%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%BC%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20for%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%3A%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A7%D0%BB%D0%B5%D0%BD%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%20%D1%81%20%D0%BD%D0%BE%D0%BC%D0%B5%D1%80%D0%BE%D0%BC%20%7Bn%7D%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%98%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%8F%D0%B2%D0%BD%D1%8B%D0%B9%20%D1%81%D1%82%D0%B5%D0%BA%20%D0%B4%D0%BB%D1%8F%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%BE%D0%B2%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8E%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BF%D1%80%D0%B8%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_computational_complexity/recursion.md b/ru/codes/pythontutor/chapter_computational_complexity/recursion.md new file mode 100644 index 000000000..bf686c970 --- /dev/null +++ b/ru/codes/pythontutor/chapter_computational_complexity/recursion.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%D0%B9%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%A3%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%3A%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A7%D0%BB%D0%B5%D0%BD%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8%20%D1%81%20%D0%BD%D0%BE%D0%BC%D0%B5%D1%80%D0%BE%D0%BC%20%7Bn%7D%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%98%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D1%8F%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%8F%D0%B2%D0%BD%D1%8B%D0%B9%20%D1%81%D1%82%D0%B5%D0%BA%20%D0%B4%D0%BB%D1%8F%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%BE%D0%B2%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%3A%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8E%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%D0%92%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%3A%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BC%D0%B8%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%C2%BB%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%20%D1%81%D1%83%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BF%D1%80%D0%B8%20%D0%B8%D0%BC%D0%B8%D1%82%D0%B0%D1%86%D0%B8%D0%B8%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B8%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_computational_complexity/space_complexity.md b/ru/codes/pythontutor/chapter_computational_complexity/space_complexity.md new file mode 100644 index 000000000..bd2d835ee --- /dev/null +++ b/ru/codes/pythontutor/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%83%D0%B7%D0%B5%D0%BB-%D0%BF%D1%80%D0%B5%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F%22%22%22%0A%20%20%20%20%23%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%BD%D0%B5%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D0%B0%D0%BD%D1%82%D1%8B%2C%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B8%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%8E%D1%82%20O%281%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%8E%D1%82%20O%281%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%8E%D1%82%20O%281%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20n%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20O%28n%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20n%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20O%28n%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20hmap%20%3D%20dict%5Bint%2C%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20print%28%22%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F%20n%20%3D%22%2C%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20%23%20%D0%94%D0%B2%D1%83%D0%BC%D0%B5%D1%80%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B7%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D0%B5%D1%82%20O%28n%5E2%29%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20%2A%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20nums%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20n%2C%20n-1%2C%20...%2C%202%2C%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D0%BB%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%29%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_computational_complexity/time_complexity.md b/ru/codes/pythontutor/chapter_computational_complexity/time_complexity.md new file mode 100644 index 000000000..c83c6651d --- /dev/null +++ b/ru/codes/pythontutor/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,38 @@ + + + +https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D0%B0%D0%BD%D1%82%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%29%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BF%D1%80%D0%BE%D0%BF%D0%BE%D1%80%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20%2A%20n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%20%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D1%82%20%D0%BE%D1%82%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%B0%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%29%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%D0%A1%D1%87%D0%B5%D1%82%D1%87%D0%B8%D0%BA%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D0%B5%D1%82%203%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%BD%D1%8B%D0%B5%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%5D%20%20%23%20%5Bn%2C%20n-1%2C%20...%2C%202%2C%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%D0%9D%D0%B0%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%BC%20%D1%88%D0%B0%D0%B3%D0%B5%20%D0%BA%D0%BB%D0%B5%D1%82%D0%BA%D0%B0%20%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%D0%B4%D0%B2%D0%BE%D0%B5%2C%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D1%83%D1%8F%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%201%2C%202%2C%204%2C%208%2C%20...%2C%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20%2A%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D1%8D%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%AD%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D1%8D%D0%BA%D1%81%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20%2F%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20%2F%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE-%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20%2F%2F%202%29%20%2B%20linear_log_recur%28n%20%2F%2F%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE-%D0%BB%D0%BE%D0%B3%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A4%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%98%D0%B7%20%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20n%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D1%85%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%D0%A7%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%20%D1%84%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8%20%28%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md b/ru/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md new file mode 100644 index 000000000..3e199562a --- /dev/null +++ b/ru/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%201%2C%202%2C%20...%2C%20n%20%D0%B2%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D0%BE%D0%BC%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%3A%201%2C%202%2C%203%2C%20...%2C%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D0%BE%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%88%D0%B0%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%201%20%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B5%20nums%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%201%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%2C%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BB%D1%83%D1%87%D1%88%D0%B0%D1%8F%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%201%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BA%D0%BE%D0%BD%D1%86%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%2C%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D1%85%D1%83%D0%B4%D1%88%D0%B0%D1%8F%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%5B1%2C%202%2C%20...%2C%20n%5D%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%88%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20nums%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%201%20%3D%22%2C%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md b/ru/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 000000000..0853e0800 --- /dev/null +++ b/ru/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%3A%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i%2C%20j%29%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%D0%BF%D1%83%D1%81%D1%82%2C%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BE%D1%82%D1%81%D1%83%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28m%2B1%2C%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20m%20%2B%201%2C%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i%2C%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20i%2C%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20f%280%2C%20n-1%29%0A%20%20%20%20return%20dfs%28nums%2C%20target%2C%200%2C%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%206%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_divide_and_conquer/build_tree.md b/ru/codes/pythontutor/chapter_divide_and_conquer/build_tree.md new file mode 100644 index 000000000..a49029c2d --- /dev/null +++ b/ru/codes/pythontutor/chapter_divide_and_conquer/build_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D%2C%0A%20%20%20%20inorder_map%3A%20dict%5Bint%2C%20int%5D%2C%0A%20%20%20%20i%3A%20int%2C%0A%20%20%20%20l%3A%20int%2C%0A%20%20%20%20r%3A%20int%2C%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%3A%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9%20%D0%B8%20%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%97%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B8%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D0%BC%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20m%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%3A%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20root.left%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%2C%20l%2C%20m%20-%201%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%3A%20%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20root.right%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%20%2B%20m%20-%20l%2C%20m%20%2B%201%2C%20r%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D%2C%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B8%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20inorder%20%D0%B8%D1%85%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D0%BC%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i%2C%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder%2C%20inorder_map%2C%200%2C%200%2C%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3%2C%209%2C%202%2C%201%2C%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9%2C%203%2C%201%2C%202%2C%207%5D%0A%20%20%20%20print%28f%22%D0%9F%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%D0%A1%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder%2C%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_divide_and_conquer/hanota.md b/ru/codes/pythontutor/chapter_divide_and_conquer/hanota.md new file mode 100644 index 000000000..bd302fc25 --- /dev/null +++ b/ru/codes/pythontutor/chapter_divide_and_conquer/hanota.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B4%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B4%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%8B%20src%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B8%D1%81%D0%BA%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%83%20tar%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int%2C%20src%3A%20list%5Bint%5D%2C%20buf%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D0%A5%D0%B0%D0%BD%D0%BE%D0%B9%D1%81%D0%BA%D0%BE%D0%B9%20%D0%B1%D0%B0%D1%88%D0%BD%D0%B8%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%20src%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%81%D1%8F%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B4%D0%B8%D1%81%D0%BA%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B2%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i-1%29%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B5%20i-1%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%20%D0%B8%D0%B7%20src%20%D0%B2%20buf%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20tar%0A%20%20%20%20dfs%28i%20-%201%2C%20src%2C%20tar%2C%20buf%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%281%29%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B8%D0%B9%D1%81%D1%8F%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B4%D0%B8%D1%81%D0%BA%20%D0%B8%D0%B7%20src%20%D0%B2%20tar%0A%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B0%20f%28i-1%29%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B5%20i-1%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%20%D0%B8%D0%B7%20buf%20%D0%B2%20tar%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20src%0A%20%20%20%20dfs%28i%20-%201%2C%20buf%2C%20src%2C%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D%2C%20B%3A%20list%5Bint%5D%2C%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D1%88%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D0%A5%D0%B0%D0%BD%D0%BE%D0%B9%D1%81%D0%BA%D0%BE%D0%B9%20%D0%B1%D0%B0%D1%88%D0%BD%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B5%20n%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%20%D0%B8%D0%B7%20A%20%D0%B2%20C%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20B%0A%20%20%20%20dfs%28n%2C%20A%2C%20B%2C%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%A5%D0%B2%D0%BE%D1%81%D1%82%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%0A%20%20%20%20A%20%3D%20%5B5%2C%204%2C%203%2C%202%2C%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A%2C%20B%2C%20C%29%0A%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B4%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md new file mode 100644 index 000000000..49721d443 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%20n-%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%2C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%20n-%D1%8E%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D1%8C%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%20%D0%9C%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BD%D0%B0%201%20%D0%B8%D0%BB%D0%B8%202%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%0A%20%20%20%20state%20%3D%200%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D1%81%200-%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20res%5B0%5D%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md new file mode 100644 index 000000000..d242a27aa --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D1%81%20%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%5B1%5D%5B1%5D%2C%20dp%5B1%5D%5B2%5D%20%3D%201%2C%200%0A%20%20%20%20dp%5B2%5D%5B1%5D%2C%20dp%5B2%5D%5B2%5D%20%3D%200%2C%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%BE%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D0%B5%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md new file mode 100644 index 000000000..f8b317ddd --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%D0%B8%20dp%5B2%5D%20%D1%83%D0%B6%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md new file mode 100644 index 000000000..f310faf45 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%2C%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%D0%B8%20dp%5B2%5D%20%D1%83%D0%B6%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%20dp%5Bi%5D%20%D1%81%D1%83%D1%89%D0%B5%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B5%0A%20%20%20%20if%20mem%5Bi%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%2C%20mem%29%20%2B%20dfs%28i%20-%202%2C%20mem%29%0A%20%20%20%20%23%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BD%D0%B0%20i-%D1%8E%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D1%8C%2C%20-1%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%20%D0%BE%D1%82%D1%81%D1%83%D1%82%D1%81%D1%82%D0%B2%D0%B8%D0%B5%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n%2C%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md new file mode 100644 index 000000000..9f2cd5ac7 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%201%2C%202%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%BE%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D0%B5%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a%2C%20b%20%3D%201%2C%202%0A%20%20%20%20for%20_%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/coin_change.md b/ru/codes/pythontutor/chapter_dynamic_programming/coin_change.md new file mode 100644 index 000000000..12b9d822e --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/coin_change.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%20%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D%2C%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D%2C%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md b/ru/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md new file mode 100644 index 000000000..5f4a9fe43 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20II%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D0%BA%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20II%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D1%83%D0%BC%D0%BC%D0%B0%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D0%BA%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D0%B9%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/edit_distance.md b/ru/codes/pythontutor/chapter_dynamic_programming/edit_distance.md new file mode 100644 index 000000000..681dac182 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/edit_distance.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%20%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%2C%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D1%8B%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%D0%A7%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%7Bs%7D%20%D0%B2%20%7Bt%7D%2C%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%7Bres%7D%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%D0%92%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%20%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20dp%5Bi-1%2C%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%2C%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D1%8B%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%2C%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BE%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20dp%5Bi-1%2C%20j-1%5D%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%B9%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%D0%A7%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%7Bs%7D%20%D0%B2%20%7Bt%7D%2C%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%7Bres%7D%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/knapsack.md b/ru/codes/pythontutor/chapter_dynamic_programming/knapsack.md new file mode 100644 index 000000000..0fd4a5095 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/knapsack.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20i%3A%20int%2C%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D1%8B%20%D1%83%D0%B6%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D0%BC%D0%BE%D1%82%D1%80%D0%B5%D0%BD%D1%8B%20%D0%B8%D0%BB%D0%B8%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%B2%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%20%D0%B8%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%20%D1%81%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%D1%8E%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%B2%D0%BE%D0%B7%D0%BC%D0%BE%D0%B6%D0%BD%D1%8B%D1%85%0A%20%20%20%20return%20max%28no%2C%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D1%81%D0%B5%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D1%8B%20%D1%83%D0%B6%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D0%BC%D0%BE%D1%82%D1%80%D0%B5%D0%BD%D1%8B%20%D0%B8%D0%BB%D0%B8%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%20%D1%83%D0%B6%D0%B5%20%D0%B5%D1%81%D1%82%D1%8C%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%B2%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%20%D0%B8%20%D0%BA%D0%BB%D0%B0%D0%B4%D1%83%D1%82%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%20%D1%81%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%D1%8E%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no%2C%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%200-1%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%20%D0%B2%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md b/ru/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md new file mode 100644 index 000000000..589402c87 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%BE%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D0%BF%D0%BE%D0%B4%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D0%B5%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D%2C%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B5%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a%2C%20b%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20min%28a%2C%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B5%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%D0%B0%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md b/ru/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md new file mode 100644 index 000000000..6db438bc7 --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%8D%D1%82%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D1%8F%D1%8F%20%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%8F%D1%87%D0%B5%D0%B9%D0%BA%D0%B0%2C%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%D0%BB%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D1%8F%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%2B%E2%88%9E%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B4%D0%BE%20%28i-1%2C%20j%29%20%D0%B8%20%28i%2C%20j-1%29%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B4%D0%BE%20%28i%2C%20j%29%0A%20%20%20%20return%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%8D%D1%82%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D1%8F%D1%8F%20%D0%BB%D0%B5%D0%B2%D0%B0%D1%8F%20%D1%8F%D1%87%D0%B5%D0%B9%D0%BA%D0%B0%2C%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%D0%BB%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D0%B0%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D1%8F%D1%82%20%D0%B7%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%2B%E2%88%9E%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%20%D1%83%D0%B6%D0%B5%20%D0%B5%D1%81%D1%82%D1%8C%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B4%D0%BB%D1%8F%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D0%B8%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B9%20%D1%8F%D1%87%D0%B5%D0%B5%D0%BA%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B4%D0%BE%20%28i%2C%20j%29%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%D0%9F%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%20%D0%BC%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20m%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%BF%D1%83%D1%82%D0%B8%20%D0%B8%D0%B7%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B3%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md b/ru/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md new file mode 100644 index 000000000..c3a1dd2cd --- /dev/null +++ b/ru/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20dp%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B0%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%20%D0%BD%D0%B5%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%91%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B5%20%D0%B8%D0%B7%20%D0%B4%D0%B2%D1%83%D1%85%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%3A%20%D0%BD%D0%B5%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BB%D0%B8%20%D0%B2%D0%B7%D1%8F%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B5%D0%B9%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_graph/graph_adjacency_list.md b/ru/codes/pythontutor/chapter_graph/graph_adjacency_list.md new file mode 100644 index 000000000..3171c6637 --- /dev/null +++ b/ru/codes/pythontutor/chapter_graph/graph_adjacency_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1%2C%203%2C%202%2C%205%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%20%20%20%20graph.add_edge%28v%5B0%5D%2C%20v%5B2%5D%29%0A%20%20%20%20graph.remove_edge%28v%5B0%5D%2C%20v%5B1%5D%29%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md b/ru/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md new file mode 100644 index 000000000..76b2c4f58 --- /dev/null +++ b/ru/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20vertices%3A%20list%5Bint%5D%2C%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D%2C%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20vertices%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0%2C%201%5D%2C%20%5B0%2C%203%5D%2C%20%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B2%2C%204%5D%2C%20%5B3%2C%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices%2C%20edges%29%0A%20%20%20%20graph.add_edge%280%2C%202%29%0A%20%20%20%20graph.remove_edge%280%2C%201%29%0A%20%20%20%20graph.add_vertex%286%29%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_graph/graph_bfs.md b/ru/codes/pythontutor/chapter_graph/graph_bfs.md new file mode 100644 index 000000000..040cc0a25 --- /dev/null +++ b/ru/codes/pythontutor/chapter_graph/graph_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%0A%20%20%20%20%20%20%20%20res.append%28vet%29%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%20%20%20%20res%20%3D%20graph_bfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_graph/graph_dfs.md b/ru/codes/pythontutor/chapter_graph/graph_dfs.md new file mode 100644 index 000000000..199e80143 --- /dev/null +++ b/ru/codes/pythontutor/chapter_graph/graph_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0Adef%20dfs%28graph%3A%20GraphAdjList%2C%20visited%3A%20set%5BVertex%5D%2C%20res%3A%20list%5BVertex%5D%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20res.append%28vet%29%0A%20%20%20%20visited.add%28vet%29%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20adjVet%29%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20start_vet%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20res%20%3D%20graph_dfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_greedy/coin_change_greedy.md b/ru/codes/pythontutor/chapter_greedy/coin_change_greedy.md new file mode 100644 index 000000000..c816060f4 --- /dev/null +++ b/ru/codes/pythontutor/chapter_greedy/coin_change_greedy.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%2C%20%D1%87%D1%82%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20coins%20%D1%83%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D1%87%D0%B5%D0%BD%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BD%D0%B5%D1%82%D1%81%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%2C%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D0%B0%D1%8F%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BA%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%B8%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%20%D0%BD%D0%B5%D0%BC%D1%83%20%D0%B1%D0%BB%D0%B8%D0%B7%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BE%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%BD%D0%B5%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%D0%9D%D0%B0%20%D1%81%D0%B0%D0%BC%D0%BE%D0%BC%20%D0%B4%D0%B5%D0%BB%D0%B5%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%203%3A%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_greedy/fractional_knapsack.md b/ru/codes/pythontutor/chapter_greedy/fractional_knapsack.md new file mode 100644 index 000000000..5561e7dc3 --- /dev/null +++ b/ru/codes/pythontutor/chapter_greedy/fractional_knapsack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%D0%92%D0%B5%D1%81%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%D0%A1%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%94%D1%80%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D1%81%20%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%20%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0%D0%BC%D0%B8%3A%20%D0%B2%D0%B5%D1%81%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%20%D1%83%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20item.v%20%2F%20item.w%20%D0%B2%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%20%D1%83%D0%B1%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20%2F%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D1%86%D0%B5%D0%BB%D0%B8%D0%BA%D0%BE%D0%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%BD%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%87%D0%B0%D1%81%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20%2F%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%20%D0%B8%D0%B7%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_greedy/max_capacity.md b/ru/codes/pythontutor/chapter_greedy/max_capacity.md new file mode 100644 index 000000000..6ad9dda0c --- /dev/null +++ b/ru/codes/pythontutor/chapter_greedy/max_capacity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%82%D0%B0%D0%BA%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BE%D0%BD%D0%B8%20%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%BB%D0%B0%D0%B3%D0%B0%D0%BB%D0%B8%D1%81%D1%8C%20%D0%BF%D0%BE%20%D0%B4%D0%B2%D1%83%D0%BC%20%D0%BA%D0%BE%D0%BD%D1%86%D0%B0%D0%BC%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%B4%D0%B2%D0%B5%20%D0%B4%D0%BE%D1%81%D0%BA%D0%B8%20%D0%BD%D0%B5%20%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%82%D1%8F%D1%82%D1%81%D1%8F%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%B3%D0%B0%D1%82%D1%8C%20%D0%B2%D0%BD%D1%83%D1%82%D1%80%D1%8C%20%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D1%83%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_greedy/max_product_cutting.md b/ru/codes/pythontutor/chapter_greedy/max_product_cutting.md new file mode 100644 index 000000000..ea3df384c --- /dev/null +++ b/ru/codes/pythontutor/chapter_greedy/max_product_cutting.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20n%20%3C%3D%203%2C%20%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%BD%D0%BE%D0%B6%D0%B8%D1%82%D0%B5%D0%BB%D0%B8%203%2C%20%D0%B3%D0%B4%D0%B5%20a%20%E2%80%94%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%82%D1%80%D0%BE%D0%B5%D0%BA%2C%20%D0%B0%20b%20%E2%80%94%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%0A%20%20%20%20a%2C%20b%20%3D%20n%20%2F%2F%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%201%2C%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%201%20%2A%203%20%D0%B2%202%20%2A%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%202%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%200%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_hashing/array_hash_map.md b/ru/codes/pythontutor/chapter_hashing/array_hash_map.md new file mode 100644 index 000000000..61f0b4e47 --- /dev/null +++ b/ru/codes/pythontutor/chapter_hashing/array_hash_map.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20ArrayHashMap%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20%2A%2020%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%20%20%20%20hmap.put%2812836%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%27%29%0A%20%20%20%20hmap.put%2815937%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%27%29%0A%20%20%20%20hmap.put%2816750%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%27%29%0A%20%20%20%20hmap.put%2813276%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%27%29%0A%20%20%20%20hmap.put%2810583%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%AF%27%29%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%20%20%20%20hmap.remove%2810583%29%0A%20%20%20%20print%28%27%5Cn%D0%9E%D1%82%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%20%D0%BF%D0%B0%D1%80%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%27%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_hashing/hash_map_chaining.md b/ru/codes/pythontutor/chapter_hashing/hash_map_chaining.md new file mode 100644 index 000000000..8f384391e --- /dev/null +++ b/ru/codes/pythontutor/chapter_hashing/hash_map_chaining.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20%2F%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20return%20self.size%20%2F%20self.capacity%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20%2A%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key%2C%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%27%20-%3E%20%27%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%20%20%20%20hashmap.put%2812836%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%27%29%0A%20%20%20%20hashmap.put%2815937%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%27%29%0A%20%20%20%20hashmap.put%2816750%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%27%29%0A%20%20%20%20hashmap.put%2813276%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%27%29%0A%20%20%20%20hashmap.put%2810583%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%AF%27%29%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_hashing/simple_hash.md b/ru/codes/pythontutor/chapter_hashing/simple_hash.md new file mode 100644 index 000000000..26c584154 --- /dev/null +++ b/ru/codes/pythontutor/chapter_hashing/simple_hash.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%90%D0%B4%D0%B4%D0%B8%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5%20%D1%85%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D1%83%D0%BB%D1%8C%D1%82%D0%B8%D0%BF%D0%BB%D0%B8%D0%BA%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5%20%D1%85%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20%2A%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22XOR-%D1%85%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A5%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%BC%20%D1%81%D0%B4%D0%B2%D0%B8%D0%B3%D0%BE%D0%BC%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20Algo%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D1%81%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%3D%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D1%83%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%3D%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20XOR%20%3D%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%D0%A5%D0%B5%D1%88-%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D1%81%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%BC%20%D1%81%D0%B4%D0%B2%D0%B8%D0%B3%D0%BE%D0%BC%20%3D%20%7Bhash%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_heap/my_heap.md b/ru/codes/pythontutor/chapter_heap/my_heap.md new file mode 100644 index 000000000..3140e7736 --- /dev/null +++ b/ru/codes/pythontutor/chapter_heap/my_heap.md @@ -0,0 +1,20 @@ + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%28self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%29%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%28l%2C%20r%2C%20ma%29%20%3D%20%28self.left%28i%29%2C%20self.right%28i%29%2C%20i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1%2C%202%2C%203%2C%204%2C%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BA%D1%83%D1%87%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%20%D0%B1%D0%B5%D0%B7%20%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B5%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%80%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%D0%9E%D0%BA%D1%80%D1%83%D0%B3%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%B7%20%D0%BF%D1%80%D0%B8%20%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B8%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%B0%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BA%D1%83%D1%87%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%3A%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%83%D0%B6%D0%B5%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%BE%D1%80%D1%80%D0%B5%D0%BA%D1%82%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D1%81%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%8B%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%20%3D%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%28self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%29%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%28self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%29%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BA%D1%83%D1%87%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%27%29%0A%20%20%20%20%20%20%20%20self.swap%280%2C%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%28l%2C%20r%2C%20ma%29%20%3D%20%28self.left%28i%29%2C%20self.right%28i%29%2C%20i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%207%2C%206%2C%207%2C%206%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%2C%205%5D%29%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_heap/top_k.md b/ru/codes/pythontutor/chapter_heap/top_k.md new file mode 100644 index 000000000..7b49b6b0f --- /dev/null +++ b/ru/codes/pythontutor/chapter_heap/top_k.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20k%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20%D0%BA%D1%83%D1%87%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B5%20k%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20k%2B1%2C%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%D0%B9%20k%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BD%D0%B0%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D0%BD%D1%83%20%D0%BA%D1%83%D1%87%D0%B8%20%D0%B8%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_searching/binary_search.md b/ru/codes/pythontutor/chapter_searching/binary_search.md new file mode 100644 index 000000000..bf47eae34 --- /dev/null +++ b/ru/codes/pythontutor/chapter_searching/binary_search.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%2C%20%D1%82%D0%BE%20%D0%B5%D1%81%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D1%8E%D1%82%20%D0%BD%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D0%B8%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B0%D0%B5%D1%82%D1%81%D1%8F%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%20%28%D0%BF%D1%80%D0%B8%20i%20%3E%20j%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D1%83%D1%81%D1%82%29%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D0%B2%20Python%20%D0%BC%D0%BE%D0%B3%D1%83%D1%82%20%D0%B1%D1%8B%D1%82%D1%8C%20%D1%81%D0%BA%D0%BE%D0%BB%D1%8C%20%D1%83%D0%B3%D0%BE%D0%B4%D0%BD%D0%BE%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D0%BC%D0%B8%20%28%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D1%8B%20%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BC%D0%BE%D0%BC%20%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8%29%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20%D0%BD%D0%B5%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D1%83%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B8%D1%85%20%D1%87%D0%B8%D1%81%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%0A%20%20%20%20return%20-1%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%206%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%2C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%2C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n%29%2C%20%D1%82%D0%BE%20%D0%B5%D1%81%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D1%8E%D1%82%20%D0%BD%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%B8%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B7%D0%B0%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%20%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B0%D0%B5%D1%82%D1%81%D1%8F%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%20%28%D0%BF%D1%80%D0%B8%20i%20%3D%20j%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%BF%D1%83%D1%81%D1%82%29%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%29%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%D0%AD%D1%82%D0%BE%20%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%2C%20%D1%87%D1%82%D0%BE%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m%29%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%0A%20%20%20%20return%20-1%20%20%23%20%D0%A6%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%28%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%2C%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%29%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums%2C%20target%29%0A%20%20%20%20print%28%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%86%D0%B5%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%206%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_searching/binary_search_edge.md b/ru/codes/pythontutor/chapter_searching/binary_search_edge.md new file mode 100644 index 000000000..bf0145ace --- /dev/null +++ b/ru/codes/pythontutor/chapter_searching/binary_search_edge.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20target%22%22%22%0A%20%20%20%20%23%20%D0%AD%D0%BA%D0%B2%D0%B8%D0%B2%D0%B0%D0%BB%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D1%83%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20target%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20%23%20target%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20target%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B9%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20target%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D1%83%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D0%B5%D1%82%20%D0%BD%D0%B0%20%D1%81%D0%B0%D0%BC%D1%8B%D0%B9%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20target%2C%20%D0%B0%20i%20%E2%80%94%20%D0%BD%D0%B0%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20target%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20target%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20target%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%B9%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B9%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B0%D0%BC%D0%BE%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_searching/binary_search_insertion.md b/ru/codes/pythontutor/chapter_searching/binary_search_insertion.md new file mode 100644 index 000000000..fe183f3c4 --- /dev/null +++ b/ru/codes/pythontutor/chapter_searching/binary_search_insertion.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D0%B1%D0%B5%D0%B7%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D1%85%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20target%20%D0%B8%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20m%0A%20%20%20%20%23%20target%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D1%85%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%28%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%29%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%20%D0%B7%D0%B0%D0%BC%D0%BA%D0%BD%D1%83%D1%82%D1%8B%D0%B9%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%20%5B0%2C%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%8B%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bm%2B1%2C%20j%5D%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20target%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D0%B5%20%5Bi%2C%20m-1%5D%0A%20%20%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%82%D0%BE%D1%87%D0%BA%D1%83%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%D1%81%D1%8F%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%D0%91%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%7Btarget%7D%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_searching/two_sum.md b/ru/codes/pythontutor/chapter_searching/two_sum.md new file mode 100644 index 000000000..bb7ccdf62 --- /dev/null +++ b/ru/codes/pythontutor/chapter_searching/two_sum.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B5%D1%82%D0%BE%D0%B4%201%3A%20%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%22%22%22%0A%20%20%20%20%23%20%D0%94%D0%B2%D0%B0%20%D0%B2%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%BD%D1%8B%D1%85%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B0%2C%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B5%D1%82%D0%BE%D0%B4%202%3A%20%D0%B2%D1%81%D0%BF%D0%BE%D0%BC%D0%BE%D0%B3%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0%22%22%22%0A%20%20%20%20%23%20%D0%92%D1%81%D0%BF%D0%BE%D0%BC%D0%BE%D0%B3%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0%2C%20%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%D0%9E%D0%B4%D0%B8%D0%BD%20%D1%86%D0%B8%D0%BA%D0%BB%2C%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F%20%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D%2C%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/bubble_sort.md b/ru/codes/pythontutor/chapter_sorting/bubble_sort.md new file mode 100644 index 000000000..b75227efe --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/bubble_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%28%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%84%D0%BB%D0%B0%D0%B3%D0%BE%D0%BC%29%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%84%D0%BB%D0%B0%D0%B3%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%D0%9D%D0%B0%20%D1%8D%D1%82%D0%BE%D0%B9%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8%20%C2%AB%D0%B2%D1%81%D0%BF%D0%BB%D1%8B%D1%82%D0%B8%D1%8F%C2%BB%20%D0%BD%D0%B5%20%D0%B1%D1%8B%D0%BB%D0%BE%20%D0%BD%D0%B8%20%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%D0%B0%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/bucket_sort.md b/ru/codes/pythontutor/chapter_sorting/bucket_sort.md new file mode 100644 index 000000000..5084d0213 --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/bucket_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20k%20%3D%20n%2F2%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%2C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%B0%D0%B3%D0%B0%D1%8F%20%D1%80%D0%B0%D1%81%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%202%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%20%D0%BA%D0%B0%D0%B6%D0%B4%D1%83%D1%8E%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%83%0A%20%20%20%20k%20%3D%20len%28nums%29%20%2F%2F%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%D0%A0%D0%B0%D1%81%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D0%BF%D0%BE%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D0%B0%D0%BC%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B5%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%BB%D0%B5%D0%B6%D0%B0%D1%82%20%D0%B2%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B5%20%5B0%2C%201%29%3B%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20num%20%2A%20k%20%D0%B4%D0%BB%D1%8F%20%D0%BE%D1%82%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%BE%D0%B2%20%5B0%2C%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20num%20%D0%B2%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%83%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D1%83%20%D0%B2%D0%BD%D1%83%D1%82%D1%80%D0%B8%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%B9%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%8B%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B2%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%BD%D1%83%D1%8E%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8E%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%3B%20%D0%B5%D0%B5%20%D1%82%D0%B0%D0%BA%D0%B6%D0%B5%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B4%D1%80%D1%83%D0%B3%D0%B8%D0%BC%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%BC%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D1%8B%20%D0%B8%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%B4%D0%B8%D0%BD%D0%B8%D1%82%D1%8C%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D1%8B%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9F%D1%83%D1%81%D1%82%D1%8C%20%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B5%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5%20%E2%80%94%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D1%81%20%D0%BF%D0%BB%D0%B0%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%B9%20%D1%82%D0%BE%D1%87%D0%BA%D0%BE%D0%B9%20%D0%B8%D0%B7%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BA%D0%BE%D1%80%D0%B7%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/counting_sort.md b/ru/codes/pythontutor/chapter_sorting/counting_sort.md new file mode 100644 index 000000000..b0079f68e --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/counting_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%2C%20%D0%BD%D0%B5%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%D0%B8%D1%82%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BE%D0%B2%0A%20%20%20%20%23%201.%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m%2C%20num%29%0A%20%20%20%20%23%202.%20%D0%9F%D0%BE%D0%B4%D1%81%D1%87%D0%B8%D1%82%D0%B0%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%B9%20%D1%86%D0%B8%D1%84%D1%80%D1%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20num%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20counter%20%D0%B8%20%D0%B7%D0%B0%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%20%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%20%D0%BD%D0%B5%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%8E%D1%82%D1%81%D1%8F%29%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%2C%20%D0%BF%D0%BE%D0%B7%D0%B2%D0%BE%D0%BB%D1%8F%D0%B5%D1%82%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%20%D0%B8%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D1%81%D1%82%D0%B0%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%BE%D0%B9%0A%20%20%20%20%23%201.%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%D0%9F%D0%BE%D0%B4%D1%81%D1%87%D0%B8%D1%82%D0%B0%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20%D0%BA%D0%B0%D0%B6%D0%B4%D0%BE%D0%B9%20%D1%86%D0%B8%D1%84%D1%80%D1%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B0%D0%B5%D1%82%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%20num%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D1%84%D0%B8%D0%BA%D1%81%D0%BD%D1%8B%D0%B5%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20counter%20%D0%B8%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%C2%AB%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9%C2%BB%20%D0%B2%20%C2%AB%D0%BA%D0%BE%D0%BD%D0%B5%D1%87%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%C2%BB%0A%20%20%20%20%23%20%D0%A2%D0%BE%20%D0%B5%D1%81%D1%82%D1%8C%20counter%5Bnum%5D-1%20%E2%80%94%20%D1%8D%D1%82%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B5%D0%B3%D0%BE%20%D0%BF%D0%BE%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20num%20%D0%B2%20res%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%20nums%20%D0%B2%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%20%D0%B8%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%B2%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20res%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20res%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%B0%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20num%20%D0%BF%D0%BE%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%D0%A3%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D1%84%D0%B8%D0%BA%D1%81%D0%BD%D1%83%D1%8E%20%D1%81%D1%83%D0%BC%D0%BC%D1%83%20%D0%BD%D0%B0%201%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%B3%D0%BE%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%20num%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%BC%20%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%B0%20res%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/heap_sort.md b/ru/codes/pythontutor/chapter_sorting/heap_sort.md new file mode 100644 index 000000000..254fe2859 --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/heap_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20n%3B%20%D0%BD%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20i%2C%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%D0%B2%D0%B5%D1%80%D1%85%D1%83%20%D0%B2%D0%BD%D0%B8%D0%B7%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20%D1%81%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%D1%81%D1%80%D0%B5%D0%B4%D0%B8%20i%2C%20l%20%D0%B8%20r%20%D0%B8%20%D0%BE%D0%B1%D0%BE%D0%B7%D0%BD%D0%B0%D1%87%D0%B8%D1%82%D1%8C%20%D0%B5%D0%B3%D0%BE%20%D0%BA%D0%B0%D0%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D1%83%D0%B7%D0%B5%D0%BB%20i%20%D1%83%D0%B6%D0%B5%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D0%B5%D0%BD%20%D0%B8%D0%BB%D0%B8%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B%20l%20%D0%B8%20r%20%D0%B2%D0%BD%D0%B5%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%2C%20%D0%B4%D0%B0%D0%BB%D1%8C%D0%BD%D0%B5%D0%B9%D1%88%D0%B5%D0%B5%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B5%20%D1%82%D1%80%D0%B5%D0%B1%D1%83%D0%B5%D1%82%D1%81%D1%8F%2C%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B4%D0%B2%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%B2%D0%BD%D0%B8%D0%B7%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%22%22%22%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%3A%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20heapify%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D0%B5%D1%85%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%2C%20%D0%BA%D1%80%D0%BE%D0%BC%D0%B5%20%D0%BB%D0%B8%D1%81%D1%82%D0%BE%D0%B2%D1%8B%D1%85%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20%2F%2F%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D0%BA%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%20%D0%B2%20%D1%82%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20n-1%20%D0%B8%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%20%D1%81%20%D1%81%D0%B0%D0%BC%D1%8B%D0%BC%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%BC%20%D0%BB%D0%B8%D1%81%D1%82%D0%BE%D0%BC%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20%28%D0%BF%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D0%B8%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%29%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D1%8F%20%D1%81%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%83%D0%B7%D0%BB%D0%B0%2C%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%BE%D1%81%D0%B5%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%D0%B2%D0%B5%D1%80%D1%85%D1%83%20%D0%B2%D0%BD%D0%B8%D0%B7%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%BA%D1%83%D1%87%D0%B5%D0%B9%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/insertion_sort.md b/ru/codes/pythontutor/chapter_sorting/insertion_sort.md new file mode 100644 index 000000000..5cd1adf55 --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/insertion_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20base%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i-1%5D%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D1%8C%20nums%5Bj%5D%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20%D0%B2%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20base%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%D0%BC%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/merge_sort.md b/ru/codes/pythontutor/chapter_sorting/merge_sort.md new file mode 100644 index 000000000..36029084f --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/merge_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%28i%2C%20j%2C%20k%29%20%3D%20%28left%2C%20mid%20%2B%201%2C%200%29%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20for%20k%20in%20range%280%2C%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20%2F%2F%202%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%27%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC%20nums%20%3D%27%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/quick_sort.md b/ru/codes/pythontutor/chapter_sorting/quick_sort.md new file mode 100644 index 000000000..ba8bbb381 --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/quick_sort.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%83%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%83%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%23%20%D0%97%D0%B0%D0%B2%D0%B5%D1%80%D1%88%D0%B8%D1%82%D1%8C%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8E%2C%20%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%201%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%0A%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%23%20%D0%A0%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B2%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D1%82%D1%8C%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B8%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D1%8B%0A%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%92%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D1%83%20%D0%B8%D0%B7%20%D1%82%D1%80%D0%B5%D1%85%20%D0%BA%D0%B0%D0%BD%D0%B4%D0%B8%D0%B4%D0%B0%D1%82%D0%BE%D0%B2%22%22%22%0A%20%20%20%20l%2C%20m%2C%20r%20%3D%20nums%5Bleft%5D%2C%20nums%5Bmid%5D%2C%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20l%20%D0%B8%20r%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20m%20%D0%B8%20r%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%B1%D0%B8%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%D0%B8%20%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8%20%28%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%B0%20%D1%82%D1%80%D0%B5%D1%85%29%22%22%22%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20med%20%3D%20median_three%28nums%2C%20left%2C%20%28left%20%2B%20right%29%20%2F%2F%202%2C%20right%29%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D1%83%20%D0%B2%20%D0%BA%D1%80%D0%B0%D0%B9%D0%BD%D0%B8%D0%B9%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20nums%5Bleft%5D%2C%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D%2C%20nums%5Bleft%5D%0A%20%20%20%20%23%20%D0%92%D0%B7%D1%8F%D1%82%D1%8C%20nums%5Bleft%5D%20%D0%B2%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BB%D0%B5%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%D0%98%D0%B4%D1%82%D0%B8%20%D1%81%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%20%D0%B2%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%D1%85%20%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B0%20%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%83%20%D0%B4%D0%B2%D1%83%D1%85%20%D0%BF%D0%BE%D0%B4%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%9E%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%BD%D1%8B%D0%BC%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%BE%D0%BF%D0%BE%D1%80%D0%BD%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20%28%D0%BC%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%29%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%28i%2C%20j%29%20%3D%20%28left%2C%20right%29%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%28nums%5Bi%5D%2C%20nums%5Bj%5D%29%20%3D%20%28nums%5Bj%5D%2C%20nums%5Bi%5D%29%0A%20%20%20%20%28nums%5Bi%5D%2C%20nums%5Bleft%5D%29%20%3D%20%28nums%5Bleft%5D%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20i%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%27%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%28%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D0%B5%D0%B9%29%20nums%20%3D%27%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/radix_sort.md b/ru/codes/pythontutor/chapter_sorting/radix_sort.md new file mode 100644 index 000000000..909541c8b --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/radix_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int%2C%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20return%20num%20%2F%2F%20exp%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D%2C%20exp%3A%20int%29%3A%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%0A%20%20%20%20for%20i%20in%20range%281%2C%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums%2C%20exp%29%0A%20%20%20%20%20%20%20%20exp%20%2A%3D%2010%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20%5B105%2C%20356%2C%20428%2C%20348%2C%20818%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%27%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D0%B4%D0%BD%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%27%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_sorting/selection_sort.md b/ru/codes/pythontutor/chapter_sorting/selection_sort.md new file mode 100644 index 000000000..ccbb7a1e9 --- /dev/null +++ b/ru/codes/pythontutor/chapter_sorting/selection_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BE%D0%BC%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5Bi%2C%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%BC%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B5%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D1%8D%D1%82%D0%BE%D1%82%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20%D1%81%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%BC%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%BC%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BE%D0%BC%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_stack_and_queue/array_queue.md b/ru/codes/pythontutor/chapter_stack_and_queue/array_queue.md new file mode 100644 index 000000000..bcefe15cc --- /dev/null +++ b/ru/codes/pythontutor/chapter_stack_and_queue/array_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20size%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%B7%D0%B0%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B0%27%29%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%27%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5Bj%20%25%20self.capacity%28%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20peek%20%3D%27%2C%20peek%29%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%27%2C%20pop%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_stack_and_queue/array_stack.md b/ru/codes/pythontutor/chapter_stack_and_queue/array_stack.md new file mode 100644 index 000000000..f61ec3bb7 --- /dev/null +++ b/ru/codes/pythontutor/chapter_stack_and_queue/array_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%D0%A1%D1%82%D0%B5%D0%BA%20%D0%BD%D0%B0%20%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B5%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%2C%20%D0%BF%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self%2C%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%D1%81%D1%82%D0%B5%D0%BA%20%D0%BF%D1%83%D1%81%D1%82%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%BC%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%D1%81%D1%82%D0%B5%D0%BA%20%D0%BF%D1%83%D1%81%D1%82%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%92%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%D0%A1%D1%82%D0%B5%D0%BA%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B5%D0%BC%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22stack%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D1%82%D1%83%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md b/ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md new file mode 100644 index 000000000..d4fb15b29 --- /dev/null +++ b/ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%0A%0Aclass%20LinkedListQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%27%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%27%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20queue%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20front%20%3D%27%2C%20peek%29%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%27%2C%20pop_front%29%0A%20%20%20%20print%28%27queue%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md b/ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md new file mode 100644 index 000000000..4d6c87c9b --- /dev/null +++ b/ru/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%0A%0Aclass%20LinkedListStack%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%D1%81%D1%82%D0%B5%D0%BA%20%D0%BF%D1%83%D1%81%D1%82%27%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%27%D0%A1%D1%82%D0%B5%D0%BA%20stack%20%3D%27%2C%20stack.to_list%28%29%29%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%27%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20peek%20%3D%27%2C%20peek%29%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%27%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%27%2C%20pop%29%0A%20%20%20%20print%28%27stack%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%27%2C%20stack.to_list%28%29%29%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%27%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%27%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_tree/array_binary_tree.md b/ru/codes/pythontutor/chapter_tree/array_binary_tree.md new file mode 100644 index 000000000..a6a62b293 --- /dev/null +++ b/ru/codes/pythontutor/chapter_tree/array_binary_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self%2C%20i%3A%20int%2C%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27pre%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27in%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27post%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27pre%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27in%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27post%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20arr%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%20None%2C%206%2C%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20%28l%2C%20r%2C%20p%29%20%3D%20%28abt.left%28i%29%2C%20abt.right%28i%29%2C%20abt.parent%28i%29%29%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_tree/binary_search_tree.md b/ru/codes/pythontutor/chapter_tree/binary_search_tree.md new file mode 100644 index 000000000..d6584b2f8 --- /dev/null +++ b/ru/codes/pythontutor/chapter_tree/binary_search_tree.md @@ -0,0 +1,14 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%27%5Cn%D0%9D%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%20%D1%83%D0%B7%D0%BB%D0%B0%20%3D%20%7B%7D%2C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%20%3D%20%7B%7D%27.format%28node%2C%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%20%D0%BF%D1%83%D1%81%D1%82%D0%BE%2C%20%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%D0%98%D1%81%D0%BA%D0%B0%D1%82%D1%8C%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%20%D0%B8%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%80%D0%BE%D1%85%D0%BE%D0%B4%D0%B0%20%D0%B7%D0%B0%20%D0%BB%D0%B8%D1%81%D1%82%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B8%D0%B9%D1%81%D1%8F%20%D1%83%D0%B7%D0%B5%D0%BB%20%D0%B8%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%BC%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%81%D1%8F%20%D0%B2%20%D0%BB%D0%B5%D0%B2%D0%BE%D0%BC%20%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%28cur%2C%20pre%29%20%3D%20%28self._root%2C%20None%29%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%20%20%20%20bst.remove%281%29%0A%20%20%20%20bst.remove%282%29%0A%20%20%20%20bst.remove%284%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_tree/binary_tree_bfs.md b/ru/codes/pythontutor/chapter_tree/binary_tree_bfs.md new file mode 100644 index 000000000..580b6e2ad --- /dev/null +++ b/ru/codes/pythontutor/chapter_tree/binary_tree_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%0A%20%20%20%20return%20res%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%D0%B2%20%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83%20%3D%20%27%2C%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/pythontutor/chapter_tree/binary_tree_dfs.md b/ru/codes/pythontutor/chapter_tree/binary_tree_dfs.md new file mode 100644 index 000000000..510e224e2 --- /dev/null +++ b/ru/codes/pythontutor/chapter_tree/binary_tree_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%27Driver%20Code%27%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BF%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%27%5Cn%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%B8%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%20%D0%BF%D1%80%D0%B8%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%BC%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B5%20%3D%20%27%2C%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/codes/ruby/chapter_array_and_linkedlist/array.rb b/ru/codes/ruby/chapter_array_and_linkedlist/array.rb new file mode 100644 index 000000000..125c61b4f --- /dev/null +++ b/ru/codes/ruby/chapter_array_and_linkedlist/array.rb @@ -0,0 +1,108 @@ +=begin +File: array.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Случайный доступ к элементу ### +def random_access(nums) + # Случайным образом выбрать число из интервала [0, nums.length) + random_index = Random.rand(0...nums.length) + + # Получить и вернуть случайный элемент + nums[random_index] +end + + +# ## Увеличить длину массива ### +# Обратите внимание: Array в Ruby является динамическим массивом и может быть расширен напрямую +# Для удобства обучения эта функция рассматривает Array как массив неизменяемой длины +def extend(nums, enlarge) + # Инициализировать массив увеличенной длины + res = Array.new(nums.length + enlarge, 0) + + # Скопировать все элементы исходного массива в новый массив + for i in 0...nums.length + res[i] = nums[i] + end + + # Вернуть новый массив после расширения + res +end + +# ## Вставка элемента num по индексу index в массив ### +def insert(nums, num, index) + # Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for i in (nums.length - 1).downto(index + 1) + nums[i] = nums[i - 1] + end + + # Присвоить num элементу по индексу index + nums[index] = num +end + + +# ## Удаление элемента по индексу index ### +def remove(nums, index) + # Сдвинуть все элементы после индекса index на одну позицию вперед + for i in index...(nums.length - 1) + nums[i] = nums[i + 1] + end +end + +# ## Обход массива ### +def traverse(nums) + count = 0 + + # Обход массива по индексам + for i in 0...nums.length + count += nums[i] + end + + # Непосредственно обходить элементы массива + for num in nums + count += num + end +end + +# ## Поиск заданного элемента в массиве ### +def find(nums, target) + for i in 0...nums.length + return i if nums[i] == target + end + + -1 +end + + +### Driver Code ### +if __FILE__ == $0 + # Инициализация массива + arr = Array.new(5, 0) + puts "Массив arr = #{arr}" + nums = [1, 3, 2, 5, 4] + puts "Массив nums = #{nums}" + + # Случайный доступ + random_num = random_access(nums) + puts "Случайный элемент из nums = #{random_num}" + + # Расширение длины + nums = extend(nums, 3) + puts "После увеличения длины массива до 8 nums = #{nums}" + + # Вставка элемента + insert(nums, 6, 3) + puts "После вставки числа 6 по индексу 3 nums = #{nums}" + + # Удаление элемента + remove(nums, 2) + puts "После удаления элемента по индексу 2 nums = #{nums}" + + # Обход массива + traverse(nums) + + # Поиск элемента + index = find(nums, 3) + puts "Поиск элемента 3 в nums: индекс = #{index}" +end diff --git a/ru/codes/ruby/chapter_array_and_linkedlist/linked_list.rb b/ru/codes/ruby/chapter_array_and_linkedlist/linked_list.rb new file mode 100644 index 000000000..edf998d28 --- /dev/null +++ b/ru/codes/ruby/chapter_array_and_linkedlist/linked_list.rb @@ -0,0 +1,83 @@ +=begin +File: linked_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/print_util' + +# ## Вставка узла _p после узла n0 в связном списке ### +# В Ruby `p` — встроенная функция, а `P` — константа, поэтому вместо этого можно использовать `_p` +def insert(n0, _p) + n1 = n0.next + _p.next = n1 + n0.next = _p +end + +# ## Удаление первого узла после узла n0 в связном списке ### +def remove(n0) + return if n0.next.nil? + + # n0 -> remove_node -> n1 + remove_node = n0.next + n1 = remove_node.next + n0.next = n1 +end + +# ## Доступ к узлу связного списка по индексу index ### +def access(head, index) + for i in 0...index + return nil if head.nil? + head = head.next + end + + head +end + +# ## Поиск первого узла со значением target в связном списке ### +def find(head, target) + index = 0 + while head + return index if head.val == target + head = head.next + index += 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация связного списка + # Инициализация всех узлов + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # Построить ссылки между узлами + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + puts "Исходный связный список" + print_linked_list(n0) + + # Вставка узла + insert(n0, ListNode.new(0)) + print_linked_list n0 + + # Удаление узла + remove(n0) + puts "Связный список после удаления узла" + print_linked_list(n0) + + # Доступ к узлу + node = access(n0, 3) + puts "Значение узла по индексу 3 в связном списке = #{node.val}" + + # Поиск узла + index = find(n0, 2) + puts "Индекс узла со значением 2 в связном списке = #{index}" +end diff --git a/ru/codes/ruby/chapter_array_and_linkedlist/list.rb b/ru/codes/ruby/chapter_array_and_linkedlist/list.rb new file mode 100644 index 000000000..296c9f6f2 --- /dev/null +++ b/ru/codes/ruby/chapter_array_and_linkedlist/list.rb @@ -0,0 +1,60 @@ +=begin +File: list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация списка + nums = [1, 3, 2, 5, 4] + puts "Список nums = #{nums}" + + # Доступ к элементу + num = nums[1] + puts "Элемент по индексу 1: num = #{num}" + + # Обновление элемента + nums[1] = 0 + puts "После обновления элемента по индексу 1 до 0 nums = #{nums}" + + # Очистить список + nums.clear + puts "После очистки списка nums = #{nums}" + + # Добавление элемента в конец + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + puts "После добавления элементов nums = #{nums}" + + # Вставка элемента в середину + nums.insert(3, 6) + puts "После вставки элемента 6 по индексу 3 nums = #{nums}" + + # Удаление элемента + nums.delete_at(3) + puts "После удаления элемента по индексу 3 nums = #{nums}" + + # Обходить список по индексам + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # Непосредственно обходить элементы списка + count = 0 + nums.each do |x| + count += x + end + + # Объединить два списка + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + puts "После конкатенации списка nums1 к nums nums = #{nums}" + + nums = nums.sort { |a, b| a <=> b } + puts "После сортировки списка nums = #{nums}" +end diff --git a/ru/codes/ruby/chapter_array_and_linkedlist/my_list.rb b/ru/codes/ruby/chapter_array_and_linkedlist/my_list.rb new file mode 100644 index 000000000..db44c65ed --- /dev/null +++ b/ru/codes/ruby/chapter_array_and_linkedlist/my_list.rb @@ -0,0 +1,132 @@ +=begin +File: my_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Класс списка ### +class MyList + attr_reader :size # Получить длину списка (текущее число элементов) + attr_reader :capacity # Получить вместимость списка + + # ## Конструктор ### + def initialize + @capacity = 10 + @size = 0 + @extend_ratio = 2 + @arr = Array.new(capacity) + end + + # ## Доступ к элементу ### + def get(index) + # Если индекс выходит за границы, выбрасывается исключение; далее аналогично + raise IndexError, "индекс выходит за границы" if index < 0 || index >= size + @arr[index] + end + + # ## Доступ к элементу ### + def set(index, num) + raise IndexError, "индекс выходит за границы" if index < 0 || index >= size + @arr[index] = num + end + + # ## Добавление элемента в конец ### + def add(num) + # При превышении вместимости по числу элементов запускается расширение + extend_capacity if size == capacity + @arr[size] = num + + # Обновить число элементов + @size += 1 + end + + # ## Вставка элемента в середину ### + def insert(index, num) + raise IndexError, "индекс выходит за границы" if index < 0 || index >= size + + # При превышении вместимости по числу элементов запускается расширение + extend_capacity if size == capacity + + # Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for j in (size - 1).downto(index) + @arr[j + 1] = @arr[j] + end + @arr[index] = num + + # Обновить число элементов + @size += 1 + end + + # ## Удаление элемента ### + def remove(index) + raise IndexError, "индекс выходит за границы" if index < 0 || index >= size + num = @arr[index] + + # Сдвинуть все элементы после индекса index на одну позицию вперед + for j in index...size + @arr[j] = @arr[j + 1] + end + + # Обновить число элементов + @size -= 1 + + # Вернуть удаленный элемент + num + end + + # ## Расширение списка ### + def extend_capacity + # Создать новый массив длиной в extend_ratio раз больше исходного и скопировать в него исходный массив + arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) + # Обновить вместимость списка + @capacity = arr.length + end + + # ## Преобразование списка в массив ### + def to_array + sz = size + # Преобразовывать только элементы списка в пределах фактической длины + arr = Array.new(sz) + for i in 0...sz + arr[i] = get(i) + end + arr + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация списка + nums = MyList.new + + # Добавление элемента в конец + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + puts "Список nums = #{nums.to_array}, емкость = #{nums.capacity}, длина = #{nums.size}" + + # Вставка элемента в середину + nums.insert(3, 6) + puts "После вставки числа 6 по индексу 3 nums = #{nums.to_array}" + + # Удаление элемента + nums.remove(3) + puts "После удаления элемента по индексу 3 nums = #{nums.to_array}" + + # Доступ к элементу + num = nums.get(1) + puts "Элемент по индексу 1: num = #{num}" + + # Обновление элемента + nums.set(1, 0) + puts "После обновления элемента по индексу 1 до 0 nums = #{nums.to_array}" + + # Проверка механизма расширения + for i in 0...10 + # При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i) + end + puts "После расширения список nums = #{nums.to_array}, емкость = #{nums.capacity}, длина = #{nums.size}" +end diff --git a/ru/codes/ruby/chapter_backtracking/n_queens.rb b/ru/codes/ruby/chapter_backtracking/n_queens.rb new file mode 100644 index 000000000..3f9a5a439 --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/n_queens.rb @@ -0,0 +1,61 @@ +=begin +File: n_queens.rb +Created Time: 2024-05-21 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Алгоритм бэктрекинга: n ферзей ### +def backtrack(row, n, state, res, cols, diags1, diags2) + # Когда все строки уже обработаны, записать решение + if row == n + res << state.map { |row| row.dup } + return + end + + # Обойти все столбцы + for col in 0...n + # Вычислить главную и побочную диагонали, соответствующие этой клетке + diag1 = row - col + n - 1 + diag2 = row + col + # Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if !cols[col] && !diags1[diag1] && !diags2[diag2] + # Попытка: поставить ферзя в эту клетку + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = true + # Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # Откат: восстановить эту клетку как пустую + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = false + end + end +end + +# ## Решить задачу о n ферзях ### +def n_queens(n) + # Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + state = Array.new(n) { Array.new(n, "#") } + cols = Array.new(n, false) # Отмечать, есть ли ферзь в столбце + diags1 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на главной диагонали + diags2 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на побочной диагонали + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 4 + res = n_queens(n) + + puts "Размер входной доски = #{n}" + puts "Количество способов расстановки ферзей: #{res.length}" + + for state in res + puts "--------------------" + for row in state + p row + end + end +end diff --git a/ru/codes/ruby/chapter_backtracking/permutations_i.rb b/ru/codes/ruby/chapter_backtracking/permutations_i.rb new file mode 100644 index 000000000..73482aa7b --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/permutations_i.rb @@ -0,0 +1,46 @@ +=begin +File: permutations_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Алгоритм бэктрекинга: все перестановки I ### +def backtrack(state, choices, selected, res) + # Когда длина состояния равна числу элементов, записать решение + if state.length == choices.length + res << state.dup + return + end + + # Перебор всех вариантов выбора + choices.each_with_index do |choice, i| + # Отсечение: нельзя выбирать один и тот же элемент повторно + unless selected[i] + # Попытка: сделать выбор и обновить состояние + selected[i] = true + state << choice + # Перейти к следующему выбору + backtrack(state, choices, selected, res) + # Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false + state.pop + end + end +end + +# ## Все перестановки I ### +def permutations_i(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 3] + + res = permutations_i(nums) + + puts "Входной массив nums = #{nums}" + puts "Все перестановки res = #{res}" +end diff --git a/ru/codes/ruby/chapter_backtracking/permutations_ii.rb b/ru/codes/ruby/chapter_backtracking/permutations_ii.rb new file mode 100644 index 000000000..146e54cbc --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/permutations_ii.rb @@ -0,0 +1,48 @@ +=begin +File: permutations_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Алгоритм бэктрекинга: все перестановки II ### +def backtrack(state, choices, selected, res) + # Когда длина состояния равна числу элементов, записать решение + if state.length == choices.length + res << state.dup + return + end + + # Перебор всех вариантов выбора + duplicated = Set.new + choices.each_with_index do |choice, i| + # Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if !selected[i] && !duplicated.include?(choice) + # Попытка: сделать выбор и обновить состояние + duplicated.add(choice) + selected[i] = true + state << choice + # Перейти к следующему выбору + backtrack(state, choices, selected, res) + # Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false + state.pop + end + end +end + +# ## Все перестановки II ### +def permutations_ii(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 2] + + res = permutations_ii(nums) + + puts "Входной массив nums = #{nums}" + puts "Все перестановки res = #{res}" +end diff --git a/ru/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb b/ru/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb new file mode 100644 index 000000000..d0f0be8dc --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb @@ -0,0 +1,33 @@ +=begin +File: preorder_traversal_i_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Предварительный обход: пример 1 ### +def pre_order(root) + return unless root + + # Записать решение + $res << root if root.val == 7 + + pre_order(root.left) + pre_order(root.right) +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\nИнициализация двоичного дерева" + print_tree(root) + + # Предварительный обход + $res = [] + pre_order(root) + + puts "\nВсе узлы со значением 7" + p $res.map { |node| node.val } +end diff --git a/ru/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb b/ru/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb new file mode 100644 index 000000000..247aa0898 --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb @@ -0,0 +1,41 @@ +=begin +File: preorder_traversal_ii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Предварительный обход: пример 2 ### +def pre_order(root) + return unless root + + # Попытка + $path << root + + # Записать решение + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # Откат + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\nИнициализация двоичного дерева" + print_tree(root) + + # Предварительный обход + $path, $res = [], [] + pre_order(root) + + puts "\nВсе пути от корня к узлу 7" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb b/ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb new file mode 100644 index 000000000..f33300445 --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb @@ -0,0 +1,42 @@ +=begin +File: preorder_traversal_iii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Предварительный обход: пример 3 ### +def pre_order(root) + # Отсечение + return if !root || root.val == 3 + + # Попытка + $path.append(root) + + # Записать решение + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # Откат + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\nИнициализация двоичного дерева" + print_tree(root) + + # Предварительный обход + $path, $res = [], [] + pre_order(root) + + puts "\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb b/ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb new file mode 100644 index 000000000..287f2bf95 --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb @@ -0,0 +1,68 @@ +=begin +File: preorder_traversal_iii_template.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Проверка, является ли текущее состояние решением ### +def is_solution?(state) + !state.empty? && state.last.val == 7 +end + +# ## Записать решение ### +def record_solution(state, res) + res << state.dup +end + +# ## Проверка допустимости этого выбора в текущем состоянии ### +def is_valid?(state, choice) + choice && choice.val != 3 +end + +# ## Обновить состояние ### +def make_choice(state, choice) + state << choice +end + +# ## Восстановить состояние ### +def undo_choice(state, choice) + state.pop +end + +# ## Алгоритм бэктрекинга: пример 3 ### +def backtrack(state, choices, res) + # Проверить, является ли текущее состояние решением + record_solution(state, res) if is_solution?(state) + + # Перебор всех вариантов выбора + for choice in choices + # Отсечение: проверить допустимость выбора + if is_valid?(state, choice) + # Попытка: сделать выбор и обновить состояние + make_choice(state, choice) + # Перейти к следующему выбору + backtrack(state, [choice.left, choice.right], res) + # Откат: отменить выбор и восстановить предыдущее состояние + undo_choice(state, choice) + end + end +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\nИнициализация двоичного дерева" + print_tree(root) + + # Алгоритм бэктрекинга + res = [] + backtrack([], [root], res) + + puts "\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3" + for path in res + p path.map { |node| node.val } + end +end diff --git a/ru/codes/ruby/chapter_backtracking/subset_sum_i.rb b/ru/codes/ruby/chapter_backtracking/subset_sum_i.rb new file mode 100644 index 000000000..8dafaf848 --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/subset_sum_i.rb @@ -0,0 +1,47 @@ +=begin +File: subset_sum_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Алгоритм бэктрекинга: сумма подмножеств I ### +def backtrack(state, target, choices, start, res) + # Если сумма подмножества равна target, записать решение + if target.zero? + res << state.dup + return + end + # Обойти все варианты выбора + # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for i in start...choices.length + # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + break if target - choices[i] < 0 + # Попытка: сделать выбор и обновить target и start + state << choices[i] + # Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res) + # Откат: отменить выбор и восстановить предыдущее состояние + state.pop + end +end + +# ## Решить задачу суммы подмножеств I ### +def subset_sum_i(nums, target) + state = [] # Состояние (подмножество) + nums.sort! # Отсортировать nums + start = 0 # Стартовая вершина обхода + res = [] # Список результатов (список подмножеств) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + puts "Входной массив nums = #{nums}, target = #{target}" + puts "Все подмножества с суммой #{target}: res = #{res}" +end diff --git a/ru/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb b/ru/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb new file mode 100644 index 000000000..0f5349eac --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb @@ -0,0 +1,46 @@ +=begin +File: subset_sum_i_naive.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Алгоритм бэктрекинга: сумма подмножеств I ### +def backtrack(state, target, total, choices, res) + # Если сумма подмножества равна target, записать решение + if total == target + res << state.dup + return + end + + # Перебор всех вариантов выбора + for i in 0...choices.length + # Отсечение: если сумма подмножества превышает target, пропустить этот выбор + next if total + choices[i] > target + # Попытка: сделать выбор и обновить элемент и total + state << choices[i] + # Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res) + # Откат: отменить выбор и восстановить предыдущее состояние + state.pop + end +end + +# ## Решить задачу суммы подмножеств I (с повторяющимися подмножествами) ### +def subset_sum_i_naive(nums, target) + state = [] # Состояние (подмножество) + total = 0 # Сумма подмножеств + res = [] # Список результатов (список подмножеств) + backtrack(state, target, total, nums, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + puts "Входной массив nums = #{nums}, target = #{target}" + puts "Все подмножества с суммой #{target}: res = #{res}" + puts "Обратите внимание: результат этого метода содержит повторяющиеся множества" +end diff --git a/ru/codes/ruby/chapter_backtracking/subset_sum_ii.rb b/ru/codes/ruby/chapter_backtracking/subset_sum_ii.rb new file mode 100644 index 000000000..fbc78175b --- /dev/null +++ b/ru/codes/ruby/chapter_backtracking/subset_sum_ii.rb @@ -0,0 +1,51 @@ +=begin +File: subset_sum_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Алгоритм бэктрекинга: сумма подмножеств II ### +def backtrack(state, target, choices, start, res) + # Если сумма подмножества равна target, записать решение + if target.zero? + res << state.dup + return + end + + # Обойти все варианты выбора + # Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + # Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for i in start...choices.length + # Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + # Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + break if target - choices[i] < 0 + # Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + next if i > start && choices[i] == choices[i - 1] + # Попытка: сделать выбор и обновить target и start + state << choices[i] + # Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res) + # Откат: отменить выбор и восстановить предыдущее состояние + state.pop + end +end + +# ## Решить задачу суммы подмножеств II ### +def subset_sum_ii(nums, target) + state = [] # Состояние (подмножество) + nums.sort! # Отсортировать nums + start = 0 # Стартовая вершина обхода + res = [] # Список результатов (список подмножеств) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + puts "Входной массив nums = #{nums}, target = #{target}" + puts "Все подмножества с суммой #{target}: res = #{res}" +end diff --git a/ru/codes/ruby/chapter_computational_complexity/iteration.rb b/ru/codes/ruby/chapter_computational_complexity/iteration.rb new file mode 100644 index 000000000..43f4c1219 --- /dev/null +++ b/ru/codes/ruby/chapter_computational_complexity/iteration.rb @@ -0,0 +1,79 @@ +=begin +File: iteration.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) +=end + +# ## Цикл for ### +def for_loop(n) + res = 0 + + # Циклическое суммирование 1, 2, ..., n-1, n + for i in 1..n + res += i + end + + res +end + +# ## Цикл while ### +def while_loop(n) + res = 0 + i = 1 # Инициализация условной переменной + + # Циклическое суммирование 1, 2, ..., n-1, n + while i <= n + res += i + i += 1 # Обновить условную переменную + end + + res +end + +# ## Цикл while (двойное обновление) ### +def while_loop_ii(n) + res = 0 + i = 1 # Инициализация условной переменной + + # Циклическое суммирование 1, 4, 10, ... + while i <= n + res += i + # Обновить условную переменную + i += 1 + i *= 2 + end + + res +end + +# ## Двойной цикл for ### +def nested_for_loop(n) + res = "" + + # Цикл по i = 1, 2, ..., n-1, n + for i in 1..n + # Цикл по j = 1, 2, ..., n-1, n + for j in 1..n + res += "(#{i}, #{j}), " + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = for_loop(n) + puts "\nРезультат суммирования в цикле for res = #{res}" + + res = while_loop(n) + puts "\nРезультат суммирования в цикле while res = #{res}" + + res = while_loop_ii(n) + puts "\nРезультат суммирования в цикле while (двойное обновление) res = #{res}" + + res = nested_for_loop(n) + puts "\nРезультат обхода в двойном цикле for #{res}" +end diff --git a/ru/codes/ruby/chapter_computational_complexity/recursion.rb b/ru/codes/ruby/chapter_computational_complexity/recursion.rb new file mode 100644 index 000000000..af37d2c93 --- /dev/null +++ b/ru/codes/ruby/chapter_computational_complexity/recursion.rb @@ -0,0 +1,70 @@ +=begin +File: recursion.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Рекурсия ### +def recur(n) + # Условие завершения + return 1 if n == 1 + # Рекурсия: рекурсивный вызов + res = recur(n - 1) + # Возврат: вернуть результат + n + res +end + +# ## Имитация рекурсии итерацией ### +def for_loop_recur(n) + # Использовать явный стек для имитации системного стека вызовов + stack = [] + res = 0 + + # Рекурсия: рекурсивный вызов + for i in n.downto(0) + # Имитировать «рекурсию» с помощью операции помещения в стек + stack << i + end + # Возврат: вернуть результат + while !stack.empty? + res += stack.pop + end + + # res = 1+2+3+...+n + res +end + +# ## Хвостовая рекурсия ### +def tail_recur(n, res) + # Условие завершения + return res if n == 0 + # Хвостовой рекурсивный вызов + tail_recur(n - 1, res + n) +end + +# ## Последовательность Фибоначчи: рекурсия ### +def fib(n) + # Условие завершения: f(1) = 0, f(2) = 1 + return n - 1 if n == 1 || n == 2 + # Рекурсивный вызов f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # Вернуть результат f(n) + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = recur(n) + puts "\nРезультат суммирования в рекурсивной функции res = #{res}" + + res = for_loop_recur(n) + puts "\nРезультат суммирования при имитации рекурсии res = #{res}" + + res = tail_recur(n, 0) + puts "\nРезультат суммирования в хвостовой рекурсии res = #{res}" + + res = fib(n) + puts "\n#{n}-й элемент последовательности Фибоначчи: #{res}" +end diff --git a/ru/codes/ruby/chapter_computational_complexity/space_complexity.rb b/ru/codes/ruby/chapter_computational_complexity/space_complexity.rb new file mode 100644 index 000000000..683753742 --- /dev/null +++ b/ru/codes/ruby/chapter_computational_complexity/space_complexity.rb @@ -0,0 +1,92 @@ +=begin +File: space_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Функция ### +def function + # Выполнить некоторые операции + 0 +end + +# ## Постоянная сложность ### +def constant(n) + # Константы, переменные и объекты занимают O(1) памяти + a = 0 + nums = [0] * 10000 + node = ListNode.new + + # Переменные в цикле занимают O(1) памяти + (0...n).each { c = 0 } + # Функции в цикле занимают O(1) памяти + (0...n).each { function } +end + +# ## Линейная сложность ### +def linear(n) + # Список длины n занимает O(n) памяти + nums = Array.new(n, 0) + + # Хеш-таблица длины n занимает O(n) памяти + hmap = {} + for i in 0...n + hmap[i] = i.to_s + end +end + +# ## Линейная сложность (рекурсивная реализация) ### +def linear_recur(n) + puts "Рекурсия n = #{n}" + return if n == 1 + linear_recur(n - 1) +end + +# ## Квадратичная сложность ### +def quadratic(n) + # Двумерный список занимает O(n^2) памяти + Array.new(n) { Array.new(n, 0) } +end + +# ## Квадратичная сложность (рекурсивная реализация) ### +def quadratic_recur(n) + return 0 unless n > 0 + + # Длина массива nums равна n, n-1, ..., 2, 1 + nums = Array.new(n, 0) + quadratic_recur(n - 1) +end + +# ## Экспоненциальная сложность (построение полного двоичного дерева) ### +def build_tree(n) + return if n == 0 + + TreeNode.new.tap do |root| + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + end +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + # Постоянная сложность + constant(n) + + # Линейная сложность + linear(n) + linear_recur(n) + + # Квадратичная сложность + quadratic(n) + quadratic_recur(n) + + # Экспоненциальная сложность + root = build_tree(n) + print_tree(root) +end diff --git a/ru/codes/ruby/chapter_computational_complexity/time_complexity.rb b/ru/codes/ruby/chapter_computational_complexity/time_complexity.rb new file mode 100644 index 000000000..db4f19ca0 --- /dev/null +++ b/ru/codes/ruby/chapter_computational_complexity/time_complexity.rb @@ -0,0 +1,165 @@ +=begin +File: time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Постоянная сложность ### +def constant(n) + count = 0 + size = 100000 + + (0...size).each { count += 1 } + + count +end + +# ## Линейная сложность ### +def linear(n) + count = 0 + (0...n).each { count += 1 } + count +end + +# ## Линейная сложность (обход массива) ### +def array_traversal(nums) + count = 0 + + # Число итераций пропорционально длине массива + for num in nums + count += 1 + end + + count +end + +# ## Квадратичная сложность ### +def quadratic(n) + count = 0 + + # Число итераций квадратично зависит от размера данных n + for i in 0...n + for j in 0...n + count += 1 + end + end + + count +end + +# ## Квадратичная сложность (пузырьковая сортировка) ### +def bubble_sort(nums) + count = 0 # Счетчик + + # Внешний цикл: неотсортированный диапазон [0, i] + for i in (nums.length - 1).downto(0) + # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0...i + if nums[j] > nums[j + 1] + # Поменять местами nums[j] и nums[j + 1] + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # Обмен элементов включает 3 элементарные операции + end + end + end + + count +end + +# ## Экспоненциальная сложность (итеративная реализация) ### +def exponential(n) + count, base = 0, 1 + + # На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + (0...n).each do + (0...base).each { count += 1 } + base *= 2 + end + + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +end + +# ## Экспоненциальная сложность (рекурсивная реализация) ### +def exp_recur(n) + return 1 if n == 1 + exp_recur(n - 1) + exp_recur(n - 1) + 1 +end + +# ## Логарифмическая сложность (итеративная реализация) ### +def logarithmic(n) + count = 0 + + while n > 1 + n /= 2 + count += 1 + end + + count +end + +# ## Логарифмическая сложность (рекурсивная реализация) ### +def log_recur(n) + return 0 unless n > 1 + log_recur(n / 2) + 1 +end + +# ## Линейно-логарифмическая сложность ### +def linear_log_recur(n) + return 1 unless n > 1 + + count = linear_log_recur(n / 2) + linear_log_recur(n / 2) + (0...n).each { count += 1 } + + count +end + +# ## Факториальная сложность (рекурсивная реализация) ### +def factorial_recur(n) + return 1 if n == 0 + + count = 0 + # Из одного получается n + (0...n).each { count += factorial_recur(n - 1) } + + count +end + +### Driver Code ### +if __FILE__ == $0 + # Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + n = 8 + puts "Размер входных данных n = #{n}" + + count = constant(n) + puts "Число операций константной сложности = #{count}" + + count = linear(n) + puts "Число операций линейной сложности = #{count}" + count = array_traversal(Array.new(n, 0)) + puts "Число операций линейной сложности (обход массива) = #{count}" + + count = quadratic(n) + puts "Число операций квадратичной сложности = #{count}" + nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] + count = bubble_sort(nums) + puts "Число операций квадратичной сложности (пузырьковая сортировка) = #{count}" + + count = exponential(n) + puts "Число операций экспоненциальной сложности (итеративная реализация) = #{count}" + count = exp_recur(n) + puts "Число операций экспоненциальной сложности (рекурсивная реализация) = #{count}" + + count = logarithmic(n) + puts "Число операций логарифмической сложности (итеративная реализация) = #{count}" + count = log_recur(n) + puts "Число операций логарифмической сложности (рекурсивная реализация) = #{count}" + + count = linear_log_recur(n) + puts "Число операций линейно-логарифмической сложности (рекурсивная реализация) = #{count}" + + count = factorial_recur(n) + puts "Число операций факториальной сложности (рекурсивная реализация) = #{count}" +end diff --git a/ru/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb b/ru/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb new file mode 100644 index 000000000..167ed4d60 --- /dev/null +++ b/ru/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb @@ -0,0 +1,35 @@ +=begin +File: worst_best_time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Создать массив с элементами: 1, 2, ..., n в случайном порядке ### +def random_numbers(n) + # Создать массив nums =: 1, 2, 3, ..., n + nums = Array.new(n) { |i| i + 1 } + # Случайно перемешать элементы массива + nums.shuffle! +end + +# ## Найти индекс числа 1 в массиве nums ### +def find_one(nums) + for i in 0...nums.length + # Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + # Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + return i if nums[i] == 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + for i in 0...10 + n = 100 + nums = random_numbers(n) + index = find_one(nums) + puts "\nМассив [1, 2, ..., n] после перемешивания = #{nums}" + puts "Индекс числа 1 = #{index}" + end +end diff --git a/ru/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb b/ru/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb new file mode 100644 index 000000000..6aee6c130 --- /dev/null +++ b/ru/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb @@ -0,0 +1,42 @@ +=begin +File: binary_search_recur.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Бинарный поиск: задача f(i, j) ### +def dfs(nums, target, i, j) + # Если интервал пуст, целевой элемент отсутствует, вернуть -1 + return -1 if i > j + + # Вычислить индекс середины m + m = (i + j) / 2 + + if nums[m] < target + # Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j) + elsif nums[m] > target + # Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1) + else + # Целевой элемент найден, вернуть его индекс + return m + end +end + +# ## Бинарный поиск ### +def binary_search(nums, target) + n = nums.length + # Решить задачу f(0, n-1) + dfs(nums, target, 0, n - 1) +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # Бинарный поиск (двусторонне замкнутый интервал) + index = binary_search(nums, target) + puts "Индекс целевого элемента 6 = #{index}" +end diff --git a/ru/codes/ruby/chapter_divide_and_conquer/build_tree.rb b/ru/codes/ruby/chapter_divide_and_conquer/build_tree.rb new file mode 100644 index 000000000..4f2fff16a --- /dev/null +++ b/ru/codes/ruby/chapter_divide_and_conquer/build_tree.rb @@ -0,0 +1,46 @@ +=begin +File: build_tree.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Построить двоичное дерево: разделяй и властвуй ### +def dfs(preorder, inorder_map, i, l, r) + # Завершить при пустом диапазоне поддерева + return if r - l < 0 + + # Инициализировать корневой узел + root = TreeNode.new(preorder[i]) + # Найти m, чтобы разделить левое и правое поддеревья + m = inorder_map[preorder[i]] + # Подзадача: построить левое поддерево + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # Подзадача: построить правое поддерево + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + + # Вернуть корневой узел + root +end + +# ## Построить двоичное дерево ### +def build_tree(preorder, inorder) + # Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + inorder_map = {} + inorder.each_with_index { |val, i| inorder_map[val] = i } + dfs(preorder, inorder_map, 0, 0, inorder.length - 1) +end + +### Driver Code ### +if __FILE__ == $0 + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + puts "Предварительный обход = #{preorder}" + puts "Симметричный обход = #{inorder}" + + root = build_tree(preorder, inorder) + puts "Построенное двоичное дерево:" + print_tree(root) +end diff --git a/ru/codes/ruby/chapter_divide_and_conquer/hanota.rb b/ru/codes/ruby/chapter_divide_and_conquer/hanota.rb new file mode 100644 index 000000000..bcdfb5046 --- /dev/null +++ b/ru/codes/ruby/chapter_divide_and_conquer/hanota.rb @@ -0,0 +1,55 @@ +=begin +File: hanota.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Переместить один диск ### +def move(src, tar) + # Снять диск с вершины src + pan = src.pop + # Положить диск на вершину tar + tar << pan +end + +# ## Решить задачу Ханойской башни f(i) ### +def dfs(i, src, buf, tar) + # Если в src остался только один диск, сразу переместить его в tar + if i == 1 + move(src, tar) + return + end + + # Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf) + # Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar) + # Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar) +end + +# ## Решить задачу Ханойской башни ### +def solve_hanota(_A, _B, _C) + n = _A.length + # Переместить верхние n дисков из A в C с помощью B + dfs(n, _A, _B, _C) +end + +### Driver Code ### +if __FILE__ == $0 + # Хвост списка соответствует вершине столбца + A = [5, 4, 3, 2, 1] + B = [] + C = [] + puts "Исходное состояние:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" + + solve_hanota(A, B, C) + + puts "После завершения перемещения дисков:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb new file mode 100644 index 000000000..f20e8a60e --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb @@ -0,0 +1,37 @@ +=begin +File: climbing_stairs_backtrack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Бэктрекинг ### +def backtrack(choices, state, n, res) + # Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + res[0] += 1 if state == n + # Перебор всех вариантов выбора + for choice in choices + # Отсечение: нельзя выходить за n-ю ступень + next if state + choice > n + + # Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res) + end + # Откат +end + +# ## Подъем по лестнице: бэктрекинг ### +def climbing_stairs_backtrack(n) + choices = [1, 2] # Можно подняться на 1 или 2 ступени + state = 0 # Начать подъем с 0-й ступени + res = [0] # Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res) + res.first +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_backtrack(n) + puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb new file mode 100644 index 000000000..4d67f0c3d --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb @@ -0,0 +1,31 @@ +=begin +File: climbing_stairs_constraint_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Подъем по лестнице с ограничениями: динамическое программирование ### +def climbing_stairs_constraint_dp(n) + return 1 if n == 1 || n == 2 + + # Инициализация таблицы dp для хранения решений подзадач + dp = Array.new(n + 1) { Array.new(3, 0) } + # Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3...(n + 1) + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + end + + dp[n][1] + dp[n][2] +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_constraint_dp(n) + puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb new file mode 100644 index 000000000..e8863f427 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb @@ -0,0 +1,26 @@ +=begin +File: climbing_stairs_dfs.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Поиск ### +def dfs(i) + # dp[1] и dp[2] уже известны, вернуть их + return i if i == 1 || i == 2 + # dp[i] = dp[i-1] + dp[i-2] + dfs(i - 1) + dfs(i - 2) +end + +# ## Подъем по лестнице: поиск ### +def climbing_stairs_dfs(n) + dfs(n) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs(n) + puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb new file mode 100644 index 000000000..8a9d0c390 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb @@ -0,0 +1,33 @@ +=begin +File: climbing_stairs_dfs_mem.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Поиск с мемоизацией ### +def dfs(i, mem) + # dp[1] и dp[2] уже известны, вернуть их + return i if i == 1 || i == 2 + # Если запись dp[i] существует, сразу вернуть ее + return mem[i] if mem[i] != -1 + + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # Сохранить dp[i] + mem[i] = count +end + +# ## Подъем по лестнице: поиск с мемоизацией ### +def climbing_stairs_dfs_mem(n) + # mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + mem = Array.new(n + 1, -1) + dfs(n, mem) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs_mem(n) + puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb new file mode 100644 index 000000000..c9989405b --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb @@ -0,0 +1,40 @@ +=begin +File: climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Подъем по лестнице: динамическое программирование ### +def climbing_stairs_dp(n) + return n if n == 1 || n == 2 + + # Инициализация таблицы dp для хранения решений подзадач + dp = Array.new(n + 1, 0) + # Начальное состояние: заранее задать решения наименьших подзадач + dp[1], dp[2] = 1, 2 + # Переход состояний: постепенное решение больших подзадач через меньшие + (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } + + dp[n] +end + +# ## Подъем по лестнице: динамическое программирование с оптимизацией памяти ### +def climbing_stairs_dp_comp(n) + return n if n == 1 || n == 2 + + a, b = 1, 2 + (3...(n + 1)).each { a, b = b, a + b } + + b +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dp(n) + puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" + + res = climbing_stairs_dp_comp(n) + puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/coin_change.rb b/ru/codes/ruby/chapter_dynamic_programming/coin_change.rb new file mode 100644 index 000000000..4753004c8 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/coin_change.rb @@ -0,0 +1,65 @@ +=begin +File: coin_change.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Размен монет: динамическое программирование ### +def coin_change_dp(coins, amt) + n = coins.length + _MAX = amt + 1 + # Инициализация таблицы dp + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # Переход состояний: первая строка и первый столбец + (1...(amt + 1)).each { |a| dp[0][a] = _MAX } + # Переход состояний: остальные строки и столбцы + for i in 1...(n + 1) + for a in 1...(amt + 1) + if coins[i - 1] > a + # Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + else + # Меньшее из двух решений: не брать или взять монету i + dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min + end + end + end + dp[n][amt] != _MAX ? dp[n][amt] : -1 +end + +# ## Размен монет: динамическое программирование с оптимизацией памяти ### +def coin_change_dp_comp(coins, amt) + n = coins.length + _MAX = amt + 1 + # Инициализация таблицы dp + dp = Array.new(amt + 1, _MAX) + dp[0] = 0 + # Переход состояний + for i in 1...(n + 1) + # Прямой обход + for a in 1...(amt + 1) + if coins[i - 1] > a + # Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + else + # Меньшее из двух решений: не брать или взять монету i + dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min + end + end + end + dp[amt] != _MAX ? dp[amt] : -1 +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 4 + + # Динамическое программирование + res = coin_change_dp(coins, amt) + puts "Минимальное число монет для набора целевой суммы = #{res}" + + # Динамическое программирование с оптимизацией памяти + res = coin_change_dp_comp(coins, amt) + puts "Минимальное число монет для набора целевой суммы = #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb b/ru/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb new file mode 100644 index 000000000..1a41cbaf8 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb @@ -0,0 +1,63 @@ +=begin +File: coin_change_ii.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Размен монет II: динамическое программирование ### +def coin_change_ii_dp(coins, amt) + n = coins.length + # Инициализация таблицы dp + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # Инициализация первого столбца + (0...(n + 1)).each { |i| dp[i][0] = 1 } + # Переход состояний + for i in 1...(n + 1) + for a in 1...(amt + 1) + if coins[i - 1] > a + # Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + else + # Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + end + end + end + dp[n][amt] +end + +# ## Размен монет II: динамическое программирование с оптимизацией памяти ### +def coin_change_ii_dp_comp(coins, amt) + n = coins.length + # Инициализация таблицы dp + dp = Array.new(amt + 1, 0) + dp[0] = 1 + # Переход состояний + for i in 1...(n + 1) + # Прямой обход + for a in 1...(amt + 1) + if coins[i - 1] > a + # Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + else + # Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]] + end + end + end + dp[amt] +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 5 + + # Динамическое программирование + res = coin_change_ii_dp(coins, amt) + puts "Количество комбинаций монет для набора целевой суммы = #{res}" + + # Динамическое программирование с оптимизацией памяти + res = coin_change_ii_dp_comp(coins, amt) + puts "Количество комбинаций монет для набора целевой суммы = #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/edit_distance.rb b/ru/codes/ruby/chapter_dynamic_programming/edit_distance.rb new file mode 100644 index 000000000..de8d42dec --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/edit_distance.rb @@ -0,0 +1,115 @@ +=begin +File: edit_distance.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Редакционное расстояние: полный перебор ### +def edit_distance_dfs(s, t, i, j) + # Если s и t пусты, вернуть 0 + return 0 if i == 0 && j == 0 + # Если s пусто, вернуть длину t + return j if i == 0 + # Если t пусто, вернуть длину s + return i if j == 0 + # Если два символа равны, сразу пропустить их + return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # Вернуть минимальное число шагов редактирования + [insert, delete, replace].min + 1 +end + +def edit_distance_dfs_mem(s, t, mem, i, j) + # Если s и t пусты, вернуть 0 + return 0 if i == 0 && j == 0 + # Если s пусто, вернуть длину t + return j if i == 0 + # Если t пусто, вернуть длину s + return i if j == 0 + # Если запись уже есть, сразу вернуть ее + return mem[i][j] if mem[i][j] != -1 + # Если два символа равны, сразу пропустить их + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = [insert, delete, replace].min + 1 +end + +# ## Редакционное расстояние: динамическое программирование ### +def edit_distance_dp(s, t) + n, m = s.length, t.length + dp = Array.new(n + 1) { Array.new(m + 1, 0) } + # Переход состояний: первая строка и первый столбец + (1...(n + 1)).each { |i| dp[i][0] = i } + (1...(m + 1)).each { |j| dp[0][j] = j } + # Переход состояний: остальные строки и столбцы + for i in 1...(n + 1) + for j in 1...(m +1) + if s[i - 1] == t[j - 1] + # Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1] + else + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 + end + end + end + dp[n][m] +end + +# ## Редакционное расстояние: динамическое программирование с оптимизацией памяти ### +def edit_distance_dp_comp(s, t) + n, m = s.length, t.length + dp = Array.new(m + 1, 0) + # Переход состояний: первая строка + (1...(m + 1)).each { |j| dp[j] = j } + # Переход состояний: остальные строки + for i in 1...(n + 1) + # Переход состояний: первый столбец + leftup = dp.first # Временно сохранить dp[i-1, j-1] + dp[0] += 1 + # Переход состояний: остальные столбцы + for j in 1...(m + 1) + temp = dp[j] + if s[i - 1] == t[j - 1] + # Если два символа равны, сразу пропустить их + dp[j] = leftup + else + # Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = [dp[j - 1], dp[j], leftup].min + 1 + end + leftup = temp # Обновить до значения dp[i-1, j-1] для следующей итерации + end + end + dp[m] +end + +### Driver Code ### +if __FILE__ == $0 + s = 'bag' + t = 'pack' + n, m = s.length, t.length + + # Полный перебор + res = edit_distance_dfs(s, t, n, m) + puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" + + # Поиск с мемоизацией + mem = Array.new(n + 1) { Array.new(m + 1, -1) } + res = edit_distance_dfs_mem(s, t, mem, n, m) + puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" + + # Динамическое программирование + res = edit_distance_dp(s, t) + puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" + + # Динамическое программирование с оптимизацией памяти + res = edit_distance_dp_comp(s, t) + puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/knapsack.rb b/ru/codes/ruby/chapter_dynamic_programming/knapsack.rb new file mode 100644 index 000000000..085f2fe0f --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/knapsack.rb @@ -0,0 +1,99 @@ +=begin +File: knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Рюкзак 0-1: полный перебор ### +def knapsack_dfs(wgt, val, i, c) + # Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + return 0 if i == 0 || c == 0 + # Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c + # Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # Вернуть вариант с большей стоимостью из двух возможных + [no, yes].max +end + +# ## Рюкзак 0-1: поиск с мемоизацией ### +def knapsack_dfs_mem(wgt, val, mem, i, c) + # Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + return 0 if i == 0 || c == 0 + # Если запись уже есть, вернуть сразу + return mem[i][c] if mem[i][c] != -1 + # Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c + # Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = [no, yes].max +end + +# ## Рюкзак 0-1: динамическое программирование ### +def knapsack_dp(wgt, val, cap) + n = wgt.length + # Инициализация таблицы dp + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # Переход состояний + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + else + # Большее из двух решений: не брать или взять предмет i + dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +# ## Рюкзак 0-1: динамическое программирование с оптимизацией памяти ### +def knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # Инициализация таблицы dp + dp = Array.new(cap + 1, 0) + # Переход состояний + for i in 1...(n + 1) + # Обход в обратном порядке + for c in cap.downto(1) + if wgt[i - 1] > c + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + else + # Большее из двух решений: не брать или взять предмет i + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # Полный перебор + res = knapsack_dfs(wgt, val, n, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" + + # Поиск с мемоизацией + mem = Array.new(n + 1) { Array.new(cap + 1, -1) } + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" + + # Динамическое программирование + res = knapsack_dp(wgt, val, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" + + # Динамическое программирование с оптимизацией памяти + res = knapsack_dp_comp(wgt, val, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb b/ru/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb new file mode 100644 index 000000000..65d5cb078 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb @@ -0,0 +1,39 @@ +=begin +File: min_cost_climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Минимальная стоимость подъема по лестнице: динамическое программирование ### +def min_cost_climbing_stairs_dp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + # Инициализация таблицы dp для хранения решений подзадач + dp = Array.new(n + 1, 0) + # Начальное состояние: заранее задать решения наименьших подзадач + dp[1], dp[2] = cost[1], cost[2] + # Переход состояний: постепенное решение больших подзадач через меньшие + (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } + dp[n] +end + +# Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти +def min_cost_climbing_stairs_dp_comp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + a, b = cost[1], cost[2] + (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } + b +end + +### Driver Code ### +if __FILE__ == $0 + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + puts "Список стоимостей ступеней = #{cost}" + + res = min_cost_climbing_stairs_dp(cost) + puts "Минимальная стоимость подъема по лестнице = #{res}" + + res = min_cost_climbing_stairs_dp_comp(cost) + puts "Минимальная стоимость подъема по лестнице = #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/min_path_sum.rb b/ru/codes/ruby/chapter_dynamic_programming/min_path_sum.rb new file mode 100644 index 000000000..69259a145 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/min_path_sum.rb @@ -0,0 +1,93 @@ +=begin +File: min_path_sum.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Минимальная сумма пути: полный перебор ### +def min_path_sum_dfs(grid, i, j) + # Если это верхняя левая ячейка, завершить поиск + return grid[i][j] if i == 0 && j == 0 + # Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + return Float::INFINITY if i < 0 || j < 0 + # Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + [left, up].min + grid[i][j] +end + +# ## Минимальная сумма пути: поиск с мемоизацией ### +def min_path_sum_dfs_mem(grid, mem, i, j) + # Если это верхняя левая ячейка, завершить поиск + return grid[0][0] if i == 0 && j == 0 + # Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + return Float::INFINITY if i < 0 || j < 0 + # Если запись уже есть, вернуть сразу + return mem[i][j] if mem[i][j] != -1 + # Минимальная стоимость пути для левой и верхней ячеек + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = [left, up].min + grid[i][j] +end + +# ## Минимальная сумма пути: динамическое программирование ### +def min_path_sum_dp(grid) + n, m = grid.length, grid.first.length + # Инициализация таблицы dp + dp = Array.new(n) { Array.new(m, 0) } + dp[0][0] = grid[0][0] + # Переход состояний: первая строка + (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } + # Переход состояний: первый столбец + (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } + # Переход состояний: остальные строки и столбцы + for i in 1...n + for j in 1...m + dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] + end + end + dp[n -1][m -1] +end + +# ## Минимальная сумма пути: динамическое программирование с оптимизацией памяти ### +def min_path_sum_dp_comp(grid) + n, m = grid.length, grid.first.length + # Инициализация таблицы dp + dp = Array.new(m, 0) + # Переход состояний: первая строка + dp[0] = grid[0][0] + (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } + # Переход состояний: остальные строки + for i in 1...n + # Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0] + # Переход состояний: остальные столбцы + (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } + end + dp[m - 1] +end + +### Driver Code ### +if __FILE__ == $0 + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = grid.length, grid.first.length + + # Полный перебор + res = min_path_sum_dfs(grid, n - 1, m - 1) + puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" + + # Поиск с мемоизацией + mem = Array.new(n) { Array.new(m, - 1) } + res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) + puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" + + # Динамическое программирование + res = min_path_sum_dp(grid) + puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" + + # Динамическое программирование с оптимизацией памяти + res = min_path_sum_dp_comp(grid) + puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}" +end diff --git a/ru/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb b/ru/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb new file mode 100644 index 000000000..d62da8651 --- /dev/null +++ b/ru/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb @@ -0,0 +1,61 @@ +=begin +File: unbounded_knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Полный рюкзак: динамическое программирование ### +def unbounded_knapsack_dp(wgt, val, cap) + n = wgt.length + # Инициализация таблицы dp + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # Переход состояний + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + else + # Большее из двух решений: не брать или взять предмет i + dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +# ## Полный рюкзак: динамическое программирование с оптимизацией памяти ##3 +def unbounded_knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # Инициализация таблицы dp + dp = Array.new(cap + 1, 0) + # Переход состояний + for i in 1...(n + 1) + # Прямой обход + for c in 1...(cap + 1) + if wgt[i -1] > c + # Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + else + # Большее из двух решений: не брать или взять предмет i + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # Динамическое программирование + res = unbounded_knapsack_dp(wgt, val, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" + + # Динамическое программирование с оптимизацией памяти + res = unbounded_knapsack_dp_comp(wgt, val, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" +end diff --git a/ru/codes/ruby/chapter_graph/graph_adjacency_list.rb b/ru/codes/ruby/chapter_graph/graph_adjacency_list.rb new file mode 100644 index 000000000..a35a8bed3 --- /dev/null +++ b/ru/codes/ruby/chapter_graph/graph_adjacency_list.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_list.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/vertex' + +# ## Класс неориентированного графа на основе списка смежности ### +class GraphAdjList + attr_reader :adj_list + + # ## Конструктор ### + def initialize(edges) + # Список смежности, где key — вершина, а value — все смежные ей вершины + @adj_list = {} + # Добавить все вершины и ребра + for edge in edges + add_vertex(edge[0]) + add_vertex(edge[1]) + add_edge(edge[0], edge[1]) + end + end + + # ## Получение числа вершин ### + def size + @adj_list.length + end + + # ## Добавление ребра ### + def add_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + @adj_list[vet1] << vet2 + @adj_list[vet2] << vet1 + end + + # ## Удаление ребра ### + def remove_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + # Удалить ребро vet1 - vet2 + @adj_list[vet1].delete(vet2) + @adj_list[vet2].delete(vet1) + end + + # ## Добавление вершины ### + def add_vertex(vet) + return if @adj_list.include?(vet) + + # Добавить новый список в список смежности + @adj_list[vet] = [] + end + + # ## Удаление вершины ### + def remove_vertex(vet) + raise ArgumentError unless @adj_list.include?(vet) + + # Удалить из списка смежности список, соответствующий вершине vet + @adj_list.delete(vet) + # Обойти списки других вершин и удалить все ребра, содержащие vet + for vertex in @adj_list + @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) + end + end + + # ## Вывести список смежности ### + def __print__ + puts 'Список смежности =' + for vertex in @adj_list + tmp = @adj_list[vertex.first].map { |v| v.val } + puts "#{vertex.first.val}: #{tmp}," + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация неориентированного графа + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList.new(edges) + puts "\nГраф после инициализации" + graph.__print__ + + # Добавить ребро + # Вершины 1 и 2, то есть v[0] и v[2] + graph.add_edge(v[0], v[2]) + puts "\nГраф после добавления ребра 1-2" + graph.__print__ + + # Удалить ребро + # Вершины 1 и 3 соответствуют v[0], v[1] + graph.remove_edge(v[0], v[1]) + puts "\nГраф после удаления ребра 1-3" + graph.__print__ + + # Добавление вершины + v5 = Vertex.new(6) + graph.add_vertex(v5) + puts "\nГраф после добавления вершины 6" + graph.__print__ + + # Удаление вершины + # Вершина 3 соответствует v[1] + graph.remove_vertex(v[1]) + puts "\nГраф после удаления вершины 3" + graph.__print__ +end diff --git a/ru/codes/ruby/chapter_graph/graph_adjacency_matrix.rb b/ru/codes/ruby/chapter_graph/graph_adjacency_matrix.rb new file mode 100644 index 000000000..6373196bd --- /dev/null +++ b/ru/codes/ruby/chapter_graph/graph_adjacency_matrix.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_matrix.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +# ## Класс неориентированного графа на основе матрицы смежности ### +class GraphAdjMat + def initialize(vertices, edges) + # ## Конструктор ### + # Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + @vertices = [] + # Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + @adj_mat = [] + # Добавление вершины + vertices.each { |val| add_vertex(val) } + # Добавить ребра + # Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + edges.each { |e| add_edge(e[0], e[1]) } + end + + # ## Получение числа вершин ### + def size + @vertices.length + end + + # ## Добавление вершины ### + def add_vertex(val) + n = size + # Добавить значение новой вершины в список вершин + @vertices << val + # Добавить строку в матрицу смежности + new_row = Array.new(n, 0) + @adj_mat << new_row + # Добавить столбец в матрицу смежности + @adj_mat.each { |row| row << 0 } + end + + # ## Удаление вершины ### + def remove_vertex(index) + raise IndexError if index >= size + + # Удалить вершину с индексом index из списка вершин + @vertices.delete_at(index) + # Удалить строку с индексом index из матрицы смежности + @adj_mat.delete_at(index) + # Удалить столбец с индексом index из матрицы смежности + @adj_mat.each { |row| row.delete_at(index) } + end + + # ## Добавление ребра ### + def add_edge(i, j) + # Параметры i и j соответствуют индексам элементов vertices + # Обработка выхода индекса за границы и случая равенства + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + # В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + @adj_mat[i][j] = 1 + @adj_mat[j][i] = 1 + end + + # ## Удаление ребра ### + def remove_edge(i, j) + # Параметры i и j соответствуют индексам элементов vertices + # Обработка выхода индекса за границы и случая равенства + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + @adj_mat[i][j] = 0 + @adj_mat[j][i] = 0 + end + + # ## Вывести матрицу смежности ### + def __print__ + puts "Список вершин = #{@vertices}" + puts 'Матрица смежности =' + print_matrix(@adj_mat) + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация неориентированного графа + # Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat.new(vertices, edges) + puts "\nГраф после инициализации" + graph.__print__ + + # Добавление ребра + # Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.add_edge(0, 2) + puts "\nГраф после добавления ребра 1-2" + graph.__print__ + + # Удалить ребро + # Индексы вершин 1 и 3 равны 0 и 1 + graph.remove_edge(0, 1) + puts "\nГраф после удаления ребра 1-3" + graph.__print__ + + # Добавление вершины + graph.add_vertex(6) + puts "\nГраф после добавления вершины 6" + graph.__print__ + + # Удаление вершины + # Индекс вершины 3 равен 1 + graph.remove_vertex(1) + puts "\nГраф после удаления вершины 3" + graph.__print__ +end diff --git a/ru/codes/ruby/chapter_graph/graph_bfs.rb b/ru/codes/ruby/chapter_graph/graph_bfs.rb new file mode 100644 index 000000000..0a79f4a8f --- /dev/null +++ b/ru/codes/ruby/chapter_graph/graph_bfs.rb @@ -0,0 +1,61 @@ +=begin +File: graph_bfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +# ## Обход в ширину ### +def graph_bfs(graph, start_vet) + # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины + # Последовательность обхода вершин + res = [] + # Хеш-множество для хранения уже посещенных вершин + visited = Set.new([start_vet]) + # Очередь используется для реализации BFS + que = [start_vet] + # Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while que.length > 0 + vet = que.shift # Извлечь головную вершину из очереди + res << vet # Отметить посещенную вершину + # Обойти все смежные вершины данной вершины + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # Пропустить уже посещенную вершину + que << adj_vet # Помещать в очередь только непосещенные вершины + visited.add(adj_vet) # Отметить эту вершину как посещенную + end + end + # Вернуть последовательность обхода вершин + res +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация неориентированного графа + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ] + graph = GraphAdjList.new(edges) + puts "\nГраф после инициализации" + graph.__print__ + + # Обход в ширину + res = graph_bfs(graph, v.first) + puts "\nПоследовательность вершин при обходе в ширину (BFS)" + p vets_to_vals(res) +end diff --git a/ru/codes/ruby/chapter_graph/graph_dfs.rb b/ru/codes/ruby/chapter_graph/graph_dfs.rb new file mode 100644 index 000000000..d48185c48 --- /dev/null +++ b/ru/codes/ruby/chapter_graph/graph_dfs.rb @@ -0,0 +1,54 @@ +=begin +File: graph_dfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +# ## Вспомогательная функция обхода в глубину ### +def dfs(graph, visited, res, vet) + res << vet # Отметить посещенную вершину + visited.add(vet) # Отметить эту вершину как посещенную + # Обойти все смежные вершины данной вершины + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # Пропустить уже посещенную вершину + # Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adj_vet) + end +end + +# ## Обход в глубину ### +def graph_dfs(graph, start_vet) + # Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины + # Последовательность обхода вершин + res = [] + # Хеш-множество для хранения уже посещенных вершин + visited = Set.new + dfs(graph, visited, res, start_vet) + res +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация неориентированного графа + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList.new(edges) + puts "\nГраф после инициализации" + graph.__print__ + + # Обход в глубину + res = graph_dfs(graph, v[0]) + puts "\nПоследовательность вершин при обходе в глубину (DFS)" + p vets_to_vals(res) +end diff --git a/ru/codes/ruby/chapter_greedy/coin_change_greedy.rb b/ru/codes/ruby/chapter_greedy/coin_change_greedy.rb new file mode 100644 index 000000000..ebe8a8f45 --- /dev/null +++ b/ru/codes/ruby/chapter_greedy/coin_change_greedy.rb @@ -0,0 +1,50 @@ +=begin +File: coin_change_greedy.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Размен монет: жадный алгоритм ### +def coin_change_greedy(coins, amt) + # Предположить, что список coins упорядочен + i = coins.length - 1 + count = 0 + # Циклически выполнять жадный выбор, пока не останется суммы + while amt > 0 + # Найти монету, которая меньше остатка суммы и наиболее к нему близка + while i > 0 && coins[i] > amt + i -= 1 + end + # Выбрать coins[i] + amt -= coins[i] + count += 1 + end + # Если допустимое решение не найдено, вернуть -1 + amt == 0 ? count : -1 +end + +### Driver Code ### +if __FILE__ == $0 + # Жадный подход: гарантирует нахождение глобально оптимального решения + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "Минимальное количество монет для суммы #{amt}: #{res}" + + # Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "Минимальное количество монет для суммы #{amt}: #{res}" + puts "На самом деле минимум равен 3: 20 + 20 + 20" + + # Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "Минимальное количество монет для суммы #{amt}: #{res}" + puts "На самом деле минимум равен 2: 49 + 49" +end diff --git a/ru/codes/ruby/chapter_greedy/fractional_knapsack.rb b/ru/codes/ruby/chapter_greedy/fractional_knapsack.rb new file mode 100644 index 000000000..f1677524c --- /dev/null +++ b/ru/codes/ruby/chapter_greedy/fractional_knapsack.rb @@ -0,0 +1,51 @@ +=begin +File: fractional_knapsack.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Предмет ### +class Item + attr_accessor :w # Вес предмета + attr_accessor :v # Стоимость предмета + + def initialize(w, v) + @w = w + @v = v + end +end + +# ## Дробный рюкзак: жадный алгоритм ### +def fractional_knapsack(wgt, val, cap) + # Создать список предметов с двумя свойствами: вес и стоимость + items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } + # Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } + # Циклический жадный выбор + res = 0 + for item in items + if item.w <= cap + # Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v + cap -= item.w + else + # Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (item.v.to_f / item.w) * cap + # Свободной вместимости больше не осталось, поэтому выйти из цикла + break + end + end + res +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # Жадный алгоритм + res = fractional_knapsack(wgt, val, cap) + puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}" +end diff --git a/ru/codes/ruby/chapter_greedy/max_capacity.rb b/ru/codes/ruby/chapter_greedy/max_capacity.rb new file mode 100644 index 000000000..44b7ec4cf --- /dev/null +++ b/ru/codes/ruby/chapter_greedy/max_capacity.rb @@ -0,0 +1,37 @@ +=begin +File: max_capacity.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Максимальная вместимость: жадный алгоритм ### +def max_capacity(ht) + # Инициализировать i и j так, чтобы они располагались по двум концам массива + i, j = 0, ht.length - 1 + # Начальная максимальная вместимость равна 0 + res = 0 + + # Выполнять жадный выбор в цикле, пока две доски не встретятся + while i < j + # Обновить максимальную вместимость + cap = [ht[i], ht[j]].min * (j - i) + res = [res, cap].max + # Сдвигать внутрь более короткую сторону + if ht[i] < ht[j] + i += 1 + else + j -= 1 + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # Жадный алгоритм + res = max_capacity(ht) + puts "Максимальная вместимость = #{res}" +end diff --git a/ru/codes/ruby/chapter_greedy/max_product_cutting.rb b/ru/codes/ruby/chapter_greedy/max_product_cutting.rb new file mode 100644 index 000000000..d8c1bb77d --- /dev/null +++ b/ru/codes/ruby/chapter_greedy/max_product_cutting.rb @@ -0,0 +1,28 @@ +=begin +File: max_product_cutting.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Максимальное произведение разрезания: жадный алгоритм ### +def max_product_cutting(n) + # Когда n <= 3, обязательно нужно выделить одну 1 + return 1 * (n - 1) if n <= 3 + # Жадно выделить множители 3, где a — число троек, а b — остаток + a, b = n / 3, n % 3 + # Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return (3.pow(a - 1) * 2 * 2).to_i if b == 1 + # Если остаток равен 2, ничего не делать + return (3.pow(a) * 2).to_i if b == 2 + # Если остаток равен 0, ничего не делать + 3.pow(a).to_i +end + +### Driver Code ### +if __FILE__ == $0 + n = 58 + + # Жадный алгоритм + res = max_product_cutting(n) + puts "Максимальное произведение после разрезания = #{res}" +end diff --git a/ru/codes/ruby/chapter_hashing/array_hash_map.rb b/ru/codes/ruby/chapter_hashing/array_hash_map.rb new file mode 100644 index 000000000..cf23e14ab --- /dev/null +++ b/ru/codes/ruby/chapter_hashing/array_hash_map.rb @@ -0,0 +1,121 @@ +=begin +File: array_hash_map.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Пара ключ-значение ### +class Pair + attr_accessor :key, :val + + def initialize(key, val) + @key = key + @val = val + end +end + +# ## Хеш-таблица на основе массива ### +class ArrayHashMap + # ## Конструктор ### + def initialize + # Инициализировать массив, содержащий 100 корзин + @buckets = Array.new(100) + end + + # ## Хеш-функция ### + def hash_func(key) + index = key % 100 + end + + # ## Операция поиска ### + def get(key) + index = hash_func(key) + pair = @buckets[index] + + return if pair.nil? + pair.val + end + + # ## Операция добавления ### + def put(key, val) + pair = Pair.new(key, val) + index = hash_func(key) + @buckets[index] = pair + end + + # ## Операция удаления ### + def remove(key) + index = hash_func(key) + # Присвоить nil, что означает удаление + @buckets[index] = nil + end + + # ## Получить все пары ключ-значение ### + def entry_set + result = [] + @buckets.each { |pair| result << pair unless pair.nil? } + result + end + + # ## Получить все ключи ### + def key_set + result = [] + @buckets.each { |pair| result << pair.key unless pair.nil? } + result + end + + # ## Получить все значения ### + def value_set + result = [] + @buckets.each { |pair| result << pair.val unless pair.nil? } + result + end + + # ## Вывести хеш-таблицу ### + def print + @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация хеш-таблицы + hmap = ArrayHashMap.new + + # Операция добавления + # Добавить пару (key, value) в хеш-таблицу + hmap.put(12836, "Сяо Ха") + hmap.put(15937, "Сяо Ло") + hmap.put(16750, "Сяо Суань") + hmap.put(13276, "Сяо Фа") + hmap.put(10583, "Сяо Я") + puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" + hmap.print + + # Операция поиска + # По ключу key получить из хеш-таблицы значение value + name = hmap.get(15937) + puts "\nДля номера 15937 найдено имя #{name}" + + # Операция удаления + # Удалить пару значений (key, value) из хеш-таблицы + hmap.remove(10583) + puts "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" + hmap.print + + # Обход хеш-таблицы + puts "\nОтдельный обход пар ключ-значение" + for pair in hmap.entry_set + puts "#{pair.key} -> #{pair.val}" + end + + puts "\nОтдельный обход ключей" + for key in hmap.key_set + puts key + end + + puts "\nОтдельный обход значений" + for val in hmap.value_set + puts val + end +end diff --git a/ru/codes/ruby/chapter_hashing/built_in_hash.rb b/ru/codes/ruby/chapter_hashing/built_in_hash.rb new file mode 100644 index 000000000..d40bcc3ac --- /dev/null +++ b/ru/codes/ruby/chapter_hashing/built_in_hash.rb @@ -0,0 +1,34 @@ +=begin +File: built_in_hash.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### Driver Code ### +if __FILE__ == $0 + num = 3 + hash_num = num.hash + puts "Хеш-значение целого числа #{num}: #{hash_num}" + + bol = true + hash_bol = bol.hash + puts "Хеш-значение булева значения #{bol}: #{hash_bol}" + + dec = 3.14159 + hash_dec = dec.hash + puts "Хеш-значение десятичного числа #{dec}: #{hash_dec}" + + str = "Hello Algo" + hash_str = str.hash + puts "Хеш-значение строки #{str}: #{hash_str}" + + tup = [12836, 'Сяо Ха'] + hash_tup = tup.hash + puts "Хеш-значение кортежа #{tup}: #{hash_tup}" + + obj = ListNode.new(0) + hash_obj = obj.hash + puts "Хеш-значение объекта узла #{obj}: #{hash_obj}" +end diff --git a/ru/codes/ruby/chapter_hashing/hash_map.rb b/ru/codes/ruby/chapter_hashing/hash_map.rb new file mode 100644 index 000000000..949d540f7 --- /dev/null +++ b/ru/codes/ruby/chapter_hashing/hash_map.rb @@ -0,0 +1,44 @@ +=begin +File: hash_map.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # Инициализация хеш-таблицы + hmap = {} + + # Операция добавления + # Добавить пару (key, value) в хеш-таблицу + hmap[12836] = "Сяо Ха" + hmap[15937] = "Сяо Ло" + hmap[16750] = "Сяо Суань" + hmap[13276] = "Сяо Фа" + hmap[10583] = "Сяо Я" + puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" + print_hash_map(hmap) + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение value + name = hmap[15937] + puts "\nДля номера 15937 найдено имя #{name}" + + # Операция удаления + # Удалить пару (key, value) из хеш-таблицы + hmap.delete(10583) + puts "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение" + print_hash_map(hmap) + + # Обход хеш-таблицы + puts "\nОтдельный обход пар ключ-значение" + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } + + puts "\nОтдельный обход ключей" + hmap.keys.each { |key| puts key } + + puts "\nОтдельный обход значений" + hmap.values.each { |val| puts val } +end diff --git a/ru/codes/ruby/chapter_hashing/hash_map_chaining.rb b/ru/codes/ruby/chapter_hashing/hash_map_chaining.rb new file mode 100644 index 000000000..6620dd16b --- /dev/null +++ b/ru/codes/ruby/chapter_hashing/hash_map_chaining.rb @@ -0,0 +1,128 @@ +=begin +File: hash_map_chaining.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +# ## Хеш-таблица с цепочками ### +class HashMapChaining + # ## Конструктор ### + def initialize + @size = 0 # Число пар ключ-значение + @capacity = 4 # Вместимость хеш-таблицы + @load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения + @extend_ratio = 2 # Коэффициент расширения + @buckets = Array.new(@capacity) { [] } # Массив корзин + end + + # ## Хеш-функция ### + def hash_func(key) + key % @capacity + end + + # ## Коэффициент загрузки ### + def load_factor + @size / @capacity + end + + # ## Операция поиска ### + def get(key) + index = hash_func(key) + bucket = @buckets[index] + # Обойти корзину; если найден key, вернуть соответствующее val + for pair in bucket + return pair.val if pair.key == key + end + # Если key не найден, вернуть nil + nil + end + + # ## Операция добавления ### + def put(key, val) + # Когда коэффициент загрузки превышает порог, выполнить расширение + extend if load_factor > @load_thres + index = hash_func(key) + bucket = @buckets[index] + # Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for pair in bucket + if pair.key == key + pair.val = val + return + end + end + # Если такого key нет, добавить пару ключ-значение в конец + pair = Pair.new(key, val) + bucket << pair + @size += 1 + end + + # ## Операция удаления ### + def remove(key) + index = hash_func(key) + bucket = @buckets[index] + # Обойти корзину и удалить из нее пару ключ-значение + for pair in bucket + if pair.key == key + bucket.delete(pair) + @size -= 1 + break + end + end + end + + # ## Расширение хеш-таблицы ### + def extend + # Временно сохранить исходную хеш-таблицу + buckets = @buckets + # Инициализация новой хеш-таблицы после расширения + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) { [] } + @size = 0 + # Перенести пары ключ-значение из исходной хеш-таблицы в новую + for bucket in buckets + for pair in bucket + put(pair.key, pair.val) + end + end + end + + # ## Вывести хеш-таблицу ### + def print + for bucket in @buckets + res = [] + for pair in bucket + res << "#{pair.key} -> #{pair.val}" + end + pp res + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # ## Инициализация хеш-таблицы + hashmap = HashMapChaining.new + + # Операция добавления + # Добавить пару (key, value) в хеш-таблицу + hashmap.put(12836, "Сяо Ха") + hashmap.put(15937, "Сяо Ло") + hashmap.put(16750, "Сяо Суань") + hashmap.put(13276, "Сяо Фа") + hashmap.put(10583, "Сяо Я") + puts "\nПосле завершения добавления хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение value + name = hashmap.get(13276) + puts "\nДля номера 13276 найдено имя #{name}" + + # Операция удаления + # Удалить пару (key, value) из хеш-таблицы + hashmap.remove(12836) + puts "\nПосле удаления 12836 хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print +end diff --git a/ru/codes/ruby/chapter_hashing/hash_map_open_addressing.rb b/ru/codes/ruby/chapter_hashing/hash_map_open_addressing.rb new file mode 100644 index 000000000..aa1b6864a --- /dev/null +++ b/ru/codes/ruby/chapter_hashing/hash_map_open_addressing.rb @@ -0,0 +1,147 @@ +=begin +File: hash_map_open_addressing.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +# ## Хеш-таблица с открытой адресацией ### +class HashMapOpenAddressing + TOMBSTONE = Pair.new(-1, '-1') # Удалить метку + + # ## Конструктор ### + def initialize + @size = 0 # Число пар ключ-значение + @capacity = 4 # Вместимость хеш-таблицы + @load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения + @extend_ratio = 2 # Коэффициент расширения + @buckets = Array.new(@capacity) # Массив корзин + end + + # ## Хеш-функция ### + def hash_func(key) + key % @capacity + end + + # ## Коэффициент загрузки ### + def load_factor + @size / @capacity + end + + # ## Найти индекс корзины, соответствующий key ### + def find_bucket(key) + index = hash_func(key) + first_tombstone = -1 + # Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while !@buckets[index].nil? + # Если встретился key, вернуть соответствующий индекс корзины + if @buckets[index].key == key + # Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if first_tombstone != -1 + @buckets[first_tombstone] = @buckets[index] + @buckets[index] = TOMBSTONE + return first_tombstone # Вернуть индекс корзины после перемещения + end + return index # Вернуть индекс корзины + end + # Записать первую встретившуюся метку удаления + first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE + # Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % @capacity + end + # Если key не существует, вернуть индекс точки добавления + first_tombstone == -1 ? index : first_tombstone + end + + # ## Операция поиска ### + def get(key) + # Найти индекс корзины, соответствующий key + index = find_bucket(key) + # Если пара ключ-значение найдена, вернуть соответствующее val + return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) + # Если пара ключ-значение не существует, вернуть nil + nil + end + + # ## Операция добавления ### + def put(key, val) + # Когда коэффициент загрузки превышает порог, выполнить расширение + extend if load_factor > @load_thres + # Найти индекс корзины, соответствующий key + index = find_bucket(key) + # Если пара ключ-значение найдена, перезаписать val и вернуть + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index].val = val + return + end + # Если пары ключ-значение нет, добавить ее + @buckets[index] = Pair.new(key, val) + @size += 1 + end + + # ## Операция удаления ### + def remove(key) + # Найти индекс корзины, соответствующий key + index = find_bucket(key) + # Если пара ключ-значение найдена, заменить ее меткой удаления + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index] = TOMBSTONE + @size -= 1 + end + end + + # ## Расширение хеш-таблицы ### + def extend + # Временно сохранить исходную хеш-таблицу + buckets_tmp = @buckets + # Инициализация новой хеш-таблицы после расширения + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) + @size = 0 + # Перенести пары ключ-значение из исходной хеш-таблицы в новую + for pair in buckets_tmp + put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) + end + end + + # ## Вывести хеш-таблицу ### + def print + for pair in @buckets + if pair.nil? + puts "Nil" + elsif pair == TOMBSTONE + puts "TOMBSTONE" + else + puts "#{pair.key} -> #{pair.val}" + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация хеш-таблицы + hashmap = HashMapOpenAddressing.new + + # Операция добавления + # Добавить пару (key, val) в хеш-таблицу + hashmap.put(12836, "Сяо Ха") + hashmap.put(15937, "Сяо Ло") + hashmap.put(16750, "Сяо Суань") + hashmap.put(13276, "Сяо Фа") + hashmap.put(10583, "Сяо Я") + puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение" + hashmap.print + + # Операция поиска + # Передать ключ key в хеш-таблицу и получить значение val + name = hashmap.get(13276) + puts "\nДля номера 13276 найдено имя #{name}" + + # Операция удаления + # Удалить пару (key, val) из хеш-таблицы + hashmap.remove(16750) + puts "\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение" + hashmap.print +end diff --git a/ru/codes/ruby/chapter_hashing/simple_hash.rb b/ru/codes/ruby/chapter_hashing/simple_hash.rb new file mode 100644 index 000000000..f16efacdf --- /dev/null +++ b/ru/codes/ruby/chapter_hashing/simple_hash.rb @@ -0,0 +1,62 @@ +=begin +File: simple_hash.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Аддитивное хеширование ### +def add_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash += c.ord } + + hash % modulus +end + +# ## Мультипликативное хеширование ### +def mul_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = 31 * hash + c.ord } + + hash % modulus +end + +# ## XOR-хеширование ### +def xor_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash ^= c.ord } + + hash % modulus +end + +# ## Хеширование с циклическим сдвигом ### +def rot_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } + + hash % modulus +end + +### Driver Code ### +if __FILE__ == $0 + key = "Hello Algo" + + hash = add_hash(key) + puts "Хеш-сумма сложением = #{hash}" + + hash = mul_hash(key) + puts "Хеш-сумма умножением = #{hash}" + + hash = xor_hash(key) + puts "Хеш-сумма XOR = #{hash}" + + hash = rot_hash(key) + puts "Хеш-сумма с циклическим сдвигом = #{hash}" +end diff --git a/ru/codes/ruby/chapter_heap/my_heap.rb b/ru/codes/ruby/chapter_heap/my_heap.rb new file mode 100644 index 000000000..df447caf6 --- /dev/null +++ b/ru/codes/ruby/chapter_heap/my_heap.rb @@ -0,0 +1,147 @@ +=begin +File: my_heap.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/print_util' + +# ## Максимальная куча ### +class MaxHeap + attr_reader :max_heap + + # ## Конструктор, строящий кучу по входному списку ### + def initialize(nums) + # Добавить элементы списка в кучу без изменений + @max_heap = nums + # Выполнить heapify для всех узлов, кроме листовых + parent(size - 1).downto(0) do |i| + sift_down(i) + end + end + + # ## Получить индекс левого дочернего узла ### + def left(i) + 2 * i + 1 + end + + # ## Получить индекс правого дочернего узла ### + def right(i) + 2 * i + 2 + end + + # ## Получить индекс родительского узла ### + def parent(i) + (i - 1) / 2 # Округление вниз при делении + end + + # ## Обмен элементов ### + def swap(i, j) + @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] + end + + # ## Получить размер кучи ### + def size + @max_heap.length + end + + # ## Проверка, пуста ли куча ### + def is_empty? + size == 0 + end + + # ## Доступ к элементу на вершине кучи ### + def peek + @max_heap[0] + end + + # ## Добавление элемента в кучу ### + def push(val) + # Добавление узла + @max_heap << val + # Просеивание снизу вверх + sift_up(size - 1) + end + + # ## Начиная с узла i, выполнить просеивание снизу вверх ### + def sift_up(i) + loop do + # Получение родительского узла для узла i + p = parent(i) + # Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + break if p < 0 || @max_heap[i] <= @max_heap[p] + # Поменять два узла местами + swap(i, p) + # Циклическое просеивание вверх + i = p + end + end + + # ## Извлечение элемента из кучи ### + def pop + # Обработка пустого случая + raise IndexError, "куча пуста" if is_empty? + # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(0, size - 1) + # Удаление узла + val = @max_heap.pop + # Просеивание сверху вниз + sift_down(0) + # Вернуть элемент с вершины кучи + val + end + + # ## Начиная с узла i, выполнить просеивание сверху вниз ### + def sift_down(i) + loop do + # Определить узел с максимальным значением среди i, l и r и обозначить его как ma + l, r, ma = left(i), right(i), i + ma = l if l < size && @max_heap[l] > @max_heap[ma] + ma = r if r < size && @max_heap[r] > @max_heap[ma] + + # Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + break if ma == i + + # Поменять два узла местами + swap(i, ma) + # Циклическое просеивание вниз + i = ma + end + end + + # ## Вывести кучу (двоичное дерево) ### + def __print__ + print_heap(@max_heap) + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация максимальной кучи + max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + puts "\nПосле построения кучи из входного списка" + max_heap.__print__ + + # Получение элемента с вершины кучи + peek = max_heap.peek + puts "\nЭлемент на вершине кучи = #{peek}" + + # Добавление элемента в кучу + val = 7 + max_heap.push(val) + puts "\nПосле добавления элемента #{val} в кучу" + max_heap.__print__ + + # Извлечение элемента с вершины кучи + peek = max_heap.pop + puts "\nПосле извлечения вершины кучи #{peek}" + max_heap.__print__ + + # Получение размера кучи + size = max_heap.size + puts "\nКоличество элементов в куче = #{size}" + + # Проверка, пуста ли куча + is_empty = max_heap.is_empty? + puts "\nПуста ли куча: #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_heap/top_k.rb b/ru/codes/ruby/chapter_heap/top_k.rb new file mode 100644 index 000000000..740a9f4c7 --- /dev/null +++ b/ru/codes/ruby/chapter_heap/top_k.rb @@ -0,0 +1,64 @@ +=begin +File: top_k.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative "./my_heap" + +# ## Добавление элемента в кучу ### +def push_min_heap(heap, val) + # Инвертировать знак элемента + heap.push(-val) +end + +# ## Извлечение элемента из кучи ### +def pop_min_heap(heap) + # Инвертировать знак элемента + -heap.pop +end + +# ## Доступ к элементу на вершине кучи ### +def peek_min_heap(heap) + # Инвертировать знак элемента + -heap.peek +end + +# ## Извлечение элементов из кучи ### +def get_min_heap(heap) + # Инвертировать все элементы кучи + heap.max_heap.map { |x| -x } +end + +# ## Поиск k наибольших элементов массива с помощью кучи ### +def top_k_heap(nums, k) + # Инициализация минимальной кучи + # Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную + max_heap = MaxHeap.new([]) + + # Поместить первые k элементов массива в кучу + for i in 0...k + push_min_heap(max_heap, nums[i]) + end + + # Начиная с элемента k+1, поддерживать длину кучи равной k + for i in k...nums.length + # Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if nums[i] > peek_min_heap(max_heap) + pop_min_heap(max_heap) + push_min_heap(max_heap, nums[i]) + end + end + + get_min_heap(max_heap) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + puts "#{k} наибольших элементов:" + print_heap(res) +end diff --git a/ru/codes/ruby/chapter_searching/binary_search.rb b/ru/codes/ruby/chapter_searching/binary_search.rb new file mode 100644 index 000000000..46f03fde3 --- /dev/null +++ b/ru/codes/ruby/chapter_searching/binary_search.rb @@ -0,0 +1,63 @@ +=begin +File: binary_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +# ## Бинарный поиск (двусторонне замкнутый интервал) ### +def binary_search(nums, target) + # Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + i, j = 0, nums.length - 1 + + # Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while i <= j + # Теоретически числа в Ruby могут быть сколь угодно большими (ограничены только объемом памяти), поэтому не нужно учитывать переполнение больших чисел + m = (i + j) / 2 # Вычислить индекс середины m + + if nums[m] < target + i = m + 1 # Это означает, что target находится в интервале [m+1, j] + elsif nums[m] > target + j = m - 1 # Это означает, что target находится в интервале [i, m-1] + else + return m # Целевой элемент найден, вернуть его индекс + end + end + + -1 # Целевой элемент не найден, вернуть -1 +end + +# ## Бинарный поиск (лево замкнутый, право открытый интервал) ### +def binary_search_lcro(nums, target) + # Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + i, j = 0, nums.length + + # Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while i < j + # Вычислить индекс середины m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # Это означает, что target находится в интервале [m+1, j) + elsif nums[m] > target + j = m - 1 # Это означает, что target находится в интервале [i, m) + else + return m # Целевой элемент найден, вернуть его индекс + end + end + + -1 # Целевой элемент не найден, вернуть -1 +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # Бинарный поиск (двусторонне замкнутый интервал) + index = binary_search(nums, target) + puts "Индекс целевого элемента 6 = #{index}" + + # Бинарный поиск (лево замкнутый, право открытый интервал) + index = binary_search_lcro(nums, target) + puts "Индекс целевого элемента 6 = #{index}" +end diff --git a/ru/codes/ruby/chapter_searching/binary_search_edge.rb b/ru/codes/ruby/chapter_searching/binary_search_edge.rb new file mode 100644 index 000000000..e397ca78e --- /dev/null +++ b/ru/codes/ruby/chapter_searching/binary_search_edge.rb @@ -0,0 +1,47 @@ +=begin +File: binary_search_edge.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative './binary_search_insertion' + +# ## Бинарный поиск самого левого target ### +def binary_search_left_edge(nums, target) + # Эквивалентно поиску точки вставки target + i = binary_search_insertion(nums, target) + + # target не найден, вернуть -1 + return -1 if i == nums.length || nums[i] != target + + i # Найти target и вернуть индекс i +end + +# ## Бинарный поиск самого правого target ### +def binary_search_right_edge(nums, target) + # Преобразовать задачу в поиск самого левого target + 1 + i = binary_search_insertion(nums, target + 1) + + # j указывает на самый правый target, а i — на первый элемент больше target + j = i - 1 + + # target не найден, вернуть -1 + return -1 if j == -1 || nums[j] != target + + j # Найти target и вернуть индекс j +end + +### Driver Code ### +if __FILE__ == $0 + # Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\nМассив nums = #{nums}" + + # Бинарный поиск левой и правой границы + for target in [6, 7] + index = binary_search_left_edge(nums, target) + puts "Индекс крайнего левого элемента #{target}: #{index}" + index = binary_search_right_edge(nums, target) + puts "Индекс крайнего правого элемента #{target}: #{index}" + end +end diff --git a/ru/codes/ruby/chapter_searching/binary_search_insertion.rb b/ru/codes/ruby/chapter_searching/binary_search_insertion.rb new file mode 100644 index 000000000..d320b0cdb --- /dev/null +++ b/ru/codes/ruby/chapter_searching/binary_search_insertion.rb @@ -0,0 +1,68 @@ +=begin +File: binary_search_insertion.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +# ## Бинарный поиск точки вставки (без повторяющихся элементов) ### +def binary_search_insertion_simple(nums, target) + # Инициализировать двусторонне замкнутый интервал [0, n-1] + i, j = 0, nums.length - 1 + + while i <= j + # Вычислить индекс середины m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target находится в интервале [m+1, j] + elsif nums[m] > target + j = m - 1 # target находится в интервале [i, m-1] + else + return m # Найти target и вернуть точку вставки m + end + end + + i # target не найден, вернуть точку вставки i +end + +# ## Бинарный поиск точки вставки (с повторяющимися элементами) ### +def binary_search_insertion(nums, target) + # Инициализировать двусторонне замкнутый интервал [0, n-1] + i, j = 0, nums.length - 1 + + while i <= j + # Вычислить индекс середины m + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target находится в интервале [m+1, j] + elsif nums[m] > target + j = m - 1 # target находится в интервале [i, m-1] + else + j = m - 1 # Первый элемент меньше target находится в интервале [i, m-1] + end + end + + i # Вернуть точку вставки i +end + +### Driver Code ### +if __FILE__ == $0 + # Массив без повторяющихся элементов + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + puts "\nМассив nums = #{nums}" + # Бинарный поиск точки вставки + for target in [6, 9] + index = binary_search_insertion_simple(nums, target) + puts "Индекс позиции вставки элемента #{target}: #{index}" + end + + # Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\nМассив nums = #{nums}" + # Бинарный поиск точки вставки + for target in [2, 6, 20] + index = binary_search_insertion(nums, target) + puts "Индекс позиции вставки элемента #{target}: #{index}" + end +end diff --git a/ru/codes/ruby/chapter_searching/hashing_search.rb b/ru/codes/ruby/chapter_searching/hashing_search.rb new file mode 100644 index 000000000..cc84157fc --- /dev/null +++ b/ru/codes/ruby/chapter_searching/hashing_search.rb @@ -0,0 +1,47 @@ +=begin +File: hashing_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +# ## Хеш-поиск (массив) ### +def hashing_search_array(hmap, target) + # key хеш-таблицы: целевой элемент, value: индекс + # Если такого key нет в хеш-таблице, вернуть -1 + hmap[target] || -1 +end + +# ## Хеш-поиск (связный список) ### +def hashing_search_linkedlist(hmap, target) + # key хеш-таблицы: целевой элемент, value: объект узла + # Если такого key нет в хеш-таблице, вернуть None + hmap[target] || nil +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # Хеш-поиск (массив) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # Инициализация хеш-таблицы + map0 = {} + for i in 0...nums.length + map0[nums[i]] = i # key: элемент, value: индекс + end + index = hashing_search_array(map0, target) + puts "Индекс целевого элемента 3 = #{index}" + + # Хеш-поиск (связный список) + head = arr_to_linked_list(nums) + # Инициализация хеш-таблицы + map1 = {} + while head + map1[head.val] = head + head = head.next + end + node = hashing_search_linkedlist(map1, target) + puts "Объект узла со значением 3 = #{node}" +end diff --git a/ru/codes/ruby/chapter_searching/linear_search.rb b/ru/codes/ruby/chapter_searching/linear_search.rb new file mode 100644 index 000000000..a59e24a0a --- /dev/null +++ b/ru/codes/ruby/chapter_searching/linear_search.rb @@ -0,0 +1,44 @@ +=begin +File: linear_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +# ## Линейный поиск (массив) ### +def linear_search_array(nums, target) + # Обход массива + for i in 0...nums.length + return i if nums[i] == target # Целевой элемент найден, вернуть его индекс + end + + -1 # Целевой элемент не найден, вернуть -1 +end + +# ## Линейный поиск (связный список) ### +def linear_search_linkedlist(head, target) + # Обойти связный список + while head + return head if head.val == target # Найти целевой узел и вернуть его + + head = head.next + end + + nil # Целевой узел не найден, вернуть None +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # Выполнить линейный поиск в массиве + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index = linear_search_array(nums, target) + puts "Индекс целевого элемента 3 = #{index}" + + # Выполнить линейный поиск в связном списке + head = arr_to_linked_list(nums) + node = linear_search_linkedlist(head, target) + puts "Объект узла со значением 3 = #{node}" +end diff --git a/ru/codes/ruby/chapter_searching/two_sum.rb b/ru/codes/ruby/chapter_searching/two_sum.rb new file mode 100644 index 000000000..311210a10 --- /dev/null +++ b/ru/codes/ruby/chapter_searching/two_sum.rb @@ -0,0 +1,46 @@ +=begin +File: two_sum.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +# ## Метод 1: полный перебор ### +def two_sum_brute_force(nums, target) + # Два вложенных цикла, временная сложность O(n^2) + for i in 0...(nums.length - 1) + for j in (i + 1)...nums.length + return [i, j] if nums[i] + nums[j] == target + end + end + + [] +end + +# ## Метод 2: вспомогательная хеш-таблица ### +def two_sum_hash_table(nums, target) + # Вспомогательная хеш-таблица, пространственная сложность O(n) + dic = {} + # Один цикл, временная сложность O(n) + for i in 0...nums.length + return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) + + dic[nums[i]] = i + end + + [] +end + +### Driver Code ### +if __FILE__ == $0 + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Основной код ====== + # Метод 1 + res = two_sum_brute_force(nums, target) + puts "Результат метода 1 res = #{res}" + # Метод 2 + res = two_sum_hash_table(nums, target) + puts "Результат метода 2 res = #{res}" +end diff --git a/ru/codes/ruby/chapter_sorting/bubble_sort.rb b/ru/codes/ruby/chapter_sorting/bubble_sort.rb new file mode 100644 index 000000000..e3eaa83c6 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/bubble_sort.rb @@ -0,0 +1,51 @@ +=begin +File: bubble_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Пузырьковая сортировка ### +def bubble_sort(nums) + n = nums.length + # Внешний цикл: неотсортированный диапазон [0, i] + for i in (n - 1).downto(1) + # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0...i + if nums[j] > nums[j + 1] + # Поменять местами nums[j] и nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + end + end + end +end + +# ## Пузырьковая сортировка (оптимизация флагом) ### +def bubble_sort_with_flag(nums) + n = nums.length + # Внешний цикл: неотсортированный диапазон [0, i] + for i in (n - 1).downto(1) + flag = false # Инициализировать флаг + + # Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0...i + if nums[j] > nums[j + 1] + # Поменять местами nums[j] и nums[j + 1] + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = true # Записать обмен элементов + end + end + + break unless flag # На этой итерации «всплытия» не было ни одного обмена, сразу выйти + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + puts "После пузырьковой сортировки nums = #{nums}" + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + puts "После пузырьковой сортировки nums = #{nums1}" +end diff --git a/ru/codes/ruby/chapter_sorting/bucket_sort.rb b/ru/codes/ruby/chapter_sorting/bucket_sort.rb new file mode 100644 index 000000000..5aa6a2e32 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/bucket_sort.rb @@ -0,0 +1,43 @@ +=begin +File: bucket_sort.rb +Created Time: 2024-04-17 +Author: Martin Xu (martin.xus@gmail.com) +=end + +# ## Сортировка корзинами ### +def bucket_sort(nums) + # Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + k = nums.length / 2 + buckets = Array.new(k) { [] } + + # 1. Распределить элементы массива по корзинам + nums.each do |num| + # Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + i = (num * k).to_i + # Добавить num в корзину i + buckets[i] << num + end + + # 2. Выполнить сортировку внутри каждой корзины + buckets.each do |bucket| + # Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + bucket.sort! + end + + # 3. Обойти корзины и объединить результаты + i = 0 + buckets.each do |bucket| + bucket.each do |num| + nums[i] = num + i += 1 + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + puts "После сортировки корзинами nums = #{nums}" +end diff --git a/ru/codes/ruby/chapter_sorting/counting_sort.rb b/ru/codes/ruby/chapter_sorting/counting_sort.rb new file mode 100644 index 000000000..f65beafc3 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/counting_sort.rb @@ -0,0 +1,62 @@ +=begin +File: counting_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Сортировка подсчетом ### +def counting_sort_naive(nums) + # Простая реализация, не подходит для сортировки объектов + # 1. Найти максимальный элемент массива m + m = 0 + nums.each { |num| m = [m, num].max } + # 2. Подсчитать число появлений каждой цифры + # counter[num] обозначает число появлений num + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. Обойти counter и заполнить исходный массив nums элементами + i = 0 + for num in 0...(m + 1) + (0...counter[num]).each do + nums[i] = num + i += 1 + end + end +end + +# ## Сортировка подсчетом ### +def counting_sort(nums) + # Полная реализация, позволяет сортировать объекты и является стабильной сортировкой + # 1. Найти максимальный элемент массива m + m = nums.max + # 2. Подсчитать число появлений каждой цифры + # counter[num] обозначает число появлений num + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + # То есть counter[num]-1 — это индекс последнего появления num в res + (0...m).each { |i| counter[i + 1] += counter[i] } + # 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + # Инициализировать массив res для хранения результата + n = nums.length + res = Array.new(n, 0) + (n - 1).downto(0).each do |i| + num = nums[i] + res[counter[num] - 1] = num # Поместить num по соответствующему индексу + counter[num] -= 1 # Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + end + # Перезаписать исходный массив nums массивом результата res + (0...n).each { |i| nums[i] = res[i] } +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + puts "После сортировки подсчетом (объекты не поддерживаются) nums = #{nums}" + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + puts "После сортировки подсчетом nums1 = #{nums1}" +end diff --git a/ru/codes/ruby/chapter_sorting/heap_sort.rb b/ru/codes/ruby/chapter_sorting/heap_sort.rb new file mode 100644 index 000000000..8df9d5ec0 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/heap_sort.rb @@ -0,0 +1,45 @@ +=begin +File: heap_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +# ## Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз ### +def sift_down(nums, n, i) + while true + # Определить узел с максимальным значением среди i, l и r и обозначить его как ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + ma = l if l < n && nums[l] > nums[ma] + ma = r if r < n && nums[r] > nums[ma] + # Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + break if ma == i + # Поменять два узла местами + nums[i], nums[ma] = nums[ma], nums[i] + # Циклическое просеивание вниз + i = ma + end +end + +# ## Сортировка кучей ### +def heap_sort(nums) + # Построение кучи: выполнить heapify для всех узлов, кроме листовых + (nums.length / 2 - 1).downto(0) do |i| + sift_down(nums, nums.length, i) + end + # Извлекать максимальный элемент из кучи в течение n-1 итераций + (nums.length - 1).downto(1) do |i| + # Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + nums[0], nums[i] = nums[i], nums[0] + # Начиная с корневого узла, выполнить просеивание сверху вниз + sift_down(nums, i, 0) + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + puts "После сортировки кучей nums = #{nums.inspect}" +end diff --git a/ru/codes/ruby/chapter_sorting/insertion_sort.rb b/ru/codes/ruby/chapter_sorting/insertion_sort.rb new file mode 100644 index 000000000..c6e2f8cdc --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/insertion_sort.rb @@ -0,0 +1,26 @@ +=begin +File: insertion_sort.rb +Created Time: 2024-04-02 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Сортировка вставками ### +def insertion_sort(nums) + n = nums.length + # Внешний цикл: отсортированный диапазон [0, i-1] + for i in 1...n + base = nums[i] + j = i - 1 + # Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while j >= 0 && nums[j] > base + nums[j + 1] = nums[j] # Сдвинуть nums[j] на одну позицию вправо + j -= 1 + end + nums[j + 1] = base # Поместить base в правильную позицию + end +end + +### Driver Code ### +nums = [4, 1, 3, 1, 5, 2] +insertion_sort(nums) +puts "После сортировки вставками nums = #{nums}" diff --git a/ru/codes/ruby/chapter_sorting/merge_sort.rb b/ru/codes/ruby/chapter_sorting/merge_sort.rb new file mode 100644 index 000000000..c33418557 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/merge_sort.rb @@ -0,0 +1,60 @@ +=begin +File: merge_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +# ## Слияние левого и правого подмассивов ### +def merge(nums, left, mid, right) + # Интервал левого подмассива: [left, mid], правого подмассива: [mid+1, right] + # Создать временный массив tmp для хранения результата слияния + tmp = Array.new(right - left + 1, 0) + # Инициализировать начальные индексы левого и правого подмассивов + i, j, k = left, mid + 1, 0 + # Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while i <= mid && j <= right + if nums[i] <= nums[j] + tmp[k] = nums[i] + i += 1 + else + tmp[k] = nums[j] + j += 1 + end + k += 1 + end + # Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while i <= mid + tmp[k] = nums[i] + i += 1 + k += 1 + end + while j <= right + tmp[k] = nums[j] + j += 1 + k += 1 + end + # Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + (0...tmp.length).each do |k| + nums[left + k] = tmp[k] + end +end + +# ## Сортировка слиянием ### +def merge_sort(nums, left, right) + # Условие завершения + # Когда длина подмассива равна 1, рекурсия завершается + return if left >= right + # Этап разбиения + mid = left + (right - left) / 2 # Вычислить середину + merge_sort(nums, left, mid) # Рекурсивно обработать левый подмассив + merge_sort(nums, mid + 1, right) # Рекурсивно обработать правый подмассив + # Этап слияния + merge(nums, left, mid, right) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, nums.length - 1) + puts "После сортировки слиянием nums = #{nums.inspect}" +end diff --git a/ru/codes/ruby/chapter_sorting/quick_sort.rb b/ru/codes/ruby/chapter_sorting/quick_sort.rb new file mode 100644 index 000000000..b6d6285c1 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/quick_sort.rb @@ -0,0 +1,153 @@ +=begin +File: quick_sort.rb +Created Time: 2024-04-01 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Класс быстрой сортировки ### +class QuickSort + class << self + # ## Разбиение с опорными указателями ### + def partition(nums, left, right) + # Взять nums[left] в качестве опорного элемента + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # Идти справа налево в поисках первого элемента меньше опорного + end + while i < j && nums[i] <= nums[left] + i += 1 # Идти слева направо в поисках первого элемента больше опорного + end + # Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + end + # Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + i # Вернуть индекс опорного элемента + end + + # ## Класс быстрой сортировки ### + def quick_sort(nums, left, right) + # Рекурсивно обрабатывать, пока длина подмассива не станет равной 1 + if left < right + # Разбиение с опорными указателями + pivot = partition(nums, left, right) + # Рекурсивно обработать левый и правый подмассивы + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +# ## Класс быстрой сортировки (оптимизация медианой) ### +class QuickSortMedian + class << self + # ## Выбрать медиану из трех кандидатов ### + def median_three(nums, left, mid, right) + # Выбрать медиану из трех кандидатов + _l, _m, _r = nums[left], nums[mid], nums[right] + # m находится между l и r + return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) + # l находится между m и r + return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) + return right + end + + # ## Разбиение с опорными указателями (медиана трех) ### + def partition(nums, left, right) + # ## Использовать nums[left] как опорный элемент + med = median_three(nums, left, (left + right) / 2, right) + # Переместить медиану в крайний левый элемент массива + nums[left], nums[med] = nums[med], nums[left] + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # Идти справа налево в поисках первого элемента меньше опорного + end + while i < j && nums[i] <= nums[left] + i += 1 # Идти слева направо в поисках первого элемента больше опорного + end + # Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + end + # Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + i # Вернуть индекс опорного элемента + end + + # ## Быстрая сортировка ### + def quick_sort(nums, left, right) + # Рекурсивно обрабатывать, пока длина подмассива не станет равной 1 + if left < right + # Разбиение с опорными указателями + pivot = partition(nums, left, right) + # Рекурсивно обработать левый и правый подмассивы + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +# ## Класс быстрой сортировки (оптимизация глубины рекурсии) ### +class QuickSortTailCall + class << self + # ## Разбиение с опорными указателями ### + def partition(nums, left, right) + # Использовать nums[left] как опорный элемент + i = left + j = right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # Идти справа налево в поисках первого элемента меньше опорного + end + while i < j && nums[i] <= nums[left] + i += 1 # Идти слева направо в поисках первого элемента больше опорного + end + # Обмен элементов + nums[i], nums[j] = nums[j], nums[i] + end + # Переместить опорный элемент на границу двух подмассивов + nums[i], nums[left] = nums[left], nums[i] + i # Вернуть индекс опорного элемента + end + + # ## Быстрая сортировка (оптимизация глубины рекурсии) ### + def quick_sort(nums, left, right) + # Рекурсивно обрабатывать, пока длина подмассива не станет равной 1 + while left < right + # Разбиение с опорными указателями + pivot = partition(nums, left, right) + # Выполнить быструю сортировку для более короткого из двух подмассивов + if pivot - left < right - pivot + quick_sort(nums, left, pivot - 1) + left = pivot + 1 # Оставшийся неотсортированный диапазон: [pivot + 1, right] + else + quick_sort(nums, pivot + 1, right) + right = pivot - 1 # Оставшийся неотсортированный диапазон: [left, pivot - 1] + end + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # Быстрая сортировка + nums = [2, 4, 1, 0, 3, 5] + QuickSort.quick_sort(nums, 0, nums.length - 1) + puts "После быстрой сортировки nums = #{nums}" + + # Быстрая сортировка (оптимизация медианным опорным элементом) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) + puts "После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = #{nums1}" + + # Быстрая сортировка (оптимизация глубины рекурсии) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) + puts "После быстрой сортировки (оптимизация глубины рекурсии) nums2 = #{nums2}" +end diff --git a/ru/codes/ruby/chapter_sorting/radix_sort.rb b/ru/codes/ruby/chapter_sorting/radix_sort.rb new file mode 100644 index 000000000..59a87ade5 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/radix_sort.rb @@ -0,0 +1,70 @@ +=begin +File: radix_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Получить k-й разряд элемента num, где exp = 10^(k-1) ### +def digit(num, exp) + # Передача exp вместо k позволяет избежать повторного выполнения дорогостоящих вычислений степени + (num / exp) % 10 +end + +# ## Сортировка подсчетом (сортировка по k-му разряду nums) ### +def counting_sort_digit(nums, exp) + # Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + counter = Array.new(10, 0) + n = nums.length + # Подсчитать число появлений каждой цифры от 0 до 9 + for i in 0...n + d = digit(nums[i], exp) # Получить k-й разряд nums[i], обозначив его как d + counter[d] += 1 # Подсчитать число появлений цифры d + end + # Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + (1...10).each { |i| counter[i] += counter[i - 1] } + # Выполняя обратный проход, заполнить res элементами по статистике в корзинах + res = Array.new(n, 0) + for i in (n - 1).downto(0) + d = digit(nums[i], exp) + j = counter[d] - 1 # Получить индекс j цифры d в массиве + res[j] = nums[i] # Поместить текущий элемент по индексу j + counter[d] -= 1 # Уменьшить количество d на 1 + end + # Перезаписать исходный массив nums результатом + (0...n).each { |i| nums[i] = res[i] } +end + +# ## Поразрядная сортировка ### +def radix_sort(nums) + # Получить максимальный элемент массива, чтобы определить максимальное число разрядов + m = nums.max + # Проходить разряды от младшего к старшему + exp = 1 + while exp <= m + # Выполнить сортировку подсчетом по k-му разряду элементов массива + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # то есть exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + end +end + +### Driver Code ### +if __FILE__ == $0 + # Поразрядная сортировка + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + puts "После поразрядной сортировки nums = #{nums}" +end diff --git a/ru/codes/ruby/chapter_sorting/selection_sort.rb b/ru/codes/ruby/chapter_sorting/selection_sort.rb new file mode 100644 index 000000000..64c8a9389 --- /dev/null +++ b/ru/codes/ruby/chapter_sorting/selection_sort.rb @@ -0,0 +1,29 @@ +=begin +File: selection_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Сортировка выбором ### +def selection_sort(nums) + n = nums.length + # Внешний цикл: неотсортированный диапазон [i, n-1] + for i in 0...(n - 1) + # Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + k = i + for j in (i + 1)...n + if nums[j] < nums[k] + k = j # Записать индекс минимального элемента + end + end + # Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + nums[i], nums[k] = nums[k], nums[i] + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + puts "После сортировки выбором nums = #{nums}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/array_deque.rb b/ru/codes/ruby/chapter_stack_and_queue/array_deque.rb new file mode 100644 index 000000000..ed889c9fd --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/array_deque.rb @@ -0,0 +1,145 @@ +=begin +File: array_deque.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Двусторонняя очередь на основе кольцевого массива ### +class ArrayDeque + # ## Получение длины двусторонней очереди ### + attr_reader :size + + # ## Конструктор ### + def initialize(capacity) + @nums = Array.new(capacity, 0) + @front = 0 + @size = 0 + end + + # ## Получить вместимость двусторонней очереди ### + def capacity + @nums.length + end + + # ## Проверка, пуста ли двусторонняя очередь ### + def is_empty? + size.zero? + end + + # ## Добавление в голову очереди ### + def push_first(num) + if size == capacity + puts 'Двусторонняя очередь заполнена' + return + end + + # Указатель головы сдвигается на одну позицию влево + # С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + @front = index(@front - 1) + # Добавить num в голову очереди + @nums[@front] = num + @size += 1 + end + + # ## Добавление в хвост очереди ### + def push_last(num) + if size == capacity + puts 'Двусторонняя очередь заполнена' + return + end + + # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + rear = index(@front + size) + # Добавить num в хвост очереди + @nums[rear] = num + @size += 1 + end + + # ## Извлечение из головы очереди ### + def pop_first + num = peek_first + # Указатель головы сдвигается на одну позицию назад + @front = index(@front + 1) + @size -= 1 + num + end + + # ## Извлечение из хвоста очереди ### + def pop_last + num = peek_last + @size -= 1 + num + end + + # ## Доступ к элементу в начале очереди ### + def peek_first + raise IndexError, 'двусторонняя очередь пуста' if is_empty? + + @nums[@front] + end + + # ## Доступ к элементу в хвосте очереди ### + def peek_last + raise IndexError, 'двусторонняя очередь пуста' if is_empty? + + # Вычислить индекс хвостового элемента + last = index(@front + size - 1) + @nums[last] + end + + # ## Вернуть массив для вывода ### + def to_array + # Преобразовывать только элементы списка в пределах фактической длины + res = [] + for i in 0...size + res << @nums[index(@front + i)] + end + res + end + + private + + # ## Вычислить индекс в кольцевом массиве ### + def index(i) + # С помощью операции взятия по модулю соединить начало и конец массива + # Когда i выходит за конец массива, он возвращается в начало + # Когда i выходит за начало массива, он возвращается в конец + (i + capacity) % capacity + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация двусторонней очереди + deque = ArrayDeque.new(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "Двусторонняя очередь deque = #{deque.to_array}" + + # Доступ к элементу + peek_first = deque.peek_first + puts "Первый элемент peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "Последний элемент peek_last = #{peek_last}" + + # Добавление элемента в очередь + deque.push_last(4) + puts "После добавления элемента 4 в хвост deque = #{deque.to_array}" + deque.push_first(1) + puts "После добавления элемента 1 в хвост deque = #{deque.to_array}" + + # Извлечение элемента из очереди + pop_last = deque.pop_last + puts "Извлечен элемент из хвоста = #{pop_last}, deque после извлечения из хвоста = #{deque.to_array}" + pop_first = deque.pop_first + puts "Извлечен элемент из головы = #{pop_first}, deque после извлечения из головы = #{deque.to_array}" + + # Получение длины двусторонней очереди + size = deque.size + puts "Длина двусторонней очереди size = #{size}" + + # Проверка, пуста ли двусторонняя очередь + is_empty = deque.is_empty? + puts "Пуста ли двусторонняя очередь = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/array_queue.rb b/ru/codes/ruby/chapter_stack_and_queue/array_queue.rb new file mode 100644 index 000000000..c930bac42 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/array_queue.rb @@ -0,0 +1,107 @@ +=begin +File: array_queue.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Очередь на основе кольцевого массива ### +class ArrayQueue + # ## Получение длины очереди ### + attr_reader :size + + # ## Конструктор ### + def initialize(size) + @nums = Array.new(size, 0) # Массив для хранения элементов очереди + @front = 0 # Указатель head, указывающий на первый элемент очереди + @size = 0 # Длина очереди + end + + # ## Получить вместимость очереди ### + def capacity + @nums.length + end + + # ## Проверка, пуста ли очередь ### + def is_empty? + size.zero? + end + + # ## Добавление в очередь ### + def push(num) + raise IndexError, 'очередь заполнена' if size == capacity + + # Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + # С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + rear = (@front + size) % capacity + # Добавить num в хвост очереди + @nums[rear] = num + @size += 1 + end + + # ## Извлечение из очереди ### + def pop + num = peek + # Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + @front = (@front + 1) % capacity + @size -= 1 + num + end + + # ## Доступ к элементу в начале очереди ### + def peek + raise IndexError, 'очередь пуста' if is_empty? + + @nums[@front] + end + + # ## Вернуть список для вывода ### + def to_array + res = Array.new(size, 0) + j = @front + + for i in 0...size + res[i] = @nums[j % capacity] + j += 1 + end + + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация очереди + queue = ArrayQueue.new(10) + + # Добавление элемента в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "Очередь queue = #{queue.to_array}" + + # Доступ к элементу в начале очереди + peek = queue.peek + puts "Первый элемент peek = #{peek}" + + # Извлечение элемента из очереди + pop = queue.pop + puts "Извлеченный элемент pop = #{pop}" + puts "queue после извлечения = #{queue.to_array}" + + # Получение длины очереди + size = queue.size + puts "Длина очереди size = #{size}" + + # Проверка, пуста ли очередь + is_empty = queue.is_empty? + puts "Пуста ли очередь = #{is_empty}" + + # Проверка кольцевого массива + for i in 0...10 + queue.push(i) + queue.pop + puts "После #{i}-го цикла enqueue + dequeue queue = #{queue.to_array}" + end +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/array_stack.rb b/ru/codes/ruby/chapter_stack_and_queue/array_stack.rb new file mode 100644 index 000000000..3d539bc78 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/array_stack.rb @@ -0,0 +1,78 @@ +=begin +File: array_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Стек на основе массива ### +class ArrayStack + # ## Конструктор ### + def initialize + @stack = [] + end + + # ## Получить длину стека ### + def size + @stack.length + end + + # ## Проверка, пуст ли стек ### + def is_empty? + @stack.empty? + end + + # ## Помещение в стек ### + def push(item) + @stack << item + end + + # ## Извлечение из стека ### + def pop + raise IndexError, 'стек пуст' if is_empty? + + @stack.pop + end + + # ## Доступ к верхнему элементу стека ### + def peek + raise IndexError, 'стек пуст' if is_empty? + + @stack.last + end + + # ## Вернуть список для вывода ### + def to_array + @stack + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация стека + stack = ArrayStack.new + + # Помещение элемента в стек + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "Стек stack = #{stack.to_array}" + + # Доступ к верхнему элементу стека + peek = stack.peek + puts "Верхний элемент peek = #{peek}" + + # Извлечение элемента из стека + pop = stack.pop + puts "Извлеченный элемент pop = #{pop}" + puts "stack после извлечения = #{stack.to_array}" + + # Получение длины стека + size = stack.size + puts "Длина стека size = #{size}" + + # Проверка на пустоту + is_empty = stack.is_empty? + puts "Пуст ли стек = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/deque.rb b/ru/codes/ruby/chapter_stack_and_queue/deque.rb new file mode 100644 index 000000000..f1ac4f729 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/deque.rb @@ -0,0 +1,42 @@ +=begin +File: deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # Инициализировать двустороннюю очередь + # В Ruby нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь + deque = [] + + # Элемент помещается в очередь + deque << 2 + deque << 5 + deque << 4 + # Обратите внимание: поскольку используется массив, временная сложность метода Array#unshift равна O(n) + deque.unshift(3) + deque.unshift(1) + puts "Двусторонняя очередь deque = #{deque}" + + # Доступ к элементу + peek_first = deque.first + puts "Первый элемент peek_first = #{peek_first}" + peek_last = deque.last + puts "Последний элемент peek_last = #{peek_last}" + + # Элемент извлекается из очереди + # Обратите внимание: поскольку используется массив, временная сложность метода Array#shift равна O(n) + pop_front = deque.shift + puts "Извлечен элемент из головы pop_front = #{pop_front}, deque после извлечения из головы = #{deque}" + pop_back = deque.pop + puts "Извлечен элемент из хвоста pop_back = #{pop_back}, deque после извлечения из хвоста = #{deque}" + + # Получение длины двусторонней очереди + size = deque.length + puts "Длина двусторонней очереди size = #{size}" + + # Проверка, пуста ли двусторонняя очередь + is_empty = size.zero? + puts "Пуста ли двусторонняя очередь = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb b/ru/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb new file mode 100644 index 000000000..c3396ca6a --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb @@ -0,0 +1,168 @@ +=begin +File: linkedlist_deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Узел двусвязного списка +class ListNode + attr_accessor :val + attr_accessor :next # Ссылка на узел-преемник + attr_accessor :prev # Ссылка на узел-предшественник + + # ## Конструктор ### + def initialize(val) + @val = val + end +end + +# ## Двусторонняя очередь на основе двусвязного списка ### +class LinkedListDeque + # ## Получение длины двусторонней очереди ### + attr_reader :size + + # ## Конструктор ### + def initialize + @front = nil # Головной узел front + @rear = nil # Хвостовой узел rear + @size = 0 # Длина двусторонней очереди + end + + # ## Проверка, пуста ли двусторонняя очередь ### + def is_empty? + size.zero? + end + + # ## Операция добавления в очередь ### + def push(num, is_front) + node = ListNode.new(num) + # Если связный список пуст, пусть front и rear оба указывают на node + if is_empty? + @front = @rear = node + # Операция добавления в голову очереди + elsif is_front + # Добавить node в голову списка + @front.prev = node + node.next = @front + @front = node # Обновить головной узел + # Операция добавления в хвост очереди + else + # Добавить node в хвост списка + @rear.next = node + node.prev = @rear + @rear = node # Обновить хвостовой узел + end + @size += 1 # Обновить длину очереди + end + + # ## Добавление в голову очереди ### + def push_first(num) + push(num, true) + end + + # ## Добавление в хвост очереди ### + def push_last(num) + push(num, false) + end + + # ## Операция извлечения из очереди ### + def pop(is_front) + raise IndexError, 'двусторонняя очередь пуста' if is_empty? + + # Операция извлечения из головы очереди + if is_front + val = @front.val # Временно сохранить значение головного узла + # Удалить головной узел + fnext = @front.next + unless fnext.nil? + fnext.prev = nil + @front.next = nil + end + @front = fnext # Обновить головной узел + # Операция извлечения из хвоста очереди + else + val = @rear.val # Временно сохранить значение хвостового узла + # Удалить хвостовой узел + rprev = @rear.prev + unless rprev.nil? + rprev.next = nil + @rear.prev = nil + end + @rear = rprev # Обновить хвостовой узел + end + @size -= 1 # Обновить длину очереди + + val + end + + # ## Извлечение из головы очереди ### + def pop_first + pop(true) + end + + # ## Извлечение из головы очереди ### + def pop_last + pop(false) + end + + # ## Доступ к элементу в начале очереди ### + def peek_first + raise IndexError, 'двусторонняя очередь пуста' if is_empty? + + @front.val + end + + # ## Доступ к элементу в хвосте очереди ### + def peek_last + raise IndexError, 'двусторонняя очередь пуста' if is_empty? + + @rear.val + end + + # ## Вернуть массив для вывода ### + def to_array + node = @front + res = Array.new(size, 0) + for i in 0...size + res[i] = node.val + node = node.next + end + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация двусторонней очереди + deque = LinkedListDeque.new + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "Двусторонняя очередь deque = #{deque.to_array}" + + # Доступ к элементу + peek_first = deque.peek_first + puts "Первый элемент peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "Последний элемент peek_last = #{peek_last}" + + # Добавление элемента в очередь + deque.push_last(4) + puts "После добавления элемента 4 в хвост deque = #{deque.to_array}" + deque.push_first(1) + puts "После добавления элемента 1 в голову deque = #{deque.to_array}" + + # Извлечение элемента из очереди + pop_last = deque.pop_last + puts "Извлечен элемент из хвоста = #{pop_last}, deque после извлечения из хвоста = #{deque.to_array}" + pop_first = deque.pop_first + puts "Извлечен элемент из головы = #{pop_first}, deque после извлечения из головы = #{deque.to_array}" + + # Получение длины двусторонней очереди + size = deque.size + puts "Длина двусторонней очереди size = #{size}" + + # Проверка, пуста ли двусторонняя очередь + is_empty = deque.is_empty? + puts "Пуста ли двусторонняя очередь = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb b/ru/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb new file mode 100644 index 000000000..e4a2522a4 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb @@ -0,0 +1,101 @@ +=begin +File: linkedlist_queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +# ## Очередь на основе связного списка ### +class LinkedListQueue + # ## Получение длины очереди ### + attr_reader :size + + # ## Конструктор ### + def initialize + @front = nil # Головной узел front + @rear = nil # Хвостовой узел rear + @size = 0 + end + + # ## Проверка, пуста ли очередь ### + def is_empty? + @front.nil? + end + + # ## Добавление в очередь ### + def push(num) + # Добавить num после хвостового узла + node = ListNode.new(num) + + # Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if @front.nil? + @front = node + @rear = node + # Если очередь не пуста, добавить этот узел после хвостового узла + else + @rear.next = node + @rear = node + end + + @size += 1 + end + + # ## Извлечение из очереди ### + def pop + num = peek + # Удалить головной узел + @front = @front.next + @size -= 1 + num + end + + # ## Доступ к элементу в начале очереди ### + def peek + raise IndexError, 'очередь пуста' if is_empty? + + @front.val + end + + # ## Преобразовать связный список в Array и вернуть ### + def to_array + queue = [] + temp = @front + while temp + queue << temp.val + temp = temp.next + end + queue + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация очереди + queue = LinkedListQueue.new + + # Элемент помещается в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "Очередь queue = #{queue.to_array}" + + # Доступ к элементу в начале очереди + peek = queue.peek + puts "Первый элемент front = #{peek}" + + # Извлечение элемента из очереди + pop_front = queue.pop + puts "Извлеченный элемент pop = #{pop_front}" + puts "queue после извлечения = #{queue.to_array}" + + # Получение длины очереди + size = queue.size + puts "Длина очереди size = #{size}" + + # Проверка, пуста ли очередь + is_empty = queue.is_empty? + puts "Пуста ли очередь = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb b/ru/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb new file mode 100644 index 000000000..2cf3165a0 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb @@ -0,0 +1,87 @@ +=begin +File: linkedlist_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +# ## Стек на основе связного списка ### +class LinkedListStack + attr_reader :size + + # ## Конструктор ### + def initialize + @size = 0 + end + + # ## Проверка, пуст ли стек ### + def is_empty? + @peek.nil? + end + + # ## Помещение в стек ### + def push(val) + node = ListNode.new(val) + node.next = @peek + @peek = node + @size += 1 + end + + # ## Извлечение из стека ### + def pop + num = peek + @peek = @peek.next + @size -= 1 + num + end + + # ## Доступ к верхнему элементу стека ### + def peek + raise IndexError, 'стек пуст' if is_empty? + + @peek.val + end + + # ## Преобразовать связный список в Array и вернуть ### + def to_array + arr = [] + node = @peek + while node + arr << node.val + node = node.next + end + arr.reverse + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация стека + stack = LinkedListStack.new + + # Помещение элемента в стек + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "Стек stack = #{stack.to_array}" + + # Доступ к верхнему элементу стека + peek = stack.peek + puts "Верхний элемент peek = #{peek}" + + # Извлечение элемента из стека + pop = stack.pop + puts "Извлеченный элемент pop = #{pop}" + puts "stack после извлечения = #{stack.to_array}" + + # Получение длины стека + size = stack.size + puts "Длина стека size = #{size}" + + # Проверка на пустоту + is_empty = stack.is_empty? + puts "Пуст ли стек = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/queue.rb b/ru/codes/ruby/chapter_stack_and_queue/queue.rb new file mode 100644 index 000000000..26ae2a9d3 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/queue.rb @@ -0,0 +1,38 @@ +=begin +File: queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # Инициализировать очередь + # Во встроенной очереди Ruby (Thread::Queue) нет методов peek и обхода, поэтому Array можно использовать как очередь + queue = [] + + # Добавление элемента в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "Очередь queue = #{queue}" + + # Обратиться к элементу очереди + peek = queue.first + puts "Первый элемент peek = #{peek}" + + # Элемент извлекается из очереди + # Обратите внимание: поскольку используется массив, временная сложность метода Array#shift равна O(n) + pop = queue.shift + puts "Извлеченный элемент pop = #{pop}" + puts "queue после извлечения = #{queue}" + + # Получение длины очереди + size = queue.length + puts "Длина очереди size = #{size}" + + # Проверка, пуста ли очередь + is_empty = queue.empty? + puts "Пуста ли очередь = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_stack_and_queue/stack.rb b/ru/codes/ruby/chapter_stack_and_queue/stack.rb new file mode 100644 index 000000000..67910a1b3 --- /dev/null +++ b/ru/codes/ruby/chapter_stack_and_queue/stack.rb @@ -0,0 +1,37 @@ +=begin +File: stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # Инициализировать стек + # В Ruby нет встроенного класса стека, поэтому Array можно использовать как стек + stack = [] + + # Помещение элемента в стек + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + puts "Стек stack = #{stack}" + + # Доступ к верхнему элементу стека + peek = stack.last + puts "Верхний элемент peek = #{peek}" + + # Извлечение элемента из стека + pop = stack.pop + puts "Извлеченный элемент pop = #{pop}" + puts "stack после извлечения = #{stack}" + + # Получение длины стека + size = stack.length + puts "Длина стека size = #{size}" + + # Проверка на пустоту + is_empty = stack.empty? + puts "Пуст ли стек = #{is_empty}" +end diff --git a/ru/codes/ruby/chapter_tree/array_binary_tree.rb b/ru/codes/ruby/chapter_tree/array_binary_tree.rb new file mode 100644 index 000000000..f9c800d2a --- /dev/null +++ b/ru/codes/ruby/chapter_tree/array_binary_tree.rb @@ -0,0 +1,124 @@ +=begin +File: array_binary_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Класс двоичного дерева в массивном представлении ### +class ArrayBinaryTree + # ## Конструктор ### + def initialize(arr) + @tree = arr.to_a + end + + # ## Вместимость списка ### + def size + @tree.length + end + + # ## Получить значение узла с индексом i ### + def val(i) + # Если индекс выходит за границы, вернуть nil, обозначающий пустую ячейку + return if i < 0 || i >= size + + @tree[i] + end + + # ## Получить индекс левого дочернего узла узла с индексом i ### + def left(i) + 2 * i + 1 + end + + # ## Получить индекс правого дочернего узла узла с индексом i ### + def right(i) + 2 * i + 2 + end + + # ## Получить индекс родительского узла узла с индексом i ### + def parent(i) + (i - 1) / 2 + end + + # ## Обход в ширину ### + def level_order + @res = [] + + # Непосредственно обходить массив + for i in 0...size + @res << val(i) unless val(i).nil? + end + + @res + end + + # ## Обход в глубину ### + def dfs(i, order) + return if val(i).nil? + # Предварительный обход + @res << val(i) if order == :pre + dfs(left(i), order) + # Симметричный обход + @res << val(i) if order == :in + dfs(right(i), order) + # Обратный обход + @res << val(i) if order == :post + end + + # ## Предварительный обход ### + def pre_order + @res = [] + dfs(0, :pre) + @res + end + + # ## Симметричный обход ### + def in_order + @res = [] + dfs(0, :in) + @res + end + + # ## Обратный обход ### + def post_order + @res = [] + dfs(0, :post) + @res + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализировать двоичное дерево + # Здесь используется функция, напрямую строящая двоичное дерево из массива + arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + root = arr_to_tree(arr) + puts "\nИнициализация двоичного дерева\n\n" + puts 'Массивное представление двоичного дерева:' + pp arr + puts 'Связное представление двоичного дерева:' + print_tree(root) + + # Класс двоичного дерева в массивном представлении + abt = ArrayBinaryTree.new(arr) + + # Доступ к узлу + i = 1 + l, r, _p = abt.left(i), abt.right(i), abt.parent(i) + puts "\nИндекс текущего узла = #{i}, значение = #{abt.val(i).inspect}" + puts "Индекс его левого дочернего узла = #{l}, значение = #{abt.val(l).inspect}" + puts "Индекс его правого дочернего узла = #{r}, значение = #{abt.val(r).inspect}" + puts "Индекс его родительского узла = #{_p}, значение = #{abt.val(_p).inspect}" + + # Обходить дерево + res = abt.level_order + puts "\nОбход в ширину: #{res}" + res = abt.pre_order + puts "Предварительный обход: #{res}" + res = abt.in_order + puts "Симметричный обход: #{res}" + res = abt.post_order + puts "Обратный обход: #{res}" +end diff --git a/ru/codes/ruby/chapter_tree/avl_tree.rb b/ru/codes/ruby/chapter_tree/avl_tree.rb new file mode 100644 index 000000000..16f01db66 --- /dev/null +++ b/ru/codes/ruby/chapter_tree/avl_tree.rb @@ -0,0 +1,216 @@ +=begin +File: avl_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## AVL-дерево ### +class AVLTree + # ## Конструктор ### + def initialize + @root = nil + end + + # ## Получение корневого узла двоичного дерева ### + def get_root + @root + end + + # ## Получить высоту узла ### + def height(node) + # Высота пустого узла равна -1, высота листового узла равна 0 + return node.height unless node.nil? + + -1 + end + + # ## Обновить высоту узла ### + def update_height(node) + # Высота узла равна высоте более высокого поддерева + 1 + node.height = [height(node.left), height(node.right)].max + 1 + end + + # ## Получить коэффициент баланса ### + def balance_factor(node) + # Коэффициент баланса пустого узла равен 0 + return 0 if node.nil? + + # Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + height(node.left) - height(node.right) + end + + # ## Операция правого вращения ### + def right_rotate(node) + child = node.left + grand_child = child.right + # Выполнить правое вращение узла node вокруг child + child.right = node + node.left = grand_child + # Обновить высоту узла + update_height(node) + update_height(child) + # Вернуть корневой узел поддерева после вращения + child + end + + # ## Операция левого вращения ### + def left_rotate(node) + child = node.right + grand_child = child.left + # Выполнить левое вращение узла node вокруг child + child.left = node + node.right = grand_child + # Обновить высоту узла + update_height(node) + update_height(child) + # Вернуть корневой узел поддерева после вращения + child + end + + # ## Выполнить вращение, чтобы снова сбалансировать поддерево ### + def rotate(node) + # Получить коэффициент баланса узла node + balance_factor = balance_factor(node) + # Обойти левое поддерево + if balance_factor > 1 + if balance_factor(node.left) >= 0 + # Правое вращение + return right_rotate(node) + else + # Сначала левое вращение, затем правое + node.left = left_rotate(node.left) + return right_rotate(node) + end + # Правостороннее дерево обхода + elsif balance_factor < -1 + if balance_factor(node.right) <= 0 + # Левое вращение + return left_rotate(node) + else + # Сначала правое вращение, затем левое + node.right = right_rotate(node.right) + return left_rotate(node) + end + end + # Дерево сбалансировано, вращение не требуется, вернуть сразу + node + end + + # ## Вставка узла ### + def insert(val) + @root = insert_helper(@root, val) + end + + # ## Рекурсивная вставка узла (вспомогательный метод) ### + def insert_helper(node, val) + return TreeNode.new(val) if node.nil? + # 1. Найти позицию вставки и вставить узел + if val < node.val + node.left = insert_helper(node.left, val) + elsif val > node.val + node.right = insert_helper(node.right, val) + else + # Повторяющийся узел не вставлять, сразу вернуть + return node + end + # Обновить высоту узла + update_height(node) + # 2. Выполнить вращение, чтобы снова сбалансировать поддерево + rotate(node) + end + + # ## Удаление узла ### + def remove(val) + @root = remove_helper(@root, val) + end + + # ## Рекурсивное удаление узла (вспомогательный метод) ### + def remove_helper(node, val) + return if node.nil? + # 1. Найти узел и удалить его + if val < node.val + node.left = remove_helper(node.left, val) + elsif val > node.val + node.right = remove_helper(node.right, val) + else + if node.left.nil? || node.right.nil? + child = node.left || node.right + # Число дочерних узлов = 0, удалить node и сразу вернуть + return if child.nil? + # Число дочерних узлов = 1, удалить node напрямую + node = child + else + # Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + temp = node.right + while !temp.left.nil? + temp = temp.left + end + node.right = remove_helper(node.right, temp.val) + node.val = temp.val + end + end + # Обновить высоту узла + update_height(node) + # 2. Выполнить вращение, чтобы снова сбалансировать поддерево + rotate(node) + end + + # ## Поиск узла ### + def search(val) + cur = @root + # Искать в цикле и выйти после прохода за листовой узел + while !cur.nil? + # Целевой узел находится в правом поддереве cur + if cur.val < val + cur = cur.right + # Целевой узел находится в левом поддереве cur + elsif cur.val > val + cur = cur.left + # Найти целевой узел и выйти из цикла + else + break + end + end + # Вернуть целевой узел + cur + end +end + +### Driver Code ### +if __FILE__ == $0 + def test_insert(tree, val) + tree.insert(val) + puts "\nAVL-дерево после вставки узла #{val}:" + print_tree(tree.get_root) + end + + def test_remove(tree, val) + tree.remove(val) + puts "\nAVL-дерево после удаления узла #{val}:" + print_tree(tree.get_root) + end + + # Инициализация пустого AVL-дерева + avl_tree = AVLTree.new + + # Вставка узла + # Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] + test_insert(avl_tree, val) + end + + # Вставка повторяющегося узла + test_insert(avl_tree, 7) + + # Удаление узла + # Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + test_remove(avl_tree, 8) # Удаление узла степени 0 + test_remove(avl_tree, 5) # Удаление узла степени 1 + test_remove(avl_tree, 4) # Удаление узла степени 2 + + result_node = avl_tree.search(7) + puts "\nНайденный объект узла = #{result_node}, значение узла = #{result_node.val}" +end diff --git a/ru/codes/ruby/chapter_tree/binary_search_tree.rb b/ru/codes/ruby/chapter_tree/binary_search_tree.rb new file mode 100644 index 000000000..ea7d0ea53 --- /dev/null +++ b/ru/codes/ruby/chapter_tree/binary_search_tree.rb @@ -0,0 +1,161 @@ +=begin +File: binary_search_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Двоичное дерево поиска ### +class BinarySearchTree + # ## Конструктор ### + def initialize + # Инициализировать пустое дерево + @root = nil + end + + # ## Получение корневого узла двоичного дерева ### + def get_root + @root + end + + # ## Поиск узла ### + def search(num) + cur = @root + + # Искать в цикле и выйти после прохода за листовой узел + while !cur.nil? + # Целевой узел находится в правом поддереве cur + if cur.val < num + cur = cur.right + # Целевой узел находится в левом поддереве cur + elsif cur.val > num + cur = cur.left + # Найти целевой узел и выйти из цикла + else + break + end + end + + cur + end + + # ## Вставка узла ### + def insert(num) + # Если дерево пусто, инициализировать корневой узел + if @root.nil? + @root = TreeNode.new(num) + return + end + + # Искать в цикле и выйти после прохода за листовой узел + cur, pre = @root, nil + while !cur.nil? + # Найти повторяющийся узел и сразу вернуть + return if cur.val == num + + pre = cur + # Позиция вставки находится в правом поддереве cur + if cur.val < num + cur = cur.right + # Позиция вставки находится в левом поддереве cur + else + cur = cur.left + end + end + + # Вставка узла + node = TreeNode.new(num) + if pre.val < num + pre.right = node + else + pre.left = node + end + end + + # ## Удаление узла ### + def remove(num) + # Если дерево пусто, сразу вернуть + return if @root.nil? + + # Искать в цикле и выйти после прохода за листовой узел + cur, pre = @root, nil + while !cur.nil? + # Найти узел для удаления и выйти из цикла + break if cur.val == num + + pre = cur + # Узел для удаления находится в правом поддереве cur + if cur.val < num + cur = cur.right + # Узел для удаления находится в левом поддереве cur + else + cur = cur.left + end + end + # Если узел для удаления отсутствует, сразу вернуть + return if cur.nil? + + # Число дочерних узлов = 0 или 1 + if cur.left.nil? || cur.right.nil? + # Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + child = cur.left || cur.right + # Удалить узел cur + if cur != @root + if pre.left == cur + pre.left = child + else + pre.right = child + end + else + # Если удаляемый узел является корнем, заново назначить корневой узел + @root = child + end + # Число дочерних узлов = 2 + else + # Получить следующий узел после cur в симметричном обходе + tmp = cur.right + while !tmp.left.nil? + tmp = tmp.left + end + # Рекурсивно удалить узел tmp + remove(tmp.val) + # Перезаписать cur значением tmp + cur.val = tmp.val + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализация двоичного дерева поиска + bst = BinarySearchTree.new + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + nums.each { |num| bst.insert(num) } + puts "\nИсходное двоичное дерево\n" + print_tree(bst.get_root) + + # Поиск узла + node = bst.search(7) + puts "\nНайденный объект узла: #{node}, значение узла = #{node.val}" + + # Вставка узла + bst.insert(16) + puts "\nПосле вставки узла 16 двоичное дерево имеет вид\n" + print_tree(bst.get_root) + + # Удаление узла + bst.remove(1) + puts "\nПосле удаления узла 1 двоичное дерево имеет вид\n" + print_tree(bst.get_root) + + bst.remove(2) + puts "\nПосле удаления узла 2 двоичное дерево имеет вид\n" + print_tree(bst.get_root) + + bst.remove(4) + puts "\nПосле удаления узла 4 двоичное дерево имеет вид\n" + print_tree(bst.get_root) +end diff --git a/ru/codes/ruby/chapter_tree/binary_tree.rb b/ru/codes/ruby/chapter_tree/binary_tree.rb new file mode 100644 index 000000000..196916137 --- /dev/null +++ b/ru/codes/ruby/chapter_tree/binary_tree.rb @@ -0,0 +1,38 @@ +=begin +File: binary_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # Инициализация двоичного дерева + # Инициализация узлов + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # Построить связи между узлами (указатели) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + puts "\nИнициализация двоичного дерева\n\n" + print_tree(n1) + + # Вставка и удаление узлов + _p = TreeNode.new(0) + # Вставить узел _p между n1 -> n2 + n1.left = _p + _p.left = n2 + puts "\nПосле вставки узла _p\n\n" + print_tree(n1) + # Удаление узла + n1.left = n2 + puts "\nПосле удаления узла _p\n\n" + print_tree(n1) +end diff --git a/ru/codes/ruby/chapter_tree/binary_tree_bfs.rb b/ru/codes/ruby/chapter_tree/binary_tree_bfs.rb new file mode 100644 index 000000000..6668213f6 --- /dev/null +++ b/ru/codes/ruby/chapter_tree/binary_tree_bfs.rb @@ -0,0 +1,36 @@ +=begin +File: binary_tree_bfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Обход в ширину ### +def level_order(root) + # Инициализировать очередь и добавить корневой узел + queue = [root] + # Инициализировать список для хранения последовательности обхода + res = [] + while !queue.empty? + node = queue.shift # Извлечение из очереди + res << node.val # Сохранить значение узла + queue << node.left unless node.left.nil? # Поместить левый дочерний узел в очередь + queue << node.right unless node.right.nil? # Поместить правый дочерний узел в очередь + end + res +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализировать двоичное дерево + # Здесь используется функция, напрямую строящая двоичное дерево из массива + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\nИнициализация двоичного дерева\n\n" + print_tree(root) + + # Обход в ширину + res = level_order(root) + puts "\nПоследовательность печати узлов при обходе в ширину = #{res}" +end diff --git a/ru/codes/ruby/chapter_tree/binary_tree_dfs.rb b/ru/codes/ruby/chapter_tree/binary_tree_dfs.rb new file mode 100644 index 000000000..dce4ba09c --- /dev/null +++ b/ru/codes/ruby/chapter_tree/binary_tree_dfs.rb @@ -0,0 +1,62 @@ +=begin +File: binary_tree_dfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +# ## Предварительный обход ### +def pre_order(root) + return if root.nil? + + # Порядок обхода: корень -> левое поддерево -> правое поддерево + $res << root.val + pre_order(root.left) + pre_order(root.right) +end + +# ## Симметричный обход ### +def in_order(root) + return if root.nil? + + # Порядок обхода: левое поддерево -> корень -> правое поддерево + in_order(root.left) + $res << root.val + in_order(root.right) +end + +# ## Обратный обход ### +def post_order(root) + return if root.nil? + + # Порядок обхода: левое поддерево -> правое поддерево -> корень + post_order(root.left) + post_order(root.right) + $res << root.val +end + +### Driver Code ### +if __FILE__ == $0 + # Инициализировать двоичное дерево + # Здесь используется функция, напрямую строящая двоичное дерево из массива + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\nИнициализация двоичного дерева\n\n" + print_tree(root) + + # Предварительный обход + $res = [] + pre_order(root) + puts "\nПоследовательность печати узлов при предварительном обходе = #{$res}" + + # Симметричный обход + $res.clear + in_order(root) + puts "\nПоследовательность печати узлов при симметричном обходе = #{$res}" + + # Обратный обход + $res.clear + post_order(root) + puts "\nПоследовательность печати узлов при обратном обходе = #{$res}" +end diff --git a/ru/codes/ruby/test_all.rb b/ru/codes/ruby/test_all.rb new file mode 100644 index 000000000..a4d417800 --- /dev/null +++ b/ru/codes/ruby/test_all.rb @@ -0,0 +1,23 @@ +require 'open3' + +start_time = Time.now +ruby_code_dir = File.dirname(__FILE__) +files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") + +errors = [] + +files.each do |file| + stdout, stderr, status = Open3.capture3("ruby #{file}") + errors << stderr unless status.success? +end + +puts "\x1b[34mTested #{files.count} files\x1b[m" + +unless errors.empty? + puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" + raise errors.join("\n\n") +else + puts "\x1b[32mPASS\x1b[m" +end + +puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" diff --git a/ru/codes/ruby/utils/list_node.rb b/ru/codes/ruby/utils/list_node.rb new file mode 100644 index 000000000..16ca3b8ba --- /dev/null +++ b/ru/codes/ruby/utils/list_node.rb @@ -0,0 +1,38 @@ +=begin +File: list_node.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Класс узла связного списка ### +class ListNode + attr_accessor :val # Значение узла + attr_accessor :next # Ссылка на следующий узел + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end +end + +# ## Десериализация списка в связный список ### +def arr_to_linked_list(arr) + head = current = ListNode.new(arr[0]) + + for i in 1...arr.length + current.next = ListNode.new(arr[i]) + current = current.next + end + + head +end + +# ## Сериализация связного списка в список ### +def linked_list_to_arr(head) + arr = [] + + while head + arr << head.val + head = head.next + end +end diff --git a/ru/codes/ruby/utils/print_util.rb b/ru/codes/ruby/utils/print_util.rb new file mode 100644 index 000000000..115ac9b9d --- /dev/null +++ b/ru/codes/ruby/utils/print_util.rb @@ -0,0 +1,80 @@ +=begin +File: print_util.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative "./tree_node" + +# ## Вывести матрицу ### +def print_matrix(mat) + s = [] + mat.each { |arr| s << " #{arr.to_s}" } + puts "[\n#{s.join(",\n")}\n]" +end + +# ## Вывести связный список ### +def print_linked_list(head) + list = [] + while head + list << head.val + head = head.next + end + puts "#{list.join(" -> ")}" +end + +class Trunk + attr_accessor :prev, :str + + def initialize(prev, str) + @prev = prev + @str = str + end +end + +def show_trunk(p) + return if p.nil? + + show_trunk(p.prev) + print p.str +end + +# ## Вывести двоичное дерево ### +# Этот вывод дерева заимствован из TECHIE DELIGHT +# https://www.techiedelight.com/c-program-print-binary-tree/ +def print_tree(root, prev=nil, is_right=false) + return if root.nil? + + prev_str = " " + trunk = Trunk.new(prev, prev_str) + print_tree(root.right, trunk, true) + + if prev.nil? + trunk.str = "———" + elsif is_right + trunk.str = "/———" + prev_str = " |" + else + trunk.str = "\\———" + prev.str = prev_str + end + + show_trunk(trunk) + puts " #{root.val}" + prev.str = prev_str if prev + trunk.str = " |" + print_tree(root.left, trunk, false) +end + +# ## Вывести хеш-таблицу ### +def print_hash_map(hmap) + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } +end + +# ## Вывести кучу ### +def print_heap(heap) + puts "Массивное представление кучи:#{heap}" + puts "Древовидное представление кучи:" + root = arr_to_tree(heap) + print_tree(root) +end diff --git a/ru/codes/ruby/utils/tree_node.rb b/ru/codes/ruby/utils/tree_node.rb new file mode 100644 index 000000000..68d3e5340 --- /dev/null +++ b/ru/codes/ruby/utils/tree_node.rb @@ -0,0 +1,53 @@ +=begin +File: tree_node.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Класс узла двоичного дерева ### +class TreeNode + attr_accessor :val # Значение узла + attr_accessor :height # Высота узла + attr_accessor :left # Ссылка на левый дочерний узел + attr_accessor :right # Ссылка на правый дочерний узел + + def initialize(val=0) + @val = val + @height = 0 + end +end + +# ## Десериализация списка в двоичное дерево: рекурсия ### +def arr_to_tree_dfs(arr, i) + # Если индекс выходит за длину массива или соответствующий элемент равен nil, вернуть nil + return if i < 0 || i >= arr.length || arr[i].nil? + # Построить текущий узел + root = TreeNode.new(arr[i]) + # Рекурсивно построить левое и правое поддеревья + root.left = arr_to_tree_dfs(arr, 2 * i + 1) + root.right = arr_to_tree_dfs(arr, 2 * i + 2) + root +end + +# ## Десериализация списка в двоичное дерево ### +def arr_to_tree(arr) + arr_to_tree_dfs(arr, 0) +end + +# ## Сериализация двоичного дерева в список: рекурсия ### +def tree_to_arr_dfs(root, i, res) + return if root.nil? + + res += Array.new(i - res.length + 1) if i >= res.length + res[i] = root.val + + tree_to_arr_dfs(root.left, 2 * i + 1, res) + tree_to_arr_dfs(root.right, 2 * i + 2, res) +end + +# ## Сериализация двоичного дерева в список ### +def tree_to_arr(root) + res = [] + tree_to_arr_dfs(root, 0, res) + res +end diff --git a/ru/codes/ruby/utils/vertex.rb b/ru/codes/ruby/utils/vertex.rb new file mode 100644 index 000000000..3596ffdc9 --- /dev/null +++ b/ru/codes/ruby/utils/vertex.rb @@ -0,0 +1,24 @@ +=begin +File: vertex.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## Класс вершины ### +class Vertex + attr_accessor :val + + def initialize(val) + @val = val + end +end + +# ## На вход подается список значений vals, на выходе возвращается список вершин vets ### +def vals_to_vets(vals) + Array.new(vals.length) { |i| Vertex.new(vals[i]) } +end + +# ## На вход подается список вершин vets, на выходе возвращается список значений vals ### +def vets_to_vals(vets) + Array.new(vets.length) { |i| vets[i].val } +end diff --git a/ru/codes/rust/.gitignore b/ru/codes/rust/.gitignore new file mode 100644 index 000000000..447098846 --- /dev/null +++ b/ru/codes/rust/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/ru/codes/rust/Cargo.toml b/ru/codes/rust/Cargo.toml new file mode 100644 index 000000000..160f9134c --- /dev/null +++ b/ru/codes/rust/Cargo.toml @@ -0,0 +1,413 @@ +[package] +name = "hello-algo-rust" +version = "0.1.0" +edition = "2021" +publish = false + +# Run Command: cargo run --bin time_complexity +[[bin]] +name = "time_complexity" +path = "chapter_computational_complexity/time_complexity.rs" + +# Run Command: cargo run --bin worst_best_time_complexity +[[bin]] +name = "worst_best_time_complexity" +path = "chapter_computational_complexity/worst_best_time_complexity.rs" + +# Run Command: cargo run --bin space_complexity +[[bin]] +name = "space_complexity" +path = "chapter_computational_complexity/space_complexity.rs" + +# Run Command: cargo run --bin iteration +[[bin]] +name = "iteration" +path = "chapter_computational_complexity/iteration.rs" + +# Run Command: cargo run --bin recursion +[[bin]] +name = "recursion" +path = "chapter_computational_complexity/recursion.rs" + +# Run Command: cargo run --bin two_sum +[[bin]] +name = "two_sum" +path = "chapter_searching/two_sum.rs" + +# Run Command: cargo run --bin array +[[bin]] +name = "array" +path = "chapter_array_and_linkedlist/array.rs" + +# Run Command: cargo run --bin linked_list +[[bin]] +name = "linked_list" +path = "chapter_array_and_linkedlist/linked_list.rs" + +# Run Command: cargo run --bin list +[[bin]] +name = "list" +path = "chapter_array_and_linkedlist/list.rs" + +# Run Command: cargo run --bin my_list +[[bin]] +name = "my_list" +path = "chapter_array_and_linkedlist/my_list.rs" + +# Run Command: cargo run --bin stack +[[bin]] +name = "stack" +path = "chapter_stack_and_queue/stack.rs" + +# Run Command: cargo run --bin linkedlist_stack +[[bin]] +name = "linkedlist_stack" +path = "chapter_stack_and_queue/linkedlist_stack.rs" + +# Run Command: cargo run --bin queue +[[bin]] +name = "queue" +path = "chapter_stack_and_queue/queue.rs" + +# Run Command: cargo run --bin linkedlist_queue +[[bin]] +name = "linkedlist_queue" +path = "chapter_stack_and_queue/linkedlist_queue.rs" + +# Run Command: cargo run --bin deque +[[bin]] +name = "deque" +path = "chapter_stack_and_queue/deque.rs" + +# Run Command: cargo run --bin array_deque +[[bin]] +name = "array_deque" +path = "chapter_stack_and_queue/array_deque.rs" + +# Run Command: cargo run --bin linkedlist_deque +[[bin]] +name = "linkedlist_deque" +path = "chapter_stack_and_queue/linkedlist_deque.rs" + +# Run Command: cargo run --bin simple_hash +[[bin]] +name = "simple_hash" +path = "chapter_hashing/simple_hash.rs" + +# Run Command: cargo run --bin hash_map +[[bin]] +name = "hash_map" +path = "chapter_hashing/hash_map.rs" + +# Run Command: cargo run --bin array_hash_map +[[bin]] +name = "array_hash_map" +path = "chapter_hashing/array_hash_map.rs" + +# Run Command: cargo run --bin build_in_hash +[[bin]] +name = "build_in_hash" +path = "chapter_hashing/build_in_hash.rs" + +# Run Command: cargo run --bin hash_map_chaining +[[bin]] +name = "hash_map_chaining" +path = "chapter_hashing/hash_map_chaining.rs" + +# Run Command: cargo run --bin hash_map_open_addressing +[[bin]] +name = "hash_map_open_addressing" +path = "chapter_hashing/hash_map_open_addressing.rs" + +# Run Command: cargo run --bin binary_search +[[bin]] +name = "binary_search" +path = "chapter_searching/binary_search.rs" + +# Run Command: cargo run --bin binary_search_edge +[[bin]] +name = "binary_search_edge" +path = "chapter_searching/binary_search_edge.rs" + +# Run Command: cargo run --bin binary_search_insertion +[[bin]] +name = "binary_search_insertion" +path = "chapter_searching/binary_search_insertion.rs" + +# Run Command: cargo run --bin bubble_sort +[[bin]] +name = "bubble_sort" +path = "chapter_sorting/bubble_sort.rs" + +# Run Command: cargo run --bin insertion_sort +[[bin]] +name = "insertion_sort" +path = "chapter_sorting/insertion_sort.rs" + +# Run Command: cargo run --bin quick_sort +[[bin]] +name = "quick_sort" +path = "chapter_sorting/quick_sort.rs" + +# Run Command: cargo run --bin merge_sort +[[bin]] +name = "merge_sort" +path = "chapter_sorting/merge_sort.rs" + +# Run Command: cargo run --bin selection_sort +[[bin]] +name = "selection_sort" +path = "chapter_sorting/selection_sort.rs" + +# Run Command: cargo run --bin bucket_sort +[[bin]] +name = "bucket_sort" +path = "chapter_sorting/bucket_sort.rs" + +# Run Command: cargo run --bin heap_sort +[[bin]] +name = "heap_sort" +path = "chapter_sorting/heap_sort.rs" + +# Run Command: cargo run --bin counting_sort +[[bin]] +name = "counting_sort" +path = "chapter_sorting/counting_sort.rs" + +# Run Command: cargo run --bin radix_sort +[[bin]] +name = "radix_sort" +path = "chapter_sorting/radix_sort.rs" + +# Run Command: cargo run --bin array_stack +[[bin]] +name = "array_stack" +path = "chapter_stack_and_queue/array_stack.rs" + +# Run Command: cargo run --bin array_queue +[[bin]] +name = "array_queue" +path = "chapter_stack_and_queue/array_queue.rs" + +# Run Command: cargo run --bin array_binary_tree +[[bin]] +name = "array_binary_tree" +path = "chapter_tree/array_binary_tree.rs" + +# Run Command: cargo run --bin avl_tree +[[bin]] +name = "avl_tree" +path = "chapter_tree/avl_tree.rs" + +# Run Command: cargo run --bin binary_search_tree +[[bin]] +name = "binary_search_tree" +path = "chapter_tree/binary_search_tree.rs" + +# Run Command: cargo run --bin binary_tree_bfs +[[bin]] +name = "binary_tree_bfs" +path = "chapter_tree/binary_tree_bfs.rs" + +# Run Command: cargo run --bin binary_tree_dfs +[[bin]] +name = "binary_tree_dfs" +path = "chapter_tree/binary_tree_dfs.rs" + +# Run Command: cargo run --bin binary_tree +[[bin]] +name = "binary_tree" +path = "chapter_tree/binary_tree.rs" + +# Run Command: cargo run --bin heap +[[bin]] +name = "heap" +path = "chapter_heap/heap.rs" + +# Run Command: cargo run --bin my_heap +[[bin]] +name = "my_heap" +path = "chapter_heap/my_heap.rs" + +# Run Command: cargo run --bin top_k +[[bin]] +name = "top_k" +path = "chapter_heap/top_k.rs" + +# Run Command: cargo run --bin graph_adjacency_list +[[bin]] +name = "graph_adjacency_list" +path = "chapter_graph/graph_adjacency_list.rs" + +# Run Command: cargo run --bin graph_adjacency_matrix +[[bin]] +name = "graph_adjacency_matrix" +path = "chapter_graph/graph_adjacency_matrix.rs" + +# Run Command: cargo run --bin graph_bfs +[[bin]] +name = "graph_bfs" +path = "chapter_graph/graph_bfs.rs" + +# Run Command: cargo run --bin graph_dfs +[[bin]] +name = "graph_dfs" +path = "chapter_graph/graph_dfs.rs" + +# Run Command: cargo run --bin linear_search +[[bin]] +name = "linear_search" +path = "chapter_searching/linear_search.rs" + +# Run Command: cargo run --bin hashing_search +[[bin]] +name = "hashing_search" +path = "chapter_searching/hashing_search.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs +[[bin]] +name = "climbing_stairs_dfs" +path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs_mem +[[bin]] +name = "climbing_stairs_dfs_mem" +path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" + +# Run Command: cargo run --bin climbing_stairs_dp +[[bin]] +name = "climbing_stairs_dp" +path = "chapter_dynamic_programming/climbing_stairs_dp.rs" + +# Run Command: cargo run --bin min_cost_climbing_stairs_dp +[[bin]] +name = "min_cost_climbing_stairs_dp" +path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_constraint_dp +[[bin]] +name = "climbing_stairs_constraint_dp" +path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_backtrack +[[bin]] +name = "climbing_stairs_backtrack" +path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" + +# Run Command: cargo run --bin subset_sum_i_naive +[[bin]] +name = "subset_sum_i_naive" +path = "chapter_backtracking/subset_sum_i_naive.rs" + +# Run Command: cargo run --bin subset_sum_i +[[bin]] +name = "subset_sum_i" +path = "chapter_backtracking/subset_sum_i.rs" + +# Run Command: cargo run --bin subset_sum_ii +[[bin]] +name = "subset_sum_ii" +path = "chapter_backtracking/subset_sum_ii.rs" + +# Run Command: cargo run --bin coin_change +[[bin]] +name = "coin_change" +path = "chapter_dynamic_programming/coin_change.rs" + +# Run Command: cargo run --bin coin_change_ii +[[bin]] +name = "coin_change_ii" +path = "chapter_dynamic_programming/coin_change_ii.rs" + +# Run Command: cargo run --bin unbounded_knapsack +[[bin]] +name = "unbounded_knapsack" +path = "chapter_dynamic_programming/unbounded_knapsack.rs" + +# Run Command: cargo run --bin knapsack +[[bin]] +name = "knapsack" +path = "chapter_dynamic_programming/knapsack.rs" + +# Run Command: cargo run --bin min_path_sum +[[bin]] +name = "min_path_sum" +path = "chapter_dynamic_programming/min_path_sum.rs" + +# Run Command: cargo run --bin edit_distance +[[bin]] +name = "edit_distance" +path = "chapter_dynamic_programming/edit_distance.rs" + +# Run Command: cargo run --bin n_queens +[[bin]] +name = "n_queens" +path = "chapter_backtracking/n_queens.rs" + +# Run Command: cargo run --bin permutations_i +[[bin]] +name = "permutations_i" +path = "chapter_backtracking/permutations_i.rs" + +# Run Command: cargo run --bin permutations_ii +[[bin]] +name = "permutations_ii" +path = "chapter_backtracking/permutations_ii.rs" + +# Run Command: cargo run --bin preorder_traversal_i_compact +[[bin]] +name = "preorder_traversal_i_compact" +path = "chapter_backtracking/preorder_traversal_i_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_ii_compact +[[bin]] +name = "preorder_traversal_ii_compact" +path = "chapter_backtracking/preorder_traversal_ii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_compact +[[bin]] +name = "preorder_traversal_iii_compact" +path = "chapter_backtracking/preorder_traversal_iii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_template +[[bin]] +name = "preorder_traversal_iii_template" +path = "chapter_backtracking/preorder_traversal_iii_template.rs" + +# Run Command: cargo run --bin binary_search_recur +[[bin]] +name = "binary_search_recur" +path = "chapter_divide_and_conquer/binary_search_recur.rs" + +# Run Command: cargo run --bin hanota +[[bin]] +name = "hanota" +path = "chapter_divide_and_conquer/hanota.rs" + +# Run Command: cargo run --bin build_tree +[[bin]] +name = "build_tree" +path = "chapter_divide_and_conquer/build_tree.rs" + +# Run Command: cargo run --bin coin_change_greedy +[[bin]] +name = "coin_change_greedy" +path = "chapter_greedy/coin_change_greedy.rs" + +# Run Command: cargo run --bin fractional_knapsack +[[bin]] +name = "fractional_knapsack" +path = "chapter_greedy/fractional_knapsack.rs" + +# Run Command: cargo run --bin max_capacity +[[bin]] +name = "max_capacity" +path = "chapter_greedy/max_capacity.rs" + +# Run Command: cargo run --bin max_product_cutting +[[bin]] +name = "max_product_cutting" +path = "chapter_greedy/max_product_cutting.rs" + +[dependencies] +rand = "0.8.5" diff --git a/ru/codes/rust/chapter_array_and_linkedlist/array.rs b/ru/codes/rust/chapter_array_and_linkedlist/array.rs new file mode 100644 index 000000000..9e256ed48 --- /dev/null +++ b/ru/codes/rust/chapter_array_and_linkedlist/array.rs @@ -0,0 +1,111 @@ +/* + * File: array.rs + * Created Time: 2023-01-15 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::Rng; + +/* Случайный доступ к элементу */ +fn random_access(nums: &[i32]) -> i32 { + // Случайным образом выбрать число из интервала [0, nums.len()) + let random_index = rand::thread_rng().gen_range(0..nums.len()); + // Получить и вернуть случайный элемент + let random_num = nums[random_index]; + random_num +} + +/* Увеличить длину массива */ +fn extend(nums: &[i32], enlarge: usize) -> Vec { + // Инициализировать массив увеличенной длины + let mut res: Vec = vec![0; nums.len() + enlarge]; + // Скопировать все элементы исходного массива в новый + res[0..nums.len()].copy_from_slice(nums); + + // Вернуть новый массив после расширения + res +} + +/* Вставить элемент num по индексу index в массив */ +fn insert(nums: &mut [i32], num: i32, index: usize) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for i in (index + 1..nums.len()).rev() { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; +} + +/* Удалить элемент по индексу index */ +fn remove(nums: &mut [i32], index: usize) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for i in index..nums.len() - 1 { + nums[i] = nums[i + 1]; + } +} + +/* Обход массива */ +fn traverse(nums: &[i32]) { + let mut _count = 0; + // Обход массива по индексам + for i in 0..nums.len() { + _count += nums[i]; + } + // Непосредственно обходить элементы массива + _count = 0; + for &num in nums { + _count += num; + } +} + +/* Найти заданный элемент в массиве */ +fn find(nums: &[i32], target: i32) -> Option { + for i in 0..nums.len() { + if nums[i] == target { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + /* Инициализация массива */ + let arr: [i32; 5] = [0; 5]; + print!("Массив arr = "); + print_util::print_array(&arr); + // В Rust при указании длины ([i32; 5]) получается массив, а без указания длины (&[i32]) — срез + // Так как массивы в Rust имеют длину, определяемую на этапе компиляции, для задания длины можно использовать только константы + // Vector обычно используется в Rust как тип динамического массива + // Для удобства реализации метода расширения extend() ниже vector рассматривается как массив (array) + let nums: Vec = vec![1, 3, 2, 5, 4]; + print!("\nМассив nums = "); + print_util::print_array(&nums); + + // Случайный доступ + let random_num = random_access(&nums); + println!("\nСлучайный элемент из nums = {}", random_num); + + // Расширение длины + let mut nums: Vec = extend(&nums, 3); + print!("После увеличения длины массива до 8 nums = "); + print_util::print_array(&nums); + + // Вставка элемента + insert(&mut nums, 6, 3); + print!("\nПосле вставки числа 6 по индексу 3 nums = "); + print_util::print_array(&nums); + + // Удаление элемента + remove(&mut nums, 2); + print!("\nПосле удаления элемента по индексу 2 nums = "); + print_util::print_array(&nums); + + // Обход массива + traverse(&nums); + + // Поиск элемента + let index = find(&nums, 3).unwrap(); + println!("\nПоиск элемента 3 в nums: индекс = {}", index); +} diff --git a/ru/codes/rust/chapter_array_and_linkedlist/linked_list.rs b/ru/codes/rust/chapter_array_and_linkedlist/linked_list.rs new file mode 100644 index 000000000..845b60965 --- /dev/null +++ b/ru/codes/rust/chapter_array_and_linkedlist/linked_list.rs @@ -0,0 +1,100 @@ +/* + * File: linked_list.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; +use std::cell::RefCell; +use std::rc::Rc; + +/* Вставить узел P после узла n0 в связном списке */ +#[allow(non_snake_case)] +pub fn insert(n0: &Rc>>, P: Rc>>) { + let n1 = n0.borrow_mut().next.take(); + P.borrow_mut().next = n1; + n0.borrow_mut().next = Some(P); +} + +/* Удалить первый узел после узла n0 в связном списке */ +#[allow(non_snake_case)] +pub fn remove(n0: &Rc>>) { + // n0 -> P -> n1 + let P = n0.borrow_mut().next.take(); + if let Some(node) = P { + let n1 = node.borrow_mut().next.take(); + n0.borrow_mut().next = n1; + } +} + +/* Доступ к узлу связного списка по индексу index */ +pub fn access(head: Rc>>, index: i32) -> Option>>> { + fn dfs( + head: Option<&Rc>>>, + index: i32, + ) -> Option>>> { + if index <= 0 { + return head.cloned(); + } + + if let Some(node) = head { + dfs(node.borrow().next.as_ref(), index - 1) + } else { + None + } + } + + dfs(Some(head).as_ref(), index) +} + +/* Найти в связном списке первый узел со значением target */ +pub fn find(head: Rc>>, target: T) -> i32 { + fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { + if let Some(node) = head { + if node.borrow().val == target { + return idx; + } + return find(node.borrow().next.as_ref(), target, idx + 1); + } else { + -1 + } + } + + find(Some(head).as_ref(), target, 0) +} + +/* Driver Code */ +fn main() { + /* Инициализация связного списка */ + // Инициализация всех узлов + let n0 = ListNode::new(1); + let n1 = ListNode::new(3); + let n2 = ListNode::new(2); + let n3 = ListNode::new(5); + let n4 = ListNode::new(4); + // Построить ссылки между узлами + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + print!("Исходный связный список "); + print_util::print_linked_list(&n0); + + /* Вставка узла */ + insert(&n0, ListNode::new(0)); + print!("Связный список после вставки узла "); + print_util::print_linked_list(&n0); + + /* Удаление узла */ + remove(&n0); + print!("Связный список после удаления узла "); + print_util::print_linked_list(&n0); + + /* Доступ к узлу */ + let node = access(n0.clone(), 3); + println!("Значение узла по индексу 3 в связном списке = {}", node.unwrap().borrow().val); + + /* Поиск узла */ + let index = find(n0.clone(), 2); + println!("Индекс узла со значением 2 в связном списке = {}", index); +} diff --git a/ru/codes/rust/chapter_array_and_linkedlist/list.rs b/ru/codes/rust/chapter_array_and_linkedlist/list.rs new file mode 100644 index 000000000..6143f9005 --- /dev/null +++ b/ru/codes/rust/chapter_array_and_linkedlist/list.rs @@ -0,0 +1,71 @@ +/* + * File: list.rs + * Created Time: 2023-01-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; + +/* Driver Code */ +fn main() { + // Инициализация списка + let mut nums: Vec = vec![1, 3, 2, 5, 4]; + print!("Список nums = "); + print_util::print_array(&nums); + + // Доступ к элементу + let num = nums[1]; + println!("\nЭлемент по индексу 1: num = {num}"); + + // Обновление элемента + nums[1] = 0; + print!("После обновления элемента по индексу 1 до 0 nums = "); + print_util::print_array(&nums); + + // Очистить список + nums.clear(); + print!("\nПосле очистки списка nums = "); + print_util::print_array(&nums); + + // Добавление элемента в конец + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + print!("\nПосле добавления элементов nums = "); + print_util::print_array(&nums); + + // Вставка элемента в середину + nums.insert(3, 6); + print!("\nПосле вставки числа 6 по индексу 3 nums = "); + print_util::print_array(&nums); + + // Удаление элемента + nums.remove(3); + print!("\nПосле удаления элемента по индексу 3 nums = "); + print_util::print_array(&nums); + + // Обходить список по индексам + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + // Непосредственно обходить элементы списка + _count = 0; + for x in &nums { + _count += x; + } + + // Объединить два списка + let mut nums1 = vec![6, 8, 7, 10, 9]; + nums.append(&mut nums1); // После append (перемещение) nums1 становится пустым! + + // nums.extend(&nums1); // extend (заимствование), nums1 можно продолжать использовать + print!("\nПосле конкатенации списка nums1 к nums nums = "); + print_util::print_array(&nums); + + // Отсортировать список + nums.sort(); + print!("\nПосле сортировки списка nums = "); + print_util::print_array(&nums); +} diff --git a/ru/codes/rust/chapter_array_and_linkedlist/my_list.rs b/ru/codes/rust/chapter_array_and_linkedlist/my_list.rs new file mode 100644 index 000000000..e6085bc7f --- /dev/null +++ b/ru/codes/rust/chapter_array_and_linkedlist/my_list.rs @@ -0,0 +1,164 @@ +/* + * File: my_list.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Класс списка */ +#[allow(dead_code)] +struct MyList { + arr: Vec, // Массив (для хранения элементов списка) + capacity: usize, // Вместимость списка + size: usize, // Длина списка (текущее число элементов) + extend_ratio: usize, // Коэффициент увеличения списка при каждом расширении +} + +#[allow(unused, unused_comparisons)] +impl MyList { + /* Конструктор */ + pub fn new(capacity: usize) -> Self { + let mut vec = vec![0; capacity]; + Self { + arr: vec, + capacity, + size: 0, + extend_ratio: 2, + } + } + + /* Получить длину списка (текущее число элементов) */ + pub fn size(&self) -> usize { + return self.size; + } + + /* Получить вместимость списка */ + pub fn capacity(&self) -> usize { + return self.capacity; + } + + /* Доступ к элементу */ + pub fn get(&self, index: usize) -> i32 { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if index >= self.size { + panic!("индекс выходит за границы") + }; + return self.arr[index]; + } + + /* Обновление элемента */ + pub fn set(&mut self, index: usize, num: i32) { + if index >= self.size { + panic!("индекс выходит за границы") + }; + self.arr[index] = num; + } + + /* Добавление элемента в конец */ + pub fn add(&mut self, num: i32) { + // При превышении вместимости по числу элементов запускается расширение + if self.size == self.capacity() { + self.extend_capacity(); + } + self.arr[self.size] = num; + // Обновить число элементов + self.size += 1; + } + + /* Вставка элемента в середину */ + pub fn insert(&mut self, index: usize, num: i32) { + if index >= self.size() { + panic!("индекс выходит за границы") + }; + // При превышении вместимости по числу элементов запускается расширение + if self.size == self.capacity() { + self.extend_capacity(); + } + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for j in (index..self.size).rev() { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // Обновить число элементов + self.size += 1; + } + + /* Удаление элемента */ + pub fn remove(&mut self, index: usize) -> i32 { + if index >= self.size() { + panic!("индекс выходит за границы") + }; + let num = self.arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for j in index..self.size - 1 { + self.arr[j] = self.arr[j + 1]; + } + // Обновить число элементов + self.size -= 1; + // Вернуть удаленный элемент + return num; + } + + /* Расширение списка */ + pub fn extend_capacity(&mut self) { + // Создать новый массив длиной в extend_ratio раз больше исходного и скопировать в него исходный массив + let new_capacity = self.capacity * self.extend_ratio; + self.arr.resize(new_capacity, 0); + // Обновить вместимость списка + self.capacity = new_capacity; + } + + /* Преобразовать список в массив */ + pub fn to_array(&self) -> Vec { + // Преобразовывать только элементы списка в пределах фактической длины + let mut arr = Vec::new(); + for i in 0..self.size { + arr.push(self.get(i)); + } + arr + } +} + +/* Driver Code */ +fn main() { + /* Инициализация списка */ + let mut nums = MyList::new(10); + /* Добавление элемента в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print!("Список nums = "); + print_util::print_array(&nums.to_array()); + print!(" , вместимость = {} , длина = {}", nums.capacity(), nums.size()); + + /* Вставка элемента в середину */ + nums.insert(3, 6); + print!("\nПосле вставки числа 6 по индексу 3 nums = "); + print_util::print_array(&nums.to_array()); + + /* Удаление элемента */ + nums.remove(3); + print!("\nПосле удаления элемента по индексу 3 nums = "); + print_util::print_array(&nums.to_array()); + + /* Доступ к элементу */ + let num = nums.get(1); + println!("\nЭлемент по индексу 1: num = {num}"); + + /* Обновление элемента */ + nums.set(1, 0); + print!("После обновления элемента по индексу 1 до 0 nums = "); + print_util::print_array(&nums.to_array()); + + /* Проверка механизма расширения */ + for i in 0..10 { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i); + } + print!("\nСписок nums после увеличения вместимости = "); + print_util::print_array(&nums.to_array()); + print!(" , вместимость = {} , длина = {}", nums.capacity(), nums.size()); +} diff --git a/ru/codes/rust/chapter_backtracking/n_queens.rs b/ru/codes/rust/chapter_backtracking/n_queens.rs new file mode 100644 index 000000000..13c0bb361 --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/n_queens.rs @@ -0,0 +1,76 @@ +/* + * File: n_queens.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Алгоритм бэктрекинга: n ферзей */ +fn backtrack( + row: usize, + n: usize, + state: &mut Vec>, + res: &mut Vec>>, + cols: &mut [bool], + diags1: &mut [bool], + diags2: &mut [bool], +) { + // Когда все строки уже обработаны, записать решение + if row == n { + res.push(state.clone()); + return; + } + // Обойти все столбцы + for col in 0..n { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + let diag1 = row + n - 1 - col; + let diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // Попытка: поставить ферзя в эту клетку + state[row][col] = "Q".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = "#".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); + } + } +} + +/* Решить задачу о n ферзях */ +fn n_queens(n: usize) -> Vec>> { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; + let mut cols = vec![false; n]; // Отмечать, есть ли ферзь в столбце + let mut diags1 = vec![false; 2 * n - 1]; // Отмечать наличие ферзя на главной диагонали + let mut diags2 = vec![false; 2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали + let mut res: Vec>> = Vec::new(); + + backtrack( + 0, + n, + &mut state, + &mut res, + &mut cols, + &mut diags1, + &mut diags2, + ); + + res +} + +/* Driver Code */ +pub fn main() { + let n: usize = 4; + let res = n_queens(n); + + println!("Размер входной доски = {n}"); + println!("Количество способов расстановки ферзей: {}", res.len()); + for state in res.iter() { + println!("--------------------"); + for row in state.iter() { + println!("{:?}", row); + } + } +} diff --git a/ru/codes/rust/chapter_backtracking/permutations_i.rs b/ru/codes/rust/chapter_backtracking/permutations_i.rs new file mode 100644 index 000000000..98688aacf --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/permutations_i.rs @@ -0,0 +1,46 @@ +/* + * File: permutations_i.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки I */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // Когда длина состояния равна числу элементов, записать решение + if state.len() == choices.len() { + res.push(state); + return; + } + // Перебор всех вариантов выбора + for i in 0..choices.len() { + let choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно + if !selected[i] { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.push(choice); + // Перейти к следующему выбору + backtrack(state.clone(), choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop(); + } + } +} + +/* Все перестановки I */ +fn permutations_i(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); // Состояние (подмножество) + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 3]; + + let res = permutations_i(&mut nums); + + println!("Входной массив nums = {:?}", &nums); + println!("Все перестановки res = {:?}", &res); +} diff --git a/ru/codes/rust/chapter_backtracking/permutations_ii.rs b/ru/codes/rust/chapter_backtracking/permutations_ii.rs new file mode 100644 index 000000000..6af358371 --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/permutations_ii.rs @@ -0,0 +1,50 @@ +/* + * File: permutations_ii.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::collections::HashSet; + +/* Алгоритм бэктрекинга: все перестановки II */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // Когда длина состояния равна числу элементов, записать решение + if state.len() == choices.len() { + res.push(state); + return; + } + // Перебор всех вариантов выбора + let mut duplicated = HashSet::::new(); + for i in 0..choices.len() { + let choice = choices[i]; + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if !selected[i] && !duplicated.contains(&choice) { + // Попытка: сделать выбор и обновить состояние + duplicated.insert(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.push(choice); + // Перейти к следующему выбору + backtrack(state.clone(), choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop(); + } + } +} + +/* Все перестановки II */ +fn permutations_ii(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 2]; + + let res = permutations_ii(&mut nums); + + println!("Входной массив nums = {:?}", &nums); + println!("Все перестановки res = {:?}", &res); +} diff --git a/ru/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs b/ru/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs new file mode 100644 index 000000000..dfe458fad --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs @@ -0,0 +1,41 @@ +/* + * File: preorder_traversal_i_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* Предварительный обход: пример 1 */ +fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { + if root.is_none() { + return; + } + if let Some(node) = root { + if node.borrow().val == 7 { + // Записать решение + res.push(node.clone()); + } + pre_order(res, node.borrow().left.as_ref()); + pre_order(res, node.borrow().right.as_ref()); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("Инициализация двоичного дерева"); + print_util::print_tree(root.as_ref().unwrap()); + + // Предварительный обход + let mut res = Vec::new(); + pre_order(&mut res, root.as_ref()); + + println!("\nВсе узлы со значением 7"); + let mut vals = Vec::new(); + for node in res { + vals.push(node.borrow().val) + } + println!("{:?}", vals); +} diff --git a/ru/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs b/ru/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs new file mode 100644 index 000000000..3093431bd --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs @@ -0,0 +1,52 @@ +/* + * File: preorder_traversal_ii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* Предварительный обход: пример 2 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + if root.is_none() { + return; + } + if let Some(node) = root { + // Попытка + path.push(node.clone()); + if node.borrow().val == 7 { + // Записать решение + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // Откат + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("Инициализация двоичного дерева"); + print_util::print_tree(root.as_ref().unwrap()); + + // Предварительный обход + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\nВсе пути от корня к узлу 7"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/ru/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/ru/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs new file mode 100644 index 000000000..0de758e3d --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -0,0 +1,53 @@ +/* + * File: preorder_traversal_iii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* Предварительный обход: пример 3 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + // Отсечение + if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { + return; + } + if let Some(node) = root { + // Попытка + path.push(node.clone()); + if node.borrow().val == 7 { + // Записать решение + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // Откат + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("Инициализация двоичного дерева"); + print_util::print_tree(root.as_ref().unwrap()); + + // Предварительный обход + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/ru/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/ru/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs new file mode 100644 index 000000000..3cb33c66b --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -0,0 +1,88 @@ +/* + * File: preorder_traversal_iii_template.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* Проверить, является ли текущее состояние решением */ +fn is_solution(state: &mut Vec>>) -> bool { + return !state.is_empty() && state.last().unwrap().borrow().val == 7; +} + +/* Записать решение */ +fn record_solution( + state: &mut Vec>>, + res: &mut Vec>>>, +) { + res.push(state.clone()); +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { + return choice.is_some() && choice.unwrap().borrow().val != 3; +} + +/* Обновить состояние */ +fn make_choice(state: &mut Vec>>, choice: Rc>) { + state.push(choice); +} + +/* Восстановить состояние */ +fn undo_choice(state: &mut Vec>>, _: Rc>) { + state.pop(); +} + +/* Алгоритм бэктрекинга: пример 3 */ +fn backtrack( + state: &mut Vec>>, + choices: &Vec>>>, + res: &mut Vec>>>, +) { + // Проверить, является ли текущее состояние решением + if is_solution(state) { + // Записать решение + record_solution(state, res); + } + // Перебор всех вариантов выбора + for &choice in choices.iter() { + // Отсечение: проверить допустимость выбора + if is_valid(state, choice) { + // Попытка: сделать выбор и обновить состояние + make_choice(state, choice.unwrap().clone()); + // Перейти к следующему выбору + backtrack( + state, + &vec![ + choice.unwrap().borrow().left.as_ref(), + choice.unwrap().borrow().right.as_ref(), + ], + res, + ); + // Откат: отменить выбор и восстановить предыдущее состояние + undo_choice(state, choice.unwrap().clone()); + } + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("Инициализация двоичного дерева"); + print_util::print_tree(root.as_ref().unwrap()); + + // Алгоритм бэктрекинга + let mut res = Vec::new(); + backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); + + println!("\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 3"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/ru/codes/rust/chapter_backtracking/subset_sum_i.rs b/ru/codes/rust/chapter_backtracking/subset_sum_i.rs new file mode 100644 index 000000000..f0d8c5fe0 --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/subset_sum_i.rs @@ -0,0 +1,56 @@ +/* + * File: subset_sum_i.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // Если сумма подмножества равна target, записать решение + if target == 0 { + res.push(state.clone()); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for i in start..choices.len() { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target - choices[i] < 0 { + break; + } + // Попытка: сделать выбор и обновить target и start + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств I */ +fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // Состояние (подмножество) + nums.sort(); // Отсортировать nums + let start = 0; // Стартовая вершина обхода + let mut res = Vec::new(); // Список результатов (список подмножеств) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i(&mut nums, target); + + println!("Входной массив nums = {:?}, target = {}", &nums, target); + println!("Все подмножества с суммой {}: res = {:?}", target, &res); +} diff --git a/ru/codes/rust/chapter_backtracking/subset_sum_i_naive.rs b/ru/codes/rust/chapter_backtracking/subset_sum_i_naive.rs new file mode 100644 index 000000000..beba8a11a --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/subset_sum_i_naive.rs @@ -0,0 +1,54 @@ +/* + * File: subset_sum_i_naive.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +fn backtrack( + state: &mut Vec, + target: i32, + total: i32, + choices: &[i32], + res: &mut Vec>, +) { + // Если сумма подмножества равна target, записать решение + if total == target { + res.push(state.clone()); + return; + } + // Перебор всех вариантов выбора + for i in 0..choices.len() { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if total + choices[i] > target { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { + let mut state = Vec::new(); // Состояние (подмножество) + let total = 0; // Сумма подмножеств + let mut res = Vec::new(); // Список результатов (список подмножеств) + backtrack(&mut state, target, total, nums, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i_naive(&nums, target); + + println!("Входной массив nums = {:?}, target = {}", &nums, target); + println!("Все подмножества с суммой {}: res = {:?}", target, &res); + println!("Обратите внимание: результат этого метода содержит повторяющиеся множества"); +} diff --git a/ru/codes/rust/chapter_backtracking/subset_sum_ii.rs b/ru/codes/rust/chapter_backtracking/subset_sum_ii.rs new file mode 100644 index 000000000..5b0ae049e --- /dev/null +++ b/ru/codes/rust/chapter_backtracking/subset_sum_ii.rs @@ -0,0 +1,61 @@ +/* + * File: subset_sum_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // Если сумма подмножества равна target, записать решение + if target == 0 { + res.push(state.clone()); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for i in start..choices.len() { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target - choices[i] < 0 { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if i > start && choices[i] == choices[i - 1] { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств II */ +fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // Состояние (подмножество) + nums.sort(); // Отсортировать nums + let start = 0; // Стартовая вершина обхода + let mut res = Vec::new(); // Список результатов (список подмножеств) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 4, 5]; + let target = 9; + + let res = subset_sum_ii(&mut nums, target); + + println!("Входной массив nums = {:?}, target = {}", &nums, target); + println!("Все подмножества с суммой {}: res = {:?}", target, &res); +} diff --git a/ru/codes/rust/chapter_computational_complexity/iteration.rs b/ru/codes/rust/chapter_computational_complexity/iteration.rs new file mode 100644 index 000000000..3d3c861b4 --- /dev/null +++ b/ru/codes/rust/chapter_computational_complexity/iteration.rs @@ -0,0 +1,74 @@ +/* + * File: iteration.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Цикл for */ +fn for_loop(n: i32) -> i32 { + let mut res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for i in 1..=n { + res += i; + } + res +} + +/* Цикл while */ +fn while_loop(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // Инициализация условной переменной + + // Циклическое суммирование 1, 2, ..., n-1, n + while i <= n { + res += i; + i += 1; // Обновить условную переменную + } + res +} + +/* Цикл while (двойное обновление) */ +fn while_loop_ii(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // Инициализация условной переменной + + // Циклическое суммирование 1, 4, 10, ... + while i <= n { + res += i; + // Обновить условную переменную + i += 1; + i *= 2; + } + res +} + +/* Двойной цикл for */ +fn nested_for_loop(n: i32) -> String { + let mut res = vec![]; + // Цикл по i = 1, 2, ..., n-1, n + for i in 1..=n { + // Цикл по j = 1, 2, ..., n-1, n + for j in 1..=n { + res.push(format!("({}, {}), ", i, j)); + } + } + res.join("") +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = for_loop(n); + println!("\nРезультат суммирования в цикле for res = {res}"); + + res = while_loop(n); + println!("\nРезультат суммирования в цикле while res = {res}"); + + res = while_loop_ii(n); + println!("\nРезультат суммирования в цикле while (двойное обновление) res = {}", res); + + let res = nested_for_loop(n); + println!("\nРезультат обхода в двойном цикле for {res}"); +} diff --git a/ru/codes/rust/chapter_computational_complexity/recursion.rs b/ru/codes/rust/chapter_computational_complexity/recursion.rs new file mode 100644 index 000000000..9b37d9bce --- /dev/null +++ b/ru/codes/rust/chapter_computational_complexity/recursion.rs @@ -0,0 +1,76 @@ +/* + * File: recursion.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Рекурсия */ +fn recur(n: i32) -> i32 { + // Условие завершения + if n == 1 { + return 1; + } + // Рекурсия: рекурсивный вызов + let res = recur(n - 1); + // Возврат: вернуть результат + n + res +} + +/* Имитация рекурсии итерацией */ +fn for_loop_recur(n: i32) -> i32 { + // Использовать явный стек для имитации системного стека вызовов + let mut stack = Vec::new(); + let mut res = 0; + // Рекурсия: рекурсивный вызов + for i in (1..=n).rev() { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.push(i); + } + // Возврат: вернуть результат + while !stack.is_empty() { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.pop().unwrap(); + } + // res = 1+2+3+...+n + res +} + +/* Хвостовая рекурсия */ +fn tail_recur(n: i32, res: i32) -> i32 { + // Условие завершения + if n == 0 { + return res; + } + // Хвостовой рекурсивный вызов + tail_recur(n - 1, res + n) +} + +/* Последовательность Фибоначчи: рекурсия */ +fn fib(n: i32) -> i32 { + // Условие завершения: f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1; + } + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + let res = fib(n - 1) + fib(n - 2); + // Вернуть результат + res +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = recur(n); + println!("\nРезультат суммирования в рекурсивной функции res = {res}"); + + res = for_loop_recur(n); + println!("\nРезультат суммирования при имитации рекурсии res = {res}"); + + res = tail_recur(n, 0); + println!("\nРезультат суммирования в хвостовой рекурсии res = {res}"); + + res = fib(n); + println!("\nЧлен последовательности Фибоначчи с номером {n} = {res}"); +} diff --git a/ru/codes/rust/chapter_computational_complexity/space_complexity.rs b/ru/codes/rust/chapter_computational_complexity/space_complexity.rs new file mode 100644 index 000000000..4c23dd39c --- /dev/null +++ b/ru/codes/rust/chapter_computational_complexity/space_complexity.rs @@ -0,0 +1,114 @@ +/* + * File: space_complexity.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode, TreeNode}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* Функция */ +fn function() -> i32 { + // Выполнить некоторые операции + return 0; +} + +/* Постоянная сложность */ +#[allow(unused)] +fn constant(n: i32) { + // Константы, переменные и объекты занимают O(1) памяти + const A: i32 = 0; + let b = 0; + let nums = vec![0; 10000]; + let node = ListNode::new(0); + // Переменные в цикле занимают O(1) памяти + for i in 0..n { + let c = 0; + } + // Функции в цикле занимают O(1) памяти + for i in 0..n { + function(); + } +} + +/* Линейная сложность */ +#[allow(unused)] +fn linear(n: i32) { + // Массив длины n занимает O(n) памяти + let mut nums = vec![0; n as usize]; + // Список длины n занимает O(n) памяти + let mut nodes = Vec::new(); + for i in 0..n { + nodes.push(ListNode::new(i)) + } + // Хеш-таблица длины n занимает O(n) памяти + let mut map = HashMap::new(); + for i in 0..n { + map.insert(i, i.to_string()); + } +} + +/* Линейная сложность (рекурсивная реализация) */ +fn linear_recur(n: i32) { + println!("Рекурсия n = {}", n); + if n == 1 { + return; + }; + linear_recur(n - 1); +} + +/* Квадратичная сложность */ +#[allow(unused)] +fn quadratic(n: i32) { + // Матрица занимает O(n^2) памяти + let num_matrix = vec![vec![0; n as usize]; n as usize]; + // Двумерный список занимает O(n^2) памяти + let mut num_list = Vec::new(); + for i in 0..n { + let mut tmp = Vec::new(); + for j in 0..n { + tmp.push(0); + } + num_list.push(tmp); + } +} + +/* Квадратичная сложность (рекурсивная реализация) */ +fn quadratic_recur(n: i32) -> i32 { + if n <= 0 { + return 0; + }; + // Длина массива nums равна n, n-1, ..., 2, 1 + let nums = vec![0; n as usize]; + println!("В рекурсии n = {} , длина nums = {}", n, nums.len()); + return quadratic_recur(n - 1); +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +fn build_tree(n: i32) -> Option>> { + if n == 0 { + return None; + }; + let root = TreeNode::new(0); + root.borrow_mut().left = build_tree(n - 1); + root.borrow_mut().right = build_tree(n - 1); + return Some(root); +} + +/* Driver Code */ +fn main() { + let n = 5; + // Постоянная сложность + constant(n); + // Линейная сложность + linear(n); + linear_recur(n); + // Квадратичная сложность + quadratic(n); + quadratic_recur(n); + // Экспоненциальная сложность + let root = build_tree(n); + print_util::print_tree(&root.unwrap()); +} diff --git a/ru/codes/rust/chapter_computational_complexity/time_complexity.rs b/ru/codes/rust/chapter_computational_complexity/time_complexity.rs new file mode 100644 index 000000000..68bbe42ff --- /dev/null +++ b/ru/codes/rust/chapter_computational_complexity/time_complexity.rs @@ -0,0 +1,170 @@ +/* + * File: time_complexity.rs + * Created Time: 2023-01-10 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +/* Постоянная сложность */ +fn constant(n: i32) -> i32 { + _ = n; + let mut count = 0; + let size = 100_000; + for _ in 0..size { + count += 1; + } + count +} + +/* Линейная сложность */ +fn linear(n: i32) -> i32 { + let mut count = 0; + for _ in 0..n { + count += 1; + } + count +} + +/* Линейная сложность (обход массива) */ +fn array_traversal(nums: &[i32]) -> i32 { + let mut count = 0; + // Число итераций пропорционально длине массива + for _ in nums { + count += 1; + } + count +} + +/* Квадратичная сложность */ +fn quadratic(n: i32) -> i32 { + let mut count = 0; + // Число итераций квадратично зависит от размера данных n + for _ in 0..n { + for _ in 0..n { + count += 1; + } + } + count +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +fn bubble_sort(nums: &mut [i32]) -> i32 { + let mut count = 0; // Счетчик + + // Внешний цикл: неотсортированный диапазон [0, i] + for i in (1..nums.len()).rev() { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0..i { + if nums[j] > nums[j + 1] { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + count +} + +/* Экспоненциальная сложность (итеративная реализация) */ +fn exponential(n: i32) -> i32 { + let mut count = 0; + let mut base = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for _ in 0..n { + for _ in 0..base { + count += 1 + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +fn exp_recur(n: i32) -> i32 { + if n == 1 { + return 1; + } + exp_recur(n - 1) + exp_recur(n - 1) + 1 +} + +/* Логарифмическая сложность (итеративная реализация) */ +fn logarithmic(mut n: i32) -> i32 { + let mut count = 0; + while n > 1 { + n = n / 2; + count += 1; + } + count +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +fn log_recur(n: i32) -> i32 { + if n <= 1 { + return 0; + } + log_recur(n / 2) + 1 +} + +/* Линейно-логарифмическая сложность */ +fn linear_log_recur(n: i32) -> i32 { + if n <= 1 { + return 1; + } + let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); + for _ in 0..n { + count += 1; + } + return count; +} + +/* Факториальная сложность (рекурсивная реализация) */ +fn factorial_recur(n: i32) -> i32 { + if n == 0 { + return 1; + } + let mut count = 0; + // Из одного получается n + for _ in 0..n { + count += factorial_recur(n - 1); + } + count +} + +/* Driver Code */ +fn main() { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + let n: i32 = 8; + println!("Размер входных данных n = {}", n); + + let mut count = constant(n); + println!("Число операций константной сложности = {}", count); + + count = linear(n); + println!("Число операций линейной сложности = {}", count); + count = array_traversal(&vec![0; n as usize]); + println!("Число операций линейной сложности (обход массива) = {}", count); + + count = quadratic(n); + println!("Число операций квадратичной сложности = {}", count); + let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] + count = bubble_sort(&mut nums); + println!("Число операций квадратичной сложности (пузырьковая сортировка) = {}", count); + + count = exponential(n); + println!("Число операций экспоненциальной сложности (итеративная реализация) = {}", count); + count = exp_recur(n); + println!("Число операций экспоненциальной сложности (рекурсивная реализация) = {}", count); + + count = logarithmic(n); + println!("Число операций логарифмической сложности (итеративная реализация) = {}", count); + count = log_recur(n); + println!("Число операций логарифмической сложности (рекурсивная реализация) = {}", count); + + count = linear_log_recur(n); + println!("Число операций линейно-логарифмической сложности (рекурсивная реализация) = {}", count); + + count = factorial_recur(n); + println!("Число операций факториальной сложности (рекурсивная реализация) = {}", count); +} diff --git a/ru/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs b/ru/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs new file mode 100644 index 000000000..6288ee35e --- /dev/null +++ b/ru/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs @@ -0,0 +1,42 @@ +/* + * File: worst_best_time_complexity.rs + * Created Time: 2023-01-13 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::seq::SliceRandom; +use rand::thread_rng; + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +fn random_numbers(n: i32) -> Vec { + // Создать массив nums = { 1, 2, 3, ..., n } + let mut nums = (1..=n).collect::>(); + // Случайно перемешать элементы массива + nums.shuffle(&mut thread_rng()); + nums +} + +/* Найти индекс числа 1 в массиве nums */ +fn find_one(nums: &[i32]) -> Option { + for i in 0..nums.len() { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if nums[i] == 1 { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + for _ in 0..10 { + let n = 100; + let nums = random_numbers(n); + let index = find_one(&nums).unwrap(); + print!("\nМассив [1, 2, ..., n] после перемешивания = "); + print_util::print_array(&nums); + println!("\nИндекс числа 1 = {}", index); + } +} diff --git a/ru/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs b/ru/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs new file mode 100644 index 000000000..63c74b080 --- /dev/null +++ b/ru/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs @@ -0,0 +1,41 @@ +/* + * File: binary_search_recur.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Бинарный поиск: задача f(i, j) */ +fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if i > j { + return -1; + } + let m: i32 = i + (j - i) / 2; + if nums[m as usize] < target { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if nums[m as usize] > target { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } +} + +/* Бинарный поиск */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + let n = nums.len() as i32; + // Решить задачу f(0, n-1) + dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // Бинарный поиск (двусторонне замкнутый интервал) + let index = binary_search(&nums, target); + println!("Индекс целевого элемента 6 = {index}"); +} diff --git a/ru/codes/rust/chapter_divide_and_conquer/build_tree.rs b/ru/codes/rust/chapter_divide_and_conquer/build_tree.rs new file mode 100644 index 000000000..023a5c5f3 --- /dev/null +++ b/ru/codes/rust/chapter_divide_and_conquer/build_tree.rs @@ -0,0 +1,56 @@ +/* + * File: build_tree.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; +use std::collections::HashMap; +use std::{cell::RefCell, rc::Rc}; + +/* Построить двоичное дерево: разделяй и властвуй */ +fn dfs( + preorder: &[i32], + inorder_map: &HashMap, + i: i32, + l: i32, + r: i32, +) -> Option>> { + // Завершить при пустом диапазоне поддерева + if r - l < 0 { + return None; + } + // Инициализировать корневой узел + let root = TreeNode::new(preorder[i as usize]); + // Найти m, чтобы разделить левое и правое поддеревья + let m = inorder_map.get(&preorder[i as usize]).unwrap(); + // Подзадача: построить левое поддерево + root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + Some(root) +} + +/* Построить двоичное дерево */ +fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + let mut inorder_map: HashMap = HashMap::new(); + for i in 0..inorder.len() { + inorder_map.insert(inorder[i], i as i32); + } + let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); + root +} + +/* Driver Code */ +fn main() { + let preorder = [3, 9, 2, 1, 7]; + let inorder = [9, 3, 1, 2, 7]; + println!("Предварительный обход = {:?}", preorder); + println!("Симметричный обход = {:?}", inorder); + + let root = build_tree(&preorder, &inorder); + println!("Построенное двоичное дерево:"); + print_util::print_tree(root.as_ref().unwrap()); +} diff --git a/ru/codes/rust/chapter_divide_and_conquer/hanota.rs b/ru/codes/rust/chapter_divide_and_conquer/hanota.rs new file mode 100644 index 000000000..cf698fca4 --- /dev/null +++ b/ru/codes/rust/chapter_divide_and_conquer/hanota.rs @@ -0,0 +1,55 @@ +/* + * File: hanota.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +#![allow(non_snake_case)] + +/* Переместить один диск */ +fn move_pan(src: &mut Vec, tar: &mut Vec) { + // Снять диск с вершины src + let pan = src.pop().unwrap(); + // Положить диск на вершину tar + tar.push(pan); +} + +/* Решить задачу Ханойской башни f(i) */ +fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { + // Если в src остался только один диск, сразу переместить его в tar + if i == 1 { + move_pan(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move_pan(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar); +} + +/* Решить задачу Ханойской башни */ +fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { + let n = A.len() as i32; + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C); +} + +/* Driver Code */ +pub fn main() { + let mut A = vec![5, 4, 3, 2, 1]; + let mut B = Vec::new(); + let mut C = Vec::new(); + println!("Исходное состояние:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); + + solve_hanota(&mut A, &mut B, &mut C); + + println!("После завершения перемещения дисков:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs new file mode 100644 index 000000000..39ed590d8 --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs @@ -0,0 +1,41 @@ +/* + * File: climbing_stairs_backtrack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Бэктрекинг */ +fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if state == n { + res[0] = res[0] + 1; + } + // Перебор всех вариантов выбора + for &choice in choices { + // Отсечение: нельзя выходить за n-ю ступень + if state + choice > n { + continue; + } + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +fn climbing_stairs_backtrack(n: usize) -> i32 { + let choices = vec![1, 2]; // Можно подняться на 1 или 2 ступени + let state = 0; // Начать подъем с 0-й ступени + let mut res = Vec::new(); + res.push(0); // Использовать res[0] для хранения числа решений + backtrack(&choices, state, n as i32, &mut res); + res[0] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_backtrack(n); + println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs new file mode 100644 index 000000000..74c9d2129 --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs @@ -0,0 +1,33 @@ +/* + * File: climbing_stairs_constraint_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +fn climbing_stairs_constraint_dp(n: usize) -> i32 { + if n == 1 || n == 2 { + return 1; + }; + // Инициализация таблицы dp для хранения решений подзадач + let mut dp = vec![vec![-1; 3]; n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3..=n { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + dp[n][1] + dp[n][2] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_constraint_dp(n); + println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs new file mode 100644 index 000000000..358472c9e --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs @@ -0,0 +1,29 @@ +/* + * File: climbing_stairs_dfs.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Поиск */ +fn dfs(i: usize) -> i32 { + // dp[1] и dp[2] уже известны, вернуть их + if i == 1 || i == 2 { + return i as i32; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1) + dfs(i - 2); + count +} + +/* Подъем по лестнице: поиск */ +fn climbing_stairs_dfs(n: usize) -> i32 { + dfs(n) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs(n); + println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs new file mode 100644 index 000000000..56bfb9ec0 --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs @@ -0,0 +1,37 @@ +/* + * File: climbing_stairs_dfs_mem.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Поиск с мемоизацией */ +fn dfs(i: usize, mem: &mut [i32]) -> i32 { + // dp[1] и dp[2] уже известны, вернуть их + if i == 1 || i == 2 { + return i as i32; + } + // Если запись dp[i] существует, сразу вернуть ее + if mem[i] != -1 { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + count +} + +/* Подъем по лестнице: поиск с мемоизацией */ +fn climbing_stairs_dfs_mem(n: usize) -> i32 { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + let mut mem = vec![-1; n + 1]; + dfs(n, &mut mem) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs_mem(n); + println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs new file mode 100644 index 000000000..154e093ce --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs @@ -0,0 +1,48 @@ +/* + * File: climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Подъем по лестнице: динамическое программирование */ +fn climbing_stairs_dp(n: usize) -> i32 { + // dp[1] и dp[2] уже известны, вернуть их + if n == 1 || n == 2 { + return n as i32; + } + // Инициализация таблицы dp для хранения решений подзадач + let mut dp = vec![-1; n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3..=n { + dp[i] = dp[i - 1] + dp[i - 2]; + } + dp[n] +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +fn climbing_stairs_dp_comp(n: usize) -> i32 { + if n == 1 || n == 2 { + return n as i32; + } + let (mut a, mut b) = (1, 2); + for _ in 3..=n { + let tmp = b; + b = a + b; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dp(n); + println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); + + let res = climbing_stairs_dp_comp(n); + println!("Количество способов подняться по лестнице из {n} ступеней = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/coin_change.rs b/ru/codes/rust/chapter_dynamic_programming/coin_change.rs new file mode 100644 index 000000000..3bdd37764 --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/coin_change.rs @@ -0,0 +1,75 @@ +/* + * File: coin_change.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Размен монет: динамическое программирование */ +fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // Инициализация таблицы dp + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // Переход состояний: первая строка и первый столбец + for a in 1..=amt { + dp[0][a] = max; + } + // Переход состояний: остальные строки и столбцы + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); + } + } + } + if dp[n][amt] != max { + return dp[n][amt] as i32; + } else { + -1 + } +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // Инициализация таблицы dp + let mut dp = vec![0; amt + 1]; + dp.fill(max); + dp[0] = 0; + // Переход состояний + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); + } + } + } + if dp[amt] != max { + return dp[amt] as i32; + } else { + -1 + } +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 4; + + // Динамическое программирование + let res = coin_change_dp(&coins, amt); + println!("Минимальное число монет для набора целевой суммы = {res}"); + + // Динамическое программирование с оптимизацией памяти + let res = coin_change_dp_comp(&coins, amt); + println!("Минимальное число монет для набора целевой суммы = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/coin_change_ii.rs b/ru/codes/rust/chapter_dynamic_programming/coin_change_ii.rs new file mode 100644 index 000000000..384595a9a --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/coin_change_ii.rs @@ -0,0 +1,64 @@ +/* + * File: coin_change_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Размен монет II: динамическое программирование */ +fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // Инициализация таблицы dp + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // Инициализация первого столбца + for i in 0..=n { + dp[i][0] = 1; + } + // Переход состояний + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; + } + } + } + dp[n][amt] +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // Инициализация таблицы dp + let mut dp = vec![0; amt + 1]; + dp[0] = 1; + // Переход состояний + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; + } + } + } + dp[amt] +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 5; + + // Динамическое программирование + let res = coin_change_ii_dp(&coins, amt); + println!("Количество комбинаций монет для набора целевой суммы = {res}"); + + // Динамическое программирование с оптимизацией памяти + let res = coin_change_ii_dp_comp(&coins, amt); + println!("Количество комбинаций монет для набора целевой суммы = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/edit_distance.rs b/ru/codes/rust/chapter_dynamic_programming/edit_distance.rs new file mode 100644 index 000000000..e805b1e27 --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/edit_distance.rs @@ -0,0 +1,145 @@ +/* + * File: edit_distance.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Редакционное расстояние: полный перебор */ +fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { + // Если s и t пусты, вернуть 0 + if i == 0 && j == 0 { + return 0; + } + // Если s пусто, вернуть длину t + if i == 0 { + return j as i32; + } + // Если t пусто, вернуть длину s + if j == 0 { + return i as i32; + } + // Если два символа равны, сразу пропустить их + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs(s, t, i - 1, j - 1); + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + let insert = edit_distance_dfs(s, t, i, j - 1); + let delete = edit_distance_dfs(s, t, i - 1, j); + let replace = edit_distance_dfs(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + std::cmp::min(std::cmp::min(insert, delete), replace) + 1 +} + +/* Редакционное расстояние: поиск с мемоизацией */ +fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { + // Если s и t пусты, вернуть 0 + if i == 0 && j == 0 { + return 0; + } + // Если s пусто, вернуть длину t + if i == 0 { + return j as i32; + } + // Если t пусто, вернуть длину s + if j == 0 { + return i as i32; + } + // Если запись уже есть, сразу вернуть ее + if mem[i][j] != -1 { + return mem[i][j]; + } + // Если два символа равны, сразу пропустить их + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); + let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); + let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; + mem[i][j] +} + +/* Редакционное расстояние: динамическое программирование */ +fn edit_distance_dp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![vec![0; m + 1]; n + 1]; + // Переход состояний: первая строка и первый столбец + for i in 1..=n { + dp[i][0] = i as i32; + } + for j in 1..m { + dp[0][j] = j as i32; + } + // Переход состояний: остальные строки и столбцы + for i in 1..=n { + for j in 1..=m { + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = + std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + dp[n][m] +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![0; m + 1]; + // Переход состояний: первая строка + for j in 1..m { + dp[j] = j as i32; + } + // Переход состояний: остальные строки + for i in 1..=n { + // Переход состояний: первый столбец + let mut leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i as i32; + // Переход состояний: остальные столбцы + for j in 1..=m { + let temp = dp[j]; + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + dp[m] +} + +/* Driver Code */ +pub fn main() { + let s = "bag"; + let t = "pack"; + let (n, m) = (s.len(), t.len()); + + // Полный перебор + let res = edit_distance_dfs(s, t, n, m); + println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); + + // Поиск с запоминанием + let mut mem = vec![vec![0; m + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); + println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); + + // Динамическое программирование + let res = edit_distance_dp(s, t); + println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); + + // Динамическое программирование с оптимизацией памяти + let res = edit_distance_dp_comp(s, t); + println!("Чтобы преобразовать {s} в {t}, нужно минимум {res} шагов"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/knapsack.rs b/ru/codes/rust/chapter_dynamic_programming/knapsack.rs new file mode 100644 index 000000000..da782c796 --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/knapsack.rs @@ -0,0 +1,113 @@ +/* + * File: knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Рюкзак 0-1: полный перебор */ +fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 || c == 0 { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i - 1] > c as i32 { + return knapsack_dfs(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + let no = knapsack_dfs(wgt, val, i - 1, c); + let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + std::cmp::max(no, yes) +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 || c == 0 { + return 0; + } + // Если запись уже есть, вернуть сразу + if mem[i][c] != -1 { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i - 1] > c as i32 { + return knapsack_dfs_mem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); + let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = std::cmp::max(no, yes); + mem[i][c] +} + +/* Рюкзак 0-1: динамическое программирование */ +fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // Инициализация таблицы dp + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // Переход состояний + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = std::cmp::max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], + ); + } + } + } + dp[n][cap] +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // Инициализация таблицы dp + let mut dp = vec![0; cap + 1]; + // Переход состояний + for i in 1..=n { + // Обход в обратном порядке + for c in (1..=cap).rev() { + if wgt[i - 1] <= c as i32 { + // Большее из двух решений: не брать или взять предмет i + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap: usize = 50; + let n = wgt.len(); + + // Полный перебор + let res = knapsack_dfs(&wgt, &val, n, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); + + // Поиск с запоминанием + let mut mem = vec![vec![0; cap + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); + + // Динамическое программирование + let res = knapsack_dp(&wgt, &val, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); + + // Динамическое программирование с оптимизацией памяти + let res = knapsack_dp_comp(&wgt, &val, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs b/ru/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs new file mode 100644 index 000000000..636199a2f --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs @@ -0,0 +1,52 @@ +/* + * File: min_cost_climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::cmp; + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + } + // Инициализация таблицы dp для хранения решений подзадач + let mut dp = vec![-1; n + 1]; + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3..=n { + dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; + } + dp[n] +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + }; + let (mut a, mut b) = (cost[1], cost[2]); + for i in 3..=n { + let tmp = b; + b = cmp::min(a, tmp) + cost[i]; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + println!("Список стоимостей ступеней = {:?}", &cost); + + let res = min_cost_climbing_stairs_dp(&cost); + println!("Минимальная стоимость подъема по лестнице = {res}"); + + let res = min_cost_climbing_stairs_dp_comp(&cost); + println!("Минимальная стоимость подъема по лестнице = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/min_path_sum.rs b/ru/codes/rust/chapter_dynamic_programming/min_path_sum.rs new file mode 100644 index 000000000..0ac9e722e --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/min_path_sum.rs @@ -0,0 +1,120 @@ +/* + * File: min_path_sum.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Минимальная сумма пути: полный перебор */ +fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { + // Если это верхняя левая ячейка, завершить поиск + if i == 0 && j == 0 { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 || j < 0 { + return i32::MAX; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + let up = min_path_sum_dfs(grid, i - 1, j); + let left = min_path_sum_dfs(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + std::cmp::min(left, up) + grid[i as usize][j as usize] +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { + // Если это верхняя левая ячейка, завершить поиск + if i == 0 && j == 0 { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 || j < 0 { + return i32::MAX; + } + // Если запись уже есть, вернуть сразу + if mem[i as usize][j as usize] != -1 { + return mem[i as usize][j as usize]; + } + // Минимальная стоимость пути для левой и верхней ячеек + let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); + let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; + mem[i as usize][j as usize] +} + +/* Минимальная сумма пути: динамическое программирование */ +fn min_path_sum_dp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // Инициализация таблицы dp + let mut dp = vec![vec![0; m]; n]; + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for j in 1..m { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for i in 1..n { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for i in 1..n { + for j in 1..m { + dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + dp[n - 1][m - 1] +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // Инициализация таблицы dp + let mut dp = vec![0; m]; + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for j in 1..m { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for i in 1..n { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for j in 1..m { + dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + dp[m - 1] +} + +/* Driver Code */ +pub fn main() { + let grid = vec![ + vec![1, 3, 1, 5], + vec![2, 2, 4, 2], + vec![5, 3, 2, 1], + vec![4, 3, 5, 2], + ]; + let (n, m) = (grid.len(), grid[0].len()); + + // Полный перебор + let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); + println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); + + // Поиск с мемоизацией + let mut mem = vec![vec![0; m]; n]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); + println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); + + // Динамическое программирование + let res = min_path_sum_dp(&grid); + println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); + + // Динамическое программирование с оптимизацией памяти + let res = min_path_sum_dp_comp(&grid); + println!("Минимальная сумма пути из левого верхнего угла в правый нижний = {res}"); +} diff --git a/ru/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs b/ru/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs new file mode 100644 index 000000000..164754f9e --- /dev/null +++ b/ru/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs @@ -0,0 +1,60 @@ +/* + * File: unbounded_knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Полный рюкзак: динамическое программирование */ +fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // Инициализация таблицы dp + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // Переход состояний + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // Инициализация таблицы dp + let mut dp = vec![0; cap + 1]; + // Переход состояний + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [1, 2, 3]; + let val = [5, 11, 15]; + let cap: usize = 4; + + // Динамическое программирование + let res = unbounded_knapsack_dp(&wgt, &val, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); + + // Динамическое программирование с оптимизацией памяти + let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {res}"); +} diff --git a/ru/codes/rust/chapter_graph/graph_adjacency_list.rs b/ru/codes/rust/chapter_graph/graph_adjacency_list.rs new file mode 100644 index 000000000..994e7cbf0 --- /dev/null +++ b/ru/codes/rust/chapter_graph/graph_adjacency_list.rs @@ -0,0 +1,135 @@ +/* + * File: graph_adjacency_list.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; + +use std::collections::HashMap; + +/* Тип неориентированного графа на основе списка смежности */ +pub struct GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + pub adj_list: HashMap>, // maybe HashSet for value part is better? +} + +impl GraphAdjList { + /* Конструктор */ + pub fn new(edges: Vec<[Vertex; 2]>) -> Self { + let mut graph = GraphAdjList { + adj_list: HashMap::new(), + }; + // Добавить все вершины и ребра + for edge in edges { + graph.add_vertex(edge[0]); + graph.add_vertex(edge[1]); + graph.add_edge(edge[0], edge[1]); + } + + graph + } + + /* Получить число вершин */ + #[allow(unused)] + pub fn size(&self) -> usize { + self.adj_list.len() + } + + /* Добавление ребра */ + pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if vet1 == vet2 { + panic!("value error"); + } + // Добавить ребро vet1 - vet2 + self.adj_list.entry(vet1).or_default().push(vet2); + self.adj_list.entry(vet2).or_default().push(vet1); + } + + /* Удаление ребра */ + #[allow(unused)] + pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if vet1 == vet2 { + panic!("value error"); + } + // Удалить ребро vet1 - vet2 + self.adj_list + .entry(vet1) + .and_modify(|v| v.retain(|&e| e != vet2)); + self.adj_list + .entry(vet2) + .and_modify(|v| v.retain(|&e| e != vet1)); + } + + /* Добавление вершины */ + pub fn add_vertex(&mut self, vet: Vertex) { + if self.adj_list.contains_key(&vet) { + return; + } + // Добавить новый список в список смежности + self.adj_list.insert(vet, vec![]); + } + + /* Удаление вершины */ + #[allow(unused)] + pub fn remove_vertex(&mut self, vet: Vertex) { + // Удалить из списка смежности список, соответствующий вершине vet + self.adj_list.remove(&vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + for list in self.adj_list.values_mut() { + list.retain(|&v| v != vet); + } + } + + /* Вывести список смежности */ + pub fn print(&self) { + println!("Список смежности ="); + for (vertex, list) in &self.adj_list { + let list = list.iter().map(|vertex| vertex.val).collect::>(); + println!("{}: {:?},", vertex.val, list); + } + } +} + +/* Driver Code */ +#[allow(unused)] +fn main() { + /* Инициализация неориентированного графа */ + let v = vals_to_vets(vec![1, 3, 2, 5, 4]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + + let mut graph = GraphAdjList::new(edges); + println!("\nГраф после инициализации"); + graph.print(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.add_edge(v[0], v[2]); + println!("\nГраф после добавления ребра 1-2"); + graph.print(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.remove_edge(v[0], v[1]); + println!("\nГраф после удаления ребра 1-3"); + graph.print(); + + /* Добавление вершины */ + let v5 = Vertex { val: 6 }; + graph.add_vertex(v5); + println!("\nГраф после добавления вершины 6"); + graph.print(); + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.remove_vertex(v[1]); + println!("\nГраф после удаления вершины 3"); + graph.print(); +} diff --git a/ru/codes/rust/chapter_graph/graph_adjacency_matrix.rs b/ru/codes/rust/chapter_graph/graph_adjacency_matrix.rs new file mode 100644 index 000000000..0b3cd042e --- /dev/null +++ b/ru/codes/rust/chapter_graph/graph_adjacency_matrix.rs @@ -0,0 +1,136 @@ +/* + * File: graph_adjacency_matrix.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Тип неориентированного графа на основе матрицы смежности */ +pub struct GraphAdjMat { + // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + pub vertices: Vec, + // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + pub adj_mat: Vec>, +} + +impl GraphAdjMat { + /* Конструктор */ + pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { + let mut graph = GraphAdjMat { + vertices: vec![], + adj_mat: vec![], + }; + // Добавление вершины + for val in vertices { + graph.add_vertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for edge in edges { + graph.add_edge(edge[0], edge[1]) + } + + graph + } + + /* Получить число вершин */ + pub fn size(&self) -> usize { + self.vertices.len() + } + + /* Добавление вершины */ + pub fn add_vertex(&mut self, val: i32) { + let n = self.size(); + // Добавить значение новой вершины в список вершин + self.vertices.push(val); + // Добавить строку в матрицу смежности + self.adj_mat.push(vec![0; n]); + // Добавить столбец в матрицу смежности + for row in self.adj_mat.iter_mut() { + row.push(0); + } + } + + /* Удаление вершины */ + pub fn remove_vertex(&mut self, index: usize) { + if index >= self.size() { + panic!("index error") + } + // Удалить вершину с индексом index из списка вершин + self.vertices.remove(index); + // Удалить строку с индексом index из матрицы смежности + self.adj_mat.remove(index); + // Удалить столбец с индексом index из матрицы смежности + for row in self.adj_mat.iter_mut() { + row.remove(index); + } + } + + /* Добавление ребра */ + pub fn add_edge(&mut self, i: usize, j: usize) { + // Параметры i и j соответствуют индексам элементов vertices + // Обработка выхода индекса за границы и случая равенства + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + self.adj_mat[i][j] = 1; + self.adj_mat[j][i] = 1; + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + pub fn remove_edge(&mut self, i: usize, j: usize) { + // Параметры i и j соответствуют индексам элементов vertices + // Обработка выхода индекса за границы и случая равенства + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + self.adj_mat[i][j] = 0; + self.adj_mat[j][i] = 0; + } + + /* Вывести матрицу смежности */ + pub fn print(&self) { + println!("Список вершин = {:?}", self.vertices); + println!("Матрица смежности ="); + println!("["); + for row in &self.adj_mat { + println!(" {:?},", row); + } + println!("]") + } +} + +/* Driver Code */ +fn main() { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + let vertices = vec![1, 3, 2, 5, 4]; + let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; + let mut graph = GraphAdjMat::new(vertices, edges); + println!("\nГраф после инициализации"); + graph.print(); + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.add_edge(0, 2); + println!("\nГраф после добавления ребра 1-2"); + graph.print(); + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.remove_edge(0, 1); + println!("\nГраф после удаления ребра 1-3"); + graph.print(); + + /* Добавление вершины */ + graph.add_vertex(6); + println!("\nГраф после добавления вершины 6"); + graph.print(); + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.remove_vertex(1); + println!("\nГраф после удаления вершины 3"); + graph.print(); +} diff --git a/ru/codes/rust/chapter_graph/graph_bfs.rs b/ru/codes/rust/chapter_graph/graph_bfs.rs new file mode 100644 index 000000000..027e43d53 --- /dev/null +++ b/ru/codes/rust/chapter_graph/graph_bfs.rs @@ -0,0 +1,69 @@ +/* + * File: graph_bfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::{HashSet, VecDeque}; + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // Последовательность обхода вершин + let mut res = vec![]; + // Хеш-множество для хранения уже посещенных вершин + let mut visited = HashSet::new(); + visited.insert(start_vet); + // Очередь используется для реализации BFS + let mut que = VecDeque::new(); + que.push_back(start_vet); + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while let Some(vet) = que.pop_front() { + res.push(vet); // Отметить посещенную вершину + + // Обойти все смежные вершины данной вершины + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // Пропустить уже посещенную вершину + } + que.push_back(adj_vet); // Помещать в очередь только непосещенные вершины + visited.insert(adj_vet); // Отметить эту вершину как посещенную + } + } + } + // Вернуть последовательность обхода вершин + res +} + +/* Driver Code */ +fn main() { + /* Инициализация неориентированного графа */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ]; + let graph = GraphAdjList::new(edges); + println!("\nГраф после инициализации"); + graph.print(); + + /* Обход в ширину */ + let res = graph_bfs(graph, v[0]); + println!("\nПоследовательность вершин при обходе в ширину (BFS)"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/ru/codes/rust/chapter_graph/graph_dfs.rs b/ru/codes/rust/chapter_graph/graph_dfs.rs new file mode 100644 index 000000000..c9c622c18 --- /dev/null +++ b/ru/codes/rust/chapter_graph/graph_dfs.rs @@ -0,0 +1,61 @@ +/* + * File: graph_dfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::HashSet; + +/* Вспомогательная функция обхода в глубину */ +fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { + res.push(vet); // Отметить посещенную вершину + visited.insert(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // Пропустить уже посещенную вершину + } + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adj_vet); + } + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // Последовательность обхода вершин + let mut res = vec![]; + // Хеш-множество для хранения уже посещенных вершин + let mut visited = HashSet::new(); + dfs(&graph, &mut visited, &mut res, start_vet); + + res +} + +/* Driver Code */ +fn main() { + /* Инициализация неориентированного графа */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + let graph = GraphAdjList::new(edges); + println!("\nГраф после инициализации"); + graph.print(); + + /* Обход в глубину */ + let res = graph_dfs(graph, v[0]); + println!("\nПоследовательность вершин при обходе в глубину (DFS)"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/ru/codes/rust/chapter_greedy/coin_change_greedy.rs b/ru/codes/rust/chapter_greedy/coin_change_greedy.rs new file mode 100644 index 000000000..67a59878f --- /dev/null +++ b/ru/codes/rust/chapter_greedy/coin_change_greedy.rs @@ -0,0 +1,54 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Размен монет: жадный алгоритм */ +fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { + // Предположить, что список coins упорядочен + let mut i = coins.len() - 1; + let mut count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while amt > 0 { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while i > 0 && coins[i] > amt { + i -= 1; + } + // Выбрать coins[i] + amt -= coins[i]; + count += 1; + } + // Если допустимое решение не найдено, вернуть -1 + if amt == 0 { + count + } else { + -1 + } +} + +/* Driver Code */ +fn main() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + let coins = [1, 5, 10, 20, 50, 100]; + let amt = 186; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("Минимальное число монет для набора суммы {} = {}", amt, res); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + let coins = [1, 20, 50]; + let amt = 60; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("Минимальное число монет для набора суммы {} = {}", amt, res); + println!("На самом деле минимум равен 3: 20 + 20 + 20"); + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + let coins = [1, 49, 50]; + let amt = 98; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("Минимальное число монет для набора суммы {} = {}", amt, res); + println!("На самом деле минимум равен 2: 49 + 49"); +} diff --git a/ru/codes/rust/chapter_greedy/fractional_knapsack.rs b/ru/codes/rust/chapter_greedy/fractional_knapsack.rs new file mode 100644 index 000000000..35490f902 --- /dev/null +++ b/ru/codes/rust/chapter_greedy/fractional_knapsack.rs @@ -0,0 +1,59 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Предмет */ +struct Item { + w: i32, // Вес предмета + v: i32, // Стоимость предмета +} + +impl Item { + fn new(w: i32, v: i32) -> Self { + Self { w, v } + } +} + +/* Дробный рюкзак: жадный алгоритм */ +fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { + // Создать список предметов с двумя свойствами: вес и стоимость + let mut items = wgt + .iter() + .zip(val.iter()) + .map(|(&w, &v)| Item::new(w, v)) + .collect::>(); + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort_by(|a, b| { + (b.v as f64 / b.w as f64) + .partial_cmp(&(a.v as f64 / a.w as f64)) + .unwrap() + }); + // Циклический жадный выбор + let mut res = 0.0; + for item in &items { + if item.w <= cap { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v as f64; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += item.v as f64 / item.w as f64 * cap as f64; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + res +} + +/* Driver Code */ +fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap = 50; + + // Жадный алгоритм + let res = fractional_knapsack(&wgt, &val, cap); + println!("Максимальная стоимость предметов без превышения вместимости рюкзака = {}", res); +} diff --git a/ru/codes/rust/chapter_greedy/max_capacity.rs b/ru/codes/rust/chapter_greedy/max_capacity.rs new file mode 100644 index 000000000..9952a985a --- /dev/null +++ b/ru/codes/rust/chapter_greedy/max_capacity.rs @@ -0,0 +1,36 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Максимальная вместимость: жадный алгоритм */ +fn max_capacity(ht: &[i32]) -> i32 { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + let mut i = 0; + let mut j = ht.len() - 1; + // Начальная максимальная вместимость равна 0 + let mut res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while i < j { + // Обновить максимальную вместимость + let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; + res = std::cmp::max(res, cap); + // Сдвигать внутрь более короткую сторону + if ht[i] < ht[j] { + i += 1; + } else { + j -= 1; + } + } + res +} + +/* Driver Code */ +fn main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // Жадный алгоритм + let res = max_capacity(&ht); + println!("Максимальная вместимость = {}", res); +} diff --git a/ru/codes/rust/chapter_greedy/max_product_cutting.rs b/ru/codes/rust/chapter_greedy/max_product_cutting.rs new file mode 100644 index 000000000..42094cb13 --- /dev/null +++ b/ru/codes/rust/chapter_greedy/max_product_cutting.rs @@ -0,0 +1,35 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Максимальное произведение разрезания: жадный алгоритм */ +fn max_product_cutting(n: i32) -> i32 { + // Когда n <= 3, обязательно нужно выделить одну 1 + if n <= 3 { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + let a = n / 3; + let b = n % 3; + if b == 1 { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + 3_i32.pow(a as u32 - 1) * 2 * 2 + } else if b == 2 { + // Если остаток равен 2, ничего не делать + 3_i32.pow(a as u32) * 2 + } else { + // Если остаток равен 0, ничего не делать + 3_i32.pow(a as u32) + } +} + +/* Driver Code */ +fn main() { + let n = 58; + + // Жадный алгоритм + let res = max_product_cutting(n); + println!("Максимальное произведение после разрезания = {}", res); +} diff --git a/ru/codes/rust/chapter_hashing/array_hash_map.rs b/ru/codes/rust/chapter_hashing/array_hash_map.rs new file mode 100644 index 000000000..baee122c8 --- /dev/null +++ b/ru/codes/rust/chapter_hashing/array_hash_map.rs @@ -0,0 +1,124 @@ +/** + * File: array_hash_map.rs + * Created Time: 2023-2-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com) + */ + +/* Пара ключ-значение */ +#[derive(Debug, Clone, PartialEq)] +pub struct Pair { + pub key: i32, + pub val: String, +} +/* Хеш-таблица на основе массива */ +pub struct ArrayHashMap { + buckets: Vec>, +} + +impl ArrayHashMap { + pub fn new() -> ArrayHashMap { + // Инициализировать массив, содержащий 100 корзин + Self { + buckets: vec![None; 100], + } + } + + /* Хеш-функция */ + fn hash_func(&self, key: i32) -> usize { + key as usize % 100 + } + + /* Операция поиска */ + pub fn get(&self, key: i32) -> Option<&String> { + let index = self.hash_func(key); + self.buckets[index].as_ref().map(|pair| &pair.val) + } + + /* Операция добавления */ + pub fn put(&mut self, key: i32, val: &str) { + let index = self.hash_func(key); + self.buckets[index] = Some(Pair { + key, + val: val.to_string(), + }); + } + + /* Операция удаления */ + pub fn remove(&mut self, key: i32) { + let index = self.hash_func(key); + // Присвоить None, что означает удаление + self.buckets[index] = None; + } + + /* Получить все пары ключ-значение */ + pub fn entry_set(&self) -> Vec<&Pair> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref()) + .collect() + } + + /* Получить все ключи */ + pub fn key_set(&self) -> Vec<&i32> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) + .collect() + } + + /* Получить все значения */ + pub fn value_set(&self) -> Vec<&String> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) + .collect() + } + + /* Вывести хеш-таблицу */ + pub fn print(&self) { + for pair in self.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + } +} + +fn main() { + /* Инициализация хеш-таблицы */ + let mut map = ArrayHashMap::new(); + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + let name = map.get(15937).unwrap(); + println!("\nДля номера 15937 найдено имя {}", name); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(10583); + println!("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + + /* Обход хеш-таблицы */ + println!("\nОтдельный обход пар ключ-значение"); + for pair in map.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + + println!("\nОтдельный обход ключей"); + for key in map.key_set() { + println!("{}", key); + } + + println!("\nОтдельный обход значений"); + for val in map.value_set() { + println!("{}", val); + } +} diff --git a/ru/codes/rust/chapter_hashing/build_in_hash.rs b/ru/codes/rust/chapter_hashing/build_in_hash.rs new file mode 100644 index 000000000..60a1a2eee --- /dev/null +++ b/ru/codes/rust/chapter_hashing/build_in_hash.rs @@ -0,0 +1,49 @@ +/* + * File: build_in_hash.rs + * Created Time: 2023-7-6 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::ListNode; + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +/* Driver Code */ +fn main() { + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + println!("Хеш-значение целого числа {} = {}", num, hash_num); + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + println!("Хеш-значение булева значения {} = {}", bol, hash_bol); + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + println!("Хеш-значение десятичного числа {} = {}", dec, hash_dec); + + let str = "Hello Algo"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + println!("Хеш-значение строки {} = {}", str, hash_str); + + let arr = (&12836, &"Сяо Ха"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + println!("Хеш-значение кортежа {:?} = {}", arr, hash_tup); + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + println!("Хеш-значение объекта узла {:?} = {}", node, hash); +} diff --git a/ru/codes/rust/chapter_hashing/hash_map.rs b/ru/codes/rust/chapter_hashing/hash_map.rs new file mode 100644 index 000000000..3272de284 --- /dev/null +++ b/ru/codes/rust/chapter_hashing/hash_map.rs @@ -0,0 +1,48 @@ +/* + * File: hash_map.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::HashMap; + +/* Driver Code */ +pub fn main() { + // Инициализация хеш-таблицы + let mut map = HashMap::new(); + + // Операция добавления + // Добавить пару (key, value) в хеш-таблицу + map.insert(12836, "Сяо Ха"); + map.insert(15937, "Сяо Ло"); + map.insert(16750, "Сяо Суань"); + map.insert(13276, "Сяо Фа"); + map.insert(10583, "Сяо Я"); + println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + print_util::print_hash_map(&map); + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение value + let name = map.get(&15937).copied().unwrap(); + println!("\nДля номера 15937 найдено имя {name}"); + + // Операция удаления + // Удалить пару (key, value) из хеш-таблицы + _ = map.remove(&10583); + println!("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"); + print_util::print_hash_map(&map); + + // Обход хеш-таблицы + println!("\nОтдельный обход пар ключ-значение"); + print_util::print_hash_map(&map); + println!("\nОтдельный обход ключей"); + for key in map.keys() { + println!("{key}"); + } + println!("\nОтдельный обход значений"); + for value in map.values() { + println!("{value}"); + } +} diff --git a/ru/codes/rust/chapter_hashing/hash_map_chaining.rs b/ru/codes/rust/chapter_hashing/hash_map_chaining.rs new file mode 100644 index 000000000..72384cc49 --- /dev/null +++ b/ru/codes/rust/chapter_hashing/hash_map_chaining.rs @@ -0,0 +1,156 @@ +/* + * File: hash_map_chaining.rs + * Created Time: 2023-07-07 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +#[derive(Clone)] +/* Пара ключ-значение */ +struct Pair { + key: i32, + val: String, +} + +/* Хеш-таблица с цепочками */ +struct HashMapChaining { + size: usize, + capacity: usize, + load_thres: f32, + extend_ratio: usize, + buckets: Vec>, +} + +impl HashMapChaining { + /* Конструктор */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![vec![]; 4], + } + } + + /* Хеш-функция */ + fn hash_func(&self, key: i32) -> usize { + key as usize % self.capacity + } + + /* Коэффициент загрузки */ + fn load_factor(&self) -> f32 { + self.size as f32 / self.capacity as f32 + } + + /* Операция удаления */ + fn remove(&mut self, key: i32) -> Option { + let index = self.hash_func(key); + + // Обойти корзину и удалить из нее пару ключ-значение + for (i, p) in self.buckets[index].iter_mut().enumerate() { + if p.key == key { + let pair = self.buckets[index].remove(i); + self.size -= 1; + return Some(pair.val); + } + } + + // Если key не найден, вернуть None + None + } + + /* Расширить хеш-таблицу */ + fn extend(&mut self) { + // Временно сохранить исходную хеш-таблицу + let buckets_tmp = std::mem::take(&mut self.buckets); + + // Инициализация новой хеш-таблицы после расширения + self.capacity *= self.extend_ratio; + self.buckets = vec![Vec::new(); self.capacity as usize]; + self.size = 0; + + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for bucket in buckets_tmp { + for pair in bucket { + self.put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + fn print(&self) { + for bucket in &self.buckets { + let mut res = Vec::new(); + for pair in bucket { + res.push(format!("{} -> {}", pair.key, pair.val)); + } + println!("{:?}", res); + } + } + + /* Операция добавления */ + fn put(&mut self, key: i32, val: String) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if self.load_factor() > self.load_thres { + self.extend(); + } + + let index = self.hash_func(key); + + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for pair in self.buckets[index].iter_mut() { + if pair.key == key { + pair.val = val; + return; + } + } + + // Если такого key нет, добавить пару ключ-значение в конец + let pair = Pair { key, val }; + self.buckets[index].push(pair); + self.size += 1; + } + + /* Операция поиска */ + fn get(&self, key: i32) -> Option<&str> { + let index = self.hash_func(key); + + // Обойти корзину; если найден key, вернуть соответствующее val + for pair in self.buckets[index].iter() { + if pair.key == key { + return Some(&pair.val); + } + } + + // Если key не найден, вернуть None + None + } +} + +/* Driver Code */ +pub fn main() { + /* Инициализация хеш-таблицы */ + let mut map = HashMapChaining::new(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха".to_string()); + map.put(15937, "Сяо Ло".to_string()); + map.put(16750, "Сяо Суань".to_string()); + map.put(13276, "Сяо Фа".to_string()); + map.put(10583, "Сяо Я".to_string()); + println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + println!( + "\nПо номеру 13276 найдено имя {}",\nmatch map.get(13276) {\n Some(value) => value,\n None => "Not a valid Key",\n} + ); + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(12836); + println!("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение"); + map.print(); +} diff --git a/ru/codes/rust/chapter_hashing/hash_map_open_addressing.rs b/ru/codes/rust/chapter_hashing/hash_map_open_addressing.rs new file mode 100644 index 000000000..32f4bf299 --- /dev/null +++ b/ru/codes/rust/chapter_hashing/hash_map_open_addressing.rs @@ -0,0 +1,181 @@ +/* + * File: hash_map_open_addressing.rs + * Created Time: 2023-07-16 + * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) + */ +#![allow(non_snake_case)] +#![allow(unused)] + +mod array_hash_map; + +use array_hash_map::Pair; + +/* Хеш-таблица с открытой адресацией */ +struct HashMapOpenAddressing { + size: usize, // Число пар ключ-значение + capacity: usize, // Вместимость хеш-таблицы + load_thres: f64, // Порог коэффициента загрузки для запуска расширения + extend_ratio: usize, // Коэффициент расширения + buckets: Vec>, // Массив корзин + TOMBSTONE: Option, // Удалить метку +} + +impl HashMapOpenAddressing { + /* Конструктор */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![None; 4], + TOMBSTONE: Some(Pair { + key: -1, + val: "-1".to_string(), + }), + } + } + + /* Хеш-функция */ + fn hash_func(&self, key: i32) -> usize { + (key % self.capacity as i32) as usize + } + + /* Коэффициент загрузки */ + fn load_factor(&self) -> f64 { + self.size as f64 / self.capacity as f64 + } + + /* Найти индекс корзины, соответствующий key */ + fn find_bucket(&mut self, key: i32) -> usize { + let mut index = self.hash_func(key); + let mut first_tombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while self.buckets[index].is_some() { + // Если встретился key, вернуть соответствующий индекс корзины + if self.buckets[index].as_ref().unwrap().key == key { + // Если ранее встретилась метка удаления, переместить пару ключ-значение в этот индекс + if first_tombstone != -1 { + self.buckets[first_tombstone as usize] = self.buckets[index].take(); + self.buckets[index] = self.TOMBSTONE.clone(); + return first_tombstone as usize; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { + first_tombstone = index as i32; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % self.capacity; + } + // Если key не существует, вернуть индекс точки добавления + if first_tombstone == -1 { + index + } else { + first_tombstone as usize + } + } + + /* Операция поиска */ + fn get(&mut self, key: i32) -> Option<&str> { + // Найти индекс корзины, соответствующий key + let index = self.find_bucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + return self.buckets[index].as_ref().map(|pair| &pair.val as &str); + } + // Если пары ключ-значение не существует, вернуть null + None + } + + /* Операция добавления */ + fn put(&mut self, key: i32, val: String) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if self.load_factor() > self.load_thres { + self.extend(); + } + // Найти индекс корзины, соответствующий key + let index = self.find_bucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index].as_mut().unwrap().val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + self.buckets[index] = Some(Pair { key, val }); + self.size += 1; + } + + /* Операция удаления */ + fn remove(&mut self, key: i32) { + // Найти индекс корзины, соответствующий key + let index = self.find_bucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index] = self.TOMBSTONE.clone(); + self.size -= 1; + } + } + + /* Расширить хеш-таблицу */ + fn extend(&mut self) { + // Временно сохранить исходную хеш-таблицу + let buckets_tmp = self.buckets.clone(); + // Инициализация новой хеш-таблицы после расширения + self.capacity *= self.extend_ratio; + self.buckets = vec![None; self.capacity]; + self.size = 0; + + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for pair in buckets_tmp { + if pair.is_none() || pair == self.TOMBSTONE { + continue; + } + let pair = pair.unwrap(); + + self.put(pair.key, pair.val); + } + } + /* Вывести хеш-таблицу */ + fn print(&self) { + for pair in &self.buckets { + if pair.is_none() { + println!("null"); + } else if pair == &self.TOMBSTONE { + println!("TOMBSTONE"); + } else { + let pair = pair.as_ref().unwrap(); + println!("{} -> {}", pair.key, pair.val); + } + } + } +} + +/* Driver Code */ +fn main() { + /* Инициализация хеш-таблицы */ + let mut hashmap = HashMapOpenAddressing::new(); + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + hashmap.put(12836, "Сяо Ха".to_string()); + hashmap.put(15937, "Сяо Ло".to_string()); + hashmap.put(16750, "Сяо Суань".to_string()); + hashmap.put(13276, "Сяо Фа".to_string()); + hashmap.put(10583, "Сяо Я".to_string()); + + println!("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"); + hashmap.print(); + + /* Операция поиска */ + // Передать ключ key в хеш-таблицу и получить значение val + let name = hashmap.get(13276).unwrap(); + println!("\nДля номера 13276 найдено имя {}", name); + + /* Операция удаления */ + // Удалить пару (key, val) из хеш-таблицы + hashmap.remove(16750); + println!("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"); + hashmap.print(); +} diff --git a/ru/codes/rust/chapter_hashing/simple_hash.rs b/ru/codes/rust/chapter_hashing/simple_hash.rs new file mode 100644 index 000000000..e09898470 --- /dev/null +++ b/ru/codes/rust/chapter_hashing/simple_hash.rs @@ -0,0 +1,70 @@ +/* + * File: simple_hash.rs + * Created Time: 2023-09-07 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Аддитивное хеширование */ +fn add_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* Мультипликативное хеширование */ +fn mul_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (31 * hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* XOR-хеширование */ +fn xor_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash ^= c as i64; + } + + (hash & MODULUS) as i32 +} + +/* Хеширование с циклическим сдвигом */ +fn rot_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; + } + + hash as i32 +} + +/* Driver Code */ +fn main() { + let key = "Hello Algo"; + + let hash = add_hash(key); + println!("Хеш-сумма сложением = {hash}"); + + let hash = mul_hash(key); + println!("Хеш-сумма умножением = {hash}"); + + let hash = xor_hash(key); + println!("Хеш-сумма XOR = {hash}"); + + let hash = rot_hash(key); + println!("Хеш-сумма с циклическим сдвигом = {hash}"); +} diff --git a/ru/codes/rust/chapter_heap/heap.rs b/ru/codes/rust/chapter_heap/heap.rs new file mode 100644 index 000000000..ec4ed3ca3 --- /dev/null +++ b/ru/codes/rust/chapter_heap/heap.rs @@ -0,0 +1,71 @@ +/* + * File: heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::{cmp::Reverse, collections::BinaryHeap}; + +fn test_push_max(heap: &mut BinaryHeap, val: i32) { + heap.push(val); // Добавление элемента в кучу + println!("\nПосле добавления элемента {} в кучу", val); + print_util::print_heap(heap.iter().map(|&val| val).collect()); +} + +fn test_pop_max(heap: &mut BinaryHeap) { + let val = heap.pop().unwrap(); + println!("\nПосле извлечения элемента вершины кучи {}", val); + print_util::print_heap(heap.iter().map(|&val| val).collect()); +} + +/* Driver Code */ +fn main() { + /* Инициализация кучи */ + // Инициализация минимальной кучи + #[allow(unused_assignments)] + let mut min_heap = BinaryHeap::new(); + // BinaryHeap в Rust является максимальной кучей; для минимальной кучи обычно используют оболочку Reverse + // Инициализировать максимальную кучу + let mut max_heap = BinaryHeap::new(); + + println!("\nНиже приведен тестовый пример для max-heap"); + + /* Добавление элемента в кучу */ + test_push_max(&mut max_heap, 1); + test_push_max(&mut max_heap, 3); + test_push_max(&mut max_heap, 2); + test_push_max(&mut max_heap, 5); + test_push_max(&mut max_heap, 4); + + /* Получение элемента с вершины кучи */ + let peek = max_heap.peek().unwrap(); + println!("\nЭлемент на вершине кучи = {}", peek); + + /* Извлечение элемента с вершины кучи */ + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + + /* Получение размера кучи */ + let size = max_heap.len(); + println!("\nКоличество элементов в куче = {}", size); + + /* Проверка, пуста ли куча */ + let is_empty = max_heap.is_empty(); + println!("\nПуста ли куча: {}", is_empty); + + /* Построить кучу по входному списку */ + // Временная сложность равна O(n), а не O(nlogn) + min_heap = BinaryHeap::from( + vec![1, 3, 2, 5, 4] + .into_iter() + .map(|val| Reverse(val)) + .collect::>>(), + ); + println!("\nПосле построения min-heap из входного списка"); + print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); +} diff --git a/ru/codes/rust/chapter_heap/my_heap.rs b/ru/codes/rust/chapter_heap/my_heap.rs new file mode 100644 index 000000000..f267cd6aa --- /dev/null +++ b/ru/codes/rust/chapter_heap/my_heap.rs @@ -0,0 +1,165 @@ +/* + * File: my_heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* Максимальная куча */ +struct MaxHeap { + // Использовать vector вместо массива, чтобы не учитывать проблему расширения + max_heap: Vec, +} + +impl MaxHeap { + /* Конструктор, строящий кучу по входному списку */ + fn new(nums: Vec) -> Self { + // Добавить элементы списка в кучу без изменений + let mut heap = MaxHeap { max_heap: nums }; + // Выполнить heapify для всех узлов, кроме листовых + for i in (0..=Self::parent(heap.size() - 1)).rev() { + heap.sift_down(i); + } + heap + } + + /* Получить индекс левого дочернего узла */ + fn left(i: usize) -> usize { + 2 * i + 1 + } + + /* Получить индекс правого дочернего узла */ + fn right(i: usize) -> usize { + 2 * i + 2 + } + + /* Получить индекс родительского узла */ + fn parent(i: usize) -> usize { + (i - 1) / 2 // Округление вниз при делении + } + + /* Поменять элементы местами */ + fn swap(&mut self, i: usize, j: usize) { + self.max_heap.swap(i, j); + } + + /* Получение размера кучи */ + fn size(&self) -> usize { + self.max_heap.len() + } + + /* Проверка, пуста ли куча */ + fn is_empty(&self) -> bool { + self.max_heap.is_empty() + } + + /* Доступ к элементу на вершине кучи */ + fn peek(&self) -> Option { + self.max_heap.first().copied() + } + + /* Добавление элемента в кучу */ + fn push(&mut self, val: i32) { + // Добавление узла + self.max_heap.push(val); + // Просеивание снизу вверх + self.sift_up(self.size() - 1); + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + fn sift_up(&mut self, mut i: usize) { + loop { + // Если узел i уже является вершиной кучи, завершить просеивание + if i == 0 { + break; + } + // Получение родительского узла для узла i + let p = Self::parent(i); + // Когда «узел не требует исправления», завершить просеивание + if self.max_heap[i] <= self.max_heap[p] { + break; + } + // Поменять два узла местами + self.swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + fn pop(&mut self) -> i32 { + // Обработка пустого случая + if self.is_empty() { + panic!("index out of bounds"); + } + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + self.swap(0, self.size() - 1); + // Удаление узла + let val = self.max_heap.pop().unwrap(); + // Просеивание сверху вниз + self.sift_down(0); + // Вернуть элемент с вершины кучи + val + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + fn sift_down(&mut self, mut i: usize) { + loop { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + let (l, r, mut ma) = (Self::left(i), Self::right(i), i); + if l < self.size() && self.max_heap[l] > self.max_heap[ma] { + ma = l; + } + if r < self.size() && self.max_heap[r] > self.max_heap[ma] { + ma = r; + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i { + break; + } + // Поменять два узла местами + self.swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Вывести кучу (двоичное дерево) */ + fn print(&self) { + print_util::print_heap(self.max_heap.clone()); + } +} + +/* Driver Code */ +fn main() { + /* Инициализация максимальной кучи */ + let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + println!("\nПосле построения кучи из входного списка"); + max_heap.print(); + + /* Получение элемента с вершины кучи */ + let peek = max_heap.peek(); + if let Some(peek) = peek { + println!("\nЭлемент на вершине кучи = {}", peek); + } + + /* Добавление элемента в кучу */ + let val = 7; + max_heap.push(val); + println!("\nПосле добавления элемента {} в кучу", val); + max_heap.print(); + + /* Извлечение элемента с вершины кучи */ + let peek = max_heap.pop(); + println!("\nПосле извлечения элемента вершины кучи {}", peek); + max_heap.print(); + + /* Получение размера кучи */ + let size = max_heap.size(); + println!("\nКоличество элементов в куче = {}", size); + + /* Проверка, пуста ли куча */ + let is_empty = max_heap.is_empty(); + println!("\nПуста ли куча: {}", is_empty); +} diff --git a/ru/codes/rust/chapter_heap/top_k.rs b/ru/codes/rust/chapter_heap/top_k.rs new file mode 100644 index 000000000..7ff708a3d --- /dev/null +++ b/ru/codes/rust/chapter_heap/top_k.rs @@ -0,0 +1,39 @@ +/* + * File: top_k.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cmp::Reverse; +use std::collections::BinaryHeap; + +/* Найти k наибольших элементов массива с помощью кучи */ +fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { + // BinaryHeap — это максимальная куча; с помощью Reverse элементы инвертируются, чтобы реализовать минимальную кучу + let mut heap = BinaryHeap::>::new(); + // Поместить первые k элементов массива в кучу + for &num in nums.iter().take(k) { + heap.push(Reverse(num)); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for &num in nums.iter().skip(k) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if num > heap.peek().unwrap().0 { + heap.pop(); + heap.push(Reverse(num)); + } + } + heap +} + +/* Driver Code */ +fn main() { + let nums = vec![1, 7, 6, 3, 2]; + let k = 3; + + let res = top_k_heap(nums, k); + println!("Наибольшие {} элементов", k); + print_util::print_heap(res.into_iter().map(|item| item.0).collect()); +} diff --git a/ru/codes/rust/chapter_searching/binary_search.rs b/ru/codes/rust/chapter_searching/binary_search.rs new file mode 100644 index 000000000..7ad51483b --- /dev/null +++ b/ru/codes/rust/chapter_searching/binary_search.rs @@ -0,0 +1,65 @@ +/* + * File: binary_search.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + let mut i = 0; + let mut j = nums.len() as i32 - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while i <= j { + let m = i + (j - i) / 2; // Вычислить индекс середины m + if nums[m as usize] < target { + // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + } else if nums[m as usize] > target { + // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + let mut i = 0; + let mut j = nums.len() as i32; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while i < j { + let m = i + (j - i) / 2; // Вычислить индекс середины m + if nums[m as usize] < target { + // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + } else if nums[m as usize] > target { + // Это означает, что target находится в интервале [i, m) + j = m; + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // Бинарный поиск (двусторонне замкнутый интервал) + let mut index = binary_search(&nums, target); + println!("Индекс целевого элемента 6 = {index}"); + + // Бинарный поиск (лево замкнутый, право открытый интервал) + index = binary_search_lcro(&nums, target); + println!("Индекс целевого элемента 6 = {index}"); +} diff --git a/ru/codes/rust/chapter_searching/binary_search_edge.rs b/ru/codes/rust/chapter_searching/binary_search_edge.rs new file mode 100644 index 000000000..df7a2ee95 --- /dev/null +++ b/ru/codes/rust/chapter_searching/binary_search_edge.rs @@ -0,0 +1,50 @@ +/* + * File: binary_search_edge.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ + +mod binary_search_insertion; + +use binary_search_insertion::binary_search_insertion; + +/* Бинарный поиск самого левого target */ +fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { + // Эквивалентно поиску точки вставки target + let i = binary_search_insertion(nums, target); + // target не найден, вернуть -1 + if i == nums.len() as i32 || nums[i as usize] != target { + return -1; + } + // Найти target и вернуть индекс i + i +} + +/* Бинарный поиск самого правого target */ +fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { + // Преобразовать задачу в поиск самого левого target + 1 + let i = binary_search_insertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + let j = i - 1; + // target не найден, вернуть -1 + if j == -1 || nums[j as usize] != target { + return -1; + } + // Найти target и вернуть индекс j + j +} + +/* Driver Code */ +fn main() { + // Массив с повторяющимися элементами + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\nМассив nums = {:?}", nums); + + // Бинарный поиск левой и правой границы + for target in [6, 7] { + let index = binary_search_left_edge(&nums, target); + println!("Индекс самого левого элемента {} равен {}", target, index); + let index = binary_search_right_edge(&nums, target); + println!("Индекс самого правого элемента {} равен {}", target, index); + } +} diff --git a/ru/codes/rust/chapter_searching/binary_search_insertion.rs b/ru/codes/rust/chapter_searching/binary_search_insertion.rs new file mode 100644 index 000000000..13a9f5756 --- /dev/null +++ b/ru/codes/rust/chapter_searching/binary_search_insertion.rs @@ -0,0 +1,61 @@ +/* + * File: binary_search_insertion.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ +#![allow(unused)] + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // Инициализировать двусторонне замкнутый интервал [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // Вычислить индекс середины m + if nums[m as usize] < target { + i = m + 1; // target находится в интервале [m+1, j] + } else if nums[m as usize] > target { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; + } + } + // target не найден, вернуть точку вставки i + i +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // Инициализировать двусторонне замкнутый интервал [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // Вычислить индекс середины m + if nums[m as usize] < target { + i = m + 1; // target находится в интервале [m+1, j] + } else if nums[m as usize] > target { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + i +} + +/* Driver Code */ +fn main() { + // Массив без повторяющихся элементов + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + println!("\nМассив nums = {:?}", nums); + // Бинарный поиск точки вставки + for target in [6, 9] { + let index = binary_search_insertion_simple(&nums, target); + println!("Индекс позиции вставки элемента {} равен {}", target, index); + } + + // Массив с повторяющимися элементами + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\nМассив nums = {:?}", nums); + // Бинарный поиск точки вставки + for target in [2, 6, 20] { + let index = binary_search_insertion(&nums, target); + println!("Индекс позиции вставки элемента {} равен {}", target, index); + } +} diff --git a/ru/codes/rust/chapter_searching/hashing_search.rs b/ru/codes/rust/chapter_searching/hashing_search.rs new file mode 100644 index 000000000..3da866fac --- /dev/null +++ b/ru/codes/rust/chapter_searching/hashing_search.rs @@ -0,0 +1,50 @@ +/* + * File: hashing_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* Хеш-поиск (массив) */ +fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть None + map.get(&target) +} + +/* Хеш-поиск (связный список) */ +fn hashing_search_linked_list( + map: &HashMap>>>, + target: i32, +) -> Option<&Rc>>> { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть None + map.get(&target) +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* Хеш-поиск (массив) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // Инициализация хеш-таблицы + let mut map = HashMap::new(); + for (i, num) in nums.iter().enumerate() { + map.insert(*num, i); // key: элемент, value: индекс + } + let index = hashing_search_array(&map, target); + println!("Индекс целевого элемента 3 = {}", index.unwrap()); + + /* Хеш-поиск (связный список) */ + let head = ListNode::arr_to_linked_list(&nums); + // Инициализировать хеш-таблицу + // let mut map1 = HashMap::new(); + let map1 = ListNode::linked_list_to_hashmap(head); + let node = hashing_search_linked_list(&map1, target); + println!("Объект узла со значением 3 = {:?}", node); +} diff --git a/ru/codes/rust/chapter_searching/linear_search.rs b/ru/codes/rust/chapter_searching/linear_search.rs new file mode 100644 index 000000000..a64183fa6 --- /dev/null +++ b/ru/codes/rust/chapter_searching/linear_search.rs @@ -0,0 +1,54 @@ +/* + * File: linear_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* Линейный поиск (массив) */ +fn linear_search_array(nums: &[i32], target: i32) -> i32 { + // Обход массива + for (i, num) in nums.iter().enumerate() { + // Целевой элемент найден, вернуть его индекс + if num == &target { + return i as i32; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Линейный поиск (связный список) */ +fn linear_search_linked_list( + head: Rc>>, + target: i32, +) -> Option>>> { + // Найти целевой узел и вернуть его + if head.borrow().val == target { + return Some(head); + }; + // Найти целевой узел и вернуть его + if let Some(node) = &head.borrow_mut().next { + return linear_search_linked_list(node.clone(), target); + } + // Целевой узел не найден, вернуть None + return None; +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* Выполнить линейный поиск в массиве */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + let index = linear_search_array(&nums, target); + println!("Индекс целевого элемента 3 = {}", index); + + /* Выполнить линейный поиск в связном списке */ + let head = ListNode::arr_to_linked_list(&nums); + let node = linear_search_linked_list(head.unwrap(), target); + println!("Объект узла со значением 3 = {:?}", node); +} diff --git a/ru/codes/rust/chapter_searching/two_sum.rs b/ru/codes/rust/chapter_searching/two_sum.rs new file mode 100644 index 000000000..737e03aa1 --- /dev/null +++ b/ru/codes/rust/chapter_searching/two_sum.rs @@ -0,0 +1,52 @@ +/* + * File: two_sum.rs + * Created Time: 2023-01-14 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::HashMap; + +/* Метод 1: полный перебор */ +pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { + let size = nums.len(); + // Два вложенных цикла, временная сложность O(n^2) + for i in 0..size - 1 { + for j in i + 1..size { + if nums[i] + nums[j] == target { + return Some(vec![i as i32, j as i32]); + } + } + } + None +} + +/* Метод 2: вспомогательная хеш-таблица */ +pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { + // Вспомогательная хеш-таблица, пространственная сложность O(n) + let mut dic = HashMap::new(); + // Один цикл, временная сложность O(n) + for (i, num) in nums.iter().enumerate() { + match dic.get(&(target - num)) { + Some(v) => return Some(vec![*v as i32, i as i32]), + None => dic.insert(num, i as i32), + }; + } + None +} + +fn main() { + // ======= Test Case ======= + let nums = vec![2, 7, 11, 15]; + let target = 13; + + // ====== Основной код ====== + // Метод 1 + let res = two_sum_brute_force(&nums, target).unwrap(); + print!("Результат метода 1 res = "); + print_util::print_array(&res); + // Метод 2 + let res = two_sum_hash_table(&nums, target).unwrap(); + print!("\nРезультат метода 2 res = "); + print_util::print_array(&res); +} diff --git a/ru/codes/rust/chapter_sorting/bubble_sort.rs b/ru/codes/rust/chapter_sorting/bubble_sort.rs new file mode 100644 index 000000000..4ecf1cac4 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/bubble_sort.rs @@ -0,0 +1,53 @@ +/* + * File: bubble_sort.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Пузырьковая сортировка */ +fn bubble_sort(nums: &mut [i32]) { + // Внешний цикл: неотсортированный диапазон [0, i] + for i in (1..nums.len()).rev() { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0..i { + if nums[j] > nums[j + 1] { + // Поменять местами nums[j] и nums[j + 1] + nums.swap(j, j + 1); + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +fn bubble_sort_with_flag(nums: &mut [i32]) { + // Внешний цикл: неотсортированный диапазон [0, i] + for i in (1..nums.len()).rev() { + let mut flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0..i { + if nums[j] > nums[j + 1] { + // Поменять местами nums[j] и nums[j + 1] + nums.swap(j, j + 1); + flag = true; // Записать обмен элементов + } + } + if !flag { + break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + }; + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + bubble_sort(&mut nums); + print!("После пузырьковой сортировки nums = "); + print_util::print_array(&nums); + + let mut nums1 = [4, 1, 3, 1, 5, 2]; + bubble_sort_with_flag(&mut nums1); + print!("\nПосле пузырьковой сортировки nums1 = "); + print_util::print_array(&nums1); +} diff --git a/ru/codes/rust/chapter_sorting/bucket_sort.rs b/ru/codes/rust/chapter_sorting/bucket_sort.rs new file mode 100644 index 000000000..a56d0cd65 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/bucket_sort.rs @@ -0,0 +1,43 @@ +/* + * File: bucket_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* Сортировка корзинами */ +fn bucket_sort(nums: &mut [f64]) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + let k = nums.len() / 2; + let mut buckets = vec![vec![]; k]; + // 1. Распределить элементы массива по корзинам + for &num in nums.iter() { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + let i = (num * k as f64) as usize; + // Добавить num в корзину i + buckets[i].push(num); + } + // 2. Выполнить сортировку внутри каждой корзины + for bucket in &mut buckets { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); + } + // 3. Обойти корзины и объединить результаты + let mut i = 0; + for bucket in buckets.iter() { + for &num in bucket.iter() { + nums[i] = num; + i += 1; + } + } +} + +/* Driver Code */ +fn main() { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucket_sort(&mut nums); + print!("После сортировки корзинами nums = "); + print_util::print_array(&nums); +} diff --git a/ru/codes/rust/chapter_sorting/counting_sort.rs b/ru/codes/rust/chapter_sorting/counting_sort.rs new file mode 100644 index 000000000..32b5fe15e --- /dev/null +++ b/ru/codes/rust/chapter_sorting/counting_sort.rs @@ -0,0 +1,70 @@ +/* + * File: counting_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +fn counting_sort_naive(nums: &mut [i32]) { + // 1. Найти максимальный элемент массива m + let m = *nums.iter().max().unwrap(); + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + let mut counter = vec![0; m as usize + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + let mut i = 0; + for num in 0..m + 1 { + for _ in 0..counter[num as usize] { + nums[i] = num; + i += 1; + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +fn counting_sort(nums: &mut [i32]) { + // 1. Найти максимальный элемент массива m + let m = *nums.iter().max().unwrap() as usize; + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + let mut counter = vec![0; m + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for i in 0..m { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + let n = nums.len(); + let mut res = vec![0; n]; + for i in (0..n).rev() { + let num = nums[i]; + res[counter[num as usize] - 1] = num; // Поместить num по соответствующему индексу + counter[num as usize] -= 1; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + nums.copy_from_slice(&res) +} + +/* Driver Code */ +fn main() { + let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort_naive(&mut nums); + print!("После сортировки подсчетом (объекты не поддерживаются) nums = "); + print_util::print_array(&nums); + + let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort(&mut nums1); + print!("\nПосле сортировки подсчетом nums1 = "); + print_util::print_array(&nums1); +} diff --git a/ru/codes/rust/chapter_sorting/heap_sort.rs b/ru/codes/rust/chapter_sorting/heap_sort.rs new file mode 100644 index 000000000..f189d1fb3 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/heap_sort.rs @@ -0,0 +1,54 @@ +/* + * File: heap_sort.rs + * Created Time: 2023-07-04 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { + loop { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let mut ma = i; + if l < n && nums[l] > nums[ma] { + ma = l; + } + if r < n && nums[r] > nums[ma] { + ma = r; + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i { + break; + } + // Поменять два узла местами + nums.swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } +} + +/* Сортировка кучей */ +fn heap_sort(nums: &mut [i32]) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for i in (0..nums.len() / 2).rev() { + sift_down(nums, nums.len(), i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for i in (1..nums.len()).rev() { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + nums.swap(0, i); + // Начиная с корневого узла, выполнить просеивание сверху вниз + sift_down(nums, i, 0); + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + heap_sort(&mut nums); + print!("После сортировки кучей nums = "); + print_util::print_array(&nums); +} diff --git a/ru/codes/rust/chapter_sorting/insertion_sort.rs b/ru/codes/rust/chapter_sorting/insertion_sort.rs new file mode 100644 index 000000000..278294d13 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/insertion_sort.rs @@ -0,0 +1,29 @@ +/* + * File: insertion_sort.rs + * Created Time: 2023-02-13 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* Сортировка вставками */ +fn insertion_sort(nums: &mut [i32]) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for i in 1..nums.len() { + let (base, mut j) = (nums[i], (i - 1) as i32); + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while j >= 0 && nums[j as usize] > base { + nums[(j + 1) as usize] = nums[j as usize]; // Сдвинуть nums[j] на одну позицию вправо + j -= 1; + } + nums[(j + 1) as usize] = base; // Поместить base в правильную позицию + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + insertion_sort(&mut nums); + print!("После сортировки вставками nums = "); + print_util::print_array(&nums); +} diff --git a/ru/codes/rust/chapter_sorting/merge_sort.rs b/ru/codes/rust/chapter_sorting/merge_sort.rs new file mode 100644 index 000000000..c22ac71d9 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/merge_sort.rs @@ -0,0 +1,66 @@ +/** + * File: merge_sort.rs + * Created Time: 2023-02-14 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +/* Объединить левый и правый подмассивы */ +fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + let tmp_size = right - left + 1; + let mut tmp = vec![0; tmp_size]; + // Инициализировать начальные индексы левого и правого подмассивов + let (mut i, mut j, mut k) = (left, mid + 1, 0); + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i]; + i += 1; + } else { + tmp[k] = nums[j]; + j += 1; + } + k += 1; + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while i <= mid { + tmp[k] = nums[i]; + k += 1; + i += 1; + } + while j <= right { + tmp[k] = nums[j]; + k += 1; + j += 1; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for k in 0..tmp_size { + nums[left + k] = tmp[k]; + } +} + +/* Сортировка слиянием */ +fn merge_sort(nums: &mut [i32], left: usize, right: usize) { + // Условие завершения + if left >= right { + return; // Завершить рекурсию, когда длина подмассива равна 1 + } + + // Этап разбиения + let mid = left + (right - left) / 2; // Вычислить середину + merge_sort(nums, left, mid); // Рекурсивно обработать левый подмассив + merge_sort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + + // Этап слияния + merge(nums, left, mid, right); +} + +/* Driver Code */ +fn main() { + /* Сортировка слиянием */ + let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; + let right = nums.len() - 1; + merge_sort(&mut nums, 0, right); + println!("После сортировки слиянием nums = {:?}", nums); +} diff --git a/ru/codes/rust/chapter_sorting/quick_sort.rs b/ru/codes/rust/chapter_sorting/quick_sort.rs new file mode 100644 index 000000000..42519eb14 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/quick_sort.rs @@ -0,0 +1,148 @@ +/** + * File: quick_sort.rs + * Created Time: 2023-02-16 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +/* Быстрая сортировка */ +struct QuickSort; + +impl QuickSort { + /* Разбиение с опорными указателями */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // Взять nums[left] в качестве опорного элемента + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + } + while i < j && nums[i] <= nums[left] { + i += 1; // Идти слева направо в поисках первого элемента больше опорного + } + nums.swap(i, j); // Поменять эти два элемента местами + } + nums.swap(i, left); // Переместить опорный элемент на границу двух подмассивов + i // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // Завершить рекурсию, когда длина подмассива равна 1 + if left >= right { + return; + } + // Разбиение с опорными указателями + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // Рекурсивно обработать левый и правый подмассивы + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* Быстрая сортировка (оптимизация медианным опорным элементом) */ +struct QuickSortMedian; + +impl QuickSortMedian { + /* Выбрать медиану из трех кандидатов */ + fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { + let (l, m, r) = (nums[left], nums[mid], nums[right]); + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid; // m находится между l и r + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left; // l находится между m и r + } + right + } + + /* Разбиение с опорными указателями (медиана трех) */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // Выбрать медиану из трех кандидатов + let med = Self::median_three(nums, left, (left + right) / 2, right); + // Переместить медиану в крайний левый элемент массива + nums.swap(left, med); + // Взять nums[left] в качестве опорного элемента + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + } + while i < j && nums[i] <= nums[left] { + i += 1; // Идти слева направо в поисках первого элемента больше опорного + } + nums.swap(i, j); // Поменять эти два элемента местами + } + nums.swap(i, left); // Переместить опорный элемент на границу двух подмассивов + i // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // Завершить рекурсию, когда длина подмассива равна 1 + if left >= right { + return; + } + // Разбиение с опорными указателями + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // Рекурсивно обработать левый и правый подмассивы + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +struct QuickSortTailCall; + +impl QuickSortTailCall { + /* Разбиение с опорными указателями */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // Взять nums[left] в качестве опорного элемента + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + } + while i < j && nums[i] <= nums[left] { + i += 1; // Идти слева направо в поисках первого элемента больше опорного + } + nums.swap(i, j); // Поменять эти два элемента местами + } + nums.swap(i, left); // Переместить опорный элемент на границу двух подмассивов + i // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { + // Завершить, когда длина подмассива равна 1 + while left < right { + // Операция разбиения с опорными указателями + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // Выполнить быструю сортировку для более короткого из двух подмассивов + if pivot - left < right - pivot { + Self::quick_sort(left, pivot - 1, nums); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + Self::quick_sort(pivot + 1, right, nums); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +fn main() { + /* Быстрая сортировка */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("После быстрой сортировки nums = {:?}", nums); + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("После быстрой сортировки (оптимизация медианным опорным элементом) nums = {:?}", nums); + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("После быстрой сортировки (оптимизация глубины рекурсии) nums = {:?}", nums); +} diff --git a/ru/codes/rust/chapter_sorting/radix_sort.rs b/ru/codes/rust/chapter_sorting/radix_sort.rs new file mode 100644 index 000000000..bbc554616 --- /dev/null +++ b/ru/codes/rust/chapter_sorting/radix_sort.rs @@ -0,0 +1,63 @@ +/* + * File: radix_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +fn digit(num: i32, exp: i32) -> usize { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return ((num / exp) % 10) as usize; +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +fn counting_sort_digit(nums: &mut [i32], exp: i32) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + let mut counter = [0; 10]; + let n = nums.len(); + // Подсчитать число появлений каждой цифры от 0 до 9 + for i in 0..n { + let d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d] += 1; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for i in 1..10 { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + let mut res = vec![0; n]; + for i in (0..n).rev() { + let d = digit(nums[i], exp); + let j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d] -= 1; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + nums.copy_from_slice(&res); +} + +/* Поразрядная сортировка */ +fn radix_sort(nums: &mut [i32]) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + let m = *nums.into_iter().max().unwrap(); + // Проходить разряды от младшего к старшему + let mut exp = 1; + while exp <= m { + counting_sort_digit(nums, exp); + exp *= 10; + } +} + +/* Driver Code */ +fn main() { + // Поразрядная сортировка + let mut nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, + 63832996, + ]; + radix_sort(&mut nums); + print!("После поразрядной сортировки nums = "); + print_util::print_array(&nums); +} diff --git a/ru/codes/rust/chapter_sorting/selection_sort.rs b/ru/codes/rust/chapter_sorting/selection_sort.rs new file mode 100644 index 000000000..b71618f7c --- /dev/null +++ b/ru/codes/rust/chapter_sorting/selection_sort.rs @@ -0,0 +1,35 @@ +/* + * File: selection_sort.rs + * Created Time: 2023-05-30 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* Сортировка выбором */ +fn selection_sort(nums: &mut [i32]) { + if nums.is_empty() { + return; + } + let n = nums.len(); + // Внешний цикл: неотсортированный диапазон [i, n-1] + for i in 0..n - 1 { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + let mut k = i; + for j in i + 1..n { + if nums[j] < nums[k] { + k = j; // Записать индекс минимального элемента + } + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + nums.swap(i, k); + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + selection_sort(&mut nums); + print!("\nПосле сортировки выбором nums = "); + print_util::print_array(&nums); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/array_deque.rs b/ru/codes/rust/chapter_stack_and_queue/array_deque.rs new file mode 100644 index 000000000..8125202f2 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/array_deque.rs @@ -0,0 +1,160 @@ +/* + * File: array_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; +/* Двусторонняя очередь на основе кольцевого массива */ +struct ArrayDeque { + nums: Vec, // Массив для хранения элементов двусторонней очереди + front: usize, // Указатель head, указывающий на первый элемент очереди + que_size: usize, // Длина двусторонней очереди +} + +impl ArrayDeque { + /* Конструктор */ + pub fn new(capacity: usize) -> Self { + Self { + nums: vec![T::default(); capacity], + front: 0, + que_size: 0, + } + } + + /* Получить вместимость двусторонней очереди */ + pub fn capacity(&self) -> usize { + self.nums.len() + } + + /* Получение длины двусторонней очереди */ + pub fn size(&self) -> usize { + self.que_size + } + + /* Проверка, пуста ли двусторонняя очередь */ + pub fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* Вычислить индекс в кольцевом массиве */ + fn index(&self, i: i32) -> usize { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + ((i + self.capacity() as i32) % self.capacity() as i32) as usize + } + + /* Добавление в голову очереди */ + pub fn push_first(&mut self, num: T) { + if self.que_size == self.capacity() { + println!("Двусторонняя очередь заполнена"); + return; + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + self.front = self.index(self.front as i32 - 1); + // Добавить num в голову очереди + self.nums[self.front] = num; + self.que_size += 1; + } + + /* Добавление в хвост очереди */ + pub fn push_last(&mut self, num: T) { + if self.que_size == self.capacity() { + println!("Двусторонняя очередь заполнена"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + let rear = self.index(self.front as i32 + self.que_size as i32); + // Добавить num в хвост очереди + self.nums[rear] = num; + self.que_size += 1; + } + + /* Извлечение из головы очереди */ + fn pop_first(&mut self) -> T { + let num = self.peek_first(); + // Указатель головы сдвигается на одну позицию назад + self.front = self.index(self.front as i32 + 1); + self.que_size -= 1; + num + } + + /* Извлечение из хвоста очереди */ + fn pop_last(&mut self) -> T { + let num = self.peek_last(); + self.que_size -= 1; + num + } + + /* Доступ к элементу в начале очереди */ + fn peek_first(&self) -> T { + if self.is_empty() { + panic!("двусторонняя очередь пуста") + }; + self.nums[self.front] + } + + /* Доступ к элементу в конце очереди */ + fn peek_last(&self) -> T { + if self.is_empty() { + panic!("двусторонняя очередь пуста") + }; + // Вычислить индекс хвостового элемента + let last = self.index(self.front as i32 + self.que_size as i32 - 1); + self.nums[last] + } + + /* Вернуть массив для вывода */ + fn to_array(&self) -> Vec { + // Преобразовывать только элементы списка в пределах фактической длины + let mut res = vec![T::default(); self.que_size]; + let mut j = self.front; + for i in 0..self.que_size { + res[i] = self.nums[self.index(j as i32)]; + j += 1; + } + res + } +} + +/* Driver Code */ +fn main() { + /* Инициализация двусторонней очереди */ + let mut deque = ArrayDeque::new(10); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("Двусторонняя очередь deque = "); + print_util::print_array(&deque.to_array()); + + /* Доступ к элементу */ + let peek_first = deque.peek_first(); + print!("\nПервый элемент peek_first = {}", peek_first); + let peek_last = deque.peek_last(); + print!("\nПоследний элемент peek_last = {}", peek_last); + + /* Добавление элемента в очередь */ + deque.push_last(4); + print!("\nПосле добавления элемента 4 в хвост deque = "); + print_util::print_array(&deque.to_array()); + deque.push_first(1); + print!("\nПосле добавления элемента 1 в голову deque = "); + print_util::print_array(&deque.to_array()); + + /* Извлечение элемента из очереди */ + let pop_last = deque.pop_last(); + print!("\nИзвлеченный из хвоста элемент = {}, deque после извлечения из хвоста = ", pop_last); + print_util::print_array(&deque.to_array()); + let pop_first = deque.pop_first(); + print!("\nИзвлеченный из головы элемент = {}, deque после извлечения из головы = ", pop_first); + print_util::print_array(&deque.to_array()); + + /* Получение длины двусторонней очереди */ + let size = deque.size(); + print!("\nДлина двусторонней очереди size = {}", size); + + /* Проверка, пуста ли двусторонняя очередь */ + let is_empty = deque.is_empty(); + print!("\nПуста ли двусторонняя очередь = {}", is_empty); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/array_queue.rs b/ru/codes/rust/chapter_stack_and_queue/array_queue.rs new file mode 100644 index 000000000..ee0678193 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/array_queue.rs @@ -0,0 +1,125 @@ +/* + * File: array_queue.rs + * Created Time: 2023-02-06 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +/* Очередь на основе кольцевого массива */ +struct ArrayQueue { + nums: Vec, // Массив для хранения элементов очереди + front: i32, // Указатель head, указывающий на первый элемент очереди + que_size: i32, // Длина очереди + que_capacity: i32, // Вместимость очереди +} + +impl ArrayQueue { + /* Конструктор */ + fn new(capacity: i32) -> ArrayQueue { + ArrayQueue { + nums: vec![T::default(); capacity as usize], + front: 0, + que_size: 0, + que_capacity: capacity, + } + } + + /* Получить вместимость очереди */ + fn capacity(&self) -> i32 { + self.que_capacity + } + + /* Получение длины очереди */ + fn size(&self) -> i32 { + self.que_size + } + + /* Проверка, пуста ли очередь */ + fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* Поместить в очередь */ + fn push(&mut self, num: T) { + if self.que_size == self.capacity() { + println!("Очередь заполнена"); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + let rear = (self.front + self.que_size) % self.que_capacity; + // Добавить num в хвост очереди + self.nums[rear as usize] = num; + self.que_size += 1; + } + + /* Извлечь из очереди */ + fn pop(&mut self) -> T { + let num = self.peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + self.front = (self.front + 1) % self.que_capacity; + self.que_size -= 1; + num + } + + /* Доступ к элементу в начале очереди */ + fn peek(&self) -> T { + if self.is_empty() { + panic!("index out of bounds"); + } + self.nums[self.front as usize] + } + + /* Вернуть массив */ + fn to_vector(&self) -> Vec { + let cap = self.que_capacity; + let mut j = self.front; + let mut arr = vec![T::default(); cap as usize]; + for i in 0..self.que_size { + arr[i as usize] = self.nums[(j % cap) as usize]; + j += 1; + } + arr + } +} + +/* Driver Code */ +fn main() { + /* Инициализация очереди */ + let capacity = 10; + let mut queue = ArrayQueue::new(capacity); + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + println!("Очередь queue = {:?}", queue.to_vector()); + + /* Доступ к элементу в начале очереди */ + let peek = queue.peek(); + println!("Первый элемент peek = {}", peek); + + /* Извлечение элемента из очереди */ + let pop = queue.pop(); + println!( + "Извлеченный элемент pop = {:?}, queue после извлечения = {:?}", + pop, + queue.to_vector() + ); + + /* Получение длины очереди */ + let size = queue.size(); + println!("Длина очереди size = {}", size); + + /* Проверка, пуста ли очередь */ + let is_empty = queue.is_empty(); + println!("Пуста ли очередь = {}", is_empty); + + /* Проверка кольцевого массива */ + for i in 0..10 { + queue.push(i); + queue.pop(); + println!("После {:?}-го раунда операций enqueue и dequeue queue = {:?}", i, queue.to_vector()); + } +} diff --git a/ru/codes/rust/chapter_stack_and_queue/array_stack.rs b/ru/codes/rust/chapter_stack_and_queue/array_stack.rs new file mode 100644 index 000000000..77a64d2b5 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/array_stack.rs @@ -0,0 +1,86 @@ +/* + * File: array_stack.rs + * Created Time: 2023-02-05 + * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Стек на основе массива */ +struct ArrayStack { + stack: Vec, +} + +impl ArrayStack { + /* Инициализация стека */ + fn new() -> ArrayStack { + ArrayStack:: { + stack: Vec::::new(), + } + } + + /* Получение длины стека */ + fn size(&self) -> usize { + self.stack.len() + } + + /* Проверка, пуст ли стек */ + fn is_empty(&self) -> bool { + self.size() == 0 + } + + /* Поместить в стек */ + fn push(&mut self, num: T) { + self.stack.push(num); + } + + /* Извлечь из стека */ + fn pop(&mut self) -> Option { + self.stack.pop() + } + + /* Доступ к верхнему элементу стека */ + fn peek(&self) -> Option<&T> { + if self.is_empty() { + panic!("стек пуст") + }; + self.stack.last() + } + + /* Вернуть &Vec */ + fn to_array(&self) -> &Vec { + &self.stack + } +} + +/* Driver Code */ +fn main() { + // Инициализация стека + let mut stack = ArrayStack::::new(); + + // Помещение элемента в стек + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("Стек stack = "); + print_util::print_array(stack.to_array()); + + // Доступ к верхнему элементу стека + let peek = stack.peek().unwrap(); + print!("\nВерхний элемент peek = {}", peek); + + // Извлечение элемента из стека + let pop = stack.pop().unwrap(); + print!("\nИзвлеченный элемент pop = {pop}, stack после извлечения = "); + print_util::print_array(stack.to_array()); + + // Получение длины стека + let size = stack.size(); + print!("\nДлина стека size = {size}"); + + // Проверка на пустоту + let is_empty = stack.is_empty(); + print!("\nПуст ли стек = {is_empty}"); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/deque.rs b/ru/codes/rust/chapter_stack_and_queue/deque.rs new file mode 100644 index 000000000..941737693 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/deque.rs @@ -0,0 +1,49 @@ +/* + * File: deque.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // Инициализация двусторонней очереди + let mut deque: VecDeque = VecDeque::new(); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + print!("Двусторонняя очередь deque = "); + print_util::print_queue(&deque); + + // Доступ к элементу + let peek_first = deque.front().unwrap(); + print!("\nПервый элемент peekFirst = {peek_first}"); + let peek_last = deque.back().unwrap(); + print!("\nПоследний элемент peekLast = {peek_last}"); + + /* Добавление элемента в очередь */ + deque.push_back(4); + print!("\nПосле добавления элемента 4 в хвост deque = "); + print_util::print_queue(&deque); + deque.push_front(1); + print!("\nПосле добавления элемента 1 в голову deque = "); + print_util::print_queue(&deque); + + // Извлечение элемента из очереди + let pop_last = deque.pop_back().unwrap(); + print!("\nИзвлеченный из хвоста элемент = {pop_last}, deque после извлечения из хвоста = "); + print_util::print_queue(&deque); + let pop_first = deque.pop_front().unwrap(); + print!("\nИзвлеченный из головы элемент = {pop_first}, deque после извлечения из головы = "); + print_util::print_queue(&deque); + + // Получение длины двусторонней очереди + let size = deque.len(); + print!("\nДлина двусторонней очереди size = {size}"); + + // Проверка, пуста ли двусторонняя очередь + let is_empty = deque.is_empty(); + print!("\nПуста ли двусторонняя очередь = {is_empty}"); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs b/ru/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs new file mode 100644 index 000000000..c8a745b46 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs @@ -0,0 +1,218 @@ +/* + * File: linkedlist_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::rc::Rc; + +/* Узел двусвязного списка */ +pub struct ListNode { + pub val: T, // Значение узла + pub next: Option>>>, // Указатель на узел-преемник + pub prev: Option>>>, // Указатель на узел-предшественник +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { + val, + next: None, + prev: None, + })) + } +} + +/* Двусторонняя очередь на основе двусвязного списка */ +#[allow(dead_code)] +pub struct LinkedListDeque { + front: Option>>>, // Головной узел front + rear: Option>>>, // Хвостовой узел rear + que_size: usize, // Длина двусторонней очереди +} + +impl LinkedListDeque { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* Получение длины двусторонней очереди */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* Проверка, пуста ли двусторонняя очередь */ + pub fn is_empty(&self) -> bool { + return self.que_size == 0; + } + + /* Операция добавления в очередь */ + fn push(&mut self, num: T, is_front: bool) { + let node = ListNode::new(num); + // Операция добавления в голову очереди + if is_front { + match self.front.take() { + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + None => { + self.rear = Some(node.clone()); + self.front = Some(node); + } + // Добавить node в голову списка + Some(old_front) => { + old_front.borrow_mut().prev = Some(node.clone()); + node.borrow_mut().next = Some(old_front); + self.front = Some(node); // Обновить головной узел + } + } + } + // Операция добавления в хвост очереди + else { + match self.rear.take() { + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + None => { + self.front = Some(node.clone()); + self.rear = Some(node); + } + // Добавить node в хвост списка + Some(old_rear) => { + old_rear.borrow_mut().next = Some(node.clone()); + node.borrow_mut().prev = Some(old_rear); + self.rear = Some(node); // Обновить хвостовой узел + } + } + } + self.que_size += 1; // Обновить длину очереди + } + + /* Добавление в голову очереди */ + pub fn push_first(&mut self, num: T) { + self.push(num, true); + } + + /* Добавление в хвост очереди */ + pub fn push_last(&mut self, num: T) { + self.push(num, false); + } + + /* Операция извлечения из очереди */ + fn pop(&mut self, is_front: bool) -> Option { + // Если очередь пуста, сразу вернуть None + if self.is_empty() { + return None; + }; + // Операция извлечения из головы очереди + if is_front { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + new_front.borrow_mut().prev.take(); + self.front = Some(new_front); // Обновить головной узел + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; // Обновить длину очереди + old_front.borrow().val + }) + } + // Операция извлечения из хвоста очереди + else { + self.rear.take().map(|old_rear| { + match old_rear.borrow_mut().prev.take() { + Some(new_rear) => { + new_rear.borrow_mut().next.take(); + self.rear = Some(new_rear); // Обновить хвостовой узел + } + None => { + self.front.take(); + } + } + self.que_size -= 1; // Обновить длину очереди + old_rear.borrow().val + }) + } + } + + /* Извлечение из головы очереди */ + pub fn pop_first(&mut self) -> Option { + return self.pop(true); + } + + /* Извлечение из хвоста очереди */ + pub fn pop_last(&mut self) -> Option { + return self.pop(false); + } + + /* Доступ к элементу в начале очереди */ + pub fn peek_first(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* Доступ к элементу в конце очереди */ + pub fn peek_last(&self) -> Option<&Rc>>> { + self.rear.as_ref() + } + + /* Вернуть массив для вывода */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + res + } +} + +/* Driver Code */ +fn main() { + /* Инициализация двусторонней очереди */ + let mut deque = LinkedListDeque::new(); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("Двусторонняя очередь deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* Доступ к элементу */ + let peek_first = deque.peek_first().unwrap().borrow().val; + print!("\nПервый элемент peek_first = {}", peek_first); + let peek_last = deque.peek_last().unwrap().borrow().val; + print!("\nПоследний элемент peek_last = {}", peek_last); + + /* Добавление элемента в очередь */ + deque.push_last(4); + print!("\nПосле добавления элемента 4 в хвост deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + deque.push_first(1); + print!("\nПосле добавления элемента 1 в голову deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* Извлечение элемента из очереди */ + let pop_last = deque.pop_last().unwrap(); + print!("\nИзвлеченный из хвоста элемент = {}, deque после извлечения из хвоста = ", pop_last); + print_util::print_array(&deque.to_array(deque.peek_first())); + let pop_first = deque.pop_first().unwrap(); + print!("\nИзвлеченный из головы элемент = {}, deque после извлечения из головы = ", pop_first); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* Получение длины двусторонней очереди */ + let size = deque.size(); + print!("\nДлина двусторонней очереди size = {}", size); + + /* Проверка, пуста ли двусторонняя очередь */ + let is_empty = deque.is_empty(); + print!("\nПуста ли двусторонняя очередь = {}", is_empty); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs b/ru/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs new file mode 100644 index 000000000..b85a6ee67 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs @@ -0,0 +1,126 @@ +/* + * File: linkedlist_queue.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* Очередь на основе связного списка */ +#[allow(dead_code)] +pub struct LinkedListQueue { + front: Option>>>, // Головной узел front + rear: Option>>>, // Хвостовой узел rear + que_size: usize, // Длина очереди +} + +impl LinkedListQueue { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* Получение длины очереди */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* Проверка, пуста ли очередь */ + pub fn is_empty(&self) -> bool { + return self.que_size == 0; + } + + /* Поместить в очередь */ + pub fn push(&mut self, num: T) { + // Добавить num после хвостового узла + let new_rear = ListNode::new(num); + match self.rear.take() { + // Если очередь не пуста, добавить этот узел после хвостового узла + Some(old_rear) => { + old_rear.borrow_mut().next = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + None => { + self.front = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + } + self.que_size += 1; + } + + /* Извлечь из очереди */ + pub fn pop(&mut self) -> Option { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + self.front = Some(new_front); + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; + old_front.borrow().val + }) + } + + /* Доступ к элементу в начале очереди */ + pub fn peek(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* Преобразовать связный список в Array и вернуть */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + + res + } +} + +/* Driver Code */ +fn main() { + /* Инициализация очереди */ + let mut queue = LinkedListQueue::new(); + + /* Добавление элемента в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print!("Очередь queue = "); + print_util::print_array(&queue.to_array(queue.peek())); + + /* Доступ к элементу в начале очереди */ + let peek = queue.peek().unwrap().borrow().val; + print!("\nПервый элемент peek = {}", peek); + + /* Извлечение элемента из очереди */ + let pop = queue.pop().unwrap(); + print!("\nИзвлеченный элемент pop = {}, queue после извлечения = ", pop); + print_util::print_array(&queue.to_array(queue.peek())); + + /* Получение длины очереди */ + let size = queue.size(); + print!("\nДлина очереди size = {}", size); + + /* Проверка, пуста ли очередь */ + let is_empty = queue.is_empty(); + print!("\nПуста ли очередь = {}", is_empty); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs b/ru/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs new file mode 100644 index 000000000..dc23834cc --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs @@ -0,0 +1,105 @@ +/* + * File: linkedlist_stack.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* Стек на основе связного списка */ +#[allow(dead_code)] +pub struct LinkedListStack { + stack_peek: Option>>>, // Использовать головной узел как вершину стека + stk_size: usize, // Длина стека +} + +impl LinkedListStack { + pub fn new() -> Self { + Self { + stack_peek: None, + stk_size: 0, + } + } + + /* Получение длины стека */ + pub fn size(&self) -> usize { + return self.stk_size; + } + + /* Проверка, пуст ли стек */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* Поместить в стек */ + pub fn push(&mut self, num: T) { + let node = ListNode::new(num); + node.borrow_mut().next = self.stack_peek.take(); + self.stack_peek = Some(node); + self.stk_size += 1; + } + + /* Извлечь из стека */ + pub fn pop(&mut self) -> Option { + self.stack_peek.take().map(|old_head| { + self.stack_peek = old_head.borrow_mut().next.take(); + self.stk_size -= 1; + + old_head.borrow().val + }) + } + + /* Доступ к верхнему элементу стека */ + pub fn peek(&self) -> Option<&Rc>>> { + self.stack_peek.as_ref() + } + + /* Преобразовать List в Array и вернуть */ + pub fn to_array(&self) -> Vec { + fn _to_array(head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = _to_array(node.borrow().next.as_ref()); + nums.push(node.borrow().val); + return nums; + } + return Vec::new(); + } + + _to_array(self.peek()) + } +} + +/* Driver Code */ +fn main() { + /* Инициализация стека */ + let mut stack = LinkedListStack::new(); + + /* Помещение элемента в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("Стек stack = "); + print_util::print_array(&stack.to_array()); + + /* Доступ к верхнему элементу стека */ + let peek = stack.peek().unwrap().borrow().val; + print!("\nВерхний элемент peek = {}", peek); + + /* Извлечение элемента из стека */ + let pop = stack.pop().unwrap(); + print!("\nИзвлеченный элемент pop = {}, stack после извлечения = ", pop); + print_util::print_array(&stack.to_array()); + + /* Получение длины стека */ + let size = stack.size(); + print!("\nДлина стека size = {}", size); + + /* Проверка на пустоту */ + let is_empty = stack.is_empty(); + print!("\nПуст ли стек = {}", is_empty); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/queue.rs b/ru/codes/rust/chapter_stack_and_queue/queue.rs new file mode 100644 index 000000000..0fdbaf942 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/queue.rs @@ -0,0 +1,41 @@ +/* + * File: queue.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // Инициализация очереди + let mut queue: VecDeque = VecDeque::new(); + + // Добавление элемента в очередь + queue.push_back(1); + queue.push_back(3); + queue.push_back(2); + queue.push_back(5); + queue.push_back(4); + print!("Очередь queue = "); + print_util::print_queue(&queue); + + // Доступ к элементу в начале очереди + let peek = queue.front().unwrap(); + println!("\nПервый элемент peek = {peek}"); + + // Извлечение элемента из очереди + let pop = queue.pop_front().unwrap(); + print!("Извлеченный элемент pop = {pop}, queue после извлечения = "); + print_util::print_queue(&queue); + + // Получение длины очереди + let size = queue.len(); + print!("\nДлина очереди size = {size}"); + + // Проверка, пуста ли очередь + let is_empty = queue.is_empty(); + print!("\nПуста ли очередь = {is_empty}"); +} diff --git a/ru/codes/rust/chapter_stack_and_queue/stack.rs b/ru/codes/rust/chapter_stack_and_queue/stack.rs new file mode 100644 index 000000000..e97ca9eb6 --- /dev/null +++ b/ru/codes/rust/chapter_stack_and_queue/stack.rs @@ -0,0 +1,40 @@ +/* + * File: stack.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Driver Code */ +pub fn main() { + // Инициализировать стек + // В Rust рекомендуется использовать Vec как стек + let mut stack: Vec = Vec::new(); + + // Помещение элемента в стек + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("Стек stack = "); + print_util::print_array(&stack); + + // Доступ к верхнему элементу стека + let peek = stack.last().unwrap(); + print!("\nВерхний элемент peek = {peek}"); + + // Извлечение элемента из стека + let pop = stack.pop().unwrap(); + print!("\nИзвлеченный элемент pop = {pop}, stack после извлечения = "); + print_util::print_array(&stack); + + // Получение длины стека + let size = stack.len(); + print!("\nДлина стека size = {size}"); + + // Проверка, пуст ли стек + let is_empty = stack.is_empty(); + print!("\nПуст ли стек = {is_empty}"); +} diff --git a/ru/codes/rust/chapter_tree/array_binary_tree.rs b/ru/codes/rust/chapter_tree/array_binary_tree.rs new file mode 100644 index 000000000..301aa6834 --- /dev/null +++ b/ru/codes/rust/chapter_tree/array_binary_tree.rs @@ -0,0 +1,186 @@ +/* + * File: array_binary_tree.rs + * Created Time: 2023-07-25 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, tree_node}; + +/* Класс двоичного дерева в массивном представлении */ +struct ArrayBinaryTree { + tree: Vec>, +} + +impl ArrayBinaryTree { + /* Конструктор */ + fn new(arr: Vec>) -> Self { + Self { tree: arr } + } + + /* Вместимость списка */ + fn size(&self) -> i32 { + self.tree.len() as i32 + } + + /* Получить значение узла с индексом i */ + fn val(&self, i: i32) -> Option { + // Если индекс выходит за границы, вернуть None, обозначающий пустую позицию + if i < 0 || i >= self.size() { + None + } else { + self.tree[i as usize] + } + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + fn left(&self, i: i32) -> i32 { + 2 * i + 1 + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + fn right(&self, i: i32) -> i32 { + 2 * i + 2 + } + + /* Получить индекс родительского узла узла с индексом i */ + fn parent(&self, i: i32) -> i32 { + (i - 1) / 2 + } + + /* Обход в ширину */ + fn level_order(&self) -> Vec { + self.tree.iter().filter_map(|&x| x).collect() + } + + /* Обход в глубину */ + fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { + if self.val(i).is_none() { + return; + } + let val = self.val(i).unwrap(); + // Предварительный обход + if order == "pre" { + res.push(val); + } + self.dfs(self.left(i), order, res); + // Симметричный обход + if order == "in" { + res.push(val); + } + self.dfs(self.right(i), order, res); + // Обратный обход + if order == "post" { + res.push(val); + } + } + + /* Предварительный обход */ + fn pre_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "pre", &mut res); + res + } + + /* Симметричный обход */ + fn in_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "in", &mut res); + res + } + + /* Обратный обход */ + fn post_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "post", &mut res); + res + } +} + +/* Driver Code */ +fn main() { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + let arr = vec![ + Some(1), + Some(2), + Some(3), + Some(4), + None, + Some(6), + Some(7), + Some(8), + Some(9), + None, + None, + Some(12), + None, + None, + Some(15), + ]; + + let root = tree_node::vec_to_tree(arr.clone()).unwrap(); + println!("\nИнициализация двоичного дерева\n"); + println!("Массивное представление двоичного дерева:"); + println!( + "[{}]", + arr.iter() + .map(|&val| if let Some(val) = val { + format!("{val}") + } else { + "null".to_string() + }) + .collect::>() + .join(", ") + ); + println!("Связное представление двоичного дерева:"); + print_util::print_tree(&root); + + // Класс двоичного дерева в массивном представлении + let abt = ArrayBinaryTree::new(arr); + + // Доступ к узлу + let i = 1; + let l = abt.left(i); + let r = abt.right(i); + let p = abt.parent(i); + println!( + "\nТекущий индекс узла = {}, значение = {}",\ni,\nif let Some(val) = abt.val(i) {\n format!("{val}")\n} else {\n "null".to_string()\n} + ); + println!( + "Индекс левого дочернего узла = {}, значение = {}", + l, + if let Some(val) = abt.val(l) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "Индекс правого дочернего узла = {}, значение = {}", + r, + if let Some(val) = abt.val(r) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "Индекс родительского узла = {}, значение = {}", + p, + if let Some(val) = abt.val(p) { + format!("{val}") + } else { + "null".to_string() + } + ); + + // Обходить дерево + let mut res = abt.level_order(); + println!("\nОбход в ширину: {:?}", res); + res = abt.pre_order(); + println!("Предварительный обход: {:?}", res); + res = abt.in_order(); + println!("Симметричный обход: {:?}", res); + res = abt.post_order(); + println!("Обратный обход: {:?}", res); +} diff --git a/ru/codes/rust/chapter_tree/avl_tree.rs b/ru/codes/rust/chapter_tree/avl_tree.rs new file mode 100644 index 000000000..0393a04ee --- /dev/null +++ b/ru/codes/rust/chapter_tree/avl_tree.rs @@ -0,0 +1,295 @@ +/* + * File: avl_tree.rs + * Created Time: 2023-07-14 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +type OptionTreeNodeRc = Option>>; + +/* AVL-дерево */ +struct AVLTree { + root: OptionTreeNodeRc, // Корневой узел +} + +impl AVLTree { + /* Конструктор */ + fn new() -> Self { + Self { root: None } + } + + /* Получить высоту узла */ + fn height(node: OptionTreeNodeRc) -> i32 { + // Высота пустого узла равна -1, высота листового узла равна 0 + match node { + Some(node) => node.borrow().height, + None => -1, + } + } + + /* Обновить высоту узла */ + fn update_height(node: OptionTreeNodeRc) { + if let Some(node) = node { + let left = node.borrow().left.clone(); + let right = node.borrow().right.clone(); + // Высота узла равна высоте более высокого поддерева + 1 + node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; + } + } + + /* Получить коэффициент баланса */ + fn balance_factor(node: OptionTreeNodeRc) -> i32 { + match node { + // Коэффициент баланса пустого узла равен 0 + None => 0, + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + Some(node) => { + Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) + } + } + } + + /* Операция правого вращения */ + fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().left.clone().unwrap(); + let grand_child = child.borrow().right.clone(); + // Выполнить правое вращение узла node вокруг child + child.borrow_mut().right = Some(node.clone()); + node.borrow_mut().left = grand_child; + // Обновить высоту узла + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // Вернуть корневой узел поддерева после вращения + Some(child) + } + None => None, + } + } + + /* Операция левого вращения */ + fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().right.clone().unwrap(); + let grand_child = child.borrow().left.clone(); + // Выполнить левое вращение узла node вокруг child + child.borrow_mut().left = Some(node.clone()); + node.borrow_mut().right = grand_child; + // Обновить высоту узла + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // Вернуть корневой узел поддерева после вращения + Some(child) + } + None => None, + } + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + // Получить коэффициент баланса узла node + let balance_factor = Self::balance_factor(node.clone()); + // Левосторонне перекошенное дерево + if balance_factor > 1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().left.clone()) >= 0 { + // Правое вращение + Self::right_rotate(Some(node)) + } else { + // Сначала левое вращение, затем правое + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::left_rotate(left); + Self::right_rotate(Some(node)) + } + } + // Правосторонне перекошенное дерево + else if balance_factor < -1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().right.clone()) <= 0 { + // Левое вращение + Self::left_rotate(Some(node)) + } else { + // Сначала правое вращение, затем левое + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::right_rotate(right); + Self::left_rotate(Some(node)) + } + } else { + // Дерево сбалансировано, вращение не требуется, вернуть сразу + node + } + } + + /* Вставка узла */ + fn insert(&mut self, val: i32) { + self.root = Self::insert_helper(self.root.clone(), val); + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. Найти позицию вставки и вставить узел */ + match { + let node_val = node.borrow().val; + node_val + } + .cmp(&val) + { + Ordering::Greater => { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::insert_helper(left, val); + } + Ordering::Less => { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::insert_helper(right, val); + } + Ordering::Equal => { + return Some(node); // Повторяющийся узел не вставлять, сразу вернуть + } + } + Self::update_height(Some(node.clone())); // Обновить высоту узла + + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = Self::rotate(Some(node)).unwrap(); + // Вернуть корневой узел поддерева + Some(node) + } + None => Some(TreeNode::new(val)), + } + } + + /* Удаление узла */ + fn remove(&self, val: i32) { + Self::remove_helper(self.root.clone(), val); + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. Найти узел и удалить его */ + if val < node.borrow().val { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::remove_helper(left, val); + } else if val > node.borrow().val { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, val); + } else if node.borrow().left.is_none() || node.borrow().right.is_none() { + let child = if node.borrow().left.is_some() { + node.borrow().left.clone() + } else { + node.borrow().right.clone() + }; + match child { + // Число дочерних узлов = 0, удалить node и сразу вернуть + None => { + return None; + } + // Число дочерних узлов = 1, удалить node напрямую + Some(child) => node = child, + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + let mut temp = node.borrow().right.clone().unwrap(); + loop { + let temp_left = temp.borrow().left.clone(); + if temp_left.is_none() { + break; + } + temp = temp_left.unwrap(); + } + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); + node.borrow_mut().val = temp.borrow().val; + } + Self::update_height(Some(node.clone())); // Обновить высоту узла + + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = Self::rotate(Some(node)).unwrap(); + // Вернуть корневой узел поддерева + Some(node) + } + None => None, + } + } + + /* Поиск узла */ + fn search(&self, val: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // Искать в цикле и выйти после прохода за листовой узел + while let Some(current) = cur.clone() { + match current.borrow().val.cmp(&val) { + // Целевой узел находится в правом поддереве cur + Ordering::Less => { + cur = current.borrow().right.clone(); + } + // Целевой узел находится в левом поддереве cur + Ordering::Greater => { + cur = current.borrow().left.clone(); + } + // Найти целевой узел и выйти из цикла + Ordering::Equal => { + break; + } + } + } + // Вернуть целевой узел + cur + } +} + +/* Driver Code */ +fn main() { + fn test_insert(tree: &mut AVLTree, val: i32) { + tree.insert(val); + println!("\nПосле вставки узла {} AVL-дерево имеет вид", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + fn test_remove(tree: &mut AVLTree, val: i32) { + tree.remove(val); + println!("\nПосле удаления узла {} AVL-дерево имеет вид", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + /* Инициализация пустого AVL-дерева */ + let mut avl_tree = AVLTree::new(); + + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + test_insert(&mut avl_tree, 1); + test_insert(&mut avl_tree, 2); + test_insert(&mut avl_tree, 3); + test_insert(&mut avl_tree, 4); + test_insert(&mut avl_tree, 5); + test_insert(&mut avl_tree, 8); + test_insert(&mut avl_tree, 7); + test_insert(&mut avl_tree, 9); + test_insert(&mut avl_tree, 10); + test_insert(&mut avl_tree, 6); + + /* Вставка повторяющегося узла */ + test_insert(&mut avl_tree, 7); + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + test_remove(&mut avl_tree, 8); // Удаление узла степени 0 + test_remove(&mut avl_tree, 5); // Удаление узла степени 1 + test_remove(&mut avl_tree, 4); // Удаление узла степени 2 + + /* Поиск узла */ + let node = avl_tree.search(7); + if let Some(node) = node { + println!( + "\nНайденный объект узла = {:?}, значение узла = {}",\n&*node.borrow(),\nnode.borrow().val + ); + } +} diff --git a/ru/codes/rust/chapter_tree/binary_search_tree.rs b/ru/codes/rust/chapter_tree/binary_search_tree.rs new file mode 100644 index 000000000..564add1ab --- /dev/null +++ b/ru/codes/rust/chapter_tree/binary_search_tree.rs @@ -0,0 +1,193 @@ +/* + * File: binary_search_tree.rs + * Created Time: 2023-04-20 + * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +use hello_algo_rust::include::TreeNode; + +type OptionTreeNodeRc = Option>>; + +/* Двоичное дерево поиска */ +pub struct BinarySearchTree { + root: OptionTreeNodeRc, +} + +impl BinarySearchTree { + /* Конструктор */ + pub fn new() -> Self { + // Инициализировать пустое дерево + Self { root: None } + } + + /* Получить корневой узел двоичного дерева */ + pub fn get_root(&self) -> OptionTreeNodeRc { + self.root.clone() + } + + /* Поиск узла */ + pub fn search(&self, num: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // Искать в цикле и выйти после прохода за листовой узел + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // Целевой узел находится в правом поддереве cur + Ordering::Greater => cur = node.borrow().right.clone(), + // Целевой узел находится в левом поддереве cur + Ordering::Less => cur = node.borrow().left.clone(), + // Найти целевой узел и выйти из цикла + Ordering::Equal => break, + } + } + + // Вернуть целевой узел + cur + } + + /* Вставка узла */ + pub fn insert(&mut self, num: i32) { + // Если дерево пусто, инициализировать корневой узел + if self.root.is_none() { + self.root = Some(TreeNode::new(num)); + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // Искать в цикле и выйти после прохода за листовой узел + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // Найти повторяющийся узел и сразу вернуть + Ordering::Equal => return, + // Позиция вставки находится в правом поддереве cur + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // Позиция вставки находится в левом поддереве cur + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // Вставка узла + let pre = pre.unwrap(); + let node = Some(TreeNode::new(num)); + if num > pre.borrow().val { + pre.borrow_mut().right = node; + } else { + pre.borrow_mut().left = node; + } + } + + /* Удаление узла */ + pub fn remove(&mut self, num: i32) { + // Если дерево пусто, сразу вернуть + if self.root.is_none() { + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // Искать в цикле и выйти после прохода за листовой узел + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // Найти узел для удаления и выйти из цикла + Ordering::Equal => break, + // Узел для удаления находится в правом поддереве cur + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // Узел для удаления находится в левом поддереве cur + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // Если узел для удаления отсутствует, сразу вернуть + if cur.is_none() { + return; + } + let cur = cur.unwrap(); + let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); + match (left_child.clone(), right_child.clone()) { + // Число дочерних узлов = 0 или 1 + (None, None) | (Some(_), None) | (None, Some(_)) => { + // Когда число дочерних узлов = 0 / 1, child = nullptr / этот дочерний узел + let child = left_child.or(right_child); + let pre = pre.unwrap(); + // Удалить узел cur + if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { + let left = pre.borrow().left.clone(); + if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { + pre.borrow_mut().left = child; + } else { + pre.borrow_mut().right = child; + } + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + self.root = child; + } + } + // Число дочерних узлов = 2 + (Some(_), Some(_)) => { + // Получить следующий узел после cur в симметричном обходе + let mut tmp = cur.borrow().right.clone(); + while let Some(node) = tmp.clone() { + if node.borrow().left.is_some() { + tmp = node.borrow().left.clone(); + } else { + break; + } + } + let tmp_val = tmp.unwrap().borrow().val; + // Рекурсивно удалить узел tmp + self.remove(tmp_val); + // Перезаписать cur значением tmp + cur.borrow_mut().val = tmp_val; + } + } + } +} + +/* Driver Code */ +fn main() { + /* Инициализация двоичного дерева поиска */ + let mut bst = BinarySearchTree::new(); + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for &num in &nums { + bst.insert(num); + } + println!("\nИсходное двоичное дерево\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* Найти узел */ + let node = bst.search(7); + println!( + "\nНайденный объект узла = {:?}, значение узла = {}",\nnode.clone().unwrap(),\nnode.clone().unwrap().borrow().val + ); + + /* Вставка узла */ + bst.insert(16); + println!("\nПосле вставки узла 16 двоичное дерево имеет вид\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* Удаление узла */ + bst.remove(1); + println!("\nПосле удаления узла 1 двоичное дерево имеет вид\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(2); + println!("\nПосле удаления узла 2 двоичное дерево имеет вид\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(4); + println!("\nПосле удаления узла 4 двоичное дерево имеет вид\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); +} diff --git a/ru/codes/rust/chapter_tree/binary_tree.rs b/ru/codes/rust/chapter_tree/binary_tree.rs new file mode 100644 index 000000000..7a5e3979b --- /dev/null +++ b/ru/codes/rust/chapter_tree/binary_tree.rs @@ -0,0 +1,38 @@ +/** + * File: binary_tree.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ +use std::rc::Rc; +use hello_algo_rust::include::{print_util, TreeNode}; + +/* Driver Code */ +fn main() { + /* Инициализация двоичного дерева */ + // Инициализация узла + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // Построить связи между узлами (указатели) + n1.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().right = Some(Rc::clone(&n3)); + n2.borrow_mut().left = Some(Rc::clone(&n4)); + n2.borrow_mut().right = Some(Rc::clone(&n5)); + println!("\nИнициализация двоичного дерева\n"); + print_util::print_tree(&n1); + + // Вставка и удаление узлов + let p = TreeNode::new(0); + // Вставить узел P между n1 -> n2 + p.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().left = Some(Rc::clone(&p)); + println!("\nПосле вставки узла P\n"); + print_util::print_tree(&n1); + // Удалить узел P + drop(p); + n1.borrow_mut().left = Some(Rc::clone(&n2)); + println!("\nПосле удаления узла P\n"); + print_util::print_tree(&n1); +} diff --git a/ru/codes/rust/chapter_tree/binary_tree_bfs.rs b/ru/codes/rust/chapter_tree/binary_tree_bfs.rs new file mode 100644 index 000000000..8c2bb2a72 --- /dev/null +++ b/ru/codes/rust/chapter_tree/binary_tree_bfs.rs @@ -0,0 +1,45 @@ +/* + * File: binary_tree_bfs.rs + * Created Time: 2023-04-07 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::collections::VecDeque; +use std::{cell::RefCell, rc::Rc}; + +/* Обход в ширину */ +fn level_order(root: &Rc>) -> Vec { + // Инициализировать очередь и добавить корневой узел + let mut que = VecDeque::new(); + que.push_back(root.clone()); + // Инициализировать список для хранения последовательности обхода + let mut vec = Vec::new(); + + while let Some(node) = que.pop_front() { + // Извлечение из очереди + vec.push(node.borrow().val); // Сохранить значение узла + if let Some(left) = node.borrow().left.as_ref() { + que.push_back(left.clone()); // Поместить левый дочерний узел в очередь + } + if let Some(right) = node.borrow().right.as_ref() { + que.push_back(right.clone()); // Поместить правый дочерний узел в очередь + }; + } + vec +} + +/* Driver Code */ +fn main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); + println!("Инициализация двоичного дерева\n"); + print_util::print_tree(&root); + + /* Обход в ширину */ + let vec = level_order(&root); + print!("\nПоследовательность печати узлов при обходе в ширину = {:?}", vec); +} diff --git a/ru/codes/rust/chapter_tree/binary_tree_dfs.rs b/ru/codes/rust/chapter_tree/binary_tree_dfs.rs new file mode 100644 index 000000000..1697c8960 --- /dev/null +++ b/ru/codes/rust/chapter_tree/binary_tree_dfs.rs @@ -0,0 +1,87 @@ +/* + * File: binary_tree_dfs.rs + * Created Time: 2023-04-06 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::cell::RefCell; +use std::rc::Rc; + +/* Предварительный обход */ +fn pre_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // Порядок обхода: корень -> левое поддерево -> правое поддерево + let node = node.borrow(); + res.push(node.val); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* Симметричный обход */ +fn in_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // Порядок обхода: левое поддерево -> корень -> правое поддерево + let node = node.borrow(); + dfs(node.left.as_ref(), res); + res.push(node.val); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* Обратный обход */ +fn post_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // Порядок обхода: левое поддерево -> правое поддерево -> корень + let node = node.borrow(); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + res.push(node.val); + } + } + + dfs(root, &mut result); + + result +} + +/* Driver Code */ +fn main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); + println!("Инициализация двоичного дерева\n"); + print_util::print_tree(root.as_ref().unwrap()); + + /* Предварительный обход */ + let vec = pre_order(root.as_ref()); + println!("\nПоследовательность печати узлов при предварительном обходе = {:?}", vec); + + /* Симметричный обход */ + let vec = in_order(root.as_ref()); + println!("\nПоследовательность печати узлов при симметричном обходе = {:?}", vec); + + /* Обратный обход */ + let vec = post_order(root.as_ref()); + print!("\nПоследовательность печати узлов при обратном обходе = {:?}", vec); +} diff --git a/ru/codes/rust/src/include/list_node.rs b/ru/codes/rust/src/include/list_node.rs new file mode 100644 index 000000000..edb6d1ef6 --- /dev/null +++ b/ru/codes/rust/src/include/list_node.rs @@ -0,0 +1,57 @@ +/* + * File: list_node.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) + */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ListNode { + pub val: T, + pub next: Option>>>, +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { val, next: None })) + } + + /* Десериализовать массив в связный список */ + pub fn arr_to_linked_list(array: &[T]) -> Option>>> + where + T: Copy + Clone, + { + let mut head = None; + // insert in reverse order + for item in array.iter().rev() { + let node = Rc::new(RefCell::new(ListNode { + val: *item, + next: head.take(), + })); + head = Some(node); + } + head + } + + /* Преобразовать связный список в хеш-таблицу */ + pub fn linked_list_to_hashmap( + linked_list: Option>>>, + ) -> HashMap>>> + where + T: std::hash::Hash + Eq + Copy + Clone, + { + let mut hashmap = HashMap::new(); + let mut node = linked_list; + + while let Some(cur) = node { + let borrow = cur.borrow(); + hashmap.insert(borrow.val.clone(), cur.clone()); + node = borrow.next.clone(); + } + + hashmap + } +} diff --git a/ru/codes/rust/src/include/mod.rs b/ru/codes/rust/src/include/mod.rs new file mode 100644 index 000000000..6cba6f9a5 --- /dev/null +++ b/ru/codes/rust/src/include/mod.rs @@ -0,0 +1,16 @@ +/* + * File: include.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) + */ + +pub mod list_node; +pub mod print_util; +pub mod tree_node; +pub mod vertex; + +// rexport to include +pub use list_node::*; +pub use print_util::*; +pub use tree_node::*; +pub use vertex::*; diff --git a/ru/codes/rust/src/include/print_util.rs b/ru/codes/rust/src/include/print_util.rs new file mode 100644 index 000000000..5a3cd4189 --- /dev/null +++ b/ru/codes/rust/src/include/print_util.rs @@ -0,0 +1,103 @@ +/* + * File: print_util.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use std::cell::{Cell, RefCell}; +use std::fmt::Display; +use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; + +use super::list_node::ListNode; +use super::tree_node::{TreeNode, vec_to_tree}; + +struct Trunk<'a, 'b> { + prev: Option<&'a Trunk<'a, 'b>>, + str: Cell<&'b str>, +} + +/* Вывести массив */ +pub fn print_array(nums: &[T]) { + print!("["); + if nums.len() > 0 { + for (i, num) in nums.iter().enumerate() { + print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); + } + } else { + print!("]"); + } +} + +/* Вывести хеш-таблицу */ +pub fn print_hash_map(map: &HashMap) { + for (key, value) in map { + println!("{key} -> {value}"); + } +} + +/* Вывести очередь (двустороннюю очередь) */ +pub fn print_queue(queue: &VecDeque) { + print!("["); + let iter = queue.iter(); + for (i, data) in iter.enumerate() { + print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); + } +} + +/* Вывести связный список */ +pub fn print_linked_list(head: &Rc>>) { + print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); + if let Some(node) = &head.borrow().next { + return print_linked_list(node); + } +} + +/* Вывести двоичное дерево */ +pub fn print_tree(root: &Rc>) { + _print_tree(Some(root), None, false); +} + +/* Вывести двоичное дерево */ +fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { + if let Some(node) = root { + let mut prev_str = " "; + let trunk = Trunk { prev, str: Cell::new(prev_str) }; + _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); + + if prev.is_none() { + trunk.str.set("———"); + } else if is_right { + trunk.str.set("/———"); + prev_str = " |"; + } else { + trunk.str.set("\\———"); + prev.as_ref().unwrap().str.set(prev_str); + } + + show_trunks(Some(&trunk)); + println!(" {}", node.borrow().val); + if let Some(prev) = prev { + prev.str.set(prev_str); + } + trunk.str.set(" |"); + + _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); + } +} + +fn show_trunks(trunk: Option<&Trunk>) { + if let Some(trunk) = trunk { + show_trunks(trunk.prev); + print!("{}", trunk.str.get()); + } +} + +/* Вывести кучу */ +pub fn print_heap(heap: Vec) { + println!("Массивное представление кучи: {:?}", heap); + println!("Древовидное представление кучи:"); + if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { + print_tree(&root); + } +} diff --git a/ru/codes/rust/src/include/tree_node.rs b/ru/codes/rust/src/include/tree_node.rs new file mode 100644 index 000000000..2e9713fa1 --- /dev/null +++ b/ru/codes/rust/src/include/tree_node.rs @@ -0,0 +1,92 @@ +/* + * File: tree_node.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) + */ + +use std::cell::RefCell; +use std::rc::Rc; + +/* Тип узла двоичного дерева */ +#[derive(Debug)] +pub struct TreeNode { + pub val: i32, + pub height: i32, + pub parent: Option>>, + pub left: Option>>, + pub right: Option>>, +} + +impl TreeNode { + /* Конструктор */ + pub fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + parent: None, + left: None, + right: None, + })) + } +} + +#[macro_export] +macro_rules! op_vec { + ( $( $x:expr ),* ) => { + vec![ + $(Option::from($x)),* + ] + }; +} + +// Правила кодирования сериализации см.: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// Массивное представление двоичного дерева: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// Связное представление двоичного дерева: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* Десериализовать список в двоичное дерево: рекурсия */ +fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { + if i >= arr.len() || arr[i].is_none() { + return None; + } + let root = TreeNode::new(arr[i].unwrap()); + root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); + root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); + Some(root) +} + +/* Десериализовать список в двоичное дерево */ +pub fn vec_to_tree(arr: Vec>) -> Option>> { + vec_to_tree_dfs(&arr, 0) +} + +/* Сериализовать двоичное дерево в список: рекурсия */ +fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { + if let Some(root) = root { + // i + 1 is the minimum valid size to access index i + while res.len() < i + 1 { + res.push(None); + } + res[i] = Some(root.borrow().val); + tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); + tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); + } +} + +/* Сериализовать двоичное дерево в список */ +pub fn tree_to_vec(root: Option>>) -> Vec> { + let mut res = vec![]; + tree_to_vec_dfs(root.as_ref(), 0, &mut res); + res +} diff --git a/ru/codes/rust/src/include/vertex.rs b/ru/codes/rust/src/include/vertex.rs new file mode 100644 index 000000000..2655d985e --- /dev/null +++ b/ru/codes/rust/src/include/vertex.rs @@ -0,0 +1,27 @@ +/* + * File: vertex.rs + * Created Time: 2023-07-13 + * Author: night-cruise (2586447362@qq.com) + */ + +/* Тип вершины */ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Vertex { + pub val: i32, +} + +impl From for Vertex { + fn from(value: i32) -> Self { + Self { val: value } + } +} + +/* На вход подается список значений vals, на выходе возвращается список вершин vets */ +pub fn vals_to_vets(vals: Vec) -> Vec { + vals.into_iter().map(|val| val.into()).collect() +} + +/* На вход подается список вершин vets, на выходе возвращается список значений vals */ +pub fn vets_to_vals(vets: Vec) -> Vec { + vets.into_iter().map(|vet| vet.val).collect() +} diff --git a/ru/codes/rust/src/lib.rs b/ru/codes/rust/src/lib.rs new file mode 100644 index 000000000..2883b9104 --- /dev/null +++ b/ru/codes/rust/src/lib.rs @@ -0,0 +1 @@ +pub mod include; diff --git a/ru/codes/swift/.gitignore b/ru/codes/swift/.gitignore new file mode 100644 index 000000000..6295af4cc --- /dev/null +++ b/ru/codes/swift/.gitignore @@ -0,0 +1,130 @@ +# Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager +# Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager + +### Objective-C ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Objective-C Patch ### + +### Swift ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + + + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + + +### SwiftPackageManager ### +Packages +xcuserdata +*.xcodeproj + + +# End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager diff --git a/ru/codes/swift/Package.resolved b/ru/codes/swift/Package.resolved new file mode 100644 index 000000000..159c83d26 --- /dev/null +++ b/ru/codes/swift/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "branch" : "release/1.1", + "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" + } + } + ], + "version" : 2 +} diff --git a/ru/codes/swift/Package.swift b/ru/codes/swift/Package.swift new file mode 100644 index 000000000..5326dc138 --- /dev/null +++ b/ru/codes/swift/Package.swift @@ -0,0 +1,206 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "HelloAlgo", + products: [ + // chapter_computational_complexity + .executable(name: "iteration", targets: ["iteration"]), + .executable(name: "recursion", targets: ["recursion"]), + .executable(name: "time_complexity", targets: ["time_complexity"]), + .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), + .executable(name: "space_complexity", targets: ["space_complexity"]), + // chapter_array_and_linkedlist + .executable(name: "array", targets: ["array"]), + .executable(name: "linked_list", targets: ["linked_list"]), + .executable(name: "list", targets: ["list"]), + .executable(name: "my_list", targets: ["my_list"]), + // chapter_stack_and_queue + .executable(name: "stack", targets: ["stack"]), + .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), + .executable(name: "array_stack", targets: ["array_stack"]), + .executable(name: "queue", targets: ["queue"]), + .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), + .executable(name: "array_queue", targets: ["array_queue"]), + .executable(name: "deque", targets: ["deque"]), + .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), + .executable(name: "array_deque", targets: ["array_deque"]), + // chapter_hashing + .executable(name: "hash_map", targets: ["hash_map"]), + .executable(name: "array_hash_map", targets: ["array_hash_map"]), + .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), + .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), + .executable(name: "simple_hash", targets: ["simple_hash"]), + .executable(name: "built_in_hash", targets: ["built_in_hash"]), + // chapter_tree + .executable(name: "binary_tree", targets: ["binary_tree"]), + .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), + .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), + .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), + .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), + .executable(name: "avl_tree", targets: ["avl_tree"]), + // chapter_heap + .executable(name: "heap", targets: ["heap"]), + .executable(name: "my_heap", targets: ["my_heap"]), + .executable(name: "top_k", targets: ["top_k"]), + // chapter_graph + .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), + .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), + .executable(name: "graph_bfs", targets: ["graph_bfs"]), + .executable(name: "graph_dfs", targets: ["graph_dfs"]), + // chapter_searching + .executable(name: "binary_search", targets: ["binary_search"]), + .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), + .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), + .executable(name: "two_sum", targets: ["two_sum"]), + .executable(name: "linear_search", targets: ["linear_search"]), + .executable(name: "hashing_search", targets: ["hashing_search"]), + // chapter_sorting + .executable(name: "selection_sort", targets: ["selection_sort"]), + .executable(name: "bubble_sort", targets: ["bubble_sort"]), + .executable(name: "insertion_sort", targets: ["insertion_sort"]), + .executable(name: "quick_sort", targets: ["quick_sort"]), + .executable(name: "merge_sort", targets: ["merge_sort"]), + .executable(name: "heap_sort", targets: ["heap_sort"]), + .executable(name: "bucket_sort", targets: ["bucket_sort"]), + .executable(name: "counting_sort", targets: ["counting_sort"]), + .executable(name: "radix_sort", targets: ["radix_sort"]), + // chapter_divide_and_conquer + .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), + .executable(name: "build_tree", targets: ["build_tree"]), + .executable(name: "hanota", targets: ["hanota"]), + // chapter_backtracking + .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), + .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), + .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), + .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), + .executable(name: "permutations_i", targets: ["permutations_i"]), + .executable(name: "permutations_ii", targets: ["permutations_ii"]), + .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), + .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), + .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), + .executable(name: "n_queens", targets: ["n_queens"]), + // chapter_dynamic_programming + .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), + .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), + .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), + .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), + .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), + .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), + .executable(name: "min_path_sum", targets: ["min_path_sum"]), + .executable(name: "knapsack", targets: ["knapsack"]), + .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), + .executable(name: "coin_change", targets: ["coin_change"]), + .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), + .executable(name: "edit_distance", targets: ["edit_distance"]), + // chapter_greedy + .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), + .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), + .executable(name: "max_capacity", targets: ["max_capacity"]), + .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), + ], + targets: [ + // helper + .target(name: "utils", path: "utils"), + .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), + .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), + // chapter_computational_complexity + .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), + .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), + .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), + .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), + .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), + // chapter_array_and_linkedlist + .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), + .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), + .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), + .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), + // chapter_stack_and_queue + .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), + .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), + .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), + .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), + .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), + .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), + .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), + .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), + .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), + // chapter_hashing + .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), + .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), + .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), + .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), + .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), + .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), + // chapter_tree + .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), + .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), + .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), + .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), + .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), + .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), + // chapter_heap + .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), + .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), + .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), + // chapter_graph + .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), + .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), + .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), + .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), + // chapter_searching + .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), + .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), + .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), + .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), + .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), + .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), + // chapter_sorting + .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), + .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), + .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), + .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), + .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), + .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), + .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), + .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), + .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), + // chapter_divide_and_conquer + .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), + .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), + .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), + // chapter_backtracking + .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), + .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), + .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), + .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), + .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), + .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), + .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), + .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), + // chapter_dynamic_programming + .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), + .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), + .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), + .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), + .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), + .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), + .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), + .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), + .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), + .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), + .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), + .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), + // chapter_greedy + .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), + .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), + .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), + .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), + ] +) diff --git a/ru/codes/swift/chapter_array_and_linkedlist/array.swift b/ru/codes/swift/chapter_array_and_linkedlist/array.swift new file mode 100644 index 000000000..b7cca7f2c --- /dev/null +++ b/ru/codes/swift/chapter_array_and_linkedlist/array.swift @@ -0,0 +1,107 @@ +/** + * File: array.swift + * Created Time: 2023-01-05 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Случайный доступ к элементу */ +func randomAccess(nums: [Int]) -> Int { + // Случайным образом выбрать число из интервала [0, nums.count) + let randomIndex = nums.indices.randomElement()! + // Получить и вернуть случайный элемент + let randomNum = nums[randomIndex] + return randomNum +} + +/* Увеличить длину массива */ +func extend(nums: [Int], enlarge: Int) -> [Int] { + // Инициализировать массив увеличенной длины + var res = Array(repeating: 0, count: nums.count + enlarge) + // Скопировать все элементы исходного массива в новый массив + for i in nums.indices { + res[i] = nums[i] + } + // Вернуть новый массив после расширения + return res +} + +/* Вставить элемент num по индексу index в массив */ +func insert(nums: inout [Int], num: Int, index: Int) { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for i in nums.indices.dropFirst(index).reversed() { + nums[i] = nums[i - 1] + } + // Присвоить num элементу по индексу index + nums[index] = num +} + +/* Удалить элемент по индексу index */ +func remove(nums: inout [Int], index: Int) { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for i in nums.indices.dropFirst(index).dropLast() { + nums[i] = nums[i + 1] + } +} + +/* Обход массива */ +func traverse(nums: [Int]) { + var count = 0 + // Обход массива по индексам + for i in nums.indices { + count += nums[i] + } + // Непосредственно обходить элементы массива + for num in nums { + count += num + } + // Одновременно обходить индексы и элементы данных + for (i, num) in nums.enumerated() { + count += nums[i] + count += num + } +} + +/* Найти заданный элемент в массиве */ +func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 +} + +@main +enum _Array { + /* Driver Code */ + static func main() { + /* Инициализация массива */ + let arr = Array(repeating: 0, count: 5) + print("Массив arr = \(arr)") + var nums = [1, 3, 2, 5, 4] + print("Массив nums = \(nums)") + + /* Случайный доступ */ + let randomNum = randomAccess(nums: nums) + print("Случайный элемент из nums = \(randomNum)") + + /* Расширение длины */ + nums = extend(nums: nums, enlarge: 3) + print("После увеличения длины массива до 8 nums = \(nums)") + + /* Вставка элемента */ + insert(nums: &nums, num: 6, index: 3) + print("После вставки числа 6 по индексу 3 nums = \(nums)") + + /* Удаление элемента */ + remove(nums: &nums, index: 2) + print("После удаления элемента по индексу 2 nums = \(nums)") + + /* Обход массива */ + traverse(nums: nums) + + /* Поиск элемента */ + let index = find(nums: nums, target: 3) + print("Поиск элемента 3 в nums: индекс = \(index)") + } +} diff --git a/ru/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/ru/codes/swift/chapter_array_and_linkedlist/linked_list.swift new file mode 100644 index 000000000..771194d87 --- /dev/null +++ b/ru/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -0,0 +1,90 @@ +/** + * File: linked_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Вставить узел P после узла n0 в связном списке */ +func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + P.next = n1 + n0.next = P +} + +/* Удалить первый узел после узла n0 в связном списке */ +func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 +} + +/* Доступ к узлу связного списка по индексу index */ +func access(head: ListNode, index: Int) -> ListNode? { + var head: ListNode? = head + for _ in 0 ..< index { + if head == nil { + return nil + } + head = head?.next + } + return head +} + +/* Найти в связном списке первый узел со значением target */ +func find(head: ListNode, target: Int) -> Int { + var head: ListNode? = head + var index = 0 + while head != nil { + if head?.val == target { + return index + } + head = head?.next + index += 1 + } + return -1 +} + +@main +enum LinkedList { + /* Driver Code */ + static func main() { + /* Инициализация связного списка */ + // Инициализация всех узлов + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // Построить ссылки между узлами + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("Исходный связный список") + PrintUtil.printLinkedList(head: n0) + + /* Вставка узла */ + insert(n0: n0, P: ListNode(x: 0)) + print("Связный список после вставки узла") + PrintUtil.printLinkedList(head: n0) + + /* Удаление узла */ + remove(n0: n0) + print("Связный список после удаления узла") + PrintUtil.printLinkedList(head: n0) + + /* Доступ к узлу */ + let node = access(head: n0, index: 3) + print("Значение узла по индексу 3 в связном списке = \(node!.val)") + + /* Поиск узла */ + let index = find(head: n0, target: 2) + print("Индекс узла со значением 2 в связном списке = \(index)") + } +} diff --git a/ru/codes/swift/chapter_array_and_linkedlist/list.swift b/ru/codes/swift/chapter_array_and_linkedlist/list.swift new file mode 100644 index 000000000..507c619b8 --- /dev/null +++ b/ru/codes/swift/chapter_array_and_linkedlist/list.swift @@ -0,0 +1,63 @@ +/** + * File: list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum List { + /* Driver Code */ + static func main() { + /* Инициализация списка */ + var nums = [1, 3, 2, 5, 4] + print("Список nums = \(nums)") + + /* Доступ к элементу */ + let num = nums[1] + print("Элемент по индексу 1: num = \(num)") + + /* Обновление элемента */ + nums[1] = 0 + print("После обновления элемента по индексу 1 до 0 nums = \(nums)") + + /* Очистить список */ + nums.removeAll() + print("После очистки списка nums = \(nums)") + + /* Добавление элемента в конец */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("После добавления элементов nums = \(nums)") + + /* Вставка элемента в середину */ + nums.insert(6, at: 3) + print("После вставки числа 6 по индексу 3 nums = \(nums)") + + /* Удаление элемента */ + nums.remove(at: 3) + print("После удаления элемента по индексу 3 nums = \(nums)") + + /* Обходить список по индексам */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + /* Непосредственно обходить элементы списка */ + count = 0 + for x in nums { + count += x + } + + /* Объединить два списка */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) + print("После конкатенации списка nums1 к nums nums = \(nums)") + + /* Отсортировать список */ + nums.sort() + print("После сортировки списка nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_array_and_linkedlist/my_list.swift b/ru/codes/swift/chapter_array_and_linkedlist/my_list.swift new file mode 100644 index 000000000..e4a89835c --- /dev/null +++ b/ru/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -0,0 +1,146 @@ +/** + * File: my_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Класс списка */ +class MyList { + private var arr: [Int] // Массив (для хранения элементов списка) + private var _capacity: Int // Вместимость списка + private var _size: Int // Длина списка (текущее число элементов) + private let extendRatio: Int // Коэффициент увеличения списка при каждом расширении + + /* Конструктор */ + init() { + _capacity = 10 + _size = 0 + extendRatio = 2 + arr = Array(repeating: 0, count: _capacity) + } + + /* Получить длину списка (текущее число элементов) */ + func size() -> Int { + _size + } + + /* Получить вместимость списка */ + func capacity() -> Int { + _capacity + } + + /* Доступ к элементу */ + func get(index: Int) -> Int { + // Если индекс выходит за границы, выбросить ошибку; далее аналогично + if index < 0 || index >= size() { + fatalError("индекс выходит за границы") + } + return arr[index] + } + + /* Обновление элемента */ + func set(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("индекс выходит за границы") + } + arr[index] = num + } + + /* Добавление элемента в конец */ + func add(num: Int) { + // При превышении вместимости по числу элементов запускается расширение + if size() == capacity() { + extendCapacity() + } + arr[size()] = num + // Обновить число элементов + _size += 1 + } + + /* Вставка элемента в середину */ + func insert(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("индекс выходит за границы") + } + // При превышении вместимости по числу элементов запускается расширение + if size() == capacity() { + extendCapacity() + } + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for j in (index ..< size()).reversed() { + arr[j + 1] = arr[j] + } + arr[index] = num + // Обновить число элементов + _size += 1 + } + + /* Удаление элемента */ + @discardableResult + func remove(index: Int) -> Int { + if index < 0 || index >= size() { + fatalError("индекс выходит за границы") + } + let num = arr[index] + // Сдвинуть все элементы после индекса index на одну позицию вперед + for j in index ..< (size() - 1) { + arr[j] = arr[j + 1] + } + // Обновить число элементов + _size -= 1 + // Вернуть удаленный элемент + return num + } + + /* Расширение списка */ + func extendCapacity() { + // Создать новый массив длиной в extendRatio раз больше исходного и скопировать в него исходный массив + arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) + // Обновить вместимость списка + _capacity = arr.count + } + + /* Преобразовать список в массив */ + func toArray() -> [Int] { + Array(arr.prefix(size())) + } +} + +@main +enum _MyList { + /* Driver Code */ + static func main() { + /* Инициализация списка */ + let nums = MyList() + /* Добавление элемента в конец */ + nums.add(num: 1) + nums.add(num: 3) + nums.add(num: 2) + nums.add(num: 5) + nums.add(num: 4) + print("Список nums = \(nums.toArray()) , вместимость = \(nums.capacity()) , длина = \(nums.size())") + + /* Вставка элемента в середину */ + nums.insert(index: 3, num: 6) + print("После вставки числа 6 по индексу 3 nums = \(nums.toArray())") + + /* Удаление элемента */ + nums.remove(index: 3) + print("После удаления элемента по индексу 3 nums = \(nums.toArray())") + + /* Доступ к элементу */ + let num = nums.get(index: 1) + print("Элемент по индексу 1: num = \(num)") + + /* Обновление элемента */ + nums.set(index: 1, num: 0) + print("После обновления элемента по индексу 1 до 0 nums = \(nums.toArray())") + + /* Проверка механизма расширения */ + for i in 0 ..< 10 { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(num: i) + } + print("Список nums после увеличения вместимости = \(nums.toArray()) , вместимость = \(nums.capacity()) , длина = \(nums.size())") + } +} diff --git a/ru/codes/swift/chapter_backtracking/n_queens.swift b/ru/codes/swift/chapter_backtracking/n_queens.swift new file mode 100644 index 000000000..54a4c4ab7 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/n_queens.swift @@ -0,0 +1,67 @@ +/** + * File: n_queens.swift + * Created Time: 2023-05-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Алгоритм бэктрекинга: n ферзей */ +func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { + // Когда все строки уже обработаны, записать решение + if row == n { + res.append(state) + return + } + // Обойти все столбцы + for col in 0 ..< n { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + let diag1 = row - col + n - 1 + let diag2 = row + col + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // Попытка: поставить ферзя в эту клетку + state[row][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + // Перейти к размещению следующей строки + backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + // Откат: восстановить эту клетку как пустую + state[row][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + } + } +} + +/* Решить задачу о n ферзях */ +func nQueens(n: Int) -> [[[String]]] { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + var state = Array(repeating: Array(repeating: "#", count: n), count: n) + var cols = Array(repeating: false, count: n) // Отмечать, есть ли ферзь в столбце + var diags1 = Array(repeating: false, count: 2 * n - 1) // Отмечать наличие ферзя на главной диагонали + var diags2 = Array(repeating: false, count: 2 * n - 1) // Отмечать наличие ферзя на побочной диагонали + var res: [[[String]]] = [] + + backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + + return res +} + +@main +enum NQueens { + /* Driver Code */ + static func main() { + let n = 4 + let res = nQueens(n: n) + + print("Размер входной доски = \(n)") + print("Количество способов расстановки ферзей: \(res.count)") + for state in res { + print("--------------------") + for row in state { + print(row) + } + } + } +} diff --git a/ru/codes/swift/chapter_backtracking/permutations_i.swift b/ru/codes/swift/chapter_backtracking/permutations_i.swift new file mode 100644 index 000000000..0ca9e6b55 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/permutations_i.swift @@ -0,0 +1,50 @@ +/** + * File: permutations_i.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Алгоритм бэктрекинга: все перестановки I */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // Когда длина состояния равна числу элементов, записать решение + if state.count == choices.count { + res.append(state) + return + } + // Перебор всех вариантов выбора + for (i, choice) in choices.enumerated() { + // Отсечение: нельзя выбирать один и тот же элемент повторно + if !selected[i] { + // Попытка: сделать выбор и обновить состояние + selected[i] = true + state.append(choice) + // Перейти к следующему выбору + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false + state.removeLast() + } + } +} + +/* Все перестановки I */ +func permutationsI(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsI { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsI(nums: nums) + + print("Входной массив nums = \(nums)") + print("Все перестановки res = \(res)") + } +} diff --git a/ru/codes/swift/chapter_backtracking/permutations_ii.swift b/ru/codes/swift/chapter_backtracking/permutations_ii.swift new file mode 100644 index 000000000..24a317263 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/permutations_ii.swift @@ -0,0 +1,52 @@ +/** + * File: permutations_ii.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Алгоритм бэктрекинга: все перестановки II */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // Когда длина состояния равна числу элементов, записать решение + if state.count == choices.count { + res.append(state) + return + } + // Перебор всех вариантов выбора + var duplicated: Set = [] + for (i, choice) in choices.enumerated() { + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if !selected[i], !duplicated.contains(choice) { + // Попытка: сделать выбор и обновить состояние + duplicated.insert(choice) // Записать значения уже выбранных элементов + selected[i] = true + state.append(choice) + // Перейти к следующему выбору + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false + state.removeLast() + } + } +} + +/* Все перестановки II */ +func permutationsII(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsII { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsII(nums: nums) + + print("Входной массив nums = \(nums)") + print("Все перестановки res = \(res)") + } +} diff --git a/ru/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift b/ru/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift new file mode 100644 index 000000000..c340c1d7a --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var res: [TreeNode] = [] + +/* Предварительный обход: пример 1 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // Записать решение + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) +} + +@main +enum PreorderTraversalICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + PrintUtil.printTree(root: root) + + // Предварительный обход + res = [] + preOrder(root: root) + + print("\nВсе узлы со значением 7") + var vals: [Int] = [] + for node in res { + vals.append(node.val) + } + print(vals) + } +} diff --git a/ru/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift b/ru/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift new file mode 100644 index 000000000..d3116e7f3 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* Предварительный обход: пример 2 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // Попытка + path.append(root) + if root.val == 7 { + // Записать решение + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // Откат + path.removeLast() +} + +@main +enum PreorderTraversalIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + PrintUtil.printTree(root: root) + + // Предварительный обход + path = [] + res = [] + preOrder(root: root) + + print("\nВсе пути от корня к узлу 7") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/ru/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift b/ru/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift new file mode 100644 index 000000000..b6dee82d8 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* Предварительный обход: пример 3 */ +func preOrder(root: TreeNode?) { + // Отсечение + guard let root = root, root.val != 3 else { + return + } + // Попытка + path.append(root) + if root.val == 7 { + // Записать решение + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // Откат + path.removeLast() +} + +@main +enum PreorderTraversalIIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + PrintUtil.printTree(root: root) + + // Предварительный обход + path = [] + res = [] + preOrder(root: root) + + print("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/ru/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift b/ru/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift new file mode 100644 index 000000000..0c9f99c5c --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Проверить, является ли текущее состояние решением */ +func isSolution(state: [TreeNode]) -> Bool { + !state.isEmpty && state.last!.val == 7 +} + +/* Записать решение */ +func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { + res.append(state) +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { + choice != nil && choice!.val != 3 +} + +/* Обновить состояние */ +func makeChoice(state: inout [TreeNode], choice: TreeNode) { + state.append(choice) +} + +/* Восстановить состояние */ +func undoChoice(state: inout [TreeNode], choice: TreeNode) { + state.removeLast() +} + +/* Алгоритм бэктрекинга: пример 3 */ +func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { + // Проверить, является ли текущее состояние решением + if isSolution(state: state) { + recordSolution(state: state, res: &res) + } + // Перебор всех вариантов выбора + for choice in choices { + // Отсечение: проверить допустимость выбора + if isValid(state: state, choice: choice) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state: &state, choice: choice) + // Перейти к следующему выбору + backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state: &state, choice: choice) + } + } +} + +@main +enum PreorderTraversalIIITemplate { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\nИнициализация двоичного дерева") + PrintUtil.printTree(root: root) + + // Алгоритм бэктрекинга + var state: [TreeNode] = [] + var res: [[TreeNode]] = [] + backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) + + print("\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/ru/codes/swift/chapter_backtracking/subset_sum_i.swift b/ru/codes/swift/chapter_backtracking/subset_sum_i.swift new file mode 100644 index 000000000..76421baaf --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/subset_sum_i.swift @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // Если сумма подмножества равна target, записать решение + if target == 0 { + res.append(state) + return + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for i in choices.indices.dropFirst(start) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target - choices[i] < 0 { + break + } + // Попытка: сделать выбор и обновить target и start + state.append(choices[i]) + // Перейти к следующему выбору + backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeLast() + } +} + +/* Решить задачу суммы подмножеств I */ +func subsetSumI(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // Состояние (подмножество) + let nums = nums.sorted() // Отсортировать nums + let start = 0 // Стартовая вершина обхода + var res: [[Int]] = [] // Список результатов (список подмножеств) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumI { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumI(nums: nums, target: target) + + print("Входной массив nums = \(nums), target = \(target)") + print("Все подмножества с суммой \(target): res = \(res)") + } +} diff --git a/ru/codes/swift/chapter_backtracking/subset_sum_i_naive.swift b/ru/codes/swift/chapter_backtracking/subset_sum_i_naive.swift new file mode 100644 index 000000000..a56d23a09 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/subset_sum_i_naive.swift @@ -0,0 +1,51 @@ +/** + * File: subset_sum_i_naive.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { + // Если сумма подмножества равна target, записать решение + if total == target { + res.append(state) + return + } + // Перебор всех вариантов выбора + for i in choices.indices { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if total + choices[i] > target { + continue + } + // Попытка: сделать выбор и обновить элемент и total + state.append(choices[i]) + // Перейти к следующему выбору + backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeLast() + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // Состояние (подмножество) + let total = 0 // Сумма подмножеств + var res: [[Int]] = [] // Список результатов (список подмножеств) + backtrack(state: &state, target: target, total: total, choices: nums, res: &res) + return res +} + +@main +enum SubsetSumINaive { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumINaive(nums: nums, target: target) + + print("Входной массив nums = \(nums), target = \(target)") + print("Все подмножества с суммой \(target): res = \(res)") + print("Обратите внимание: результат этого метода содержит повторяющиеся множества") + } +} diff --git a/ru/codes/swift/chapter_backtracking/subset_sum_ii.swift b/ru/codes/swift/chapter_backtracking/subset_sum_ii.swift new file mode 100644 index 000000000..a99cdb9b7 --- /dev/null +++ b/ru/codes/swift/chapter_backtracking/subset_sum_ii.swift @@ -0,0 +1,58 @@ +/** + * File: subset_sum_ii.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // Если сумма подмножества равна target, записать решение + if target == 0 { + res.append(state) + return + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for i in choices.indices.dropFirst(start) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if target - choices[i] < 0 { + break + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if i > start, choices[i] == choices[i - 1] { + continue + } + // Попытка: сделать выбор и обновить target и start + state.append(choices[i]) + // Перейти к следующему выбору + backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + state.removeLast() + } +} + +/* Решить задачу суммы подмножеств II */ +func subsetSumII(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // Состояние (подмножество) + let nums = nums.sorted() // Отсортировать nums + let start = 0 // Стартовая вершина обхода + var res: [[Int]] = [] // Список результатов (список подмножеств) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumII { + /* Driver Code */ + static func main() { + let nums = [4, 4, 5] + let target = 9 + + let res = subsetSumII(nums: nums, target: target) + + print("Входной массив nums = \(nums), target = \(target)") + print("Все подмножества с суммой \(target): res = \(res)") + } +} diff --git a/ru/codes/swift/chapter_computational_complexity/iteration.swift b/ru/codes/swift/chapter_computational_complexity/iteration.swift new file mode 100644 index 000000000..0287d5c3e --- /dev/null +++ b/ru/codes/swift/chapter_computational_complexity/iteration.swift @@ -0,0 +1,75 @@ +/** + * File: iteration.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Цикл for */ +func forLoop(n: Int) -> Int { + var res = 0 + // Циклическое суммирование 1, 2, ..., n-1, n + for i in 1 ... n { + res += i + } + return res +} + +/* Цикл while */ +func whileLoop(n: Int) -> Int { + var res = 0 + var i = 1 // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while i <= n { + res += i + i += 1 // Обновить условную переменную + } + return res +} + +/* Цикл while (двойное обновление) */ +func whileLoopII(n: Int) -> Int { + var res = 0 + var i = 1 // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while i <= n { + res += i + // Обновить условную переменную + i += 1 + i *= 2 + } + return res +} + +/* Двойной цикл for */ +func nestedForLoop(n: Int) -> String { + var res = "" + // Цикл по i = 1, 2, ..., n-1, n + for i in 1 ... n { + // Цикл по j = 1, 2, ..., n-1, n + for j in 1 ... n { + res.append("(\(i), \(j)), ") + } + } + return res +} + +@main +enum Iteration { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = forLoop(n: n) + print("\nРезультат суммирования в цикле for res = \(res)") + + res = whileLoop(n: n) + print("\nРезультат суммирования в цикле while res = \(res)") + + res = whileLoopII(n: n) + print("\nРезультат суммирования в цикле while (двойное обновление) res = \(res)") + + let resStr = nestedForLoop(n: n) + print("\nРезультат обхода в двойном цикле for \(resStr)") + } +} diff --git a/ru/codes/swift/chapter_computational_complexity/recursion.swift b/ru/codes/swift/chapter_computational_complexity/recursion.swift new file mode 100644 index 000000000..155cd8fbe --- /dev/null +++ b/ru/codes/swift/chapter_computational_complexity/recursion.swift @@ -0,0 +1,79 @@ +/** + * File: recursion.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Рекурсия */ +func recur(n: Int) -> Int { + // Условие завершения + if n == 1 { + return 1 + } + // Рекурсия: рекурсивный вызов + let res = recur(n: n - 1) + // Возврат: вернуть результат + return n + res +} + +/* Имитация рекурсии итерацией */ +func forLoopRecur(n: Int) -> Int { + // Использовать явный стек для имитации системного стека вызовов + var stack: [Int] = [] + var res = 0 + // Рекурсия: рекурсивный вызов + for i in (1 ... n).reversed() { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.append(i) + } + // Возврат: вернуть результат + while !stack.isEmpty { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.removeLast() + } + // res = 1+2+3+...+n + return res +} + +/* Хвостовая рекурсия */ +func tailRecur(n: Int, res: Int) -> Int { + // Условие завершения + if n == 0 { + return res + } + // Хвостовой рекурсивный вызов + return tailRecur(n: n - 1, res: res + n) +} + +/* Последовательность Фибоначчи: рекурсия */ +func fib(n: Int) -> Int { + // Условие завершения: f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + let res = fib(n: n - 1) + fib(n: n - 2) + // Вернуть результат f(n) + return res +} + +@main +enum Recursion { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = recursion.recur(n: n) + print("\nРезультат суммирования в рекурсивной функции res = \(res)") + + res = recursion.forLoopRecur(n: n) + print("\nРезультат суммирования при имитации рекурсии res = \(res)") + + res = recursion.tailRecur(n: n, res: 0) + print("\nРезультат суммирования в хвостовой рекурсии res = \(res)") + + res = recursion.fib(n: n) + print("\nЧлен последовательности Фибоначчи с номером \(n) = \(res)") + } +} diff --git a/ru/codes/swift/chapter_computational_complexity/space_complexity.swift b/ru/codes/swift/chapter_computational_complexity/space_complexity.swift new file mode 100644 index 000000000..ad3d890bd --- /dev/null +++ b/ru/codes/swift/chapter_computational_complexity/space_complexity.swift @@ -0,0 +1,98 @@ +/** + * File: space_complexity.swift + * Created Time: 2023-01-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Функция */ +@discardableResult +func function() -> Int { + // Выполнить некоторые операции + return 0 +} + +/* Постоянная сложность */ +func constant(n: Int) { + // Константы, переменные и объекты занимают O(1) памяти + let a = 0 + var b = 0 + let nums = Array(repeating: 0, count: 10000) + let node = ListNode(x: 0) + // Переменные в цикле занимают O(1) памяти + for _ in 0 ..< n { + let c = 0 + } + // Функции в цикле занимают O(1) памяти + for _ in 0 ..< n { + function() + } +} + +/* Линейная сложность */ +func linear(n: Int) { + // Массив длины n занимает O(n) памяти + let nums = Array(repeating: 0, count: n) + // Список длины n занимает O(n) памяти + let nodes = (0 ..< n).map { ListNode(x: $0) } + // Хеш-таблица длины n занимает O(n) памяти + let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) +} + +/* Линейная сложность (рекурсивная реализация) */ +func linearRecur(n: Int) { + print("Рекурсия n = \(n)") + if n == 1 { + return + } + linearRecur(n: n - 1) +} + +/* Квадратичная сложность */ +func quadratic(n: Int) { + // Двумерный список занимает O(n^2) памяти + let numList = Array(repeating: Array(repeating: 0, count: n), count: n) +} + +/* Квадратичная сложность (рекурсивная реализация) */ +@discardableResult +func quadraticRecur(n: Int) -> Int { + if n <= 0 { + return 0 + } + // Длина массива nums равна n, n-1, ..., 2, 1 + let nums = Array(repeating: 0, count: n) + print("В рекурсии n = \(n), длина nums = \(nums.count)") + return quadraticRecur(n: n - 1) +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +func buildTree(n: Int) -> TreeNode? { + if n == 0 { + return nil + } + let root = TreeNode(x: 0) + root.left = buildTree(n: n - 1) + root.right = buildTree(n: n - 1) + return root +} + +@main +enum SpaceComplexity { + /* Driver Code */ + static func main() { + let n = 5 + // Постоянная сложность + constant(n: n) + // Линейная сложность + linear(n: n) + linearRecur(n: n) + // Квадратичная сложность + quadratic(n: n) + quadraticRecur(n: n) + // Экспоненциальная сложность + let root = buildTree(n: n) + PrintUtil.printTree(root: root) + } +} diff --git a/ru/codes/swift/chapter_computational_complexity/time_complexity.swift b/ru/codes/swift/chapter_computational_complexity/time_complexity.swift new file mode 100644 index 000000000..16de375fd --- /dev/null +++ b/ru/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -0,0 +1,172 @@ +/** + * File: time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Постоянная сложность */ +func constant(n: Int) -> Int { + var count = 0 + let size = 100_000 + for _ in 0 ..< size { + count += 1 + } + return count +} + +/* Линейная сложность */ +func linear(n: Int) -> Int { + var count = 0 + for _ in 0 ..< n { + count += 1 + } + return count +} + +/* Линейная сложность (обход массива) */ +func arrayTraversal(nums: [Int]) -> Int { + var count = 0 + // Число итераций пропорционально длине массива + for _ in nums { + count += 1 + } + return count +} + +/* Квадратичная сложность */ +func quadratic(n: Int) -> Int { + var count = 0 + // Число итераций квадратично зависит от размера данных n + for _ in 0 ..< n { + for _ in 0 ..< n { + count += 1 + } + } + return count +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +func bubbleSort(nums: inout [Int]) -> Int { + var count = 0 // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for i in nums.indices.dropFirst().reversed() { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 // Обмен элементов включает 3 элементарные операции + } + } + } + return count +} + +/* Экспоненциальная сложность (итеративная реализация) */ +func exponential(n: Int) -> Int { + var count = 0 + var base = 1 + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for _ in 0 ..< n { + for _ in 0 ..< base { + count += 1 + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +func expRecur(n: Int) -> Int { + if n == 1 { + return 1 + } + return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 +} + +/* Логарифмическая сложность (итеративная реализация) */ +func logarithmic(n: Int) -> Int { + var count = 0 + var n = n + while n > 1 { + n = n / 2 + count += 1 + } + return count +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +func logRecur(n: Int) -> Int { + if n <= 1 { + return 0 + } + return logRecur(n: n / 2) + 1 +} + +/* Линейно-логарифмическая сложность */ +func linearLogRecur(n: Int) -> Int { + if n <= 1 { + return 1 + } + var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) + for _ in stride(from: 0, to: n, by: 1) { + count += 1 + } + return count +} + +/* Факториальная сложность (рекурсивная реализация) */ +func factorialRecur(n: Int) -> Int { + if n == 0 { + return 1 + } + var count = 0 + // Из одного получается n + for _ in 0 ..< n { + count += factorialRecur(n: n - 1) + } + return count +} + +@main +enum TimeComplexity { + /* Driver Code */ + static func main() { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + let n = 8 + print("Размер входных данных n = \(n)") + + var count = constant(n: n) + print("Число операций константной сложности = \(count)") + + count = linear(n: n) + print("Число операций линейной сложности = \(count)") + count = arrayTraversal(nums: Array(repeating: 0, count: n)) + print("Число операций линейной сложности (обход массива) = \(count)") + + count = quadratic(n: n) + print("Число операций квадратичной сложности = \(count)") + var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] + count = bubbleSort(nums: &nums) + print("Число операций квадратичной сложности (пузырьковая сортировка) = \(count)") + + count = exponential(n: n) + print("Число операций экспоненциальной сложности (итеративная реализация) = \(count)") + count = expRecur(n: n) + print("Число операций экспоненциальной сложности (рекурсивная реализация) = \(count)") + + count = logarithmic(n: n) + print("Число операций логарифмической сложности (итеративная реализация) = \(count)") + count = logRecur(n: n) + print("Число операций логарифмической сложности (рекурсивная реализация) = \(count)") + + count = linearLogRecur(n: n) + print("Число операций линейно-логарифмической сложности (рекурсивная реализация) = \(count)") + + count = factorialRecur(n: n) + print("Число операций факториальной сложности (рекурсивная реализация) = \(count)") + } +} diff --git a/ru/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/ru/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift new file mode 100644 index 000000000..442500584 --- /dev/null +++ b/ru/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +func randomNumbers(n: Int) -> [Int] { + // Создать массив nums = { 1, 2, 3, ..., n } + var nums = Array(1 ... n) + // Случайно перемешать элементы массива + nums.shuffle() + return nums +} + +/* Найти индекс числа 1 в массиве nums */ +func findOne(nums: [Int]) -> Int { + for i in nums.indices { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if nums[i] == 1 { + return i + } + } + return -1 +} + +@main +enum WorstBestTimeComplexity { + /* Driver Code */ + static func main() { + for _ in 0 ..< 10 { + let n = 100 + let nums = randomNumbers(n: n) + let index = findOne(nums: nums) + print("Массив [1, 2, ..., n] после перемешивания = \(nums)") + print("Индекс числа 1 = \(index)") + } + } +} diff --git a/ru/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift b/ru/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift new file mode 100644 index 000000000..10e5667fc --- /dev/null +++ b/ru/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift @@ -0,0 +1,44 @@ +/** + * File: binary_search_recur.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Бинарный поиск: задача f(i, j) */ +func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if i > j { + return -1 + } + // Вычислить индекс середины m + let m = (i + j) / 2 + if nums[m] < target { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums: nums, target: target, i: m + 1, j: j) + } else if nums[m] > target { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums: nums, target: target, i: i, j: m - 1) + } else { + // Целевой элемент найден, вернуть его индекс + return m + } +} + +/* Бинарный поиск */ +func binarySearch(nums: [Int], target: Int) -> Int { + // Решить задачу f(0, n-1) + dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) +} + +@main +enum BinarySearchRecur { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + // Бинарный поиск (двусторонне замкнутый интервал) + let index = binarySearch(nums: nums, target: target) + print("Индекс целевого элемента 6 = \(index)") + } +} diff --git a/ru/codes/swift/chapter_divide_and_conquer/build_tree.swift b/ru/codes/swift/chapter_divide_and_conquer/build_tree.swift new file mode 100644 index 000000000..a68a3a2cc --- /dev/null +++ b/ru/codes/swift/chapter_divide_and_conquer/build_tree.swift @@ -0,0 +1,47 @@ +/** + * File: build_tree.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Построить двоичное дерево: разделяй и властвуй */ +func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { + // Завершить при пустом диапазоне поддерева + if r - l < 0 { + return nil + } + // Инициализировать корневой узел + let root = TreeNode(x: preorder[i]) + // Найти m, чтобы разделить левое и правое поддеревья + let m = inorderMap[preorder[i]]! + // Подзадача: построить левое поддерево + root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) + // Подзадача: построить правое поддерево + root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) + // Вернуть корневой узел + return root +} + +/* Построить двоичное дерево */ +func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } + return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) +} + +@main +enum BuildTree { + /* Driver Code */ + static func main() { + let preorder = [3, 9, 2, 1, 7] + let inorder = [9, 3, 1, 2, 7] + print("Предварительный обход = \(preorder)") + print("Симметричный обход = \(inorder)") + + let root = buildTree(preorder: preorder, inorder: inorder) + print("Построенное двоичное дерево:") + PrintUtil.printTree(root: root) + } +} diff --git a/ru/codes/swift/chapter_divide_and_conquer/hanota.swift b/ru/codes/swift/chapter_divide_and_conquer/hanota.swift new file mode 100644 index 000000000..3beb234f7 --- /dev/null +++ b/ru/codes/swift/chapter_divide_and_conquer/hanota.swift @@ -0,0 +1,58 @@ +/** + * File: hanota.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Переместить один диск */ +func move(src: inout [Int], tar: inout [Int]) { + // Снять диск с вершины src + let pan = src.popLast()! + // Положить диск на вершину tar + tar.append(pan) +} + +/* Решить задачу Ханойской башни f(i) */ +func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { + // Если в src остался только один диск, сразу переместить его в tar + if i == 1 { + move(src: &src, tar: &tar) + return + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src: &src, tar: &tar) + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) +} + +/* Решить задачу Ханойской башни */ +func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { + let n = A.count + // Хвост списка соответствует вершине столбца + // Переместить верхние n дисков из src в C с помощью B + dfs(i: n, src: &A, buf: &B, tar: &C) +} + +@main +enum Hanota { + /* Driver Code */ + static func main() { + // Хвост списка соответствует вершине столбца + var A = [5, 4, 3, 2, 1] + var B: [Int] = [] + var C: [Int] = [] + print("Исходное состояние:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + + solveHanota(A: &A, B: &B, C: &C) + + print("После завершения перемещения дисков:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift new file mode 100644 index 000000000..99dad8598 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Бэктрекинг */ +func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if state == n { + res[0] += 1 + } + // Перебор всех вариантов выбора + for choice in choices { + // Отсечение: нельзя выходить за n-ю ступень + if state + choice > n { + continue + } + // Попытка: сделать выбор и обновить состояние + backtrack(choices: choices, state: state + choice, n: n, res: &res) + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // Можно подняться на 1 или 2 ступени + let state = 0 // Начать подъем с 0-й ступени + var res: [Int] = [] + res.append(0) // Использовать res[0] для хранения числа решений + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] +} + +@main +enum ClimbingStairsBacktrack { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsBacktrack(n: n) + print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift new file mode 100644 index 000000000..f0bdb15d5 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return 1 + } + // Инициализация таблицы dp для хранения решений подзадач + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3 ... n { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +@main +enum ClimbingStairsConstraintDP { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsConstraintDP(n: n) + print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift new file mode 100644 index 000000000..31dc58064 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Поиск */ +func dfs(i: Int) -> Int { + // dp[1] и dp[2] уже известны, вернуть их + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count +} + +/* Подъем по лестнице: поиск */ +func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) +} + +@main +enum ClimbingStairsDFS { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFS(n: n) + print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift new file mode 100644 index 000000000..2d1d0a66d --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dfs_mem.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Поиск с мемоизацией */ +func dfs(i: Int, mem: inout [Int]) -> Int { + // dp[1] и dp[2] уже известны, вернуть их + if i == 1 || i == 2 { + return i + } + // Если запись dp[i] существует, сразу вернуть ее + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // Сохранить dp[i] + mem[i] = count + return count +} + +/* Подъем по лестнице: поиск с мемоизацией */ +func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) +} + +@main +enum ClimbingStairsDFSMem { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFSMem(n: n) + print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift new file mode 100644 index 000000000..86f59dc69 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Подъем по лестнице: динамическое программирование */ +func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // Инициализация таблицы dp для хранения решений подзадач + var dp = Array(repeating: 0, count: n + 1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1 + dp[2] = 2 + // Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3 ... n { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in 3 ... n { + (a, b) = (b, a + b) + } + return b +} + +@main +enum ClimbingStairsDP { + /* Driver Code */ + static func main() { + let n = 9 + + var res = climbingStairsDP(n: n) + print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") + + res = climbingStairsDPComp(n: n) + print("Количество способов подняться по лестнице из \(n) ступеней = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/coin_change.swift b/ru/codes/swift/chapter_dynamic_programming/coin_change.swift new file mode 100644 index 000000000..089945a7d --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/coin_change.swift @@ -0,0 +1,69 @@ +/** + * File: coin_change.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Размен монет: динамическое программирование */ +func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // Инициализация таблицы dp + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // Переход состояний: первая строка и первый столбец + for a in 1 ... amt { + dp[0][a] = MAX + } + // Переход состояний: остальные строки и столбцы + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1 +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // Инициализация таблицы dp + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // Переход состояний + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return dp[amt] != MAX ? dp[amt] : -1 +} + +@main +enum CoinChange { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 4 + + // Динамическое программирование + var res = coinChangeDP(coins: coins, amt: amt) + print("Минимальное число монет для набора целевой суммы = \(res)") + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(coins: coins, amt: amt) + print("Минимальное число монет для набора целевой суммы = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/coin_change_ii.swift b/ru/codes/swift/chapter_dynamic_programming/coin_change_ii.swift new file mode 100644 index 000000000..750b52344 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/coin_change_ii.swift @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Размен монет II: динамическое программирование */ +func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // Инициализация таблицы dp + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // Инициализация первого столбца + for i in 0 ... n { + dp[i][0] = 1 + } + // Переход состояний + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a] + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // Инициализация таблицы dp + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // Переход состояний + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a] + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +@main +enum CoinChangeII { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 5 + + // Динамическое программирование + var res = coinChangeIIDP(coins: coins, amt: amt) + print("Количество комбинаций монет для набора целевой суммы = \(res)") + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(coins: coins, amt: amt) + print("Количество комбинаций монет для набора целевой суммы = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/edit_distance.swift b/ru/codes/swift/chapter_dynamic_programming/edit_distance.swift new file mode 100644 index 000000000..41ce6471f --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/edit_distance.swift @@ -0,0 +1,147 @@ +/** + * File: edit_distance.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Редакционное расстояние: полный перебор */ +func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { + // Если s и t пусты, вернуть 0 + if i == 0, j == 0 { + return 0 + } + // Если s пусто, вернуть длину t + if i == 0 { + return j + } + // Если t пусто, вернуть длину s + if j == 0 { + return i + } + // Если два символа равны, сразу пропустить их + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // Вернуть минимальное число шагов редактирования + return min(min(insert, delete), replace) + 1 +} + +/* Редакционное расстояние: поиск с мемоизацией */ +func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { + // Если s и t пусты, вернуть 0 + if i == 0, j == 0 { + return 0 + } + // Если s пусто, вернуть длину t + if i == 0 { + return j + } + // Если t пусто, вернуть длину s + if j == 0 { + return i + } + // Если запись уже есть, сразу вернуть ее + if mem[i][j] != -1 { + return mem[i][j] + } + // Если два символа равны, сразу пропустить их + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* Редакционное расстояние: динамическое программирование */ +func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // Переход состояний: первая строка и первый столбец + for i in 1 ... n { + dp[i][0] = i + } + for j in 1 ... m { + dp[0][j] = j + } + // Переход состояний: остальные строки и столбцы + for i in 1 ... n { + for j in 1 ... m { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1] + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // Переход состояний: первая строка + for j in 1 ... m { + dp[j] = j + } + // Переход состояний: остальные строки + for i in 1 ... n { + // Переход состояний: первый столбец + var leftup = dp[0] // Временно сохранить dp[i-1, j-1] + dp[0] = i + // Переход состояний: остальные столбцы + for j in 1 ... m { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // Если два символа равны, сразу пропустить их + dp[j] = leftup + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m] +} + +@main +enum EditDistance { + /* Driver Code */ + static func main() { + let s = "bag" + let t = "pack" + let n = s.utf8CString.count + let m = t.utf8CString.count + + // Полный перебор + var res = editDistanceDFS(s: s, t: t, i: n, j: m) + print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") + + // Поиск с мемоизацией + var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) + res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) + print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") + + // Динамическое программирование + res = editDistanceDP(s: s, t: t) + print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s: s, t: t) + print("Чтобы преобразовать \(s) в \(t), нужно минимум \(res) шагов") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/knapsack.swift b/ru/codes/swift/chapter_dynamic_programming/knapsack.swift new file mode 100644 index 000000000..ac1e9a808 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/knapsack.swift @@ -0,0 +1,110 @@ +/** + * File: knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Рюкзак 0-1: полный перебор */ +func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 || c == 0 { + return 0 + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // Вернуть вариант с большей стоимостью из двух возможных + return max(no, yes) +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if i == 0 || c == 0 { + return 0 + } + // Если запись уже есть, вернуть сразу + if mem[i][c] != -1 { + return mem[i][c] + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if wgt[i - 1] > c { + return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* Рюкзак 0-1: динамическое программирование */ +func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // Инициализация таблицы dp + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // Переход состояний + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // Инициализация таблицы dp + var dp = Array(repeating: 0, count: cap + 1) + // Переход состояний + for i in 1 ... n { + // Обход в обратном порядке + for c in (1 ... cap).reversed() { + if wgt[i - 1] <= c { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum Knapsack { + /* Driver Code */ + static func main() { + let wgt = [10, 20, 30, 40, 50] + let val = [50, 120, 150, 210, 240] + let cap = 50 + let n = wgt.count + + // Полный перебор + var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + + // Поиск с мемоизацией + var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) + res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + + // Динамическое программирование + res = knapsackDP(wgt: wgt, val: val, cap: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(wgt: wgt, val: val, cap: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift b/ru/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift new file mode 100644 index 000000000..9e43ee431 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // Инициализация таблицы dp для хранения решений подзадач + var dp = Array(repeating: 0, count: n + 1) + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1] + dp[2] = cost[2] + // Переход состояний: постепенное решение больших подзадач через меньшие + for i in 3 ... n { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in 3 ... n { + (a, b) = (b, min(a, b) + cost[i]) + } + return b +} + +@main +enum MinCostClimbingStairsDP { + /* Driver Code */ + static func main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print("Список стоимостей ступеней = \(cost)") + + var res = minCostClimbingStairsDP(cost: cost) + print("Минимальная стоимость подъема по лестнице = \(res)") + + res = minCostClimbingStairsDPComp(cost: cost) + print("Минимальная стоимость подъема по лестнице = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/min_path_sum.swift b/ru/codes/swift/chapter_dynamic_programming/min_path_sum.swift new file mode 100644 index 000000000..e43b4825e --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/min_path_sum.swift @@ -0,0 +1,123 @@ +/** + * File: min_path_sum.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Минимальная сумма пути: полный перебор */ +func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // Если это верхняя левая ячейка, завершить поиск + if i == 0, j == 0 { + return grid[0][0] + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 || j < 0 { + return .max + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + let up = minPathSumDFS(grid: grid, i: i - 1, j: j) + let left = minPathSumDFS(grid: grid, i: i, j: j - 1) + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return min(left, up) + grid[i][j] +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // Если это верхняя левая ячейка, завершить поиск + if i == 0, j == 0 { + return grid[0][0] + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if i < 0 || j < 0 { + return .max + } + // Если запись уже есть, вернуть сразу + if mem[i][j] != -1 { + return mem[i][j] + } + // Минимальная стоимость пути для левой и верхней ячеек + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* Минимальная сумма пути: динамическое программирование */ +func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // Инициализация таблицы dp + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // Переход состояний: первая строка + for j in 1 ..< m { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // Переход состояний: первый столбец + for i in 1 ..< n { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // Переход состояний: остальные строки и столбцы + for i in 1 ..< n { + for j in 1 ..< m { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // Инициализация таблицы dp + var dp = Array(repeating: 0, count: m) + // Переход состояний: первая строка + dp[0] = grid[0][0] + for j in 1 ..< m { + dp[j] = dp[j - 1] + grid[0][j] + } + // Переход состояний: остальные строки + for i in 1 ..< n { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0] + // Переход состояний: остальные столбцы + for j in 1 ..< m { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] +} + +@main +enum MinPathSum { + /* Driver Code */ + static func main() { + let grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ] + let n = grid.count + let m = grid[0].count + + // Полный перебор + var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) + print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") + + // Поиск с мемоизацией + var mem = Array(repeating: Array(repeating: -1, count: m), count: n) + res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) + print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") + + // Динамическое программирование + res = minPathSumDP(grid: grid) + print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") + + // Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(grid: grid) + print("Минимальная сумма пути из левого верхнего угла в правый нижний = \(res)") + } +} diff --git a/ru/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift b/ru/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift new file mode 100644 index 000000000..f4471bb91 --- /dev/null +++ b/ru/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Полный рюкзак: динамическое программирование */ +func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // Инициализация таблицы dp + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // Переход состояний + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // Инициализация таблицы dp + var dp = Array(repeating: 0, count: cap + 1) + // Переход состояний + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c] + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum UnboundedKnapsack { + /* Driver Code */ + static func main() { + let wgt = [1, 2, 3] + let val = [5, 11, 15] + let cap = 4 + + // Динамическое программирование + var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + } +} diff --git a/ru/codes/swift/chapter_graph/graph_adjacency_list.swift b/ru/codes/swift/chapter_graph/graph_adjacency_list.swift new file mode 100644 index 000000000..897bf6f63 --- /dev/null +++ b/ru/codes/swift/chapter_graph/graph_adjacency_list.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Класс неориентированного графа на основе списка смежности */ +public class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + public private(set) var adjList: [Vertex: [Vertex]] + + /* Конструктор */ + public init(edges: [[Vertex]]) { + adjList = [:] + // Добавить все вершины и ребра + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* Получить число вершин */ + public func size() -> Int { + adjList.count + } + + /* Добавление ребра */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("Неверный аргумент") + } + // Добавить ребро vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* Удаление ребра */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("Неверный аргумент") + } + // Удалить ребро vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* Добавление вершины */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // Добавить новый список в список смежности + adjList[vet] = [] + } + + /* Удаление вершины */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("Неверный аргумент") + } + // Удалить из списка смежности список, соответствующий вершине vet + adjList.removeValue(forKey: vet) + // Обойти списки других вершин и удалить все ребра, содержащие vet + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* Вывести список смежности */ + public func print() { + Swift.print("Список смежности =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* Инициализация неориентированного графа */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\nГраф после инициализации") + graph.print() + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\nГраф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\nГраф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\nГраф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(vet: v[1]) + print("\nГраф после удаления вершины 3") + graph.print() + } +} + +#endif diff --git a/ru/codes/swift/chapter_graph/graph_adjacency_list_target.swift b/ru/codes/swift/chapter_graph/graph_adjacency_list_target.swift new file mode 100644 index 000000000..897bf6f63 --- /dev/null +++ b/ru/codes/swift/chapter_graph/graph_adjacency_list_target.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Класс неориентированного графа на основе списка смежности */ +public class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + public private(set) var adjList: [Vertex: [Vertex]] + + /* Конструктор */ + public init(edges: [[Vertex]]) { + adjList = [:] + // Добавить все вершины и ребра + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* Получить число вершин */ + public func size() -> Int { + adjList.count + } + + /* Добавление ребра */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("Неверный аргумент") + } + // Добавить ребро vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* Удаление ребра */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("Неверный аргумент") + } + // Удалить ребро vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* Добавление вершины */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // Добавить новый список в список смежности + adjList[vet] = [] + } + + /* Удаление вершины */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("Неверный аргумент") + } + // Удалить из списка смежности список, соответствующий вершине vet + adjList.removeValue(forKey: vet) + // Обойти списки других вершин и удалить все ребра, содержащие vet + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* Вывести список смежности */ + public func print() { + Swift.print("Список смежности =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* Инициализация неориентированного графа */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\nГраф после инициализации") + graph.print() + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v[0] и v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\nГраф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v[0] и v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\nГраф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\nГраф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Вершина 3 соответствует v[1] + graph.removeVertex(vet: v[1]) + print("\nГраф после удаления вершины 3") + graph.print() + } +} + +#endif diff --git a/ru/codes/swift/chapter_graph/graph_adjacency_matrix.swift b/ru/codes/swift/chapter_graph/graph_adjacency_matrix.swift new file mode 100644 index 000000000..169c431fa --- /dev/null +++ b/ru/codes/swift/chapter_graph/graph_adjacency_matrix.swift @@ -0,0 +1,130 @@ +/** + * File: graph_adjacency_matrix.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + private var vertices: [Int] // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + private var adjMat: [[Int]] // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + init(vertices: [Int], edges: [[Int]]) { + self.vertices = [] + adjMat = [] + // Добавление вершины + for val in vertices { + addVertex(val: val) + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for e in edges { + addEdge(i: e[0], j: e[1]) + } + } + + /* Получить число вершин */ + func size() -> Int { + vertices.count + } + + /* Добавление вершины */ + func addVertex(val: Int) { + let n = size() + // Добавить значение новой вершины в список вершин + vertices.append(val) + // Добавить строку в матрицу смежности + let newRow = Array(repeating: 0, count: n) + adjMat.append(newRow) + // Добавить столбец в матрицу смежности + for i in adjMat.indices { + adjMat[i].append(0) + } + } + + /* Удаление вершины */ + func removeVertex(index: Int) { + if index >= size() { + fatalError("Выход за границы диапазона") + } + // Удалить вершину с индексом index из списка вершин + vertices.remove(at: index) + // Удалить строку с индексом index из матрицы смежности + adjMat.remove(at: index) + // Удалить столбец с индексом index из матрицы смежности + for i in adjMat.indices { + adjMat[i].remove(at: index) + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + func addEdge(i: Int, j: Int) { + // Обработка выхода индекса за границы и случая равенства + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("Выход за границы диапазона") + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + func removeEdge(i: Int, j: Int) { + // Обработка выхода индекса за границы и случая равенства + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("Выход за границы диапазона") + } + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* Вывести матрицу смежности */ + func print() { + Swift.print("Список вершин = ", terminator: "") + Swift.print(vertices) + Swift.print("Матрица смежности =") + PrintUtil.printMatrix(matrix: adjMat) + } +} + +@main +enum GraphAdjacencyMatrix { + /* Driver Code */ + static func main() { + /* Инициализация неориентированного графа */ + // Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices + let vertices = [1, 3, 2, 5, 4] + let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] + let graph = GraphAdjMat(vertices: vertices, edges: edges) + print("\nГраф после инициализации") + graph.print() + + /* Добавление ребра */ + // Индексы вершин 1 и 2 равны 0 и 2 соответственно + graph.addEdge(i: 0, j: 2) + print("\nГраф после добавления ребра 1-2") + graph.print() + + /* Удаление ребра */ + // Индексы вершин 1 и 3 равны 0 и 1 соответственно + graph.removeEdge(i: 0, j: 1) + print("\nГраф после удаления ребра 1-3") + graph.print() + + /* Добавление вершины */ + graph.addVertex(val: 6) + print("\nГраф после добавления вершины 6") + graph.print() + + /* Удаление вершины */ + // Индекс вершины 3 равен 1 + graph.removeVertex(index: 1) + print("\nГраф после удаления вершины 3") + graph.print() + } +} diff --git a/ru/codes/swift/chapter_graph/graph_bfs.swift b/ru/codes/swift/chapter_graph/graph_bfs.swift new file mode 100644 index 000000000..e51288bd5 --- /dev/null +++ b/ru/codes/swift/chapter_graph/graph_bfs.swift @@ -0,0 +1,56 @@ +/** + * File: graph_bfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // Последовательность обхода вершин + var res: [Vertex] = [] + // Хеш-множество для хранения уже посещенных вершин + var visited: Set = [startVet] + // Очередь используется для реализации BFS + var que: [Vertex] = [startVet] + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while !que.isEmpty { + let vet = que.removeFirst() // Извлечь головную вершину из очереди + res.append(vet) // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // Пропустить уже посещенную вершину + } + que.append(adjVet) // Помещать в очередь только непосещенные вершины + visited.insert(adjVet) // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res +} + +@main +enum GraphBFS { + /* Driver Code */ + static func main() { + /* Инициализация неориентированного графа */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + let edges = [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], + [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], + [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], + ] + let graph = GraphAdjList(edges: edges) + print("\nГраф после инициализации") + graph.print() + + /* Обход в ширину */ + let res = graphBFS(graph: graph, startVet: v[0]) + print("\nПоследовательность вершин при обходе в ширину (BFS)") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/ru/codes/swift/chapter_graph/graph_dfs.swift b/ru/codes/swift/chapter_graph/graph_dfs.swift new file mode 100644 index 000000000..18e859bb1 --- /dev/null +++ b/ru/codes/swift/chapter_graph/graph_dfs.swift @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* Вспомогательная функция обхода в глубину */ +func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // Отметить посещенную вершину + visited.insert(vet) // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // Пропустить уже посещенную вершину + } + // Рекурсивно обходить смежные вершины + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // Последовательность обхода вершин + var res: [Vertex] = [] + // Хеш-множество для хранения уже посещенных вершин + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res +} + +@main +enum GraphDFS { + /* Driver Code */ + static func main() { + /* Инициализация неориентированного графа */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) + let edges = [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ] + let graph = GraphAdjList(edges: edges) + print("\nГраф после инициализации") + graph.print() + + /* Обход в глубину */ + let res = graphDFS(graph: graph, startVet: v[0]) + print("\nПоследовательность вершин при обходе в глубину (DFS)") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/ru/codes/swift/chapter_greedy/coin_change_greedy.swift b/ru/codes/swift/chapter_greedy/coin_change_greedy.swift new file mode 100644 index 000000000..29f082f0d --- /dev/null +++ b/ru/codes/swift/chapter_greedy/coin_change_greedy.swift @@ -0,0 +1,54 @@ +/** + * File: coin_change_greedy.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Размен монет: жадный алгоритм */ +func coinChangeGreedy(coins: [Int], amt: Int) -> Int { + // Предположить, что список coins упорядочен + var i = coins.count - 1 + var count = 0 + var amt = amt + // Циклически выполнять жадный выбор, пока не останется суммы + while amt > 0 { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while i > 0 && coins[i] > amt { + i -= 1 + } + // Выбрать coins[i] + amt -= coins[i] + count += 1 + } + // Если допустимое решение не найдено, вернуть -1 + return amt == 0 ? count : -1 +} + +@main +enum CoinChangeGreedy { + /* Driver Code */ + static func main() { + // Жадный подход: гарантирует нахождение глобально оптимального решения + var coins = [1, 5, 10, 20, 50, 100] + var amt = 186 + var res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("Минимальное число монет для набора суммы \(amt) = \(res)") + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 20, 50] + amt = 60 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("Минимальное число монет для набора суммы \(amt) = \(res)") + print("На самом деле минимум равен 3: 20 + 20 + 20") + + // Жадный подход: не гарантирует нахождение глобально оптимального решения + coins = [1, 49, 50] + amt = 98 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("Минимальное число монет для набора суммы \(amt) = \(res)") + print("На самом деле минимум равен 2: 49 + 49") + } +} diff --git a/ru/codes/swift/chapter_greedy/fractional_knapsack.swift b/ru/codes/swift/chapter_greedy/fractional_knapsack.swift new file mode 100644 index 000000000..4461f4ac9 --- /dev/null +++ b/ru/codes/swift/chapter_greedy/fractional_knapsack.swift @@ -0,0 +1,57 @@ +/** + * File: fractional_knapsack.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Предмет */ +class Item { + var w: Int // Вес предмета + var v: Int // Стоимость предмета + + init(w: Int, v: Int) { + self.w = w + self.v = v + } +} + +/* Дробный рюкзак: жадный алгоритм */ +func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { + // Создать список предметов с двумя свойствами: вес и стоимость + var items = zip(wgt, val).map { Item(w: $0, v: $1) } + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } + // Циклический жадный выбор + var res = 0.0 + var cap = cap + for item in items { + if item.w <= cap { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += Double(item.v) + cap -= item.w + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += Double(item.v) / Double(item.w) * Double(cap) + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break + } + } + return res +} + +@main +enum FractionalKnapsack { + /* Driver Code */ + static func main() { + // Вес предмета + let wgt = [10, 20, 30, 40, 50] + // Стоимость предмета + let val = [50, 120, 150, 210, 240] + // Вместимость рюкзака + let cap = 50 + + // Жадный алгоритм + let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) + print("Максимальная стоимость предметов без превышения вместимости рюкзака = \(res)") + } +} diff --git a/ru/codes/swift/chapter_greedy/max_capacity.swift b/ru/codes/swift/chapter_greedy/max_capacity.swift new file mode 100644 index 000000000..8435bbd11 --- /dev/null +++ b/ru/codes/swift/chapter_greedy/max_capacity.swift @@ -0,0 +1,38 @@ +/** + * File: max_capacity.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Максимальная вместимость: жадный алгоритм */ +func maxCapacity(ht: [Int]) -> Int { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + var i = ht.startIndex, j = ht.endIndex - 1 + // Начальная максимальная вместимость равна 0 + var res = 0 + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while i < j { + // Обновить максимальную вместимость + let cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // Сдвигать внутрь более короткую сторону + if ht[i] < ht[j] { + i += 1 + } else { + j -= 1 + } + } + return res +} + +@main +enum MaxCapacity { + /* Driver Code */ + static func main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4] + + // Жадный алгоритм + let res = maxCapacity(ht: ht) + print("Максимальная вместимость = \(res)") + } +} diff --git a/ru/codes/swift/chapter_greedy/max_product_cutting.swift b/ru/codes/swift/chapter_greedy/max_product_cutting.swift new file mode 100644 index 000000000..8eba99c48 --- /dev/null +++ b/ru/codes/swift/chapter_greedy/max_product_cutting.swift @@ -0,0 +1,43 @@ +/** + * File: max_product_cutting.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import Foundation + +func pow(_ x: Int, _ y: Int) -> Int { + Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) +} + +/* Максимальное произведение разрезания: жадный алгоритм */ +func maxProductCutting(n: Int) -> Int { + // Когда n <= 3, обязательно нужно выделить одну 1 + if n <= 3 { + return 1 * (n - 1) + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + let a = n / 3 + let b = n % 3 + if b == 1 { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return pow(3, a - 1) * 2 * 2 + } + if b == 2 { + // Если остаток равен 2, ничего не делать + return pow(3, a) * 2 + } + // Если остаток равен 0, ничего не делать + return pow(3, a) +} + +@main +enum MaxProductCutting { + static func main() { + let n = 58 + + // Жадный алгоритм + let res = maxProductCutting(n: n) + print("Максимальное произведение после разрезания = \(res)") + } +} diff --git a/ru/codes/swift/chapter_hashing/array_hash_map.swift b/ru/codes/swift/chapter_hashing/array_hash_map.swift new file mode 100644 index 000000000..44fcd3745 --- /dev/null +++ b/ru/codes/swift/chapter_hashing/array_hash_map.swift @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + private var buckets: [Pair?] + + init() { + // Инициализировать массив, содержащий 100 корзин + buckets = Array(repeating: nil, count: 100) + } + + /* Хеш-функция */ + private func hashFunc(key: Int) -> Int { + let index = key % 100 + return index + } + + /* Операция поиска */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let pair = buckets[index] + return pair?.val + } + + /* Операция добавления */ + func put(key: Int, val: String) { + let pair = Pair(key: key, val: val) + let index = hashFunc(key: key) + buckets[index] = pair + } + + /* Операция удаления */ + func remove(key: Int) { + let index = hashFunc(key: key) + // Присвоить nil, что означает удаление + buckets[index] = nil + } + + /* Получить все пары ключ-значение */ + func pairSet() -> [Pair] { + buckets.compactMap { $0 } + } + + /* Получить все ключи */ + func keySet() -> [Int] { + buckets.compactMap { $0?.key } + } + + /* Получить все значения */ + func valueSet() -> [String] { + buckets.compactMap { $0?.val } + } + + /* Вывести хеш-таблицу */ + func print() { + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") + } + } +} + +@main +enum _ArrayHashMap { + /* Driver Code */ + static func main() { + /* Инициализация хеш-таблицы */ + let map = ArrayHashMap() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(key: 12836, val: "Сяо Ха") + map.put(key: 15937, val: "Сяо Ло") + map.put(key: 16750, val: "Сяо Суань") + map.put(key: 13276, val: "Сяо Фа") + map.put(key: 10583, val: "Сяо Я") + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + let name = map.get(key: 15937)! + print("\nДля номера 15937 найдено имя \(name)") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(key: 10583) + print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Обход хеш-таблицы */ + print("\nОтдельный обход пар ключ-значение") + for pair in map.pairSet() { + print("\(pair.key) -> \(pair.val)") + } + print("\nОтдельный обход ключей") + for key in map.keySet() { + print(key) + } + print("\nОтдельный обход значений") + for val in map.valueSet() { + print(val) + } + } +} diff --git a/ru/codes/swift/chapter_hashing/built_in_hash.swift b/ru/codes/swift/chapter_hashing/built_in_hash.swift new file mode 100644 index 000000000..578ec127a --- /dev/null +++ b/ru/codes/swift/chapter_hashing/built_in_hash.swift @@ -0,0 +1,37 @@ +/** + * File: built_in_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BuiltInHash { + /* Driver Code */ + static func main() { + let num = 3 + let hashNum = num.hashValue + print("Хеш-значение целого числа \(num) = \(hashNum)") + + let bol = true + let hashBol = bol.hashValue + print("Хеш-значение булева значения \(bol) = \(hashBol)") + + let dec = 3.14159 + let hashDec = dec.hashValue + print("Хеш-значение десятичного числа \(dec) = \(hashDec)") + + let str = "Hello Algo" + let hashStr = str.hashValue + print("Хеш-значение строки \(str) = \(hashStr)") + + let arr = [AnyHashable(12836), AnyHashable("Сяо Ха")] + let hashTup = arr.hashValue + print("Хеш-значение массива \(arr) = \(hashTup)") + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + print("Хеш-значение объекта узла \(obj) = \(hashObj)") + } +} diff --git a/ru/codes/swift/chapter_hashing/hash_map.swift b/ru/codes/swift/chapter_hashing/hash_map.swift new file mode 100644 index 000000000..c882616ef --- /dev/null +++ b/ru/codes/swift/chapter_hashing/hash_map.swift @@ -0,0 +1,51 @@ +/** + * File: hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum HashMap { + /* Driver Code */ + static func main() { + /* Инициализация хеш-таблицы */ + var map: [Int: String] = [:] + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map[12836] = "Сяо Ха" + map[15937] = "Сяо Ло" + map[16750] = "Сяо Суань" + map[13276] = "Сяо Фа" + map[10583] = "Сяо Я" + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + PrintUtil.printHashMap(map: map) + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + let name = map[15937]! + print("\nДля номера 15937 найдено имя \(name)") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.removeValue(forKey: 10583) + print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение") + PrintUtil.printHashMap(map: map) + + /* Обход хеш-таблицы */ + print("\nОтдельный обход пар ключ-значение") + for (key, value) in map { + print("\(key) -> \(value)") + } + print("\nОтдельный обход ключей") + for key in map.keys { + print(key) + } + print("\nОтдельный обход значений") + for value in map.values { + print(value) + } + } +} diff --git a/ru/codes/swift/chapter_hashing/hash_map_chaining.swift b/ru/codes/swift/chapter_hashing/hash_map_chaining.swift new file mode 100644 index 000000000..b5f8fe810 --- /dev/null +++ b/ru/codes/swift/chapter_hashing/hash_map_chaining.swift @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + var size: Int // Число пар ключ-значение + var capacity: Int // Вместимость хеш-таблицы + var loadThres: Double // Порог коэффициента загрузки для запуска расширения + var extendRatio: Int // Коэффициент расширения + var buckets: [[Pair]] // Массив корзин + + /* Конструктор */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: [], count: capacity) + } + + /* Хеш-функция */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* Коэффициент загрузки */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* Операция поиска */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let bucket = buckets[index] + // Обойти корзину; если найден key, вернуть соответствующее val + for pair in bucket { + if pair.key == key { + return pair.val + } + } + // Если key не найден, вернуть nil + return nil + } + + /* Операция добавления */ + func put(key: Int, val: String) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + let bucket = buckets[index] + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for pair in bucket { + if pair.key == key { + pair.val = val + return + } + } + // Если такого key нет, добавить пару ключ-значение в конец + let pair = Pair(key: key, val: val) + buckets[index].append(pair) + size += 1 + } + + /* Операция удаления */ + func remove(key: Int) { + let index = hashFunc(key: key) + let bucket = buckets[index] + // Обойти корзину и удалить из нее пару ключ-значение + for (pairIndex, pair) in bucket.enumerated() { + if pair.key == key { + buckets[index].remove(at: pairIndex) + size -= 1 + break + } + } + } + + /* Расширить хеш-таблицу */ + func extend() { + // Временно сохранить исходную хеш-таблицу + let bucketsTmp = buckets + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio + buckets = Array(repeating: [], count: capacity) + size = 0 + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for bucket in bucketsTmp { + for pair in bucket { + put(key: pair.key, val: pair.val) + } + } + } + + /* Вывести хеш-таблицу */ + func print() { + for bucket in buckets { + let res = bucket.map { "\($0.key) -> \($0.val)" } + Swift.print(res) + } + } +} + +@main +enum _HashMapChaining { + /* Driver Code */ + static func main() { + /* Инициализация хеш-таблицы */ + let map = HashMapChaining() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(key: 12836, val: "Сяо Ха") + map.put(key: 15937, val: "Сяо Ло") + map.put(key: 16750, val: "Сяо Суань") + map.put(key: 13276, val: "Сяо Фа") + map.put(key: 10583, val: "Сяо Я") + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + let name = map.get(key: 13276) + print("\nДля номера 13276 найдено имя \(name!)") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(key: 12836) + print("\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + } +} diff --git a/ru/codes/swift/chapter_hashing/hash_map_open_addressing.swift b/ru/codes/swift/chapter_hashing/hash_map_open_addressing.swift new file mode 100644 index 000000000..4d52a59fe --- /dev/null +++ b/ru/codes/swift/chapter_hashing/hash_map_open_addressing.swift @@ -0,0 +1,164 @@ +/** + * File: hash_map_open_addressing.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + var size: Int // Число пар ключ-значение + var capacity: Int // Вместимость хеш-таблицы + var loadThres: Double // Порог коэффициента загрузки для запуска расширения + var extendRatio: Int // Коэффициент расширения + var buckets: [Pair?] // Массив корзин + var TOMBSTONE: Pair // Удалить метку + + /* Конструктор */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: nil, count: capacity) + TOMBSTONE = Pair(key: -1, val: "-1") + } + + /* Хеш-функция */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* Коэффициент загрузки */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* Найти индекс корзины, соответствующий key */ + func findBucket(key: Int) -> Int { + var index = hashFunc(key: key) + var firstTombstone = -1 + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while buckets[index] != nil { + // Если встретился key, вернуть соответствующий индекс корзины + if buckets[index]!.key == key { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if firstTombstone != -1 { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // Вернуть индекс корзины после перемещения + } + return index // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if firstTombstone == -1 && buckets[index] == TOMBSTONE { + firstTombstone = index + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % capacity + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone == -1 ? index : firstTombstone + } + + /* Операция поиска */ + func get(key: Int) -> String? { + // Найти индекс корзины, соответствующий key + let index = findBucket(key: key) + // Если пара ключ-значение найдена, вернуть соответствующее val + if buckets[index] != nil, buckets[index] != TOMBSTONE { + return buckets[index]!.val + } + // Если пары ключ-значение не существует, вернуть null + return nil + } + + /* Операция добавления */ + func put(key: Int, val: String) { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if loadFactor() > loadThres { + extend() + } + // Найти индекс корзины, соответствующий key + let index = findBucket(key: key) + // Если пара ключ-значение найдена, перезаписать val и вернуть + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index]!.val = val + return + } + // Если пары ключ-значение нет, добавить ее + buckets[index] = Pair(key: key, val: val) + size += 1 + } + + /* Операция удаления */ + func remove(key: Int) { + // Найти индекс корзины, соответствующий key + let index = findBucket(key: key) + // Если пара ключ-значение найдена, заменить ее меткой удаления + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index] = TOMBSTONE + size -= 1 + } + } + + /* Расширить хеш-таблицу */ + func extend() { + // Временно сохранить исходную хеш-таблицу + let bucketsTmp = buckets + // Инициализация новой хеш-таблицы после расширения + capacity *= extendRatio + buckets = Array(repeating: nil, count: capacity) + size = 0 + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for pair in bucketsTmp { + if let pair, pair != TOMBSTONE { + put(key: pair.key, val: pair.val) + } + } + } + + /* Вывести хеш-таблицу */ + func print() { + for pair in buckets { + if pair == nil { + Swift.print("null") + } else if pair == TOMBSTONE { + Swift.print("TOMBSTONE") + } else { + Swift.print("\(pair!.key) -> \(pair!.val)") + } + } + } +} + +@main +enum _HashMapOpenAddressing { + /* Driver Code */ + static func main() { + /* Инициализация хеш-таблицы */ + let map = HashMapOpenAddressing() + + /* Операция добавления */ + // Добавить пару (key, value) в хеш-таблицу + map.put(key: 12836, val: "Сяо Ха") + map.put(key: 15937, val: "Сяо Ло") + map.put(key: 16750, val: "Сяо Суань") + map.put(key: 13276, val: "Сяо Фа") + map.put(key: 10583, val: "Сяо Я") + print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + + /* Операция поиска */ + // Ввести в хеш-таблицу ключ key и получить значение value + let name = map.get(key: 13276) + print("\nДля номера 13276 найдено имя \(name!)") + + /* Операция удаления */ + // Удалить пару (key, value) из хеш-таблицы + map.remove(key: 16750) + print("\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение") + map.print() + } +} diff --git a/ru/codes/swift/chapter_hashing/simple_hash.swift b/ru/codes/swift/chapter_hashing/simple_hash.swift new file mode 100644 index 000000000..0005e504c --- /dev/null +++ b/ru/codes/swift/chapter_hashing/simple_hash.swift @@ -0,0 +1,73 @@ +/** + * File: simple_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Аддитивное хеширование */ +func addHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* Мультипликативное хеширование */ +func mulHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (31 * hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* XOR-хеширование */ +func xorHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash ^= Int(scalar.value) + } + } + return hash & MODULUS +} + +/* Хеширование с циклическим сдвигом */ +func rotHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS + } + } + return hash +} + +@main +enum SimpleHash { + /* Driver Code */ + static func main() { + let key = "Hello Algo" + + var hash = addHash(key: key) + print("Хеш-сумма сложением = \(hash)") + + hash = mulHash(key: key) + print("Хеш-сумма умножением = \(hash)") + + hash = xorHash(key: key) + print("Хеш-сумма XOR = \(hash)") + + hash = rotHash(key: key) + print("Хеш-сумма с циклическим сдвигом = \(hash)") + } +} diff --git a/ru/codes/swift/chapter_heap/heap.swift b/ru/codes/swift/chapter_heap/heap.swift new file mode 100644 index 000000000..dc24f2e6c --- /dev/null +++ b/ru/codes/swift/chapter_heap/heap.swift @@ -0,0 +1,62 @@ +/** + * File: heap.swift + * Created Time: 2024-03-17 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +func testPush(heap: inout Heap, val: Int) { + heap.insert(val) + print("\nПосле добавления элемента \(val) в кучу\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +func testPop(heap: inout Heap) { + let val = heap.removeMax() + print("\nПосле извлечения элемента вершины кучи \(val)\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +@main +enum _Heap { + /* Driver Code */ + static func main() { + /* Инициализация кучи */ + // Тип Heap в Swift одновременно поддерживает максимальную и минимальную кучу + var heap = Heap() + + /* Добавление элемента в кучу */ + testPush(heap: &heap, val: 1) + testPush(heap: &heap, val: 3) + testPush(heap: &heap, val: 2) + testPush(heap: &heap, val: 5) + testPush(heap: &heap, val: 4) + + /* Получение элемента с вершины кучи */ + let peek = heap.max() + print("\nЭлемент на вершине кучи = \(peek!)\n") + + /* Извлечение элемента с вершины кучи */ + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + + /* Получение размера кучи */ + let size = heap.count + print("\nКоличество элементов в куче = \(size)\n") + + /* Проверка, пуста ли куча */ + let isEmpty = heap.isEmpty + print("\nПуста ли куча: \(isEmpty)\n") + + /* Построить кучу по входному списку */ + // Временная сложность равна O(n), а не O(nlogn) + let heap2 = Heap([1, 3, 2, 5, 4]) + print("\nПосле построения кучи из входного списка") + PrintUtil.printHeap(queue: heap2.unordered) + } +} diff --git a/ru/codes/swift/chapter_heap/my_heap.swift b/ru/codes/swift/chapter_heap/my_heap.swift new file mode 100644 index 000000000..8a0fa0b78 --- /dev/null +++ b/ru/codes/swift/chapter_heap/my_heap.swift @@ -0,0 +1,163 @@ +/** + * File: my_heap.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Максимальная куча */ +class MaxHeap { + private var maxHeap: [Int] + + /* Конструктор, строящий кучу по входному списку */ + init(nums: [Int]) { + // Добавить элементы списка в кучу без изменений + maxHeap = nums + // Выполнить heapify для всех узлов, кроме листовых + for i in (0 ... parent(i: size() - 1)).reversed() { + siftDown(i: i) + } + } + + /* Получить индекс левого дочернего узла */ + private func left(i: Int) -> Int { + 2 * i + 1 + } + + /* Получить индекс правого дочернего узла */ + private func right(i: Int) -> Int { + 2 * i + 2 + } + + /* Получить индекс родительского узла */ + private func parent(i: Int) -> Int { + (i - 1) / 2 // Округление вниз при делении + } + + /* Поменять элементы местами */ + private func swap(i: Int, j: Int) { + maxHeap.swapAt(i, j) + } + + /* Получение размера кучи */ + func size() -> Int { + maxHeap.count + } + + /* Проверка, пуста ли куча */ + func isEmpty() -> Bool { + size() == 0 + } + + /* Доступ к элементу на вершине кучи */ + func peek() -> Int { + maxHeap[0] + } + + /* Добавление элемента в кучу */ + func push(val: Int) { + // Добавление узла + maxHeap.append(val) + // Просеивание снизу вверх + siftUp(i: size() - 1) + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + private func siftUp(i: Int) { + var i = i + while true { + // Получение родительского узла для узла i + let p = parent(i: i) + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if p < 0 || maxHeap[i] <= maxHeap[p] { + break + } + // Поменять два узла местами + swap(i: i, j: p) + // Циклическое просеивание вверх + i = p + } + } + + /* Извлечение элемента из кучи */ + func pop() -> Int { + // Обработка пустого случая + if isEmpty() { + fatalError("куча пуста") + } + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + swap(i: 0, j: size() - 1) + // Удаление узла + let val = maxHeap.remove(at: size() - 1) + // Просеивание сверху вниз + siftDown(i: 0) + // Вернуть элемент с вершины кучи + return val + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + private func siftDown(i: Int) { + var i = i + while true { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + let l = left(i: i) + let r = right(i: i) + var ma = i + if l < size(), maxHeap[l] > maxHeap[ma] { + ma = l + } + if r < size(), maxHeap[r] > maxHeap[ma] { + ma = r + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i { + break + } + // Поменять два узла местами + swap(i: i, j: ma) + // Циклическое просеивание вниз + i = ma + } + } + + /* Вывести кучу (двоичное дерево) */ + func print() { + let queue = maxHeap + PrintUtil.printHeap(queue: queue) + } +} + +@main +enum MyHeap { + /* Driver Code */ + static func main() { + /* Инициализация максимальной кучи */ + let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\nПосле построения кучи из входного списка") + maxHeap.print() + + /* Получение элемента с вершины кучи */ + var peek = maxHeap.peek() + print("\nЭлемент на вершине кучи = \(peek)") + + /* Добавление элемента в кучу */ + let val = 7 + maxHeap.push(val: val) + print("\nПосле добавления элемента \(val) в кучу") + maxHeap.print() + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop() + print("\nПосле извлечения элемента вершины кучи \(peek)") + maxHeap.print() + + /* Получение размера кучи */ + let size = maxHeap.size() + print("\nКоличество элементов в куче = \(size)") + + /* Проверка, пуста ли куча */ + let isEmpty = maxHeap.isEmpty() + print("\nПуста ли куча: \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_heap/top_k.swift b/ru/codes/swift/chapter_heap/top_k.swift new file mode 100644 index 000000000..f2699bd6c --- /dev/null +++ b/ru/codes/swift/chapter_heap/top_k.swift @@ -0,0 +1,36 @@ +/** + * File: top_k.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +/* Найти k наибольших элементов массива с помощью кучи */ +func topKHeap(nums: [Int], k: Int) -> [Int] { + // Инициализировать минимальную кучу и построить ее по первым k элементам + var heap = Heap(nums.prefix(k)) + // Начиная с элемента k+1, поддерживать длину кучи равной k + for i in nums.indices.dropFirst(k) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if nums[i] > heap.min()! { + _ = heap.removeMin() + heap.insert(nums[i]) + } + } + return heap.unordered +} + +@main +enum TopK { + /* Driver Code */ + static func main() { + let nums = [1, 7, 6, 3, 2] + let k = 3 + + let res = topKHeap(nums: nums, k: k) + print("Наибольшие \(k) элементов") + PrintUtil.printHeap(queue: res) + } +} diff --git a/ru/codes/swift/chapter_searching/binary_search.swift b/ru/codes/swift/chapter_searching/binary_search.swift new file mode 100644 index 000000000..97317d4d1 --- /dev/null +++ b/ru/codes/swift/chapter_searching/binary_search.swift @@ -0,0 +1,62 @@ +/** + * File: binary_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +func binarySearch(nums: [Int], target: Int) -> Int { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + var i = nums.startIndex + var j = nums.endIndex - 1 + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while i <= j { + let m = i + (j - i) / 2 // Вычислить индекс середины m + if nums[m] < target { // Это означает, что target находится в интервале [m+1, j] + i = m + 1 + } else if nums[m] > target { // Это означает, что target находится в интервале [i, m-1] + j = m - 1 + } else { // Целевой элемент найден, вернуть его индекс + return m + } + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +func binarySearchLCRO(nums: [Int], target: Int) -> Int { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + var i = nums.startIndex + var j = nums.endIndex + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while i < j { + let m = i + (j - i) / 2 // Вычислить индекс середины m + if nums[m] < target { // Это означает, что target находится в интервале [m+1, j) + i = m + 1 + } else if nums[m] > target { // Это означает, что target находится в интервале [i, m) + j = m + } else { // Целевой элемент найден, вернуть его индекс + return m + } + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +@main +enum BinarySearch { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + /* Бинарный поиск (двусторонне замкнутый интервал) */ + var index = binarySearch(nums: nums, target: target) + print("Индекс целевого элемента 6 = \(index)") + + /* Бинарный поиск (лево замкнутый, право открытый интервал) */ + index = binarySearchLCRO(nums: nums, target: target) + print("Индекс целевого элемента 6 = \(index)") + } +} diff --git a/ru/codes/swift/chapter_searching/binary_search_edge.swift b/ru/codes/swift/chapter_searching/binary_search_edge.swift new file mode 100644 index 000000000..9948df47a --- /dev/null +++ b/ru/codes/swift/chapter_searching/binary_search_edge.swift @@ -0,0 +1,51 @@ +/** + * File: binary_search_edge.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import binary_search_insertion_target + +/* Бинарный поиск самого левого target */ +func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // Эквивалентно поиску точки вставки target + let i = binarySearchInsertion(nums: nums, target: target) + // target не найден, вернуть -1 + if i == nums.endIndex || nums[i] != target { + return -1 + } + // Найти target и вернуть индекс i + return i +} + +/* Бинарный поиск самого правого target */ +func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // Преобразовать задачу в поиск самого левого target + 1 + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j указывает на самый правый target, а i — на первый элемент больше target + let j = i - 1 + // target не найден, вернуть -1 + if j == -1 || nums[j] != target { + return -1 + } + // Найти target и вернуть индекс j + return j +} + +@main +enum BinarySearchEdge { + /* Driver Code */ + static func main() { + // Массив с повторяющимися элементами + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\nМассив nums = \(nums)") + + // Бинарный поиск левой и правой границы + for target in [6, 7] { + var index = binarySearchLeftEdge(nums: nums, target: target) + print("Индекс самого левого элемента \(target) равен \(index)") + index = binarySearchRightEdge(nums: nums, target: target) + print("Индекс самого правого элемента \(target) равен \(index)") + } + } +} diff --git a/ru/codes/swift/chapter_searching/binary_search_insertion.swift b/ru/codes/swift/chapter_searching/binary_search_insertion.swift new file mode 100644 index 000000000..57cd71c03 --- /dev/null +++ b/ru/codes/swift/chapter_searching/binary_search_insertion.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // Инициализировать двусторонне замкнутый интервал [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // Вычислить индекс середины m + if nums[m] < target { + i = m + 1 // target находится в интервале [m+1, j] + } else if nums[m] > target { + j = m - 1 // target находится в интервале [i, m-1] + } else { + return m // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // Инициализировать двусторонне замкнутый интервал [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // Вычислить индекс середины m + if nums[m] < target { + i = m + 1 // target находится в интервале [m+1, j] + } else if nums[m] > target { + j = m - 1 // target находится в интервале [i, m-1] + } else { + j = m - 1 // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // Массив без повторяющихся элементов + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\nМассив nums = \(nums)") + // Бинарный поиск точки вставки + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("Индекс позиции вставки элемента \(target) равен \(index)") + } + + // Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\nМассив nums = \(nums)") + // Бинарный поиск точки вставки + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("Индекс позиции вставки элемента \(target) равен \(index)") + } + } +} + +#endif diff --git a/ru/codes/swift/chapter_searching/binary_search_insertion_target.swift b/ru/codes/swift/chapter_searching/binary_search_insertion_target.swift new file mode 100644 index 000000000..57cd71c03 --- /dev/null +++ b/ru/codes/swift/chapter_searching/binary_search_insertion_target.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // Инициализировать двусторонне замкнутый интервал [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // Вычислить индекс середины m + if nums[m] < target { + i = m + 1 // target находится в интервале [m+1, j] + } else if nums[m] > target { + j = m - 1 // target находится в интервале [i, m-1] + } else { + return m // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // Инициализировать двусторонне замкнутый интервал [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // Вычислить индекс середины m + if nums[m] < target { + i = m + 1 // target находится в интервале [m+1, j] + } else if nums[m] > target { + j = m - 1 // target находится в интервале [i, m-1] + } else { + j = m - 1 // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // Массив без повторяющихся элементов + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\nМассив nums = \(nums)") + // Бинарный поиск точки вставки + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("Индекс позиции вставки элемента \(target) равен \(index)") + } + + // Массив с повторяющимися элементами + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\nМассив nums = \(nums)") + // Бинарный поиск точки вставки + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("Индекс позиции вставки элемента \(target) равен \(index)") + } + } +} + +#endif diff --git a/ru/codes/swift/chapter_searching/hashing_search.swift b/ru/codes/swift/chapter_searching/hashing_search.swift new file mode 100644 index 000000000..dd56f471a --- /dev/null +++ b/ru/codes/swift/chapter_searching/hashing_search.swift @@ -0,0 +1,50 @@ +/** + * File: hashing_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Хеш-поиск (массив) */ +func hashingSearchArray(map: [Int: Int], target: Int) -> Int { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + return map[target, default: -1] +} + +/* Хеш-поиск (связный список) */ +func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + return map[target] +} + +@main +enum HashingSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* Хеш-поиск (массив) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + // Инициализация хеш-таблицы + var map: [Int: Int] = [:] + for i in nums.indices { + map[nums[i]] = i // key: элемент, value: индекс + } + let index = hashingSearchArray(map: map, target: target) + print("Индекс целевого элемента 3 = \(index)") + + /* Хеш-поиск (связный список) */ + var head = ListNode.arrToLinkedList(arr: nums) + // Инициализация хеш-таблицы + var map1: [Int: ListNode] = [:] + while head != nil { + map1[head!.val] = head! // key: значение узла, value: узел + head = head?.next + } + let node = hashingSearchLinkedList(map: map1, target: target) + print("Объект узла со значением 3 = \(node!)") + } +} diff --git a/ru/codes/swift/chapter_searching/linear_search.swift b/ru/codes/swift/chapter_searching/linear_search.swift new file mode 100644 index 000000000..00e23991a --- /dev/null +++ b/ru/codes/swift/chapter_searching/linear_search.swift @@ -0,0 +1,53 @@ +/** + * File: linear_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Линейный поиск (массив) */ +func linearSearchArray(nums: [Int], target: Int) -> Int { + // Обход массива + for i in nums.indices { + // Целевой элемент найден, вернуть его индекс + if nums[i] == target { + return i + } + } + // Целевой элемент не найден, вернуть -1 + return -1 +} + +/* Линейный поиск (связный список) */ +func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { + var head = head + // Обойти связный список + while head != nil { + // Найти целевой узел и вернуть его + if head?.val == target { + return head + } + head = head?.next + } + // Целевой узел не найден, вернуть null + return nil +} + +@main +enum LinearSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* Выполнить линейный поиск в массиве */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + let index = linearSearchArray(nums: nums, target: target) + print("Индекс целевого элемента 3 = \(index)") + + /* Выполнить линейный поиск в связном списке */ + let head = ListNode.arrToLinkedList(arr: nums) + let node = linearSearchLinkedList(head: head, target: target) + print("Объект узла со значением 3 = \(node!)") + } +} diff --git a/ru/codes/swift/chapter_searching/two_sum.swift b/ru/codes/swift/chapter_searching/two_sum.swift new file mode 100644 index 000000000..c496aeab5 --- /dev/null +++ b/ru/codes/swift/chapter_searching/two_sum.swift @@ -0,0 +1,49 @@ +/** + * File: two_sum.swift + * Created Time: 2023-01-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Метод 1: полный перебор */ +func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { + // Два вложенных цикла, временная сложность O(n^2) + for i in nums.indices.dropLast() { + for j in nums.indices.dropFirst(i + 1) { + if nums[i] + nums[j] == target { + return [i, j] + } + } + } + return [0] +} + +/* Метод 2: вспомогательная хеш-таблица */ +func twoSumHashTable(nums: [Int], target: Int) -> [Int] { + // Вспомогательная хеш-таблица, пространственная сложность O(n) + var dic: [Int: Int] = [:] + // Один цикл, временная сложность O(n) + for i in nums.indices { + if let j = dic[target - nums[i]] { + return [j, i] + } + dic[nums[i]] = i + } + return [0] +} + +@main +enum LeetcodeTwoSum { + /* Driver Code */ + static func main() { + // ======= Test Case ======= + let nums = [2, 7, 11, 15] + let target = 13 + // ====== Основной код ====== + // Метод 1 + var res = twoSumBruteForce(nums: nums, target: target) + print("Результат метода 1 res = \(res)") + // Метод 2 + res = twoSumHashTable(nums: nums, target: target) + print("Результат метода 2 res = \(res)") + } +} diff --git a/ru/codes/swift/chapter_sorting/bubble_sort.swift b/ru/codes/swift/chapter_sorting/bubble_sort.swift new file mode 100644 index 000000000..40f9d60d2 --- /dev/null +++ b/ru/codes/swift/chapter_sorting/bubble_sort.swift @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Пузырьковая сортировка */ +func bubbleSort(nums: inout [Int]) { + // Внешний цикл: неотсортированный диапазон [0, i] + for i in nums.indices.dropFirst().reversed() { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // Поменять местами nums[j] и nums[j + 1] + nums.swapAt(j, j + 1) + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +func bubbleSortWithFlag(nums: inout [Int]) { + // Внешний цикл: неотсортированный диапазон [0, i] + for i in nums.indices.dropFirst().reversed() { + var flag = false // Инициализировать флаг + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // Поменять местами nums[j] и nums[j + 1] + nums.swapAt(j, j + 1) + flag = true // Записать обмен элементов + } + } + if !flag { // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + break + } + } +} + +@main +enum BubbleSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + bubbleSort(nums: &nums) + print("После пузырьковой сортировки nums = \(nums)") + + var nums1 = [4, 1, 3, 1, 5, 2] + bubbleSortWithFlag(nums: &nums1) + print("После пузырьковой сортировки nums1 = \(nums1)") + } +} diff --git a/ru/codes/swift/chapter_sorting/bucket_sort.swift b/ru/codes/swift/chapter_sorting/bucket_sort.swift new file mode 100644 index 000000000..e50c925ed --- /dev/null +++ b/ru/codes/swift/chapter_sorting/bucket_sort.swift @@ -0,0 +1,43 @@ +/** + * File: bucket_sort.swift + * Created Time: 2023-03-27 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Сортировка корзинами */ +func bucketSort(nums: inout [Double]) { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + let k = nums.count / 2 + var buckets = (0 ..< k).map { _ in [Double]() } + // 1. Распределить элементы массива по корзинам + for num in nums { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + let i = Int(num * Double(k)) + // Добавить num в корзину i + buckets[i].append(num) + } + // 2. Выполнить сортировку внутри каждой корзины + for i in buckets.indices { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + buckets[i].sort() + } + // 3. Обойти корзины и объединить результаты + var i = nums.startIndex + for bucket in buckets { + for num in bucket { + nums[i] = num + i += 1 + } + } +} + +@main +enum BucketSort { + /* Driver Code */ + static func main() { + // Пусть входные данные — числа с плавающей точкой из диапазона [0, 1) + var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucketSort(nums: &nums) + print("После сортировки корзинами nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_sorting/counting_sort.swift b/ru/codes/swift/chapter_sorting/counting_sort.swift new file mode 100644 index 000000000..056d2ff4b --- /dev/null +++ b/ru/codes/swift/chapter_sorting/counting_sort.swift @@ -0,0 +1,70 @@ +/** + * File: counting_sort.swift + * Created Time: 2023-03-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +func countingSortNaive(nums: inout [Int]) { + // 1. Найти максимальный элемент массива m + let m = nums.max()! + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. Обойти counter и заполнить исходный массив nums элементами + var i = 0 + for num in 0 ..< m + 1 { + for _ in 0 ..< counter[num] { + nums[i] = num + i += 1 + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +func countingSort(nums: inout [Int]) { + // 1. Найти максимальный элемент массива m + let m = nums.max()! + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for i in 0 ..< m { + counter[i + 1] += counter[i] + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let num = nums[i] + res[counter[num] - 1] = num // Поместить num по соответствующему индексу + counter[num] -= 1 // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + for i in nums.indices { + nums[i] = res[i] + } +} + +@main +enum CountingSort { + /* Driver Code */ + static func main() { + var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSortNaive(nums: &nums) + print("После сортировки подсчетом (объекты не поддерживаются) nums = \(nums)") + + var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSort(nums: &nums1) + print("После сортировки подсчетом nums1 = \(nums1)") + } +} diff --git a/ru/codes/swift/chapter_sorting/heap_sort.swift b/ru/codes/swift/chapter_sorting/heap_sort.swift new file mode 100644 index 000000000..4ed3e3f5f --- /dev/null +++ b/ru/codes/swift/chapter_sorting/heap_sort.swift @@ -0,0 +1,55 @@ +/** + * File: heap_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +func siftDown(nums: inout [Int], n: Int, i: Int) { + var i = i + while true { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + let l = 2 * i + 1 + let r = 2 * i + 2 + var ma = i + if l < n, nums[l] > nums[ma] { + ma = l + } + if r < n, nums[r] > nums[ma] { + ma = r + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if ma == i { + break + } + // Поменять два узла местами + nums.swapAt(i, ma) + // Циклическое просеивание вниз + i = ma + } +} + +/* Сортировка кучей */ +func heapSort(nums: inout [Int]) { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { + siftDown(nums: &nums, n: nums.count, i: i) + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for i in nums.indices.dropFirst().reversed() { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + nums.swapAt(0, i) + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums: &nums, n: i, i: 0) + } +} + +@main +enum HeapSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + heapSort(nums: &nums) + print("После сортировки кучей nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_sorting/insertion_sort.swift b/ru/codes/swift/chapter_sorting/insertion_sort.swift new file mode 100644 index 000000000..59a65e043 --- /dev/null +++ b/ru/codes/swift/chapter_sorting/insertion_sort.swift @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Сортировка вставками */ +func insertionSort(nums: inout [Int]) { + // Внешний цикл: отсортированный диапазон [0, i-1] + for i in nums.indices.dropFirst() { + let base = nums[i] + var j = i - 1 + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while j >= 0, nums[j] > base { + nums[j + 1] = nums[j] // Сдвинуть nums[j] на одну позицию вправо + j -= 1 + } + nums[j + 1] = base // Поместить base в правильную позицию + } +} + +@main +enum InsertionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + insertionSort(nums: &nums) + print("После сортировки вставками nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_sorting/merge_sort.swift b/ru/codes/swift/chapter_sorting/merge_sort.swift new file mode 100644 index 000000000..4ea5595dc --- /dev/null +++ b/ru/codes/swift/chapter_sorting/merge_sort.swift @@ -0,0 +1,65 @@ +/** + * File: merge_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Объединить левый и правый подмассивы */ +func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + var tmp = Array(repeating: 0, count: right - left + 1) + // Инициализировать начальные индексы левого и правого подмассивов + var i = left, j = mid + 1, k = 0 + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while i <= mid, j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i += 1 + } else { + tmp[k] = nums[j] + j += 1 + } + k += 1 + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while i <= mid { + tmp[k] = nums[i] + i += 1 + k += 1 + } + while j <= right { + tmp[k] = nums[j] + j += 1 + k += 1 + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for k in tmp.indices { + nums[left + k] = tmp[k] + } +} + +/* Сортировка слиянием */ +func mergeSort(nums: inout [Int], left: Int, right: Int) { + // Условие завершения + if left >= right { // Завершить рекурсию, когда длина подмассива равна 1 + return + } + // Этап разбиения + let mid = left + (right - left) / 2 // Вычислить середину + mergeSort(nums: &nums, left: left, right: mid) // Рекурсивно обработать левый подмассив + mergeSort(nums: &nums, left: mid + 1, right: right) // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums: &nums, left: left, mid: mid, right: right) +} + +@main +enum MergeSort { + /* Driver Code */ + static func main() { + /* Сортировка слиянием */ + var nums = [7, 3, 2, 6, 0, 1, 5, 4] + mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("После сортировки слиянием nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_sorting/quick_sort.swift b/ru/codes/swift/chapter_sorting/quick_sort.swift new file mode 100644 index 000000000..a5807f2ce --- /dev/null +++ b/ru/codes/swift/chapter_sorting/quick_sort.swift @@ -0,0 +1,114 @@ +/** + * File: quick_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Класс быстрой сортировки */ +/* Разбиение с опорными указателями */ +func partition(nums: inout [Int], left: Int, right: Int) -> Int { + // Взять nums[left] в качестве опорного элемента + var i = left + var j = right + while i < j { + while i < j, nums[j] >= nums[left] { + j -= 1 // Идти справа налево в поисках первого элемента меньше опорного + } + while i < j, nums[i] <= nums[left] { + i += 1 // Идти слева направо в поисках первого элемента больше опорного + } + nums.swapAt(i, j) // Поменять эти два элемента местами + } + nums.swapAt(i, left) // Переместить опорный элемент на границу двух подмассивов + return i // Вернуть индекс опорного элемента +} + +/* Быстрая сортировка */ +func quickSort(nums: inout [Int], left: Int, right: Int) { + // Завершить рекурсию, когда длина подмассива равна 1 + if left >= right { + return + } + // Разбиение с опорными указателями + let pivot = partition(nums: &nums, left: left, right: right) + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums: &nums, left: left, right: pivot - 1) + quickSort(nums: &nums, left: pivot + 1, right: right) +} + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +/* Выбрать медиану из трех кандидатов */ +func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { + let l = nums[left] + let m = nums[mid] + let r = nums[right] + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid // m находится между l и r + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left // l находится между m и r + } + return right +} + +/* Разбиение с опорными указателями (медиана трех) */ +func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { + // Выбрать медиану из трех кандидатов + let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) + // Переместить медиану в крайний левый элемент массива + nums.swapAt(left, med) + return partition(nums: &nums, left: left, right: right) +} + +/* Быстрая сортировка (оптимизация медианным опорным элементом) */ +func quickSortMedian(nums: inout [Int], left: Int, right: Int) { + // Завершить рекурсию, когда длина подмассива равна 1 + if left >= right { + return + } + // Разбиение с опорными указателями + let pivot = partitionMedian(nums: &nums, left: left, right: right) + // Рекурсивно обработать левый и правый подмассивы + quickSortMedian(nums: &nums, left: left, right: pivot - 1) + quickSortMedian(nums: &nums, left: pivot + 1, right: right) +} + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { + var left = left + var right = right + // Завершить, когда длина подмассива равна 1 + while left < right { + // Операция разбиения с опорными указателями + let pivot = partition(nums: &nums, left: left, right: right) + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left) < (right - pivot) { + quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Рекурсивно отсортировать левый подмассив + left = pivot + 1 // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Рекурсивно отсортировать правый подмассив + right = pivot - 1 // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } +} + +@main +enum QuickSort { + /* Driver Code */ + static func main() { + /* Быстрая сортировка */ + var nums = [2, 4, 1, 0, 3, 5] + quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("После быстрой сортировки nums = \(nums)") + + /* Быстрая сортировка (оптимизация медианным опорным элементом) */ + var nums1 = [2, 4, 1, 0, 3, 5] + quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) + print("После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = \(nums1)") + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + var nums2 = [2, 4, 1, 0, 3, 5] + quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) + print("После быстрой сортировки (оптимизация глубины рекурсии) nums2 = \(nums2)") + } +} diff --git a/ru/codes/swift/chapter_sorting/radix_sort.swift b/ru/codes/swift/chapter_sorting/radix_sort.swift new file mode 100644 index 000000000..2ee69124d --- /dev/null +++ b/ru/codes/swift/chapter_sorting/radix_sort.swift @@ -0,0 +1,79 @@ +/** + * File: radix_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +func digit(num: Int, exp: Int) -> Int { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + (num / exp) % 10 +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +func countingSortDigit(nums: inout [Int], exp: Int) { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + var counter = Array(repeating: 0, count: 10) + // Подсчитать число появлений каждой цифры от 0 до 9 + for i in nums.indices { + let d = digit(num: nums[i], exp: exp) // Получить k-й разряд nums[i], обозначив его как d + counter[d] += 1 // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for i in 1 ..< 10 { + counter[i] += counter[i - 1] + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let d = digit(num: nums[i], exp: exp) + let j = counter[d] - 1 // Получить индекс j цифры d в массиве + res[j] = nums[i] // Поместить текущий элемент по индексу j + counter[d] -= 1 // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for i in nums.indices { + nums[i] = res[i] + } +} + +/* Поразрядная сортировка */ +func radixSort(nums: inout [Int]) { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + var m = Int.min + for num in nums { + if num > m { + m = num + } + } + // Проходить разряды от младшего к старшему + for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums: &nums, exp: exp) + } +} + +@main +enum RadixSort { + /* Driver Code */ + static func main() { + // Поразрядная сортировка + var nums = [ + 10_546_151, + 35_663_510, + 42_865_989, + 34_862_445, + 81_883_077, + 88_906_420, + 72_429_244, + 30_524_779, + 82_060_337, + 63_832_996, + ] + radixSort(nums: &nums) + print("После поразрядной сортировки nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_sorting/selection_sort.swift b/ru/codes/swift/chapter_sorting/selection_sort.swift new file mode 100644 index 000000000..fd8776749 --- /dev/null +++ b/ru/codes/swift/chapter_sorting/selection_sort.swift @@ -0,0 +1,31 @@ +/** + * File: selection_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Сортировка выбором */ +func selectionSort(nums: inout [Int]) { + // Внешний цикл: неотсортированный диапазон [i, n-1] + for i in nums.indices.dropLast() { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + var k = i + for j in nums.indices.dropFirst(i + 1) { + if nums[j] < nums[k] { + k = j // Записать индекс минимального элемента + } + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + nums.swapAt(i, k) + } +} + +@main +enum SelectionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + selectionSort(nums: &nums) + print("После сортировки выбором nums = \(nums)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/array_deque.swift b/ru/codes/swift/chapter_stack_and_queue/array_deque.swift new file mode 100644 index 000000000..06a89535c --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/array_deque.swift @@ -0,0 +1,148 @@ +/** + * File: array_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Двусторонняя очередь на основе кольцевого массива */ +class ArrayDeque { + private var nums: [Int] // Массив для хранения элементов двусторонней очереди + private var front: Int // Указатель head, указывающий на первый элемент очереди + private var _size: Int // Длина двусторонней очереди + + /* Конструктор */ + init(capacity: Int) { + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* Получить вместимость двусторонней очереди */ + func capacity() -> Int { + nums.count + } + + /* Получение длины двусторонней очереди */ + func size() -> Int { + _size + } + + /* Проверка, пуста ли двусторонняя очередь */ + func isEmpty() -> Bool { + size() == 0 + } + + /* Вычислить индекс в кольцевом массиве */ + private func index(i: Int) -> Int { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + (i + capacity()) % capacity() + } + + /* Добавление в голову очереди */ + func pushFirst(num: Int) { + if size() == capacity() { + print("Двусторонняя очередь заполнена") + return + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + front = index(i: front - 1) + // Добавить num в голову очереди + nums[front] = num + _size += 1 + } + + /* Добавление в хвост очереди */ + func pushLast(num: Int) { + if size() == capacity() { + print("Двусторонняя очередь заполнена") + return + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + let rear = index(i: front + size()) + // Добавить num в хвост очереди + nums[rear] = num + _size += 1 + } + + /* Извлечение из головы очереди */ + func popFirst() -> Int { + let num = peekFirst() + // Указатель головы сдвигается на одну позицию назад + front = index(i: front + 1) + _size -= 1 + return num + } + + /* Извлечение из хвоста очереди */ + func popLast() -> Int { + let num = peekLast() + _size -= 1 + return num + } + + /* Доступ к элементу в начале очереди */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("двусторонняя очередь пуста") + } + return nums[front] + } + + /* Доступ к элементу в конце очереди */ + func peekLast() -> Int { + if isEmpty() { + fatalError("двусторонняя очередь пуста") + } + // Вычислить индекс хвостового элемента + let last = index(i: front + size() - 1) + return nums[last] + } + + /* Вернуть массив для вывода */ + func toArray() -> [Int] { + // Преобразовывать только элементы списка в пределах фактической длины + (front ..< front + size()).map { nums[index(i: $0)] } + } +} + +@main +enum _ArrayDeque { + /* Driver Code */ + static func main() { + /* Инициализация двусторонней очереди */ + let deque = ArrayDeque(capacity: 10) + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("Двусторонняя очередь deque = \(deque.toArray())") + + /* Доступ к элементу */ + let peekFirst = deque.peekFirst() + print("Первый элемент peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("Последний элемент peekLast = \(peekLast)") + + /* Добавление элемента в очередь */ + deque.pushLast(num: 4) + print("После добавления элемента 4 в хвост deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("После добавления элемента 1 в голову deque = \(deque.toArray())") + + /* Извлечение элемента из очереди */ + let popLast = deque.popLast() + print("Извлеченный из хвоста элемент = \(popLast), deque после извлечения из хвоста = \(deque.toArray())") + let popFirst = deque.popFirst() + print("Извлеченный из головы элемент = \(popFirst), deque после извлечения из головы = \(deque.toArray())") + + /* Получение длины двусторонней очереди */ + let size = deque.size() + print("Длина двусторонней очереди size = \(size)") + + /* Проверка, пуста ли двусторонняя очередь */ + let isEmpty = deque.isEmpty() + print("Пуста ли двусторонняя очередь = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/array_queue.swift b/ru/codes/swift/chapter_stack_and_queue/array_queue.swift new file mode 100644 index 000000000..b3fb17934 --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/array_queue.swift @@ -0,0 +1,113 @@ +/** + * File: array_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + private var nums: [Int] // Массив для хранения элементов очереди + private var front: Int // Указатель head, указывающий на первый элемент очереди + private var _size: Int // Длина очереди + + init(capacity: Int) { + // Инициализация массива + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* Получить вместимость очереди */ + func capacity() -> Int { + nums.count + } + + /* Получение длины очереди */ + func size() -> Int { + _size + } + + /* Проверка, пуста ли очередь */ + func isEmpty() -> Bool { + size() == 0 + } + + /* Поместить в очередь */ + func push(num: Int) { + if size() == capacity() { + print("Очередь заполнена") + return + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + let rear = (front + size()) % capacity() + // Добавить num в хвост очереди + nums[rear] = num + _size += 1 + } + + /* Извлечь из очереди */ + @discardableResult + func pop() -> Int { + let num = peek() + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + front = (front + 1) % capacity() + _size -= 1 + return num + } + + /* Доступ к элементу в начале очереди */ + func peek() -> Int { + if isEmpty() { + fatalError("очередь пуста") + } + return nums[front] + } + + /* Вернуть массив */ + func toArray() -> [Int] { + // Преобразовывать только элементы списка в пределах фактической длины + (front ..< front + size()).map { nums[$0 % capacity()] } + } +} + +@main +enum _ArrayQueue { + /* Driver Code */ + static func main() { + /* Инициализация очереди */ + let capacity = 10 + let queue = ArrayQueue(capacity: capacity) + + /* Добавление элемента в очередь */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("Очередь queue = \(queue.toArray())") + + /* Доступ к элементу в начале очереди */ + let peek = queue.peek() + print("Первый элемент peek = \(peek)") + + /* Извлечение элемента из очереди */ + let pop = queue.pop() + print("Извлеченный элемент pop = \(pop), queue после извлечения = \(queue.toArray())") + + /* Получение длины очереди */ + let size = queue.size() + print("Длина очереди size = \(size)") + + /* Проверка, пуста ли очередь */ + let isEmpty = queue.isEmpty() + print("Пуста ли очередь = \(isEmpty)") + + /* Проверка кольцевого массива */ + for i in 0 ..< 10 { + queue.push(num: i) + queue.pop() + print("После \(i)-го раунда операций enqueue и dequeue queue = \(queue.toArray())") + } + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/array_stack.swift b/ru/codes/swift/chapter_stack_and_queue/array_stack.swift new file mode 100644 index 000000000..5c438e613 --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/array_stack.swift @@ -0,0 +1,85 @@ +/** + * File: array_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Стек на основе массива */ +class ArrayStack { + private var stack: [Int] + + init() { + // Инициализация списка (динамического массива) + stack = [] + } + + /* Получение длины стека */ + func size() -> Int { + stack.count + } + + /* Проверка, пуст ли стек */ + func isEmpty() -> Bool { + stack.isEmpty + } + + /* Поместить в стек */ + func push(num: Int) { + stack.append(num) + } + + /* Извлечь из стека */ + @discardableResult + func pop() -> Int { + if isEmpty() { + fatalError("стек пуст") + } + return stack.removeLast() + } + + /* Доступ к верхнему элементу стека */ + func peek() -> Int { + if isEmpty() { + fatalError("стек пуст") + } + return stack.last! + } + + /* Преобразовать List в Array и вернуть */ + func toArray() -> [Int] { + stack + } +} + +@main +enum _ArrayStack { + /* Driver Code */ + static func main() { + /* Инициализация стека */ + let stack = ArrayStack() + + /* Помещение элемента в стек */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("Стек stack = \(stack.toArray())") + + /* Доступ к верхнему элементу стека */ + let peek = stack.peek() + print("Верхний элемент peek = \(peek)") + + /* Извлечение элемента из стека */ + let pop = stack.pop() + print("Извлеченный элемент pop = \(pop), stack после извлечения = \(stack.toArray())") + + /* Получение длины стека */ + let size = stack.size() + print("Длина стека size = \(size)") + + /* Проверка на пустоту */ + let isEmpty = stack.isEmpty() + print("Пуст ли стек = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/deque.swift b/ru/codes/swift/chapter_stack_and_queue/deque.swift new file mode 100644 index 000000000..f5aa48207 --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/deque.swift @@ -0,0 +1,44 @@ +/** + * File: deque.swift + * Created Time: 2023-01-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Deque { + /* Driver Code */ + static func main() { + /* Инициализация двусторонней очереди */ + // В Swift нет встроенного класса двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь + var deque: [Int] = [] + + /* Добавление элемента в очередь */ + deque.append(2) + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) + deque.insert(1, at: 0) + print("Двусторонняя очередь deque = \(deque)") + + /* Доступ к элементу */ + let peekFirst = deque.first! + print("Первый элемент peekFirst = \(peekFirst)") + let peekLast = deque.last! + print("Последний элемент peekLast = \(peekLast)") + + /* Извлечение элемента из очереди */ + // При использовании Array для имитации popFirst имеет сложность O(n) + let popFirst = deque.removeFirst() + print("Извлеченный из головы элемент popFirst = \(popFirst), deque после извлечения из головы = \(deque)") + let popLast = deque.removeLast() + print("Извлеченный из хвоста элемент popLast = \(popLast), deque после извлечения из хвоста = \(deque)") + + /* Получение длины двусторонней очереди */ + let size = deque.count + print("Длина двусторонней очереди size = \(size)") + + /* Проверка, пуста ли двусторонняя очередь */ + let isEmpty = deque.isEmpty + print("Пуста ли двусторонняя очередь = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift b/ru/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift new file mode 100644 index 000000000..c59585da8 --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift @@ -0,0 +1,180 @@ +/** + * File: linkedlist_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Узел двусвязного списка */ +class ListNode { + var val: Int // Значение узла + var next: ListNode? // Ссылка на узел-преемник + weak var prev: ListNode? // Ссылка на узел-предшественник + + init(val: Int) { + self.val = val + } +} + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + private var front: ListNode? // Головной узел front + private var rear: ListNode? // Хвостовой узел rear + private var _size: Int // Длина двусторонней очереди + + init() { + _size = 0 + } + + /* Получение длины двусторонней очереди */ + func size() -> Int { + _size + } + + /* Проверка, пуста ли двусторонняя очередь */ + func isEmpty() -> Bool { + size() == 0 + } + + /* Операция добавления в очередь */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if isEmpty() { + front = node + rear = node + } + // Операция добавления в голову очереди + else if isFront { + // Добавить node в голову списка + front?.prev = node + node.next = front + front = node // Обновить головной узел + } + // Операция добавления в хвост очереди + else { + // Добавить node в хвост списка + rear?.next = node + node.prev = rear + rear = node // Обновить хвостовой узел + } + _size += 1 // Обновить длину очереди + } + + /* Добавление в голову очереди */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* Добавление в хвост очереди */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* Операция извлечения из очереди */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("двусторонняя очередь пуста") + } + let val: Int + // Операция извлечения из головы очереди + if isFront { + val = front!.val // Временно сохранить значение головного узла + // Удалить головной узел + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // Обновить головной узел + } + // Операция извлечения из хвоста очереди + else { + val = rear!.val // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // Обновить хвостовой узел + } + _size -= 1 // Обновить длину очереди + return val + } + + /* Извлечение из головы очереди */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* Извлечение из хвоста очереди */ + func popLast() -> Int { + pop(isFront: false) + } + + /* Доступ к элементу в начале очереди */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("двусторонняя очередь пуста") + } + return front!.val + } + + /* Доступ к элементу в конце очереди */ + func peekLast() -> Int { + if isEmpty() { + fatalError("двусторонняя очередь пуста") + } + return rear!.val + } + + /* Вернуть массив для вывода */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListDeque { + /* Driver Code */ + static func main() { + /* Инициализация двусторонней очереди */ + let deque = LinkedListDeque() + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("Двусторонняя очередь deque = \(deque.toArray())") + + /* Доступ к элементу */ + let peekFirst = deque.peekFirst() + print("Первый элемент peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("Последний элемент peekLast = \(peekLast)") + + /* Добавление элемента в очередь */ + deque.pushLast(num: 4) + print("После добавления элемента 4 в хвост deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("После добавления элемента 1 в голову deque = \(deque.toArray())") + + /* Извлечение элемента из очереди */ + let popLast = deque.popLast() + print("Извлеченный из хвоста элемент = \(popLast), deque после извлечения из хвоста = \(deque.toArray())") + let popFirst = deque.popFirst() + print("Извлеченный из головы элемент = \(popFirst), deque после извлечения из головы = \(deque.toArray())") + + /* Получение длины двусторонней очереди */ + let size = deque.size() + print("Длина двусторонней очереди size = \(size)") + + /* Проверка, пуста ли двусторонняя очередь */ + let isEmpty = deque.isEmpty() + print("Пуста ли двусторонняя очередь = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift b/ru/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift new file mode 100644 index 000000000..d0158398f --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift @@ -0,0 +1,107 @@ +/** + * File: linkedlist_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Очередь на основе связного списка */ +class LinkedListQueue { + private var front: ListNode? // Головной узел + private var rear: ListNode? // Хвостовой узел + private var _size: Int + + init() { + _size = 0 + } + + /* Получение длины очереди */ + func size() -> Int { + _size + } + + /* Проверка, пуста ли очередь */ + func isEmpty() -> Bool { + size() == 0 + } + + /* Поместить в очередь */ + func push(num: Int) { + // Добавить num после хвостового узла + let node = ListNode(x: num) + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if front == nil { + front = node + rear = node + } + // Если очередь не пуста, добавить этот узел после хвостового узла + else { + rear?.next = node + rear = node + } + _size += 1 + } + + /* Извлечь из очереди */ + @discardableResult + func pop() -> Int { + let num = peek() + // Удалить головной узел + front = front?.next + _size -= 1 + return num + } + + /* Доступ к элементу в начале очереди */ + func peek() -> Int { + if isEmpty() { + fatalError("очередь пуста") + } + return front!.val + } + + /* Преобразовать связный список в Array и вернуть */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListQueue { + /* Driver Code */ + static func main() { + /* Инициализация очереди */ + let queue = LinkedListQueue() + + /* Добавление элемента в очередь */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("Очередь queue = \(queue.toArray())") + + /* Доступ к элементу в начале очереди */ + let peek = queue.peek() + print("Первый элемент peek = \(peek)") + + /* Извлечение элемента из очереди */ + let pop = queue.pop() + print("Извлеченный элемент pop = \(pop), queue после извлечения = \(queue.toArray())") + + /* Получение длины очереди */ + let size = queue.size() + print("Длина очереди size = \(size)") + + /* Проверка, пуста ли очередь */ + let isEmpty = queue.isEmpty() + print("Пуста ли очередь = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift b/ru/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift new file mode 100644 index 000000000..ff3420e34 --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift @@ -0,0 +1,96 @@ +/** + * File: linkedlist_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Стек на основе связного списка */ +class LinkedListStack { + private var _peek: ListNode? // Использовать головной узел как вершину стека + private var _size: Int // Длина стека + + init() { + _size = 0 + } + + /* Получение длины стека */ + func size() -> Int { + _size + } + + /* Проверка, пуст ли стек */ + func isEmpty() -> Bool { + size() == 0 + } + + /* Поместить в стек */ + func push(num: Int) { + let node = ListNode(x: num) + node.next = _peek + _peek = node + _size += 1 + } + + /* Извлечь из стека */ + @discardableResult + func pop() -> Int { + let num = peek() + _peek = _peek?.next + _size -= 1 + return num + } + + /* Доступ к верхнему элементу стека */ + func peek() -> Int { + if isEmpty() { + fatalError("стек пуст") + } + return _peek!.val + } + + /* Преобразовать List в Array и вернуть */ + func toArray() -> [Int] { + var node = _peek + var res = Array(repeating: 0, count: size()) + for i in res.indices.reversed() { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListStack { + /* Driver Code */ + static func main() { + /* Инициализация стека */ + let stack = LinkedListStack() + + /* Помещение элемента в стек */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("Стек stack = \(stack.toArray())") + + /* Доступ к верхнему элементу стека */ + let peek = stack.peek() + print("Верхний элемент peek = \(peek)") + + /* Извлечение элемента из стека */ + let pop = stack.pop() + print("Извлеченный элемент pop = \(pop), stack после извлечения = \(stack.toArray())") + + /* Получение длины стека */ + let size = stack.size() + print("Длина стека size = \(size)") + + /* Проверка на пустоту */ + let isEmpty = stack.isEmpty() + print("Пуст ли стек = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/queue.swift b/ru/codes/swift/chapter_stack_and_queue/queue.swift new file mode 100644 index 000000000..48abbad7d --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/queue.swift @@ -0,0 +1,40 @@ +/** + * File: queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Queue { + /* Driver Code */ + static func main() { + /* Инициализация очереди */ + // В Swift нет встроенного класса очереди, поэтому Array можно использовать как очередь + var queue: [Int] = [] + + /* Добавление элемента в очередь */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + print("Очередь queue = \(queue)") + + /* Доступ к элементу в начале очереди */ + let peek = queue.first! + print("Первый элемент peek = \(peek)") + + /* Извлечение элемента из очереди */ + // При использовании Array для имитации pop имеет сложность O(n) + let pool = queue.removeFirst() + print("Извлеченный элемент pop = \(pool), queue после извлечения = \(queue)") + + /* Получение длины очереди */ + let size = queue.count + print("Длина очереди size = \(size)") + + /* Проверка, пуста ли очередь */ + let isEmpty = queue.isEmpty + print("Пуста ли очередь = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_stack_and_queue/stack.swift b/ru/codes/swift/chapter_stack_and_queue/stack.swift new file mode 100644 index 000000000..09d13025d --- /dev/null +++ b/ru/codes/swift/chapter_stack_and_queue/stack.swift @@ -0,0 +1,39 @@ +/** + * File: stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Stack { + /* Driver Code */ + static func main() { + /* Инициализация стека */ + // В Swift нет встроенного класса стека, поэтому Array можно использовать как стек + var stack: [Int] = [] + + /* Помещение элемента в стек */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("Стек stack = \(stack)") + + /* Доступ к верхнему элементу стека */ + let peek = stack.last! + print("Верхний элемент peek = \(peek)") + + /* Извлечение элемента из стека */ + let pop = stack.removeLast() + print("Извлеченный элемент pop = \(pop), stack после извлечения = \(stack)") + + /* Получение длины стека */ + let size = stack.count + print("Длина стека size = \(size)") + + /* Проверка на пустоту */ + let isEmpty = stack.isEmpty + print("Пуст ли стек = \(isEmpty)") + } +} diff --git a/ru/codes/swift/chapter_tree/array_binary_tree.swift b/ru/codes/swift/chapter_tree/array_binary_tree.swift new file mode 100644 index 000000000..90c30c842 --- /dev/null +++ b/ru/codes/swift/chapter_tree/array_binary_tree.swift @@ -0,0 +1,141 @@ +/** + * File: array_binary_tree.swift + * Created Time: 2023-07-23 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree { + private var tree: [Int?] + + /* Конструктор */ + init(arr: [Int?]) { + tree = arr + } + + /* Вместимость списка */ + func size() -> Int { + tree.count + } + + /* Получить значение узла с индексом i */ + func val(i: Int) -> Int? { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* Получить индекс родительского узла узла с индексом i */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* Обход в ширину */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // Непосредственно обходить массив + for i in 0 ..< size() { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* Обход в глубину */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // Если это пустая позиция, вернуть + guard let val = val(i: i) else { + return + } + // Предварительный обход + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // Симметричный обход + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // Обратный обход + if order == "post" { + res.append(val) + } + } + + /* Предварительный обход */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* Симметричный обход */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* Обратный обход */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } +} + +@main +enum _ArrayBinaryTree { + /* Driver Code */ + static func main() { + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + + let root = TreeNode.listToTree(arr: arr) + print("\nИнициализация двоичного дерева\n") + print("Массивное представление двоичного дерева:") + print(arr) + print("Связное представление двоичного дерева:") + PrintUtil.printTree(root: root) + + // Класс двоичного дерева в массивном представлении + let abt = ArrayBinaryTree(arr: arr) + + // Доступ к узлу + let i = 1 + let l = abt.left(i: i) + let r = abt.right(i: i) + let p = abt.parent(i: i) + print("\nТекущий узел: индекс = \(i), значение = \(abt.val(i: i) as Any)") + print("Индекс левого дочернего узла = \(l), значение = \(abt.val(i: l) as Any)") + print("Индекс правого дочернего узла = \(r), значение = \(abt.val(i: r) as Any)") + print("Индекс родительского узла = \(p), значение = \(abt.val(i: p) as Any)") + + // Обходить дерево + var res = abt.levelOrder() + print("\nОбход в ширину: \(res)") + res = abt.preOrder() + print("Предварительный обход: \(res)") + res = abt.inOrder() + print("Симметричный обход: \(res)") + res = abt.postOrder() + print("Обратный обход: \(res)") + } +} diff --git a/ru/codes/swift/chapter_tree/avl_tree.swift b/ru/codes/swift/chapter_tree/avl_tree.swift new file mode 100644 index 000000000..31f85c33a --- /dev/null +++ b/ru/codes/swift/chapter_tree/avl_tree.swift @@ -0,0 +1,230 @@ +/** + * File: avl_tree.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* AVL-дерево */ +class AVLTree { + fileprivate var root: TreeNode? // Корневой узел + + init() {} + + /* Получить высоту узла */ + func height(node: TreeNode?) -> Int { + // Высота пустого узла равна -1, высота листового узла равна 0 + node?.height ?? -1 + } + + /* Обновить высоту узла */ + private func updateHeight(node: TreeNode?) { + // Высота узла равна высоте более высокого поддерева + 1 + node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 + } + + /* Получить коэффициент баланса */ + func balanceFactor(node: TreeNode?) -> Int { + // Коэффициент баланса пустого узла равен 0 + guard let node = node else { return 0 } + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return height(node: node.left) - height(node: node.right) + } + + /* Операция правого вращения */ + private func rightRotate(node: TreeNode?) -> TreeNode? { + let child = node?.left + let grandChild = child?.right + // Выполнить правое вращение узла node вокруг child + child?.right = node + node?.left = grandChild + // Обновить высоту узла + updateHeight(node: node) + updateHeight(node: child) + // Вернуть корневой узел поддерева после вращения + return child + } + + /* Операция левого вращения */ + private func leftRotate(node: TreeNode?) -> TreeNode? { + let child = node?.right + let grandChild = child?.left + // Выполнить левое вращение узла node вокруг child + child?.left = node + node?.right = grandChild + // Обновить высоту узла + updateHeight(node: node) + updateHeight(node: child) + // Вернуть корневой узел поддерева после вращения + return child + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + private func rotate(node: TreeNode?) -> TreeNode? { + // Получить коэффициент баланса узла node + let balanceFactor = balanceFactor(node: node) + // Левосторонне перекошенное дерево + if balanceFactor > 1 { + if self.balanceFactor(node: node?.left) >= 0 { + // Правое вращение + return rightRotate(node: node) + } else { + // Сначала левое вращение, затем правое + node?.left = leftRotate(node: node?.left) + return rightRotate(node: node) + } + } + // Правосторонне перекошенное дерево + if balanceFactor < -1 { + if self.balanceFactor(node: node?.right) <= 0 { + // Левое вращение + return leftRotate(node: node) + } else { + // Сначала правое вращение, затем левое + node?.right = rightRotate(node: node?.right) + return leftRotate(node: node) + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node + } + + /* Вставка узла */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return TreeNode(x: val) + } + /* 1. Найти позицию вставки и вставить узел */ + if val < node!.val { + node?.left = insertHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = insertHelper(node: node?.right, val: val) + } else { + return node // Повторяющийся узел не вставлять, сразу вернуть + } + updateHeight(node: node) // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node: node) + // Вернуть корневой узел поддерева + return node + } + + /* Удаление узла */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return nil + } + /* 1. Найти узел и удалить его */ + if val < node!.val { + node?.left = removeHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = removeHelper(node: node?.right, val: val) + } else { + if node?.left == nil || node?.right == nil { + let child = node?.left ?? node?.right + // Число дочерних узлов = 0, удалить node и сразу вернуть + if child == nil { + return nil + } + // Число дочерних узлов = 1, удалить node напрямую + else { + node = child + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = rotate(node: node) + // Вернуть корневой узел поддерева + return node + } + + /* Поиск узла */ + func search(val: Int) -> TreeNode? { + var cur = root + while cur != nil { + // Целевой узел находится в правом поддереве cur + if cur!.val < val { + cur = cur?.right + } + // Целевой узел находится в левом поддереве cur + else if cur!.val > val { + cur = cur?.left + } + // Найти целевой узел и выйти из цикла + else { + break + } + } + // Вернуть целевой узел + return cur + } +} + +@main +enum _AVLTree { + static func testInsert(tree: AVLTree, val: Int) { + tree.insert(val: val) + print("\nПосле вставки узла \(val) AVL-дерево имеет вид") + PrintUtil.printTree(root: tree.root) + } + + static func testRemove(tree: AVLTree, val: Int) { + tree.remove(val: val) + print("\nПосле удаления узла \(val) AVL-дерево имеет вид") + PrintUtil.printTree(root: tree.root) + } + + /* Driver Code */ + static func main() { + /* Инициализация пустого AVL-дерева */ + let avlTree = AVLTree() + + /* Вставка узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + testInsert(tree: avlTree, val: 1) + testInsert(tree: avlTree, val: 2) + testInsert(tree: avlTree, val: 3) + testInsert(tree: avlTree, val: 4) + testInsert(tree: avlTree, val: 5) + testInsert(tree: avlTree, val: 8) + testInsert(tree: avlTree, val: 7) + testInsert(tree: avlTree, val: 9) + testInsert(tree: avlTree, val: 10) + testInsert(tree: avlTree, val: 6) + + /* Вставка повторяющегося узла */ + testInsert(tree: avlTree, val: 7) + + /* Удаление узла */ + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(tree: avlTree, val: 8) // Удаление узла степени 0 + testRemove(tree: avlTree, val: 5) // Удаление узла степени 1 + testRemove(tree: avlTree, val: 4) // Удаление узла степени 2 + + /* Поиск узла */ + let node = avlTree.search(val: 7) + print("\nНайденный объект узла = \(node!), значение узла = \(node!.val)") + } +} diff --git a/ru/codes/swift/chapter_tree/binary_search_tree.swift b/ru/codes/swift/chapter_tree/binary_search_tree.swift new file mode 100644 index 000000000..a5a852d30 --- /dev/null +++ b/ru/codes/swift/chapter_tree/binary_search_tree.swift @@ -0,0 +1,173 @@ +/** + * File: binary_search_tree.swift + * Created Time: 2023-01-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Двоичное дерево поиска */ +class BinarySearchTree { + private var root: TreeNode? + + /* Конструктор */ + init() { + // Инициализировать пустое дерево + root = nil + } + + /* Получить корневой узел двоичного дерева */ + func getRoot() -> TreeNode? { + root + } + + /* Поиск узла */ + func search(num: Int) -> TreeNode? { + var cur = root + // Искать в цикле и выйти после прохода за листовой узел + while cur != nil { + // Целевой узел находится в правом поддереве cur + if cur!.val < num { + cur = cur?.right + } + // Целевой узел находится в левом поддереве cur + else if cur!.val > num { + cur = cur?.left + } + // Найти целевой узел и выйти из цикла + else { + break + } + } + // Вернуть целевой узел + return cur + } + + /* Вставка узла */ + func insert(num: Int) { + // Если дерево пусто, инициализировать корневой узел + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // Искать в цикле и выйти после прохода за листовой узел + while cur != nil { + // Найти повторяющийся узел и сразу вернуть + if cur!.val == num { + return + } + pre = cur + // Позиция вставки находится в правом поддереве cur + if cur!.val < num { + cur = cur?.right + } + // Позиция вставки находится в левом поддереве cur + else { + cur = cur?.left + } + } + // Вставка узла + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + + /* Удаление узла */ + func remove(num: Int) { + // Если дерево пусто, сразу вернуть + if root == nil { + return + } + var cur = root + var pre: TreeNode? + // Искать в цикле и выйти после прохода за листовой узел + while cur != nil { + // Найти узел для удаления и выйти из цикла + if cur!.val == num { + break + } + pre = cur + // Узел для удаления находится в правом поддереве cur + if cur!.val < num { + cur = cur?.right + } + // Узел для удаления находится в левом поддереве cur + else { + cur = cur?.left + } + } + // Если узел для удаления отсутствует, сразу вернуть + if cur == nil { + return + } + // Число дочерних узлов = 0 или 1 + if cur?.left == nil || cur?.right == nil { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + let child = cur?.left ?? cur?.right + // Удалить узел cur + if cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + root = child + } + } + // Число дочерних узлов = 2 + else { + // Получить следующий узел после cur в симметричном обходе + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // Рекурсивно удалить узел tmp + remove(num: tmp!.val) + // Перезаписать cur значением tmp + cur?.val = tmp!.val + } + } +} + +@main +enum _BinarySearchTree { + /* Driver Code */ + static func main() { + /* Инициализация двоичного дерева поиска */ + let bst = BinarySearchTree() + // Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + for num in nums { + bst.insert(num: num) + } + print("\nИсходное двоичное дерево\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* Поиск узла */ + let node = bst.search(num: 7) + print("\nНайденный объект узла = \(node!), значение узла = \(node!.val)") + + /* Вставка узла */ + bst.insert(num: 16) + print("\nПосле вставки узла 16 двоичное дерево имеет вид\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* Удаление узла */ + bst.remove(num: 1) + print("\nПосле удаления узла 1 двоичное дерево имеет вид\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 2) + print("\nПосле удаления узла 2 двоичное дерево имеет вид\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 4) + print("\nПосле удаления узла 4 двоичное дерево имеет вид\n") + PrintUtil.printTree(root: bst.getRoot()) + } +} diff --git a/ru/codes/swift/chapter_tree/binary_tree.swift b/ru/codes/swift/chapter_tree/binary_tree.swift new file mode 100644 index 000000000..1bb78b7ec --- /dev/null +++ b/ru/codes/swift/chapter_tree/binary_tree.swift @@ -0,0 +1,40 @@ +/** + * File: binary_tree.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BinaryTree { + /* Driver Code */ + static func main() { + /* Инициализация двоичного дерева */ + // Инициализация узла + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // Построить связи между узлами (указатели) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\nИнициализация двоичного дерева\n") + PrintUtil.printTree(root: n1) + + /* Вставка и удаление узлов */ + let P = TreeNode(x: 0) + // Вставить узел P между n1 -> n2 + n1.left = P + P.left = n2 + print("\nПосле вставки узла P\n") + PrintUtil.printTree(root: n1) + // Удалить узел P + n1.left = n2 + print("\nПосле удаления узла P\n") + PrintUtil.printTree(root: n1) + } +} diff --git a/ru/codes/swift/chapter_tree/binary_tree_bfs.swift b/ru/codes/swift/chapter_tree/binary_tree_bfs.swift new file mode 100644 index 000000000..d12f34d42 --- /dev/null +++ b/ru/codes/swift/chapter_tree/binary_tree_bfs.swift @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* Обход в ширину */ +func levelOrder(root: TreeNode) -> [Int] { + // Инициализировать очередь и добавить корневой узел + var queue: [TreeNode] = [root] + // Инициализировать список для хранения последовательности обхода + var list: [Int] = [] + while !queue.isEmpty { + let node = queue.removeFirst() // Извлечение из очереди + list.append(node.val) // Сохранить значение узла + if let left = node.left { + queue.append(left) // Поместить левый дочерний узел в очередь + } + if let right = node.right { + queue.append(right) // Поместить правый дочерний узел в очередь + } + } + return list +} + +@main +enum BinaryTreeBFS { + /* Driver Code */ + static func main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\nИнициализация двоичного дерева\n") + PrintUtil.printTree(root: node) + + /* Обход в ширину */ + let list = levelOrder(root: node) + print("\nПоследовательность печати узлов при обходе в ширину = \(list)") + } +} diff --git a/ru/codes/swift/chapter_tree/binary_tree_dfs.swift b/ru/codes/swift/chapter_tree/binary_tree_dfs.swift new file mode 100644 index 000000000..2f408aef3 --- /dev/null +++ b/ru/codes/swift/chapter_tree/binary_tree_dfs.swift @@ -0,0 +1,70 @@ +/** + * File: binary_tree_dfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +// Инициализировать список для хранения последовательности обхода +var list: [Int] = [] + +/* Предварительный обход */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.append(root.val) + preOrder(root: root.left) + preOrder(root: root.right) +} + +/* Симметричный обход */ +func inOrder(root: TreeNode?) { + guard let root = root else { + return + } + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root: root.left) + list.append(root.val) + inOrder(root: root.right) +} + +/* Обратный обход */ +func postOrder(root: TreeNode?) { + guard let root = root else { + return + } + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root: root.left) + postOrder(root: root.right) + list.append(root.val) +} + +@main +enum BinaryTreeDFS { + /* Driver Code */ + static func main() { + /* Инициализация двоичного дерева */ + // Здесь используется функция, напрямую строящая двоичное дерево из массива + let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\nИнициализация двоичного дерева\n") + PrintUtil.printTree(root: root) + + /* Предварительный обход */ + list.removeAll() + preOrder(root: root) + print("\nПоследовательность печати узлов при предварительном обходе = \(list)") + + /* Симметричный обход */ + list.removeAll() + inOrder(root: root) + print("\nПоследовательность печати узлов при симметричном обходе = \(list)") + + /* Обратный обход */ + list.removeAll() + postOrder(root: root) + print("\nПоследовательность печати узлов при обратном обходе = \(list)") + } +} diff --git a/ru/codes/swift/utils/ListNode.swift b/ru/codes/swift/utils/ListNode.swift new file mode 100644 index 000000000..451030e89 --- /dev/null +++ b/ru/codes/swift/utils/ListNode.swift @@ -0,0 +1,33 @@ +/** + * File: ListNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public class ListNode: Hashable { + public var val: Int // Значение узла + public var next: ListNode? // Ссылка на узел-преемник + + public init(x: Int) { + val = x + } + + public static func == (lhs: ListNode, rhs: ListNode) -> Bool { + lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + hasher.combine(next.map { ObjectIdentifier($0) }) + } + + public static func arrToLinkedList(arr: [Int]) -> ListNode? { + let dum = ListNode(x: 0) + var head: ListNode? = dum + for val in arr { + head?.next = ListNode(x: val) + head = head?.next + } + return dum.next + } +} diff --git a/ru/codes/swift/utils/Pair.swift b/ru/codes/swift/utils/Pair.swift new file mode 100644 index 000000000..fc298b761 --- /dev/null +++ b/ru/codes/swift/utils/Pair.swift @@ -0,0 +1,20 @@ +/** + * File: Pair.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Пара ключ-значение */ +public class Pair: Equatable { + public var key: Int + public var val: String + + public init(key: Int, val: String) { + self.key = key + self.val = val + } + + public static func == (lhs: Pair, rhs: Pair) -> Bool { + lhs.key == rhs.key && lhs.val == rhs.val + } +} diff --git a/ru/codes/swift/utils/PrintUtil.swift b/ru/codes/swift/utils/PrintUtil.swift new file mode 100644 index 000000000..2305e802f --- /dev/null +++ b/ru/codes/swift/utils/PrintUtil.swift @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public enum PrintUtil { + private class Trunk { + var prev: Trunk? + var str: String + + init(prev: Trunk?, str: String) { + self.prev = prev + self.str = str + } + } + + public static func printLinkedList(head: ListNode) { + var head: ListNode? = head + var list: [String] = [] + while head != nil { + list.append("\(head!.val)") + head = head?.next + } + print(list.joined(separator: " -> ")) + } + + public static func printTree(root: TreeNode?) { + printTree(root: root, prev: nil, isRight: false) + } + + private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { + if root == nil { + return + } + + var prevStr = " " + let trunk = Trunk(prev: prev, str: prevStr) + + printTree(root: root?.right, prev: trunk, isRight: true) + + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev?.str = prevStr + } + + showTrunks(p: trunk) + print(" \(root!.val)") + + if prev != nil { + prev?.str = prevStr + } + trunk.str = " |" + + printTree(root: root?.left, prev: trunk, isRight: false) + } + + private static func showTrunks(p: Trunk?) { + if p == nil { + return + } + + showTrunks(p: p?.prev) + print(p!.str, terminator: "") + } + + public static func printHashMap(map: [K: V]) { + for (key, value) in map { + print("\(key) -> \(value)") + } + } + + public static func printHeap(queue: [Int]) { + print("Массивное представление кучи:", terminator: "") + print(queue) + print("Древовидное представление кучи:") + let root = TreeNode.listToTree(arr: queue) + printTree(root: root) + } + + public static func printMatrix(matrix: [[T]]) { + print("[") + for row in matrix { + print(" \(row),") + } + print("]") + } +} diff --git a/ru/codes/swift/utils/TreeNode.swift b/ru/codes/swift/utils/TreeNode.swift new file mode 100644 index 000000000..298c60aa1 --- /dev/null +++ b/ru/codes/swift/utils/TreeNode.swift @@ -0,0 +1,71 @@ +/** + * File: TreeNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Класс узла двоичного дерева */ +public class TreeNode { + public var val: Int // Значение узла + public var height: Int // Высота узла + public var left: TreeNode? // Ссылка на левый дочерний узел + public var right: TreeNode? // Ссылка на правый дочерний узел + + /* Конструктор */ + public init(x: Int) { + val = x + height = 0 + } + + // Правила кодирования сериализации см.: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // Представление двоичного дерева массивом: + // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + // Представление двоичного дерева связным списком: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* Десериализовать список в двоичное дерево: рекурсия */ + private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { + if i < 0 || i >= arr.count || arr[i] == nil { + return nil + } + let root = TreeNode(x: arr[i]!) + root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) + root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) + return root + } + + /* Десериализовать список в двоичное дерево */ + public static func listToTree(arr: [Int?]) -> TreeNode? { + listToTreeDFS(arr: arr, i: 0) + } + + /* Сериализовать двоичное дерево в список: рекурсия */ + private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { + if root == nil { + return + } + while i >= res.count { + res.append(nil) + } + res[i] = root?.val + treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) + treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) + } + + /* Сериализовать двоичное дерево в список */ + public static func treeToList(root: TreeNode?) -> [Int?] { + var res: [Int?] = [] + treeToListDFS(root: root, i: 0, res: &res) + return res + } +} diff --git a/ru/codes/swift/utils/Vertex.swift b/ru/codes/swift/utils/Vertex.swift new file mode 100644 index 000000000..c86ad8044 --- /dev/null +++ b/ru/codes/swift/utils/Vertex.swift @@ -0,0 +1,32 @@ +/** + * File: Vertex.swift + * Created Time: 2023-02-19 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* Класс вершины */ +public class Vertex: Hashable { + public var val: Int + + public init(val: Int) { + self.val = val + } + + public static func == (lhs: Vertex, rhs: Vertex) -> Bool { + lhs.val == rhs.val + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + } + + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + public static func valsToVets(vals: [Int]) -> [Vertex] { + vals.map { Vertex(val: $0) } + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + public static func vetsToVals(vets: [Vertex]) -> [Int] { + vets.map { $0.val } + } +} diff --git a/ru/codes/typescript/.gitignore b/ru/codes/typescript/.gitignore new file mode 100644 index 000000000..d5f19d89b --- /dev/null +++ b/ru/codes/typescript/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/ru/codes/typescript/.prettierrc b/ru/codes/typescript/.prettierrc new file mode 100644 index 000000000..3f4aa8cb6 --- /dev/null +++ b/ru/codes/typescript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/ru/codes/typescript/chapter_array_and_linkedlist/array.ts b/ru/codes/typescript/chapter_array_and_linkedlist/array.ts new file mode 100644 index 000000000..fdab1e2f3 --- /dev/null +++ b/ru/codes/typescript/chapter_array_and_linkedlist/array.ts @@ -0,0 +1,101 @@ +/** + * File: array.ts + * Created Time: 2022-12-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Случайный доступ к элементу */ +function randomAccess(nums: number[]): number { + // Случайным образом выбрать число из интервала [0, nums.length) + const random_index = Math.floor(Math.random() * nums.length); + // Получить и вернуть случайный элемент + const random_num = nums[random_index]; + return random_num; +} + +/* Увеличить длину массива */ +// Обратите внимание: Array в TypeScript — это динамический массив, его можно расширять напрямую +// Для удобства обучения в этой функции Array рассматривается как массив неизменяемой длины +function extend(nums: number[], enlarge: number): number[] { + // Инициализировать массив увеличенной длины + const res = new Array(nums.length + enlarge).fill(0); + // Скопировать все элементы исходного массива в новый массив + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // Вернуть новый массив после расширения + return res; +} + +/* Вставить элемент num по индексу index в массив */ +function insert(nums: number[], num: number, index: number): void { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; +} + +/* Удалить элемент по индексу index */ +function remove(nums: number[], index: number): void { + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* Обход массива */ +function traverse(nums: number[]): void { + let count = 0; + // Обход массива по индексам + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // Непосредственно обходить элементы массива + for (const num of nums) { + count += num; + } +} + +/* Найти заданный элемент в массиве */ +function find(nums: number[], target: number): number { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) { + return i; + } + } + return -1; +} + +/* Driver Code */ +/* Инициализация массива */ +const arr: number[] = new Array(5).fill(0); +console.log('Массив arr =', arr); +let nums: number[] = [1, 3, 2, 5, 4]; +console.log('Массив nums =', nums); + +/* Случайный доступ */ +let random_num = randomAccess(nums); +console.log('Случайный элемент из nums =', random_num); + +/* Расширение длины */ +nums = extend(nums, 3); +console.log('После расширения длины массива до 8 nums =', nums); + +/* Вставка элемента */ +insert(nums, 6, 3); +console.log('После вставки числа 6 по индексу 3 nums =', nums); + +/* Удаление элемента */ +remove(nums, 2); +console.log('После удаления элемента по индексу 2 nums =', nums); + +/* Обход массива */ +traverse(nums); + +/* Поиск элемента */ +let index = find(nums, 3); +console.log('Поиск элемента 3 в nums: индекс =', index); + +export {}; diff --git a/ru/codes/typescript/chapter_array_and_linkedlist/linked_list.ts b/ru/codes/typescript/chapter_array_and_linkedlist/linked_list.ts new file mode 100644 index 000000000..0c49f7e31 --- /dev/null +++ b/ru/codes/typescript/chapter_array_and_linkedlist/linked_list.ts @@ -0,0 +1,86 @@ +/** + * File: linked_list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { printLinkedList } from '../modules/PrintUtil'; + +/* Вставить узел P после узла n0 в связном списке */ +function insert(n0: ListNode, P: ListNode): void { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* Удалить первый узел после узла n0 в связном списке */ +function remove(n0: ListNode): void { + if (!n0.next) { + return; + } + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* Доступ к узлу связного списка по индексу index */ +function access(head: ListNode | null, index: number): ListNode | null { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* Найти в связном списке первый узел со значением target */ +function find(head: ListNode | null, target: number): number { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* Инициализация связного списка */ +// Инициализация всех узлов +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// Построить ссылки между узлами +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('Инициализированный связный список'); +printLinkedList(n0); + +/* Вставка узла */ +insert(n0, new ListNode(0)); +console.log('Связный список после вставки узла'); +printLinkedList(n0); + +/* Удаление узла */ +remove(n0); +console.log('Связный список после удаления узла'); +printLinkedList(n0); + +/* Доступ к узлу */ +const node = access(n0, 3); +console.log(`Значение узла по индексу 3 в связном списке = ${node?.val}`); + +/* Поиск узла */ +const index = find(n0, 2); +console.log(`Индекс узла со значением 2 в связном списке = ${index}`); + +export {}; diff --git a/ru/codes/typescript/chapter_array_and_linkedlist/list.ts b/ru/codes/typescript/chapter_array_and_linkedlist/list.ts new file mode 100644 index 000000000..788605cc1 --- /dev/null +++ b/ru/codes/typescript/chapter_array_and_linkedlist/list.ts @@ -0,0 +1,59 @@ +/** + * File: list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Инициализация списка */ +const nums: number[] = [1, 3, 2, 5, 4]; +console.log(`Список nums = ${nums}`); + +/* Доступ к элементу */ +const num: number = nums[1]; +console.log(`Элемент по индексу 1: num = ${num}`); + +/* Обновление элемента */ +nums[1] = 0; +console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums}`); + +/* Очистить список */ +nums.length = 0; +console.log(`После очистки списка nums = ${nums}`); + +/* Добавление элемента в конец */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`После добавления элементов nums = ${nums}`); + +/* Вставка элемента в середину */ +nums.splice(3, 0, 6); +console.log(`После вставки числа 6 по индексу 3 nums = ${nums}`); + +/* Удаление элемента */ +nums.splice(3, 1); +console.log(`После удаления элемента по индексу 3 nums = ${nums}`); + +/* Обходить список по индексам */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* Непосредственно обходить элементы списка */ +count = 0; +for (const x of nums) { + count += x; +} + +/* Объединить два списка */ +const nums1: number[] = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`После конкатенации списка nums1 к nums nums = ${nums}`); + +/* Отсортировать список */ +nums.sort((a, b) => a - b); +console.log(`После сортировки списка nums = ${nums}`); + +export {}; diff --git a/ru/codes/typescript/chapter_array_and_linkedlist/my_list.ts b/ru/codes/typescript/chapter_array_and_linkedlist/my_list.ts new file mode 100644 index 000000000..ab222b904 --- /dev/null +++ b/ru/codes/typescript/chapter_array_and_linkedlist/my_list.ts @@ -0,0 +1,141 @@ +/** + * File: my_list.ts + * Created Time: 2022-12-11 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Класс списка */ +class MyList { + private arr: Array; // Массив (для хранения элементов списка) + private _capacity: number = 10; // Вместимость списка + private _size: number = 0; // Длина списка (текущее число элементов) + private extendRatio: number = 2; // Коэффициент увеличения списка при каждом расширении + + /* Конструктор */ + constructor() { + this.arr = new Array(this._capacity); + } + + /* Получить длину списка (текущее число элементов) */ + public size(): number { + return this._size; + } + + /* Получить вместимость списка */ + public capacity(): number { + return this._capacity; + } + + /* Доступ к элементу */ + public get(index: number): number { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); + return this.arr[index]; + } + + /* Обновление элемента */ + public set(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); + this.arr[index] = num; + } + + /* Добавление элемента в конец */ + public add(num: number): void { + // Если длина равна вместимости, требуется расширение + if (this._size === this._capacity) this.extendCapacity(); + // Добавить новый элемент в конец списка + this.arr[this._size] = num; + this._size++; + } + + /* Вставка элемента в середину */ + public insert(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); + // При превышении вместимости по числу элементов запускается расширение + if (this._size === this._capacity) { + this.extendCapacity(); + } + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + for (let j = this._size - 1; j >= index; j--) { + this.arr[j + 1] = this.arr[j]; + } + // Обновить число элементов + this.arr[index] = num; + this._size++; + } + + /* Удаление элемента */ + public remove(index: number): number { + if (index < 0 || index >= this._size) throw new Error('индекс выходит за границы'); + let num = this.arr[index]; + // Сдвинуть все элементы после индекса index на одну позицию вперед + for (let j = index; j < this._size - 1; j++) { + this.arr[j] = this.arr[j + 1]; + } + // Обновить число элементов + this._size--; + // Вернуть удаленный элемент + return num; + } + + /* Расширение списка */ + public extendCapacity(): void { + // Создать новый массив длиной size и скопировать в него исходный массив + this.arr = this.arr.concat( + new Array(this.capacity() * (this.extendRatio - 1)) + ); + // Обновить вместимость списка + this._capacity = this.arr.length; + } + + /* Преобразовать список в массив */ + public toArray(): number[] { + let size = this.size(); + // Преобразовывать только элементы списка в пределах фактической длины + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* Инициализация списка */ +const nums = new MyList(); +/* Добавление элемента в конец */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `Список nums = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` +); + +/* Вставка элемента в середину */ +nums.insert(3, 6); +console.log(`После вставки числа 6 по индексу 3 nums = ${nums.toArray()}`); + +/* Удаление элемента */ +nums.remove(3); +console.log(`После удаления элемента по индексу 3 nums = ${nums.toArray()}`); + +/* Доступ к элементу */ +const num = nums.get(1); +console.log(`Элемент по индексу 1: num = ${num}`); + +/* Обновление элемента */ +nums.set(1, 0); +console.log(`После обновления элемента по индексу 1 до 0 nums = ${nums.toArray()}`); + +/* Проверка механизма расширения */ +for (let i = 0; i < 10; i++) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + nums.add(i); +} +console.log( + `Список nums после увеличения вместимости = ${nums.toArray()}, вместимость = ${nums.capacity()}, длина = ${nums.size()}` +); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/n_queens.ts b/ru/codes/typescript/chapter_backtracking/n_queens.ts new file mode 100644 index 000000000..d62789941 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/n_queens.ts @@ -0,0 +1,65 @@ +/** + * File: n_queens.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Алгоритм бэктрекинга: n ферзей */ +function backtrack( + row: number, + n: number, + state: string[][], + res: string[][][], + cols: boolean[], + diags1: boolean[], + diags2: boolean[] +): void { + // Когда все строки уже обработаны, записать решение + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // Обойти все столбцы + for (let col = 0; col < n; col++) { + // Вычислить главную и побочную диагонали, соответствующие этой клетке + const diag1 = row - col + n - 1; + const diag2 = row + col; + // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // Попытка: поставить ферзя в эту клетку + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // Перейти к размещению следующей строки + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // Откат: восстановить эту клетку как пустую + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* Решить задачу о n ферзях */ +function nQueens(n: number): string[][][] { + // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // Отмечать, есть ли ферзь в столбце + const diags1 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на главной диагонали + const diags2 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на побочной диагонали + const res: string[][][] = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`Размер входной доски = ${n}`); +console.log(`Количество способов расстановки ферзей: ${res.length}`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/permutations_i.ts b/ru/codes/typescript/chapter_backtracking/permutations_i.ts new file mode 100644 index 000000000..8adf47ab0 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/permutations_i.ts @@ -0,0 +1,49 @@ +/** + * File: permutations_i.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки I */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // Когда длина состояния равна числу элементов, записать решение + if (state.length === choices.length) { + res.push([...state]); + return; + } + // Перебор всех вариантов выбора + choices.forEach((choice, i) => { + // Отсечение: нельзя выбирать один и тот же элемент повторно + if (!selected[i]) { + // Попытка: сделать выбор и обновить состояние + selected[i] = true; + state.push(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop(); + } + }); +} + +/* Все перестановки I */ +function permutationsI(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 3]; +const res: number[][] = permutationsI(nums); + +console.log(`Входной массив nums = ${JSON.stringify(nums)}`); +console.log(`Все перестановки res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/permutations_ii.ts b/ru/codes/typescript/chapter_backtracking/permutations_ii.ts new file mode 100644 index 000000000..a2606dfe3 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/permutations_ii.ts @@ -0,0 +1,51 @@ +/** + * File: permutations_ii.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Алгоритм бэктрекинга: все перестановки II */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // Когда длина состояния равна числу элементов, записать решение + if (state.length === choices.length) { + res.push([...state]); + return; + } + // Перебор всех вариантов выбора + const duplicated = new Set(); + choices.forEach((choice, i) => { + // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы + if (!selected[i] && !duplicated.has(choice)) { + // Попытка: сделать выбор и обновить состояние + duplicated.add(choice); // Записать значения уже выбранных элементов + selected[i] = true; + state.push(choice); + // Перейти к следующему выбору + backtrack(state, choices, selected, res); + // Откат: отменить выбор и восстановить предыдущее состояние + selected[i] = false; + state.pop(); + } + }); +} + +/* Все перестановки II */ +function permutationsII(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 2]; +const res: number[][] = permutationsII(nums); + +console.log(`Входной массив nums = ${JSON.stringify(nums)}`); +console.log(`Все перестановки res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts b/ru/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts new file mode 100644 index 000000000..db504691d --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts @@ -0,0 +1,36 @@ +/** + * File: preorder_traversal_i_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Предварительный обход: пример 1 */ +function preOrder(root: TreeNode | null, res: TreeNode[]): void { + if (root === null) { + return; + } + if (root.val === 7) { + // Записать решение + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Предварительный обход +const res: TreeNode[] = []; +preOrder(root, res); + +console.log('\nВывести все узлы со значением 7'); +console.log(res.map((node) => node.val)); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts b/ru/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts new file mode 100644 index 000000000..731d576fe --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Предварительный обход: пример 2 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + if (root === null) { + return; + } + // Попытка + path.push(root); + if (root.val === 7) { + // Записать решение + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // Откат + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Предварительный обход +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\nВывести все пути от корня к узлу 7'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts b/ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts new file mode 100644 index 000000000..5a7a29507 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts @@ -0,0 +1,48 @@ +/** + * File: preorder_traversal_iii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Предварительный обход: пример 3 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + // Отсечение + if (root === null || root.val === 3) { + return; + } + // Попытка + path.push(root); + if (root.val === 7) { + // Записать решение + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // Откат + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Предварительный обход +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\nВывести все пути от корня к узлу 7, не содержащие узлов со значением 3'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts b/ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts new file mode 100644 index 000000000..b6a13ae84 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts @@ -0,0 +1,75 @@ +/** + * File: preorder_traversal_iii_template.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Проверить, является ли текущее состояние решением */ +function isSolution(state: TreeNode[]): boolean { + return state && state[state.length - 1]?.val === 7; +} + +/* Записать решение */ +function recordSolution(state: TreeNode[], res: TreeNode[][]): void { + res.push([...state]); +} + +/* Проверить, допустим ли этот выбор в текущем состоянии */ +function isValid(state: TreeNode[], choice: TreeNode): boolean { + return choice !== null && choice.val !== 3; +} + +/* Обновить состояние */ +function makeChoice(state: TreeNode[], choice: TreeNode): void { + state.push(choice); +} + +/* Восстановить состояние */ +function undoChoice(state: TreeNode[]): void { + state.pop(); +} + +/* Алгоритм бэктрекинга: пример 3 */ +function backtrack( + state: TreeNode[], + choices: TreeNode[], + res: TreeNode[][] +): void { + // Проверить, является ли текущее состояние решением + if (isSolution(state)) { + // Записать решение + recordSolution(state, res); + } + // Перебор всех вариантов выбора + for (const choice of choices) { + // Отсечение: проверить допустимость выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + // Перейти к следующему выбору + backtrack(state, [choice.left, choice.right], res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева'); +printTree(root); + +// Алгоритм бэктрекинга +const res: TreeNode[][] = []; +backtrack([], [root], res); + +console.log('\nВсе пути от корня к узлу 7, не содержащие узлов со значением 3'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/subset_sum_i.ts b/ru/codes/typescript/chapter_backtracking/subset_sum_i.ts new file mode 100644 index 000000000..6b7ec7ce3 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/subset_sum_i.ts @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // Если сумма подмножества равна target, записать решение + if (target === 0) { + res.push([...state]); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + for (let i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Попытка: сделать выбор и обновить target и start + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств I */ +function subsetSumI(nums: number[], target: number): number[][] { + const state = []; // Состояние (подмножество) + nums.sort((a, b) => a - b); // Отсортировать nums + const start = 0; // Стартовая вершина обхода + const res = []; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts b/ru/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts new file mode 100644 index 000000000..c81c54780 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts @@ -0,0 +1,52 @@ +/** + * File: subset_sum_i_naive.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств I */ +function backtrack( + state: number[], + target: number, + total: number, + choices: number[], + res: number[][] +): void { + // Если сумма подмножества равна target, записать решение + if (total === target) { + res.push([...state]); + return; + } + // Перебор всех вариантов выбора + for (let i = 0; i < choices.length; i++) { + // Отсечение: если сумма подмножества превышает target, пропустить этот выбор + if (total + choices[i] > target) { + continue; + } + // Попытка: сделать выбор и обновить элемент и total + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target, total + choices[i], choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств I (с повторяющимися подмножествами) */ +function subsetSumINaive(nums: number[], target: number): number[][] { + const state = []; // Состояние (подмножество) + const total = 0; // Сумма подмножеств + const res = []; // Список результатов (список подмножеств) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); +console.log('Обратите внимание: результат этого метода содержит повторяющиеся множества'); + +export {}; diff --git a/ru/codes/typescript/chapter_backtracking/subset_sum_ii.ts b/ru/codes/typescript/chapter_backtracking/subset_sum_ii.ts new file mode 100644 index 000000000..05abc1251 --- /dev/null +++ b/ru/codes/typescript/chapter_backtracking/subset_sum_ii.ts @@ -0,0 +1,59 @@ +/** + * File: subset_sum_ii.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Алгоритм бэктрекинга: сумма подмножеств II */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // Если сумма подмножества равна target, записать решение + if (target === 0) { + res.push([...state]); + return; + } + // Обойти все варианты выбора + // Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств + // Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента + for (let i = start; i < choices.length; i++) { + // Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл + // Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target + if (target - choices[i] < 0) { + break; + } + // Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // Попытка: сделать выбор и обновить target и start + state.push(choices[i]); + // Перейти к следующему выбору + backtrack(state, target - choices[i], choices, i + 1, res); + // Откат: отменить выбор и восстановить предыдущее состояние + state.pop(); + } +} + +/* Решить задачу суммы подмножеств II */ +function subsetSumII(nums: number[], target: number): number[][] { + const state = []; // Состояние (подмножество) + nums.sort((a, b) => a - b); // Отсортировать nums + const start = 0; // Стартовая вершина обхода + const res = []; // Список результатов (список подмножеств) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`Входной массив nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`Все подмножества с суммой ${target}: res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ru/codes/typescript/chapter_computational_complexity/iteration.ts b/ru/codes/typescript/chapter_computational_complexity/iteration.ts new file mode 100644 index 000000000..8667c710c --- /dev/null +++ b/ru/codes/typescript/chapter_computational_complexity/iteration.ts @@ -0,0 +1,72 @@ +/** + * File: iteration.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Цикл for */ +function forLoop(n: number): number { + let res = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* Цикл while */ +function whileLoop(n: number): number { + let res = 0; + let i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // Обновить условную переменную + } + return res; +} + +/* Цикл while (двойное обновление) */ +function whileLoopII(n: number): number { + let res = 0; + let i = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) { + res += i; + // Обновить условную переменную + i++; + i *= 2; + } + return res; +} + +/* Двойной цикл for */ +function nestedForLoop(n: number): string { + let res = ''; + // Цикл по i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // Цикл по j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = forLoop(n); +console.log(`Результат суммирования в цикле for res = ${res}`); + +res = whileLoop(n); +console.log(`Результат суммирования в цикле while res = ${res}`); + +res = whileLoopII(n); +console.log(`Результат суммирования в цикле while (двойное обновление) res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`Результат обхода в двойном цикле for ${resStr}`); + +export {}; diff --git a/ru/codes/typescript/chapter_computational_complexity/recursion.ts b/ru/codes/typescript/chapter_computational_complexity/recursion.ts new file mode 100644 index 000000000..b6e0e2b16 --- /dev/null +++ b/ru/codes/typescript/chapter_computational_complexity/recursion.ts @@ -0,0 +1,70 @@ +/** + * File: recursion.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Рекурсия */ +function recur(n: number): number { + // Условие завершения + if (n === 1) return 1; + // Рекурсия: рекурсивный вызов + const res = recur(n - 1); + // Возврат: вернуть результат + return n + res; +} + +/* Имитация рекурсии итерацией */ +function forLoopRecur(n: number): number { + // Использовать явный стек для имитации системного стека вызовов + const stack: number[] = []; + let res: number = 0; + // Рекурсия: рекурсивный вызов + for (let i = n; i > 0; i--) { + // Имитировать «рекурсию» с помощью операции помещения в стек + stack.push(i); + } + // Возврат: вернуть результат + while (stack.length) { + // Имитировать «возврат» с помощью операции извлечения из стека + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* Хвостовая рекурсия */ +function tailRecur(n: number, res: number): number { + // Условие завершения + if (n === 0) return res; + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); +} + +/* Последовательность Фибоначчи: рекурсия */ +function fib(n: number): number { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = recur(n); +console.log(`Результат суммирования в рекурсивной функции res = ${res}`); + +res = forLoopRecur(n); +console.log(`Результат суммирования при имитации рекурсии итерацией res = ${res}`); + +res = tailRecur(n, 0); +console.log(`Результат суммирования в хвостовой рекурсии res = ${res}`); + +res = fib(n); +console.log(`Член последовательности Фибоначчи с номером ${n} = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_computational_complexity/space_complexity.ts b/ru/codes/typescript/chapter_computational_complexity/space_complexity.ts new file mode 100644 index 000000000..1d453cb1f --- /dev/null +++ b/ru/codes/typescript/chapter_computational_complexity/space_complexity.ts @@ -0,0 +1,103 @@ +/** + * File: space_complexity.ts + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Функция */ +function constFunc(): number { + // Выполнить некоторые операции + return 0; +} + +/* Постоянная сложность */ +function constant(n: number): void { + // Константы, переменные и объекты занимают O(1) памяти + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // Переменные в цикле занимают O(1) памяти + for (let i = 0; i < n; i++) { + const c = 0; + } + // Функции в цикле занимают O(1) памяти + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* Линейная сложность */ +function linear(n: number): void { + // Массив длины n занимает O(n) памяти + const nums = new Array(n); + // Список длины n занимает O(n) памяти + const nodes: ListNode[] = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // Хеш-таблица длины n занимает O(n) памяти + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* Линейная сложность (рекурсивная реализация) */ +function linearRecur(n: number): void { + console.log(`Рекурсия n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* Квадратичная сложность */ +function quadratic(n: number): void { + // Матрица занимает O(n^2) памяти + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // Двумерный список занимает O(n^2) памяти + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* Квадратичная сложность (рекурсивная реализация) */ +function quadraticRecur(n: number): number { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`В рекурсии n = ${n} длина nums = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* Экспоненциальная сложность (построение полного двоичного дерева) */ +function buildTree(n: number): TreeNode | null { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// Постоянная сложность +constant(n); +// Линейная сложность +linear(n); +linearRecur(n); +// Квадратичная сложность +quadratic(n); +quadraticRecur(n); +// Экспоненциальная сложность +const root = buildTree(n); +printTree(root); diff --git a/ru/codes/typescript/chapter_computational_complexity/time_complexity.ts b/ru/codes/typescript/chapter_computational_complexity/time_complexity.ts new file mode 100644 index 000000000..54b8501c2 --- /dev/null +++ b/ru/codes/typescript/chapter_computational_complexity/time_complexity.ts @@ -0,0 +1,157 @@ +/** + * File: time_complexity.ts + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* Постоянная сложность */ +function constant(n: number): number { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* Линейная сложность */ +function linear(n: number): number { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* Линейная сложность (обход массива) */ +function arrayTraversal(nums: number[]): number { + let count = 0; + // Число итераций пропорционально длине массива + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* Квадратичная сложность */ +function quadratic(n: number): number { + let count = 0; + // Число итераций квадратично зависит от размера данных n + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* Квадратичная сложность (пузырьковая сортировка) */ +function bubbleSort(nums: number[]): number { + let count = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; +} + +/* Экспоненциальная сложность (итеративная реализация) */ +function exponential(n: number): number { + let count = 0, + base = 1; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* Экспоненциальная сложность (рекурсивная реализация) */ +function expRecur(n: number): number { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* Логарифмическая сложность (итеративная реализация) */ +function logarithmic(n: number): number { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* Логарифмическая сложность (рекурсивная реализация) */ +function logRecur(n: number): number { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* Линейно-логарифмическая сложность */ +function linearLogRecur(n: number): number { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* Факториальная сложность (рекурсивная реализация) */ +function factorialRecur(n: number): number { + if (n === 0) return 1; + let count = 0; + // Из одного получается n + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях +const n = 8; +console.log('Размер входных данных n = ' + n); + +let count = constant(n); +console.log('Число операций постоянной сложности = ' + count); + +count = linear(n); +console.log('Число операций линейной сложности = ' + count); +count = arrayTraversal(new Array(n)); +console.log('Число операций линейной сложности (обход массива) = ' + count); + +count = quadratic(n); +console.log('Число операций квадратичной сложности = ' + count); +var nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('Число операций квадратичной сложности (пузырьковая сортировка) = ' + count); + +count = exponential(n); +console.log('Число операций экспоненциальной сложности (итеративная реализация) = ' + count); +count = expRecur(n); +console.log('Число операций экспоненциальной сложности (рекурсивная реализация) = ' + count); + +count = logarithmic(n); +console.log('Число операций логарифмической сложности (итеративная реализация) = ' + count); +count = logRecur(n); +console.log('Число операций логарифмической сложности (рекурсивная реализация) = ' + count); + +count = linearLogRecur(n); +console.log('Число операций линейно-логарифмической сложности (рекурсивная реализация) = ' + count); + +count = factorialRecur(n); +console.log('Число операций факториальной сложности (рекурсивная реализация) = ' + count); + +export {}; diff --git a/ru/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts b/ru/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts new file mode 100644 index 000000000..a0cda6a4d --- /dev/null +++ b/ru/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.ts + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* Создать массив с элементами { 1, 2, ..., n } в случайном порядке */ +function randomNumbers(n: number): number[] { + const nums = Array(n); + // Создать массив nums = { 1, 2, 3, ..., n } + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // Случайно перемешать элементы массива + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* Найти индекс числа 1 в массиве nums */ +function findOne(nums: number[]): number { + for (let i = 0; i < nums.length; i++) { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\nПосле перемешивания массива [ 1, 2, ..., n ] = [' + nums.join(', ') + ']'); + console.log('Индекс числа 1 = ' + index); +} + +export {}; diff --git a/ru/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts b/ru/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts new file mode 100644 index 000000000..6cd78cd04 --- /dev/null +++ b/ru/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts @@ -0,0 +1,41 @@ +/** + * File: binary_search_recur.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Бинарный поиск: задача f(i, j) */ +function dfs(nums: number[], target: number, i: number, j: number): number { + // Если интервал пуст, целевой элемент отсутствует, вернуть -1 + if (i > j) { + return -1; + } + // Вычислить индекс середины m + const m = i + ((j - i) >> 1); + if (nums[m] < target) { + // Рекурсивная подзадача f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // Рекурсивная подзадача f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } +} + +/* Бинарный поиск */ +function binarySearch(nums: number[], target: number): number { + const n = nums.length; + // Решить задачу f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// Бинарный поиск (двусторонне замкнутый интервал) +const index = binarySearch(nums, target); +console.log(`Индекс целевого элемента 6 = ${index}`); + +export {}; diff --git a/ru/codes/typescript/chapter_divide_and_conquer/build_tree.ts b/ru/codes/typescript/chapter_divide_and_conquer/build_tree.ts new file mode 100644 index 000000000..f02a0131e --- /dev/null +++ b/ru/codes/typescript/chapter_divide_and_conquer/build_tree.ts @@ -0,0 +1,50 @@ +/** + * File: build_tree.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { printTree } from '../modules/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; + +/* Построить двоичное дерево: разделяй и властвуй */ +function dfs( + preorder: number[], + inorderMap: Map, + i: number, + l: number, + r: number +): TreeNode | null { + // Завершить при пустом диапазоне поддерева + if (r - l < 0) return null; + // Инициализировать корневой узел + const root: TreeNode = new TreeNode(preorder[i]); + // Найти m, чтобы разделить левое и правое поддеревья + const m = inorderMap.get(preorder[i]); + // Подзадача: построить левое поддерево + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // Подзадача: построить правое поддерево + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // Вернуть корневой узел + return root; +} + +/* Построить двоичное дерево */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('Предварительный обход = ' + JSON.stringify(preorder)); +console.log('Симметричный обход = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('Построенное двоичное дерево:'); +printTree(root); diff --git a/ru/codes/typescript/chapter_divide_and_conquer/hanota.ts b/ru/codes/typescript/chapter_divide_and_conquer/hanota.ts new file mode 100644 index 000000000..e462c8591 --- /dev/null +++ b/ru/codes/typescript/chapter_divide_and_conquer/hanota.ts @@ -0,0 +1,52 @@ +/** + * File: hanota.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Переместить один диск */ +function move(src: number[], tar: number[]): void { + // Снять диск с вершины src + const pan = src.pop(); + // Положить диск на вершину tar + tar.push(pan); +} + +/* Решить задачу Ханойской башни f(i) */ +function dfs(i: number, src: number[], buf: number[], tar: number[]): void { + // Если в src остался только один диск, сразу переместить его в tar + if (i === 1) { + move(src, tar); + return; + } + // Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar + dfs(i - 1, src, tar, buf); + // Подзадача f(1): переместить оставшийся один диск из src в tar + move(src, tar); + // Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src + dfs(i - 1, buf, src, tar); +} + +/* Решить задачу Ханойской башни */ +function solveHanota(A: number[], B: number[], C: number[]): void { + const n = A.length; + // Переместить верхние n дисков из A в C с помощью B + dfs(n, A, B, C); +} + +/* Driver Code */ +// Хвост списка соответствует вершине столбца +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('Начальное состояние:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('После завершения перемещения дисков:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts new file mode 100644 index 000000000..4e3e700e4 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_backtrack.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Бэктрекинг */ +function backtrack( + choices: number[], + state: number, + n: number, + res: Map<0, any> +): void { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state === n) res.set(0, res.get(0) + 1); + // Перебор всех вариантов выбора + for (const choice of choices) { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) continue; + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } +} + +/* Подъем по лестнице: бэктрекинг */ +function climbingStairsBacktrack(n: number): number { + const choices = [1, 2]; // Можно подняться на 1 или 2 ступени + const state = 0; // Начать подъем с 0-й ступени + const res = new Map(); + res.set(0, 0); // Использовать res[0] для хранения числа решений + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts new file mode 100644 index 000000000..4eb56c636 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_constraint_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Подъем по лестнице с ограничениями: динамическое программирование */ +function climbingStairsConstraintDP(n: number): number { + if (n === 1 || n === 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + const dp = Array.from({ length: n + 1 }, () => new Array(3)); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts new file mode 100644 index 000000000..ea643c536 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts @@ -0,0 +1,26 @@ +/** + * File: climbing_stairs_dfs.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Поиск */ +function dfs(i: number): number { + // dp[1] и dp[2] уже известны, вернуть их + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* Подъем по лестнице: поиск */ +function climbingStairsDFS(n: number): number { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts new file mode 100644 index 000000000..251033cc8 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs_mem.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Поиск с мемоизацией */ +function dfs(i: number, mem: number[]): number { + // dp[1] и dp[2] уже известны, вернуть их + if (i === 1 || i === 2) return i; + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; +} + +/* Подъем по лестнице: поиск с мемоизацией */ +function climbingStairsDFSMem(n: number): number { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts new file mode 100644 index 000000000..b0be6684a --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_dp.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Подъем по лестнице: динамическое программирование */ +function climbingStairsDP(n: number): number { + if (n === 1 || n === 2) return n; + // Инициализация таблицы dp для хранения решений подзадач + const dp = new Array(n + 1).fill(-1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* Подъем по лестнице: динамическое программирование с оптимизацией памяти */ +function climbingStairsDPComp(n: number): number { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); +res = climbingStairsDPComp(n); +console.log(`Количество способов подняться по лестнице из ${n} ступеней = ${res} вариантов`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/coin_change.ts b/ru/codes/typescript/chapter_dynamic_programming/coin_change.ts new file mode 100644 index 000000000..d64ef6bcc --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -0,0 +1,68 @@ +/** + * File: coin_change.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Размен монет: динамическое программирование */ +function coinChangeDP(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // Переход состояний: первая строка и первый столбец + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // Переход состояний: остальные строки и столбцы + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* Размен монет: динамическое программирование с оптимизацией памяти */ +function coinChangeDPComp(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // Инициализация таблицы dp + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// Динамическое программирование +let res = coinChangeDP(coins, amt); +console.log(`Минимальное число монет для набора целевой суммы = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = coinChangeDPComp(coins, amt); +console.log(`Минимальное число монет для набора целевой суммы = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/ru/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts new file mode 100644 index 000000000..745976be9 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Размен монет II: динамическое программирование */ +function coinChangeIIDP(coins: Array, amt: number): number { + const n = coins.length; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // Инициализация первого столбца + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* Размен монет II: динамическое программирование с оптимизацией памяти */ +function coinChangeIIDPComp(coins: Array, amt: number): number { + const n = coins.length; + // Инициализация таблицы dp + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Сумма двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// Динамическое программирование +let res = coinChangeIIDP(coins, amt); +console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = coinChangeIIDPComp(coins, amt); +console.log(`Количество комбинаций монет для набора целевой суммы = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/ru/codes/typescript/chapter_dynamic_programming/edit_distance.ts new file mode 100644 index 000000000..9028d209a --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -0,0 +1,148 @@ +/** + * File: edit_distance.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Редакционное расстояние: полный перебор */ +function editDistanceDFS(s: string, t: string, i: number, j: number): number { + // Если s и t пусты, вернуть 0 + if (i === 0 && j === 0) return 0; + + // Если s пусто, вернуть длину t + if (i === 0) return j; + + // Если t пусто, вернуть длину s + if (j === 0) return i; + + // Если два символа равны, сразу пропустить их + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return Math.min(insert, del, replace) + 1; +} + +/* Редакционное расстояние: поиск с мемоизацией */ +function editDistanceDFSMem( + s: string, + t: string, + mem: Array>, + i: number, + j: number +): number { + // Если s и t пусты, вернуть 0 + if (i === 0 && j === 0) return 0; + + // Если s пусто, вернуть длину t + if (i === 0) return j; + + // Если t пусто, вернуть длину s + if (j === 0) return i; + + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] !== -1) return mem[i][j]; + + // Если два символа равны, сразу пропустить их + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* Редакционное расстояние: динамическое программирование */ +function editDistanceDP(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => 0) + ); + // Переход состояний: первая строка и первый столбец + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // Переход состояний: остальные строки и столбцы + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* Редакционное расстояние: динамическое программирование с оптимизацией памяти */ +function editDistanceDPComp(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // Переход состояний: первая строка + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // Переход состояний: остальные строки + for (let i = 1; i <= n; i++) { + // Переход состояний: первый столбец + let leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = i; + // Переход состояний: остальные столбцы + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; +} + +/* Driver Code */ +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// Полный перебор +let res = editDistanceDFS(s, t, n, m); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +// Поиск с мемоизацией +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => -1) +); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +// Динамическое программирование +res = editDistanceDP(s, t); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +// Динамическое программирование с оптимизацией памяти +res = editDistanceDPComp(s, t); +console.log(`Чтобы преобразовать ${s} в ${t}, нужно минимум ${res} шагов`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/knapsack.ts b/ru/codes/typescript/chapter_dynamic_programming/knapsack.ts new file mode 100644 index 000000000..5427cf3d6 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -0,0 +1,134 @@ +/** + * File: knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Рюкзак 0-1: полный перебор */ +function knapsackDFS( + wgt: Array, + val: Array, + i: number, + c: number +): number { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i === 0 || c === 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return Math.max(no, yes); +} + +/* Рюкзак 0-1: поиск с мемоизацией */ +function knapsackDFSMem( + wgt: Array, + val: Array, + mem: Array>, + i: number, + c: number +): number { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i === 0 || c === 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* Рюкзак 0-1: динамическое программирование */ +function knapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* Рюкзак 0-1: динамическое программирование с оптимизацией памяти */ +function knapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array(cap + 1).fill(0); + // Переход состояний + for (let i = 1; i <= n; i++) { + // Обход в обратном порядке + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// Полный перебор +let res = knapsackDFS(wgt, val, n, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Поиск с мемоизацией +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Динамическое программирование +res = knapsackDP(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = knapsackDPComp(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/ru/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts new file mode 100644 index 000000000..8c02b92cc --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Минимальная стоимость подъема по лестнице: динамическое программирование */ +function minCostClimbingStairsDP(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // Инициализация таблицы dp для хранения решений подзадач + const dp = new Array(n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти */ +function minCostClimbingStairsDPComp(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log(`Стоимость подъема по ступеням: ${cost}`); + +let res = minCostClimbingStairsDP(cost); +console.log(`Минимальная стоимость подъема по лестнице = ${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`Минимальная стоимость подъема по лестнице = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/ru/codes/typescript/chapter_dynamic_programming/min_path_sum.ts new file mode 100644 index 000000000..202cef4e5 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Минимальная сумма пути: полный перебор */ +function minPathSumDFS( + grid: Array>, + i: number, + j: number +): number { + // Если это верхняя левая ячейка, завершить поиск + if (i === 0 && j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Infinity; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return Math.min(left, up) + grid[i][j]; +} + +/* Минимальная сумма пути: поиск с мемоизацией */ +function minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: number, + j: number +): number { + // Если это верхняя левая ячейка, завершить поиск + if (i === 0 && j === 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 || j < 0) { + return Infinity; + } + // Если запись уже есть, вернуть сразу + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Минимальная стоимость пути для левой и верхней ячеек + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* Минимальная сумма пути: динамическое программирование */ +function minPathSumDP(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // Инициализация таблицы dp + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (let i = 1; i < n; i++) { + for (let j: number = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* Минимальная сумма пути: динамическое программирование с оптимизацией памяти */ +function minPathSumDPComp(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // Инициализация таблицы dp + const dp = new Array(m); + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (let i = 1; i < n; i++) { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + // Переход состояний: остальные столбцы + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// Полный перебор +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +// Поиск с мемоизацией +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +// Динамическое программирование +res = minPathSumDP(grid); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = minPathSumDPComp(grid); +console.log(`Минимальная сумма пути из левого верхнего угла в правый нижний = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/ru/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts new file mode 100644 index 000000000..965c309f3 --- /dev/null +++ b/ru/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -0,0 +1,73 @@ +/** + * File: unbounded_knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Полный рюкзак: динамическое программирование */ +function unboundedKnapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* Полный рюкзак: динамическое программирование с оптимизацией памяти */ +function unboundedKnapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // Инициализация таблицы dp + const dp = Array.from({ length: cap + 1 }, () => 0); + // Переход состояний + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// Динамическое программирование +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +// Динамическое программирование с оптимизацией памяти +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_graph/graph_adjacency_list.ts b/ru/codes/typescript/chapter_graph/graph_adjacency_list.ts new file mode 100644 index 000000000..0fd586395 --- /dev/null +++ b/ru/codes/typescript/chapter_graph/graph_adjacency_list.ts @@ -0,0 +1,140 @@ +/** + * File: graph_adjacency_list.ts + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { Vertex } from '../modules/Vertex'; + +/* Класс неориентированного графа на основе списка смежности */ +class GraphAdjList { + // Список смежности, где key — вершина, а value — все смежные ей вершины + adjList: Map; + + /* Конструктор */ + constructor(edges: Vertex[][]) { + this.adjList = new Map(); + // Добавить все вершины и ребра + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* Получить число вершин */ + size(): number { + return this.adjList.size; + } + + /* Добавление ребра */ + addEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // Добавить ребро vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* Удаление ребра */ + removeEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 || + this.adjList.get(vet1).indexOf(vet2) === -1 + ) { + throw new Error('Illegal Argument Exception'); + } + // Удалить ребро vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* Добавление вершины */ + addVertex(vet: Vertex): void { + if (this.adjList.has(vet)) return; + // Добавить новый список в список смежности + this.adjList.set(vet, []); + } + + /* Удаление вершины */ + removeVertex(vet: Vertex): void { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // Удалить из списка смежности список, соответствующий вершине vet + this.adjList.delete(vet); + // Обойти списки других вершин и удалить все ребра, содержащие vet + for (const set of this.adjList.values()) { + const index: number = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* Вывести список смежности */ + print(): void { + console.log('Список смежности ='); + for (const [key, value] of this.adjList.entries()) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* Инициализация неориентированного графа */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\nПосле инициализации граф имеет вид'); + graph.print(); + + /* Добавление ребра */ + // Вершины 1 и 2 соответствуют v0 и v2 + graph.addEdge(v0, v2); + console.log('\nПосле добавления ребра 1-2 граф имеет вид'); + graph.print(); + + /* Удаление ребра */ + // Вершины 1 и 3 соответствуют v0 и v1 + graph.removeEdge(v0, v1); + console.log('\nПосле удаления ребра 1-3 граф имеет вид'); + graph.print(); + + /* Добавление вершины */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\nПосле добавления вершины 6 граф имеет вид'); + graph.print(); + + /* Удаление вершины */ + // Вершина 3 соответствует v1 + graph.removeVertex(v1); + console.log('\nПосле удаления вершины 3 граф имеет вид'); + graph.print(); +} + +export { GraphAdjList }; diff --git a/ru/codes/typescript/chapter_graph/graph_adjacency_matrix.ts b/ru/codes/typescript/chapter_graph/graph_adjacency_matrix.ts new file mode 100644 index 000000000..4142fa84d --- /dev/null +++ b/ru/codes/typescript/chapter_graph/graph_adjacency_matrix.ts @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.ts + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Класс неориентированного графа на основе матрицы смежности */ +class GraphAdjMat { + vertices: number[]; // Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин» + adjMat: number[][]; // Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин» + + /* Конструктор */ + constructor(vertices: number[], edges: number[][]) { + this.vertices = []; + this.adjMat = []; + // Добавление вершины + for (const val of vertices) { + this.addVertex(val); + } + // Добавить ребра + // Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* Получить число вершин */ + size(): number { + return this.vertices.length; + } + + /* Добавление вершины */ + addVertex(val: number): void { + const n: number = this.size(); + // Добавить значение новой вершины в список вершин + this.vertices.push(val); + // Добавить строку в матрицу смежности + const newRow: number[] = []; + for (let j: number = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // Добавить столбец в матрицу смежности + for (const row of this.adjMat) { + row.push(0); + } + } + + /* Удаление вершины */ + removeVertex(index: number): void { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // Удалить вершину с индексом index из списка вершин + this.vertices.splice(index, 1); + + // Удалить строку с индексом index из матрицы смежности + this.adjMat.splice(index, 1); + // Удалить столбец с индексом index из матрицы смежности + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* Добавление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + addEdge(i: number, j: number): void { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* Удаление ребра */ + // Параметры i и j соответствуют индексам элементов vertices + removeEdge(i: number, j: number): void { + // Обработка выхода индекса за границы и случая равенства + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* Вывести матрицу смежности */ + print(): void { + console.log('Список вершин = ', this.vertices); + console.log('Матрица смежности =', this.adjMat); + } +} + +/* Driver Code */ +/* Инициализация неориентированного графа */ +// Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices +const vertices: number[] = [1, 3, 2, 5, 4]; +const edges: number[][] = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); +console.log('\nПосле инициализации граф имеет вид'); +graph.print(); + +/* Добавление ребра */ +// Индексы вершин 1 и 2 равны 0 и 2 соответственно +graph.addEdge(0, 2); +console.log('\nПосле добавления ребра 1-2 граф имеет вид'); +graph.print(); + +/* Удаление ребра */ +// Индексы вершин 1 и 3 равны 0 и 1 соответственно +graph.removeEdge(0, 1); +console.log('\nПосле удаления ребра 1-3 граф имеет вид'); +graph.print(); + +/* Добавление вершины */ +graph.addVertex(6); +console.log('\nПосле добавления вершины 6 граф имеет вид'); +graph.print(); + +/* Удаление вершины */ +// Индекс вершины 3 равен 1 +graph.removeVertex(1); +console.log('\nПосле удаления вершины 3 граф имеет вид'); +graph.print(); + +export {}; diff --git a/ru/codes/typescript/chapter_graph/graph_bfs.ts b/ru/codes/typescript/chapter_graph/graph_bfs.ts new file mode 100644 index 000000000..57a4f73b9 --- /dev/null +++ b/ru/codes/typescript/chapter_graph/graph_bfs.ts @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { GraphAdjList } from './graph_adjacency_list'; +import { Vertex } from '../modules/Vertex'; + +/* Обход в ширину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // Последовательность обхода вершин + const res: Vertex[] = []; + // Хеш-множество для хранения уже посещенных вершин + const visited: Set = new Set(); + visited.add(startVet); + // Очередь используется для реализации BFS + const que = [startVet]; + // Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины + while (que.length) { + const vet = que.shift(); // Извлечь головную вершину из очереди + res.push(vet); // Отметить посещенную вершину + // Обойти все смежные вершины данной вершины + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + que.push(adjVet); // Помещать в очередь только непосещенные вершины + visited.add(adjVet); // Отметить эту вершину как посещенную + } + } + // Вернуть последовательность обхода вершин + return res; +} + +/* Driver Code */ +/* Инициализация неориентированного графа */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], +]; +const graph = new GraphAdjList(edges); +console.log('\nПосле инициализации граф имеет вид'); +graph.print(); + +/* Обход в ширину */ +const res = graphBFS(graph, v[0]); +console.log('\nПоследовательность вершин при обходе в ширину (BFS)'); +console.log(Vertex.vetsToVals(res)); diff --git a/ru/codes/typescript/chapter_graph/graph_dfs.ts b/ru/codes/typescript/chapter_graph/graph_dfs.ts new file mode 100644 index 000000000..d0a097122 --- /dev/null +++ b/ru/codes/typescript/chapter_graph/graph_dfs.ts @@ -0,0 +1,58 @@ +/** + * File: graph_dfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { Vertex } from '../modules/Vertex'; +import { GraphAdjList } from './graph_adjacency_list'; + +/* Вспомогательная функция обхода в глубину */ +function dfs( + graph: GraphAdjList, + visited: Set, + res: Vertex[], + vet: Vertex +): void { + res.push(vet); // Отметить посещенную вершину + visited.add(vet); // Отметить эту вершину как посещенную + // Обойти все смежные вершины данной вершины + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // Пропустить уже посещенную вершину + } + // Рекурсивно обходить смежные вершины + dfs(graph, visited, res, adjVet); + } +} + +/* Обход в глубину */ +// Использовать список смежности для представления графа, чтобы получить все смежные вершины заданной вершины +function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // Последовательность обхода вершин + const res: Vertex[] = []; + // Хеш-множество для хранения уже посещенных вершин + const visited: Set = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* Инициализация неориентированного графа */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\nПосле инициализации граф имеет вид'); +graph.print(); + +/* Обход в глубину */ +const res = graphDFS(graph, v[0]); +console.log('\nПоследовательность вершин при обходе в глубину (DFS)'); +console.log(Vertex.vetsToVals(res)); diff --git a/ru/codes/typescript/chapter_greedy/coin_change_greedy.ts b/ru/codes/typescript/chapter_greedy/coin_change_greedy.ts new file mode 100644 index 000000000..0ddb0b5f4 --- /dev/null +++ b/ru/codes/typescript/chapter_greedy/coin_change_greedy.ts @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Размен монет: жадный алгоритм */ +function coinChangeGreedy(coins: number[], amt: number): number { + // Предположить, что массив coins упорядочен + let i = coins.length - 1; + let count = 0; + // Циклически выполнять жадный выбор, пока не останется суммы + while (amt > 0) { + // Найти монету, которая меньше остатка суммы и наиболее к нему близка + while (i > 0 && coins[i] > amt) { + i--; + } + // Выбрать coins[i] + amt -= coins[i]; + count++; + } + // Если допустимое решение не найдено, вернуть -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// Жадный подход: гарантирует нахождение глобально оптимального решения +let coins: number[] = [1, 5, 10, 20, 50, 100]; +let amt: number = 186; +let res: number = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); + +// Жадный подход: не гарантирует нахождение глобально оптимального решения +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); +console.log('На самом деле минимум равен 3: 20 + 20 + 20'); + +// Жадный подход: не гарантирует нахождение глобально оптимального решения +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`Минимальное число монет для набора суммы ${amt} = ${res}`); +console.log('На самом деле минимум равен 2: 49 + 49'); + +export {}; diff --git a/ru/codes/typescript/chapter_greedy/fractional_knapsack.ts b/ru/codes/typescript/chapter_greedy/fractional_knapsack.ts new file mode 100644 index 000000000..92fcab0f9 --- /dev/null +++ b/ru/codes/typescript/chapter_greedy/fractional_knapsack.ts @@ -0,0 +1,50 @@ +/** + * File: fractional_knapsack.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Предмет */ +class Item { + w: number; // Вес предмета + v: number; // Стоимость предмета + + constructor(w: number, v: number) { + this.w = w; + this.v = v; + } +} + +/* Дробный рюкзак: жадный алгоритм */ +function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { + // Создать список предметов с двумя свойствами: вес и стоимость + const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); + // Отсортировать по удельной стоимости item.v / item.w в порядке убывания + items.sort((a, b) => b.v / b.w - a.v / a.w); + // Циклический жадный выбор + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком + res += item.v; + cap -= item.w; + } else { + // Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета + res += (item.v / item.w) * cap; + // Свободной вместимости больше не осталось, поэтому выйти из цикла + break; + } + } + return res; +} + +/* Driver Code */ +const wgt: number[] = [10, 20, 30, 40, 50]; +const val: number[] = [50, 120, 150, 210, 240]; +const cap: number = 50; + +// Жадный алгоритм +const res: number = fractionalKnapsack(wgt, val, cap); +console.log(`Максимальная стоимость предметов без превышения вместимости рюкзака = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_greedy/max_capacity.ts b/ru/codes/typescript/chapter_greedy/max_capacity.ts new file mode 100644 index 000000000..2f9714814 --- /dev/null +++ b/ru/codes/typescript/chapter_greedy/max_capacity.ts @@ -0,0 +1,36 @@ +/** + * File: max_capacity.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Максимальная вместимость: жадный алгоритм */ +function maxCapacity(ht: number[]): number { + // Инициализировать i и j так, чтобы они располагались по двум концам массива + let i = 0, + j = ht.length - 1; + // Начальная максимальная вместимость равна 0 + let res = 0; + // Выполнять жадный выбор в цикле, пока две доски не встретятся + while (i < j) { + // Обновить максимальную вместимость + const cap: number = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // Сдвигать внутрь более короткую сторону + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; + +// Жадный алгоритм +const res: number = maxCapacity(ht); +console.log(`Максимальная вместимость = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_greedy/max_product_cutting.ts b/ru/codes/typescript/chapter_greedy/max_product_cutting.ts new file mode 100644 index 000000000..7dd20abd7 --- /dev/null +++ b/ru/codes/typescript/chapter_greedy/max_product_cutting.ts @@ -0,0 +1,35 @@ +/** + * File: max_product_cutting.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Максимальное произведение разрезания: жадный алгоритм */ +function maxProductCutting(n: number): number { + // Когда n <= 3, обязательно нужно выделить одну 1 + if (n <= 3) { + return 1 * (n - 1); + } + // Жадно выделить множители 3, где a — число троек, а b — остаток + let a: number = Math.floor(n / 3); + let b: number = n % 3; + if (b === 1) { + // Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // Если остаток равен 2, ничего не делать + return Math.pow(3, a) * 2; + } + // Если остаток равен 0, ничего не делать + return Math.pow(3, a); +} + +/* Driver Code */ +let n: number = 58; + +// Жадный алгоритм +let res: number = maxProductCutting(n); +console.log(`Максимальное произведение после разрезания = ${res}`); + +export {}; diff --git a/ru/codes/typescript/chapter_hashing/array_hash_map.ts b/ru/codes/typescript/chapter_hashing/array_hash_map.ts new file mode 100644 index 000000000..f0e26ccf6 --- /dev/null +++ b/ru/codes/typescript/chapter_hashing/array_hash_map.ts @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* Пара ключ-значение Number -> String */ +class Pair { + public key: number; + public val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица на основе массива */ +class ArrayHashMap { + private readonly buckets: (Pair | null)[]; + + constructor() { + // Инициализировать массив, содержащий 100 корзин + this.buckets = new Array(100).fill(null); + } + + /* Хеш-функция */ + private hashFunc(key: number): number { + return key % 100; + } + + /* Операция поиска */ + public get(key: number): string | null { + let index = this.hashFunc(key); + let pair = this.buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* Операция добавления */ + public set(key: number, val: string) { + let index = this.hashFunc(key); + this.buckets[index] = new Pair(key, val); + } + + /* Операция удаления */ + public delete(key: number) { + let index = this.hashFunc(key); + // Присвоить null, что означает удаление + this.buckets[index] = null; + } + + /* Получить все пары ключ-значение */ + public entries(): (Pair | null)[] { + let arr: (Pair | null)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i]); + } + } + return arr; + } + + /* Получить все ключи */ + public keys(): (number | undefined)[] { + let arr: (number | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].key); + } + } + return arr; + } + + /* Получить все значения */ + public values(): (string | undefined)[] { + let arr: (string | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].val); + } + } + return arr; + } + + /* Вывести хеш-таблицу */ + public print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* Инициализация хеш-таблицы */ +const map = new ArrayHashMap(); +/* Операция добавления */ +// Добавить пару (key, value) в хеш-таблицу +map.set(12836, 'Сяо Ха'); +map.set(15937, 'Сяо Ло'); +map.set(16750, 'Сяо Суань'); +map.set(13276, 'Сяо Фа'); +map.set(10583, 'Сяо Я'); +console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +/* Операция поиска */ +// Ввести в хеш-таблицу ключ key и получить значение value +let name = map.get(15937); +console.info('\nПо номеру 15937 найдено имя ' + name); + +/* Операция удаления */ +// Удалить пару (key, value) из хеш-таблицы +map.delete(10583); +console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +/* Обход хеш-таблицы */ +console.info('\nОтдельный обход пар ключ-значение'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\nОтдельный обход ключей'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\nОтдельный обход значений'); +for (const val of map.values()) { + console.info(val); +} + +export {}; diff --git a/ru/codes/typescript/chapter_hashing/hash_map.ts b/ru/codes/typescript/chapter_hashing/hash_map.ts new file mode 100644 index 000000000..c66d51c85 --- /dev/null +++ b/ru/codes/typescript/chapter_hashing/hash_map.ts @@ -0,0 +1,46 @@ +/** + * File: hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* Driver Code */ +/* Инициализация хеш-таблицы */ +const map = new Map(); + +/* Операция добавления */ +// Добавить пару (key, value) в хеш-таблицу +map.set(12836, 'Сяо Ха'); +map.set(15937, 'Сяо Ло'); +map.set(16750, 'Сяо Суань'); +map.set(13276, 'Сяо Фа'); +map.set(10583, 'Сяо Я'); +console.info('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +console.info(map); + +/* Операция поиска */ +// Ввести в хеш-таблицу ключ key и получить значение value +let name = map.get(15937); +console.info('\nПо номеру 15937 найдено имя ' + name); + +/* Операция удаления */ +// Удалить пару (key, value) из хеш-таблицы +map.delete(10583); +console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение'); +console.info(map); + +/* Обход хеш-таблицы */ +console.info('\nОтдельный обход пар ключ-значение'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\nОтдельный обход ключей'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\nОтдельный обход значений'); +for (const v of map.values()) { + console.info(v); +} + +export {}; diff --git a/ru/codes/typescript/chapter_hashing/hash_map_chaining.ts b/ru/codes/typescript/chapter_hashing/hash_map_chaining.ts new file mode 100644 index 000000000..3878f6d5b --- /dev/null +++ b/ru/codes/typescript/chapter_hashing/hash_map_chaining.ts @@ -0,0 +1,146 @@ +/** + * File: hash_map_chaining.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Пара ключ-значение Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица с цепочками */ +class HashMapChaining { + #size: number; // Число пар ключ-значение + #capacity: number; // Вместимость хеш-таблицы + #loadThres: number; // Порог коэффициента загрузки для запуска расширения + #extendRatio: number; // Коэффициент расширения + #buckets: Pair[][]; // Массив корзин + + /* Конструктор */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* Хеш-функция */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* Коэффициент загрузки */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* Операция поиска */ + get(key: number): string | null { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // Обойти корзину; если найден key, вернуть соответствующее val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // Если key не найден, вернуть null + return null; + } + + /* Операция добавления */ + put(key: number, val: string): void { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // Если такого key нет, добавить пару ключ-значение в конец + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* Операция удаления */ + remove(key: number): void { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // Обойти корзину и удалить из нее пару ключ-значение + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* Расширить хеш-таблицу */ + #extend(): void { + // Временно сохранить исходную хеш-таблицу + const bucketsTmp = this.#buckets; + // Инициализация новой хеш-таблицы после расширения + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + print(): void { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* Инициализация хеш-таблицы */ +const map = new HashMapChaining(); + +/* Операция добавления */ +// Добавить пару (key, value) в хеш-таблицу +map.put(12836, 'Сяо Ха'); +map.put(15937, 'Сяо Ло'); +map.put(16750, 'Сяо Суань'); +map.put(13276, 'Сяо Фа'); +map.put(10583, 'Сяо Я'); +console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +/* Операция поиска */ +// Ввести в хеш-таблицу ключ key и получить значение value +const name = map.get(13276); +console.log('\nПо номеру 13276 найдено имя ' + name); + +/* Операция удаления */ +// Удалить пару (key, value) из хеш-таблицы +map.remove(12836); +console.log('\nПосле удаления 12836 хеш-таблица имеет вид\nКлюч -> Значение'); +map.print(); + +export {}; diff --git a/ru/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/ru/codes/typescript/chapter_hashing/hash_map_open_addressing.ts new file mode 100644 index 000000000..b579137e2 --- /dev/null +++ b/ru/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -0,0 +1,182 @@ +/** + * File: hash_map_open_addressing.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* Пара ключ-значение Number -> String */ +class Pair { + key: number; + val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* Хеш-таблица с открытой адресацией */ +class HashMapOpenAddressing { + private size: number; // Число пар ключ-значение + private capacity: number; // Вместимость хеш-таблицы + private loadThres: number; // Порог коэффициента загрузки для запуска расширения + private extendRatio: number; // Коэффициент расширения + private buckets: Array; // Массив корзин + private TOMBSTONE: Pair; // Удалить метку + + /* Конструктор */ + constructor() { + this.size = 0; // Число пар ключ-значение + this.capacity = 4; // Вместимость хеш-таблицы + this.loadThres = 2.0 / 3.0; // Порог коэффициента загрузки для запуска расширения + this.extendRatio = 2; // Коэффициент расширения + this.buckets = Array(this.capacity).fill(null); // Массив корзин + this.TOMBSTONE = new Pair(-1, '-1'); // Удалить метку + } + + /* Хеш-функция */ + private hashFunc(key: number): number { + return key % this.capacity; + } + + /* Коэффициент загрузки */ + private loadFactor(): number { + return this.size / this.capacity; + } + + /* Найти индекс корзины, соответствующий key */ + private findBucket(key: number): number { + let index = this.hashFunc(key); + let firstTombstone = -1; + // Выполнять линейное пробирование и завершить при встрече с пустой корзиной + while (this.buckets[index] !== null) { + // Если встретился key, вернуть соответствующий индекс корзины + if (this.buckets[index]!.key === key) { + // Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс + if (firstTombstone !== -1) { + this.buckets[firstTombstone] = this.buckets[index]; + this.buckets[index] = this.TOMBSTONE; + return firstTombstone; // Вернуть индекс корзины после перемещения + } + return index; // Вернуть индекс корзины + } + // Записать первую встретившуюся метку удаления + if ( + firstTombstone === -1 && + this.buckets[index] === this.TOMBSTONE + ) { + firstTombstone = index; + } + // Вычислить индекс корзины; при выходе за конец вернуться к началу + index = (index + 1) % this.capacity; + } + // Если key не существует, вернуть индекс точки добавления + return firstTombstone === -1 ? index : firstTombstone; + } + + /* Операция поиска */ + get(key: number): string | null { + // Найти индекс корзины, соответствующий key + const index = this.findBucket(key); + // Если пара ключ-значение найдена, вернуть соответствующее val + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + return this.buckets[index]!.val; + } + // Если пары ключ-значение не существует, вернуть null + return null; + } + + /* Операция добавления */ + put(key: number, val: string): void { + // Когда коэффициент загрузки превышает порог, выполнить расширение + if (this.loadFactor() > this.loadThres) { + this.extend(); + } + // Найти индекс корзины, соответствующий key + const index = this.findBucket(key); + // Если пара ключ-значение найдена, перезаписать val и вернуть + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index]!.val = val; + return; + } + // Если пары ключ-значение нет, добавить ее + this.buckets[index] = new Pair(key, val); + this.size++; + } + + /* Операция удаления */ + remove(key: number): void { + // Найти индекс корзины, соответствующий key + const index = this.findBucket(key); + // Если пара ключ-значение найдена, заменить ее меткой удаления + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index] = this.TOMBSTONE; + this.size--; + } + } + + /* Расширить хеш-таблицу */ + private extend(): void { + // Временно сохранить исходную хеш-таблицу + const bucketsTmp = this.buckets; + // Инициализация новой хеш-таблицы после расширения + this.capacity *= this.extendRatio; + this.buckets = Array(this.capacity).fill(null); + this.size = 0; + // Перенести пары ключ-значение из исходной хеш-таблицы в новую + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* Вывести хеш-таблицу */ + print(): void { + for (const pair of this.buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// Инициализация хеш-таблицы +const hashmap = new HashMapOpenAddressing(); + +// Операция добавления +// Добавить пару (key, val) в хеш-таблицу +hashmap.put(12836, 'Сяо Ха'); +hashmap.put(15937, 'Сяо Ло'); +hashmap.put(16750, 'Сяо Суань'); +hashmap.put(13276, 'Сяо Фа'); +hashmap.put(10583, 'Сяо Я'); +console.log('\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение'); +hashmap.print(); + +// Операция поиска +// Передать ключ key в хеш-таблицу и получить значение val +const name = hashmap.get(13276); +console.log('\nПо номеру 13276 найдено имя ' + name); + +// Операция удаления +// Удалить пару (key, val) из хеш-таблицы +hashmap.remove(16750); +console.log('\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение'); +hashmap.print(); + +export {}; diff --git a/ru/codes/typescript/chapter_hashing/simple_hash.ts b/ru/codes/typescript/chapter_hashing/simple_hash.ts new file mode 100644 index 000000000..1f5263bcf --- /dev/null +++ b/ru/codes/typescript/chapter_hashing/simple_hash.ts @@ -0,0 +1,60 @@ +/** + * File: simple_hash.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* Аддитивное хеширование */ +function addHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Мультипликативное хеширование */ +function mulHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* XOR-хеширование */ +function xorHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* Хеширование с циклическим сдвигом */ +function rotHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello Algo'; + +let hash = addHash(key); +console.log('Хеш-значение по сложению = ' + hash); + +hash = mulHash(key); +console.log('Хеш-значение по умножению = ' + hash); + +hash = xorHash(key); +console.log('Хеш-значение по XOR = ' + hash); + +hash = rotHash(key); +console.log('Хеш-значение по циклическому сдвигу = ' + hash); diff --git a/ru/codes/typescript/chapter_heap/my_heap.ts b/ru/codes/typescript/chapter_heap/my_heap.ts new file mode 100644 index 000000000..28e38a721 --- /dev/null +++ b/ru/codes/typescript/chapter_heap/my_heap.ts @@ -0,0 +1,155 @@ +/** + * File: my_heap.ts + * Created Time: 2023-02-07 + * Author: Justin (xiefahit@gmail.com) + */ + +import { printHeap } from '../modules/PrintUtil'; + +/* Класс максимальной кучи */ +class MaxHeap { + private maxHeap: number[]; + /* Конструктор, создающий пустую кучу или строящий кучу по входному списку */ + constructor(nums?: number[]) { + // Добавить элементы списка в кучу без изменений + this.maxHeap = nums === undefined ? [] : [...nums]; + // Выполнить heapify для всех узлов, кроме листовых + for (let i = this.parent(this.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + + /* Получить индекс левого дочернего узла */ + private left(i: number): number { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла */ + private right(i: number): number { + return 2 * i + 2; + } + + /* Получить индекс родительского узла */ + private parent(i: number): number { + return Math.floor((i - 1) / 2); // Округление вниз при делении + } + + /* Поменять элементы местами */ + private swap(i: number, j: number): void { + const tmp = this.maxHeap[i]; + this.maxHeap[i] = this.maxHeap[j]; + this.maxHeap[j] = tmp; + } + + /* Получение размера кучи */ + public size(): number { + return this.maxHeap.length; + } + + /* Проверка, пуста ли куча */ + public isEmpty(): boolean { + return this.size() === 0; + } + + /* Доступ к элементу на вершине кучи */ + public peek(): number { + return this.maxHeap[0]; + } + + /* Добавление элемента в кучу */ + public push(val: number): void { + // Добавление узла + this.maxHeap.push(val); + // Просеивание снизу вверх + this.siftUp(this.size() - 1); + } + + /* Начиная с узла i, выполнить просеивание снизу вверх */ + private siftUp(i: number): void { + while (true) { + // Получение родительского узла для узла i + const p = this.parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; + // Поменять два узла местами + this.swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + /* Извлечение элемента из кучи */ + public pop(): number { + // Обработка пустого случая + if (this.isEmpty()) throw new RangeError('Heap is empty.'); + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + this.swap(0, this.size() - 1); + // Удаление узла + const val = this.maxHeap.pop(); + // Просеивание сверху вниз + this.siftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + /* Начиная с узла i, выполнить просеивание сверху вниз */ + private siftDown(i: number): void { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + const l = this.left(i), + r = this.right(i); + let ma = i; + if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; + if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma === i) break; + // Поменять два узла местами + this.swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + /* Вывести кучу (двоичное дерево) */ + public print(): void { + printHeap(this.maxHeap); + } + + /* Извлечь элементы из кучи */ + public getMaxHeap(): number[] { + return this.maxHeap; + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* Инициализация максимальной кучи */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\nПосле построения кучи из входного списка'); + maxHeap.print(); + + /* Получение элемента с вершины кучи */ + let peek = maxHeap.peek(); + console.log(`\nЭлемент на вершине кучи = ${peek}`); + + /* Добавление элемента в кучу */ + const val = 7; + maxHeap.push(val); + console.log(`\nПосле добавления элемента ${val} в кучу`); + maxHeap.print(); + + /* Извлечение элемента с вершины кучи */ + peek = maxHeap.pop(); + console.log(`\nПосле извлечения элемента вершины кучи ${peek}`); + maxHeap.print(); + + /* Получение размера кучи */ + const size = maxHeap.size(); + console.log(`\nКоличество элементов в куче = ${size}`); + + /* Проверка, пуста ли куча */ + const isEmpty = maxHeap.isEmpty(); + console.log(`\nПуста ли куча: ${isEmpty}`); +} + +export { MaxHeap }; diff --git a/ru/codes/typescript/chapter_heap/top_k.ts b/ru/codes/typescript/chapter_heap/top_k.ts new file mode 100644 index 000000000..11464a9a4 --- /dev/null +++ b/ru/codes/typescript/chapter_heap/top_k.ts @@ -0,0 +1,58 @@ +/** + * File: top_k.ts + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { MaxHeap } from './my_heap'; + +/* Добавление элемента в кучу */ +function pushMinHeap(maxHeap: MaxHeap, val: number): void { + // Инвертировать знак элемента + maxHeap.push(-val); +} + +/* Извлечение элемента из кучи */ +function popMinHeap(maxHeap: MaxHeap): number { + // Инвертировать знак элемента + return -maxHeap.pop(); +} + +/* Доступ к элементу на вершине кучи */ +function peekMinHeap(maxHeap: MaxHeap): number { + // Инвертировать знак элемента + return -maxHeap.peek(); +} + +/* Извлечь элементы из кучи */ +function getMinHeap(maxHeap: MaxHeap): number[] { + // Инвертировать знак элемента + return maxHeap.getMaxHeap().map((num: number) => -num); +} + +/* Найти k наибольших элементов массива с помощью кучи */ +function topKHeap(nums: number[], k: number): number[] { + // Инициализация минимальной кучи + // Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную + const maxHeap = new MaxHeap([]); + // Поместить первые k элементов массива в кучу + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // Начиная с элемента k+1, поддерживать длину кучи равной k + for (let i = k; i < nums.length; i++) { + // Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // Вернуть элементы кучи + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`Наибольшие ${k} элементов`, res); diff --git a/ru/codes/typescript/chapter_searching/binary_search.ts b/ru/codes/typescript/chapter_searching/binary_search.ts new file mode 100644 index 000000000..d94edbcd3 --- /dev/null +++ b/ru/codes/typescript/chapter_searching/binary_search.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search.ts + * Created Time: 2022-12-27 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +function binarySearch(nums: number[], target: number): number { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + let i = 0, + j = nums.length - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + // Вычислить индекс середины m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + } else if (nums[m] > target) { + // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + return -1; // Целевой элемент не найден, вернуть -1 +} + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +function binarySearchLCRO(nums: number[], target: number): number { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + let i = 0, + j = nums.length; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i < j) { + // Вычислить индекс середины m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + } else if (nums[m] > target) { + // Это означает, что target находится в интервале [i, m) + j = m; + } else { + // Целевой элемент найден, вернуть его индекс + return m; + } + } + return -1; // Целевой элемент не найден, вернуть -1 +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* Бинарный поиск (двусторонне замкнутый интервал) */ +let index = binarySearch(nums, target); +console.info('Индекс целевого элемента 6 = %d', index); + +/* Бинарный поиск (лево замкнутый, право открытый интервал) */ +index = binarySearchLCRO(nums, target); +console.info('Индекс целевого элемента 6 = %d', index); + +export {}; diff --git a/ru/codes/typescript/chapter_searching/binary_search_edge.ts b/ru/codes/typescript/chapter_searching/binary_search_edge.ts new file mode 100644 index 000000000..6ebd32c4a --- /dev/null +++ b/ru/codes/typescript/chapter_searching/binary_search_edge.ts @@ -0,0 +1,46 @@ +/** + * File: binary_search_edge.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ +import { binarySearchInsertion } from './binary_search_insertion'; + +/* Бинарный поиск самого левого target */ +function binarySearchLeftEdge(nums: Array, target: number): number { + // Эквивалентно поиску точки вставки target + const i = binarySearchInsertion(nums, target); + // target не найден, вернуть -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // Найти target и вернуть индекс i + return i; +} + +/* Бинарный поиск самого правого target */ +function binarySearchRightEdge(nums: Array, target: number): number { + // Преобразовать задачу в поиск самого левого target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j указывает на самый правый target, а i — на первый элемент больше target + const j = i - 1; + // target не найден, вернуть -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // Найти target и вернуть индекс j + return j; +} + +/* Driver Code */ +// Массив с повторяющимися элементами +let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\nМассив nums = ' + nums); +// Бинарный поиск левой и правой границы +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('Индекс самого левого элемента ' + target + ' = ' + index); + index = binarySearchRightEdge(nums, target); + console.log('Индекс самого правого элемента ' + target + ' = ' + index); +} + +export {}; diff --git a/ru/codes/typescript/chapter_searching/binary_search_insertion.ts b/ru/codes/typescript/chapter_searching/binary_search_insertion.ts new file mode 100644 index 000000000..20b8bf785 --- /dev/null +++ b/ru/codes/typescript/chapter_searching/binary_search_insertion.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* Бинарный поиск точки вставки (без повторяющихся элементов) */ +function binarySearchInsertionSimple( + nums: Array, + target: number +): number { + let i = 0, + j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + return m; // Найти target и вернуть точку вставки m + } + } + // target не найден, вернуть точку вставки i + return i; +} + +/* Бинарный поиск точки вставки (с повторяющимися элементами) */ +function binarySearchInsertion(nums: Array, target: number): number { + let i = 0, + j = nums.length - 1; // Инициализировать двусторонне замкнутый интервал [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // Вычислить индекс середины m, используя Math.floor() для округления вниз + if (nums[m] < target) { + i = m + 1; // target находится в интервале [m+1, j] + } else if (nums[m] > target) { + j = m - 1; // target находится в интервале [i, m-1] + } else { + j = m - 1; // Первый элемент меньше target находится в интервале [i, m-1] + } + } + // Вернуть точку вставки i + return i; +} + +/* Driver Code */ +// Массив без повторяющихся элементов +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\nМассив nums = ' + nums); +// Бинарный поиск точки вставки +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('Индекс точки вставки элемента ' + target + ' = ' + index); +} + +// Массив с повторяющимися элементами +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\nМассив nums = ' + nums); +// Бинарный поиск точки вставки +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('Индекс точки вставки элемента ' + target + ' = ' + index); +} + +export { binarySearchInsertion }; diff --git a/ru/codes/typescript/chapter_searching/hashing_search.ts b/ru/codes/typescript/chapter_searching/hashing_search.ts new file mode 100644 index 000000000..595d9be14 --- /dev/null +++ b/ru/codes/typescript/chapter_searching/hashing_search.ts @@ -0,0 +1,50 @@ +/** + * File: hashing_search.ts + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* Хеш-поиск (массив) */ +function hashingSearchArray(map: Map, target: number): number { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + return map.has(target) ? (map.get(target) as number) : -1; +} + +/* Хеш-поиск (связный список) */ +function hashingSearchLinkedList( + map: Map, + target: number +): ListNode | null { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + return map.has(target) ? (map.get(target) as ListNode) : null; +} + +/* Driver Code */ +const target = 3; + +/* Хеш-поиск (массив) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// Инициализация хеш-таблицы +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: элемент, value: индекс +} +const index = hashingSearchArray(map, target); +console.log('Индекс целевого элемента 3 = ' + index); + +/* Хеш-поиск (связный список) */ +let head = arrToLinkedList(nums); +// Инициализация хеш-таблицы +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: значение узла, value: узел + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('Объект узла со значением 3 =', node); + +export {}; diff --git a/ru/codes/typescript/chapter_searching/linear_search.ts b/ru/codes/typescript/chapter_searching/linear_search.ts new file mode 100644 index 000000000..3e513197a --- /dev/null +++ b/ru/codes/typescript/chapter_searching/linear_search.ts @@ -0,0 +1,52 @@ +/** + * File: linear_search.ts + * Created Time: 2023-01-07 + * Author: Daniel (better.sunjian@gmail.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* Линейный поиск (массив) */ +function linearSearchArray(nums: number[], target: number): number { + // Обход массива + for (let i = 0; i < nums.length; i++) { + // Целевой элемент найден, вернуть его индекс + if (nums[i] === target) { + return i; + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +/* Линейный поиск (связный список) */ +function linearSearchLinkedList( + head: ListNode | null, + target: number +): ListNode | null { + // Обойти связный список + while (head) { + // Найти целевой узел и вернуть его + if (head.val === target) { + return head; + } + head = head.next; + } + // Целевой узел не найден, вернуть null + return null; +} + +/* Driver Code */ +const target = 3; + +/* Выполнить линейный поиск в массиве */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('Индекс целевого элемента 3 =', index); + +/* Выполнить линейный поиск в связном списке */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('Объект узла со значением 3 =', node); + +export {}; diff --git a/ru/codes/typescript/chapter_searching/two_sum.ts b/ru/codes/typescript/chapter_searching/two_sum.ts new file mode 100644 index 000000000..e0d48201a --- /dev/null +++ b/ru/codes/typescript/chapter_searching/two_sum.ts @@ -0,0 +1,49 @@ +/** + * File: two_sum.ts + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* Метод 1: полный перебор */ +function twoSumBruteForce(nums: number[], target: number): number[] { + const n = nums.length; + // Два вложенных цикла, временная сложность O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* Метод 2: вспомогательная хеш-таблица */ +function twoSumHashTable(nums: number[], target: number): number[] { + // Вспомогательная хеш-таблица, пространственная сложность O(n) + let m: Map = new Map(); + // Один цикл, временная сложность O(n) + for (let i = 0; i < nums.length; i++) { + let index = m.get(target - nums[i]); + if (index !== undefined) { + return [index, i]; + } else { + m.set(nums[i], i); + } + } + return []; +} + +/* Driver Code */ +// Метод 1 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('Метод 1: res = ', res); + +// Метод 2 +res = twoSumHashTable(nums, target); +console.log('Метод 2: res = ', res); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/bubble_sort.ts b/ru/codes/typescript/chapter_sorting/bubble_sort.ts new file mode 100644 index 000000000..b97ec4518 --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/bubble_sort.ts @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Пузырьковая сортировка */ +function bubbleSort(nums: number[]): void { + // Внешний цикл: неотсортированный диапазон [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* Пузырьковая сортировка (оптимизация флагом) */ +function bubbleSortWithFlag(nums: number[]): void { + // Внешний цикл: неотсортированный диапазон [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // Инициализировать флаг + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // Записать обмен элементов + } + } + if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('После завершения пузырьковой сортировки nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('После завершения пузырьковой сортировки nums =', nums1); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/bucket_sort.ts b/ru/codes/typescript/chapter_sorting/bucket_sort.ts new file mode 100644 index 000000000..d750c647e --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/bucket_sort.ts @@ -0,0 +1,41 @@ +/** + * File: bucket_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка корзинами */ +function bucketSort(nums: number[]): void { + // Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину + const k = nums.length / 2; + const buckets: number[][] = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. Распределить элементы массива по корзинам + for (const num of nums) { + // Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1] + const i = Math.floor(num * k); + // Добавить num в корзину i + buckets[i].push(num); + } + // 2. Выполнить сортировку внутри каждой корзины + for (const bucket of buckets) { + // Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки + bucket.sort((a, b) => a - b); + } + // 3. Обойти корзины и объединить результаты + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('После завершения сортировки корзинами nums =', nums); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/counting_sort.ts b/ru/codes/typescript/chapter_sorting/counting_sort.ts new file mode 100644 index 000000000..67a7ba11c --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/counting_sort.ts @@ -0,0 +1,67 @@ +/** + * File: counting_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка подсчетом */ +// Простая реализация, не подходит для сортировки объектов +function countingSortNaive(nums: number[]): void { + // 1. Найти максимальный элемент массива m + let m: number = Math.max(...nums); + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. Обойти counter и заполнить исходный массив nums элементами + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* Сортировка подсчетом */ +// Полная реализация, позволяет сортировать объекты и является стабильной сортировкой +function countingSort(nums: number[]): void { + // 1. Найти максимальный элемент массива m + let m: number = Math.max(...nums); + // 2. Подсчитать число появлений каждой цифры + // counter[num] обозначает число появлений num + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс» + // То есть counter[num]-1 — это индекс последнего появления num в res + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res + // Инициализировать массив res для хранения результата + const n = nums.length; + const res: number[] = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // Поместить num по соответствующему индексу + counter[num]--; // Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num + } + // Перезаписать исходный массив nums массивом результата res + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('После завершения сортировки подсчетом (объекты не сортируются) nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('После завершения сортировки подсчетом nums1 =', nums1); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/heap_sort.ts b/ru/codes/typescript/chapter_sorting/heap_sort.ts new file mode 100644 index 000000000..13abd3733 --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/heap_sort.ts @@ -0,0 +1,51 @@ +/** + * File: heap_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз */ +function siftDown(nums: number[], n: number, i: number): void { + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let ma = i; + if (l < n && nums[l] > nums[ma]) { + ma = l; + } + if (r < n && nums[r] > nums[ma]) { + ma = r; + } + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma === i) { + break; + } + // Поменять два узла местами + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // Циклическое просеивание вниз + i = ma; + } +} + +/* Сортировка кучей */ +function heapSort(nums: number[]): void { + // Построение кучи: выполнить heapify для всех узлов, кроме листовых + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // Извлекать максимальный элемент из кучи в течение n-1 итераций + for (let i = nums.length - 1; i > 0; i--) { + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // Начиная с корневого узла, выполнить просеивание сверху вниз + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('После завершения сортировки кучей nums =', nums); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/insertion_sort.ts b/ru/codes/typescript/chapter_sorting/insertion_sort.ts new file mode 100644 index 000000000..a0a82f4a3 --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/insertion_sort.ts @@ -0,0 +1,27 @@ +/** + * File: insertion_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка вставками */ +function insertionSort(nums: number[]): void { + // Внешний цикл: отсортированный диапазон [0, i-1] + for (let i = 1; i < nums.length; i++) { + const base = nums[i]; + let j = i - 1; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // Сдвинуть nums[j] на одну позицию вправо + j--; + } + nums[j + 1] = base; // Поместить base в правильную позицию + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('После завершения сортировки вставками nums =', nums); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/merge_sort.ts b/ru/codes/typescript/chapter_sorting/merge_sort.ts new file mode 100644 index 000000000..cee0773d6 --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/merge_sort.ts @@ -0,0 +1,54 @@ +/** + * File: merge_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Объединить левый и правый подмассивы */ +function merge(nums: number[], left: number, mid: number, right: number): void { + // Диапазон левого подмассива: [left, mid], диапазон правого подмассива: [mid+1, right] + // Создать временный массив tmp для хранения результата слияния + const tmp = new Array(right - left + 1); + // Инициализировать начальные индексы левого и правого подмассивов + let i = left, + j = mid + 1, + k = 0; + // Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // Скопировать оставшиеся элементы левого и правого подмассивов во временный массив + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* Сортировка слиянием */ +function mergeSort(nums: number[], left: number, right: number): void { + // Условие завершения + if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + let mid = Math.floor(left + (right - left) / 2); // Вычислить середину + mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('После завершения сортировки слиянием nums =', nums); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/quick_sort.ts b/ru/codes/typescript/chapter_sorting/quick_sort.ts new file mode 100644 index 000000000..5c0115d1f --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/quick_sort.ts @@ -0,0 +1,180 @@ +/** + * File: quick_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Класс быстрой сортировки */ +class QuickSort { + /* Обмен элементов */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + partition(nums: number[], left: number, right: number): number { + // Взять nums[left] в качестве опорного элемента + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // Идти слева направо в поисках первого элемента больше опорного + } + // Обмен элементов + this.swap(nums, i, j); // Поменять эти два элемента местами + } + this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + quickSort(nums: number[], left: number, right: number): void { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) { + return; + } + // Разбиение с опорными указателями + const pivot = this.partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация медианным опорным элементом) */ +class QuickSortMedian { + /* Обмен элементов */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Выбрать медиану из трех кандидатов */ + medianThree( + nums: number[], + left: number, + mid: number, + right: number + ): number { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m находится между l и r + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l находится между m и r + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* Разбиение с опорными указателями (медиана трех) */ + partition(nums: number[], left: number, right: number): number { + // Выбрать медиану из трех кандидатов + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // Переместить медиану в крайний левый элемент массива + this.swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // Идти справа налево в поисках первого элемента меньше опорного + } + while (i < j && nums[i] <= nums[left]) { + i++; // Идти слева направо в поисках первого элемента больше опорного + } + this.swap(nums, i, j); // Поменять эти два элемента местами + } + this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка */ + quickSort(nums: number[], left: number, right: number): void { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) { + return; + } + // Разбиение с опорными указателями + const pivot = this.partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* Класс быстрой сортировки (оптимизация глубины рекурсии) */ +class QuickSortTailCall { + /* Обмен элементов */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* Разбиение с опорными указателями */ + partition(nums: number[], left: number, right: number): number { + // Взять nums[left] в качестве опорного элемента + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // Идти справа налево в поисках первого элемента меньше опорного + } + while (i < j && nums[i] <= nums[left]) { + i++; // Идти слева направо в поисках первого элемента больше опорного + } + this.swap(nums, i, j); // Поменять эти два элемента местами + } + this.swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + /* Быстрая сортировка (оптимизация глубины рекурсии) */ + quickSort(nums: number[], left: number, right: number): void { + // Завершить, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + let pivot = this.partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* Быстрая сортировка */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('После завершения быстрой сортировки nums =', nums); + +/* Быстрая сортировка (оптимизация медианным опорным элементом) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('После завершения быстрой сортировки (оптимизация медианным опорным элементом) nums1 =', nums1); + +/* Быстрая сортировка (оптимизация глубины рекурсии) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('После завершения быстрой сортировки (оптимизация глубины рекурсии) nums2 =', nums2); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/radix_sort.ts b/ru/codes/typescript/chapter_sorting/radix_sort.ts new file mode 100644 index 000000000..51bbb845b --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/radix_sort.ts @@ -0,0 +1,63 @@ +/** + * File: radix_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Получить k-й разряд элемента num, где exp = 10^(k-1) */ +function digit(num: number, exp: number): number { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return Math.floor(num / exp) % 10; +} + +/* Сортировка подсчетом (сортировка по k-му разряду nums) */ +function countingSortDigit(nums: number[], exp: number): void { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + const counter = new Array(10).fill(0); + const n = nums.length; + // Подсчитать число появлений каждой цифры от 0 до 9 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // Получить k-й разряд nums[i], обозначив его как d + counter[d]++; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d]--; // Уменьшить количество d на 1 + } + // Перезаписать исходный массив nums результатом + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Поразрядная сортировка */ +function radixSort(nums: number[]): void { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + let m: number = Math.max(... nums); + // Проходить разряды от младшего к старшему + for (let exp = 1; exp <= m; exp *= 10) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('После завершения поразрядной сортировки nums =', nums); + +export {}; diff --git a/ru/codes/typescript/chapter_sorting/selection_sort.ts b/ru/codes/typescript/chapter_sorting/selection_sort.ts new file mode 100644 index 000000000..d590ea8aa --- /dev/null +++ b/ru/codes/typescript/chapter_sorting/selection_sort.ts @@ -0,0 +1,29 @@ +/** + * File: selection_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Сортировка выбором */ +function selectionSort(nums: number[]): void { + let n = nums.length; + // Внешний цикл: неотсортированный диапазон [i, n-1] + for (let i = 0; i < n - 1; i++) { + // Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // Записать индекс минимального элемента + } + } + // Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('После завершения сортировки выбором nums =', nums); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/array_deque.ts b/ru/codes/typescript/chapter_stack_and_queue/array_deque.ts new file mode 100644 index 000000000..72cb06c83 --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/array_deque.ts @@ -0,0 +1,158 @@ +/** + * File: array_deque.ts + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Двусторонняя очередь на основе кольцевого массива */ +class ArrayDeque { + private nums: number[]; // Массив для хранения элементов двусторонней очереди + private front: number; // Указатель head, указывающий на первый элемент очереди + private queSize: number; // Длина двусторонней очереди + + /* Конструктор */ + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = 0; + this.queSize = 0; + } + + /* Получить вместимость двусторонней очереди */ + capacity(): number { + return this.nums.length; + } + + /* Получение длины двусторонней очереди */ + size(): number { + return this.queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* Вычислить индекс в кольцевом массиве */ + index(i: number): number { + // С помощью операции взятия по модулю соединить начало и конец массива + // Когда i выходит за конец массива, он возвращается в начало + // Когда i выходит за начало массива, он возвращается в конец + return (i + this.capacity()) % this.capacity(); + } + + /* Добавление в голову очереди */ + pushFirst(num: number): void { + if (this.queSize === this.capacity()) { + console.log('Двусторонняя очередь заполнена'); + return; + } + // Указатель головы сдвигается на одну позицию влево + // С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост + this.front = this.index(this.front - 1); + // Добавить num в голову очереди + this.nums[this.front] = num; + this.queSize++; + } + + /* Добавление в хвост очереди */ + pushLast(num: number): void { + if (this.queSize === this.capacity()) { + console.log('Двусторонняя очередь заполнена'); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + const rear: number = this.index(this.front + this.queSize); + // Добавить num в хвост очереди + this.nums[rear] = num; + this.queSize++; + } + + /* Извлечение из головы очереди */ + popFirst(): number { + const num: number = this.peekFirst(); + // Указатель головы сдвигается на одну позицию назад + this.front = this.index(this.front + 1); + this.queSize--; + return num; + } + + /* Извлечение из хвоста очереди */ + popLast(): number { + const num: number = this.peekLast(); + this.queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + peekFirst(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.nums[this.front]; + } + + /* Доступ к элементу в конце очереди */ + peekLast(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // Вычислить индекс хвостового элемента + const last = this.index(this.front + this.queSize - 1); + return this.nums[last]; + } + + /* Вернуть массив для вывода */ + toArray(): number[] { + // Преобразовывать только элементы списка в пределах фактической длины + const res: number[] = []; + for (let i = 0, j = this.front; i < this.queSize; i++, j++) { + res[i] = this.nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* Инициализация двусторонней очереди */ +const capacity = 5; +const deque: ArrayDeque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('Двусторонняя очередь deque = [' + deque.toArray() + ']'); + +/* Доступ к элементу */ +const peekFirst = deque.peekFirst(); +console.log('Элемент в начале очереди peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('Элемент в конце очереди peekLast = ' + peekLast); + +/* Добавление элемента в очередь */ +deque.pushLast(4); +console.log('После добавления элемента 4 в хвост deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('После добавления элемента 1 в голову deque = [' + deque.toArray() + ']'); + +/* Извлечение элемента из очереди */ +const popLast = deque.popLast(); +console.log( + 'Извлеченный из хвоста элемент = ' + + popLast + + ', deque после извлечения из хвоста = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + 'Извлеченный из головы элемент = ' + + popFirst + + ', deque после извлечения из головы = [' + + deque.toArray() + + ']' +); + +/* Получение длины двусторонней очереди */ +const size = deque.size(); +console.log('Длина двусторонней очереди size = ' + size); + +/* Проверка, пуста ли двусторонняя очередь */ +const isEmpty = deque.isEmpty(); +console.log('Пуста ли двусторонняя очередь = ' + isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/array_queue.ts b/ru/codes/typescript/chapter_stack_and_queue/array_queue.ts new file mode 100644 index 000000000..2ffe908ef --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/array_queue.ts @@ -0,0 +1,109 @@ +/** + * File: array_queue.ts + * Created Time: 2022-12-11 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Очередь на основе кольцевого массива */ +class ArrayQueue { + private nums: number[]; // Массив для хранения элементов очереди + private front: number; // Указатель head, указывающий на первый элемент очереди + private queSize: number; // Длина очереди + + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = this.queSize = 0; + } + + /* Получить вместимость очереди */ + get capacity(): number { + return this.nums.length; + } + + /* Получение длины очереди */ + get size(): number { + return this.queSize; + } + + /* Проверка, пуста ли очередь */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* Поместить в очередь */ + push(num: number): void { + if (this.size === this.capacity) { + console.log('Очередь заполнена'); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + const rear = (this.front + this.queSize) % this.capacity; + // Добавить num в хвост очереди + this.nums[rear] = num; + this.queSize++; + } + + /* Извлечь из очереди */ + pop(): number { + const num = this.peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + this.front = (this.front + 1) % this.capacity; + this.queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + peek(): number { + if (this.isEmpty()) throw new Error('очередь пуста'); + return this.nums[this.front]; + } + + /* Вернуть Array */ + toArray(): number[] { + // Преобразовывать только элементы списка в пределах фактической длины + const arr = new Array(this.size); + for (let i = 0, j = this.front; i < this.size; i++, j++) { + arr[i] = this.nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* Инициализация очереди */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* Добавление элемента в очередь */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('Очередь queue =', queue.toArray()); + +/* Доступ к элементу в начале очереди */ +const peek = queue.peek(); +console.log('Элемент в начале очереди peek = ' + peek); + +/* Извлечение элемента из очереди */ +const pop = queue.pop(); +console.log('Извлечен элемент pop = ' + pop + ', очередь после извлечения queue =', queue.toArray()); + +/* Получение длины очереди */ +const size = queue.size; +console.log('Длина очереди size = ' + size); + +/* Проверка, пуста ли очередь */ +const isEmpty = queue.isEmpty(); +console.log('Пуста ли очередь = ' + isEmpty); + +/* Проверка кольцевого массива */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('После ' + i + '-го добавления и извлечения queue =', queue.toArray()); +} + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/array_stack.ts b/ru/codes/typescript/chapter_stack_and_queue/array_stack.ts new file mode 100644 index 000000000..d6c28817c --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/array_stack.ts @@ -0,0 +1,77 @@ +/** + * File: array_stack.ts + * Created Time: 2022-12-08 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Стек на основе массива */ +class ArrayStack { + private stack: number[]; + constructor() { + this.stack = []; + } + + /* Получение длины стека */ + get size(): number { + return this.stack.length; + } + + /* Проверка, пуст ли стек */ + isEmpty(): boolean { + return this.stack.length === 0; + } + + /* Поместить в стек */ + push(num: number): void { + this.stack.push(num); + } + + /* Извлечь из стека */ + pop(): number | undefined { + if (this.isEmpty()) throw new Error('стек пуст'); + return this.stack.pop(); + } + + /* Доступ к верхнему элементу стека */ + top(): number | undefined { + if (this.isEmpty()) throw new Error('стек пуст'); + return this.stack[this.stack.length - 1]; + } + + /* Вернуть Array */ + toArray() { + return this.stack; + } +} + +/* Driver Code */ +/* Инициализация стека */ +const stack = new ArrayStack(); + +/* Помещение элемента в стек */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('Стек stack = '); +console.log(stack.toArray()); + +/* Доступ к верхнему элементу стека */ +const top = stack.top(); +console.log('Верхний элемент стека top = ' + top); + +/* Извлечение элемента из стека */ +const pop = stack.pop(); +console.log('Извлечен элемент pop = ' + pop + ', стек после извлечения stack = '); +console.log(stack.toArray()); + +/* Получение длины стека */ +const size = stack.size; +console.log('Длина стека size = ' + size); + +/* Проверка на пустоту */ +const isEmpty = stack.isEmpty(); +console.log('Пуст ли стек = ' + isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/deque.ts b/ru/codes/typescript/chapter_stack_and_queue/deque.ts new file mode 100644 index 000000000..8837b2fc2 --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/deque.ts @@ -0,0 +1,46 @@ +/** + * File: deque.ts + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* Инициализация двусторонней очереди */ +// В TypeScript нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь +const deque: number[] = []; + +/* Добавление элемента в очередь */ +deque.push(2); +deque.push(5); +deque.push(4); +// Обратите внимание: поскольку используется массив, временная сложность метода unshift() равна O(n) +deque.unshift(3); +deque.unshift(1); +console.log('Двусторонняя очередь deque = ', deque); + +/* Доступ к элементу */ +const peekFirst: number = deque[0]; +console.log('Элемент в начале очереди peekFirst = ' + peekFirst); +const peekLast: number = deque[deque.length - 1]; +console.log('Элемент в конце очереди peekLast = ' + peekLast); + +/* Извлечение элемента из очереди */ +// Обратите внимание: поскольку используется массив, временная сложность метода shift() равна O(n) +const popFront: number = deque.shift() as number; +console.log( + 'Извлеченный из головы элемент popFront = ' + popFront + ', deque после извлечения из головы = ' + deque +); +const popBack: number = deque.pop() as number; +console.log( + 'Извлеченный из хвоста элемент popBack = ' + popBack + ', deque после извлечения из хвоста = ' + deque +); + +/* Получение длины двусторонней очереди */ +const size: number = deque.length; +console.log('Длина двусторонней очереди size = ' + size); + +/* Проверка, пуста ли двусторонняя очередь */ +const isEmpty: boolean = size === 0; +console.log('Пуста ли двусторонняя очередь = ' + isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts b/ru/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts new file mode 100644 index 000000000..fdafbf6dd --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.ts + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Узел двусвязного списка */ +class ListNode { + prev: ListNode; // Ссылка на узел-предшественник (указатель) + next: ListNode; // Ссылка на узел-преемник (указатель) + val: number; // Значение узла + + constructor(val: number) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* Двусторонняя очередь на основе двусвязного списка */ +class LinkedListDeque { + private front: ListNode; // Головной узел front + private rear: ListNode; // Хвостовой узел rear + private queSize: number; // Длина двусторонней очереди + + constructor() { + this.front = null; + this.rear = null; + this.queSize = 0; + } + + /* Операция добавления в хвост очереди */ + pushLast(val: number): void { + const node: ListNode = new ListNode(val); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // Добавить node в хвост списка + this.rear.next = node; + node.prev = this.rear; + this.rear = node; // Обновить хвостовой узел + } + this.queSize++; + } + + /* Операция добавления в голову очереди */ + pushFirst(val: number): void { + const node: ListNode = new ListNode(val); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // Добавить node в голову списка + this.front.prev = node; + node.next = this.front; + this.front = node; // Обновить головной узел + } + this.queSize++; + } + + /* Операция извлечения из хвоста очереди */ + popLast(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.rear.val; // Сохранить значение хвостового узла + // Удалить хвостовой узел + let temp: ListNode = this.rear.prev; + if (temp !== null) { + temp.next = null; + this.rear.prev = null; + } + this.rear = temp; // Обновить хвостовой узел + this.queSize--; + return value; + } + + /* Операция извлечения из головы очереди */ + popFirst(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.front.val; // Сохранить значение хвостового узла + // Удалить головной узел + let temp: ListNode = this.front.next; + if (temp !== null) { + temp.prev = null; + this.front.next = null; + } + this.front = temp; // Обновить головной узел + this.queSize--; + return value; + } + + /* Доступ к элементу в конце очереди */ + peekLast(): number { + return this.queSize === 0 ? null : this.rear.val; + } + + /* Доступ к элементу в начале очереди */ + peekFirst(): number { + return this.queSize === 0 ? null : this.front.val; + } + + /* Получение длины двусторонней очереди */ + size(): number { + return this.queSize; + } + + /* Проверка, пуста ли двусторонняя очередь */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* Вывести двустороннюю очередь */ + print(): void { + const arr: number[] = []; + let temp: ListNode = this.front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* Инициализация двусторонней очереди */ +const linkedListDeque: LinkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('Двусторонняя очередь linkedListDeque = '); +linkedListDeque.print(); + +/* Доступ к элементу */ +const peekFirst: number = linkedListDeque.peekFirst(); +console.log('Элемент в начале очереди peekFirst = ' + peekFirst); +const peekLast: number = linkedListDeque.peekLast(); +console.log('Элемент в конце очереди peekLast = ' + peekLast); + +/* Добавление элемента в очередь */ +linkedListDeque.pushLast(4); +console.log('После добавления элемента 4 в хвост linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('После добавления элемента 1 в голову linkedListDeque = '); +linkedListDeque.print(); + +/* Извлечение элемента из очереди */ +const popLast: number = linkedListDeque.popLast(); +console.log('Извлечен элемент из хвоста = ' + popLast + ', linkedListDeque после извлечения из хвоста = '); +linkedListDeque.print(); +const popFirst: number = linkedListDeque.popFirst(); +console.log('Извлечен элемент из головы = ' + popFirst + ', linkedListDeque после извлечения из головы = '); +linkedListDeque.print(); + +/* Получение длины двусторонней очереди */ +const size: number = linkedListDeque.size(); +console.log('Длина двусторонней очереди size = ' + size); + +/* Проверка, пуста ли двусторонняя очередь */ +const isEmpty: boolean = linkedListDeque.isEmpty(); +console.log('Пуста ли двусторонняя очередь = ' + isEmpty); diff --git a/ru/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts b/ru/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts new file mode 100644 index 000000000..fb0d2b066 --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts @@ -0,0 +1,102 @@ +/** + * File: linkedlist_queue.ts + * Created Time: 2022-12-19 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* Очередь на основе связного списка */ +class LinkedListQueue { + private front: ListNode | null; // Головной узел front + private rear: ListNode | null; // Хвостовой узел rear + private queSize: number = 0; + + constructor() { + this.front = null; + this.rear = null; + } + + /* Получение длины очереди */ + get size(): number { + return this.queSize; + } + + /* Проверка, пуста ли очередь */ + isEmpty(): boolean { + return this.size === 0; + } + + /* Поместить в очередь */ + push(num: number): void { + // Добавить num после хвостового узла + const node = new ListNode(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (!this.front) { + this.front = node; + this.rear = node; + // Если очередь не пуста, добавить этот узел после хвостового узла + } else { + this.rear!.next = node; + this.rear = node; + } + this.queSize++; + } + + /* Извлечь из очереди */ + pop(): number { + const num = this.peek(); + if (!this.front) throw new Error('очередь пуста'); + // Удалить головной узел + this.front = this.front.next; + this.queSize--; + return num; + } + + /* Доступ к элементу в начале очереди */ + peek(): number { + if (this.size === 0) throw new Error('очередь пуста'); + return this.front!.val; + } + + /* Преобразовать связный список в Array и вернуть */ + toArray(): number[] { + let node = this.front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* Инициализация очереди */ +const queue = new LinkedListQueue(); + +/* Добавление элемента в очередь */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('Очередь queue = ' + queue.toArray()); + +/* Доступ к элементу в начале очереди */ +const peek = queue.peek(); +console.log('Элемент в начале очереди peek = ' + peek); + +/* Извлечение элемента из очереди */ +const pop = queue.pop(); +console.log('Извлечен элемент pop = ' + pop + ', очередь после извлечения queue = ' + queue.toArray()); + +/* Получение длины очереди */ +const size = queue.size; +console.log('Длина очереди size = ' + size); + +/* Проверка, пуста ли очередь */ +const isEmpty = queue.isEmpty(); +console.log('Пуста ли очередь = ' + isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts b/ru/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts new file mode 100644 index 000000000..ac9ecd96f --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts @@ -0,0 +1,91 @@ +/** + * File: linkedlist_stack.ts + * Created Time: 2022-12-21 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* Стек на основе связного списка */ +class LinkedListStack { + private stackPeek: ListNode | null; // Использовать головной узел как вершину стека + private stkSize: number = 0; // Длина стека + + constructor() { + this.stackPeek = null; + } + + /* Получение длины стека */ + get size(): number { + return this.stkSize; + } + + /* Проверка, пуст ли стек */ + isEmpty(): boolean { + return this.size === 0; + } + + /* Поместить в стек */ + push(num: number): void { + const node = new ListNode(num); + node.next = this.stackPeek; + this.stackPeek = node; + this.stkSize++; + } + + /* Извлечь из стека */ + pop(): number { + const num = this.peek(); + if (!this.stackPeek) throw new Error('стек пуст'); + this.stackPeek = this.stackPeek.next; + this.stkSize--; + return num; + } + + /* Доступ к верхнему элементу стека */ + peek(): number { + if (!this.stackPeek) throw new Error('стек пуст'); + return this.stackPeek.val; + } + + /* Преобразовать связный список в Array и вернуть */ + toArray(): number[] { + let node = this.stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* Инициализация стека */ +const stack = new LinkedListStack(); + +/* Помещение элемента в стек */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('Стек stack = ' + stack.toArray()); + +/* Доступ к верхнему элементу стека */ +const peek = stack.peek(); +console.log('Верхний элемент стека peek = ' + peek); + +/* Извлечение элемента из стека */ +const pop = stack.pop(); +console.log('Извлечен элемент pop = ' + pop + ', стек после извлечения stack = ' + stack.toArray()); + +/* Получение длины стека */ +const size = stack.size; +console.log('Длина стека size = ' + size); + +/* Проверка на пустоту */ +const isEmpty = stack.isEmpty(); +console.log('Пуст ли стек = ' + isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/queue.ts b/ru/codes/typescript/chapter_stack_and_queue/queue.ts new file mode 100644 index 000000000..a7e569cfb --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/queue.ts @@ -0,0 +1,37 @@ +/** + * File: queue.ts + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* Инициализация очереди */ +// В TypeScript нет встроенной очереди, поэтому Array можно использовать как очередь +const queue: number[] = []; + +/* Добавление элемента в очередь */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('Очередь queue =', queue); + +/* Доступ к элементу в начале очереди */ +const peek = queue[0]; +console.log('Элемент в начале очереди peek =', peek); + +/* Извлечение элемента из очереди */ +// В основе лежит массив, поэтому временная сложность метода shift() равна O(n) +const pop = queue.shift(); +console.log('Извлечен элемент pop =', pop, ', очередь после извлечения queue = ', queue); + +/* Получение длины очереди */ +const size = queue.length; +console.log('Длина очереди size =', size); + +/* Проверка, пуста ли очередь */ +const isEmpty = queue.length === 0; +console.log('Пуста ли очередь = ', isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_stack_and_queue/stack.ts b/ru/codes/typescript/chapter_stack_and_queue/stack.ts new file mode 100644 index 000000000..818837b56 --- /dev/null +++ b/ru/codes/typescript/chapter_stack_and_queue/stack.ts @@ -0,0 +1,37 @@ +/** + * File: stack.ts + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* Инициализация стека */ +// В TypeScript нет встроенного класса стека, поэтому Array можно использовать как стек +const stack: number[] = []; + +/* Помещение элемента в стек */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('Стек stack =', stack); + +/* Доступ к верхнему элементу стека */ +const peek = stack[stack.length - 1]; +console.log('Верхний элемент стека peek =', peek); + +/* Извлечение элемента из стека */ +const pop = stack.pop(); +console.log('Извлечен элемент pop =', pop); +console.log('Стек после извлечения =', stack); + +/* Получение длины стека */ +const size = stack.length; +console.log('Длина стека size =', size); + +/* Проверка на пустоту */ +const isEmpty = stack.length === 0; +console.log('Пуст ли стек =', isEmpty); + +export {}; diff --git a/ru/codes/typescript/chapter_tree/array_binary_tree.ts b/ru/codes/typescript/chapter_tree/array_binary_tree.ts new file mode 100644 index 000000000..b9e490a9a --- /dev/null +++ b/ru/codes/typescript/chapter_tree/array_binary_tree.ts @@ -0,0 +1,151 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-09 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +type Order = 'pre' | 'in' | 'post'; + +/* Класс двоичного дерева в массивном представлении */ +class ArrayBinaryTree { + #tree: (number | null)[]; + + /* Конструктор */ + constructor(arr: (number | null)[]) { + this.#tree = arr; + } + + /* Вместимость списка */ + size(): number { + return this.#tree.length; + } + + /* Получить значение узла с индексом i */ + val(i: number): number | null { + // Если индекс выходит за границы, вернуть null, обозначающий пустую позицию + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* Получить индекс левого дочернего узла узла с индексом i */ + left(i: number): number { + return 2 * i + 1; + } + + /* Получить индекс правого дочернего узла узла с индексом i */ + right(i: number): number { + return 2 * i + 2; + } + + /* Получить индекс родительского узла узла с индексом i */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // Округление вниз при делении + } + + /* Обход в ширину */ + levelOrder(): number[] { + let res = []; + // Непосредственно обходить массив + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* Обход в глубину */ + #dfs(i: number, order: Order, res: (number | null)[]): void { + // Если это пустая позиция, вернуть + if (this.val(i) === null) return; + // Предварительный обход + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // Симметричный обход + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // Обратный обход + if (order === 'post') res.push(this.val(i)); + } + + /* Предварительный обход */ + preOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* Симметричный обход */ + inOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* Обратный обход */ + postOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// Инициализировать двоичное дерево +// Здесь используется функция, напрямую строящая двоичное дерево из массива +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\nИнициализация двоичного дерева\n'); +console.log('Массивное представление двоичного дерева:'); +console.log(arr); +console.log('Связное представление двоичного дерева:'); +printTree(root); + +// Класс двоичного дерева в массивном представлении +const abt = new ArrayBinaryTree(arr); + +// Доступ к узлу +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\nТекущий индекс узла = ' + i + ', значение = ' + abt.val(i)); +console.log( + 'Индекс левого дочернего узла = ' + l + ', значение = ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + 'Индекс правого дочернего узла = ' + r + ', значение = ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + 'Индекс родительского узла = ' + p + ', значение = ' + (p === null ? 'null' : abt.val(p)) +); + +// Обходить дерево +let res = abt.levelOrder(); +console.log('\nОбход в ширину: ' + res); +res = abt.preOrder(); +console.log('Предварительный обход: ' + res); +res = abt.inOrder(); +console.log('Симметричный обход: ' + res); +res = abt.postOrder(); +console.log('Обратный обход: ' + res); + +export {}; diff --git a/ru/codes/typescript/chapter_tree/avl_tree.ts b/ru/codes/typescript/chapter_tree/avl_tree.ts new file mode 100644 index 000000000..7609e84b9 --- /dev/null +++ b/ru/codes/typescript/chapter_tree/avl_tree.ts @@ -0,0 +1,222 @@ +/** + * File: avl_tree.ts + * Created Time: 2023-02-06 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* AVL-дерево */ +class AVLTree { + root: TreeNode; + /* Конструктор */ + constructor() { + this.root = null; // Корневой узел + } + + /* Получить высоту узла */ + height(node: TreeNode): number { + // Высота пустого узла равна -1, высота листового узла равна 0 + return node === null ? -1 : node.height; + } + + /* Обновить высоту узла */ + private updateHeight(node: TreeNode): void { + // Высота узла равна высоте более высокого поддерева + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* Получить коэффициент баланса */ + balanceFactor(node: TreeNode): number { + // Коэффициент баланса пустого узла равен 0 + if (node === null) return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return this.height(node.left) - this.height(node.right); + } + + /* Операция правого вращения */ + private rightRotate(node: TreeNode): TreeNode { + const child = node.left; + const grandChild = child.right; + // Выполнить правое вращение узла node вокруг child + child.right = node; + node.left = grandChild; + // Обновить высоту узла + this.updateHeight(node); + this.updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Операция левого вращения */ + private leftRotate(node: TreeNode): TreeNode { + const child = node.right; + const grandChild = child.left; + // Выполнить левое вращение узла node вокруг child + child.left = node; + node.right = grandChild; + // Обновить высоту узла + this.updateHeight(node); + this.updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + /* Выполнить вращение, чтобы снова сбалансировать поддерево */ + private rotate(node: TreeNode): TreeNode { + // Получить коэффициент баланса узла node + const balanceFactor = this.balanceFactor(node); + // Левосторонне перекошенное дерево + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // Правое вращение + return this.rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // Левое вращение + return this.leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node.right = this.rightRotate(node.right); + return this.leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + /* Вставка узла */ + insert(val: number): void { + this.root = this.insertHelper(this.root, val); + } + + /* Рекурсивная вставка узла (вспомогательный метод) */ + private insertHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return new TreeNode(val); + /* 1. Найти позицию вставки и вставить узел */ + if (val < node.val) { + node.left = this.insertHelper(node.left, val); + } else if (val > node.val) { + node.right = this.insertHelper(node.right, val); + } else { + return node; // Повторяющийся узел не вставлять, сразу вернуть + } + this.updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = this.rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Удаление узла */ + remove(val: number): void { + this.root = this.removeHelper(this.root, val); + } + + /* Рекурсивное удаление узла (вспомогательный метод) */ + private removeHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return null; + /* 1. Найти узел и удалить его */ + if (val < node.val) { + node.left = this.removeHelper(node.left, val); + } else if (val > node.val) { + node.right = this.removeHelper(node.right, val); + } else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child === null) { + return null; + } else { + // Число дочерних узлов = 1, удалить node напрямую + node = child; + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.updateHeight(node); // Обновить высоту узла + /* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */ + node = this.rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + /* Поиск узла */ + search(val: number): TreeNode { + let cur = this.root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + if (cur.val < val) { + // Целевой узел находится в правом поддереве cur + cur = cur.right; + } else if (cur.val > val) { + // Целевой узел находится в левом поддереве cur + cur = cur.left; + } else { + // Найти целевой узел и выйти из цикла + break; + } + } + // Вернуть целевой узел + return cur; + } +} + +function testInsert(tree: AVLTree, val: number): void { + tree.insert(val); + console.log('\nПосле вставки узла ' + val + ' AVL-дерево имеет вид'); + printTree(tree.root); +} + +function testRemove(tree: AVLTree, val: number): void { + tree.remove(val); + console.log('\nПосле удаления узла ' + val + ' AVL-дерево имеет вид'); + printTree(tree.root); +} + +/* Driver Code */ +/* Инициализация пустого AVL-дерева */ +const avlTree = new AVLTree(); +/* Вставка узла */ +// Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* Вставка повторяющегося узла */ +testInsert(avlTree, 7); + +/* Удаление узла */ +// Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла +testRemove(avlTree, 8); // Удаление узла степени 0 +testRemove(avlTree, 5); // Удаление узла степени 1 +testRemove(avlTree, 4); // Удаление узла степени 2 + +/* Поиск узла */ +const node = avlTree.search(7); +console.log('\nНайденный объект узла =', node, ', значение узла = ' + node.val); + +export {}; diff --git a/ru/codes/typescript/chapter_tree/binary_search_tree.ts b/ru/codes/typescript/chapter_tree/binary_search_tree.ts new file mode 100644 index 000000000..bdc034570 --- /dev/null +++ b/ru/codes/typescript/chapter_tree/binary_search_tree.ts @@ -0,0 +1,146 @@ +/** + * File: binary_search_tree.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Двоичное дерево поиска */ +class BinarySearchTree { + private root: TreeNode | null; + + /* Конструктор */ + constructor() { + // Инициализировать пустое дерево + this.root = null; + } + + /* Получить корневой узел двоичного дерева */ + getRoot(): TreeNode | null { + return this.root; + } + + /* Поиск узла */ + search(num: number): TreeNode | null { + let cur = this.root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Целевой узел находится в правом поддереве cur + if (cur.val < num) cur = cur.right; + // Целевой узел находится в левом поддереве cur + else if (cur.val > num) cur = cur.left; + // Найти целевой узел и выйти из цикла + else break; + } + // Вернуть целевой узел + return cur; + } + + /* Вставка узла */ + insert(num: number): void { + // Если дерево пусто, инициализировать корневой узел + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Найти повторяющийся узел и сразу вернуть + if (cur.val === num) return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur.val < num) cur = cur.right; + // Позиция вставки находится в левом поддереве cur + else cur = cur.left; + } + // Вставка узла + const node = new TreeNode(num); + if (pre!.val < num) pre!.right = node; + else pre!.left = node; + } + + /* Удаление узла */ + remove(num: number): void { + // Если дерево пусто, сразу вернуть + if (this.root === null) return; + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur !== null) { + // Найти узел для удаления и выйти из цикла + if (cur.val === num) break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur.val < num) cur = cur.right; + // Узел для удаления находится в левом поддереве cur + else cur = cur.left; + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur === null) return; + // Число дочерних узлов = 0 или 1 + if (cur.left === null || cur.right === null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + const child: TreeNode | null = + cur.left !== null ? cur.left : cur.right; + // Удалить узел cur + if (cur !== this.root) { + if (pre!.left === cur) pre!.left = child; + else pre!.right = child; + } else { + // Если удаляемый узел является корнем, заново назначить корневой узел + this.root = child; + } + } + // Число дочерних узлов = 2 + else { + // Получить следующий узел после cur в симметричном обходе + let tmp: TreeNode | null = cur.right; + while (tmp!.left !== null) { + tmp = tmp!.left; + } + // Рекурсивно удалить узел tmp + this.remove(tmp!.val); + // Перезаписать cur значением tmp + cur.val = tmp!.val; + } + } +} + +/* Driver Code */ +/* Инициализация двоичного дерева поиска */ +const bst = new BinarySearchTree(); +// Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\nИнициализированное двоичное дерево\n'); +printTree(bst.getRoot()); + +/* Поиск узла */ +const node = bst.search(7); +console.log( + '\nНайденный объект узла = ' + node + ', значение узла = ' + (node ? node.val : 'null') +); + +/* Вставка узла */ +bst.insert(16); +console.log('\nПосле вставки узла 16 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); + +/* Удаление узла */ +bst.remove(1); +console.log('\nПосле удаления узла 1 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\nПосле удаления узла 2 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\nПосле удаления узла 4 двоичное дерево имеет вид\n'); +printTree(bst.getRoot()); + +export {}; diff --git a/ru/codes/typescript/chapter_tree/binary_tree.ts b/ru/codes/typescript/chapter_tree/binary_tree.ts new file mode 100644 index 000000000..dc3004be4 --- /dev/null +++ b/ru/codes/typescript/chapter_tree/binary_tree.ts @@ -0,0 +1,37 @@ +/** + * File: binary_tree.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Инициализация двоичного дерева */ +// Инициализация узла +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// Построить связи между узлами (указатели) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\nИнициализация двоичного дерева\n'); +printTree(n1); + +/* Вставка и удаление узлов */ +const P = new TreeNode(0); +// Вставить узел P между n1 -> n2 +n1.left = P; +P.left = n2; +console.log('\nПосле вставки узла P\n'); +printTree(n1); +// Удалить узел P +n1.left = n2; +console.log('\nПосле удаления узла P\n'); +printTree(n1); + +export {}; diff --git a/ru/codes/typescript/chapter_tree/binary_tree_bfs.ts b/ru/codes/typescript/chapter_tree/binary_tree_bfs.ts new file mode 100644 index 000000000..9de0601ed --- /dev/null +++ b/ru/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -0,0 +1,41 @@ +/** + * File: binary_tree_bfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* Обход в ширину */ +function levelOrder(root: TreeNode | null): number[] { + // Инициализировать очередь и добавить корневой узел + const queue = [root]; + // Инициализировать список для хранения последовательности обхода + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // Извлечение из очереди + list.push(node.val); // Сохранить значение узла + if (node.left) { + queue.push(node.left); // Поместить левый дочерний узел в очередь + } + if (node.right) { + queue.push(node.right); // Поместить правый дочерний узел в очередь + } + } + return list; +} + +/* Driver Code */ +/* Инициализация двоичного дерева */ +// Здесь используется функция, напрямую строящая двоичное дерево из массива +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева\n'); +printTree(root); + +/* Обход в ширину */ +const list = levelOrder(root); +console.log('\nПоследовательность печати узлов при обходе в ширину = ' + list); + +export {}; diff --git a/ru/codes/typescript/chapter_tree/binary_tree_dfs.ts b/ru/codes/typescript/chapter_tree/binary_tree_dfs.ts new file mode 100644 index 000000000..370747f8e --- /dev/null +++ b/ru/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +// Инициализировать список для хранения последовательности обхода +const list: number[] = []; + +/* Предварительный обход */ +function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // Порядок обхода: корень -> левое поддерево -> правое поддерево + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* Симметричный обход */ +function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // Порядок обхода: левое поддерево -> корень -> правое поддерево + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* Обратный обход */ +function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // Порядок обхода: левое поддерево -> правое поддерево -> корень + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* Инициализация двоичного дерева */ +// Здесь используется функция, напрямую строящая двоичное дерево из массива +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\nИнициализация двоичного дерева\n'); +printTree(root); + +/* Предварительный обход */ +list.length = 0; +preOrder(root); +console.log('\nПоследовательность печати узлов при предварительном обходе = ' + list); + +/* Симметричный обход */ +list.length = 0; +inOrder(root); +console.log('\nПоследовательность печати узлов при симметричном обходе = ' + list); + +/* Обратный обход */ +list.length = 0; +postOrder(root); +console.log('\nПоследовательность печати узлов при обратном обходе = ' + list); + +export {}; diff --git a/ru/codes/typescript/modules/ListNode.ts b/ru/codes/typescript/modules/ListNode.ts new file mode 100644 index 000000000..ba514b534 --- /dev/null +++ b/ru/codes/typescript/modules/ListNode.ts @@ -0,0 +1,28 @@ +/** + * File: ListNode.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Узел связного списка */ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* Десериализовать массив в связный список */ +function arrToLinkedList(arr: number[]): ListNode | null { + const dum: ListNode = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +export { ListNode, arrToLinkedList }; diff --git a/ru/codes/typescript/modules/PrintUtil.ts b/ru/codes/typescript/modules/PrintUtil.ts new file mode 100644 index 000000000..4851d80a1 --- /dev/null +++ b/ru/codes/typescript/modules/PrintUtil.ts @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from './ListNode'; +import { TreeNode, arrToTree } from './TreeNode'; + +/* Вывести связный список */ +function printLinkedList(head: ListNode | null): void { + const list: string[] = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +class Trunk { + prev: Trunk | null; + str: string; + + constructor(prev: Trunk | null, str: string) { + this.prev = prev; + this.str = str; + } +} + +/** + * Вывести двоичное дерево + * Этот вывод дерева заимствован из TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root: TreeNode | null) { + printTreeHelper(root, null, false); +} + +/* Вывести двоичное дерево */ +function printTreeHelper( + root: TreeNode | null, + prev: Trunk | null, + isRight: boolean +) { + if (root === null) { + return; + } + + let prev_str = ' '; + const trunk = new Trunk(prev, prev_str); + + printTreeHelper(root.right, trunk, true); + + if (prev === null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTreeHelper(root.left, trunk, false); +} + +function showTrunks(p: Trunk | null) { + if (p === null) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* Вывести кучу */ +function printHeap(arr: number[]): void { + console.log('Массивное представление кучи:'); + console.log(arr); + console.log('Древовидное представление кучи:'); + const root = arrToTree(arr); + printTree(root); +} + +export { printLinkedList, printTree, printHeap }; diff --git a/ru/codes/typescript/modules/TreeNode.ts b/ru/codes/typescript/modules/TreeNode.ts new file mode 100644 index 000000000..9acb40c2f --- /dev/null +++ b/ru/codes/typescript/modules/TreeNode.ts @@ -0,0 +1,37 @@ +/** + * File: TreeNode.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Узел двоичного дерева */ +class TreeNode { + val: number; // Значение узла + height: number; // Высота узла + left: TreeNode | null; // Указатель на левый дочерний узел + right: TreeNode | null; // Указатель на правый дочерний узел + constructor( + val?: number, + height?: number, + left?: TreeNode | null, + right?: TreeNode | null + ) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } +} + +/* Десериализовать массив в двоичное дерево */ +function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +export { TreeNode, arrToTree }; diff --git a/ru/codes/typescript/modules/Vertex.ts b/ru/codes/typescript/modules/Vertex.ts new file mode 100644 index 000000000..146e3f3df --- /dev/null +++ b/ru/codes/typescript/modules/Vertex.ts @@ -0,0 +1,33 @@ +/** + * File: Vertex.ts + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Класс вершины */ +class Vertex { + val: number; + constructor(val: number) { + this.val = val; + } + + /* На вход подается список значений vals, на выходе возвращается список вершин vets */ + public static valsToVets(vals: number[]): Vertex[] { + const vets: Vertex[] = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* На вход подается список вершин vets, на выходе возвращается список значений vals */ + public static vetsToVals(vets: Vertex[]): number[] { + const vals: number[] = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +export { Vertex }; diff --git a/ru/codes/typescript/package.json b/ru/codes/typescript/package.json new file mode 100644 index 000000000..ae81c64fe --- /dev/null +++ b/ru/codes/typescript/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "scripts": { + "check": "tsc" + }, + "devDependencies": { + "@types/node": "^24.9.2", + "typescript": "^5.9.3" + } +} diff --git a/ru/codes/typescript/tsconfig.json b/ru/codes/typescript/tsconfig.json new file mode 100644 index 000000000..954efb8e7 --- /dev/null +++ b/ru/codes/typescript/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "esnext", + "moduleResolution": "node", + "types": ["@types/node"], + "noEmit": true, + "target": "esnext", + }, + "include": ["chapter_*/*.ts"], + "exclude": ["node_modules"] +} diff --git a/ru/codes/zig/.gitignore b/ru/codes/zig/.gitignore new file mode 100644 index 000000000..76bab71be --- /dev/null +++ b/ru/codes/zig/.gitignore @@ -0,0 +1,4 @@ +zig-out +zig-cache +.zig-cache +!/.vscode/ \ No newline at end of file diff --git a/ru/codes/zig/.vscode/launch.json b/ru/codes/zig/.vscode/launch.json new file mode 100644 index 000000000..cfeec2388 --- /dev/null +++ b/ru/codes/zig/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/zig-out/bin/${fileBasenameNoExtension}", + "args": [], + "cwd": "${workspaceFolder}", + "preLaunchTask": "build" + } + ] +} \ No newline at end of file diff --git a/ru/codes/zig/.vscode/settings.json b/ru/codes/zig/.vscode/settings.json new file mode 100644 index 000000000..a1ccb9aa3 --- /dev/null +++ b/ru/codes/zig/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "zig.testArgs": ["build", "test", "-Dtest-filter=${filter}"] +} \ No newline at end of file diff --git a/ru/codes/zig/.vscode/tasks.json b/ru/codes/zig/.vscode/tasks.json new file mode 100644 index 000000000..a66ac10e2 --- /dev/null +++ b/ru/codes/zig/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "zig build", + } + ] +} \ No newline at end of file diff --git a/ru/codes/zig/build.zig b/ru/codes/zig/build.zig new file mode 100644 index 000000000..ff86b619e --- /dev/null +++ b/ru/codes/zig/build.zig @@ -0,0 +1,169 @@ +// File: build.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +//! Zig Version: 0.14.1 +//! Build Command: zig build +//! Run Command: zig build run | zig build run_* +//! Test Command: zig build test | zig build test -Dtest-filter=* + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const chapters = [_][]const u8{ + "chapter_computational_complexity", + "chapter_array_and_linkedlist", + "chapter_stack_and_queue", + "chapter_hashing", + "chapter_tree", + "chapter_heap", + "chapter_searching", + "chapter_sorting", + "chapter_dynamic_programming", + }; + + const test_step = b.step("test", "Run unit tests"); + const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; + + buildChapterExeModules(b, target, optimize, &chapters, test_step, test_filters); + buildMainExeModule(b, target, optimize); +} + +fn buildChapterExeModules( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + chapter_dirs: []const []const u8, + test_step: *std.Build.Step, + test_filters: []const []const u8, +) void { + for (chapter_dirs) |chapter_dir_name| { + const chapter_dir_path = std.fs.path.join(b.allocator, &[_][]const u8{chapter_dir_name}) catch continue; + var chapter_dir = std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true }) catch continue; + defer chapter_dir.close(); + + var it = chapter_dir.iterate(); + while (it.next() catch continue) |chapter_dir_entry| { + if (chapter_dir_entry.kind != .file or !std.mem.endsWith(u8, chapter_dir_entry.name, ".zig")) continue; + const exe_mod = buildExeModuleFromChapterDirEntry(b, target, optimize, chapter_dir_name, chapter_dir_entry) catch continue; + addTestStepToExeModule(b, test_step, exe_mod, test_filters); + } + } +} + +fn buildExeModuleFromChapterDirEntry( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + chapter_dir_name: []const u8, + chapter_dir_entry: std.fs.Dir.Entry, +) !*std.Build.Module { + const zig_file_path = try std.fs.path.join(b.allocator, &[_][]const u8{ chapter_dir_name, chapter_dir_entry.name }); + const zig_file_name = chapter_dir_entry.name[0 .. chapter_dir_entry.name.len - 4]; // abstract zig file name from xxx.zig + + // Здесь временно добавлены только главы про массивы и связные списки; после доработки будут открыты все + const new_algo_names = [_][]const u8{ + "array", + "linked_list", + "list", + "my_list", + "iteration", + "recursion", + "space_complexity", + "time_complexity", + "worst_best_time_complexity", + }; + var can_run = false; + for (new_algo_names) |name| { + if (std.mem.eql(u8, zig_file_name, name)) { + can_run = true; + } + } + if (!can_run) { + return error.CanNotRunUseOldZigCodes; + } + + // std.debug.print("now run zig file name = {s}\n", .{zig_file_name}); + + const exe_mod = b.createModule(.{ + .root_source_file = b.path(zig_file_path), + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = zig_file_name, + .root_module = exe_mod, + }); + + const utils_mod = createUtilsModule(b, target, optimize); + exe_mod.addImport("utils", utils_mod); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const step_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{zig_file_name}); + const step_desc = try std.fmt.allocPrint(b.allocator, "Run {s}/{s}.zig", .{ chapter_dir_name, zig_file_name }); + const run_step = b.step(step_name, step_desc); + run_step.dependOn(&run_cmd.step); + + return exe_mod; +} + +fn buildMainExeModule( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) void { + const exe_mod = b.createModule(.{ + .root_source_file = b.path("main.zig"), + .target = target, + .optimize = optimize, + }); + + const utils_mod = createUtilsModule(b, target, optimize); + exe_mod.addImport("utils", utils_mod); + + const exe = b.addExecutable(.{ + .name = "main", + .root_module = exe_mod, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run all hello algo zig"); + run_step.dependOn(&run_cmd.step); +} + +fn createUtilsModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { + const utils_mod = b.createModule(.{ + .root_source_file = b.path("utils/utils.zig"), + .target = target, + .optimize = optimize, + }); + return utils_mod; +} + +fn addTestStepToExeModule(b: *std.Build, test_step: *std.Build.Step, exe_mod: *std.Build.Module, test_filters: []const []const u8) void { + const exe_unit_tests = b.addTest(.{ + .root_module = exe_mod, + .filters = test_filters, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/ru/codes/zig/chapter_array_and_linkedlist/array.zig b/ru/codes/zig/chapter_array_and_linkedlist/array.zig new file mode 100644 index 000000000..f36fd0365 --- /dev/null +++ b/ru/codes/zig/chapter_array_and_linkedlist/array.zig @@ -0,0 +1,131 @@ +// File: array.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// Случайный доступ к элементу +pub fn randomAccess(nums: []const i32) i32 { + // Случайным образом выбрать целое число из интервала [0, nums.len) + const random_index = std.crypto.random.intRangeLessThan(usize, 0, nums.len); + // Получить и вернуть случайный элемент + const randomNum = nums[random_index]; + return randomNum; +} + +// Увеличить длину массива +pub fn extend(allocator: std.mem.Allocator, nums: []const i32, enlarge: usize) ![]i32 { + // Инициализировать массив увеличенной длины + const res = try allocator.alloc(i32, nums.len + enlarge); + @memset(res, 0); + + // Скопировать все элементы исходного массива в новый массив + std.mem.copyForwards(i32, res, nums); + + // Вернуть новый массив после расширения + return res; +} + +// Вставить элемент num по индексу index в массив +pub fn insert(nums: []i32, num: i32, index: usize) void { + // Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад + var i = nums.len - 1; + while (i > index) : (i -= 1) { + nums[i] = nums[i - 1]; + } + // Присвоить num элементу по индексу index + nums[index] = num; +} + +// Удалить элемент по индексу index +pub fn remove(nums: []i32, index: usize) void { + // Сдвинуть все элементы после индекса index на одну позицию вперед + var i = index; + while (i < nums.len - 1) : (i += 1) { + nums[i] = nums[i + 1]; + } +} + +// Обход массива +pub fn traverse(nums: []const i32) void { + var count: i32 = 0; + + // Обход массива по индексам + var i: usize = 0; + while (i < nums.len) : (i += 1) { + count += nums[i]; + } + + // Непосредственно обходить элементы массива + count = 0; + for (nums) |num| { + count += num; + } + + // Одновременно обходить индексы и элементы данных + for (nums, 0..) |num, index| { + count += nums[index]; + count += num; + } +} + +// Найти заданный элемент в массиве +pub fn find(nums: []i32, target: i32) i32 { + for (nums, 0..) |num, i| { + if (num == target) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn run() !void { + // Инициализация массива + const arr = [_]i32{0} ** 5; + std.debug.print("Массив arr = {}\n", .{utils.fmt.slice(&arr)}); + + // Срез массива + var array = [_]i32{ 1, 3, 2, 5, 4 }; + var known_at_runtime_zero: usize = 0; + _ = &known_at_runtime_zero; + var nums = array[known_at_runtime_zero..array.len]; // Превратить указатель в срез через известную во время выполнения переменную known_at_runtime_zero + std.debug.print("Массив nums = {}\n", .{utils.fmt.slice(nums)}); + + // Случайный доступ + const randomNum = randomAccess(nums); + std.debug.print("Случайный элемент из nums = {}\n", .{randomNum}); + + // Инициализация аллокатора памяти + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // Расширение длины + nums = try extend(allocator, nums, 3); + std.debug.print("После расширения длины массива до 8 nums = {}\n", .{utils.fmt.slice(nums)}); + + // Вставка элемента + insert(nums, 6, 3); + std.debug.print("После вставки числа 6 по индексу 3 nums = {}\n", .{utils.fmt.slice(nums)}); + + // Удаление элемента + remove(nums, 2); + std.debug.print("После удаления элемента по индексу 2 nums = {}\n", .{utils.fmt.slice(nums)}); + + // Обход массива + traverse(nums); + + // Поиск элемента + const index = find(nums, 3); + std.debug.print("Поиск элемента 3 в nums: индекс = {}\n", .{index}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "array" { + try run(); +} diff --git a/ru/codes/zig/chapter_array_and_linkedlist/linked_list.zig b/ru/codes/zig/chapter_array_and_linkedlist/linked_list.zig new file mode 100644 index 000000000..e9e3acd54 --- /dev/null +++ b/ru/codes/zig/chapter_array_and_linkedlist/linked_list.zig @@ -0,0 +1,96 @@ +// File: linked_list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); +const ListNode = utils.ListNode; + +// Вставить узел P после узла n0 в связном списке +pub fn insert(comptime T: type, n0: *ListNode(T), P: *ListNode(T)) void { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +// Удалить первый узел после узла n0 в связном списке +pub fn remove(comptime T: type, n0: *ListNode(T)) void { + // n0 -> P -> n1 => n0 -> n1 + const P = n0.next; + const n1 = P.?.next; + n0.next = n1; +} + +// Доступ к узлу связного списка по индексу index +pub fn access(comptime T: type, node: *ListNode(T), index: i32) ?*ListNode(T) { + var head: ?*ListNode(T) = node; + var i: i32 = 0; + while (i < index) : (i += 1) { + if (head) |cur| { + head = cur.next; + } else { + return null; + } + } + return head; +} + +// Найти в связном списке первый узел со значением target +pub fn find(comptime T: type, node: *ListNode(T), target: T) i32 { + var head: ?*ListNode(T) = node; + var index: i32 = 0; + while (head) |cur| { + if (cur.val == target) return index; + head = cur.next; + index += 1; + } + return -1; +} + +// Driver Code +pub fn run() void { + // Инициализация всех узлов + var n0 = ListNode(i32){ .val = 1 }; + var n1 = ListNode(i32){ .val = 3 }; + var n2 = ListNode(i32){ .val = 2 }; + var n3 = ListNode(i32){ .val = 5 }; + var n4 = ListNode(i32){ .val = 4 }; + // Построить ссылки между узлами + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + std.debug.print( + "Инициализированный связный список = {}\n",\n .{utils.fmt.linkedList(i32, &n0)},\n); + + // Вставка узла + var tmp = ListNode(i32){ .val = 0 }; + insert(i32, &n0, &tmp); + std.debug.print( + "Связный список после вставки узла = {}\n",\n .{utils.fmt.linkedList(i32, &n0)},\n); + + // Удаление узла + remove(i32, &n0); + std.debug.print( + "Связный список после удаления узла = {}\n",\n .{utils.fmt.linkedList(i32, &n0)},\n); + + // Доступ к узлу + const node = access(i32, &n0, 3); + std.debug.print( + "Значение узла по индексу 3 в связном списке = {}\n",\n .{node.?.val},\n); + + // Поиск узла + const index = find(i32, &n0, 2); + std.debug.print( + "Индекс узла со значением 2 в связном списке = {}\n",\n .{index},\n); + + std.debug.print("\n", .{}); +} + +pub fn main() void { + run(); +} + +test "linked_list" { + run(); +} diff --git a/ru/codes/zig/chapter_array_and_linkedlist/list.zig b/ru/codes/zig/chapter_array_and_linkedlist/list.zig new file mode 100644 index 000000000..819e5aa3f --- /dev/null +++ b/ru/codes/zig/chapter_array_and_linkedlist/list.zig @@ -0,0 +1,78 @@ +// File: list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// Driver Code +pub fn run() !void { + // Инициализация списка + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); // Отложенное освобождение памяти + + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("Список nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Доступ к элементу + const num = nums.items[1]; + std.debug.print("Элемент по индексу 1: num = {}\n", .{num}); + + // Обновление элемента + nums.items[1] = 0; + std.debug.print("После обновления элемента по индексу 1 до 0 nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Очистить список + nums.clearRetainingCapacity(); + std.debug.print("После очистки списка nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Добавление элемента в конец + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + std.debug.print("После добавления элементов nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Вставка элемента в середину + try nums.insert(3, 6); + std.debug.print("После вставки числа 6 по индексу 3 nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Удаление элемента + _ = nums.orderedRemove(3); + std.debug.print("После удаления элемента по индексу 3 nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Обходить список по индексам + var count: i32 = 0; + var i: usize = 0; + while (i < nums.items.len) : (i += 1) { + count += nums.items[i]; + } + + // Непосредственно обходить элементы списка + count = 0; + for (nums.items) |x| { + count += x; + } + + // Объединить два списка + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); + std.debug.print("После конкатенации списка nums1 к nums nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // Отсортировать список + std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + std.debug.print("После сортировки списка nums = {}\n", .{utils.fmt.slice(nums.items)}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "list" { + try run(); +} diff --git a/ru/codes/zig/chapter_array_and_linkedlist/my_list.zig b/ru/codes/zig/chapter_array_and_linkedlist/my_list.zig new file mode 100644 index 000000000..64af5de03 --- /dev/null +++ b/ru/codes/zig/chapter_array_and_linkedlist/my_list.zig @@ -0,0 +1,217 @@ +// File: my_list.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// Класс списка +const MyList = struct { + const Self = @This(); + + items: []i32, // Массив (для хранения элементов списка) + capacity: usize, // Вместимость списка + allocator: std.mem.Allocator, // Аллокатор памяти + + extend_ratio: usize = 2, // Коэффициент увеличения списка при каждом расширении + + // Конструктор (выделение памяти + инициализация списка) + pub fn init(allocator: std.mem.Allocator) Self { + return Self{ + .items = &[_]i32{}, + .capacity = 0, + .allocator = allocator, + }; + } + + // Деструктор (освобождение памяти) + pub fn deinit(self: Self) void { + self.allocator.free(self.allocatedSlice()); + } + + // Добавление элемента в конец + pub fn add(self: *Self, item: i32) !void { + // При превышении вместимости по числу элементов запускается расширение + const newlen = self.items.len + 1; + try self.ensureTotalCapacity(newlen); + + // Обновление элемента + self.items.len += 1; + const new_item_ptr = &self.items[self.items.len - 1]; + new_item_ptr.* = item; + } + + // Получить длину списка (текущее число элементов) + pub fn getSize(self: *Self) usize { + return self.items.len; + } + + // Получить вместимость списка + pub fn getCapacity(self: *Self) usize { + return self.capacity; + } + + // Доступ к элементу + pub fn get(self: *Self, index: usize) i32 { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 or index >= self.items.len) { + @panic("индекс выходит за границы"); + } + return self.items[index]; + } + + // Обновление элемента + pub fn set(self: *Self, index: usize, num: i32) void { + // Если индекс выходит за границы, выбрасывается исключение; далее аналогично + if (index < 0 or index >= self.items.len) { + @panic("индекс выходит за границы"); + } + self.items[index] = num; + } + + // Вставка элемента в середину + pub fn insert(self: *Self, index: usize, item: i32) !void { + if (index < 0 or index >= self.items.len) { + @panic("индекс выходит за границы"); + } + + // При превышении вместимости по числу элементов запускается расширение + const newlen = self.items.len + 1; + try self.ensureTotalCapacity(newlen); + + // Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад + self.items.len += 1; + var i = self.items.len - 1; + while (i >= index) : (i -= 1) { + self.items[i] = self.items[i - 1]; + } + self.items[index] = item; + } + + // Удаление элемента + pub fn remove(self: *Self, index: usize) i32 { + if (index < 0 or index >= self.getSize()) { + @panic("индекс выходит за границы"); + } + // Сдвинуть все элементы после индекса index на одну позицию вперед + const item = self.items[index]; + var i = index; + while (i < self.items.len - 1) : (i += 1) { + self.items[i] = self.items[i + 1]; + } + self.items.len -= 1; + // Вернуть удаленный элемент + return item; + } + + // Преобразовать список в массив + pub fn toArraySlice(self: *Self) ![]i32 { + return self.toOwnedSlice(false); + } + + // Вернуть новый срез и указать, нужно ли сбросить или очистить контейнер списка + pub fn toOwnedSlice(self: *Self, clear: bool) ![]i32 { + const allocator = self.allocator; + const old_memory = self.allocatedSlice(); + if (allocator.remap(old_memory, self.items.len)) |new_items| { + if (clear) { + self.* = init(allocator); + } + return new_items; + } + + const new_memory = try allocator.alloc(i32, self.items.len); + @memcpy(new_memory, self.items); + if (clear) { + self.clearAndFree(); + } + return new_memory; + } + + // Расширение списка + fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { + if (self.capacity >= new_capacity) return; + const capcacity = if (self.capacity == 0) 10 else self.capacity; + const better_capacity = capcacity * self.extend_ratio; + + const old_memory = self.allocatedSlice(); + if (self.allocator.remap(old_memory, better_capacity)) |new_memory| { + self.items.ptr = new_memory.ptr; + self.capacity = new_memory.len; + } else { + const new_memory = try self.allocator.alloc(i32, better_capacity); + @memcpy(new_memory[0..self.items.len], self.items); + self.allocator.free(old_memory); + self.items.ptr = new_memory.ptr; + self.capacity = new_memory.len; + } + } + + fn clearAndFree(self: *Self, allocator: std.mem.Allocator) void { + allocator.free(self.allocatedSlice()); + self.items.len = 0; + self.capacity = 0; + } + + fn allocatedSlice(self: Self) []i32 { + return self.items.ptr[0..self.capacity]; + } +}; + +// Driver Code +pub fn run() !void { + var gpa = std.heap.DebugAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + // Инициализация списка + var nums = MyList.init(allocator); + // Отложенное освобождение памяти + defer nums.deinit(); + + // Добавление элемента в конец + try nums.add(1); + try nums.add(3); + try nums.add(2); + try nums.add(5); + try nums.add(4); + std.debug.print("Список nums = {}, вместимость = {}, длина = {}\n", .{\n utils.fmt.slice(nums.items),\n nums.getCapacity(),\n nums.getSize(),\n}); + + // Вставка элемента в середину + try nums.insert(3, 6); + std.debug.print( + "После вставки числа 6 по индексу 3 получаем nums = {}\n",\n .{utils.fmt.slice(nums.items)},\n); + + // Удаление элемента + _ = nums.remove(3); + std.debug.print( + "После удаления элемента по индексу 3 получаем nums = {}\n",\n .{utils.fmt.slice(nums.items)},\n); + + // Доступ к элементу + const num = nums.get(1); + std.debug.print("Элемент по индексу 1: num = {}\n", .{num}); + + // Обновление элемента + nums.set(1, 0); + std.debug.print( + "После обновления элемента по индексу 1 значением 0 получаем nums = {}\n",\n .{utils.fmt.slice(nums.items)},\n); + + // Проверка механизма расширения + var i: i32 = 0; + while (i < 10) : (i += 1) { + // При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения + try nums.add(i); + } + std.debug.print( + "Список nums после увеличения вместимости = {}, вместимость = {}, длина = {}\n",\n .{\n utils.fmt.slice(nums.items),\n nums.getCapacity(),\n nums.getSize(),\n },\n); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "my_list" { + try run(); +} diff --git a/ru/codes/zig/chapter_computational_complexity/iteration.zig b/ru/codes/zig/chapter_computational_complexity/iteration.zig new file mode 100644 index 000000000..724b91766 --- /dev/null +++ b/ru/codes/zig/chapter_computational_complexity/iteration.zig @@ -0,0 +1,91 @@ +// File: iteration.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +// Цикл for +fn forLoop(n: usize) i32 { + var res: i32 = 0; + // Циклическое суммирование 1, 2, ..., n-1, n + for (1..n + 1) |i| { + res += @intCast(i); + } + return res; +} + +// Цикл while +fn whileLoop(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 2, ..., n-1, n + while (i <= n) : (i += 1) { + res += @intCast(i); + } + return res; +} + +// Цикл while (двойное обновление) +fn whileLoopII(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // Инициализация условной переменной + // Циклическое суммирование 1, 4, 10, ... + while (i <= n) : ({ + // Обновить условную переменную + i += 1; + i *= 2; + }) { + res += @intCast(i); + } + return res; +} + +// Двойной цикл for +fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + defer res.deinit(); + var buffer: [20]u8 = undefined; + // Цикл по i = 1, 2, ..., n-1, n + for (1..n + 1) |i| { + // Цикл по j = 1, 2, ..., n-1, n + for (1..n + 1) |j| { + const str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{ i, j }); + try res.appendSlice(str); + } + } + return res.toOwnedSlice(); +} + +// Driver Code +pub fn run() !void { + var gpa = std.heap.DebugAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const n: i32 = 5; + var res: i32 = 0; + + res = forLoop(n); + std.debug.print("Результат суммирования в цикле for res = {}\n", .{res}); + + res = whileLoop(n); + std.debug.print("Результат суммирования в цикле while res = {}\n", .{res}); + + res = whileLoopII(n); + std.debug.print("Результат суммирования в цикле while (двойное обновление) res = {}\n", .{res}); + + const resStr = try nestedForLoop(allocator, n); + std.debug.print("Результат обхода в двойном цикле for {s}\n", .{resStr}); + allocator.free(resStr); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "interation" { + try run(); +} diff --git a/ru/codes/zig/chapter_computational_complexity/recursion.zig b/ru/codes/zig/chapter_computational_complexity/recursion.zig new file mode 100644 index 000000000..9e9a40505 --- /dev/null +++ b/ru/codes/zig/chapter_computational_complexity/recursion.zig @@ -0,0 +1,88 @@ +// File: recursion.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// Рекурсивная функция +fn recur(n: i32) i32 { + // Условие завершения + if (n == 1) { + return 1; + } + // Рекурсия: рекурсивный вызов + const res = recur(n - 1); + // Возврат: вернуть результат + return n + res; +} + +// Имитация рекурсии итерацией +fn forLoopRecur(comptime n: i32) i32 { + // Использовать явный стек для имитации системного стека вызовов + var stack: [n]i32 = undefined; + var res: i32 = 0; + // Рекурсия: рекурсивный вызов + var i: usize = n; + while (i > 0) { + stack[i - 1] = @intCast(i); + i -= 1; + } + // Возврат: вернуть результат + var index: usize = n; + while (index > 0) { + index -= 1; + res += stack[index]; + } + // res = 1+2+3+...+n + return res; +} + +// Хвосторекурсивная функция +fn tailRecur(n: i32, res: i32) i32 { + // Условие завершения + if (n == 0) { + return res; + } + // Хвостовой рекурсивный вызов + return tailRecur(n - 1, res + n); +} + +// Числа Фибоначчи +fn fib(n: i32) i32 { + // Условие завершения: f(1) = 0, f(2) = 1 + if (n == 1 or n == 2) { + return n - 1; + } + // Рекурсивный вызов f(n) = f(n-1) + f(n-2) + const res: i32 = fib(n - 1) + fib(n - 2); + // Вернуть результат f(n) + return res; +} + +// Driver Code +pub fn run() void { + const n: i32 = 5; + var res: i32 = 0; + + res = recur(n); + std.debug.print("Результат суммирования в рекурсивной функции res = {}\n", .{recur(n)}); + + res = forLoopRecur(n); + std.debug.print("Результат суммирования при имитации рекурсии итерацией res = {}\n", .{forLoopRecur(n)}); + + res = tailRecur(n, 0); + std.debug.print("Результат суммирования в хвостовой рекурсии res = {}\n", .{tailRecur(n, 0)}); + + res = fib(n); + std.debug.print("Член последовательности Фибоначчи с номером {} = {}\n", .{ n, fib(n) }); + + std.debug.print("\n", .{}); +} + +pub fn main() void { + run(); +} + +test "recursion" { + run(); +} diff --git a/ru/codes/zig/chapter_computational_complexity/space_complexity.zig b/ru/codes/zig/chapter_computational_complexity/space_complexity.zig new file mode 100644 index 000000000..035f7b8f0 --- /dev/null +++ b/ru/codes/zig/chapter_computational_complexity/space_complexity.zig @@ -0,0 +1,142 @@ +// File: space_complexity.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); +const ListNode = utils.ListNode; +const TreeNode = utils.TreeNode; + +// Функция +fn function() i32 { + // Выполнить некоторые операции + return 0; +} + +// Постоянная сложность +fn constant(n: i32) void { + // Константы, переменные и объекты занимают O(1) памяти + const a: i32 = 0; + const b: i32 = 0; + const nums = [_]i32{0} ** 10000; + const node = ListNode(i32){ .val = 0 }; + var i: i32 = 0; + // Переменные в цикле занимают O(1) памяти + while (i < n) : (i += 1) { + const c: i32 = 0; + _ = c; + } + // Функции в цикле занимают O(1) памяти + i = 0; + while (i < n) : (i += 1) { + _ = function(); + } + _ = a; + _ = b; + _ = nums; + _ = node; +} + +// Линейная сложность +fn linear(comptime n: i32) !void { + // Массив длины n занимает O(n) памяти + const nums = [_]i32{0} ** n; + // Список длины n занимает O(n) памяти + var nodes = std.ArrayList(i32).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + try nodes.append(i); + } + // Хеш-таблица длины n занимает O(n) памяти + var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); + defer map.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); + defer std.heap.page_allocator.free(string); + try map.put(i, string); + } + _ = nums; +} + +// Линейная сложность (рекурсивная реализация) +fn linearRecur(comptime n: i32) void { + std.debug.print("Рекурсия n = {}\n", .{n}); + if (n == 1) return; + linearRecur(n - 1); +} + +// Квадратичная сложность +fn quadratic(n: i32) !void { + // Двумерный список занимает O(n^2) памяти + var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + var tmp = std.ArrayList(i32).init(std.heap.page_allocator); + defer tmp.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + try tmp.append(0); + } + try nodes.append(tmp); + } +} + +// Квадратичная сложность (рекурсивная реализация) +fn quadraticRecur(comptime n: i32) i32 { + if (n <= 0) return 0; + const nums = [_]i32{0} ** n; + std.debug.print("В рекурсии n = {} длина nums = {}\n", .{ n, nums.len }); + return quadraticRecur(n - 1); +} + +// Экспоненциальная сложность (построение полного двоичного дерева) +fn buildTree(allocator: std.mem.Allocator, n: i32) !?*TreeNode(i32) { + if (n == 0) return null; + const root = try allocator.create(TreeNode(i32)); + root.init(0); + root.left = try buildTree(allocator, n - 1); + root.right = try buildTree(allocator, n - 1); + return root; +} + +// Освободить память дерева +fn freeTree(allocator: std.mem.Allocator, root: ?*const TreeNode(i32)) void { + if (root == null) return; + freeTree(allocator, root.?.left); + freeTree(allocator, root.?.right); + allocator.destroy(root.?); +} + +// Driver Code +pub fn run() !void { + var gpa = std.heap.DebugAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const n: i32 = 5; + // Постоянная сложность + constant(n); + // Линейная сложность + try linear(n); + linearRecur(n); + // Квадратичная сложность + try quadratic(n); + _ = quadraticRecur(n); + // Экспоненциальная сложность + const root = try buildTree(allocator, n); + defer freeTree(allocator, root); + std.debug.print("{}\n", .{utils.fmt.tree(i32, root)}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "space_complexity" { + try run(); +} diff --git a/ru/codes/zig/chapter_computational_complexity/time_complexity.zig b/ru/codes/zig/chapter_computational_complexity/time_complexity.zig new file mode 100644 index 000000000..2e2e02118 --- /dev/null +++ b/ru/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -0,0 +1,184 @@ +// File: time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// Постоянная сложность +fn constant(n: i32) i32 { + _ = n; + var count: i32 = 0; + const size: i32 = 100_000; + var i: i32 = 0; + while (i < size) : (i += 1) { + count += 1; + } + return count; +} + +// Линейная сложность +fn linear(n: i32) i32 { + var count: i32 = 0; + var i: i32 = 0; + while (i < n) : (i += 1) { + count += 1; + } + return count; +} + +// Линейная сложность (обход массива) +fn arrayTraversal(nums: []i32) i32 { + var count: i32 = 0; + // Число итераций пропорционально длине массива + for (nums) |_| { + count += 1; + } + return count; +} + +// Квадратичная сложность +fn quadratic(n: i32) i32 { + var count: i32 = 0; + var i: i32 = 0; + // Число итераций квадратично зависит от размера данных n + while (i < n) : (i += 1) { + var j: i32 = 0; + while (j < n) : (j += 1) { + count += 1; + } + } + return count; +} + +// Квадратичная сложность (пузырьковая сортировка) +fn bubbleSort(nums: []i32) i32 { + var count: i32 = 0; // Счетчик + // Внешний цикл: неотсортированный диапазон [0, i] + var i: i32 = @as(i32, @intCast(nums.len)) - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + const tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // Обмен элементов включает 3 элементарные операции + } + } + } + return count; +} + +// Экспоненциальная сложность (итеративная реализация) +fn exponential(n: i32) i32 { + var count: i32 = 0; + var bas: i32 = 1; + var i: i32 = 0; + // На каждом шаге клетка делится надвое, образуя последовательность 1, 2, 4, 8, ..., 2^(n-1) + while (i < n) : (i += 1) { + var j: i32 = 0; + while (j < bas) : (j += 1) { + count += 1; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +// Экспоненциальная сложность (рекурсивная реализация) +fn expRecur(n: i32) i32 { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +// Логарифмическая сложность (итеративная реализация) +fn logarithmic(n: i32) i32 { + var count: i32 = 0; + var n_var: i32 = n; + while (n_var > 1) : (n_var = @divTrunc(n_var, 2)) { + count += 1; + } + return count; +} + +// Логарифмическая сложность (рекурсивная реализация) +fn logRecur(n: i32) i32 { + if (n <= 1) return 0; + return logRecur(@divTrunc(n, 2)) + 1; +} + +// Линейно-логарифмическая сложность +fn linearLogRecur(n: i32) i32 { + if (n <= 1) return 1; + var count: i32 = linearLogRecur(@divTrunc(n, 2)) + linearLogRecur(@divTrunc(n, 2)); + var i: i32 = 0; + while (i < n) : (i += 1) { + count += 1; + } + return count; +} + +// Факториальная сложность (рекурсивная реализация) +fn factorialRecur(n: i32) i32 { + if (n == 0) return 1; + var count: i32 = 0; + var i: i32 = 0; + // Из одного получается n + while (i < n) : (i += 1) { + count += factorialRecur(n - 1); + } + return count; +} + +// Driver Code +pub fn run() void { + // Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях + const n: i32 = 8; + std.debug.print("Размер входных данных n = {}\n", .{n}); + + var count = constant(n); + std.debug.print("Число операций постоянной сложности = {}\n", .{count}); + + count = linear(n); + std.debug.print("Число операций линейной сложности = {}\n", .{count}); + var nums = [_]i32{0} ** n; + count = arrayTraversal(&nums); + std.debug.print("Число операций линейной сложности (обход массива) = {}\n", .{count}); + + count = quadratic(n); + std.debug.print("Число операций квадратичной сложности = {}\n", .{count}); + for (&nums, 0..) |*num, i| { + num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] + } + count = bubbleSort(&nums); + std.debug.print("Число операций квадратичной сложности (пузырьковая сортировка) = {}\n", .{count}); + + count = exponential(n); + std.debug.print("Число операций экспоненциальной сложности (итеративная реализация) = {}\n", .{count}); + count = expRecur(n); + std.debug.print("Число операций экспоненциальной сложности (рекурсивная реализация) = {}\n", .{count}); + + count = logarithmic(n); + std.debug.print("Число операций логарифмической сложности (итеративная реализация) = {}\n", .{count}); + count = logRecur(n); + std.debug.print("Число операций логарифмической сложности (рекурсивная реализация) = {}\n", .{count}); + + count = linearLogRecur(n); + std.debug.print("Число операций линейно-логарифмической сложности (рекурсивная реализация) = {}\n", .{count}); + + count = factorialRecur(n); + std.debug.print("Число операций факториальной сложности (рекурсивная реализация) = {}\n", .{count}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + run(); +} + +test "time_complexity" { + run(); +} diff --git a/ru/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/ru/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig new file mode 100644 index 000000000..d3270280c --- /dev/null +++ b/ru/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -0,0 +1,53 @@ +// File: worst_best_time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// Создать массив с элементами { 1, 2, ..., n } в случайном порядке +pub fn randomNumbers(comptime n: usize) [n]i32 { + var nums: [n]i32 = undefined; + // Создать массив nums = { 1, 2, 3, ..., n } + for (&nums, 0..) |*num, i| { + num.* = @as(i32, @intCast(i)) + 1; + } + // Случайно перемешать элементы массива + const rand = std.crypto.random; + rand.shuffle(i32, &nums); + return nums; +} + +// Найти индекс числа 1 в массиве nums +pub fn findOne(nums: []i32) i32 { + for (nums, 0..) |num, i| { + // Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1) + // Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n) + if (num == 1) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn run() void { + var i: i32 = 0; + while (i < 10) : (i += 1) { + const n: usize = 100; + var nums = randomNumbers(n); + const index = findOne(&nums); + std.debug.print("После перемешивания массива [ 1, 2, ..., n ] = ", .{}); + std.debug.print("{}\n", .{utils.fmt.slice(nums)}); + + std.debug.print("Индекс числа 1 = {}\n", .{index}); + } + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + run(); +} + +test "worst_best_time_complexity" { + run(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig new file mode 100644 index 000000000..494a5d77b --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig @@ -0,0 +1,44 @@ +// File: climbing_stairs_backtrack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Бэктрекинг +fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { + // Когда подъем достигает n-й ступени, число вариантов увеличивается на 1 + if (state == n) { + res.items[0] = res.items[0] + 1; + } + // Перебор всех вариантов выбора + for (choices) |choice| { + // Отсечение: нельзя выходить за n-ю ступень + if (state + choice > n) { + continue; + } + // Попытка: сделать выбор и обновить состояние + backtrack(choices, state + choice, n, res); + // Откат + } +} + +// Подъем по лестнице: бэктрекинг +fn climbingStairsBacktrack(n: usize) !i32 { + var choices = [_]i32{ 1, 2 }; // Можно подняться на 1 или 2 ступени + var state: i32 = 0; // Начать подъем с 0-й ступени + var res = std.ArrayList(i32).init(std.heap.page_allocator); + defer res.deinit(); + try res.append(0); // Использовать res[0] для хранения числа решений + backtrack(&choices, state, @intCast(n), res); + return res.items[0]; +} + +// Driver Code +pub fn main() !void { + var n: usize = 9; + + var res = try climbingStairsBacktrack(n); + std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig new file mode 100644 index 000000000..82ac11dc4 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig @@ -0,0 +1,35 @@ +// File: climbing_stairs_constraint_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Подъем по лестнице с ограничениями: динамическое программирование +fn climbingStairsConstraintDP(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return 1; + } + // Инициализация таблицы dp для хранения решений подзадач + var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (3..n + 1) |i| { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsConstraintDP(n); + std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig new file mode 100644 index 000000000..7e4740c9f --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig @@ -0,0 +1,31 @@ +// File: climbing_stairs_dfs.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Поиск +fn dfs(i: usize) i32 { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1) + dfs(i - 2); + return count; +} + +// Подъем по лестнице: поиск +fn climbingStairsDFS(comptime n: usize) i32 { + return dfs(n); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFS(n); + std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig new file mode 100644 index 000000000..dd4b08d99 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig @@ -0,0 +1,39 @@ +// File: climbing_stairs_dfs_mem.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Поиск с мемоизацией +fn dfs(i: usize, mem: []i32) i32 { + // dp[1] и dp[2] уже известны, вернуть их + if (i == 1 or i == 2) { + return @intCast(i); + } + // Если запись dp[i] существует, сразу вернуть ее + if (mem[i] != -1) { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1, mem) + dfs(i - 2, mem); + // Сохранить dp[i] + mem[i] = count; + return count; +} + +// Подъем по лестнице: поиск с мемоизацией +fn climbingStairsDFSMem(comptime n: usize) i32 { + // mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи + var mem = [_]i32{ -1 } ** (n + 1); + return dfs(n, &mem); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFSMem(n); + std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig new file mode 100644 index 000000000..c4207c727 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig @@ -0,0 +1,51 @@ +// File: climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Подъем по лестнице: динамическое программирование +fn climbingStairsDP(comptime n: usize) i32 { + // dp[1] и dp[2] уже известны, вернуть их + if (n == 1 or n == 2) { + return @intCast(n); + } + // Инициализация таблицы dp для хранения решений подзадач + var dp = [_]i32{-1} ** (n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = 1; + dp[2] = 2; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (3..n + 1) |i| { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +// Подъем по лестнице: динамическое программирование с оптимизацией памяти +fn climbingStairsDPComp(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return @intCast(n); + } + var a: i32 = 1; + var b: i32 = 2; + for (3..n + 1) |_| { + var tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDP(n); + std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); + + res = climbingStairsDPComp(n); + std.debug.print("Количество способов подняться по лестнице из {} ступеней: {} вариантов\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/coin_change.zig b/ru/codes/zig/chapter_dynamic_programming/coin_change.zig new file mode 100644 index 000000000..ef29d4750 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/coin_change.zig @@ -0,0 +1,77 @@ +// File: coin_change.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Размен монет: динамическое программирование +fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // Инициализация таблицы dp + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // Переход состояний: первая строка и первый столбец + for (1..amt + 1) |a| { + dp[0][a] = max; + } + // Переход состояний: остальные строки и столбцы + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[n][amt] != max) { + return @intCast(dp[n][amt]); + } else { + return -1; + } +} + +// Размен монет: динамическое программирование с оптимизацией памяти +fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // Инициализация таблицы dp + var dp = [_]i32{0} ** (amt + 1); + @memset(&dp, max); + dp[0] = 0; + // Переход состояний + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[amt] != max) { + return @intCast(dp[amt]); + } else { + return -1; + } +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 4; + + // Динамическое программирование + var res = coinChangeDP(&coins, amt); + std.debug.print("Минимальное число монет для набора целевой суммы = {}\n", .{res}); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeDPComp(&coins, amt); + std.debug.print("Минимальное число монет для набора целевой суммы = {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/coin_change_ii.zig b/ru/codes/zig/chapter_dynamic_programming/coin_change_ii.zig new file mode 100644 index 000000000..9388f960d --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/coin_change_ii.zig @@ -0,0 +1,66 @@ +// File: coin_change_ii.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Размен монет II: динамическое программирование +fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // Инициализация таблицы dp + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // Инициализация первого столбца + for (0..n + 1) |i| { + dp[i][0] = 1; + } + // Переход состояний + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // Если целевая сумма превышена, монету i не выбирать + dp[i][a] = dp[i - 1][a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[n][amt]; +} + +// Размен монет II: динамическое программирование с оптимизацией памяти +fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // Инициализация таблицы dp + var dp = [_]i32{0} ** (amt + 1); + dp[0] = 1; + // Переход состояний + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // Если целевая сумма превышена, монету i не выбирать + dp[a] = dp[a]; + } else { + // Меньшее из двух решений: не брать или взять монету i + dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[amt]; +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 5; + + // Динамическое программирование + var res = coinChangeIIDP(&coins, amt); + std.debug.print("Количество комбинаций монет для набора целевой суммы = {}\n", .{res}); + + // Динамическое программирование с оптимизацией памяти + res = coinChangeIIDPComp(&coins, amt); + std.debug.print("Количество комбинаций монет для набора целевой суммы = {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/edit_distance.zig b/ru/codes/zig/chapter_dynamic_programming/edit_distance.zig new file mode 100644 index 000000000..fae650ef0 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/edit_distance.zig @@ -0,0 +1,146 @@ +// File: edit_distance.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Редакционное расстояние: полный перебор +fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { + // Если s и t пусты, вернуть 0 + if (i == 0 and j == 0) { + return 0; + } + // Если s пусто, вернуть длину t + if (i == 0) { + return @intCast(j); + } + // Если t пусто, вернуть длину s + if (j == 0) { + return @intCast(i); + } + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) { + return editDistanceDFS(s, t, i - 1, j - 1); + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + var insert = editDistanceDFS(s, t, i, j - 1); + var delete = editDistanceDFS(s, t, i - 1, j); + var replace = editDistanceDFS(s, t, i - 1, j - 1); + // Вернуть минимальное число шагов редактирования + return @min(@min(insert, delete), replace) + 1; +} + +// Редакционное расстояние: поиск с мемоизацией +fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { + // Если s и t пусты, вернуть 0 + if (i == 0 and j == 0) { + return 0; + } + // Если s пусто, вернуть длину t + if (i == 0) { + return @intCast(j); + } + // Если t пусто, вернуть длину s + if (j == 0) { + return @intCast(i); + } + // Если запись уже есть, сразу вернуть ее + if (mem[i][j] != -1) { + return mem[i][j]; + } + // Если два символа равны, сразу пропустить их + if (s[i - 1] == t[j - 1]) { + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + } + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + var insert = editDistanceDFSMem(s, t, mem, i, j - 1); + var delete = editDistanceDFSMem(s, t, mem, i - 1, j); + var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // Сохранить и вернуть минимальное число шагов редактирования + mem[i][j] = @min(@min(insert, delete), replace) + 1; + return mem[i][j]; +} + +// Редакционное расстояние: динамическое программирование +fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); + // Переход состояний: первая строка и первый столбец + for (1..n + 1) |i| { + dp[i][0] = @intCast(i); + } + for (1..m + 1) |j| { + dp[0][j] = @intCast(j); + } + // Переход состояний: остальные строки и столбцы + for (1..n + 1) |i| { + for (1..m + 1) |j| { + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[i][j] = dp[i - 1][j - 1]; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +// Редакционное расстояние: динамическое программирование с оптимизацией памяти +fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_]i32{0} ** (m + 1); + // Переход состояний: первая строка + for (1..m + 1) |j| { + dp[j] = @intCast(j); + } + // Переход состояний: остальные строки + for (1..n + 1) |i| { + // Переход состояний: первый столбец + var leftup = dp[0]; // Временно сохранить dp[i-1, j-1] + dp[0] = @intCast(i); + // Переход состояний: остальные столбцы + for (1..m + 1) |j| { + var temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // Если два символа равны, сразу пропустить их + dp[j] = leftup; + } else { + // Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1 + dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // Обновить до значения dp[i-1, j-1] для следующей итерации + } + } + return dp[m]; +} + +// Driver Code +pub fn main() !void { + const s = "bag"; + const t = "pack"; + comptime var n = s.len; + comptime var m = t.len; + + // Полный перебор + var res = editDistanceDFS(s, t, n, m); + std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); + + // Поиск с запоминанием + var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); + res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); + std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); + + // Динамическое программирование + res = editDistanceDP(s, t); + std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); + + // Динамическое программирование с оптимизацией памяти + res = editDistanceDPComp(s, t); + std.debug.print("Чтобы преобразовать {s} в {s}, нужно минимум {} шагов\n", .{ s, t, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/knapsack.zig b/ru/codes/zig/chapter_dynamic_programming/knapsack.zig new file mode 100644 index 000000000..5e7b444f1 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/knapsack.zig @@ -0,0 +1,110 @@ +// File: knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Рюкзак 0-1: полный перебор +fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 or c == 0) { + return 0; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + var no = knapsackDFS(wgt, val, i - 1, c); + var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // Вернуть вариант с большей стоимостью из двух возможных + return @max(no, yes); +} + +// Рюкзак 0-1: поиск с мемоизацией +fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { + // Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0 + if (i == 0 or c == 0) { + return 0; + } + // Если запись уже есть, вернуть сразу + if (mem[i][c] != -1) { + return mem[i][c]; + } + // Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут + var no = knapsackDFSMem(wgt, val, mem, i - 1, c); + var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // Сохранить и вернуть вариант с большей стоимостью из двух решений + mem[i][c] = @max(no, yes); + return mem[i][c]; +} + +// Рюкзак 0-1: динамическое программирование +fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // Инициализация таблицы dp + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // Переход состояний + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// Рюкзак 0-1: динамическое программирование с оптимизацией памяти +fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { + var n = wgt.len; + // Инициализация таблицы dp + var dp = [_]i32{0} ** (cap + 1); + // Переход состояний + for (1..n + 1) |i| { + // Обход в обратном порядке + var c = cap; + while (c > 0) : (c -= 1) { + if (wgt[i - 1] < c) { + // Большее из двух решений: не брать или взять предмет i + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; + comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; + comptime var cap = 50; + comptime var n = wgt.len; + + // Полный перебор + var res = knapsackDFS(&wgt, &val, n, cap); + std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); + + // Поиск с запоминанием + var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); + res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); + std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); + + // Динамическое программирование + res = knapsackDP(&wgt, &val, cap); + std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); + + // Динамическое программирование с оптимизацией памяти + res = knapsackDPComp(&wgt, &val, cap); + std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig b/ru/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig new file mode 100644 index 000000000..715ac118c --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig @@ -0,0 +1,54 @@ +// File: min_cost_climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Минимальная стоимость подъема по лестнице: динамическое программирование +fn minCostClimbingStairsDP(comptime cost: []i32) i32 { + comptime var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + // Инициализация таблицы dp для хранения решений подзадач + var dp = [_]i32{-1} ** (n + 1); + // Начальное состояние: заранее задать решения наименьших подзадач + dp[1] = cost[1]; + dp[2] = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (3..n + 1) |i| { + dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +// Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти +fn minCostClimbingStairsDPComp(cost: []i32) i32 { + var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + var a = cost[1]; + var b = cost[2]; + // Переход состояний: постепенное решение больших подзадач через меньшие + for (3..n + 1) |i| { + var tmp = b; + b = @min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + std.debug.print("Стоимость подъема по ступеням = {any}\n", .{cost}); + + var res = minCostClimbingStairsDP(&cost); + std.debug.print("Стоимость подъема по ступеням = {}\n", .{res}); + + res = minCostClimbingStairsDPComp(&cost); + std.debug.print("Стоимость подъема по ступеням = {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/min_path_sum.zig b/ru/codes/zig/chapter_dynamic_programming/min_path_sum.zig new file mode 100644 index 000000000..8a5b6552c --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/min_path_sum.zig @@ -0,0 +1,122 @@ +// File: min_path_sum.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Минимальная сумма пути: полный перебор +fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 and j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + var up = minPathSumDFS(grid, i - 1, j); + var left = minPathSumDFS(grid, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла до (i, j) + return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// Минимальная сумма пути: поиск с мемоизацией +fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { + // Если это верхняя левая ячейка, завершить поиск + if (i == 0 and j == 0) { + return grid[0][0]; + } + // Если индексы строки или столбца выходят за границы, вернуть стоимость +∞ + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // Если запись уже есть, вернуть сразу + if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + // Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1) + var up = minPathSumDFSMem(grid, mem, i - 1, j); + var left = minPathSumDFSMem(grid, mem, i, j - 1); + // Вернуть минимальную стоимость пути из левого верхнего угла в (i, j) + // Записать и вернуть минимальную стоимость пути из левого верхнего угла в (i, j) + mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// Минимальная сумма пути: динамическое программирование +fn minPathSumDP(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // Инициализация таблицы dp + var dp = [_][m]i32{[_]i32{0} ** m} ** n; + dp[0][0] = grid[0][0]; + // Переход состояний: первая строка + for (1..m) |j| { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // Переход состояний: первый столбец + for (1..n) |i| { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // Переход состояний: остальные строки и столбцы + for (1..n) |i| { + for (1..m) |j| { + dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +// Минимальная сумма пути: динамическое программирование с оптимизацией памяти +fn minPathSumDPComp(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // Инициализация таблицы dp + var dp = [_]i32{0} ** m; + // Переход состояний: первая строка + dp[0] = grid[0][0]; + for (1..m) |j| { + dp[j] = dp[j - 1] + grid[0][j]; + } + // Переход состояний: остальные строки + for (1..n) |i| { + // Переход состояний: первый столбец + dp[0] = dp[0] + grid[i][0]; + for (1..m) |j| { + dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +// Driver Code +pub fn main() !void { + comptime var grid = [_][4]i32{ + [_]i32{ 1, 3, 1, 5 }, + [_]i32{ 2, 2, 4, 2 }, + [_]i32{ 5, 3, 2, 1 }, + [_]i32{ 4, 3, 5, 2 }, + }; + comptime var n = grid.len; + comptime var m = grid[0].len; + + // Полный перебор + var res = minPathSumDFS(&grid, n - 1, m - 1); + std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); + + // Поиск с мемоизацией + var mem = [_][m]i32{[_]i32{-1} ** m} ** n; + res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); + std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); + + // Динамическое программирование + res = minPathSumDP(&grid); + std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); + + // Динамическое программирование с оптимизацией памяти + res = minPathSumDPComp(&grid); + std.debug.print("Минимальная сумма пути из левого верхнего угла в правый нижний = {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig b/ru/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig new file mode 100644 index 000000000..4e3979399 --- /dev/null +++ b/ru/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig @@ -0,0 +1,62 @@ +// File: unbounded_knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Полный рюкзак: динамическое программирование +fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // Инициализация таблицы dp + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // Переход состояний + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[i][c] = dp[i - 1][c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// Полный рюкзак: динамическое программирование с оптимизацией памяти +fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // Инициализация таблицы dp + var dp = [_]i32{0} ** (cap + 1); + // Переход состояний + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // Если вместимость рюкзака превышена, предмет i не выбирать + dp[c] = dp[c]; + } else { + // Большее из двух решений: не брать или взять предмет i + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 1, 2, 3 }; + comptime var val = [_]i32{ 5, 11, 15 }; + comptime var cap = 4; + + // Динамическое программирование + var res = unboundedKnapsackDP(&wgt, &val, cap); + std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); + + // Динамическое программирование с оптимизацией памяти + res = unboundedKnapsackDPComp(&wgt, &val, cap); + std.debug.print("Максимальная стоимость предметов без превышения вместимости рюкзака = {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_hashing/array_hash_map.zig b/ru/codes/zig/chapter_hashing/array_hash_map.zig new file mode 100644 index 000000000..a01cd5219 --- /dev/null +++ b/ru/codes/zig/chapter_hashing/array_hash_map.zig @@ -0,0 +1,162 @@ +// File: array_hash_map.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Пара ключ-значение +const Pair = struct { + key: usize = undefined, + val: []const u8 = undefined, + + pub fn init(key: usize, val: []const u8) Pair { + return Pair { + .key = key, + .val = val, + }; + } +}; + +// Хеш-таблица на основе массива +pub fn ArrayHashMap(comptime T: type) type { + return struct { + bucket: ?std.ArrayList(?T) = null, + mem_allocator: std.mem.Allocator = undefined, + + const Self = @This(); + + // Конструктор + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + self.mem_allocator = allocator; + // Инициализировать корзину (массив) длиной 100 + self.bucket = std.ArrayList(?T).init(self.mem_allocator); + var i: i32 = 0; + while (i < 100) : (i += 1) { + try self.bucket.?.append(null); + } + } + + // Деструктор + pub fn deinit(self: *Self) void { + if (self.bucket != null) self.bucket.?.deinit(); + } + + // Хеш-функция + fn hashFunc(key: usize) usize { + var index = key % 100; + return index; + } + + // Операция поиска + pub fn get(self: *Self, key: usize) []const u8 { + var index = hashFunc(key); + var pair = self.bucket.?.items[index]; + return pair.?.val; + } + + // Операция добавления + pub fn put(self: *Self, key: usize, val: []const u8) !void { + var pair = Pair.init(key, val); + var index = hashFunc(key); + self.bucket.?.items[index] = pair; + } + + // Операция удаления + pub fn remove(self: *Self, key: usize) !void { + var index = hashFunc(key); + // Присвоить null, что означает удаление + self.bucket.?.items[index] = null; + } + + // Получить все пары ключ-значение + pub fn pairSet(self: *Self) !std.ArrayList(T) { + var entry_set = std.ArrayList(T).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try entry_set.append(item.?); + } + return entry_set; + } + + // Получить все ключи + pub fn keySet(self: *Self) !std.ArrayList(usize) { + var key_set = std.ArrayList(usize).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try key_set.append(item.?.key); + } + return key_set; + } + + // Получить все значения + pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { + var value_set = std.ArrayList([]const u8).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try value_set.append(item.?.val); + } + return value_set; + } + + // Вывести хеш-таблицу + pub fn print(self: *Self) !void { + var entry_set = try self.pairSet(); + defer entry_set.deinit(); + for (entry_set.items) |item| { + std.debug.print("{} -> {s}\n", .{item.key, item.val}); + } + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация хеш-таблицы + var map = ArrayHashMap(Pair){}; + try map.init(std.heap.page_allocator); + defer map.deinit(); + + // Операция добавления + // Добавить пару (key, value) в хеш-таблицу + try map.put(12836, "Сяо Ха"); + try map.put(15937, "Сяо Ло"); + try map.put(16750, "Сяо Суань"); + try map.put(13276, "Сяо Фа"); + try map.put(10583, "Сяо Я"); + std.debug.print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); + try map.print(); + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение value + var name = map.get(15937); + std.debug.print("\nПо номеру 15937 найдено имя {s}\n", .{name}); + + // Операция удаления + // Удалить пару (key, value) из хеш-таблицы + try map.remove(10583); + std.debug.print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); + try map.print(); + + // Обход хеш-таблицы + std.debug.print("\nОтдельный обход пар ключ-значение\n", .{}); + var entry_set = try map.pairSet(); + for (entry_set.items) |kv| { + std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); + } + defer entry_set.deinit(); + std.debug.print("\nОтдельный обход ключей\n", .{}); + var key_set = try map.keySet(); + for (key_set.items) |key| { + std.debug.print("{}\n", .{key}); + } + defer key_set.deinit(); + std.debug.print("\nОтдельный обход значений\n", .{}); + var value_set = try map.valueSet(); + for (value_set.items) |val| { + std.debug.print("{s}\n", .{val}); + } + defer value_set.deinit(); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_hashing/hash_map.zig b/ru/codes/zig/chapter_hashing/hash_map.zig new file mode 100644 index 000000000..7c6f991f6 --- /dev/null +++ b/ru/codes/zig/chapter_hashing/hash_map.zig @@ -0,0 +1,54 @@ +// File: hash_map.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // Инициализация хеш-таблицы + var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); + // Отложенное освобождение памяти + defer map.deinit(); + + // Операция добавления + // Добавить пару (key, value) в хеш-таблицу + try map.put(12836, "Сяо Ха"); + try map.put(15937, "Сяо Ло"); + try map.put(16750, "Сяо Суань"); + try map.put(13276, "Сяо Фа"); + try map.put(10583, "Сяо Я"); + std.debug.print("\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // Операция поиска + // Передать ключ key в хеш-таблицу и получить значение value + var name = map.get(15937).?; + std.debug.print("\nПо номеру 15937 найдено имя {s}\n", .{name}); + + // Операция удаления + // Удалить пару (key, value) из хеш-таблицы + _ = map.remove(10583); + std.debug.print("\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // Обход хеш-таблицы + std.debug.print("\nОтдельный обход пар ключ-значение\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + std.debug.print("\nОтдельный обход ключей\n", .{}); + var it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{}\n", .{kv.key_ptr.*}); + } + + std.debug.print("\nОтдельный обход значений\n", .{}); + it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{s}\n", .{kv.value_ptr.*}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ru/codes/zig/chapter_heap/heap.zig b/ru/codes/zig/chapter_heap/heap.zig new file mode 100644 index 000000000..c68885acb --- /dev/null +++ b/ru/codes/zig/chapter_heap/heap.zig @@ -0,0 +1,80 @@ +// File: heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +fn lessThan(context: void, a: i32, b: i32) std.math.Order { + _ = context; + return std.math.order(a, b); +} + +fn greaterThan(context: void, a: i32, b: i32) std.math.Order { + return lessThan(context, a, b).invert(); +} + +fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { + try heap.add(val); // Добавление элемента в кучу + std.debug.print("\nПосле добавления элемента {} в кучу\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { + var val = heap.remove(); // Извлечение элемента с вершины кучи + std.debug.print("\nПосле извлечения элемента вершины кучи {}\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +// Driver Code +pub fn main() !void { + // Инициализация аллокатора памяти + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // Инициализировать кучу + // Инициализировать минимальную кучу + const PQlt = std.PriorityQueue(i32, void, lessThan); + var min_heap = PQlt.init(std.heap.page_allocator, {}); + defer min_heap.deinit(); + // Инициализация максимальной кучи + const PQgt = std.PriorityQueue(i32, void, greaterThan); + var max_heap = PQgt.init(std.heap.page_allocator, {}); + defer max_heap.deinit(); + + std.debug.print("\nНиже приведен тестовый пример для большой кучи", .{}); + + // Добавление элемента в кучу + try testPush(i32, mem_allocator, &max_heap, 1); + try testPush(i32, mem_allocator, &max_heap, 3); + try testPush(i32, mem_allocator, &max_heap, 2); + try testPush(i32, mem_allocator, &max_heap, 5); + try testPush(i32, mem_allocator, &max_heap, 4); + + // Получение элемента с вершины кучи + var peek = max_heap.peek().?; + std.debug.print("\nЭлемент на вершине кучи = {}\n", .{peek}); + + // Извлечение элемента с вершины кучи + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + + // Получить размер кучи + var size = max_heap.len; + std.debug.print("\nКоличество элементов в куче = {}\n", .{size}); + + // Проверка, пуста ли куча + var is_empty = if (max_heap.len == 0) true else false; + std.debug.print("\nПуста ли куча: {}\n", .{is_empty}); + + // Построить кучу по входному списку + try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("\nПосле построения мин-кучи из входного списка\n", .{}); + try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_heap/my_heap.zig b/ru/codes/zig/chapter_heap/my_heap.zig new file mode 100644 index 000000000..664f72349 --- /dev/null +++ b/ru/codes/zig/chapter_heap/my_heap.zig @@ -0,0 +1,186 @@ +// File: my_heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Упрощенная реализация класса кучи +pub fn MaxHeap(comptime T: type) type { + return struct { + const Self = @This(); + + max_heap: ?std.ArrayList(T) = null, // Использовать список вместо массива, чтобы не учитывать проблему расширения + + // Конструктор, строящий кучу по входному списку + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // Добавить элементы списка в кучу без изменений + try self.max_heap.?.appendSlice(nums); + // Выполнить heapify для всех узлов, кроме листовых + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + + // Деструктор, освободить память + pub fn deinit(self: *Self) void { + if (self.max_heap != null) self.max_heap.?.deinit(); + } + + // Получить индекс левого дочернего узла + fn left(i: usize) usize { + return 2 * i + 1; + } + + // Получить индекс правого дочернего узла + fn right(i: usize) usize { + return 2 * i + 2; + } + + // Получить индекс родительского узла + fn parent(i: usize) usize { + // return (i - 1) / 2; // округление вниз при делении + return @divFloor(i - 1, 2); + } + + // Поменять элементы местами + fn swap(self: *Self, i: usize, j: usize) !void { + var tmp = self.max_heap.?.items[i]; + try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); + try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); + } + + // Получение размера кучи + pub fn size(self: *Self) usize { + return self.max_heap.?.items.len; + } + + // Проверка, пуста ли куча + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // Доступ к элементу на вершине кучи + pub fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + + // Добавление элемента в кучу + pub fn push(self: *Self, val: T) !void { + // Добавление узла + try self.max_heap.?.append(val); + // Просеивание снизу вверх + try self.siftUp(self.size() - 1); + } + + // Начиная с узла i, выполнить просеивание снизу вверх + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // Получение родительского узла для узла i + var p = parent(i); + // Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления» + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // Поменять два узла местами + try self.swap(i, p); + // Циклическое просеивание вверх + i = p; + } + } + + // Извлечение элемента из кучи + pub fn pop(self: *Self) !T { + // Обработка проверки + if (self.isEmpty()) unreachable; + // Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы) + try self.swap(0, self.size() - 1); + // Удаление узла + var val = self.max_heap.?.pop(); + // Просеивание сверху вниз + try self.siftDown(0); + // Вернуть элемент с вершины кучи + return val; + } + + // Начиная с узла i, выполнить просеивание сверху вниз + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // Определить узел с максимальным значением среди i, l и r и обозначить его как ma + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти + if (ma == i) break; + // Поменять два узла местами + try self.swap(i, ma); + // Циклическое просеивание вниз + i = ma; + } + } + + fn lessThan(context: void, a: T, b: T) std.math.Order { + _ = context; + return std.math.order(a, b); + } + + fn greaterThan(context: void, a: T, b: T) std.math.Order { + return lessThan(context, a, b).invert(); + } + + // Вывести кучу (двоичное дерево) + pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { + const PQgt = std.PriorityQueue(T, void, greaterThan); + var queue = PQgt.init(std.heap.page_allocator, {}); + defer queue.deinit(); + try queue.addSlice(self.max_heap.?.items); + try inc.PrintUtil.printHeap(T, mem_allocator, queue); + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация аллокатора памяти + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // Инициализация максимальной кучи + var max_heap = MaxHeap(i32){}; + try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); + defer max_heap.deinit(); + std.debug.print("\nПосле построения кучи из входного списка\n", .{}); + try max_heap.print(mem_allocator); + + // Получение элемента с вершины кучи + var peek = max_heap.peek(); + std.debug.print("\nЭлемент на вершине кучи = {}\n", .{peek}); + + // Добавление элемента в кучу + const val = 7; + try max_heap.push(val); + std.debug.print("\nПосле добавления элемента {} в кучу\n", .{val}); + try max_heap.print(mem_allocator); + + // Извлечение элемента с вершины кучи + peek = try max_heap.pop(); + std.debug.print("\nПосле извлечения элемента вершины кучи {}\n", .{peek}); + try max_heap.print(mem_allocator); + + // Получить размер кучи + var size = max_heap.size(); + std.debug.print("\nКоличество элементов в куче = {}", .{size}); + + // Проверка, пуста ли куча + var is_empty = max_heap.isEmpty(); + std.debug.print("\nПуста ли куча: {}\n", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ru/codes/zig/chapter_searching/binary_search.zig b/ru/codes/zig/chapter_searching/binary_search.zig new file mode 100644 index 000000000..183625f0b --- /dev/null +++ b/ru/codes/zig/chapter_searching/binary_search.zig @@ -0,0 +1,64 @@ +// File: binary_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Бинарный поиск (двусторонне замкнутый интервал) +fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { + // Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно + var i: usize = 0; + var j: usize = nums.items.len - 1; + // Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст) + while (i <= j) { + var m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums.items[m] < target) { // Это означает, что target находится в интервале [m+1, j] + i = m + 1; + } else if (nums.items[m] > target) { // Это означает, что target находится в интервале [i, m-1] + j = m - 1; + } else { // Целевой элемент найден, вернуть его индекс + return @intCast(m); + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +// Бинарный поиск (лево замкнутый, право открытый интервал) +fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { + // Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно + var i: usize = 0; + var j: usize = nums.items.len; + // Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст) + while (i <= j) { + var m = i + (j - i) / 2; // Вычислить индекс середины m + if (nums.items[m] < target) { // Это означает, что target находится в интервале [m+1, j) + i = m + 1; + } else if (nums.items[m] > target) { // Это означает, что target находится в интервале [i, m) + j = m; + } else { // Целевой элемент найден, вернуть его индекс + return @intCast(m); + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 6; + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); + + // Бинарный поиск (двусторонне замкнутый интервал) + var index = binarySearch(i32, nums, target); + std.debug.print("Индекс целевого элемента 6 = {}\n", .{index}); + + // Бинарный поиск (лево замкнутый, право открытый интервал) + index = binarySearchLCRO(i32, nums, target); + std.debug.print("Индекс целевого элемента 6 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_searching/hashing_search.zig b/ru/codes/zig/chapter_searching/hashing_search.zig new file mode 100644 index 000000000..c7549d53c --- /dev/null +++ b/ru/codes/zig/chapter_searching/hashing_search.zig @@ -0,0 +1,57 @@ +// File: hashing_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Хеш-поиск (массив) +fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { + // key хеш-таблицы: целевой элемент, value: индекс + // Если такого key нет в хеш-таблице, вернуть -1 + if (map.getKey(target) == null) return -1; + return map.get(target).?; +} + +// Хеш-поиск (связный список) +fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { + // key хеш-таблицы: значение целевого узла, value: объект узла + // Если такого key нет в хеш-таблице, вернуть null + if (map.getKey(target) == null) return null; + return map.get(target); +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // Хеш-поиск (массив) + var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // Инициализация хеш-таблицы + var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer map.deinit(); + for (nums, 0..) |num, i| { + try map.put(num, @as(i32, @intCast(i))); // key: элемент, value: индекс + } + var index = hashingSearchArray(i32, map, target); + std.debug.print("Индекс целевого элемента 3 = {}\n", .{index}); + + // Хеш-поиск (связный список) + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); + // Инициализация хеш-таблицы + var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); + defer map1.deinit(); + while (head != null) { + try map1.put(head.?.val, head.?); + head = head.?.next; + } + var node = hashingSearchLinkedList(i32, map1, target); + std.debug.print("Объект узла со значением 3 = ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ru/codes/zig/chapter_searching/linear_search.zig b/ru/codes/zig/chapter_searching/linear_search.zig new file mode 100644 index 000000000..7e00af7b5 --- /dev/null +++ b/ru/codes/zig/chapter_searching/linear_search.zig @@ -0,0 +1,54 @@ +// File: linear_search.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Линейный поиск (массив) +fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { + // Обход массива + for (nums.items, 0..) |num, i| { + // Найти целевой элемент и вернуть его индекс + if (num == target) { + return @intCast(i); + } + } + // Целевой элемент не найден, вернуть -1 + return -1; +} + +// Линейный поиск (связный список) +pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { + var head = node; + // Обойти связный список + while (head != null) { + // Найти целевой узел и вернуть его + if (head.?.val == target) return head; + head = head.?.next; + } + return null; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // Выполнить линейный поиск в массиве + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); + var index = linearSearchArray(i32, nums, target); + std.debug.print("Индекс целевого элемента 3 = {}\n", .{index}); + + // Выполнить линейный поиск в связном списке + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); + var node = linearSearchLinkedList(i32, head, target); + std.debug.print("Объект узла со значением 3 = ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_searching/two_sum.zig b/ru/codes/zig/chapter_searching/two_sum.zig new file mode 100644 index 000000000..07bd85fd3 --- /dev/null +++ b/ru/codes/zig/chapter_searching/two_sum.zig @@ -0,0 +1,58 @@ +// File: two_sum.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Метод 1: полный перебор +pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { + var size: usize = nums.len; + var i: usize = 0; + // Два вложенных цикла, временная сложность O(n^2) + while (i < size - 1) : (i += 1) { + var j = i + 1; + while (j < size) : (j += 1) { + if (nums[i] + nums[j] == target) { + return [_]i32{@intCast(i), @intCast(j)}; + } + } + } + return null; +} + +// Метод 2: вспомогательная хеш-таблица +pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { + var size: usize = nums.len; + // Вспомогательная хеш-таблица, пространственная сложность O(n) + var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer dic.deinit(); + var i: usize = 0; + // Один цикл, временная сложность O(n) + while (i < size) : (i += 1) { + if (dic.contains(target - nums[i])) { + return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; + } + try dic.put(nums[i], @intCast(i)); + } + return null; +} + + +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + + // ====== Основной код ====== + // Метод 1 + var res = twoSumBruteForce(&nums, target).?; + std.debug.print("Метод 1: res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // Метод 2 + res = (try twoSumHashTable(&nums, target)).?; + std.debug.print("\nМетод 2: res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_sorting/bubble_sort.zig b/ru/codes/zig/chapter_sorting/bubble_sort.zig new file mode 100644 index 000000000..02286f4b0 --- /dev/null +++ b/ru/codes/zig/chapter_sorting/bubble_sort.zig @@ -0,0 +1,61 @@ +// File: bubble_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Пузырьковая сортировка +fn bubbleSort(nums: []i32) void { + // Внешний цикл: неотсортированный диапазон [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +// Пузырьковая сортировка (оптимизация флагом) +fn bubbleSortWithFlag(nums: []i32) void { + // Внешний цикл: неотсортированный диапазон [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var flag = false; // Инициализировать флаг + var j: usize = 0; + // Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // Поменять местами nums[j] и nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; + } + } + if (!flag) break; // На этой итерации «всплытия» не было ни одного обмена, сразу выйти + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSort(&nums); + std.debug.print("После завершения пузырьковой сортировки nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(&nums1); + std.debug.print("\nПосле завершения пузырьковой сортировки nums1 = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ru/codes/zig/chapter_sorting/insertion_sort.zig b/ru/codes/zig/chapter_sorting/insertion_sort.zig new file mode 100644 index 000000000..323e8140c --- /dev/null +++ b/ru/codes/zig/chapter_sorting/insertion_sort.zig @@ -0,0 +1,31 @@ +// File: insertion_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Сортировка вставками +fn insertionSort(nums: []i32) void { + // Внешний цикл: отсортированный диапазон [0, i-1] + var i: usize = 1; + while (i < nums.len) : (i += 1) { + var base = nums[i]; + var j: usize = i; + // Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1] + while (j >= 1 and nums[j - 1] > base) : (j -= 1) { + nums[j] = nums[j - 1]; // Сдвинуть nums[j] на одну позицию вправо + } + nums[j] = base; // Поместить base в правильную позицию + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + insertionSort(&nums); + std.debug.print("После завершения сортировки вставками nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_sorting/merge_sort.zig b/ru/codes/zig/chapter_sorting/merge_sort.zig new file mode 100644 index 000000000..5ccfab942 --- /dev/null +++ b/ru/codes/zig/chapter_sorting/merge_sort.zig @@ -0,0 +1,67 @@ +// File: merge_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Объединить левый и правый подмассивы +// Диапазон левого подмассива [left, mid] +// Диапазон правого подмассива [mid + 1, right] +fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { + // Инициализация вспомогательного массива + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var tmp = try mem_allocator.alloc(i32, right + 1 - left); + std.mem.copy(i32, tmp, nums[left..right+1]); + // Начальный и конечный индексы левого подмассива + var leftStart = left - left; + var leftEnd = mid - left; + // Начальный и конечный индексы правого подмассива + var rightStart = mid + 1 - left; + var rightEnd = right - left; + // i и j указывают соответственно на первые элементы левого и правого подмассивов + var i = leftStart; + var j = rightStart; + // Объединить левый и правый подмассивы, перезаписывая исходный массив nums + var k = left; + while (k <= right) : (k += 1) { + // Если «левый подмассив уже полностью слит», выбрать элемент правого подмассива и выполнить j++ + if (i > leftEnd) { + nums[k] = tmp[j]; + j += 1; + // Иначе, если «правый подмассив уже полностью слит» или «элемент левого подмассива <= элементу правого подмассива», выбрать элемент левого подмассива и выполнить i++ + } else if (j > rightEnd or tmp[i] <= tmp[j]) { + nums[k] = tmp[i]; + i += 1; + // Иначе, если «оба подмассива еще не полностью слиты» и «элемент левого подмассива > элемента правого подмассива», выбрать элемент правого подмассива и выполнить j++ + } else { + nums[k] = tmp[j]; + j += 1; + } + } +} + +// Сортировка слиянием +fn mergeSort(nums: []i32, left: usize, right: usize) !void { + // Условие завершения + if (left >= right) return; // Завершить рекурсию, когда длина подмассива равна 1 + // Этап разбиения + var mid = left + (right - left) / 2; // Вычислить середину + try mergeSort(nums, left, mid); // Рекурсивно обработать левый подмассив + try mergeSort(nums, mid + 1, right); // Рекурсивно обработать правый подмассив + // Этап слияния + try merge(nums, left, mid, right); +} + +// Driver Code +pub fn main() !void { + // Сортировка слиянием + var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; + try mergeSort(&nums, 0, nums.len - 1); + std.debug.print("После завершения сортировки слиянием nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_sorting/quick_sort.zig b/ru/codes/zig/chapter_sorting/quick_sort.zig new file mode 100644 index 000000000..23adb7edb --- /dev/null +++ b/ru/codes/zig/chapter_sorting/quick_sort.zig @@ -0,0 +1,162 @@ +// File: quick_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Класс быстрой сортировки +const QuickSort = struct { + + // Обмен элементов + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // Разбиение с опорными указателями + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // Взять nums[left] в качестве опорного элемента + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j and nums[i] <= nums[left]) i += 1; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + // Быстрая сортировка + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return; + // Разбиение с опорными указателями + var pivot = partition(nums, left, right); + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// Класс быстрой сортировки (оптимизация медианным опорным элементом) +const QuickSortMedian = struct { + + // Обмен элементов + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // Выбрать медиану из трех кандидатов + pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { + var l = nums[left]; + var m = nums[mid]; + var r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m находится между l и r + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l находится между m и r + return right; + } + + // Разбиение с опорными указателями (медиана трех) + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // Выбрать медиану из трех кандидатов + var med = medianThree(nums, left, (left + right) / 2, right); + // Переместить медиану в крайний левый элемент массива + swap(nums, left, med); + // Взять nums[left] в качестве опорного элемента + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j and nums[i] <= nums[left]) i += 1; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + // Быстрая сортировка + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // Завершить рекурсию, когда длина подмассива равна 1 + if (left >= right) return; + // Разбиение с опорными указателями + var pivot = partition(nums, left, right); + if (pivot == 0) return; + // Рекурсивно обработать левый и правый подмассивы + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// Класс быстрой сортировки (оптимизация глубины рекурсии) +const QuickSortTailCall = struct { + + // Обмен элементов + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // Разбиение с опорными указателями + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // Взять nums[left] в качестве опорного элемента + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // Идти справа налево в поисках первого элемента меньше опорного + while (i < j and nums[i] <= nums[left]) i += 1; // Идти слева направо в поисках первого элемента больше опорного + swap(nums, i, j); // Поменять эти два элемента местами + } + swap(nums, i, left); // Переместить опорный элемент на границу двух подмассивов + return i; // Вернуть индекс опорного элемента + } + + // Быстрая сортировка (оптимизация глубины рекурсии) + pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { + var left = left_; + var right = right_; + // Завершить рекурсию, когда длина подмассива равна 1 + while (left < right) { + // Операция разбиения с опорными указателями + var pivot = partition(nums, left, right); + // Выполнить быструю сортировку для более короткого из двух подмассивов + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // Рекурсивно отсортировать левый подмассив + left = pivot + 1; // Оставшийся неотсортированный диапазон: [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // Рекурсивно отсортировать правый подмассив + right = pivot - 1; // Оставшийся неотсортированный диапазон: [left, pivot - 1] + } + } + } +}; + +// Driver Code +pub fn main() !void { + // Быстрая сортировка + var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(&nums, 0, nums.len - 1); + std.debug.print("После завершения быстрой сортировки nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + // Быстрая сортировка (оптимизация медианным опорным элементом) + var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); + std.debug.print("\nПосле завершения быстрой сортировки (оптимизация медианным опорным элементом) nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + // Быстрая сортировка (оптимизация глубины рекурсии) + var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); + std.debug.print("\nПосле завершения быстрой сортировки (оптимизация глубины рекурсии) nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums2); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_sorting/radix_sort.zig b/ru/codes/zig/chapter_sorting/radix_sort.zig new file mode 100644 index 000000000..7e96b6e97 --- /dev/null +++ b/ru/codes/zig/chapter_sorting/radix_sort.zig @@ -0,0 +1,77 @@ +// File: radix_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Получить k-й разряд элемента num, где exp = 10^(k-1) +fn digit(num: i32, exp: i32) i32 { + // Передача exp вместо k позволяет избежать повторного дорогостоящего вычисления степени + return @mod(@divFloor(num, exp), 10); +} + +// Сортировка подсчетом (сортировка по k-му разряду nums) +fn countingSortDigit(nums: []i32, exp: i32) !void { + // Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var counter = try mem_allocator.alloc(usize, 10); + @memset(counter, 0); + var n = nums.len; + // Подсчитать число появлений каждой цифры от 0 до 9 + for (nums) |num| { + var d: u32 = @bitCast(digit(num, exp)); // Получить k-й разряд nums[i], обозначив его как d + counter[d] += 1; // Подсчитать число появлений цифры d + } + // Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива» + var i: usize = 1; + while (i < 10) : (i += 1) { + counter[i] += counter[i - 1]; + } + // Выполняя обратный проход, заполнить res элементами по статистике в корзинах + var res = try mem_allocator.alloc(i32, n); + i = n - 1; + while (i >= 0) : (i -= 1) { + var d: u32 = @bitCast(digit(nums[i], exp)); + var j = counter[d] - 1; // Получить индекс j цифры d в массиве + res[j] = nums[i]; // Поместить текущий элемент по индексу j + counter[d] -= 1; // Уменьшить количество d на 1 + if (i == 0) break; + } + // Перезаписать исходный массив nums результатом + i = 0; + while (i < n) : (i += 1) { + nums[i] = res[i]; + } +} + +// Поразрядная сортировка +fn radixSort(nums: []i32) !void { + // Получить максимальный элемент массива, чтобы определить максимальное число разрядов + var m: i32 = std.math.minInt(i32); + for (nums) |num| { + if (num > m) m = num; + } + // Проходить разряды от младшего к старшему + var exp: i32 = 1; + while (exp <= m) : (exp *= 10) { + // Выполнить сортировку подсчетом по k-му разряду элементов массива + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // то есть exp = 10^(k-1) + try countingSortDigit(nums, exp); + } +} + +// Driver Code +pub fn main() !void { + // Поразрядная сортировка + var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; + try radixSort(&nums); + std.debug.print("После завершения поразрядной сортировки nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_stack_and_queue/array_queue.zig b/ru/codes/zig/chapter_stack_and_queue/array_queue.zig new file mode 100644 index 000000000..97861e3e7 --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/array_queue.zig @@ -0,0 +1,140 @@ +// File: array_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Очередь на основе кольцевого массива +pub fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // Массив для хранения элементов очереди + cap: usize = 0, // Вместимость очереди + front: usize = 0, // Указатель head, указывающий на первый элемент очереди + queSize: usize = 0, // Указатель хвоста, указывающий на позицию после хвоста + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти + + // Конструктор (выделение памяти + инициализация массива) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // Деструктор (освобождение памяти) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // Получить вместимость очереди + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // Получение длины очереди + pub fn size(self: *Self) usize { + return self.queSize; + } + + // Проверка, пуста ли очередь + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // Поместить в очередь + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("Очередь заполнена\n", .{}); + return; + } + // Вычислить указатель хвоста, указывающий на индекс хвоста + 1 + // С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива + var rear = (self.front + self.queSize) % self.capacity(); + // Добавить num после хвостового узла + self.nums[rear] = num; + self.queSize += 1; + } + + // Извлечь из очереди + pub fn pop(self: *Self) T { + var num = self.peek(); + // Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // Доступ к элементу в начале очереди + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("очередь пуста"); + return self.nums[self.front]; + } + + // Вернуть массив + pub fn toArray(self: *Self) ![]T { + // Преобразовывать только элементы списка в пределах фактической длины + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация очереди + var capacity: usize = 10; + var queue = ArrayQueue(i32){}; + try queue.init(std.heap.page_allocator, capacity); + defer queue.deinit(); + + // Добавление элемента в очередь + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("Очередь queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // Доступ к элементу в начале очереди + var peek = queue.peek(); + std.debug.print("\nЭлемент в начале очереди peek = {}", .{peek}); + + // Извлечение элемента из очереди + var pop = queue.pop(); + std.debug.print("\nИзвлечен элемент pop = {}, очередь после извлечения queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // Получение длины очереди + var size = queue.size(); + std.debug.print("\nДлина очереди size = {}", .{size}); + + // Проверка, пуста ли очередь + var is_empty = queue.isEmpty(); + std.debug.print("\nПуста ли очередь = {}", .{is_empty}); + + // Проверка кольцевого массива + var i: i32 = 0; + while (i < 10) : (i += 1) { + try queue.push(i); + _ = queue.pop(); + std.debug.print("\nПосле {}-го добавления и извлечения queue = ", .{i}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + } + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_stack_and_queue/array_stack.zig b/ru/codes/zig/chapter_stack_and_queue/array_stack.zig new file mode 100644 index 000000000..983edf630 --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/array_stack.zig @@ -0,0 +1,97 @@ +// File: array_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Стек на основе массива +pub fn ArrayStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack: ?std.ArrayList(T) = null, + + // Конструктор (выделение памяти + инициализация стека) + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.stack == null) { + self.stack = std.ArrayList(T).init(allocator); + } + } + + // Деструктор (освобождение памяти) + pub fn deinit(self: *Self) void { + if (self.stack == null) return; + self.stack.?.deinit(); + } + + // Получение длины стека + pub fn size(self: *Self) usize { + return self.stack.?.items.len; + } + + // Проверка, пуст ли стек + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // Доступ к верхнему элементу стека + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("стек пуст"); + return self.stack.?.items[self.size() - 1]; + } + + // Поместить в стек + pub fn push(self: *Self, num: T) !void { + try self.stack.?.append(num); + } + + // Извлечь из стека + pub fn pop(self: *Self) T { + var num = self.stack.?.pop(); + return num; + } + + // Вернуть ArrayList + pub fn toList(self: *Self) std.ArrayList(T) { + return self.stack.?; + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация стека + var stack = ArrayStack(i32){}; + stack.init(std.heap.page_allocator); + // Отложенное освобождение памяти + defer stack.deinit(); + + // Помещение элемента в стек + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("Стек stack = ", .{}); + inc.PrintUtil.printList(i32, stack.toList()); + + // Доступ к верхнему элементу стека + var peek = stack.peek(); + std.debug.print("\nВерхний элемент стека peek = {}", .{peek}); + + // Извлечение элемента из стека + var top = stack.pop(); + std.debug.print("\nИзвлечен элемент pop = {}, стек после извлечения stack = ", .{top}); + inc.PrintUtil.printList(i32, stack.toList()); + + // Получение длины стека + var size = stack.size(); + std.debug.print("\nДлина стека size = {}", .{size}); + + // Проверка, пуст ли стек + var is_empty = stack.isEmpty(); + std.debug.print("\nПуст ли стек = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_stack_and_queue/deque.zig b/ru/codes/zig/chapter_stack_and_queue/deque.zig new file mode 100644 index 000000000..b5764e4a4 --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/deque.zig @@ -0,0 +1,51 @@ +// File: deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // Инициализация двусторонней очереди + const L = std.TailQueue(i32); + var deque = L{}; + + // Добавление элемента в очередь + var node1 = L.Node{ .data = 2 }; + var node2 = L.Node{ .data = 5 }; + var node3 = L.Node{ .data = 4 }; + var node4 = L.Node{ .data = 3 }; + var node5 = L.Node{ .data = 1 }; + deque.append(&node1); // Добавить в хвост очереди + deque.append(&node2); + deque.append(&node3); + deque.prepend(&node4); // Добавить в голову очереди + deque.prepend(&node5); + std.debug.print("Двусторонняя очередь deque = ", .{}); + inc.PrintUtil.printQueue(i32, deque); + + // Доступ к элементу + var peek_first = deque.first.?.data; // Элемент в голове очереди + std.debug.print("\nЭлемент в начале очереди peek_first = {}", .{peek_first}); + var peek_last = deque.last.?.data; // Элемент в хвосте очереди + std.debug.print("\nЭлемент в конце очереди peek_last = {}", .{peek_last}); + + // Извлечение элемента из очереди + var pop_first = deque.popFirst().?.data; // Извлечь элемент из головы очереди + std.debug.print("\nИзвлечен элемент из головы pop_first = {}, deque после извлечения из головы = ", .{pop_first}); + inc.PrintUtil.printQueue(i32, deque); + var pop_last = deque.pop().?.data; // Извлечь элемент из хвоста очереди + std.debug.print("\nИзвлечен элемент из хвоста pop_last = {}, deque после извлечения из хвоста = ", .{pop_last}); + inc.PrintUtil.printQueue(i32, deque); + + // Получение длины двусторонней очереди + var size = deque.len; + std.debug.print("\nДлина двусторонней очереди size = {}", .{size}); + + // Проверка, пуста ли двусторонняя очередь + var is_empty = if (deque.len == 0) true else false; + std.debug.print("\nПуста ли двусторонняя очередь = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig b/ru/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig new file mode 100644 index 000000000..268b47516 --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig @@ -0,0 +1,207 @@ +// File: linkedlist_deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Узел двусвязного списка +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // Значение узла + next: ?*Self = null, // Указатель на узел-преемник + prev: ?*Self = null, // Указатель на узел-предшественник + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; +} + +// Двусторонняя очередь на основе двусвязного списка +pub fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // Головной узел front + rear: ?*ListNode(T) = null, // Хвостовой узел rear + que_size: usize = 0, // Длина двусторонней очереди + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти + + // Конструктор (выделение памяти + инициализация очереди) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // Деструктор (освобождение памяти) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // Получение длины двусторонней очереди + pub fn size(self: *Self) usize { + return self.que_size; + } + + // Проверка, пуста ли двусторонняя очередь + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // Операция добавления в очередь + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // Если связный список пуст, сделать так, чтобы и front, и rear указывали на node + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // Операция добавления в голову очереди + } else if (is_front) { + // Добавить node в голову списка + self.front.?.prev = node; + node.next = self.front; + self.front = node; // Обновить головной узел + // Операция добавления в хвост очереди + } else { + // Добавить node в хвост списка + self.rear.?.next = node; + node.prev = self.rear; + self.rear = node; // Обновить хвостовой узел + } + self.que_size += 1; // Обновить длину очереди + } + + // Добавление в голову очереди + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // Добавление в хвост очереди + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // Операция извлечения из очереди + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("двусторонняя очередь пуста"); + var val: T = undefined; + // Операция извлечения из головы очереди + if (is_front) { + val = self.front.?.val; // Временно сохранить значение головного узла + // Удалить головной узел + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // Обновить головной узел + // Операция извлечения из хвоста очереди + } else { + val = self.rear.?.val; // Временно сохранить значение хвостового узла + // Удалить хвостовой узел + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // Обновить хвостовой узел + } + self.que_size -= 1; // Обновить длину очереди + return val; + } + + // Извлечение из головы очереди + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // Извлечение из хвоста очереди + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // Доступ к элементу в начале очереди + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("двусторонняя очередь пуста"); + return self.front.?.val; + } + + // Доступ к элементу в конце очереди + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("двусторонняя очередь пуста"); + return self.rear.?.val; + } + + // Вернуть массив для вывода + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация двусторонней очереди + var deque = LinkedListDeque(i32){}; + try deque.init(std.heap.page_allocator); + defer deque.deinit(); + try deque.pushLast(3); + try deque.pushLast(2); + try deque.pushLast(5); + std.debug.print("Двусторонняя очередь deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // Доступ к элементу + var peek_first = deque.peekFirst(); + std.debug.print("\nЭлемент в начале очереди peek_first = {}", .{peek_first}); + var peek_last = deque.peekLast(); + std.debug.print("\nЭлемент в конце очереди peek_last = {}", .{peek_last}); + + // Добавление элемента в очередь + try deque.pushLast(4); + std.debug.print("\nПосле добавления элемента 4 в хвост deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + try deque.pushFirst(1); + std.debug.print("\nПосле добавления элемента 1 в голову deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // Извлечение элемента из очереди + var pop_last = deque.popLast(); + std.debug.print("\nИзвлечен элемент из хвоста = {}, deque после извлечения из хвоста = ", .{pop_last}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + var pop_first = deque.popFirst(); + std.debug.print("\nИзвлечен элемент из головы = {}, deque после извлечения из головы = ", .{pop_first}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // Получение длины двусторонней очереди + var size = deque.size(); + std.debug.print("\nДлина двусторонней очереди size = {}", .{size}); + + // Проверка, пуста ли двусторонняя очередь + var is_empty = deque.isEmpty(); + std.debug.print("\nПуста ли двусторонняя очередь = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig b/ru/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig new file mode 100644 index 000000000..6a03fa58b --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig @@ -0,0 +1,127 @@ +// File: linkedlist_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Очередь на основе связного списка +pub fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // Головной узел front + rear: ?*inc.ListNode(T) = null, // Хвостовой узел rear + que_size: usize = 0, // Длина очереди + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти + + // Конструктор (выделение памяти + инициализация очереди) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // Деструктор (освобождение памяти) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // Получение длины очереди + pub fn size(self: *Self) usize { + return self.que_size; + } + + // Проверка, пуста ли очередь + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // Доступ к элементу в начале очереди + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("очередь пуста"); + return self.front.?.val; + } + + // Поместить в очередь + pub fn push(self: *Self, num: T) !void { + // Добавить num после хвостового узла + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел + if (self.front == null) { + self.front = node; + self.rear = node; + // Если очередь не пуста, добавить этот узел после хвостового узла + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // Извлечь из очереди + pub fn pop(self: *Self) T { + var num = self.peek(); + // Удалить головной узел + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // Преобразовать связный список в массив + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация очереди + var queue = LinkedListQueue(i32){}; + try queue.init(std.heap.page_allocator); + defer queue.deinit(); + + // Добавление элемента в очередь + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("Очередь queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // Доступ к элементу в начале очереди + var peek = queue.peek(); + std.debug.print("\nЭлемент в начале очереди peek = {}", .{peek}); + + // Извлечение элемента из очереди + var pop = queue.pop(); + std.debug.print("\nИзвлечен элемент pop = {}, очередь после извлечения queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // Получение длины очереди + var size = queue.size(); + std.debug.print("\nДлина очереди size = {}", .{size}); + + // Проверка, пуста ли очередь + var is_empty = queue.isEmpty(); + std.debug.print("\nПуста ли очередь = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig b/ru/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig new file mode 100644 index 000000000..77d87d234 --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig @@ -0,0 +1,118 @@ +// File: linkedlist_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Стек на основе связного списка +pub fn LinkedListStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack_top: ?*inc.ListNode(T) = null, // Использовать головной узел как вершину стека + stk_size: usize = 0, // Длина стека + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти + + // Конструктор (выделение памяти + инициализация стека) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.stack_top = null; + self.stk_size = 0; + } + + // Деструктор (освобождение памяти) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // Получение длины стека + pub fn size(self: *Self) usize { + return self.stk_size; + } + + // Проверка, пуст ли стек + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // Доступ к верхнему элементу стека + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("стек пуст"); + return self.stack_top.?.val; + } + + // Поместить в стек + pub fn push(self: *Self, num: T) !void { + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; + } + + // Извлечь из стека + pub fn pop(self: *Self) T { + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; + return num; + } + + // Преобразовать стек в массив + pub fn toArray(self: *Self) ![]T { + var node = self.stack_top; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[res.len - i - 1] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация стека + var stack = LinkedListStack(i32){}; + try stack.init(std.heap.page_allocator); + // Отложенное освобождение памяти + defer stack.deinit(); + + // Помещение элемента в стек + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("Стек stack = ", .{}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // Доступ к верхнему элементу стека + var peek = stack.peek(); + std.debug.print("\nВерхний элемент стека top = {}", .{peek}); + + // Извлечение элемента из стека + var pop = stack.pop(); + std.debug.print("\nИзвлечен элемент pop = {}, стек после извлечения stack = ", .{pop}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // Получение длины стека + var size = stack.size(); + std.debug.print("\nДлина стека size = {}", .{size}); + + // Проверка, пуст ли стек + var is_empty = stack.isEmpty(); + std.debug.print("\nПуст ли стек = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ru/codes/zig/chapter_stack_and_queue/queue.zig b/ru/codes/zig/chapter_stack_and_queue/queue.zig new file mode 100644 index 000000000..98b80e837 --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/queue.zig @@ -0,0 +1,46 @@ +// File: queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // Инициализация очереди + const L = std.TailQueue(i32); + var queue = L{}; + + // Добавление элемента в очередь + var node1 = L.Node{ .data = 1 }; + var node2 = L.Node{ .data = 3 }; + var node3 = L.Node{ .data = 2 }; + var node4 = L.Node{ .data = 5 }; + var node5 = L.Node{ .data = 4 }; + queue.append(&node1); + queue.append(&node2); + queue.append(&node3); + queue.append(&node4); + queue.append(&node5); + std.debug.print("Очередь queue = ", .{}); + inc.PrintUtil.printQueue(i32, queue); + + // Доступ к элементу в начале очереди + var peek = queue.first.?.data; + std.debug.print("\nЭлемент в начале очереди peek = {}", .{peek}); + + // Извлечение элемента из очереди + var pop = queue.popFirst().?.data; + std.debug.print("\nИзвлечен элемент pop = {}, очередь после извлечения queue = ", .{pop}); + inc.PrintUtil.printQueue(i32, queue); + + // Получение длины очереди + var size = queue.len; + std.debug.print("\nДлина очереди size = {}", .{size}); + + // Проверка, пуста ли очередь + var is_empty = if (queue.len == 0) true else false; + std.debug.print("\nПуста ли очередь = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_stack_and_queue/stack.zig b/ru/codes/zig/chapter_stack_and_queue/stack.zig new file mode 100644 index 000000000..9fdfd718b --- /dev/null +++ b/ru/codes/zig/chapter_stack_and_queue/stack.zig @@ -0,0 +1,43 @@ +// File: stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // Инициализировать стек + // В Zig рекомендуется использовать ArrayList как стек + var stack = std.ArrayList(i32).init(std.heap.page_allocator); + // Отложенное освобождение памяти + defer stack.deinit(); + + // Помещение элемента в стек + try stack.append(1); + try stack.append(3); + try stack.append(2); + try stack.append(5); + try stack.append(4); + std.debug.print("Стек stack = ", .{}); + inc.PrintUtil.printList(i32, stack); + + // Доступ к верхнему элементу стека + var peek = stack.items[stack.items.len - 1]; + std.debug.print("\nВерхний элемент стека peek = {}", .{peek}); + + // Извлечение элемента из стека + var pop = stack.pop(); + std.debug.print("\nИзвлечен элемент pop = {}, стек после извлечения stack = ", .{pop}); + inc.PrintUtil.printList(i32, stack); + + // Получение длины стека + var size = stack.items.len; + std.debug.print("\nДлина стека size = {}", .{size}); + + // Проверка, пуст ли стек + var is_empty = if (stack.items.len == 0) true else false; + std.debug.print("\nПуст ли стек = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_tree/avl_tree.zig b/ru/codes/zig/chapter_tree/avl_tree.zig new file mode 100644 index 000000000..d50317695 --- /dev/null +++ b/ru/codes/zig/chapter_tree/avl_tree.zig @@ -0,0 +1,249 @@ +// File: avl_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// AVL-дерево +pub fn AVLTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, // Корневой узел + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти + + // Конструктор + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + } + + // Метод-деструктор + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // Получить высоту узла + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // Высота пустого узла равна -1, высота листового узла равна 0 + return if (node == null) -1 else node.?.height; + } + + // Обновить высоту узла + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // Высота узла равна высоте более высокого поддерева + 1 + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + + // Получить коэффициент баланса + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // Коэффициент баланса пустого узла равен 0 + if (node == null) return 0; + // Коэффициент баланса узла = высота левого поддерева - высота правого поддерева + return self.height(node.?.left) - self.height(node.?.right); + } + + // Операция правого вращения + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // Выполнить правое вращение узла node вокруг child + child.?.right = node; + node.?.left = grandChild; + // Обновить высоту узла + self.updateHeight(node); + self.updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + // Операция левого вращения + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // Выполнить левое вращение узла node вокруг child + child.?.left = node; + node.?.right = grandChild; + // Обновить высоту узла + self.updateHeight(node); + self.updateHeight(child); + // Вернуть корневой узел поддерева после вращения + return child; + } + + // Выполнить вращение, чтобы снова сбалансировать поддерево + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // Получить коэффициент баланса узла node + var balance_factor = self.balanceFactor(node); + // Левосторонне перекошенное дерево + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // Правое вращение + return self.rightRotate(node); + } else { + // Сначала левое вращение, затем правое + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // Правосторонне перекошенное дерево + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // Левое вращение + return self.leftRotate(node); + } else { + // Сначала правое вращение, затем левое + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // Дерево сбалансировано, вращение не требуется, вернуть сразу + return node; + } + + // Вставка узла + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // Рекурсивная вставка узла (вспомогательный метод) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. Найти позицию вставки и вставить узел + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // Повторяющийся узел не вставлять, сразу вернуть + } + self.updateHeight(node); // Обновить высоту узла + // 2. Выполнить вращение, чтобы снова сбалансировать поддерево + node = self.rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + // Удаление узла + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // Рекурсивное удаление узла (вспомогательный метод) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. Найти узел и удалить его + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // Число дочерних узлов = 0, удалить node и сразу вернуть + if (child == null) { + return null; + // Число дочерних узлов = 1, удалить node напрямую + } else { + node = child; + } + } else { + // Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // Обновить высоту узла + // 2. Выполнить вращение, чтобы снова сбалансировать поддерево + node = self.rotate(node); + // Вернуть корневой узел поддерева + return node; + } + + // Поиск узла + fn search(self: *Self, val: T) ?*inc.TreeNode(T) { + var cur = self.root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.?.val < val) { + cur = cur.?.right; + // Целевой узел находится в левом поддереве cur + } else if (cur.?.val > val) { + cur = cur.?.left; + // Найти целевой узел и выйти из цикла + } else { + break; + } + } + // Вернуть целевой узел + return cur; + } + }; +} + +pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { + var tree = tree_; + try tree.insert(val); + std.debug.print("\nПосле вставки узла {} AVL-дерево имеет вид\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { + var tree = tree_; + tree.remove(val); + std.debug.print("\nПосле удаления узла {} AVL-дерево имеет вид\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +// Driver Code +pub fn main() !void { + // Инициализация пустого AVL-дерева + var avl_tree = AVLTree(i32){}; + avl_tree.init(std.heap.page_allocator); + defer avl_tree.deinit(); + + // Вставка узла + // Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла + try testInsert(i32, &avl_tree, 1); + try testInsert(i32, &avl_tree, 2); + try testInsert(i32, &avl_tree, 3); + try testInsert(i32, &avl_tree, 4); + try testInsert(i32, &avl_tree, 5); + try testInsert(i32, &avl_tree, 8); + try testInsert(i32, &avl_tree, 7); + try testInsert(i32, &avl_tree, 9); + try testInsert(i32, &avl_tree, 10); + try testInsert(i32, &avl_tree, 6); + + // Вставка повторяющегося узла + try testInsert(i32, &avl_tree, 7); + + // Удаление узла + // Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла + testRemove(i32, &avl_tree, 8); // Удаление узла степени 0 + testRemove(i32, &avl_tree, 5); // Удаление узла степени 1 + testRemove(i32, &avl_tree, 4); // Удаление узла степени 2 + + // Поиск узла + var node = avl_tree.search(7).?; + std.debug.print("\nНайденный объект узла = {any}, значение узла = {}\n", .{node, node.val}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_tree/binary_search_tree.zig b/ru/codes/zig/chapter_tree/binary_search_tree.zig new file mode 100644 index 000000000..f404f7a5d --- /dev/null +++ b/ru/codes/zig/chapter_tree/binary_search_tree.zig @@ -0,0 +1,182 @@ +// File: binary_search_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Двоичное дерево поиска +pub fn BinarySearchTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // Аллокатор памяти + + // Конструктор + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // Отсортировать массив + self.root = try self.buildTree(nums, 0, nums.len - 1); // Построить двоичное дерево поиска + } + + // Метод-деструктор + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // Построить двоичное дерево поиска + fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { + if (i > j) return null; + // Использовать средний узел массива как корневой узел + var mid = i + (j - i) / 2; + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(nums[mid]); + // Рекурсивно построить левое и правое поддеревья + if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); + node.right = try self.buildTree(nums, mid + 1, j); + return node; + } + + // Получить корневой узел двоичного дерева + fn getRoot(self: *Self) ?*inc.TreeNode(T) { + return self.root; + } + + // Поиск узла + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.root; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Целевой узел находится в правом поддереве cur + if (cur.?.val < num) { + cur = cur.?.right; + // Целевой узел находится в левом поддереве cur + } else if (cur.?.val > num) { + cur = cur.?.left; + // Найти целевой узел и выйти из цикла + } else { + break; + } + } + // Вернуть целевой узел + return cur; + } + + // Вставка узла + fn insert(self: *Self, num: T) !void { + // Если дерево пусто, инициализировать корневой узел + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти повторяющийся узел и сразу вернуть + if (cur.?.val == num) return; + pre = cur; + // Позиция вставки находится в правом поддереве cur + if (cur.?.val < num) { + cur = cur.?.right; + // Позиция вставки находится в левом поддереве cur + } else { + cur = cur.?.left; + } + } + // Вставка узла + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + + // Удаление узла + fn remove(self: *Self, num: T) void { + // Если дерево пусто, сразу вернуть + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // Искать в цикле и выйти после прохода за листовой узел + while (cur != null) { + // Найти узел для удаления и выйти из цикла + if (cur.?.val == num) break; + pre = cur; + // Узел для удаления находится в правом поддереве cur + if (cur.?.val < num) { + cur = cur.?.right; + // Узел для удаления находится в левом поддереве cur + } else { + cur = cur.?.left; + } + } + // Если узел для удаления отсутствует, сразу вернуть + if (cur == null) return; + // Число дочерних узлов = 0 или 1 + if (cur.?.left == null or cur.?.right == null) { + // Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // Удалить узел cur + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // Число дочерних узлов = 2 + } else { + // Получить следующий узел после cur в симметричном обходе + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // Рекурсивно удалить узел tmp + self.remove(tmp.?.val); + // Перезаписать cur значением tmp + cur.?.val = tmp_val; + } + } + }; +} + +// Driver Code +pub fn main() !void { + // Инициализация двоичного дерева + var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + var bst = BinarySearchTree(i32){}; + try bst.init(std.heap.page_allocator, &nums); + defer bst.deinit(); + std.debug.print("Инициализированное двоичное дерево\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // Поиск узла + var node = bst.search(7); + std.debug.print("\nНайденный объект узла = {any}, значение узла = {}\n", .{node, node.?.val}); + + // Вставка узла + try bst.insert(16); + std.debug.print("\nПосле вставки узла 16 двоичное дерево имеет вид\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // Удаление узла + bst.remove(1); + std.debug.print("\nПосле удаления узла 1 двоичное дерево имеет вид\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(2); + std.debug.print("\nПосле удаления узла 2 двоичное дерево имеет вид\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(4); + std.debug.print("\nПосле удаления узла 4 двоичное дерево имеет вид\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ru/codes/zig/chapter_tree/binary_tree.zig b/ru/codes/zig/chapter_tree/binary_tree.zig new file mode 100644 index 000000000..01c50fa6a --- /dev/null +++ b/ru/codes/zig/chapter_tree/binary_tree.zig @@ -0,0 +1,39 @@ +// File: binary_tree.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // Инициализация двоичного дерева + // Инициализация узлов + var n1 = inc.TreeNode(i32){ .val = 1 }; + var n2 = inc.TreeNode(i32){ .val = 2 }; + var n3 = inc.TreeNode(i32){ .val = 3 }; + var n4 = inc.TreeNode(i32){ .val = 4 }; + var n5 = inc.TreeNode(i32){ .val = 5 }; + // Построить связи между узлами (указатели) + n1.left = &n2; + n1.right = &n3; + n2.left = &n4; + n2.right = &n5; + std.debug.print("Инициализация двоичного дерева\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + // Вставка и удаление узлов + var p = inc.TreeNode(i32){ .val = 0 }; + // Вставить узел P между n1 -> n2 + n1.left = &p; + p.left = &n2; + std.debug.print("После вставки узла P\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + // Удаление узла + n1.left = &n2; + std.debug.print("После удаления узла P\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ru/codes/zig/chapter_tree/binary_tree_bfs.zig b/ru/codes/zig/chapter_tree/binary_tree_bfs.zig new file mode 100644 index 000000000..e23aa8345 --- /dev/null +++ b/ru/codes/zig/chapter_tree/binary_tree_bfs.zig @@ -0,0 +1,57 @@ +// File: binary_tree_bfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Обход в ширину +fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // Инициализировать очередь и добавить корневой узел + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // Инициализировать список для хранения последовательности обхода + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // Извлечение из очереди + var node = queue_node.data; + try list.append(node.val); // Сохранить значение узла + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // Поместить левый дочерний узел в очередь + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // Поместить правый дочерний узел в очередь + } + } + return list; +} + +// Driver Code +pub fn main() !void { + // Инициализация аллокатора памяти + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("Инициализация двоичного дерева\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // Обход в ширину + var list = try levelOrder(i32, mem_allocator, root.?); + defer list.deinit(); + std.debug.print("\nПоследовательность печати узлов при обходе в ширину = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/chapter_tree/binary_tree_dfs.zig b/ru/codes/zig/chapter_tree/binary_tree_dfs.zig new file mode 100644 index 000000000..27365a9f9 --- /dev/null +++ b/ru/codes/zig/chapter_tree/binary_tree_dfs.zig @@ -0,0 +1,70 @@ +// File: binary_tree_dfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +var list = std.ArrayList(i32).init(std.heap.page_allocator); + +// Предварительный обход +fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // Порядок обхода: корень -> левое поддерево -> правое поддерево + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); +} + +// Симметричный обход +fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // Порядок обхода: левое поддерево -> корень -> правое поддерево + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); +} + +// Обратный обход +fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // Порядок обхода: левое поддерево -> правое поддерево -> корень + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); +} + +// Driver Code +pub fn main() !void { + // Инициализация аллокатора памяти + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // Инициализировать двоичное дерево + // Здесь используется функция, напрямую строящая двоичное дерево из массива + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("Инициализация двоичного дерева\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // Предварительный обход + list.clearRetainingCapacity(); + try preOrder(i32, root); + std.debug.print("\nПоследовательность печати узлов при предварительном обходе = ", .{}); + inc.PrintUtil.printList(i32, list); + + // Симметричный обход + list.clearRetainingCapacity(); + try inOrder(i32, root); + std.debug.print("\nПоследовательность печати узлов при симметричном обходе = ", .{}); + inc.PrintUtil.printList(i32, list); + + // Обратный обход + list.clearRetainingCapacity(); + try postOrder(i32, root); + std.debug.print("\nПоследовательность печати узлов при обратном обходе = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ru/codes/zig/include/PrintUtil.zig b/ru/codes/zig/include/PrintUtil.zig new file mode 100644 index 000000000..09ded4efa --- /dev/null +++ b/ru/codes/zig/include/PrintUtil.zig @@ -0,0 +1,42 @@ +// File: PrintUtil.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; + +// Вывести очередь +pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { + var node = queue.first; + std.debug.print("[", .{}); + var i: i32 = 0; + while (node != null) : (i += 1) { + var data = node.?.data; + std.debug.print("{}{s}", .{ data, if (i == queue.len - 1) "]" else ", " }); + node = node.?.next; + } +} + +// Вывести хеш-таблицу +pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { + var it = map.iterator(); + while (it.next()) |kv| { + var key = kv.key_ptr.*; + var value = kv.value_ptr.*; + std.debug.print("{} -> {s}\n", .{ key, value }); + } +} + +// Вывести кучу +pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { + var arr = queue.items; + var len = queue.len; + std.debug.print("Массивное представление кучи:", .{}); + printArray(T, arr[0..len]); + std.debug.print("\nДревовидное представление кучи:\n", .{}); + var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); + try printTree(root, null, false); +} diff --git a/ru/codes/zig/include/include.zig b/ru/codes/zig/include/include.zig new file mode 100644 index 000000000..4c4ec8369 --- /dev/null +++ b/ru/codes/zig/include/include.zig @@ -0,0 +1,7 @@ +// File: include.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +pub const PrintUtil = @import("PrintUtil.zig"); +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; diff --git a/ru/codes/zig/main.zig b/ru/codes/zig/main.zig new file mode 100644 index 000000000..970449d97 --- /dev/null +++ b/ru/codes/zig/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +const iteration = @import("chapter_computational_complexity/iteration.zig"); +const recursion = @import("chapter_computational_complexity/recursion.zig"); +const time_complexity = @import("chapter_computational_complexity/time_complexity.zig"); +const space_complexity = @import("chapter_computational_complexity/space_complexity.zig"); +const worst_best_time_complexity = @import("chapter_computational_complexity/worst_best_time_complexity.zig"); + +const array = @import("chapter_array_and_linkedlist/array.zig"); +const linked_list = @import("chapter_array_and_linkedlist/linked_list.zig"); +const list = @import("chapter_array_and_linkedlist/list.zig"); +const my_list = @import("chapter_array_and_linkedlist/my_list.zig"); + +pub fn main() !void { + try iteration.run(); + recursion.run(); + time_complexity.run(); + try space_complexity.run(); + worst_best_time_complexity.run(); + + try array.run(); + linked_list.run(); + try list.run(); + try my_list.run(); +} diff --git a/ru/codes/zig/utils/ListNode.zig b/ru/codes/zig/utils/ListNode.zig new file mode 100644 index 000000000..05bc75b47 --- /dev/null +++ b/ru/codes/zig/utils/ListNode.zig @@ -0,0 +1,49 @@ +// File: ListNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// Узел связного списка +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, + next: ?*Self = null, + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; +} + +// Десериализовать список в связный список +pub fn listToLinkedList(comptime T: type, allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { + var dum = try allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (list.items) |val| { + var tmp = try allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} + +// Десериализовать массив в связный список +pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (arr) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} diff --git a/ru/codes/zig/utils/TreeNode.zig b/ru/codes/zig/utils/TreeNode.zig new file mode 100644 index 000000000..3617fbe03 --- /dev/null +++ b/ru/codes/zig/utils/TreeNode.zig @@ -0,0 +1,63 @@ +// File: TreeNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// Узел двоичного дерева +pub fn TreeNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // Значение узла + height: i32 = undefined, // Высота узла + left: ?*Self = null, // Указатель на левый дочерний узел + right: ?*Self = null, // Указатель на правый дочерний узел + + // Initialize a tree node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.height = 0; + self.left = null; + self.right = null; + } + }; +} + +// Десериализовать массив в двоичное дерево +pub fn arrToTree(comptime T: type, allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { + if (arr.len == 0) return null; + var root = try allocator.create(TreeNode(T)); + root.init(arr[0]); + const L = std.TailQueue(*TreeNode(T)); + var que = L{}; + var root_node = try allocator.create(L.Node); + root_node.data = root; + que.append(root_node); + var index: usize = 0; + while (que.len > 0) { + const que_node = que.popFirst().?; + var node = que_node.data; + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.left = tmp; + var tmp_node = try allocator.create(L.Node); + tmp_node.data = node.left.?; + que.append(tmp_node); + } + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.right = tmp; + var tmp_node = try allocator.create(L.Node); + tmp_node.data = node.right.?; + que.append(tmp_node); + } + } + return root; +} diff --git a/ru/codes/zig/utils/format.zig b/ru/codes/zig/utils/format.zig new file mode 100644 index 000000000..f1bc16932 --- /dev/null +++ b/ru/codes/zig/utils/format.zig @@ -0,0 +1,140 @@ +// File: format.zig +// Created Time: 2025-07-19 +// Author: CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const ListNode = @import("ListNode.zig").ListNode; +const TreeNode = @import("TreeNode.zig").TreeNode; + +pub fn slice(items: anytype) SliceFormatter(@TypeOf(items)) { + return .{ .items = items }; +} + +pub fn SliceFormatter(comptime SliceType: type) type { + return struct { + const Self = @This(); + + items: SliceType, + + pub fn format( + self: Self, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("["); + + if (self.items.len > 0) { + for (self.items, 0..) |item, i| { + try std.fmt.format(writer, "{}", .{item}); + if (i != self.items.len - 1) { + try writer.writeAll(", "); + } + } + } + + try writer.writeAll("]"); + } + }; +} + +pub fn linkedList(comptime T: type, head: *const ListNode(T)) LinkedListFormatter(T) { + return .{ .head = head }; +} + +pub fn LinkedListFormatter(comptime T: type) type { + return struct { + const Self = @This(); + + head: *const ListNode(T), + + pub fn format( + self: Self, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try printLinkedList(self.head, writer); + } + + pub fn printLinkedList(head: *const ListNode(T), writer: anytype) !void { + try std.fmt.format(writer, "{}", .{head.val}); + if (head.next) |next_node| { + try writer.writeAll("->"); + try printLinkedList(next_node, writer); + } + } + }; +} + +pub fn tree(comptime T: type, root: ?*const TreeNode(T)) TreeFormatter(T) { + return .{ .root = root }; +} + +pub fn TreeFormatter(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*const TreeNode(T), + + pub fn format( + self: Self, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try printTree(self.root, null, false, writer); + } + + // Вывести двоичное дерево + fn printTree(root: ?*const TreeNode(T), prev: ?*Trunk, isRight: bool, writer: anytype) !void { + if (root == null) { + return; + } + + var prev_str = " "; + var trunk = Trunk{ .prev = prev, .str = prev_str }; + + try printTree(root.?.right, &trunk, true, writer); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.?.str = prev_str; + } + + try showTrunks(&trunk, writer); + try std.fmt.format(writer, "{d}\n", .{root.?.val}); + + if (prev) |_| { + prev.?.str = prev_str; + } + trunk.str = " |"; + + try printTree(root.?.left, &trunk, false, writer); + } + + // Вывести двоичное дерево + // Этот вывод дерева заимствован из TECHIE DELIGHT + // https://www.techiedelight.com/c-program-print-binary-tree/ + const Trunk = struct { + prev: ?*Trunk = null, + str: []const u8 = undefined, + + pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { + self.prev = prev; + self.str = str; + } + }; + + pub fn showTrunks(p: ?*Trunk, writer: anytype) !void { + if (p == null) return; + try showTrunks(p.?.prev, writer); + try std.fmt.format(writer, "{s}", .{p.?.str}); + } + }; +} diff --git a/ru/codes/zig/utils/utils.zig b/ru/codes/zig/utils/utils.zig new file mode 100644 index 000000000..a3d53797c --- /dev/null +++ b/ru/codes/zig/utils/utils.zig @@ -0,0 +1,8 @@ +// File: format.zig +// Created Time: 2025-07-15 +// Author: CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +pub const fmt = @import("format.zig"); +pub const ListNode = @import("ListNode.zig").ListNode; +pub const TreeNode = @import("TreeNode.zig").TreeNode; diff --git a/ru/docs/assets/covers/chapter_appendix.jpg b/ru/docs/assets/covers/chapter_appendix.jpg new file mode 100644 index 000000000..c35b7db9e Binary files /dev/null and b/ru/docs/assets/covers/chapter_appendix.jpg differ diff --git a/ru/docs/assets/covers/chapter_array_and_linkedlist.jpg b/ru/docs/assets/covers/chapter_array_and_linkedlist.jpg new file mode 100644 index 000000000..8d03142ff Binary files /dev/null and b/ru/docs/assets/covers/chapter_array_and_linkedlist.jpg differ diff --git a/ru/docs/assets/covers/chapter_backtracking.jpg b/ru/docs/assets/covers/chapter_backtracking.jpg new file mode 100644 index 000000000..ea4aa1d11 Binary files /dev/null and b/ru/docs/assets/covers/chapter_backtracking.jpg differ diff --git a/ru/docs/assets/covers/chapter_complexity_analysis.jpg b/ru/docs/assets/covers/chapter_complexity_analysis.jpg new file mode 100644 index 000000000..691523b9f Binary files /dev/null and b/ru/docs/assets/covers/chapter_complexity_analysis.jpg differ diff --git a/ru/docs/assets/covers/chapter_data_structure.jpg b/ru/docs/assets/covers/chapter_data_structure.jpg new file mode 100644 index 000000000..a6ee84428 Binary files /dev/null and b/ru/docs/assets/covers/chapter_data_structure.jpg differ diff --git a/ru/docs/assets/covers/chapter_divide_and_conquer.jpg b/ru/docs/assets/covers/chapter_divide_and_conquer.jpg new file mode 100644 index 000000000..ea85022af Binary files /dev/null and b/ru/docs/assets/covers/chapter_divide_and_conquer.jpg differ diff --git a/ru/docs/assets/covers/chapter_dynamic_programming.jpg b/ru/docs/assets/covers/chapter_dynamic_programming.jpg new file mode 100644 index 000000000..274e99157 Binary files /dev/null and b/ru/docs/assets/covers/chapter_dynamic_programming.jpg differ diff --git a/ru/docs/assets/covers/chapter_graph.jpg b/ru/docs/assets/covers/chapter_graph.jpg new file mode 100644 index 000000000..0e39cccd0 Binary files /dev/null and b/ru/docs/assets/covers/chapter_graph.jpg differ diff --git a/ru/docs/assets/covers/chapter_greedy.jpg b/ru/docs/assets/covers/chapter_greedy.jpg new file mode 100644 index 000000000..872ea8d3f Binary files /dev/null and b/ru/docs/assets/covers/chapter_greedy.jpg differ diff --git a/ru/docs/assets/covers/chapter_hashing.jpg b/ru/docs/assets/covers/chapter_hashing.jpg new file mode 100644 index 000000000..7a4c95a9c Binary files /dev/null and b/ru/docs/assets/covers/chapter_hashing.jpg differ diff --git a/ru/docs/assets/covers/chapter_heap.jpg b/ru/docs/assets/covers/chapter_heap.jpg new file mode 100644 index 000000000..8d56e0abc Binary files /dev/null and b/ru/docs/assets/covers/chapter_heap.jpg differ diff --git a/ru/docs/assets/covers/chapter_hello_algo.jpg b/ru/docs/assets/covers/chapter_hello_algo.jpg new file mode 100644 index 000000000..2444c9551 Binary files /dev/null and b/ru/docs/assets/covers/chapter_hello_algo.jpg differ diff --git a/ru/docs/assets/covers/chapter_introduction.jpg b/ru/docs/assets/covers/chapter_introduction.jpg new file mode 100644 index 000000000..b0933c489 Binary files /dev/null and b/ru/docs/assets/covers/chapter_introduction.jpg differ diff --git a/ru/docs/assets/covers/chapter_preface.jpg b/ru/docs/assets/covers/chapter_preface.jpg new file mode 100644 index 000000000..b37f0c1b2 Binary files /dev/null and b/ru/docs/assets/covers/chapter_preface.jpg differ diff --git a/ru/docs/assets/covers/chapter_searching.jpg b/ru/docs/assets/covers/chapter_searching.jpg new file mode 100644 index 000000000..9bc3d79fe Binary files /dev/null and b/ru/docs/assets/covers/chapter_searching.jpg differ diff --git a/ru/docs/assets/covers/chapter_sorting.jpg b/ru/docs/assets/covers/chapter_sorting.jpg new file mode 100644 index 000000000..f89612ad7 Binary files /dev/null and b/ru/docs/assets/covers/chapter_sorting.jpg differ diff --git a/ru/docs/assets/covers/chapter_stack_and_queue.jpg b/ru/docs/assets/covers/chapter_stack_and_queue.jpg new file mode 100644 index 000000000..e41820d18 Binary files /dev/null and b/ru/docs/assets/covers/chapter_stack_and_queue.jpg differ diff --git a/ru/docs/assets/covers/chapter_tree.jpg b/ru/docs/assets/covers/chapter_tree.jpg new file mode 100644 index 000000000..b780bfe1d Binary files /dev/null and b/ru/docs/assets/covers/chapter_tree.jpg differ diff --git a/ru/docs/chapter_appendix/contribution.assets/edit_markdown.png b/ru/docs/chapter_appendix/contribution.assets/edit_markdown.png new file mode 100644 index 000000000..6f99dc943 Binary files /dev/null and b/ru/docs/chapter_appendix/contribution.assets/edit_markdown.png differ diff --git a/ru/docs/chapter_appendix/contribution.md b/ru/docs/chapter_appendix/contribution.md new file mode 100644 index 000000000..b4b368cd8 --- /dev/null +++ b/ru/docs/chapter_appendix/contribution.md @@ -0,0 +1,47 @@ +# Присоединяйтесь к созданию книги + +Возможности автора ограничены, поэтому в книге неизбежно могут встречаться упущения и ошибки. Просим отнестись к этому с пониманием. Если вы заметите опечатки, неработающие ссылки, пропуски в содержании, двусмысленные формулировки, неясные объяснения или неудачную структуру изложения, пожалуйста, помогите нам это исправить, чтобы читатели получили более качественный учебный ресурс. + +GitHub ID всех [участников](https://github.com/krahets/hello-algo/graphs/contributors) будут указаны на главных страницах репозитория книги, веб-версии и PDF-версии в знак благодарности за их бескорыстный вклад в сообщество открытого исходного кода. + +!!! success "Сила открытого исходного кода" + + Интервал между двумя тиражами бумажной книги обычно довольно велик, поэтому обновлять содержание очень неудобно. + + В этой же открытой книге цикл обновления содержания сокращается до нескольких дней, а иногда даже до нескольких часов. + +### Небольшие правки содержания + +Как показано на рисунке ниже, в правом верхнем углу каждой страницы есть "значок редактирования". Вы можете изменить текст или код следующим образом. + +1. Нажмите на "значок редактирования". Если появится сообщение "You need to fork this repository", согласитесь с этим действием. +2. Измените содержимое исходного Markdown-файла, проверьте корректность правок и постарайтесь сохранить единый стиль оформления. +3. Внизу страницы заполните описание изменений, затем нажмите кнопку "Propose file change". После перехода на следующую страницу нажмите кнопку "Create pull request", чтобы создать pull request. + +![Кнопка редактирования страницы](contribution.assets/edit_markdown.png) + +Изображения нельзя изменить напрямую, поэтому проблему с ними нужно описывать через новый [Issue](https://github.com/krahets/hello-algo/issues) или комментарий. Мы постараемся как можно быстрее перерисовать и заменить изображение. + +### Создание содержания + +Если вам интересно участвовать в этом проекте с открытым исходным кодом, например переводить код на другие языки программирования или расширять содержание статей, то следует придерживаться следующего рабочего процесса Pull Request. + +1. Войдите в GitHub и сделайте Fork [репозитория книги](https://github.com/krahets/hello-algo) в свой личный аккаунт. +2. Перейдите на страницу своего Fork-репозитория и с помощью команды `git clone` клонируйте репозиторий локально. +3. Создавайте и редактируйте содержание локально, затем проведите полное тестирование и проверьте корректность кода. +4. Сделайте Commit для локальных изменений, после чего выполните Push в удаленный репозиторий. +5. Обновите страницу репозитория и нажмите кнопку "Create pull request", чтобы отправить pull request. + +### Развертывание Docker + +В корневом каталоге `hello-algo` выполните следующий Docker-скрипт, после чего проект будет доступен по адресу `http://localhost:8000`: + +```shell +docker-compose up -d +``` + +Удалить развертывание можно следующей командой: + +```shell +docker-compose down +``` diff --git a/ru/docs/chapter_appendix/index.md b/ru/docs/chapter_appendix/index.md new file mode 100644 index 000000000..3aa3bd236 --- /dev/null +++ b/ru/docs/chapter_appendix/index.md @@ -0,0 +1,3 @@ +# Приложение + +![Приложение](../assets/covers/chapter_appendix.jpg) diff --git a/ru/docs/chapter_appendix/installation.assets/vscode_extension_installation.png b/ru/docs/chapter_appendix/installation.assets/vscode_extension_installation.png new file mode 100644 index 000000000..6b8dbfb4a Binary files /dev/null and b/ru/docs/chapter_appendix/installation.assets/vscode_extension_installation.png differ diff --git a/ru/docs/chapter_appendix/installation.assets/vscode_installation.png b/ru/docs/chapter_appendix/installation.assets/vscode_installation.png new file mode 100644 index 000000000..fce776429 Binary files /dev/null and b/ru/docs/chapter_appendix/installation.assets/vscode_installation.png differ diff --git a/ru/docs/chapter_appendix/installation.md b/ru/docs/chapter_appendix/installation.md new file mode 100644 index 000000000..637710dbf --- /dev/null +++ b/ru/docs/chapter_appendix/installation.md @@ -0,0 +1,68 @@ +# Установка среды программирования + +## Установка IDE + +В качестве локальной интегрированной среды разработки (IDE) рекомендуется использовать открытую и легковесную VS Code. Перейдите на [официальный сайт VS Code](https://code.visualstudio.com/), выберите версию для своей операционной системы и установите ее. + +![Загрузка VS Code с официального сайта](installation.assets/vscode_installation.png) + +VS Code обладает мощной экосистемой расширений и поддерживает запуск и отладку большинства языков программирования. Например, после установки расширения "Python Extension Pack" можно отлаживать код на Python. Процесс установки показан на рисунке ниже. + +![Установка расширений VS Code](installation.assets/vscode_extension_installation.png) + +## Установка языковой среды + +### Среда Python + +1. Загрузите и установите [Miniconda3](https://docs.conda.io/en/latest/miniconda.html), требуется Python 3.10 или более новая версия. +2. В магазине расширений VS Code найдите `python` и установите Python Extension Pack. +3. (Необязательно) Введите в командной строке `pip install black`, чтобы установить инструмент форматирования кода. + +### Среда C/C++ + +1. В Windows требуется установить [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([руководство по настройке](https://blog.csdn.net/qq_33698226/article/details/129031241)); в macOS Clang уже установлен по умолчанию. +2. В магазине расширений VS Code найдите `c++` и установите C/C++ Extension Pack. +3. (Необязательно) Откройте страницу Settings, найдите параметр форматирования `Clang_format_fallback Style` и задайте значение `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`. + +### Среда Java + +1. Загрузите и установите [OpenJDK](https://jdk.java.net/18/) (требуемая версия: > JDK 9). +2. В магазине расширений VS Code найдите `java` и установите Extension Pack for Java. + +### Среда C# + +1. Загрузите и установите [.Net 8.0](https://dotnet.microsoft.com/en-us/download). +2. В магазине расширений VS Code найдите `C# Dev Kit` и установите C# Dev Kit ([руководство по настройке](https://code.visualstudio.com/docs/csharp/get-started)). +3. Также можно использовать Visual Studio ([руководство по установке](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022)). + +### Среда Go + +1. Загрузите и установите [go](https://go.dev/dl/). +2. В магазине расширений VS Code найдите `go` и установите Go. +3. Нажмите `Ctrl + Shift + P`, чтобы открыть командную палитру, введите `go`, выберите `Go: Install/Update Tools`, отметьте все инструменты и установите их. + +### Среда Swift + +1. Загрузите и установите [Swift](https://www.swift.org/download/). +2. В магазине расширений VS Code найдите `swift` и установите [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang). + +### Среда JavaScript + +1. Загрузите и установите [Node.js](https://nodejs.org/en/). +2. (Необязательно) В магазине расширений VS Code найдите `Prettier` и установите инструмент форматирования кода. + +### Среда TypeScript + +1. Выполните те же шаги, что и для среды JavaScript. +2. Установите [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation). +3. В магазине расширений VS Code найдите `typescript` и установите [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors). + +### Среда Dart + +1. Загрузите и установите [Dart](https://dart.dev/get-dart). +2. В магазине расширений VS Code найдите `dart` и установите [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code). + +### Среда Rust + +1. Загрузите и установите [Rust](https://www.rust-lang.org/tools/install). +2. В магазине расширений VS Code найдите `rust` и установите [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). diff --git a/ru/docs/chapter_appendix/terminology.md b/ru/docs/chapter_appendix/terminology.md new file mode 100644 index 000000000..27c0b5b25 --- /dev/null +++ b/ru/docs/chapter_appendix/terminology.md @@ -0,0 +1,137 @@ +# Глоссарий + +В таблице ниже перечислены важные термины, встречающиеся в книге. Обратите внимание на следующие моменты. + +- Рекомендуем запомнить английские названия терминов, чтобы легче читать англоязычную литературу. +- В русской версии третий столбец дублирует основной перевод, чтобы сохранить единый формат таблицы. + +

Таблица   Важные термины по структурам данных и алгоритмам

+ +| English | Русский | Русский | +| ------------------------------ | ------------------------------ | ------------------------------ | +| algorithm | алгоритм | алгоритм | +| data structure | структура данных | структура данных | +| code | код | код | +| file | файл | файл | +| function | функция | функция | +| method | метод | метод | +| variable | переменная | переменная | +| asymptotic complexity analysis | асимптотический анализ сложности | асимптотический анализ сложности | +| time complexity | временная сложность | временная сложность | +| space complexity | пространственная сложность | пространственная сложность | +| loop | цикл | цикл | +| iteration | итерация | итерация | +| recursion | рекурсия | рекурсия | +| tail recursion | хвостовая рекурсия | хвостовая рекурсия | +| recursion tree | дерево рекурсии | дерево рекурсии | +| big-$O$ notation | нотация big-$O$ | нотация big-$O$ | +| asymptotic upper bound | асимптотическая верхняя граница | асимптотическая верхняя граница | +| sign-magnitude | прямой код | прямой код | +| 1’s complement | обратный код | обратный код | +| 2’s complement | дополнительный код | дополнительный код | +| array | массив | массив | +| index | индекс | индекс | +| linked list | связный список | связный список | +| linked list node, list node | узел связного списка | узел связного списка | +| head node | головной узел | головной узел | +| tail node | хвостовой узел | хвостовой узел | +| list | список | список | +| dynamic array | динамический массив | динамический массив | +| hard disk | жесткий диск | жесткий диск | +| random-access memory (RAM) | оперативная память | оперативная память | +| cache memory | кеш-память | кеш-память | +| cache miss | промах кеша | промах кеша | +| cache hit rate | коэффициент попадания в кеш | коэффициент попадания в кеш | +| stack | стек | стек | +| top of the stack | вершина стека | вершина стека | +| bottom of the stack | основание стека | основание стека | +| queue | очередь | очередь | +| double-ended queue | двусторонняя очередь | двусторонняя очередь | +| front of the queue | голова очереди | голова очереди | +| rear of the queue | хвост очереди | хвост очереди | +| hash table | хеш-таблица | хеш-таблица | +| hash set | хеш-набор | хеш-набор | +| bucket | корзина | корзина | +| hash function | хеш-функция | хеш-функция | +| hash collision | хеш-коллизия | хеш-коллизия | +| load factor | коэффициент заполнения | коэффициент заполнения | +| separate chaining | цепная адресация | цепная адресация | +| open addressing | открытая адресация | открытая адресация | +| linear probing | линейное зондирование | линейное зондирование | +| lazy deletion | ленивое удаление | ленивое удаление | +| binary tree | двоичное дерево | двоичное дерево | +| tree node | узел дерева | узел дерева | +| left-child node | левый дочерний узел | левый дочерний узел | +| right-child node | правый дочерний узел | правый дочерний узел | +| parent node | родительский узел | родительский узел | +| left subtree | левое поддерево | левое поддерево | +| right subtree | правое поддерево | правое поддерево | +| root node | корневой узел | корневой узел | +| leaf node | листовой узел | листовой узел | +| edge | ребро | ребро | +| level | уровень | уровень | +| degree | степень | степень | +| height | высота | высота | +| depth | глубина | глубина | +| perfect binary tree | идеальное двоичное дерево | идеальное двоичное дерево | +| complete binary tree | совершенное двоичное дерево | совершенное двоичное дерево | +| full binary tree | полное двоичное дерево | полное двоичное дерево | +| balanced binary tree | сбалансированное двоичное дерево | сбалансированное двоичное дерево | +| binary search tree | двоичное дерево поиска | двоичное дерево поиска | +| AVL tree | АВЛ-дерево | АВЛ-дерево | +| red-black tree | красно-черное дерево | красно-черное дерево | +| level-order traversal | обход по уровням | обход по уровням | +| breadth-first traversal | обход в ширину | обход в ширину | +| depth-first traversal | обход в глубину | обход в глубину | +| binary search tree | двоичное дерево поиска | двоичное дерево поиска | +| balanced binary search tree | сбалансированное двоичное дерево поиска | сбалансированное двоичное дерево поиска | +| balance factor | фактор баланса | фактор баланса | +| heap | куча | куча | +| max heap | максимальная куча | максимальная куча | +| min heap | минимальная куча | минимальная куча | +| priority queue | приоритетная очередь | приоритетная очередь | +| heapify | упорядочивание кучи | упорядочивание кучи | +| top-$k$ problem | поиск $k$ наибольших элементов | поиск $k$ наибольших элементов | +| graph | граф | граф | +| vertex | вершина | вершина | +| undirected graph | неориентированный граф | неориентированный граф | +| directed graph | ориентированный граф | ориентированный граф | +| connected graph | связный граф | связный граф | +| disconnected graph | несвязный граф | несвязный граф | +| weighted graph | взвешенный граф | взвешенный граф | +| adjacency | смежность | смежность | +| path | путь | путь | +| in-degree | входящая степень | входящая степень | +| out-degree | исходящая степень | исходящая степень | +| adjacency matrix | матрица смежности | матрица смежности | +| adjacency list | список смежности | список смежности | +| breadth-first search | поиск в ширину | поиск в ширину | +| depth-first search | поиск в глубину | поиск в глубину | +| binary search | двоичный поиск | двоичный поиск | +| searching algorithm | алгоритм поиска | алгоритм поиска | +| sorting algorithm | алгоритм сортировки | алгоритм сортировки | +| selection sort | сортировка выбором | сортировка выбором | +| bubble sort | сортировка пузырьком | сортировка пузырьком | +| insertion sort | сортировка вставкой | сортировка вставкой | +| quick sort | быстрая сортировка | быстрая сортировка | +| merge sort | сортировка слиянием | сортировка слиянием | +| heap sort | пирамидальная сортировка | пирамидальная сортировка | +| bucket sort | блочная сортировка | блочная сортировка | +| counting sort | сортировка подсчетом | сортировка подсчетом | +| radix sort | поразрядная сортировка | поразрядная сортировка | +| divide and conquer | разделяй и властвуй | разделяй и властвуй | +| hanota problem | задача о Ханойской башне | задача о Ханойской башне | +| backtracking algorithm | алгоритм поиска с возвратом | алгоритм поиска с возвратом | +| constraint | ограничение | ограничение | +| solution | решение | решение | +| state | состояние | состояние | +| pruning | отсечение | отсечение | +| permutations problem | задача о перестановках | задача о перестановках | +| subset-sum problem | задача о сумме подмножеств | задача о сумме подмножеств | +| $n$-queens problem | задача о $n$ ферзях | задача о $n$ ферзях | +| dynamic programming | динамическое программирование | динамическое программирование | +| initial state | начальное состояние | начальное состояние | +| state-transition equation | уравнение перехода состояния | уравнение перехода состояния | +| knapsack problem | задача о рюкзаке | задача о рюкзаке | +| edit distance problem | задача о расстоянии редактирования | задача о расстоянии редактирования | +| greedy algorithm | жадный алгоритм | жадный алгоритм | diff --git a/ru/docs/chapter_array_and_linkedlist/array.assets/array_definition.png b/ru/docs/chapter_array_and_linkedlist/array.assets/array_definition.png new file mode 100644 index 000000000..49ba4d106 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/array.assets/array_definition.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png b/ru/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png new file mode 100644 index 000000000..1c3a3d5cd Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png b/ru/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png new file mode 100644 index 000000000..65ebe5ed1 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png b/ru/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png new file mode 100644 index 000000000..0337f5193 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/array.md b/ru/docs/chapter_array_and_linkedlist/array.md new file mode 100644 index 000000000..2f2de7256 --- /dev/null +++ b/ru/docs/chapter_array_and_linkedlist/array.md @@ -0,0 +1,227 @@ +# Массив + +Массив (array) - это линейная структура данных, которая хранит элементы одного типа в непрерывной области памяти. Положение элемента в массиве называется его индексом (index). На рисунке ниже показаны основные понятия, связанные с массивом, и способ его хранения. + +![Определение массива и способ хранения](array.assets/array_definition.png) + +## Основные операции с массивом + +### Инициализация массива + +В зависимости от задачи мы можем выбрать один из двух способов инициализации массива: без начальных значений или с заданными начальными значениями. Если начальные значения не указаны, большинство языков программирования инициализируют элементы массива значением $0$ : + +=== "Python" + + ```python title="array.py" + # Инициализация массива + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* Инициализация массива */ + // Хранится в стеке + int arr[5]; + int nums[5] = { 1, 3, 2, 5, 4 }; + // Хранится в куче (требуется ручное освобождение памяти) + int* arr1 = new int[5]; + int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="array.java" + /* Инициализация массива */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "C#" + + ```csharp title="array.cs" + /* Инициализация массива */ + int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] + int[] nums = [1, 3, 2, 5, 4]; + ``` + +=== "Go" + + ```go title="array.go" + /* Инициализация массива */ + var arr [5]int + // В Go указание длины ([5]int) создает массив, а отсутствие длины ([]int) - срез + // Поскольку длина массива в Go определяется на этапе компиляции, для задания длины можно использовать только константы + // Чтобы упростить реализацию метода extend(), ниже будем рассматривать срезы (Slice) как массивы (Array) + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="array.swift" + /* Инициализация массива */ + let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] + let nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="array.js" + /* Инициализация массива */ + var arr = new Array(5).fill(0); + var nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="array.ts" + /* Инициализация массива */ + let arr: number[] = new Array(5).fill(0); + let nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="array.dart" + /* Инициализация массива */ + List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="array.rs" + /* Инициализация массива */ + let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] + let slice: &[i32] = &[0; 5]; + // В Rust указание длины ([i32; 5]) создает массив, а отсутствие длины (&[i32]) - срез + // Поскольку длина массива в Rust определяется на этапе компиляции, для задания длины можно использовать только константы + // Vector в Rust обычно используется как динамический массив + // Чтобы упростить реализацию метода extend(), ниже будем рассматривать vector как массив (array) + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="array.c" + /* Инициализация массива */ + int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } + int nums[5] = { 1, 3, 2, 5, 4 }; + ``` + +=== "Kotlin" + + ```kotlin title="array.kt" + /* Инициализация массива */ + var arr = IntArray(5) // { 0, 0, 0, 0, 0 } + var nums = intArrayOf(1, 3, 2, 5, 4) + ``` + +=== "Ruby" + + ```ruby title="array.rb" + # Инициализация массива + arr = Array.new(5, 0) + nums = [1, 3, 2, 5, 4] + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%0Aarr%20%3D%20%5B0%5D%20%2A%205%20%20%23%20%5B%200%2C%200%2C%200%2C%200%2C%200%20%5D%0Anums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Доступ к элементам + +Элементы массива хранятся в непрерывной области памяти, а это означает, что вычислить адрес любого элемента очень просто. Зная адрес массива в памяти (то есть адрес первого элемента) и индекс некоторого элемента, мы можем по формуле с рисунка ниже вычислить адрес этого элемента и напрямую обратиться к нему. + +![Вычисление адреса элемента массива](array.assets/array_memory_location_calculation.png) + +Если посмотреть на рисунок выше, можно заметить, что индекс первого элемента массива равен $0$ , и это кажется не слишком интуитивным, ведь естественнее было бы начинать счет с $1$ . Однако с точки зрения формулы адресации **индекс по сути является смещением относительно адреса памяти**. Смещение первого элемента равно $0$ , поэтому индекс $0$ вполне логичен. + +Доступ к элементам массива очень эффективен: любой элемент массива можно получить за $O(1)$ времени. + +```src +[file]{array}-[class]{}-[func]{random_access} +``` + +### Вставка элемента + +Элементы массива в памяти расположены "вплотную" друг к другу, и между ними нет места для размещения новых данных. Как показано на рисунке ниже, если мы хотим вставить элемент в середину массива, то все элементы после этой позиции нужно сдвинуть на одну позицию вправо, а затем записать новое значение в освободившийся индекс. + +![Пример вставки элемента в массив](array.assets/array_insert_element.png) + +Стоит отметить, что длина массива фиксирована, поэтому вставка нового элемента неизбежно приведет к "потере" элемента на конце массива. Решение этой проблемы мы оставим для обсуждения в разделе о "списках". + +```src +[file]{array}-[class]{}-[func]{insert} +``` + +### Удаление элемента + +Аналогично, как показано на рисунке ниже, если нужно удалить элемент по индексу $i$ , то все элементы после индекса $i$ необходимо сдвинуть на одну позицию влево. + +![Пример удаления элемента из массива](array.assets/array_remove_element.png) + +Обрати внимание: после удаления исходный последний элемент становится "бессмысленным", поэтому специально изменять его не требуется. + +```src +[file]{array}-[class]{}-[func]{remove} +``` + +В целом операции вставки и удаления в массиве имеют следующие недостатки. + +- **Высокая временная сложность**: средняя временная сложность и вставки, и удаления равна $O(n)$ , где $n$ - длина массива. +- **Потеря элементов**: поскольку длина массива неизменяема, после вставки элементы, выходящие за пределы длины массива, будут потеряны. +- **Потери памяти**: можно заранее инициализировать более длинный массив и использовать только его переднюю часть; тогда "теряемые" при вставке элементы на конце не будут нести смысла, но такой подход приводит к лишнему расходу памяти. + +### Обход массива + +В большинстве языков программирования массив можно обходить как по индексу, так и напрямую перебирая каждый элемент: + +```src +[file]{array}-[class]{}-[func]{traverse} +``` + +### Поиск элемента + +Чтобы найти заданный элемент в массиве, нужно пройти по массиву и на каждой итерации проверять, совпадает ли значение; если совпадает, вернуть соответствующий индекс. + +Поскольку массив - это линейная структура данных, такая операция поиска называется "линейным поиском". + +```src +[file]{array}-[class]{}-[func]{find} +``` + +### Расширение массива + +В сложной системной среде программа не может гарантировать, что память сразу после массива доступна, поэтому безопасно расширить емкость массива невозможно. Поэтому в большинстве языков программирования **длина массива неизменяема**. + +Если мы хотим расширить массив, нужно заново создать больший массив и затем по одному скопировать в него элементы исходного массива. Это операция с временной сложностью $O(n)$ , и при больших массивах она очень затратна. Соответствующий код показан ниже: + +```src +[file]{array}-[class]{}-[func]{extend} +``` + +## Преимущества и ограничения массива + +Массив хранится в непрерывной области памяти, и все его элементы имеют один и тот же тип. Такой подход содержит много априорной информации, которую система может использовать для оптимизации эффективности операций со структурой данных. + +- **Высокая пространственная эффективность**: массив выделяет для данных непрерывный блок памяти без дополнительного структурного накладного расхода. +- **Поддержка произвольного доступа**: массив позволяет обращаться к любому элементу за $O(1)$ времени. +- **Локальность кэша**: при обращении к элементу массива компьютер загружает не только сам элемент, но и соседние данные, что позволяет использовать кэш для ускорения последующих операций. + +Хранение в непрерывной области памяти - палка о двух концах, и у него есть следующие ограничения. + +- **Низкая эффективность вставки и удаления**: когда элементов в массиве много, вставка и удаление требуют сдвига большого количества элементов. +- **Неизменяемая длина**: после инициализации длина массива фиксирована; расширение массива требует копирования всех данных в новый массив, что стоит дорого. +- **Потери памяти**: если выделенный массив больше, чем реально необходимо, лишнее пространство пропадает впустую. + +## Типичные применения массива + +Массив - это базовая и очень распространенная структура данных. Он часто используется как в различных алгоритмах, так и при реализации более сложных структур данных. + +- **Произвольный доступ**: если мы хотим случайным образом выбирать некоторые образцы, можно сохранить их в массиве и сгенерировать случайную последовательность индексов для выборки. +- **Сортировка и поиск**: массив - самая распространенная структура данных для алгоритмов сортировки и поиска. Быстрая сортировка, сортировка слиянием, бинарный поиск и многие другие алгоритмы в основном работают именно с массивами. +- **Таблица поиска**: когда нужно быстро находить элемент или его соответствие, массив можно использовать как lookup table. Например, если мы хотим реализовать отображение символов в коды ASCII, можно использовать значение ASCII как индекс, а соответствующий элемент хранить по этой позиции массива. +- **Машинное обучение**: в нейронных сетях широко используются операции линейной алгебры над векторами, матрицами и тензорами, и все эти данные строятся в форме массивов. Массив - самая часто используемая структура данных в программировании нейросетей. +- **Реализация структур данных**: массивы можно использовать для реализации стеков, очередей, хеш-таблиц, куч, графов и других структур данных. Например, матрица смежности графа по сути является двумерным массивом. diff --git a/ru/docs/chapter_array_and_linkedlist/index.md b/ru/docs/chapter_array_and_linkedlist/index.md new file mode 100644 index 000000000..8f05539db --- /dev/null +++ b/ru/docs/chapter_array_and_linkedlist/index.md @@ -0,0 +1,9 @@ +# Массивы и списки + +![Массивы и списки](../assets/covers/chapter_array_and_linkedlist.jpg) + +!!! abstract + + Мир структур данных напоминает прочную кирпичную стену. + + Кирпичи массива уложены ровно и плотно прилегают друг к другу. Кирпичи связного списка разбросаны в разных местах, а соединяющие их лозы свободно тянутся между щелями. diff --git a/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png new file mode 100644 index 000000000..efeeeb201 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png new file mode 100644 index 000000000..64cbb7fd8 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png new file mode 100644 index 000000000..1498a4f40 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png new file mode 100644 index 000000000..c2dd4aeac Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/linked_list.md b/ru/docs/chapter_array_and_linkedlist/linked_list.md new file mode 100644 index 000000000..80cc0f6c9 --- /dev/null +++ b/ru/docs/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,702 @@ +# Связный список + +Память является общим ресурсом для всех программ, и в сложной среде выполнения свободные участки памяти могут быть разбросаны по всему адресу памяти. Мы знаем, что память для хранения массива должна быть непрерывной, а если массив очень велик, память может не суметь предоставить столь большой непрерывный блок. Именно здесь проявляется преимущество гибкости связного списка. + +Связный список (linked list) - это линейная структура данных, в которой каждый элемент представляет собой объект-узел, а сами узлы соединены между собой через "ссылки". Ссылка хранит адрес памяти следующего узла, благодаря чему из текущего узла можно получить доступ к следующему. + +Конструкция связного списка позволяет хранить отдельные узлы в разных местах памяти, и их адреса вовсе не обязаны быть непрерывными. + +![Определение связного списка и способ хранения](linked_list.assets/linkedlist_definition.png) + +Если посмотреть на рисунок выше, можно заметить, что базовой единицей связного списка является объект узел (node). Каждый узел содержит две части данных: "значение" узла и "ссылку" на следующий узел. + +- Первый узел связного списка называется "головным узлом", а последний - "хвостовым узлом". +- Хвостовой узел указывает на "пусто", что в Java, C++ и Python обозначается как `null` , `nullptr` и `None` соответственно. +- В языках, поддерживающих указатели, таких как C, C++, Go и Rust, упомянутую выше "ссылку" следует заменить на "указатель". + +Как показано в коде ниже, узел связного списка `ListNode` хранит не только значение, но и дополнительную ссылку (указатель). Поэтому **при одинаковом объеме данных связный список занимает больше памяти, чем массив**. + +=== "Python" + + ```python title="" + class ListNode: + """Класс узла связного списка""" + def __init__(self, val: int): + self.val: int = val # Значение узла + self.next: ListNode | None = None # Ссылка на следующий узел + ``` + +=== "C++" + + ```cpp title="" + /* Структура узла связного списка */ + struct ListNode { + int val; // Значение узла + ListNode *next; // Указатель на следующий узел + ListNode(int x) : val(x), next(nullptr) {} // Конструктор + }; + ``` + +=== "Java" + + ```java title="" + /* Класс узла связного списка */ + class ListNode { + int val; // Значение узла + ListNode next; // Ссылка на следующий узел + ListNode(int x) { val = x; } // Конструктор + } + ``` + +=== "C#" + + ```csharp title="" + /* Класс узла связного списка */ + class ListNode(int x) { // Конструктор + int val = x; // Значение узла + ListNode? next; // Ссылка на следующий узел + } + ``` + +=== "Go" + + ```go title="" + /* Структура узла связного списка */ + type ListNode struct { + Val int // Значение узла + Next *ListNode // Указатель на следующий узел + } + + // NewListNode Конструктор, создает новый связный список + func NewListNode(val int) *ListNode { + return &ListNode{ + Val: val, + Next: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Класс узла связного списка */ + class ListNode { + var val: Int // Значение узла + var next: ListNode? // Ссылка на следующий узел + + init(x: Int) { // Конструктор + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Класс узла связного списка */ + class ListNode { + constructor(val, next) { + this.val = (val === undefined ? 0 : val); // Значение узла + this.next = (next === undefined ? null : next); // Ссылка на следующий узел + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Класс узла связного списка */ + class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; // Значение узла + this.next = next === undefined ? null : next; // Ссылка на следующий узел + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Класс узла связного списка */ + class ListNode { + int val; // Значение узла + ListNode? next; // Ссылка на следующий узел + ListNode(this.val, [this.next]); // Конструктор + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* Класс узла связного списка */ + #[derive(Debug)] + struct ListNode { + val: i32, // Значение узла + next: Option>>, // Указатель на следующий узел + } + ``` + +=== "C" + + ```c title="" + /* Структура узла связного списка */ + typedef struct ListNode { + int val; // Значение узла + struct ListNode *next; // Указатель на следующий узел + } ListNode; + + /* Конструктор */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Класс узла связного списка */ + // Конструктор + class ListNode(x: Int) { + val _val: Int = x // Значение узла + val next: ListNode? = null // Ссылка на следующий узел + } + ``` + +=== "Ruby" + + ```ruby title="" + # Класс узла связного списка + class ListNode + attr_accessor :val # Значение узла + attr_accessor :next # Ссылка на следующий узел + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end + end + ``` + +## Основные операции со связным списком + +### Инициализация связного списка + +Построение связного списка состоит из двух шагов: сначала нужно инициализировать объекты всех узлов, затем установить связи-ссылки между ними. После завершения инициализации мы можем, начиная с головы списка, последовательно проходить все узлы по ссылке `next`. + +=== "Python" + + ```python title="linked_list.py" + # Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 + # Инициализация отдельных узлов + n0 = ListNode(1) + n1 = ListNode(3) + n2 = ListNode(2) + n3 = ListNode(5) + n4 = ListNode(4) + # Построение ссылок между узлами + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "C++" + + ```cpp title="linked_list.cpp" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + ListNode* n0 = new ListNode(1); + ListNode* n1 = new ListNode(3); + ListNode* n2 = new ListNode(2); + ListNode* n3 = new ListNode(5); + ListNode* n4 = new ListNode(4); + // Построение ссылок между узлами + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Java" + + ```java title="linked_list.java" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // Построение ссылок между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // Построение ссылок между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Go" + + ```go title="linked_list.go" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + // Построение ссылок между узлами + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + ``` + +=== "Swift" + + ```swift title="linked_list.swift" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // Построение ссылок между узлами + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // Построение ссылок между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "TS" + + ```typescript title="linked_list.ts" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // Построение ссылок между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Dart" + + ```dart title="linked_list.dart" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */\ + // Инициализация отдельных узлов + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // Построение ссылок между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); + let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); + let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); + let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); + let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); + + // Построение ссылок между узлами + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + ``` + +=== "C" + + ```c title="linked_list.c" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + ListNode* n0 = newListNode(1); + ListNode* n1 = newListNode(3); + ListNode* n2 = newListNode(2); + ListNode* n3 = newListNode(5); + ListNode* n4 = newListNode(4); + // Построение ссылок между узлами + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Kotlin" + + ```kotlin title="linked_list.kt" + /* Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 */ + // Инициализация отдельных узлов + val n0 = ListNode(1) + val n1 = ListNode(3) + val n2 = ListNode(2) + val n3 = ListNode(5) + val n4 = ListNode(4) + // Построение ссылок между узлами + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Ruby" + + ```ruby title="linked_list.rb" + # Инициализация связного списка 1 -> 3 -> 2 -> 5 -> 4 + # Инициализация отдельных узлов + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # Построение ссылок между узлами + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%83%D0%B7%D0%B5%D0%BB%D0%BA%D0%BB%D0%B0%D1%81%D1%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%B0%D0%B6%D0%B4%D1%8B%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +Массив в целом - это одна переменная: например, массив `nums` содержит элементы `nums[0]` , `nums[1]` и т.д. Связный список же состоит из множества независимых объектов-узлов. **Обычно в качестве обозначения всего связного списка используют головной узел**; например, в приведенном выше коде связный список можно обозначить как список `n0` . + +### Вставка узла + +Вставить узел в связный список очень легко. Как показано на рисунке ниже, предположим, что мы хотим вставить новый узел `P` между двумя соседними узлами `n0` и `n1` ; **для этого нужно изменить всего две ссылки (указателя)**, а временная сложность будет равна $O(1)$ . + +Для сравнения: временная сложность вставки элемента в массив составляет $O(n)$ , и при большом объеме данных это неэффективно. + +![Пример вставки узла в связный список](linked_list.assets/linkedlist_insert_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{insert} +``` + +### Удаление узла + +Как показано на рисунке ниже, удалить узел из связного списка тоже очень удобно: **нужно изменить всего одну ссылку (указатель)**. + +Обрати внимание: хотя после завершения операции удаления узел `P` все еще указывает на `n1` , при обходе связного списка до `P` уже нельзя добраться, а значит `P` больше не принадлежит данному списку. + +![Удаление узла из связного списка](linked_list.assets/linkedlist_remove_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{remove} +``` + +### Доступ к узлу + +**Доступ к узлам в связном списке менее эффективен**. Как уже обсуждалось в предыдущем разделе, к любому элементу массива можно обратиться за $O(1)$ времени. Со связным списком это не так: программе нужно стартовать от головного узла и последовательно двигаться дальше, пока не будет найден целевой узел. То есть для доступа к $i$ -му узлу списка нужно выполнить $i - 1$ итераций, а временная сложность составляет $O(n)$ . + +```src +[file]{linked_list}-[class]{}-[func]{access} +``` + +### Поиск узла + +Выполни обход связного списка, найди в нем узел со значением `target` и верни индекс этого узла в списке. Этот процесс тоже относится к линейному поиску. Код выглядит следующим образом: + +```src +[file]{linked_list}-[class]{}-[func]{find} +``` + +## Сравнение массива и связного списка + +В таблице ниже обобщаются свойства массива и связного списка, а также сравнивается эффективность соответствующих операций. Поскольку они используют противоположные стратегии хранения, их свойства и эффективность операций тоже во многом противоположны. + +

Таблица   Сравнение эффективности массива и связного списка

+ +| | Массив | Связный список | +| ------------- | ---------------------------------------------- | ----------------------- | +| Способ хранения | Непрерывная область памяти | Разрозненная область памяти | +| Расширение емкости | Длина неизменяема | Гибкое расширение | +| Эффективность памяти | Элементы занимают меньше памяти, но возможны потери пространства | Элементы занимают больше памяти | +| Доступ к элементу | $O(1)$ | $O(n)$ | +| Добавление элемента | $O(n)$ | $O(1)$ | +| Удаление элемента | $O(n)$ | $O(1)$ | + +## Основные типы связных списков + +Как показано на рисунке ниже, существует три распространенных типа связных списков. + +- **Односвязный список**: это обычный связный список, рассмотренный выше. Узел односвязного списка содержит значение и ссылку на следующий узел. Первый узел называется головным, последний - хвостовым, и хвост указывает на пусто `None` . +- **Циклический список**: если заставить хвостовой узел односвязного списка указывать на головной (то есть соединить хвост с головой), получится циклический список. В циклическом списке любой узел можно рассматривать как головной. +- **Двусвязный список**: по сравнению с односвязным списком двусвязный хранит ссылки в двух направлениях. Определение узла двусвязного списка включает как ссылку на следующий узел, так и ссылку на предыдущий узел. По сравнению с односвязным списком двусвязный более гибок и позволяет проходить список в обе стороны, но за это приходится платить дополнительной памятью. + +=== "Python" + + ```python title="" + class ListNode: + """Класс узла двусвязного списка""" + def __init__(self, val: int): + self.val: int = val # Значение узла + self.next: ListNode | None = None # Ссылка на следующий узел + self.prev: ListNode | None = None # Ссылка на предыдущий узел + ``` + +=== "C++" + + ```cpp title="" + /* Структура узла двусвязного списка */ + struct ListNode { + int val; // Значение узла + ListNode *next; // Указатель на следующий узел + ListNode *prev; // Указатель на предыдущий узел + ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // Конструктор + }; + ``` + +=== "Java" + + ```java title="" + /* Класс узла двусвязного списка */ + class ListNode { + int val; // Значение узла + ListNode next; // Ссылка на следующий узел + ListNode prev; // Ссылка на предыдущий узел + ListNode(int x) { val = x; } // Конструктор + } + ``` + +=== "C#" + + ```csharp title="" + /* Класс узла двусвязного списка */ + class ListNode(int x) { // Конструктор + int val = x; // Значение узла + ListNode next; // Ссылка на следующий узел + ListNode prev; // Ссылка на предыдущий узел + } + ``` + +=== "Go" + + ```go title="" + /* Структура узла двусвязного списка */ + type DoublyListNode struct { + Val int // Значение узла + Next *DoublyListNode // Указатель на следующий узел + Prev *DoublyListNode // Указатель на предыдущий узел + } + + // NewDoublyListNode Инициализация + func NewDoublyListNode(val int) *DoublyListNode { + return &DoublyListNode{ + Val: val, + Next: nil, + Prev: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Класс узла двусвязного списка */ + class ListNode { + var val: Int // Значение узла + var next: ListNode? // Ссылка на следующий узел + var prev: ListNode? // Ссылка на предыдущий узел + + init(x: Int) { // Конструктор + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Класс узла двусвязного списка */ + class ListNode { + constructor(val, next, prev) { + this.val = val === undefined ? 0 : val; // Значение узла + this.next = next === undefined ? null : next; // Ссылка на следующий узел + this.prev = prev === undefined ? null : prev; // Ссылка на предыдущий узел + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Класс узла двусвязного списка */ + class ListNode { + val: number; + next: ListNode | null; + prev: ListNode | null; + constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { + this.val = val === undefined ? 0 : val; // Значение узла + this.next = next === undefined ? null : next; // Ссылка на следующий узел + this.prev = prev === undefined ? null : prev; // Ссылка на предыдущий узел + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Класс узла двусвязного списка */ + class ListNode { + int val; // Значение узла + ListNode? next; // Ссылка на следующий узел + ListNode? prev; // Ссылка на предыдущий узел + ListNode(this.val, [this.next, this.prev]); // Конструктор + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Тип узла двусвязного списка */ + #[derive(Debug)] + struct ListNode { + val: i32, // Значение узла + next: Option>>, // Указатель на следующий узел + prev: Option>>, // Указатель на предыдущий узел + } + + /* Конструктор */ + impl ListNode { + fn new(val: i32) -> Self { + ListNode { + val, + next: None, + prev: None, + } + } + } + ``` + +=== "C" + + ```c title="" + /* Структура узла двусвязного списка */ + typedef struct ListNode { + int val; // Значение узла + struct ListNode *next; // Указатель на следующий узел + struct ListNode *prev; // Указатель на предыдущий узел + } ListNode; + + /* Конструктор */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + node->prev = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Класс узла двусвязного списка */ + // Конструктор + class ListNode(x: Int) { + val _val: Int = x // Значение узла + val next: ListNode? = null // Ссылка на следующий узел + val prev: ListNode? = null // Ссылка на предыдущий узел + } + ``` + +=== "Ruby" + + ```ruby title="" + # Класс узла двусвязного списка + class ListNode + attr_accessor :val # Значение узла + attr_accessor :next # Ссылка на следующий узел + attr_accessor :prev # Ссылка на предыдущий узел + + def initialize(val=0, next_node=nil, prev_node=nil) + @val = val + @next = next_node + @prev = prev_node + end + end + ``` + +![Распространенные типы связных списков](linked_list.assets/linkedlist_common_types.png) + +## Типичные применения связных списков + +Односвязные списки обычно используются для реализации стеков, очередей, хеш-таблиц и графов. + +- **Стеки и очереди**: если операции вставки и удаления выполняются на одном конце связного списка, он проявляет свойства LIFO, соответствующие стеку; если вставка происходит на одном конце, а удаление на другом, он проявляет свойства FIFO, соответствующие очереди. +- **Хеш-таблицы**: метод цепочек - один из основных способов разрешения коллизий в хеш-таблицах. В этом подходе все конфликтующие элементы помещаются в связный список. +- **Графы**: список смежности - это распространенный способ представления графа, при котором каждой вершине графа соответствует связный список, а каждый элемент этого списка представляет другую вершину, соединенную с данной. + +Двусвязные списки обычно используются там, где нужен быстрый доступ как к предыдущему, так и к следующему элементу. + +- **Продвинутые структуры данных**: например, в красно-черных деревьях и B-деревьях нам нужен доступ к родительскому узлу; этого можно добиться, сохранив в узле ссылку на родителя, по аналогии с двусвязным списком. +- **История браузера**: когда пользователь в браузере нажимает кнопки "вперед" или "назад", браузеру нужно знать предыдущую и следующую веб-страницы, которые он посещал. Свойства двусвязного списка делают такую операцию простой. +- **Алгоритм LRU**: в алгоритмах вытеснения из кэша (LRU) нужно быстро находить наименее недавно использованные данные, а также быстро добавлять и удалять узлы. Для этого двусвязный список подходит очень хорошо. + +Циклические списки часто применяются в сценариях, требующих периодических операций, например при планировании ресурсов в операционной системе. + +- **Алгоритм циклического распределения кванта времени**: в операционных системах round-robin scheduling - это распространенный алгоритм планирования CPU, который циклически обходит набор процессов. Каждому процессу выделяется квант времени, и когда он исчерпан, CPU переключается на следующий процесс. Такую циклическую операцию удобно реализовать с помощью кольцевого списка. +- **Буферы данных**: в некоторых реализациях буферов данных также могут использоваться циклические списки. Например, в аудио- и видеоплеерах поток данных может делиться на несколько буферных блоков и помещаться в кольцевой список для обеспечения непрерывного воспроизведения. diff --git a/ru/docs/chapter_array_and_linkedlist/list.md b/ru/docs/chapter_array_and_linkedlist/list.md new file mode 100644 index 000000000..dc441b75d --- /dev/null +++ b/ru/docs/chapter_array_and_linkedlist/list.md @@ -0,0 +1,961 @@ +# Список + +Список (list) - это абстрактное понятие структуры данных, обозначающее упорядоченную коллекцию элементов, которая поддерживает доступ к элементам, их изменение, добавление, удаление и обход, не требуя от пользователя учитывать ограничения по емкости. Список может быть реализован как на основе связного списка, так и на основе массива. + +- Связный список естественным образом можно рассматривать как список: он поддерживает операции добавления, удаления, поиска и изменения элементов и может гибко расширяться динамически. +- Массив тоже поддерживает операции добавления, удаления, поиска и изменения элементов, но из-за неизменяемости длины его можно считать лишь списком с ограниченной длиной. + +Когда список реализуется с помощью массива, **неизменяемость длины снижает его практическую полезность**. Причина в том, что мы обычно не можем заранее точно знать, сколько данных нужно хранить, а значит, трудно выбрать подходящую длину списка. Если длина слишком мала, она может не покрыть реальные потребности; если слишком велика, будет зря расходоваться память. + +Чтобы решить эту проблему, можно использовать динамический массив (dynamic array) для реализации списка. Он сохраняет все преимущества массива и при этом может динамически расширяться во время выполнения программы. + +На практике **списки из стандартных библиотек многих языков программирования реализованы именно на основе динамических массивов**, например `list` в Python, `ArrayList` в Java, `vector` в C++ и `List` в C#. В дальнейшем обсуждении мы будем считать понятия "список" и "динамический массив" эквивалентными. + +## Основные операции со списком + +### Инициализация списка + +Обычно мы используем два способа инициализации: "без начальных значений" и "с начальными значениями": + +=== "Python" + + ```python title="list.py" + # Инициализация списка + # Без начальных значений + nums1: list[int] = [] + # С начальными значениями + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Инициализация списка */ + // Обрати внимание: в C++ vector соответствует описываемому здесь nums + // Без начальных значений + vector nums1; + // С начальными значениями + vector nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="list.java" + /* Инициализация списка */ + // Без начальных значений + List nums1 = new ArrayList<>(); + // С начальными значениями (обрати внимание: элементы массива должны использовать обертку Integer[] вместо int[]) + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Инициализация списка */ + // Без начальных значений + List nums1 = []; + // С начальными значениями + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + ``` + +=== "Go" + + ```go title="list_test.go" + /* Инициализация списка */ + // Без начальных значений + nums1 := []int{} + // С начальными значениями + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Инициализация списка */ + // Без начальных значений + let nums1: [Int] = [] + // С начальными значениями + var nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="list.js" + /* Инициализация списка */ + // Без начальных значений + const nums1 = []; + // С начальными значениями + const nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Инициализация списка */ + // Без начальных значений + const nums1: number[] = []; + // С начальными значениями + const nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Инициализация списка */ + // Без начальных значений + List nums1 = []; + // С начальными значениями + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Инициализация списка */ + // Без начальных значений + let nums1: Vec = Vec::new(); + // С начальными значениями + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="list.c" + // В C нет встроенного динамического массива + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* Инициализация списка */ + // Без начальных значений + var nums1 = listOf() + // С начальными значениями + var numbers = arrayOf(1, 3, 2, 5, 4) + var nums = numbers.toMutableList() + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # Инициализация списка + # Без начальных значений + nums1 = [] + # С начальными значениями + nums = [1, 3, 2, 5, 4] + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20%23%20%D0%91%D0%B5%D0%B7%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D1%85%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%D0%A1%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC%D0%B8%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Доступ к элементам + +Список по своей сути является массивом, поэтому доступ к элементам и их обновление можно выполнять за $O(1)$ времени, что очень эффективно. + +=== "Python" + + ```python title="list.py" + # Доступ к элементу + num: int = nums[1] # Доступ к элементу по индексу 1 + + # Обновление элемента + nums[1] = 0 # Обновить элемент по индексу 1 значением 0 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Доступ к элементу */ + int num = nums[1]; // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0; // Обновить элемент по индексу 1 значением 0 + ``` + +=== "Java" + + ```java title="list.java" + /* Доступ к элементу */ + int num = nums.get(1); // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums.set(1, 0); // Обновить элемент по индексу 1 значением 0 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Доступ к элементу */ + int num = nums[1]; // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0; // Обновить элемент по индексу 1 значением 0 + ``` + +=== "Go" + + ```go title="list_test.go" + /* Доступ к элементу */ + num := nums[1] // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0 // Обновить элемент по индексу 1 значением 0 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Доступ к элементу */ + let num = nums[1] // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0 // Обновить элемент по индексу 1 значением 0 + ``` + +=== "JS" + + ```javascript title="list.js" + /* Доступ к элементу */ + const num = nums[1]; // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0; // Обновить элемент по индексу 1 значением 0 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Доступ к элементу */ + const num: number = nums[1]; // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0; // Обновить элемент по индексу 1 значением 0 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Доступ к элементу */ + int num = nums[1]; // Доступ к элементу по индексу 1 + + /* Обновление элемента */ + nums[1] = 0; // Обновить элемент по индексу 1 значением 0 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Доступ к элементу */ + let num: i32 = nums[1]; // Доступ к элементу по индексу 1 + /* Обновление элемента */ + nums[1] = 0; // Обновить элемент по индексу 1 значением 0 + ``` + +=== "C" + + ```c title="list.c" + // В C нет встроенного динамического массива + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* Доступ к элементу */ + val num = nums[1] // Доступ к элементу по индексу 1 + /* Обновление элемента */ + nums[1] = 0 // Обновить элемент по индексу 1 значением 0 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # Доступ к элементу + num = nums[1] # Доступ к элементу по индексу 1 + # Обновление элемента + nums[1] = 0 # Обновить элемент по индексу 1 значением 0 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C%D1%81%D1%8F%20%D0%BA%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%201%20%D0%BF%D0%BE%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%0A%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%201%20%D0%B4%D0%BE%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Вставка и удаление элементов + +По сравнению с массивами список позволяет свободно добавлять и удалять элементы. Добавление элемента в конец списка имеет временную сложность $O(1)$ , но операции вставки и удаления по-прежнему имеют ту же эффективность, что и у массива, то есть $O(n)$ . + +=== "Python" + + ```python title="list.py" + # Очистить список + nums.clear() + + # Добавить элементы в конец + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + # Вставить элемент в середину + nums.insert(3, 6) # Вставить число 6 по индексу 3 + + # Удалить элемент + nums.pop(3) # Удалить элемент по индексу 3 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Очистить список */ + nums.clear(); + + /* Добавить элементы в конец */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + + /* Вставить элемент в середину */ + nums.insert(nums.begin() + 3, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.erase(nums.begin() + 3); // Удалить элемент по индексу 3 + ``` + +=== "Java" + + ```java title="list.java" + /* Очистить список */ + nums.clear(); + + /* Добавить элементы в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* Вставить элемент в середину */ + nums.add(3, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.remove(3); // Удалить элемент по индексу 3 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Очистить список */ + nums.Clear(); + + /* Добавить элементы в конец */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + + /* Вставить элемент в середину */ + nums.Insert(3, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.RemoveAt(3); // Удалить элемент по индексу 3 + ``` + +=== "Go" + + ```go title="list_test.go" + /* Очистить список */ + nums = nil + + /* Добавить элементы в конец */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + + /* Вставить элемент в середину */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums = append(nums[:3], nums[4:]...) // Удалить элемент по индексу 3 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Очистить список */ + nums.removeAll() + + /* Добавить элементы в конец */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + /* Вставить элемент в середину */ + nums.insert(6, at: 3) // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.remove(at: 3) // Удалить элемент по индексу 3 + ``` + +=== "JS" + + ```javascript title="list.js" + /* Очистить список */ + nums.length = 0; + + /* Добавить элементы в конец */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* Вставить элемент в середину */ + nums.splice(3, 0, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.splice(3, 1); // Удалить элемент по индексу 3 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Очистить список */ + nums.length = 0; + + /* Добавить элементы в конец */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* Вставить элемент в середину */ + nums.splice(3, 0, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.splice(3, 1); // Удалить элемент по индексу 3 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Очистить список */ + nums.clear(); + + /* Добавить элементы в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* Вставить элемент в середину */ + nums.insert(3, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.removeAt(3); // Удалить элемент по индексу 3 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Очистить список */ + nums.clear(); + + /* Добавить элементы в конец */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* Вставить элемент в середину */ + nums.insert(3, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.remove(3); // Удалить элемент по индексу 3 + ``` + +=== "C" + + ```c title="list.c" + // В C нет встроенного динамического массива + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* Очистить список */ + nums.clear(); + + /* Добавить элементы в конец */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* Вставить элемент в середину */ + nums.add(3, 6); // Вставить число 6 по индексу 3 + + /* Удалить элемент */ + nums.remove(3); // Удалить элемент по индексу 3 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # Очистить список + nums.clear + + # Добавить элементы в конец + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + + # Вставить элемент в середину + nums.insert(3, 6) # Вставить число 6 по индексу 3 + + # Удалить элемент + nums.delete_at(3) # Удалить элемент по индексу 3 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%A1%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC%D0%B8%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D1%87%D0%B8%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%81%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%BD%D1%83%0A%20%20%20%20nums.insert%283%2C%206%29%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%206%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%83%203&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Обход списка + +Как и массив, список можно обходить как по индексам, так и напрямую по элементам. + +=== "Python" + + ```python title="list.py" + # Обход списка по индексам + count = 0 + for i in range(len(nums)): + count += nums[i] + + # Прямой обход элементов списка + for num in nums: + count += num + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Обход списка по индексам */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + + /* Прямой обход элементов списка */ + count = 0; + for (int num : nums) { + count += num; + } + ``` + +=== "Java" + + ```java title="list.java" + /* Обход списка по индексам */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + + /* Прямой обход элементов списка */ + for (int num : nums) { + count += num; + } + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Обход списка по индексам */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + + /* Прямой обход элементов списка */ + count = 0; + foreach (int num in nums) { + count += num; + } + ``` + +=== "Go" + + ```go title="list_test.go" + /* Обход списка по индексам */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + + /* Прямой обход элементов списка */ + count = 0 + for _, num := range nums { + count += num + } + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Обход списка по индексам */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + + /* Прямой обход элементов списка */ + count = 0 + for num in nums { + count += num + } + ``` + +=== "JS" + + ```javascript title="list.js" + /* Обход списка по индексам */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* Прямой обход элементов списка */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Обход списка по индексам */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* Прямой обход элементов списка */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Обход списка по индексам */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* Прямой обход элементов списка */ + count = 0; + for (var num in nums) { + count += num; + } + ``` + +=== "Rust" + + ```rust title="list.rs" + // Обход списка по индексам + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + + // Прямой обход элементов списка + _count = 0; + for num in &nums { + _count += num; + } + ``` + +=== "C" + + ```c title="list.c" + // В C нет встроенного динамического массива + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* Обход списка по индексам */ + var count = 0 + for (i in nums.indices) { + count += nums[i] + } + + /* Прямой обход элементов списка */ + for (num in nums) { + count += num + } + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # Обход списка по индексам + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # Прямой обход элементов списка + count = 0 + for num in nums + count += num + end + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D0%BE%20%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D0%BC%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%D0%9D%D0%B5%D0%BF%D0%BE%D1%81%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Конкатенация списков + +Если дан новый список `nums1` , мы можем присоединить его к хвосту исходного списка. + +=== "Python" + + ```python title="list.py" + # Конкатенация двух списков + nums1: list[int] = [6, 8, 7, 10, 9] + nums += nums1 # Присоединить список nums1 к концу nums + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Конкатенация двух списков */ + vector nums1 = { 6, 8, 7, 10, 9 }; + // Присоединить список nums1 к концу nums + nums.insert(nums.end(), nums1.begin(), nums1.end()); + ``` + +=== "Java" + + ```java title="list.java" + /* Конкатенация двух списков */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); // Присоединить список nums1 к концу nums + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Конкатенация двух списков */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); // Присоединить список nums1 к концу nums + ``` + +=== "Go" + + ```go title="list_test.go" + /* Конкатенация двух списков */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // Присоединить список nums1 к концу nums + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Конкатенация двух списков */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) // Присоединить список nums1 к концу nums + ``` + +=== "JS" + + ```javascript title="list.js" + /* Конкатенация двух списков */ + const nums1 = [6, 8, 7, 10, 9]; + nums.push(...nums1); // Присоединить список nums1 к концу nums + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Конкатенация двух списков */ + const nums1: number[] = [6, 8, 7, 10, 9]; + nums.push(...nums1); // Присоединить список nums1 к концу nums + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Конкатенация двух списков */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); // Присоединить список nums1 к концу nums + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Конкатенация двух списков */ + let nums1: Vec = vec![6, 8, 7, 10, 9]; + nums.extend(nums1); + ``` + +=== "C" + + ```c title="list.c" + // В C нет встроенного динамического массива + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* Конкатенация двух списков */ + val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() + nums.addAll(nums1) // Присоединить список nums1 к концу nums + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # Конкатенация двух списков + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%B1%D1%8A%D0%B5%D0%B4%D0%B8%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20nums1%20%3D%20%5B6%2C%208%2C%207%2C%2010%2C%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%D0%9F%D1%80%D0%B8%D1%81%D0%BE%D0%B5%D0%B4%D0%B8%D0%BD%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20nums1%20%D0%BA%20nums&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Сортировка списка + +После сортировки списка мы сможем применять алгоритмы "бинарный поиск" и "два указателя", которые очень часто встречаются в задачах по массивам. + +=== "Python" + + ```python title="list.py" + # Отсортировать список + nums.sort() # После сортировки элементы списка идут по возрастанию + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* Отсортировать список */ + sort(nums.begin(), nums.end()); // После сортировки элементы списка идут по возрастанию + ``` + +=== "Java" + + ```java title="list.java" + /* Отсортировать список */ + Collections.sort(nums); // После сортировки элементы списка идут по возрастанию + ``` + +=== "C#" + + ```csharp title="list.cs" + /* Отсортировать список */ + nums.Sort(); // После сортировки элементы списка идут по возрастанию + ``` + +=== "Go" + + ```go title="list_test.go" + /* Отсортировать список */ + sort.Ints(nums) // После сортировки элементы списка идут по возрастанию + ``` + +=== "Swift" + + ```swift title="list.swift" + /* Отсортировать список */ + nums.sort() // После сортировки элементы списка идут по возрастанию + ``` + +=== "JS" + + ```javascript title="list.js" + /* Отсортировать список */ + nums.sort((a, b) => a - b); // После сортировки элементы списка идут по возрастанию + ``` + +=== "TS" + + ```typescript title="list.ts" + /* Отсортировать список */ + nums.sort((a, b) => a - b); // После сортировки элементы списка идут по возрастанию + ``` + +=== "Dart" + + ```dart title="list.dart" + /* Отсортировать список */ + nums.sort(); // После сортировки элементы списка идут по возрастанию + ``` + +=== "Rust" + + ```rust title="list.rs" + /* Отсортировать список */ + nums.sort(); // После сортировки элементы списка идут по возрастанию + ``` + +=== "C" + + ```c title="list.c" + // В C нет встроенного динамического массива + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* Отсортировать список */ + nums.sort() // После сортировки элементы списка идут по возрастанию + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # Отсортировать список + nums = nums.sort { |a, b| a <=> b } # После сортировки элементы списка идут по возрастанию + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%0A%20%20%20%20nums.sort%28%29%20%20%23%20%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%2C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D1%8B%20%D0%BF%D0%BE%20%D0%B2%D0%BE%D0%B7%D1%80%D0%B0%D1%81%D1%82%D0%B0%D0%BD%D0%B8%D1%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Реализация списка + +Во многих языках программирования списки встроены в стандартную библиотеку, например в Java, C++ и Python. Их реализация довольно сложна, а настройки параметров тщательно продуманы: начальная емкость, коэффициент расширения и так далее. Если тебе интересно, стоит заглянуть в исходный код. + +Чтобы лучше понять принцип работы списка, попробуем реализовать его упрощенную версию, в которой есть три ключевых аспекта проектирования. + +- **Начальная емкость**: выбрать разумную начальную емкость внутреннего массива. В этом примере мы берем 10. +- **Учет количества элементов**: объявить переменную `size` , которая будет хранить текущее число элементов в списке и обновляться в реальном времени при вставке и удалении элементов. С помощью этой переменной можно находить конец списка и понимать, требуется ли расширение. +- **Механизм расширения**: если при вставке элементов емкость списка исчерпана, нужно выполнить расширение. Для этого сначала создается больший массив с учетом коэффициента расширения, а затем все элементы текущего массива по порядку переносятся в новый. В этом примере мы считаем, что каждый раз массив расширяется в 2 раза. + +```src +[file]{my_list}-[class]{my_list}-[func]{} +``` diff --git a/ru/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png new file mode 100644 index 000000000..87477bab6 Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png new file mode 100644 index 000000000..92681cc5b Binary files /dev/null and b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png differ diff --git a/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md new file mode 100644 index 000000000..1698af133 --- /dev/null +++ b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -0,0 +1,71 @@ +# Оперативная память и кэш * + +В первых двух разделах этой главы мы разобрали массивы и связные списки - две фундаментальные и важные структуры данных, которые соответственно представляют две физические структуры хранения: "непрерывное хранение" и "разрозненное хранение". + +На практике **физическая структура во многом определяет, насколько эффективно программа использует память и кэш**, а это, в свою очередь, влияет на общую производительность алгоритмической программы. + +## Устройства хранения данных в компьютере + +В компьютере есть три типа устройств хранения данных: жесткий диск (hard disk) , оперативная память (random-access memory, RAM) и кэш-память (cache memory) . В таблице ниже показаны их различные роли и характеристики производительности в компьютерной системе. + +

Таблица   Устройства хранения данных в компьютере

+ +| | Жесткий диск | Оперативная память | Кэш | +| -------------- | --------------------------------------- | ----------------------------------------- | ------------------------------------------------------- | +| Назначение | Долговременное хранение данных, включая ОС, программы, файлы и т.д. | Временное хранение выполняемых программ и обрабатываемых данных | Хранение часто используемых данных и инструкций, уменьшающее число обращений CPU к памяти | +| Энергозависимость | Данные не теряются после отключения питания | Данные теряются после отключения питания | Данные теряются после отключения питания | +| Емкость | Большая, уровень TB | Меньшая, уровень GB | Очень малая, уровень MB | +| Скорость | Низкая, от сотен до тысяч MB/s | Высокая, десятки GB/s | Очень высокая, десятки и сотни GB/s | +| Цена (юани) | Дешевый, от долей юаня до нескольких юаней за GB | Дорогая, десятки и сотни юаней за GB | Очень дорогой, входит в стоимость упаковки CPU | + +Компьютерную систему хранения можно представить в виде пирамиды, показанной на рисунке ниже. Чем ближе устройство хранения к вершине пирамиды, тем оно быстрее, тем меньше его емкость и тем выше его стоимость. Такая многоуровневая конструкция возникла не случайно, а стала результатом тщательных инженерных компромиссов. + +- **Жесткий диск трудно заменить оперативной памятью**. Во-первых, данные в оперативной памяти исчезают после отключения питания, поэтому она не подходит для долговременного хранения. Во-вторых, память стоит в десятки раз дороже жесткого диска, что мешает ее широкому применению в потребительском сегменте. +- **Кэш не может одновременно быть и очень большим, и очень быстрым**. По мере роста емкости кэшей L1, L2 и L3 их физический размер увеличивается, расстояние до ядра CPU становится больше, время передачи данных растет, а задержка доступа к элементам увеличивается. При текущем уровне технологий многоуровневая структура кэша является лучшим балансом между емкостью, скоростью и стоимостью. + +![Система хранения данных компьютера](ram_and_cache.assets/storage_pyramid.png) + +!!! tip + + Иерархия памяти компьютера отражает тонкий баланс между скоростью, емкостью и стоимостью. На самом деле подобные компромиссы встречаются почти во всех отраслях инженерии: приходится искать оптимальный баланс между преимуществами и ограничениями. + +В итоге **жесткий диск используется для долговременного хранения больших объемов данных, оперативная память - для временного хранения данных, с которыми программа работает прямо сейчас, а кэш - для хранения часто используемых данных и инструкций**, чтобы ускорять выполнение программ. Все три уровня работают совместно и обеспечивают эффективную работу компьютерной системы. + +Как показано на рисунке ниже, во время выполнения программы данные читаются с жесткого диска в оперативную память, а затем используются CPU в вычислениях. Кэш можно рассматривать как часть CPU: **он интеллектуально подгружает данные из оперативной памяти**, обеспечивая CPU высокоскоростной доступ и тем самым значительно ускоряя выполнение программы и уменьшая зависимость от более медленной RAM. + +![Поток данных между жестким диском, RAM и кэшем](ram_and_cache.assets/computer_storage_devices.png) + +## Эффективность использования памяти структурами данных + +С точки зрения использования пространства памяти массивы и связные списки имеют свои преимущества и ограничения. + +С одной стороны, **память ограничена, и один и тот же участок памяти не может совместно использоваться несколькими программами**, поэтому нам хочется, чтобы структуры данных использовали пространство как можно эффективнее. Элементы массива расположены плотно и не требуют дополнительного места для хранения ссылок (указателей) между узлами списка, поэтому массивы эффективнее по памяти. Однако массиву нужно сразу выделить достаточно большой непрерывный участок памяти, что может приводить к потерям пространства, а его расширение требует дополнительных затрат времени и памяти. Напротив, связные списки выполняют динамическое выделение и освобождение памяти "по узлам", что дает большую гибкость. + +С другой стороны, во время выполнения программы **при многократном выделении и освобождении памяти фрагментация свободной памяти становится все более серьезной**, что снижает эффективность ее использования. Массивы из-за непрерывного хранения относительно менее подвержены фрагментации. Напротив, элементы связного списка распределены по памяти, и частые операции вставки и удаления легче приводят к фрагментации. + +## Эффективность использования кэша структурами данных + +Хотя по объему кэш намного меньше оперативной памяти, он значительно быстрее и играет критически важную роль в скорости выполнения программ. Поскольку объем кэша ограничен и в нем можно хранить только небольшую долю часто используемых данных, когда CPU пытается обратиться к данным, которых в кэше нет, происходит промах кэша (cache miss) , и CPU вынужден загружать нужные данные из более медленной памяти. + +Очевидно, что **чем меньше "промахов кэша", тем выше эффективность чтения и записи данных CPU**, а значит, тем лучше производительность программы. Долю обращений, при которых CPU успешно получает данные из кэша, называют коэффициентом попадания в кэш (cache hit rate) ; этот показатель обычно используют для оценки эффективности кэша. + +Чтобы добиться как можно большей эффективности, кэш использует следующие механизмы загрузки данных. + +- **Строки кэша**: кэш хранит и загружает данные не по одному байту, а строками кэша. По сравнению с передачей по байтам это гораздо эффективнее. +- **Механизм предвыборки**: процессор старается предсказать шаблон доступа к данным (например последовательный доступ, доступ с фиксированным шагом и т.д.) и на основе этого шаблона заранее загружает данные в кэш, повышая вероятность попадания. +- **Пространственная локальность**: если к некоторым данным уже обратились, то велика вероятность, что в ближайшее время понадобятся и соседние данные. Поэтому, загружая некоторые данные, кэш часто подгружает и окружающие их данные. +- **Временная локальность**: если к данным уже обратились, то высока вероятность, что к ним снова обратятся в ближайшем будущем. Кэш использует это свойство, сохраняя недавно использованные данные. + +На практике **массивы и связные списки по-разному используют кэш**, и это проявляется в нескольких аспектах. + +- **Занимаемое пространство**: элементы связного списка занимают больше места, чем элементы массива, поэтому в кэше помещается меньше полезных данных. +- **Строки кэша**: данные списка разбросаны по памяти, а кэш загружает данные "строками", поэтому доля бесполезно загружаемых данных оказывается выше. +- **Механизм предвыборки**: шаблон доступа к данным у массивов более "предсказуем", чем у списков, то есть системе легче угадать, какие данные понадобятся следующими. +- **Пространственная локальность**: массив хранится в компактной области памяти, поэтому данные рядом с уже загруженными с большей вероятностью скоро будут использованы. + +В целом **массивы имеют более высокий коэффициент попадания в кэш, поэтому по эффективности операций они обычно превосходят связные списки**. Именно поэтому при решении алгоритмических задач структуры данных на основе массивов часто оказываются предпочтительнее. + +Важно понимать, что **высокая эффективность кэша не означает, что массивы во всех случаях лучше связных списков**. В реальных приложениях выбор структуры данных должен определяться конкретными требованиями. Например, и массивы, и списки могут использоваться для реализации "стека" (подробнее об этом будет рассказано в следующей главе), но подходят они для разных сценариев. + +- При решении алгоритмических задач мы обычно предпочитаем стек на основе массива, потому что он дает более высокую эффективность операций и поддерживает произвольный доступ, а цена за это - необходимость заранее выделить некоторый объем памяти под массив. +- Если объем данных очень велик, структура сильно динамична, а ожидаемый размер стека трудно оценить заранее, то более уместен стек на основе связного списка. Список позволяет распределить большой объем данных по разным участкам памяти и избегает накладных расходов, связанных с расширением массива. diff --git a/ru/docs/chapter_array_and_linkedlist/summary.md b/ru/docs/chapter_array_and_linkedlist/summary.md new file mode 100644 index 000000000..a7831ed00 --- /dev/null +++ b/ru/docs/chapter_array_and_linkedlist/summary.md @@ -0,0 +1,86 @@ +# Резюме + +### Ключевые выводы + +- Массивы и связные списки - это две базовые структуры данных, представляющие два способа хранения данных в памяти компьютера: хранение в непрерывной области и хранение в разрозненных областях. Их свойства во многом взаимно дополняют друг друга. +- Массив поддерживает произвольный доступ и занимает меньше памяти; однако вставка и удаление элементов в нем неэффективны, а длина после инициализации неизменяема. +- Связный список позволяет эффективно вставлять и удалять узлы путем изменения ссылок (указателей), а также гибко менять длину; однако доступ к узлам неэффективен, а памяти он занимает больше. Распространенные типы списков включают односвязные, циклические и двусвязные списки. +- Список - это упорядоченная коллекция элементов, поддерживающая добавление, удаление, поиск и изменение, и обычно реализуемая на основе динамического массива. Он сохраняет преимущества массива и при этом может гибко менять длину. +- Появление списка значительно повысило практическую полезность массива, хотя это и может приводить к потерям части памяти. +- Во время работы программы данные в основном хранятся в оперативной памяти. Массив обеспечивает более высокую эффективность использования пространства памяти, а связный список дает большую гибкость в использовании памяти. +- Кэш, используя строки кэша, механизм предвыборки, а также пространственную и временную локальность, предоставляет CPU быстрый доступ к данным и заметно повышает эффективность выполнения программ. +- Поскольку массивы обычно имеют более высокий коэффициент попадания в кэш, они в большинстве случаев работают эффективнее списков. При выборе структуры данных нужно исходить из конкретных требований и сценариев. + +### Q & A + +**Q**: Влияет ли хранение массива в стеке или в куче на временную и пространственную эффективность? + +Массивы, расположенные и в стеке, и в куче, все равно хранятся в непрерывной области памяти, поэтому эффективность операций с данными у них в целом одинакова. Однако у стека и кучи есть собственные особенности, из-за которых возникают следующие различия. + +1. Эффективность выделения и освобождения: стек представляет собой относительно небольшой участок памяти, а выделение в нем обычно выполняется автоматически компилятором; куча же обычно больше, может выделяться динамически из кода и легче фрагментируется. Поэтому выделение и освобождение памяти в куче обычно медленнее, чем в стеке. +2. Ограничение размера: объем стека относительно невелик, а размер кучи обычно ограничивается доступной памятью. Поэтому куча лучше подходит для хранения больших массивов. +3. Гибкость: размер массива в стеке должен быть известен во время компиляции, а размер массива в куче может определяться динамически во время выполнения. + +**Q**: Почему для массива требуется, чтобы все элементы были одного типа, а для связного списка это не подчеркивается? + +Связный список состоит из узлов, а узлы соединяются между собой через ссылки (указатели), поэтому каждый узел в принципе может хранить данные разного типа, например `int` , `double` , `string` , `object` и т.д. + +Напротив, элементы массива должны быть одного типа, иначе нельзя будет вычислять адрес элемента через смещение. Например, если массив одновременно содержит `int` и `long` , один элемент занимает 4 байта, а другой - 8 байт ; в этом случае формула ниже уже не позволит вычислить смещение, потому что в массиве будут присутствовать элементы разной длины. + +```shell +# Адрес элемента в памяти = адрес массива в памяти (адрес первого элемента) + длина элемента * индекс элемента +``` + +**Q**: После удаления узла `P` нужно ли присваивать `P.next = None` ? + +Можно и не изменять `P.next` . С точки зрения данного списка, при обходе от головы к хвосту узел `P` уже больше не встретится. Это означает, что узел `P` уже удален из списка, и то, куда он указывает после этого, на сам список больше не влияет. + +С точки зрения задач по структурам данных и алгоритмам, отсутствие такого разрыва обычно не критично, если логика программы остается корректной. Но с точки зрения стандартной библиотеки разорвать связь безопаснее и логичнее. Если этого не сделать и удаленный узел не будет нормально собран, он может мешать освобождению памяти последующих узлов. + +**Q**: Временная сложность вставки и удаления в связном списке равна $O(1)$ . Но до вставки или удаления обычно еще нужно потратить $O(n)$ на поиск элемента. Почему тогда общая сложность не $O(n)$ ? + +Если сначала искать элемент, а потом удалять его, то временная сложность действительно будет $O(n)$ . Однако преимущество связного списка с $O(1)$ вставкой и удалением проявляется в других сценариях. Например, двустороннюю очередь удобно реализовывать именно на связном списке: мы поддерживаем указатели на голову и хвост, и тогда каждая операция вставки или удаления остается $O(1)$ . + +**Q**: На рисунке "Определение связного списка и способ хранения" светло-голубой блок с указателем узла - это отдельный адрес памяти? Или он делит память пополам со значением узла? + +Этот рисунок дает только качественное представление; количественно все зависит от конкретных условий. + +- Значения узлов разных типов занимают разный объем памяти, например `int` , `long` , `double` и объекты-экземпляры. +- Размер памяти, занимаемой переменной-указателем, зависит от операционной системы и среды компиляции и обычно составляет 8 байт или 4 байта. + +**Q**: Всегда ли добавление элемента в конец списка имеет сложность $O(1)$ ? + +Если при добавлении элемента длина списка превышается, то сначала приходится расширять список, а уже затем добавлять новый элемент. Система выделяет новый участок памяти и переносит туда все элементы исходного списка, и в этот момент временная сложность становится $O(n)$ . + +**Q**: В утверждении "появление списка сильно повысило практическую полезность массива, но может приводить к потере части памяти" под потерями памяти имеется в виду дополнительная память под такие переменные, как емкость, длина и коэффициент расширения? + +Потери памяти здесь в основном имеют два значения: во-первых, список обычно имеет некоторую начальную емкость, которая может быть нам не нужна целиком; во-вторых, чтобы избежать слишком частых расширений, емкость при расширении обычно умножается на некоторый коэффициент, например $\times 1.5$ . Из-за этого появляется много пустых слотов, которые обычно нельзя полностью заполнить. + +**Q**: В Python после инициализации `n = [1, 2, 3]` адреса этих трех элементов выглядят непрерывными, но после `m = [2, 1, 3]` можно заметить, что `id` элементов не идут подряд, а совпадают с одинаковыми числами из `n` . Если адреса элементов не непрерывны, остается ли `m` массивом? + +Предположим, что элементами списка являются узлы `n = [n1, n2, n3, n4, n5]` . Обычно эти 5 объектов-узлов тоже будут храниться в разных местах памяти. Однако, имея индекс списка, мы по-прежнему можем за $O(1)$ получить адрес памяти соответствующего узла и обратиться к нему. Это связано с тем, что в массиве хранятся ссылки на узлы, а не сами узлы. + +В отличие от многих других языков, в Python даже числа обернуты в объекты, и в списке хранятся не сами числа, а ссылки на них. Поэтому мы и наблюдаем, что одинаковые числа в двух массивах имеют один и тот же `id` , а адреса этих чисел не обязаны быть непрерывными. + +**Q**: В C++ STL уже есть двусвязный список `std::list` , но в некоторых учебниках по алгоритмам им пользуются не так часто. Это связано с какими-то ограничениями? + +С одной стороны, при разработке алгоритмов мы обычно предпочитаем структуры на основе массива, а к связным спискам прибегаем только при необходимости, по двум главным причинам. + +- Накладные расходы по памяти: поскольку каждому элементу нужны два дополнительных указателя (на предыдущий и следующий элементы), `std::list` обычно занимает больше памяти, чем `std::vector` . +- Низкая дружелюбность к кэшу: поскольку данные не лежат непрерывно, `std::list` хуже использует кэш. В большинстве случаев `std::vector` показывает лучшую производительность. + +С другой стороны, случаи, когда связный список действительно необходим, в основном возникают в деревьях и графах. Для стеков и очередей чаще используют предоставляемые языком `stack` и `queue` , а не связный список напрямую. + +**Q**: Операция `res = [[0]] * n` создает двумерный список. Каждый `[0]` в нем независим? + +Нет, они не независимы. В таком двумерном списке все `[0]` на самом деле являются ссылками на один и тот же объект. Если изменить один из них, окажется, что меняются и все остальные соответствующие элементы. + +Если нужно, чтобы каждый `[0]` был независимым, можно использовать `res = [[0] for _ in range(n)]` . В этом варианте создаются $n$ независимых объектов-списков `[0]` . + +**Q**: Операция `res = [0] * n` создает список. Каждый целочисленный `0` в нем независим? + +В этом списке все целые числа `0` являются ссылками на один и тот же объект. Это связано с тем, что Python использует механизм кэш-пула для маленьких целых чисел (обычно от -5 до 256), чтобы максимально переиспользовать объекты и повысить производительность. + +Хотя все элементы указывают на один и тот же объект, мы все равно можем независимо изменять элементы списка, потому что целые числа в Python - это "неизменяемые объекты". Когда мы изменяем некоторый элемент, на самом деле происходит переключение ссылки на другой объект, а не изменение исходного объекта. + +Однако если элементами списка являются "изменяемые объекты" (например списки, словари или экземпляры классов), то изменение одного элемента прямо меняет сам объект, и все элементы, ссылающиеся на него, увидят одно и то же изменение. diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png new file mode 100644 index 000000000..1883e048d Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png new file mode 100644 index 000000000..3ff193ba3 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png new file mode 100644 index 000000000..ee6a7d6b8 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png new file mode 100644 index 000000000..eaed64282 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png new file mode 100644 index 000000000..cf501988a Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png new file mode 100644 index 000000000..cd8a232dc Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png new file mode 100644 index 000000000..169cff15b Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png new file mode 100644 index 000000000..d1cd08b4e Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png new file mode 100644 index 000000000..14ef2e17b Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png new file mode 100644 index 000000000..3840c2b38 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png new file mode 100644 index 000000000..0c8e3fe13 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png new file mode 100644 index 000000000..53f280433 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png new file mode 100644 index 000000000..57e54b229 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png new file mode 100644 index 000000000..a07fe9547 Binary files /dev/null and b/ru/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png differ diff --git a/ru/docs/chapter_backtracking/backtracking_algorithm.md b/ru/docs/chapter_backtracking/backtracking_algorithm.md new file mode 100644 index 000000000..ebc5aea33 --- /dev/null +++ b/ru/docs/chapter_backtracking/backtracking_algorithm.md @@ -0,0 +1,503 @@ +# Алгоритм поиска с возвратом + +Алгоритм поиска с возвратом (backtracking algorithm) - это метод решения задач путем полного перебора. Его основная идея состоит в том, чтобы, начиная с некоторого исходного состояния, грубо перебрать все возможные решения, записывать корректные решения и продолжать поиск до тех пор, пока решение не будет найдено или пока не будут исчерпаны все возможные варианты. + +Обычно алгоритмы поиска с возвратом используют "поиск в глубину" для обхода пространства решений. В главе "Бинарные деревья" мы уже упоминали, что прямой, симметричный и обратный обходы относятся к поиску в глубину. Теперь мы на основе прямого обхода построим задачу backtracking и постепенно разберем принцип работы этого алгоритма. + +!!! question "Пример 1" + + Дано двоичное дерево. Найдите и запишите все узлы со значением $7$ ; верните список этих узлов. + +Для этой задачи мы выполняем прямой обход дерева и проверяем, равно ли значение текущего узла $7$ ; если да, то добавляем значение этого узла в список результатов `res` . Соответствующий процесс показан на рисунке ниже и в коде: + +```src +[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} +``` + +![Поиск узлов при прямом обходе](backtracking_algorithm.assets/preorder_find_nodes.png) + +## Попытка и откат + +**Алгоритм называется backtracking, потому что при поиске в пространстве решений он использует стратегию "попытка" и "откат"**. Когда в процессе поиска алгоритм приходит в состояние, из которого нельзя двигаться дальше или нельзя получить удовлетворяющее условиям решение, он отменяет предыдущий выбор, возвращается к более раннему состоянию и пробует другие возможные варианты. + +Для примера 1 посещение каждого узла представляет собой "попытку", а прохождение листового узла или возврат к родителю через `return` означает "откат". + +Важно понимать, что **откат не сводится только к возврату из функции**. Чтобы показать это, слегка расширим пример 1. + +!!! question "Пример 2" + + Найдите в двоичном дереве все узлы со значением $7$ и **верните пути от корня до этих узлов**. + +Взяв за основу код примера 1, добавим список `path` для записи пути посещенных узлов. Когда встречается узел со значением $7$ , мы копируем `path` и добавляем его в список результатов `res` . После завершения обхода именно `res` будет содержать все решения. Код приведен ниже: + +```src +[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} +``` + +В каждой "попытке" мы добавляем текущий узел в `path` , чтобы записать путь; а перед "откатом" нам нужно удалить этот узел из `path` , **чтобы восстановить состояние, существовавшее до текущей попытки**. + +Если посмотреть на процесс, изображенный на рисунке ниже, **то попытку и откат можно понимать как "движение вперед" и "отмену"**: это два взаимно противоположных действия. + +=== "<1>" + ![Попытка и откат](backtracking_algorithm.assets/preorder_find_paths_step1.png) + +=== "<2>" + ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) + +=== "<3>" + ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) + +=== "<4>" + ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) + +=== "<5>" + ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) + +=== "<6>" + ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) + +=== "<7>" + ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) + +=== "<8>" + ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) + +=== "<9>" + ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) + +=== "<10>" + ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) + +=== "<11>" + ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) + +## Обрезка + +Сложные задачи backtracking обычно содержат одно или несколько ограничений, **которые часто можно использовать для "обрезки"**. + +!!! question "Пример 3" + + Найдите в двоичном дереве все узлы со значением $7$ , верните пути от корня до этих узлов, **причем путь не должен содержать узлы со значением $3$**. + +Чтобы выполнить это ограничение, **нам нужно добавить операцию обрезки**: во время поиска, если встречается узел со значением $3$ , мы сразу возвращаемся и не продолжаем дальнейший поиск. Код выглядит так: + +```src +[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} +``` + +Термин "обрезка" очень нагляден. Как показано на рисунке ниже, во время поиска **мы "срезаем" ветви поиска, не удовлетворяющие ограничениям** , тем самым избегая множества бессмысленных попыток и повышая эффективность поиска. + +![Обрезка по условиям задачи](backtracking_algorithm.assets/preorder_find_constrained_paths.png) + +## Каркас кода + +Теперь попробуем извлечь общий каркас из действий "попытка", "откат" и "обрезка", чтобы сделать код более универсальным. + +В следующем каркасе кода `state` обозначает текущее состояние задачи, а `choices` - список выборов, доступных в текущем состоянии: + +=== "Python" + + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """Каркас алгоритма поиска с возвратом""" + # Проверка, является ли текущее состояние решением + if is_solution(state): + # Запись решения + record_solution(state, res) + # Дальше не продолжаем поиск + return + # Перебор всех возможных выборов + for choice in choices: + # Обрезка: проверка допустимости выбора + if is_valid(state, choice): + # Попытка: сделать выбор и обновить состояние + make_choice(state, choice) + backtrack(state, choices, res) + # Откат: отменить выбор и восстановить предыдущее состояние + undo_choice(state, choice) + ``` + +=== "C++" + + ```cpp title="" + /* Каркас алгоритма поиска с возвратом */ + void backtrack(State *state, vector &choices, vector &res) { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for (Choice choice : choices) { + // Обрезка: проверка допустимости выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } + } + ``` + +=== "Java" + + ```java title="" + /* Каркас алгоритма поиска с возвратом */ + void backtrack(State state, List choices, List res) { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for (Choice choice : choices) { + // Обрезка: проверка допустимости выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + /* Каркас алгоритма поиска с возвратом */ + void Backtrack(State state, List choices, List res) { + // Проверка, является ли текущее состояние решением + if (IsSolution(state)) { + // Запись решения + RecordSolution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + foreach (Choice choice in choices) { + // Обрезка: проверка допустимости выбора + if (IsValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + MakeChoice(state, choice); + Backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + UndoChoice(state, choice); + } + } + } + ``` + +=== "Go" + + ```go title="" + /* Каркас алгоритма поиска с возвратом */ + func backtrack(state *State, choices []Choice, res *[]State) { + // Проверка, является ли текущее состояние решением + if isSolution(state) { + // Запись решения + recordSolution(state, res) + // Дальше не продолжаем поиск + return + } + // Перебор всех возможных выборов + for _, choice := range choices { + // Обрезка: проверка допустимости выбора + if isValid(state, choice) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice) + backtrack(state, choices, res) + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Каркас алгоритма поиска с возвратом */ + func backtrack(state: inout State, choices: [Choice], res: inout [State]) { + // Проверка, является ли текущее состояние решением + if isSolution(state: state) { + // Запись решения + recordSolution(state: state, res: &res) + // Дальше не продолжаем поиск + return + } + // Перебор всех возможных выборов + for choice in choices { + // Обрезка: проверка допустимости выбора + if isValid(state: state, choice: choice) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state: &state, choice: choice) + backtrack(state: &state, choices: choices, res: &res) + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state: &state, choice: choice) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Каркас алгоритма поиска с возвратом */ + function backtrack(state, choices, res) { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for (let choice of choices) { + // Обрезка: проверка допустимости выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Каркас алгоритма поиска с возвратом */ + function backtrack(state: State, choices: Choice[], res: State[]): void { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for (let choice of choices) { + // Обрезка: проверка допустимости выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Каркас алгоритма поиска с возвратом */ + void backtrack(State state, List, List res) { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for (Choice choice in choices) { + // Обрезка: проверка допустимости выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice); + backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + /* Каркас алгоритма поиска с возвратом */ + fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { + // Проверка, является ли текущее состояние решением + if is_solution(state) { + // Запись решения + record_solution(state, res); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for choice in choices { + // Обрезка: проверка допустимости выбора + if is_valid(state, choice) { + // Попытка: сделать выбор и обновить состояние + make_choice(state, choice); + backtrack(state, choices, res); + // Откат: отменить выбор и восстановить предыдущее состояние + undo_choice(state, choice); + } + } + } + ``` + +=== "C" + + ```c title="" + /* Каркас алгоритма поиска с возвратом */ + void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res, numRes); + // Дальше не продолжаем поиск + return; + } + // Перебор всех возможных выборов + for (int i = 0; i < numChoices; i++) { + // Обрезка: проверка допустимости выбора + if (isValid(state, &choices[i])) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, &choices[i]); + backtrack(state, choices, numChoices, res, numRes); + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, &choices[i]); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Каркас алгоритма поиска с возвратом */ + fun backtrack(state: State?, choices: List, res: List?) { + // Проверка, является ли текущее состояние решением + if (isSolution(state)) { + // Запись решения + recordSolution(state, res) + // Дальше не продолжаем поиск + return + } + // Перебор всех возможных выборов + for (choice in choices) { + // Обрезка: проверка допустимости выбора + if (isValid(state, choice)) { + // Попытка: сделать выбор и обновить состояние + makeChoice(state, choice) + backtrack(state, choices, res) + // Откат: отменить выбор и восстановить предыдущее состояние + undoChoice(state, choice) + } + } + } + ``` + +=== "Ruby" + + ```ruby title="" + ### Каркас алгоритма поиска с возвратом ### + def backtrack(state, choices, res) + # Проверка, является ли текущее состояние решением + if is_solution?(state) + # Запись решения + record_solution(state, res) + return + end + + # Перебор всех возможных выборов + for choice in choices + # Обрезка: проверка допустимости выбора + if is_valid?(state, choice) + # Попытка: сделать выбор и обновить состояние + make_choice(state, choice) + backtrack(state, choices, res) + # Откат: отменить выбор и восстановить предыдущее состояние + undo_choice(state, choice) + end + end + end + ``` + +Теперь, опираясь на этот каркас, решим пример 3. Состояние `state` здесь - это путь обхода узлов, выбор `choices` - левый и правый потомки текущего узла, а результат `res` - список путей: + +```src +[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} +``` + +Согласно условию задачи, после нахождения узла со значением $7$ мы должны продолжать поиск, **поэтому оператор `return` после записи решения нужно удалить**. На рисунке ниже сравниваются процессы поиска в случаях, когда `return` сохраняется и когда он удаляется. + +![Сравнение поиска при сохранении и удалении return](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) + +По сравнению с реализацией на основе прямого обхода, версия на основе общего каркаса backtracking выглядит более громоздкой, но при этом обладает лучшей универсальностью. На практике **многие задачи backtracking можно решать в рамках этого каркаса**. Для этого нужно лишь определить `state` и `choices` под конкретную задачу и реализовать соответствующие методы каркаса. + +## Часто используемые термины + +Чтобы яснее анализировать алгоритмические задачи, подытожим значения часто используемых терминов backtracking и сопоставим их с примером 3, как показано в таблице ниже. + +

Таблица   Часто используемые термины алгоритма backtracking

+ +| Термин | Определение | Пример 3 | +| ------------------------ | -------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| Решение (solution) | Решение - это ответ, удовлетворяющий условиям задачи; решений может быть одно или несколько | Все пути от корня до узла $7$ , удовлетворяющие ограничениям | +| Ограничение (constraint) | Ограничение определяет допустимость решения и обычно используется для обрезки | Путь не содержит узлы со значением $3$ | +| Состояние (state) | Состояние описывает ситуацию задачи в некоторый момент времени, включая уже сделанные выборы | Текущий путь посещенных узлов, то есть список узлов `path` | +| Попытка (attempt) | Попытка - это исследование пространства решений на основе доступных выборов, включая выбор, обновление состояния и проверку, является ли состояние решением | Рекурсивный переход к левому или правому потомку, добавление узла в `path` и проверка, равно ли значение узла $7$ | +| Откат (backtracking) | Откат означает отмену предыдущих выборов и возврат к более раннему состоянию при встрече состояния, не удовлетворяющего ограничениям | Завершение поиска при проходе через лист, окончании посещения узла или встрече узла со значением $3$ , то есть возврат из функции | +| Обрезка (pruning) | Обрезка - это способ избегать бессмысленных путей поиска на основе свойств задачи и ее ограничений, повышающий эффективность | При встрече узла со значением $3$ поиск по этой ветви прекращается | + +!!! tip + + Такие понятия, как задача, решение и состояние, являются общими и встречаются не только в backtracking, но и в divide and conquer, динамическом программировании, жадных алгоритмах и других темах. + +## Преимущества и ограничения + +Алгоритм поиска с возвратом по своей сути является алгоритмом поиска в глубину, который перебирает все возможные решения, пока не найдет удовлетворяющее условиям. Преимущество этого подхода в том, что он позволяет находить все возможные решения и при разумной обрезке может работать весьма эффективно. + +Однако при работе с большими или сложными задачами **эффективность backtracking может оказаться неприемлемой**. + +- **Время**: backtracking обычно требует обхода всех возможных состояний пространства состояний, и его временная сложность может достигать экспоненциального или факториального порядка. +- **Память**: при рекурсивных вызовах нужно хранить текущее состояние (например, путь, вспомогательные переменные для обрезки и т.д.), поэтому при большой глубине рекурсии потребность в памяти может стать значительной. + +Тем не менее **backtracking по-прежнему остается лучшим решением для некоторых поисковых задач и задач удовлетворения ограничений**. В таких задачах заранее невозможно предсказать, какие выборы приведут к эффективному решению, поэтому приходится перебирать все возможные варианты. В этой ситуации **ключевым становится вопрос оптимизации эффективности** , и для этого обычно используют две стратегии. + +- **Обрезка**: избегать поиска по тем путям, которые заведомо не приведут к решению, тем самым экономя время и память. +- **Эвристический поиск**: вводить во время поиска дополнительные стратегии или оценки, чтобы в первую очередь исследовать пути, наиболее вероятно ведущие к эффективному решению. + +## Типичные задачи backtracking + +Алгоритм поиска с возвратом можно использовать для решения множества поисковых задач, задач удовлетворения ограничений и задач комбинаторной оптимизации. + +**Поисковые задачи**: целью таких задач является поиск решений, удовлетворяющих определенным условиям. + +- Задача о перестановках: дано множество, требуется найти все возможные перестановки его элементов. +- Задача о сумме подмножеств: даны множество и целевая сумма; нужно найти все подмножества, сумма элементов которых равна целевой. +- Задача о Ханойской башне: даны три стержня и набор дисков разного размера; требуется перенести все диски с одного стержня на другой, перемещая за раз только один диск и не помещая больший диск на меньший. + +**Задачи удовлетворения ограничений**: целью таких задач является поиск решений, удовлетворяющих всем ограничениям. + +- Задача о $n$ ферзях: разместить $n$ ферзей на шахматной доске размера $n \times n$ так, чтобы они не атаковали друг друга. +- Судоку: заполнить сетку $9 \times 9$ числами от $1$ до $9$ так, чтобы в каждой строке, каждом столбце и каждом блоке $3 \times 3$ числа не повторялись. +- Задача раскраски графа: дан неориентированный граф; требуется раскрасить его вершины минимальным числом цветов так, чтобы соседние вершины имели разные цвета. + +**Задачи комбинаторной оптимизации**: целью таких задач является поиск оптимального решения в некотором комбинаторном пространстве при заданных ограничениях. + +- Задача о рюкзаке 0-1: даны набор предметов и рюкзак; у каждого предмета есть ценность и вес, и нужно выбрать предметы так, чтобы при ограниченной вместимости рюкзака суммарная ценность была максимальной. +- Задача коммивояжера: начиная из некоторой вершины графа, требуется посетить все остальные вершины ровно по одному разу и вернуться в исходную вершину, найдя при этом кратчайший путь. +- Задача о максимальной клике: дан неориентированный граф; требуется найти в нем максимальный полный подграф, то есть подграф, в котором любая пара вершин соединена ребром. + +Обратите внимание: для многих задач комбинаторной оптимизации backtracking не является оптимальным способом решения. + +- Задача о рюкзаке 0-1 обычно решается с помощью динамического программирования, что дает более высокую временную эффективность. +- Задача коммивояжера является известной NP-Hard задачей; для ее решения часто используют генетические алгоритмы, муравьиные алгоритмы и другие методы. +- Задача о максимальной клике является классической задачей теории графов и может решаться жадными и другими эвристическими алгоритмами. diff --git a/ru/docs/chapter_backtracking/index.md b/ru/docs/chapter_backtracking/index.md new file mode 100644 index 000000000..604d15d8e --- /dev/null +++ b/ru/docs/chapter_backtracking/index.md @@ -0,0 +1,9 @@ +# Поиск с возвратом + +![Поиск с возвратом](../assets/covers/chapter_backtracking.jpg) + +!!! abstract + + Мы словно исследователи в лабиринте: на пути вперед могут встречаться тупики и трудности. + + Сила возврата позволяет нам начать заново, пробовать снова и снова и в конце концов найти выход к свету. diff --git a/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png b/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png new file mode 100644 index 000000000..0ed26e9ec Binary files /dev/null and b/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png differ diff --git a/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png b/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png new file mode 100644 index 000000000..b56a5d580 Binary files /dev/null and b/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png differ diff --git a/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png b/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png new file mode 100644 index 000000000..5072990dc Binary files /dev/null and b/ru/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png differ diff --git a/ru/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png b/ru/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png new file mode 100644 index 000000000..22a52e9dd Binary files /dev/null and b/ru/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png differ diff --git a/ru/docs/chapter_backtracking/n_queens_problem.md b/ru/docs/chapter_backtracking/n_queens_problem.md new file mode 100644 index 000000000..9c559dc47 --- /dev/null +++ b/ru/docs/chapter_backtracking/n_queens_problem.md @@ -0,0 +1,53 @@ +# Задача о n ферзях + +!!! question + + Согласно правилам шахмат ферзь может атаковать фигуры, находящиеся с ним на одной строке, в одном столбце или на одной диагонали. Даны $n$ ферзей и шахматная доска размера $n \times n$ ; требуется найти такие расстановки, при которых ни одна пара ферзей не может атаковать друг друга. + +Как показано на рисунке ниже, при $n = 4$ существует два решения. С точки зрения backtracking доска размера $n \times n$ содержит $n^2$ клеток, которые образуют все возможные выборы `choices` . По мере поочередного размещения ферзей состояние доски непрерывно меняется, и текущее содержимое доски образует состояние `state` . + +![Решения задачи о 4 ферзях](n_queens_problem.assets/solution_4_queens.png) + +На рисунке ниже показаны три ограничения этой задачи: **несколько ферзей не могут находиться на одной строке, в одном столбце или на одной диагонали**. При этом нужно помнить, что диагонали бывают двух типов: главная `\` и побочная `/` . + +![Ограничения задачи о n ферзях](n_queens_problem.assets/n_queens_constraints.png) + +### Построчная стратегия размещения + +Число ферзей и число строк доски одинаково и равно $n$ , поэтому легко получить следующий вывод: **в каждой строке доски разрешено и нужно разместить ровно одного ферзя**. + +Иначе говоря, можно использовать построчную стратегию: начиная с первой строки, размещать по одному ферзю в каждой строке, пока не будет достигнута последняя. + +На рисунке ниже показан процесс построчного размещения для задачи о 4 ферзях. Из-за ограничений размера изображения на нем раскрыта только одна ветвь поиска для первой строки, а все варианты, не удовлетворяющие ограничениям по столбцам и диагоналям, были отсечены. + +![Построчная стратегия размещения](n_queens_problem.assets/n_queens_placing.png) + +По своей сути **построчная стратегия сама по себе выполняет роль обрезки** , потому что заранее исключает все ветви поиска, в которых в одной строке оказалось бы несколько ферзей. + +### Обрезка по столбцам и диагоналям + +Чтобы удовлетворить ограничению по столбцам, можно использовать булев массив `cols` длины $n$ , который записывает, есть ли ферзь в каждом столбце. Перед каждым размещением мы используем `cols` для отсечения столбцов, уже занятых ферзями, а затем динамически обновляем состояние `cols` во время отката. + +!!! tip + + Обратите внимание: начало координат матрицы находится в левом верхнем углу, при этом индексы строк растут сверху вниз, а индексы столбцов - слева направо. + +Как теперь обработать ограничения по диагоналям? Пусть клетка на доске имеет координаты $(row, col)$ . Выбрав некоторую главную диагональ в матрице, можно заметить, что разность индексов строки и столбца одинакова для всех клеток этой диагонали, **то есть для всех клеток главной диагонали значение $row - col$ постоянно**. + +Это означает, что если для двух клеток выполняется равенство $row_1 - col_1 = row_2 - col_2$ , то они обязательно лежат на одной и той же главной диагонали. Используя это правило, можно с помощью массива `diags1` , показанного на рисунке ниже, отмечать наличие ферзя на каждой главной диагонали. + +Аналогично **для всех клеток побочной диагонали значение $row + col$ является постоянным**. Поэтому для обработки ограничений по побочным диагоналям можно использовать еще один массив `diags2` . + +![Обработка ограничений по столбцам и диагоналям](n_queens_problem.assets/n_queens_cols_diagonals.png) + +### Реализация кода + +Заметим, что в квадратной матрице размера $n$ диапазон значений $row - col$ равен $[-n + 1, n - 1]$ , а диапазон значений $row + col$ равен $[0, 2n - 2]$ . Следовательно, число главных и побочных диагоналей равно $2n - 1$ , а значит, длины массивов `diags1` и `diags2` тоже равны $2n - 1$ . + +```src +[file]{n_queens}-[class]{}-[func]{n_queens} +``` + +Если размещать ферзей построчно $n$ раз, учитывая ограничение по столбцам, то начиная с первой строки и заканчивая последней мы получаем соответственно $n$, $n-1$, $\dots$, $2$, $1$ вариантов выбора, что дает $O(n!)$ времени. При записи решения нужно скопировать матрицу `state` и добавить ее в `res` , а копирование требует $O(n^2)$ времени. Следовательно, **общая временная сложность равна $O(n! \cdot n^2)$** . На практике обрезка по диагональным ограничениям дополнительно сильно уменьшает пространство поиска, поэтому фактическая эффективность часто лучше этой оценки. + +Массив `state` использует $O(n^2)$ пространства, а массивы `cols` , `diags1` и `diags2` используют по $O(n)$ пространства. Максимальная глубина рекурсии равна $n$ , что требует $O(n)$ памяти стека. Следовательно, **пространственная сложность равна $O(n^2)$** . diff --git a/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png new file mode 100644 index 000000000..f47f45cde Binary files /dev/null and b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png differ diff --git a/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png new file mode 100644 index 000000000..f3074aca6 Binary files /dev/null and b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png differ diff --git a/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png new file mode 100644 index 000000000..34b022cb2 Binary files /dev/null and b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png differ diff --git a/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png new file mode 100644 index 000000000..1091de387 Binary files /dev/null and b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png differ diff --git a/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png new file mode 100644 index 000000000..ef33b049f Binary files /dev/null and b/ru/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png differ diff --git a/ru/docs/chapter_backtracking/permutations_problem.md b/ru/docs/chapter_backtracking/permutations_problem.md new file mode 100644 index 000000000..b12e39ee5 --- /dev/null +++ b/ru/docs/chapter_backtracking/permutations_problem.md @@ -0,0 +1,95 @@ +# Задача о перестановках + +Задача о перестановках является типичным применением алгоритма поиска с возвратом. Ее определение состоит в том, чтобы для данного множества элементов (например, массива или строки) найти все возможные перестановки этих элементов. + +В таблице ниже приведено несколько примеров входных массивов и соответствующих им перестановок. + +

Таблица   Примеры перестановок

+ +| Входной массив | Все перестановки | +| :------------- | :----------------------------------------------------------------- | +| $[1]$ | $[1]$ | +| $[1, 2]$ | $[1, 2], [2, 1]$ | +| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | + +## Случай без равных элементов + +!!! question + + Дан массив целых чисел, в котором нет повторяющихся элементов. Верните все возможные перестановки. + +С точки зрения backtracking **процесс построения перестановок можно представить как результат последовательности выборов**. Пусть входной массив равен $[1, 2, 3]$ ; если мы сначала выберем $1$ , затем $3$ , а потом $2$ , то получим перестановку $[1, 3, 2]$ . Откат означает отмену одного из выборов с последующей попыткой других вариантов. + +С точки зрения кода backtracking множество кандидатов `choices` состоит из всех элементов входного массива, а состояние `state` - из элементов, уже выбранных к текущему моменту. Обратите внимание, что каждый элемент разрешено выбирать только один раз, **поэтому все элементы в `state` должны быть уникальны**. + +Как показано на рисунке ниже, процесс поиска можно развернуть в дерево рекурсии, где каждый узел представляет текущее состояние `state` . Начиная от корня, после трех раундов выбора мы попадаем в листья, и каждый лист соответствует одной перестановке. + +![Дерево рекурсии для перестановок](permutations_problem.assets/permutations_i.png) + +### Обрезка повторного выбора + +Чтобы гарантировать, что каждый элемент выбирается только один раз, введем булев массив `selected` , где `selected[i]` обозначает, был ли уже выбран `choices[i]` , и на его основе выполним следующую обрезку. + +- После того как сделан выбор `choice[i]` , мы присваиваем `selected[i]` значение $\text{True}$ , тем самым отмечая, что этот элемент уже выбран. +- При обходе списка вариантов `choices` пропускаем все уже выбранные элементы, то есть выполняем обрезку. + +Как показано на рисунке ниже, если в первом раунде мы выберем 1 , во втором - 3 , а в третьем - 2 , то во втором раунде нужно отсечь ветвь элемента 1 , а в третьем - ветви элементов 1 и 3 . + +![Пример обрезки в задаче о перестановках](permutations_problem.assets/permutations_i_pruning.png) + +Из рисунка видно, что такая обрезка уменьшает размер пространства поиска с $O(n^n)$ до $O(n!)$ . + +### Реализация кода + +После прояснения всей логики можно просто "заполнить пропуски" в шаблоне backtracking. Чтобы сократить общий объем кода, мы не будем отдельно реализовывать каждую функцию из каркаса, а раскроем их прямо внутри `backtrack()` : + +```src +[file]{permutations_i}-[class]{}-[func]{permutations_i} +``` + +## Учет равных элементов + +!!! question + + Дан массив целых чисел, **который может содержать повторяющиеся элементы**. Верните все неповторяющиеся перестановки. + +Пусть входной массив равен $[1, 1, 2]$ . Чтобы различать два одинаковых элемента $1$ , будем обозначать второй из них как $\hat{1}$ . + +Как показано на рисунке ниже, описанный выше метод создаст результат, половина которого окажется дублирующейся. + +![Повторяющиеся перестановки](permutations_problem.assets/permutations_ii.png) + +Как же убрать повторяющиеся перестановки? Самый прямолинейный способ - воспользоваться хеш-множеством и удалить дубликаты уже после генерации результата. Но это не слишком изящно, **потому что ветви поиска, порождающие дубликаты, вообще не нужно посещать: их следует распознавать заранее и отсекать**, что дополнительно повышает эффективность алгоритма. + +### Обрезка равных элементов + +Посмотрите на рисунок ниже: в первом раунде выбрать $1$ или выбрать $\hat{1}$ - это одно и то же, а значит, все перестановки, полученные из этих двух выборов, будут дублироваться. Поэтому ветвь $\hat{1}$ нужно отсечь. + +Точно так же, если в первом раунде выбрать $2$ , то во втором раунде выборы $1$ и $\hat{1}$ снова создадут дублирующиеся ветви, поэтому и в этом случае ветвь $\hat{1}$ нужно отсечь. + +По своей сути **наша цель заключается в том, чтобы на каждом раунде выбора каждый из нескольких равных элементов выбирался только один раз**. + +![Обрезка повторяющихся перестановок](permutations_problem.assets/permutations_ii_pruning.png) + +### Реализация кода + +На основе решения из предыдущей задачи можно на каждом раунде выбора заводить хеш-множество `duplicated` , которое будет записывать элементы, уже встречавшиеся в этом раунде, и отсекать повторы: + +```src +[file]{permutations_ii}-[class]{}-[func]{permutations_ii} +``` + +Если предположить, что все элементы попарно различны, то из $n$ элементов можно получить $n!$ перестановок; при записи результата требуется копировать список длины $n$ , что занимает $O(n)$ времени. **Следовательно, временная сложность равна $O(n!n)$** . + +Максимальная глубина рекурсии равна $n$ , что требует $O(n)$ стековой памяти. Массив `selected` занимает $O(n)$ пространства. Одновременно может существовать до $n$ хеш-множеств `duplicated` , что дает $O(n^2)$ памяти. **Следовательно, пространственная сложность равна $O(n^2)$** . + +### Сравнение двух видов обрезки + +Обратите внимание: хотя и `selected` , и `duplicated` используются для обрезки, их цели различаются. + +- **Обрезка повторного выбора**: во всем процессе поиска существует только один `selected` . Он записывает, какие элементы уже входят в текущее состояние, и нужен для того, чтобы один и тот же элемент не появлялся в `state` дважды. +- **Обрезка равных элементов**: каждый раунд выбора (каждый вызов `backtrack`) содержит собственный `duplicated` . Он записывает, какие элементы уже выбирались в текущем раунде (`for` цикле), и нужен для того, чтобы равные элементы выбирались только один раз. + +На рисунке ниже показана область действия двух условий обрезки. Помните, что каждый узел дерева соответствует одному выбору, а путь от корня до листа образует одну перестановку. + +![Область действия двух условий обрезки](permutations_problem.assets/permutations_ii_pruning_summary.png) diff --git a/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png new file mode 100644 index 000000000..40aa8e178 Binary files /dev/null and b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png differ diff --git a/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png new file mode 100644 index 000000000..506312390 Binary files /dev/null and b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png differ diff --git a/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png new file mode 100644 index 000000000..d7bf81894 Binary files /dev/null and b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png differ diff --git a/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png new file mode 100644 index 000000000..cd1d31813 Binary files /dev/null and b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png differ diff --git a/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png new file mode 100644 index 000000000..0136aad90 Binary files /dev/null and b/ru/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png differ diff --git a/ru/docs/chapter_backtracking/subset_sum_problem.md b/ru/docs/chapter_backtracking/subset_sum_problem.md new file mode 100644 index 000000000..5cffdc38c --- /dev/null +++ b/ru/docs/chapter_backtracking/subset_sum_problem.md @@ -0,0 +1,95 @@ +# Задача о сумме подмножеств + +## Случай без повторяющихся элементов + +!!! question + + Дан массив положительных целых чисел `nums` и целое положительное значение `target` . Найдите все возможные комбинации, сумма элементов которых равна `target` . Во входном массиве нет повторяющихся элементов, и каждый элемент можно выбирать неограниченное число раз. Верните эти комбинации в виде списка; в результате не должно быть повторяющихся комбинаций. + +Например, для входного множества $\{3, 4, 5\}$ и целевого значения $9$ решениями будут $\{3, 3, 3\}$ и $\{4, 5\}$ . При этом нужно обратить внимание на два обстоятельства. + +- Элементы входного множества можно выбирать повторно неограниченное число раз. +- Подмножество не различает порядок элементов, поэтому $\{4, 5\}$ и $\{5, 4\}$ считаются одним и тем же подмножеством. + +### Отталкиваемся от решения задачи о перестановках + +Как и в задаче о перестановках, можно представлять построение подмножеств как результат последовательности выборов и во время выбора динамически обновлять "сумму элементов"; когда эта сумма становится равной `target` , соответствующее подмножество записывается в список результатов. + +Однако в отличие от задачи о перестановках **в этой задаче элементы множества можно выбирать неограниченное число раз**, поэтому нам не нужен булев список `selected` для записи того, был ли выбран элемент. Можно слегка изменить код для перестановок и получить первоначальную версию решения: + +```src +[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} +``` + +Если подать на этот код массив $[3, 4, 5]$ и целевое значение $9$ , то на выходе мы получим $[3, 3, 3], [4, 5], [5, 4]$ . **Хотя все подмножества с суммой $9$ успешно найдены, среди них все же присутствуют дубликаты: $[4, 5]$ и $[5, 4]$** . + +Причина в том, что процесс поиска различает порядок выбора, тогда как для подмножеств порядок не важен. Как показано на рисунке ниже, сначала выбрать $4$ , а затем $5$ , и сначала выбрать $5$ , а затем $4$ - это разные ветви поиска, но им соответствует одно и то же подмножество. + +![Поиск подмножеств и обрезка по выходу за границу](subset_sum_problem.assets/subset_sum_i_naive.png) + +Чтобы убрать повторяющиеся подмножества, **одна из прямых идей - удалить дубликаты уже из итогового списка результатов**. Но это решение малоэффективно по двум причинам. + +- Когда массив содержит много элементов, а особенно когда `target` велик, процесс поиска порождает огромное число повторяющихся подмножеств. +- Сравнение подмножеств (то есть массивов) само по себе довольно затратно: сначала приходится сортировать массивы, а затем поэлементно сравнивать их. + +### Обрезка повторяющихся подмножеств + +**Поэтому стоит выполнять устранение дубликатов прямо во время поиска, с помощью обрезки**. Посмотрите на рисунок ниже: повторяющиеся подмножества возникают тогда, когда элементы массива выбираются в разном порядке, например так. + +1. Если в первом и втором раундах выбрать соответственно $3$ и $4$ , то будут сгенерированы все подмножества, содержащие эти два элемента, и их можно обозначить как $[3, 4, \dots]$ . +2. После этого, если в первом раунде выбрать $4$ , **то во втором раунде нужно пропустить $3$** , потому что подмножества $[4, 3, \dots]$ полностью дублируют подмножества, уже построенные на шаге `1.` . + +Во время поиска выборы на каждом уровне пробуются по одному слева направо, поэтому чем правее ветвь, тем больше ветвей оказывается отсечено. + +1. В первых двух раундах выбираются $3$ и $5$ , что дает подмножества $[3, 5, \dots]$ . +2. В первых двух раундах выбираются $4$ и $5$ , что дает подмножества $[4, 5, \dots]$ . +3. Если же в первом раунде выбрать $5$ , **то во втором раунде нужно пропустить $3$ и $4$** , потому что подмножества $[5, 3, \dots]$ и $[5, 4, \dots]$ полностью дублируют случаи, описанные в шагах `1.` и `2.` . + +![Повторяющиеся подмножества из-за разного порядка выбора](subset_sum_problem.assets/subset_sum_i_pruning.png) + +В общем виде, если входной массив имеет вид $[x_1, x_2, \dots, x_n]$ , а последовательность выборов в ходе поиска равна $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ , то она должна удовлетворять условию $i_1 \leq i_2 \leq \dots \leq i_m$ ; **все последовательности выборов, не удовлетворяющие этому условию, приводят к дубликатам и должны отсекаться**. + +### Реализация кода + +Чтобы реализовать такую обрезку, инициализируем переменную `start` , которая будет указывать начальную точку обхода. **После выбора элемента $x_i$ следующий раунд начинается с индекса $i$**. Благодаря этому последовательность выборов всегда удовлетворяет условию $i_1 \leq i_2 \leq \dots \leq i_m$ , а значит, каждое подмножество создается только один раз. + +Помимо этого, мы внесем в код еще два улучшения. + +- Перед началом поиска отсортируем массив `nums` . Тогда при обходе всех вариантов **можно сразу прервать цикл, как только сумма подмножества превысит `target`** , потому что все последующие элементы будут еще больше и их сумма тоже превысит `target` . +- Откажемся от отдельной переменной суммы `total` и **будем учитывать сумму через вычитание из `target`** ; когда `target` станет равным $0$ , решение фиксируется. + +```src +[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} +``` + +На рисунке ниже показан полный процесс backtracking для массива $[3, 4, 5]$ и целевого значения $9$ . + +![Процесс backtracking для задачи о сумме подмножеств I](subset_sum_problem.assets/subset_sum_i.png) + +## Учет повторяющихся элементов + +!!! question + + Дан массив положительных целых чисел `nums` и целое положительное значение `target` . Найдите все возможные комбинации, сумма элементов которых равна `target` . **Во входном массиве могут присутствовать повторяющиеся элементы, и каждый элемент разрешено выбирать только один раз**. Верните эти комбинации в виде списка; в результате не должно быть повторяющихся комбинаций. + +По сравнению с предыдущей задачей **во входном массиве теперь могут присутствовать повторяющиеся элементы**, и это создает новую проблему. Например, если дан массив $[4, \hat{4}, 5]$ и целевое значение $9$ , то существующий код вернет результат $[4, 5], [\hat{4}, 5]$ , то есть с повторяющимся подмножеством. + +**Причина появления дублей в том, что равные элементы выбираются несколько раз в одном и том же раунде**. На рисунке ниже в первом раунде существует три варианта выбора, и два из них равны $4$ ; из-за этого появляются две дублирующиеся ветви поиска и, соответственно, повторяющиеся подмножества. Точно так же два элемента $4$ во втором раунде тоже порождают дубликаты. + +![Повторяющиеся подмножества из-за равных элементов](subset_sum_problem.assets/subset_sum_ii_repeat.png) + +### Обрезка равных элементов + +Чтобы решить эту проблему, **нужно ограничить выбор равных элементов так, чтобы в каждом раунде каждый из них выбирался только один раз**. Реализуется это довольно изящно: поскольку массив отсортирован, равные элементы стоят рядом. Значит, если в текущем раунде текущий элемент равен соседнему слева, то этот вариант уже был рассмотрен, и текущий элемент нужно пропустить. + +Одновременно **по условию этой задачи каждый элемент массива можно выбрать только один раз**. К счастью, это ограничение тоже можно реализовать через переменную `start` : после выбора элемента $x_i$ следующий раунд начинается с индекса $i + 1$ . Так мы одновременно убираем повторяющиеся подмножества и исключаем повторный выбор одного и того же элемента. + +### Реализация кода + +```src +[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} +``` + +На рисунке ниже показан процесс backtracking для массива $[4, 4, 5]$ и целевого значения $9$ . В нем используются четыре вида обрезки. Попробуйте сопоставить рисунок с комментариями в коде, чтобы понять полный процесс поиска и то, как работает каждый тип обрезки. + +![Процесс backtracking для задачи о сумме подмножеств II](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/ru/docs/chapter_backtracking/summary.md b/ru/docs/chapter_backtracking/summary.md new file mode 100644 index 000000000..d2e13c84c --- /dev/null +++ b/ru/docs/chapter_backtracking/summary.md @@ -0,0 +1,23 @@ +# Резюме + +### Ключевые выводы + +- Алгоритм поиска с возвратом по своей сути является методом полного перебора: он ищет решения путем обхода пространства решений в глубину. Во время поиска он фиксирует решения, удовлетворяющие условиям, пока не найдет все такие решения или пока обход не завершится. +- Процесс backtracking состоит из двух частей: попытки и отката. Он с помощью поиска в глубину пробует разные варианты выбора; когда встречается состояние, не удовлетворяющее ограничениям, алгоритм отменяет предыдущий выбор, возвращается к прошлому состоянию и продолжает пробовать другие варианты. Попытка и откат являются двумя противоположными по направлению действиями. +- Задачи backtracking обычно содержат несколько ограничений, которые можно использовать для обрезки. Обрезка позволяет заранее завершать ненужные ветви поиска и тем самым значительно повышать эффективность. +- Алгоритм backtracking в первую очередь применяется для решения поисковых задач и задач с ограничениями. Задачи комбинаторной оптимизации тоже можно решать с его помощью, но для них часто существуют более эффективные или более подходящие методы. +- Задача о перестановках нацелена на поиск всех возможных перестановок элементов данного множества. Мы используем массив для записи того, был ли выбран каждый элемент, и отсекаем ветви, где один и тот же элемент выбирается повторно, чтобы гарантировать однократный выбор каждого элемента. +- В задаче о перестановках, если во множестве присутствуют повторяющиеся элементы, в итоговом результате возникнут повторяющиеся перестановки. Поэтому нужно ограничить выбор равных элементов так, чтобы в каждом раунде каждый из них выбирался только один раз; обычно это реализуется с помощью хеш-множества. +- Цель задачи о сумме подмножеств - найти все подмножества данного множества, сумма которых равна целевому значению. В множестве порядок элементов не важен, однако процесс поиска порождает результаты во всех возможных порядках, из-за чего появляются повторяющиеся подмножества. Поэтому перед запуском backtracking мы сортируем данные и вводим переменную, указывающую начальную точку обхода в каждом раунде, чтобы отсечь ветви, создающие дубликаты. +- В задаче о сумме подмножеств равные элементы массива также порождают повторяющиеся множества. При наличии предварительной сортировки их можно отсекать, проверяя равенство соседних элементов, и тем самым гарантировать, что в каждом раунде равные элементы будут выбираться только один раз. +- Задача о $n$ ферзях состоит в поиске способов разместить $n$ ферзей на доске размера $n \times n$ так, чтобы никакие два ферзя не атаковали друг друга. Ограничения этой задачи включают строки, столбцы, главные диагонали и побочные диагонали. Чтобы выполнить ограничение по строкам, используется построчная стратегия размещения, гарантирующая по одному ферзю в каждой строке. +- Обработка ограничений по столбцам и диагоналям устроена похожим образом. Для ограничения по столбцам используется массив, фиксирующий наличие ферзя в каждом столбце. Для диагоналей используются два массива, записывающие наличие ферзей на главных и побочных диагоналях. Основная сложность здесь состоит в том, чтобы найти закономерность индексов строк и столбцов клеток, лежащих на одной и той же главной или побочной диагонали. + +### Q & A + +**Q**: Как понять связь между поиском с возвратом и рекурсией? + +В целом backtracking - это скорее "алгоритмическая стратегия", а рекурсия больше похожа на "инструмент". + +- Алгоритмы поиска с возвратом обычно реализуются на основе рекурсии. Однако backtracking - это лишь один из вариантов применения рекурсии, а именно ее использование в поисковых задачах. +- Структура рекурсии отражает парадигму разбиения на подзадачи и часто применяется для решения задач divide and conquer, backtracking, динамического программирования (мемоизированной рекурсии) и других подобных задач. diff --git a/ru/docs/chapter_computational_complexity/index.md b/ru/docs/chapter_computational_complexity/index.md new file mode 100644 index 000000000..b4f1cd83c --- /dev/null +++ b/ru/docs/chapter_computational_complexity/index.md @@ -0,0 +1,9 @@ +# Анализ сложности + +![Анализ сложности](../assets/covers/chapter_complexity_analysis.jpg) + +!!! abstract + + Анализ сложности подобен пространственно-временному проводнику в огромной вселенной алгоритмов. + + Он ведет нас вглубь двух измерений - времени и пространства, помогая искать более изящные решения. diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png new file mode 100644 index 000000000..8d0f79806 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png differ diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png new file mode 100644 index 000000000..963586dbf Binary files /dev/null and b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png differ diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png new file mode 100644 index 000000000..277d53304 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png differ diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png new file mode 100644 index 000000000..f94ce8357 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png differ diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png new file mode 100644 index 000000000..acb42dae0 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png differ diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png new file mode 100644 index 000000000..09a325aed Binary files /dev/null and b/ru/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png differ diff --git a/ru/docs/chapter_computational_complexity/iteration_and_recursion.md b/ru/docs/chapter_computational_complexity/iteration_and_recursion.md new file mode 100644 index 000000000..f52ed039c --- /dev/null +++ b/ru/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -0,0 +1,194 @@ +# Итерация и рекурсия + +В алгоритмах очень часто приходится многократно выполнять одну и ту же задачу, и это тесно связано с анализом сложности. Поэтому, прежде чем переходить к временной и пространственной сложности, давай сначала разберемся, как в программах организуется повторяющееся выполнение задач, то есть с двумя базовыми управляющими структурами: итерацией и рекурсией. + +## Итерация + +Итерация (iteration) - это управляющая структура, предназначенная для многократного выполнения некоторой задачи. При итерации программа повторно выполняет определенный фрагмент кода при соблюдении некоторого условия, пока это условие не перестанет выполняться. + +### Цикл for + +Цикл `for` - одна из самых распространенных форм итерации, **она хорошо подходит в тех случаях, когда число повторений известно заранее**. + +Следующая функция реализует вычисление суммы $1 + 2 + \dots + n$ на основе цикла `for` , а результат сохраняется в переменной `res` . Обрати внимание, что в Python `range(a, b)` соответствует "лево-замкнутому, право-открытому" интервалу, то есть перебираются значения $a, a + 1, \dots, b-1$ : + +```src +[file]{iteration}-[class]{}-[func]{for_loop} +``` + +На рисунке ниже показана блок-схема этой функции суммирования. + +![Блок-схема функции суммирования](iteration_and_recursion.assets/iteration.png) + +Число операций в этой функции суммирования пропорционально размеру входных данных $n$ , то есть между ними существует "линейная зависимость". На самом деле **временная сложность как раз и описывает такую "линейную зависимость"**. Соответствующий материал будет подробно разобран в следующем разделе. + +### Цикл while + +Подобно циклу `for` , цикл `while` тоже является способом реализации итерации. В цикле `while` программа в каждом раунде сначала проверяет условие: если условие истинно, выполнение продолжается, иначе цикл завершается. + +Ниже мы используем цикл `while` для реализации суммы $1 + 2 + \dots + n$ : + +```src +[file]{iteration}-[class]{}-[func]{while_loop} +``` + +**Цикл `while` обладает большей свободой, чем цикл `for` **. В цикле `while` мы можем свободно задавать шаги инициализации и обновления условной переменной. + +Например, в следующем коде условная переменная $i$ обновляется два раза за один проход, и такой случай уже не слишком удобно выражать через цикл `for` : + +```src +[file]{iteration}-[class]{}-[func]{while_loop_ii} +``` + +В целом **код с `for` обычно компактнее, а `while` более гибок**; обе конструкции позволяют реализовывать итерационные структуры. Выбор между ними должен определяться требованиями конкретной задачи. + +### Вложенные циклы + +Мы можем вкладывать одну циклическую структуру в другую; ниже показан пример на основе цикла `for` : + +```src +[file]{iteration}-[class]{}-[func]{nested_for_loop} +``` + +На рисунке ниже показана блок-схема такого вложенного цикла. + +![Блок-схема вложенного цикла](iteration_and_recursion.assets/nested_iteration.png) + +В этом случае число операций функции пропорционально $n^2$ , то есть время работы алгоритма и размер входных данных $n$ находятся в "квадратичной зависимости". + +Мы можем продолжать добавлять вложенные циклы, и каждое новое вложение будет означать очередное "повышение размерности", увеличивая временную сложность до "кубической зависимости", "зависимости четвертой степени" и так далее. + +## Рекурсия + + Рекурсия (recursion) - это алгоритмическая стратегия, в которой функция решает задачу, вызывая саму себя. В основном она включает две фазы. + +1. **Спуск**: программа все глубже вызывает саму себя, обычно передавая меньшие или более упрощенные параметры, пока не достигнет "условия завершения". +2. **Подъем**: после срабатывания "условия завершения" программа начинает возвращаться от самой глубокой рекурсивной функции вверх, собирая результаты с каждого уровня. + +С точки зрения реализации рекурсивный код в основном состоит из трех элементов. + +1. **Условие завершения**: определяет момент перехода от "спуска" к "подъему". +2. **Рекурсивный вызов**: соответствует "спуску", когда функция вызывает саму себя, обычно с меньшими или более упрощенными параметрами. +3. **Возврат результата**: соответствует "подъему", когда результат текущего уровня рекурсии передается предыдущему. + +Посмотри на следующий код: нам достаточно вызвать функцию `recur(n)` , чтобы вычислить $1 + 2 + \dots + n$ : + +```src +[file]{recursion}-[class]{}-[func]{recur} +``` + +На рисунке ниже показан рекурсивный процесс этой функции. + +![Рекурсивный процесс функции суммирования](iteration_and_recursion.assets/recursion_sum.png) + +Хотя с вычислительной точки зрения итерация и рекурсия могут давать один и тот же результат, **они представляют собой две совершенно разные парадигмы мышления и решения задач**. + +- **Итерация**: решает задачу "снизу вверх". Мы начинаем с самых базовых шагов, а затем многократно повторяем или накапливаем их, пока задача не будет завершена. +- **Рекурсия**: решает задачу "сверху вниз". Исходная задача разбивается на более мелкие подзадачи той же формы. Затем эти подзадачи продолжают разбиваться еще дальше, пока не будет достигнут базовый случай (для которого решение уже известно). + +Возьмем в качестве примера указанную выше функцию суммирования и обозначим задачу как $f(n) = 1 + 2 + \dots + n$ . + +- **Итерация**: в цикле моделируется процесс суммирования от $1$ до $n$ , и на каждом шаге выполняется операция сложения, в результате чего получается $f(n)$ . +- **Рекурсия**: задача раскладывается на подзадачу $f(n) = n + f(n-1)$ , а затем продолжает раскладываться (рекурсивно) до базового случая $f(1) = 1$ . + +### Стек вызовов + +Каждый раз, когда рекурсивная функция вызывает сама себя, система выделяет память для нового экземпляра функции, чтобы хранить локальные переменные, адрес возврата и другую информацию. Это приводит к двум последствиям. + +- Контекстные данные функции хранятся в области памяти, называемой "пространством кадра стека", и освобождаются только после возврата функции. Поэтому **рекурсия обычно требует больше памяти, чем итерация**. +- Вызов рекурсивной функции создает дополнительный накладной расход. **Поэтому рекурсия обычно уступает циклам по временной эффективности**. + +Как показано на рисунке ниже, до срабатывания условия завершения одновременно существует $n$ еще не завершившихся рекурсивных вызовов, а **глубина рекурсии равна $n$** . + +![Глубина рекурсивного вызова](iteration_and_recursion.assets/recursion_sum_depth.png) + +На практике разрешенная языком программирования глубина рекурсии обычно ограничена, и слишком глубокая рекурсия может привести к ошибке переполнения стека. + +### Хвостовая рекурсия + +Интересно, что **если функция выполняет рекурсивный вызов в самом последнем действии перед возвратом** , то компилятор или интерпретатор может оптимизировать такую функцию так, чтобы по использованию памяти она была сопоставима с итерацией. Такой случай называется хвостовой рекурсией (tail recursion). + +- **Обычная рекурсия**: когда функция возвращается на предыдущий уровень, ей все еще нужно продолжать выполнять код, поэтому системе приходится сохранять контекст вызова предыдущего уровня. +- **Хвостовая рекурсия**: рекурсивный вызов - это последняя операция перед возвратом, а значит, после возвращения на предыдущий уровень не требуется выполнять дополнительных действий, и системе не нужно сохранять контекст предыдущей функции. + +На примере вычисления $1 + 2 + \dots + n$ можно сделать переменную результата `res` параметром функции и тем самым реализовать хвостовую рекурсию: + +```src +[file]{recursion}-[class]{}-[func]{tail_recur} +``` + +Процесс выполнения хвостовой рекурсии показан на рисунке ниже. Если сравнить обычную рекурсию и хвостовую рекурсию, то видно, что точка выполнения операции суммирования у них различается. + +- **Обычная рекурсия**: операция суммирования выполняется в процессе "подъема", то есть после возврата с каждого уровня еще нужно выполнить очередное сложение. +- **Хвостовая рекурсия**: операция суммирования выполняется в процессе "спуска", а сам "подъем" сводится лишь к последовательному возврату. + +![Процесс хвостовой рекурсии](iteration_and_recursion.assets/tail_recursion_sum.png) + +!!! tip + + Обрати внимание: многие компиляторы и интерпретаторы не поддерживают оптимизацию хвостовой рекурсии. Например, Python по умолчанию такую оптимизацию не выполняет, поэтому даже функция в хвостово-рекурсивной форме все равно может привести к переполнению стека. + +### Дерево рекурсии + +При решении алгоритмических задач, связанных с "разделяй и властвуй", рекурсия часто дает более интуитивный способ рассуждения и более читаемый код, чем итерация. Возьмем в качестве примера "последовательность Фибоначчи". + +!!! question + + Дана последовательность Фибоначчи $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ; найди $n$-й элемент этой последовательности. + +Обозначим $n$-й элемент последовательности Фибоначчи как $f(n)$ . Тогда нетрудно получить два вывода. + +- Первые два числа последовательности равны $f(1) = 0$ и $f(2) = 1$ . +- Каждое последующее число равно сумме двух предыдущих, то есть $f(n) = f(n - 1) + f(n - 2)$ . + +Следуя рекуррентному соотношению и используя первые два числа как условия завершения, мы можем написать рекурсивный код. Вызов `fib(n)` даст нам $n$-й элемент последовательности Фибоначчи: + +```src +[file]{recursion}-[class]{}-[func]{fib} +``` + +Если посмотреть на приведенный код, внутри функции выполняются два рекурсивных вызова, **а это означает, что один вызов рождает две ветви вызова**. Как показано на рисунке ниже, при таком продолжении рекурсивных вызовов в итоге получается дерево рекурсии (recursion tree) глубиной $n$ . + +![Дерево рекурсии последовательности Фибоначчи](iteration_and_recursion.assets/recursion_tree.png) + +По своей сути рекурсия воплощает парадигму "разбиения задачи на более мелкие подзадачи", и именно поэтому стратегия разделяй-и-властвуй столь важна. + +- С точки зрения алгоритмов многие важнейшие стратегии, такие как поиск, сортировка, бэктрекинг, разделяй-и-властвуй и динамическое программирование, прямо или косвенно используют такой образ мышления. +- С точки зрения структур данных рекурсия естественным образом подходит для решения задач, связанных со связными списками, деревьями и графами, потому что они хорошо поддаются анализу через идеи разделения задачи. + +## Сравнение двух подходов + +Обобщая все сказанное выше, можно представить различия между итерацией и рекурсией с точки зрения реализации, производительности и применимости в следующей таблице. + +

Таблица   Сравнение характеристик итерации и рекурсии

+ +| | Итерация | Рекурсия | +| -------- | -------------------------------------- | ------------------------------------------------------------ | +| Реализация | Циклическая структура | Функция вызывает сама себя | +| Временная эффективность | Обычно выше, так как нет накладных расходов на вызовы функций | Каждый вызов функции создает накладные расходы | +| Использование памяти | Обычно требуется фиксированный объем памяти | Накопление вызовов функции может занимать много места в кадрах стека | +| Подходящие задачи | Хорошо подходит для простых циклических задач, код интуитивен и легко читается | Хорошо подходит для разложения на подзадачи, например для деревьев, графов, разделяй-и-властвуй, бэктрекинга и т. д.; код при этом получается компактным и ясным | + +!!! tip + + Если тебе сложно понять дальнейшее содержание, можешь вернуться к нему после чтения главы о "стеке". + +Какова же внутренняя связь между итерацией и рекурсией? Если снова взять рекурсивную функцию выше, операция суммирования выполняется в фазе "подъема" рекурсии. Это означает, что функция, вызванная первой, на самом деле завершает сложение последней, **и такой механизм очень похож на принцип стека "последним пришел - первым ушел"**. + +На самом деле такие термины рекурсии, как "стек вызовов" и "пространство кадра стека", уже прямо намекают на тесную связь между рекурсией и стеком. + +1. **Спуск**: когда вызывается функция, система выделяет для нее новый кадр стека в "стеке вызовов", чтобы хранить локальные переменные, параметры, адрес возврата и другие данные. +2. **Подъем**: когда функция завершает выполнение и возвращается, соответствующий кадр стека удаляется из "стека вызовов", а среда выполнения предыдущей функции восстанавливается. + +Поэтому **мы можем использовать явный стек для имитации поведения стека вызовов** и тем самым преобразовать рекурсию в итеративную форму: + +```src +[file]{recursion}-[class]{}-[func]{for_loop_recur} +``` + +Если посмотреть на приведенный выше код, видно, что после преобразования рекурсии в итерацию код становится сложнее. Хотя во многих случаях итерация и рекурсия действительно могут быть преобразованы друг в друга, это не всегда стоит делать по двум причинам. + +- Преобразованный код может стать труднее для понимания и менее читаемым. +- Для некоторых сложных задач имитация поведения системного стека вызовов может оказаться очень трудной. + +Итак, **выбор между итерацией и рекурсией зависит от природы конкретной задачи**. В практическом программировании крайне важно взвешивать плюсы и минусы обоих подходов и выбирать подходящий метод с учетом контекста. diff --git a/ru/docs/chapter_computational_complexity/performance_evaluation.md b/ru/docs/chapter_computational_complexity/performance_evaluation.md new file mode 100644 index 000000000..39740e00e --- /dev/null +++ b/ru/docs/chapter_computational_complexity/performance_evaluation.md @@ -0,0 +1,49 @@ +# Оценка эффективности алгоритмов + +При проектировании алгоритмов мы последовательно стремимся к двум уровням целей. + +1. **Найти решение задачи**: алгоритм должен надежно получать правильный ответ в заданном диапазоне входных данных. +2. **Найти оптимальное решение**: для одной и той же задачи может существовать несколько решений, и нам хочется выбрать максимально эффективный алгоритм. + +Иными словами, если задача в принципе решается, эффективность алгоритма становится главным критерием оценки его качества. Она включает два следующих измерения. + +- **Временная эффективность**: сколько времени работает алгоритм. +- **Пространственная эффективность**: сколько памяти занимает алгоритм. + +Короче говоря, **наша цель - проектировать структуры данных и алгоритмы, которые "и быстры, и экономны по памяти"**. Эффективная оценка алгоритмов крайне важна, потому что только так можно сравнивать разные алгоритмы и направлять процесс их проектирования и оптимизации. + +Методы оценки эффективности в основном делятся на два типа: практическое тестирование и теоретическая оценка. + +## Практическое тестирование + +Предположим, у нас есть алгоритм `A` и алгоритм `B`, оба решают одну и ту же задачу, и нам нужно сравнить их эффективность. Самый прямой способ - взять компьютер, запустить оба алгоритма и зафиксировать время работы и объем используемой памяти. Такой способ оценки отражает реальную ситуацию, но имеет и серьезные ограничения. + +С одной стороны, **трудно исключить влияние факторов тестовой среды**. Аппаратная конфигурация влияет на производительность алгоритма. Например, если алгоритм имеет высокий уровень параллелизма, он лучше подходит для многоядерных CPU; если алгоритм интенсивно работает с памятью, он покажет себя лучше на быстрой памяти. Иными словами, результаты тестирования одного и того же алгоритма на разных машинах могут различаться. Это означает, что пришлось бы тестировать на самых разных машинах и усреднять результаты, а на практике это нереалистично. + +С другой стороны, **полное тестирование требует больших ресурсов**. По мере изменения объема входных данных алгоритм может вести себя по-разному. Например, при небольшом объеме входных данных время работы алгоритма `A` может быть меньше, чем у алгоритма `B`; но при большом объеме результаты могут оказаться прямо противоположными. Поэтому для убедительных выводов пришлось бы тестировать входные данные множества разных масштабов, а это требует значительных вычислительных ресурсов. + +## Теоретическая оценка + +Поскольку практическое тестирование имеет серьезные ограничения, можно попытаться оценить эффективность алгоритма только с помощью вычислений. Такой метод называется асимптотическим анализом сложности (asymptotic complexity analysis), или сокращенно анализом сложности. + +Анализ сложности показывает зависимость между временем и пространственными ресурсами, требуемыми алгоритму, и масштабом входных данных. **Он описывает тенденцию роста времени и памяти, необходимых алгоритму, по мере увеличения размера входных данных**. Это определение звучит немного тяжеловесно, поэтому полезно разложить его на три ключевые идеи. + +- "Временные и пространственные ресурсы" соответствуют временной сложности (time complexity) и пространственной сложности (space complexity) соответственно. +- "По мере увеличения размера входных данных" означает, что сложность отражает связь между эффективностью алгоритма и масштабом входа. +- "Тенденция роста времени и пространства" означает, что анализ сложности интересуется не конкретными значениями времени или памяти, а тем, насколько быстро они растут. + +**Анализ сложности устраняет недостатки практического тестирования**, что проявляется в следующих аспектах. + +- Для него не нужно реально запускать код, а значит, он экологичнее и экономит ресурсы. +- Он не зависит от тестовой среды, поэтому результаты анализа применимы ко всем платформам выполнения. +- Он позволяет увидеть эффективность алгоритма при разных объемах данных, особенно на больших данных. + +!!! tip + + Если понятие сложности пока все еще кажется тебе запутанным, не переживай: мы подробно разберем его в следующих разделах. + +Анализ сложности дает нам "линейку" для оценки эффективности алгоритмов, позволяя измерять, сколько времени и памяти требуется для выполнения конкретного алгоритма, и сравнивать эффективность разных алгоритмов между собой. + +Сложность - это математическое понятие, поэтому для начинающих оно может показаться довольно абстрактным и сравнительно трудным. С этой точки зрения анализ сложности, возможно, не лучший самый первый материал для знакомства. Однако, когда мы обсуждаем особенности конкретной структуры данных или алгоритма, почти невозможно не затронуть скорость его работы и использование памяти. + +В итоге рекомендуется еще до глубокого погружения в структуры данных и алгоритмы **сформировать хотя бы первичное понимание анализа сложности, чтобы уметь выполнять анализ сложности простых алгоритмов**. diff --git a/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png new file mode 100644 index 000000000..ccd2d6aec Binary files /dev/null and b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png differ diff --git a/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png new file mode 100644 index 000000000..991f58386 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png differ diff --git a/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png new file mode 100644 index 000000000..756214bcd Binary files /dev/null and b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png differ diff --git a/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png new file mode 100644 index 000000000..32ddbaa5f Binary files /dev/null and b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png differ diff --git a/ru/docs/chapter_computational_complexity/space_complexity.assets/space_types.png b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_types.png new file mode 100644 index 000000000..92b3a5c12 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/space_complexity.assets/space_types.png differ diff --git a/ru/docs/chapter_computational_complexity/space_complexity.md b/ru/docs/chapter_computational_complexity/space_complexity.md new file mode 100644 index 000000000..4096e333e --- /dev/null +++ b/ru/docs/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,880 @@ +# Пространственная сложность + +Пространственная сложность (space complexity) используется для оценки того, как меняется объем памяти, занимаемой алгоритмом, по мере роста объема данных. Это понятие очень похоже на временную сложность, только вместо "времени выполнения" мы рассматриваем "объем используемой памяти". + +## Пространство, связанное с алгоритмом + +Память, которую использует алгоритм во время работы, в основном включает несколько следующих частей. + +- **Входное пространство**: используется для хранения входных данных алгоритма. +- **Временное пространство**: используется для хранения переменных, объектов, контекста функций и других данных, возникающих во время выполнения алгоритма. +- **Выходное пространство**: используется для хранения выходных данных алгоритма. + +В общем случае при анализе пространственной сложности в расчет включают "временное пространство" и "выходное пространство". + +Временное пространство можно дополнительно разделить на три части. + +- **Временные данные**: используются для хранения различных констант, переменных, объектов и т.д., возникающих во время выполнения алгоритма. +- **Пространство кадров стека**: используется для хранения контекстных данных вызываемых функций. Система при каждом вызове функции создает на вершине стека новый кадр; после возврата функции пространство этого кадра освобождается. +- **Пространство инструкций**: используется для хранения скомпилированных инструкций программы и в реальном подсчете обычно не учитывается. + +При анализе пространственной сложности программы **мы обычно учитываем три части: временные данные, пространство кадров стека и выходные данные**, как показано на рисунке ниже. + +![Пространство, используемое алгоритмом](space_complexity.assets/space_types.png) + +Соответствующий код выглядит следующим образом: + +=== "Python" + + ```python title="" + class Node: + """Класс""" + def __init__(self, x: int): + self.val: int = x # Значение узла + self.next: Node | None = None # Ссылка на следующий узел + + def function() -> int: + """Функция""" + # Выполнить некоторые операции... + return 0 + + def algorithm(n) -> int: # Входные данные + A = 0 # Временные данные (константа, обычно обозначается заглавной буквой) + b = 0 # Временные данные (переменная) + node = Node(0) # Временные данные (объект) + c = function() # Пространство кадра стека (вызов функции) + return A + b + c # Выходные данные + ``` + +=== "C++" + + ```cpp title="" + /* Структура */ + struct Node { + int val; + Node *next; + Node(int x) : val(x), next(nullptr) {} + }; + + /* Функция */ + int func() { + // Выполнить некоторые операции... + return 0; + } + + int algorithm(int n) { // Входные данные + const int a = 0; // Временные данные (константа) + int b = 0; // Временные данные (переменная) + Node* node = new Node(0); // Временные данные (объект) + int c = func(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "Java" + + ```java title="" + /* Класс */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* Функция */ + int function() { + // Выполнить некоторые операции... + return 0; + } + + int algorithm(int n) { // Входные данные + final int a = 0; // Временные данные (константа) + int b = 0; // Временные данные (переменная) + Node node = new Node(0); // Временные данные (объект) + int c = function(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "C#" + + ```csharp title="" + /* Класс */ + class Node(int x) { + int val = x; + Node next; + } + + /* Функция */ + int Function() { + // Выполнить некоторые операции... + return 0; + } + + int Algorithm(int n) { // Входные данные + const int a = 0; // Временные данные (константа) + int b = 0; // Временные данные (переменная) + Node node = new(0); // Временные данные (объект) + int c = Function(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "Go" + + ```go title="" + /* Структура */ + type node struct { + val int + next *node + } + + /* Создать структуру node */ + func newNode(val int) *node { + return &node{val: val} + } + + /* Функция */ + func function() int { + // Выполнить некоторые операции... + return 0 + } + + func algorithm(n int) int { // Входные данные + const a = 0 // Временные данные (константа) + b := 0 // Временные данные (переменная) + newNode(0) // Временные данные (объект) + c := function() // Пространство кадра стека (вызов функции) + return a + b + c // Выходные данные + } + ``` + +=== "Swift" + + ```swift title="" + /* Класс */ + class Node { + var val: Int + var next: Node? + + init(x: Int) { + val = x + } + } + + /* Функция */ + func function() -> Int { + // Выполнить некоторые операции... + return 0 + } + + func algorithm(n: Int) -> Int { // Входные данные + let a = 0 // Временные данные (константа) + var b = 0 // Временные данные (переменная) + let node = Node(x: 0) // Временные данные (объект) + let c = function() // Пространство кадра стека (вызов функции) + return a + b + c // Выходные данные + } + ``` + +=== "JS" + + ```javascript title="" + /* Класс */ + class Node { + val; + next; + constructor(val) { + this.val = val === undefined ? 0 : val; // Значение узла + this.next = null; // Ссылка на следующий узел + } + } + + /* Функция */ + function constFunc() { + // Выполнить некоторые операции + return 0; + } + + function algorithm(n) { // Входные данные + const a = 0; // Временные данные (константа) + let b = 0; // Временные данные (переменная) + const node = new Node(0); // Временные данные (объект) + const c = constFunc(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "TS" + + ```typescript title="" + /* Класс */ + class Node { + val: number; + next: Node | null; + constructor(val?: number) { + this.val = val === undefined ? 0 : val; // Значение узла + this.next = null; // Ссылка на следующий узел + } + } + + /* Функция */ + function constFunc(): number { + // Выполнить некоторые операции + return 0; + } + + function algorithm(n: number): number { // Входные данные + const a = 0; // Временные данные (константа) + let b = 0; // Временные данные (переменная) + const node = new Node(0); // Временные данные (объект) + const c = constFunc(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "Dart" + + ```dart title="" + /* Класс */ + class Node { + int val; + Node next; + Node(this.val, [this.next]); + } + + /* Функция */ + int function() { + // Выполнить некоторые операции... + return 0; + } + + int algorithm(int n) { // Входные данные + const int a = 0; // Временные данные (константа) + int b = 0; // Временные данные (переменная) + Node node = Node(0); // Временные данные (объект) + int c = function(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Структура */ + struct Node { + val: i32, + next: Option>>, + } + + /* Создать структуру Node */ + impl Node { + fn new(val: i32) -> Self { + Self { val: val, next: None } + } + } + + /* Функция */ + fn function() -> i32 { + // Выполнить некоторые операции... + return 0; + } + + fn algorithm(n: i32) -> i32 { // Входные данные + const a: i32 = 0; // Временные данные (константа) + let mut b = 0; // Временные данные (переменная) + let node = Node::new(0); // Временные данные (объект) + let c = function(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "C" + + ```c title="" + /* Функция */ + int func() { + // Выполнить некоторые операции... + return 0; + } + + int algorithm(int n) { // Входные данные + const int a = 0; // Временные данные (константа) + int b = 0; // Временные данные (переменная) + int c = func(); // Пространство кадра стека (вызов функции) + return a + b + c; // Выходные данные + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Класс */ + class Node(var _val: Int) { + var next: Node? = null + } + + /* Функция */ + fun function(): Int { + // Выполнить некоторые операции... + return 0 + } + + fun algorithm(n: Int): Int { // Входные данные + val a = 0 // Временные данные (константа) + var b = 0 // Временные данные (переменная) + val node = Node(0) // Временные данные (объект) + val c = function() // Пространство кадра стека (вызов функции) + return a + b + c // Выходные данные + } + ``` + +=== "Ruby" + + ```ruby title="" + ### Класс ### + class Node + attr_accessor :val # Значение узла + attr_accessor :next # Ссылка на следующий узел + + def initialize(x) + @val = x + end + end + + ### Функция ### + def function + # Выполнить некоторые операции... + 0 + end + + ### Алгоритм ### + def algorithm(n) # Входные данные + a = 0 # Временные данные (константа) + b = 0 # Временные данные (переменная) + node = Node.new(0) # Временные данные (объект) + c = function # Пространство кадра стека (вызов функции) + a + b + c # Выходные данные + end + ``` + +## Метод вывода + +Метод вывода пространственной сложности в целом аналогичен временному анализу: меняется только объект подсчета, с "количества операций" на "размер используемого пространства". + +В отличие от временной сложности, **обычно мы рассматриваем только худшую пространственную сложность**. Это связано с тем, что память является жестким ограничением: нам нужно гарантировать, что для любых входных данных у программы будет достаточно памяти. + +Рассмотрим следующий код. Слово "худшая" в "худшей пространственной сложности" имеет два значения. + +1. **Ориентир на худшие входные данные**: когда $n < 10$ , пространственная сложность равна $O(1)$ ; но когда $n > 10$ , инициализированный массив `nums` занимает $O(n)$ пространства, поэтому худшая пространственная сложность равна $O(n)$ . +2. **Ориентир на пиковое потребление памяти во время выполнения алгоритма**: например, до выполнения последней строки программа занимает $O(1)$ пространства; при инициализации массива `nums` она занимает $O(n)$ пространства, поэтому худшая пространственная сложность равна $O(n)$ . + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 0; // O(1) + vector b(10000); // O(1) + if (n > 10) + vector nums(n); // O(n) + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) + int[] nums = new int[n]; // O(n) + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) { + int[] nums = new int[n]; // O(n) + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 0 // O(1) + b := make([]int, 10000) // O(1) + var nums []int + if n > 10 { + nums := make([]int, n) // O(n) + } + fmt.Println(a, b, nums) + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + let a = 0 // O(1) + let b = Array(repeating: 0, count: 10000) // O(1) + if n > 10 { + let nums = Array(repeating: 0, count: n) // O(n) + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 0; // O(1) + List b = List.filled(10000, 0); // O(1) + if (n > 10) { + List nums = List.filled(n, 0); // O(n) + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let a = 0; // O(1) + let b = [0; 10000]; // O(1) + if n > 10 { + let nums = vec![0; n as usize]; // O(n) + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 0; // O(1) + int b[10000]; // O(1) + if (n > 10) + int nums[n] = {0}; // O(n) + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + val a = 0 // O(1) + val b = IntArray(10000) // O(1) + if (n > 10) { + val nums = IntArray(n) // O(n) + } + } + ``` + +=== "Ruby" + + ```ruby title="" + def algorithm(n) + a = 0 # O(1) + b = Array.new(10000) # O(1) + nums = Array.new(n) if n > 10 # O(n) + end + ``` + +**В рекурсивных функциях необходимо учитывать пространство кадров стека**. Рассмотрим следующий код: + +=== "Python" + + ```python title="" + def function() -> int: + # Выполнить некоторые операции + return 0 + + def loop(n: int): + """Пространственная сложность цикла равна O(1)""" + for _ in range(n): + function() + + def recur(n: int): + """Пространственная сложность рекурсии равна O(n)""" + if n == 1: + return + return recur(n - 1) + ``` + +=== "C++" + + ```cpp title="" + int func() { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + void recur(int n) { + if (n == 1) return; + recur(n - 1); + } + ``` + +=== "Java" + + ```java title="" + int function() { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + void recur(int n) { + if (n == 1) return; + recur(n - 1); + } + ``` + +=== "C#" + + ```csharp title="" + int Function() { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + void Loop(int n) { + for (int i = 0; i < n; i++) { + Function(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + int Recur(int n) { + if (n == 1) return 1; + return Recur(n - 1); + } + ``` + +=== "Go" + + ```go title="" + func function() int { + // Выполнить некоторые операции + return 0 + } + + /* Пространственная сложность цикла равна O(1) */ + func loop(n int) { + for i := 0; i < n; i++ { + function() + } + } + + /* Пространственная сложность рекурсии равна O(n) */ + func recur(n int) { + if n == 1 { + return + } + recur(n - 1) + } + ``` + +=== "Swift" + + ```swift title="" + @discardableResult + func function() -> Int { + // Выполнить некоторые операции + return 0 + } + + /* Пространственная сложность цикла равна O(1) */ + func loop(n: Int) { + for _ in 0 ..< n { + function() + } + } + + /* Пространственная сложность рекурсии равна O(n) */ + func recur(n: Int) { + if n == 1 { + return + } + recur(n: n - 1) + } + ``` + +=== "JS" + + ```javascript title="" + function constFunc() { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + function loop(n) { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + function recur(n) { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "TS" + + ```typescript title="" + function constFunc(): number { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + function loop(n: number): void { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + function recur(n: number): void { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "Dart" + + ```dart title="" + int function() { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + void recur(int n) { + if (n == 1) return; + recur(n - 1); + } + ``` + +=== "Rust" + + ```rust title="" + fn function() -> i32 { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + fn loop(n: i32) { + for i in 0..n { + function(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + fn recur(n: i32) { + if n == 1 { + return; + } + recur(n - 1); + } + ``` + +=== "C" + + ```c title="" + int func() { + // Выполнить некоторые операции + return 0; + } + /* Пространственная сложность цикла равна O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* Пространственная сложность рекурсии равна O(n) */ + void recur(int n) { + if (n == 1) return; + recur(n - 1); + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun function(): Int { + // Выполнить некоторые операции + return 0 + } + /* Пространственная сложность цикла равна O(1) */ + fun loop(n: Int) { + for (i in 0..Функция (function) может выполняться независимо, и все ее параметры передаются явно. Метод (method) связан с объектом, неявно получает объект, который его вызывает, и может работать с данными, содержащимися в экземпляре класса. + +Ниже это проиллюстрировано на примере нескольких распространенных языков программирования. + +- C - процедурный язык программирования без объектно-ориентированной модели, поэтому в нем есть только функции. Однако мы можем имитировать объектно-ориентированное программирование через структуры (`struct`), и функции, связанные со структурами, эквивалентны методам в других языках. +- Java и C# - объектно-ориентированные языки программирования, в которых блоки кода (методы) обычно являются частью класса. Статические методы по поведению похожи на функции, потому что они привязаны к классу и не могут обращаться к конкретным переменным экземпляра. +- C++ и Python поддерживают как процедурное программирование (функции), так и объектно-ориентированное программирование (методы). + +**Q**: Отражает ли диаграмма "распространенных типов пространственной сложности" абсолютный размер занятой памяти? + +Нет, эта диаграмма показывает пространственную сложность, а значит отражает именно тенденцию роста, а не абсолютный объем занятого пространства. + +Если взять $n = 8$ , можно заметить, что значения на кривых не совпадают напрямую с соответствующими функциями. Это связано с тем, что каждая кривая содержит константный член, который сжимает диапазон значений до визуально удобного масштаба. + +На практике, поскольку мы обычно не знаем, какова "константная" сложность каждого метода, только по сложности мы, как правило, не можем выбрать оптимальное решение для случая $n = 8$ . Но для $n = 8^5$ выбор уже очевиден: в этой области доминирует именно тенденция роста. + +**Q**: Бывают ли случаи, когда в реальных сценариях алгоритм специально проектируют так, чтобы жертвовать временем ради пространства или пространством ради времени? + +На практике в большинстве случаев выбирают обмен пространства на время. Например, для индексов в базах данных обычно строят B+ деревья или хеш-индексы, расходуя значительный объем памяти ради эффективных запросов уровня $O(\log n)$ или даже $O(1)$. + +В сценариях, где память особенно дорога, наоборот, могут жертвовать временем ради пространства. Например, в embedded-разработке память устройства очень ограничена, поэтому инженеры могут отказаться от хеш-таблиц и выбрать последовательный поиск по массиву, экономя память ценой более медленного поиска. diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png new file mode 100644 index 000000000..ea6127c3d Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png new file mode 100644 index 000000000..c7c4a9fc1 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png new file mode 100644 index 000000000..984204495 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png new file mode 100644 index 000000000..e438f4f3a Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png new file mode 100644 index 000000000..735834afb Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png new file mode 100644 index 000000000..e3cde65f6 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png new file mode 100644 index 000000000..3a91544bd Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png new file mode 100644 index 000000000..bef4342a7 Binary files /dev/null and b/ru/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png differ diff --git a/ru/docs/chapter_computational_complexity/time_complexity.md b/ru/docs/chapter_computational_complexity/time_complexity.md new file mode 100644 index 000000000..880c0e6d5 --- /dev/null +++ b/ru/docs/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,1151 @@ +# Временная сложность + +Время выполнения может наглядно и точно отражать эффективность алгоритма. Если мы хотим точно оценить время работы некоторого фрагмента кода, как это сделать? + +1. **Определить платформу выполнения**, включая конфигурацию оборудования, язык программирования, системную среду и т.д., поскольку все эти факторы влияют на эффективность выполнения кода. +2. **Оценить время выполнения различных вычислительных операций**, например операция сложения `+` требует 1 нс , операция умножения `*` требует 10 нс , операция вывода `print()` требует 5 нс и т.д. +3. **Подсчитать все вычислительные операции в коде** и суммировать время выполнения всех операций, чтобы получить общее время работы. + +Например, в следующем коде размер входных данных равен $n$ : + +=== "Python" + + ```python title="" + # На некоторой платформе выполнения + def algorithm(n: int): + a = 2 # 1 нс + a = a + 1 # 1 нс + a = a * 2 # 10 нс + # Цикл выполняется n раз + for _ in range(n): # 1 нс + print(0) # 5 нс + ``` + +=== "C++" + + ```cpp title="" + // На некоторой платформе выполнения + void algorithm(int n) { + int a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // 1 нс + cout << 0 << endl; // 5 нс + } + } + ``` + +=== "Java" + + ```java title="" + // На некоторой платформе выполнения + void algorithm(int n) { + int a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // 1 нс + System.out.println(0); // 5 нс + } + } + ``` + +=== "C#" + + ```csharp title="" + // На некоторой платформе выполнения + void Algorithm(int n) { + int a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // 1 нс + Console.WriteLine(0); // 5 нс + } + } + ``` + +=== "Go" + + ```go title="" + // На некоторой платформе выполнения + func algorithm(n int) { + a := 2 // 1 нс + a = a + 1 // 1 нс + a = a * 2 // 10 нс + // Цикл выполняется n раз + for i := 0; i < n; i++ { // 1 нс + fmt.Println(a) // 5 нс + } + } + ``` + +=== "Swift" + + ```swift title="" + // На некоторой платформе выполнения + func algorithm(n: Int) { + var a = 2 // 1 нс + a = a + 1 // 1 нс + a = a * 2 // 10 нс + // Цикл выполняется n раз + for _ in 0 ..< n { // 1 нс + print(0) // 5 нс + } + } + ``` + +=== "JS" + + ```javascript title="" + // На некоторой платформе выполнения + function algorithm(n) { + var a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for(let i = 0; i < n; i++) { // 1 нс + console.log(0); // 5 нс + } + } + ``` + +=== "TS" + + ```typescript title="" + // На некоторой платформе выполнения + function algorithm(n: number): void { + var a: number = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for(let i = 0; i < n; i++) { // 1 нс + console.log(0); // 5 нс + } + } + ``` + +=== "Dart" + + ```dart title="" + // На некоторой платформе выполнения + void algorithm(int n) { + int a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // 1 нс + print(0); // 5 нс + } + } + ``` + +=== "Rust" + + ```rust title="" + // На некоторой платформе выполнения + fn algorithm(n: i32) { + let mut a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for _ in 0..n { // 1 нс + println!("{}", 0); // 5 нс + } + } + ``` + +=== "C" + + ```c title="" + // На некоторой платформе выполнения + void algorithm(int n) { + int a = 2; // 1 нс + a = a + 1; // 1 нс + a = a * 2; // 10 нс + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // 1 нс + printf("%d", 0); // 5 нс + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + // На некоторой платформе выполнения + fun algorithm(n: Int) { + var a = 2 // 1 нс + a = a + 1 // 1 нс + a = a * 2 // 10 нс + // Цикл выполняется n раз + for (i in 0.. 1$ он медленнее алгоритма `A` , а при $n > 1000000$ медленнее алгоритма `C` . На самом деле, если размер входных данных $n$ достаточно велик, алгоритм с "постоянной" сложностью обязательно лучше алгоритма с "линейной" сложностью. В этом и состоит смысл тенденции роста времени. +- **Метод вывода временной сложности проще**. Очевидно, что платформа выполнения и тип вычислительных операций не влияют на тенденцию роста времени работы алгоритма. Поэтому в анализе временной сложности мы можем считать время выполнения всех вычислительных операций одинаковым "единичным временем" и тем самым упростить "подсчет времени выполнения операций" до "подсчета количества операций", что существенно снижает сложность оценки. +- **У временной сложности есть и определенные ограничения**. Например, хотя временная сложность алгоритмов `A` и `C` одинакова, их реальное время выполнения сильно различается. Точно так же, хотя временная сложность `B` выше, чем у `C` , при малых $n$ алгоритм `B` явно лучше `C` . В таких случаях нам часто трудно судить об эффективности алгоритма, опираясь только на временную сложность. Тем не менее, несмотря на эти ограничения, анализ сложности все равно остается самым эффективным и самым распространенным способом оценки алгоритмов. + +## Асимптотическая верхняя граница функции + +Для функции с входным размером $n$ : + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # Цикл выполняется n раз + for i in range(n): # +1 + print(0) # +1 + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) + cout << 0 << endl; // +1 + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) + System.out.println(0); // +1 + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) + Console.WriteLine(0); // +1 + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // Цикл выполняется n раз + for i := 0; i < n; i++ { // +1 + fmt.Println(a) // +1 + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // Цикл выполняется n раз + for _ in 0 ..< n { // +1 + print(0) // +1 + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + var a = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // Цикл выполняется n раз + for(let i = 0; i < n; i++){ // +1 (каждый раз выполняется i ++) + console.log(0); // +1 + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void{ + var a: number = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // Цикл выполняется n раз + for(let i = 0; i < n; i++){ // +1 (каждый раз выполняется i ++) + console.log(0); // +1 + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) + print(0); // +1 + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + + // Цикл выполняется n раз + for _ in 0..n { // +1 (каждый раз выполняется i ++) + println!("{}", 0); // +1 + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // Цикл выполняется n раз + for (int i = 0; i < n; i++) { // +1 (каждый раз выполняется i ++) + printf("%d", 0); // +1 + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // Цикл выполняется n раз + for (i in 0..нотацией Big $O$ (big-$O$ notation) и обозначает асимптотическую верхнюю границу (asymptotic upper bound) функции $T(n)$ . + +По сути анализ временной сложности - это вычисление асимптотической верхней границы "количества операций $T(n)$", и у него есть строгое математическое определение. + +!!! note "Асимптотическая верхняя граница функции" + + Если существуют положительное действительное число $c$ и действительное число $n_0$ , такие что для всех $n > n_0$ выполняется $T(n) \leq c \cdot f(n)$ , то можно считать, что $f(n)$ задает асимптотическую верхнюю границу для $T(n)$ ; это записывается как $T(n) = O(f(n))$ . + +Как показано на рисунке ниже, вычислить асимптотическую верхнюю границу - значит найти такую функцию $f(n)$ , что при стремлении $n$ к бесконечности функции $T(n)$ и $f(n)$ имеют один и тот же порядок роста и отличаются только постоянным коэффициентом $c$. + +![Асимптотическая верхняя граница функции](time_complexity.assets/asymptotic_upper_bound.png) + +## Метод вывода + +Математическое определение асимптотической верхней границы выглядит довольно формально, и если ты понял его не до конца, переживать не стоит. Сначала можно освоить сам метод вывода, а в процессе дальнейшей практики постепенно почувствовать его математический смысл. + +Согласно определению, после того как мы определили $f(n)$ , мы можем получить временную сложность $O(f(n))$ . Но как определить саму асимптотическую верхнюю границу $f(n)$ ? В целом процесс состоит из двух шагов: сначала подсчитать количество операций, затем определить асимптотическую верхнюю границу. + +### Шаг 1: подсчет количества операций + +Для кода это можно делать построчно сверху вниз. Однако, поскольку в выражении $c \cdot f(n)$ выше постоянный коэффициент $c$ может быть сколь угодно большим, **различные коэффициенты и постоянные члены в числе операций $T(n)$ можно игнорировать**. Исходя из этого принципа, можно сформулировать следующие упрощающие приемы подсчета. + +1. **Игнорировать константы в $T(n)$**. Они не зависят от $n$ , а значит не влияют на временную сложность. +2. **Опускать все коэффициенты**. Например, циклы на $2n$ раз или $5n + 1$ раз можно упростить до $n$ раз, потому что коэффициент перед $n$ не влияет на временную сложность. +3. **При вложенных циклах использовать умножение**. Общее число операций равно произведению числа операций внешнего и внутреннего циклов; при этом для каждого уровня цикла по-прежнему можно применять приемы из пунктов `1.` и `2.` . + +Для заданной функции мы можем использовать перечисленные выше приемы и подсчитать число операций: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +0 (прием 1) + a = a + n # +0 (прием 1) + # +n (прием 2) + for i in range(5 * n + 1): + print(0) + # +n*n (прием 3) + for i in range(2 * n): + for j in range(n + 1): + print(0) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (int i = 0; i < 5 * n + 1; i++) { + cout << 0 << endl; + } + // +n*n (прием 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + cout << 0 << endl; + } + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (int i = 0; i < 5 * n + 1; i++) { + System.out.println(0); + } + // +n*n (прием 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + System.out.println(0); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n (прием 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +0 (прием 1) + a = a + n // +0 (прием 1) + // +n (прием 2) + for i := 0; i < 5 * n + 1; i++ { + fmt.Println(0) + } + // +n*n (прием 3) + for i := 0; i < 2 * n; i++ { + for j := 0; j < n + 1; j++ { + fmt.Println(0) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +0 (прием 1) + a = a + n // +0 (прием 1) + // +n (прием 2) + for _ in 0 ..< (5 * n + 1) { + print(0) + } + // +n*n (прием 3) + for _ in 0 ..< (2 * n) { + for _ in 0 ..< (n + 1) { + print(0) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + let a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n (прием 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + let a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n (прием 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (int i = 0; i < 5 * n + 1; i++) { + print(0); + } + // +n*n (прием 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + print(0); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + + // +n (прием 2) + for i in 0..(5 * n + 1) { + println!("{}", 0); + } + + // +n*n (прием 3) + for i in 0..(2 * n) { + for j in 0..(n + 1) { + println!("{}", 0); + } + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +0 (прием 1) + a = a + n; // +0 (прием 1) + // +n (прием 2) + for (int i = 0; i < 5 * n + 1; i++) { + printf("%d", 0); + } + // +n*n (прием 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + printf("%d", 0); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +0 (прием 1) + a = a + n // +0 (прием 1) + // +n (прием 2) + for (i in 0..<5 * n + 1) { + println(0) + } + // +n*n (прием 3) + for (i in 0..<2 * n) { + for (j in 0.. Таблица   Временная сложность, соответствующая разному количеству операций

+ +| Число операций $T(n)$ | Временная сложность $O(f(n))$ | +| ---------------------- | -------------------- | +| $100000$ | $O(1)$ | +| $3n + 2$ | $O(n)$ | +| $2n^2 + 3n + 2$ | $O(n^2)$ | +| $n^3 + 10000n^2$ | $O(n^3)$ | +| $2^n + 10000n^{10000}$ | $O(2^n)$ | + +## Распространенные типы + +Пусть размер входных данных равен $n$ ; распространенные типы временной сложности показаны на рисунке ниже (в порядке от меньшей к большей). + +$$ +\begin{aligned} +O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline +\text{Постоянная} < \text{Логарифмическая} < \text{Линейная} < \text{Линейно-логарифмическая} < \text{Квадратичная} < \text{Экспоненциальная} < \text{Факториальная} +\end{aligned} +$$ + +![Распространенные типы временной сложности](time_complexity.assets/time_complexity_common_types.png) + +### Постоянная сложность $O(1)$ + +Число операций при постоянной сложности не зависит от размера входных данных $n$ , то есть не изменяется вместе с изменением $n$ . + +В следующей функции, хотя число операций `size` может быть большим, оно не зависит от размера входных данных $n$ , поэтому временная сложность по-прежнему равна $O(1)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{constant} +``` + +### Линейная сложность $O(n)$ + +Число операций при линейной сложности растет линейно относительно размера входных данных $n$ . Линейная сложность обычно встречается в одноуровневых циклах: + +```src +[file]{time_complexity}-[class]{}-[func]{linear} +``` + +Операции обхода массива и обхода связного списка имеют временную сложность $O(n)$ , где $n$ - длина массива или списка: + +```src +[file]{time_complexity}-[class]{}-[func]{array_traversal} +``` + +Стоит отметить, что **размер входных данных $n$ нужно определять конкретно в зависимости от типа входа**. Например, в первом примере переменная $n$ сама является размером входных данных; во втором примере размером данных служит длина массива $n$ . + +### Квадратичная сложность $O(n^2)$ + +Число операций при квадратичной сложности растет квадратично относительно размера входных данных $n$ . Квадратичная сложность обычно встречается во вложенных циклах: временная сложность внешнего и внутреннего циклов равна $O(n)$ , поэтому общая временная сложность составляет $O(n^2)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{quadratic} +``` + +На рисунке ниже сравниваются три временные сложности: постоянная, линейная и квадратичная. + +![Постоянная, линейная и квадратичная временная сложность](time_complexity.assets/time_complexity_constant_linear_quadratic.png) + +Возьмем в качестве примера пузырьковую сортировку: внешний цикл выполняется $n - 1$ раз, внутренний цикл выполняется $n-1$ , $n-2$ , $\dots$ , $2$ , $1$ раз, в среднем это $n / 2$ раз, поэтому временная сложность равна $O((n - 1) n / 2) = O(n^2)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{bubble_sort} +``` + +### Экспоненциальная сложность $O(2^n)$ + +Типичный пример экспоненциального роста в биологии - "деление клеток": в начальном состоянии есть 1 клетка, после одного деления их становится 2, после двух делений - 4 и так далее; после $n$ раундов деления клеток становится $2^n$ . + +На рисунке ниже и в следующем коде моделируется процесс деления клеток; временная сложность равна $O(2^n)$ . Обрати внимание, что входное значение $n$ обозначает число раундов деления, а возвращаемое значение `count` обозначает общее число делений. + +```src +[file]{time_complexity}-[class]{}-[func]{exponential} +``` + +![Экспоненциальная временная сложность](time_complexity.assets/time_complexity_exponential.png) + +В реальных алгоритмах экспоненциальная сложность также часто встречается в рекурсивных функциях. Например, в следующем коде процесс рекурсивно делится надвое и останавливается после $n$ разбиений: + +```src +[file]{time_complexity}-[class]{}-[func]{exp_recur} +``` + +Экспоненциальный рост происходит очень быстро и часто встречается в переборных методах (грубая сила, backtracking и т.д.). Для задач большого масштаба экспоненциальная сложность неприемлема, и обычно приходится применять динамическое программирование, жадные алгоритмы и другие подходы. + +### Логарифмическая сложность $O(\log n)$ + +В противоположность экспоненциальной, логарифмическая сложность описывает ситуацию "каждый раунд уменьшение вдвое". Пусть размер входных данных равен $n$ ; так как на каждом шаге размер уменьшается вдвое, число итераций равно $\log_2 n$ , то есть является обратной функцией к $2^n$ . + +На рисунке ниже и в следующем коде моделируется процесс "каждый раунд уменьшение вдвое"; временная сложность равна $O(\log_2 n)$ и кратко записывается как $O(\log n)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{logarithmic} +``` + +![Логарифмическая временная сложность](time_complexity.assets/time_complexity_logarithmic.png) + +Подобно экспоненциальной сложности, логарифмическая также часто встречается в рекурсивных функциях. Следующий код формирует рекурсивное дерево высотой $\log_2 n$ : + +```src +[file]{time_complexity}-[class]{}-[func]{log_recur} +``` + +Логарифмическая сложность часто встречается в алгоритмах, основанных на стратегии "разделяй и властвуй", и отражает идеи "разделить одно на много" и "упростить сложное". Она растет медленно и является идеальной временной сложностью, уступающей только постоянной. + +!!! tip "Каково основание у $O(\log n)$ ?" + + Точнее говоря, "разделение на $m$ частей" соответствует временной сложности $O(\log_m n)$ . А по формуле перехода к другому основанию логарифма мы получаем равные по сложности выражения с разными основаниями: + + $$ + O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) + $$ + + Иными словами, основание $m$ можно менять без влияния на сложность. Поэтому мы обычно опускаем основание $m$ и напрямую записываем логарифмическую сложность как $O(\log n)$ . + +### Линейно-логарифмическая сложность $O(n \log n)$ + +Линейно-логарифмическая сложность часто встречается во вложенных циклах, когда временная сложность двух уровней соответственно равна $O(\log n)$ и $O(n)$ . Соответствующий код выглядит следующим образом: + +```src +[file]{time_complexity}-[class]{}-[func]{linear_log_recur} +``` + +На рисунке ниже показано, как возникает линейно-логарифмическая сложность. Общее число операций на каждом уровне бинарного дерева равно $n$ , а дерево имеет $\log_2 n + 1$ уровней, поэтому временная сложность равна $O(n \log n)$ . + +![Линейно-логарифмическая временная сложность](time_complexity.assets/time_complexity_logarithmic_linear.png) + +Временная сложность основных алгоритмов сортировки обычно равна $O(n \log n)$ , например у быстрой сортировки, сортировки слиянием, пирамидальной сортировки и т.д. + +### Факториальная сложность $O(n!)$ + +Факториальная сложность соответствует математической задаче "все перестановки". Если даны $n$ попарно различных элементов, то число всех возможных перестановок равно: + +$$ +n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 +$$ + +Факториал обычно реализуют через рекурсию. Как показано на рисунке ниже и в следующем коде, на первом уровне происходит ветвление на $n$ подзадач, на втором - на $n - 1$ и так далее, пока на $n$ -м уровне ветвление не прекращается: + +```src +[file]{time_complexity}-[class]{}-[func]{factorial_recur} +``` + +![Факториальная временная сложность](time_complexity.assets/time_complexity_factorial.png) + +Обрати внимание: поскольку при $n \geq 4$ всегда выполняется $n! > 2^n$ , факториальная сложность растет еще быстрее, чем экспоненциальная, и при больших $n$ также неприемлема. + +## Худшая, лучшая и средняя временная сложность + +**Временная эффективность алгоритма часто не фиксирована, а зависит от распределения входных данных**. Предположим, на вход подается массив `nums` длины $n$ , состоящий из чисел от $1$ до $n$ , каждое из которых встречается ровно один раз; при этом порядок элементов случайно перемешан. Задача состоит в том, чтобы вернуть индекс элемента $1$ . Тогда можно сделать следующие выводы. + +- Когда `nums = [?, ?, ..., 1]` , то есть когда последний элемент равен $1$ , нужно полностью пройти по массиву, **что дает худшую временную сложность $O(n)$** . +- Когда `nums = [1, ?, ?, ...]` , то есть когда первый элемент равен $1$ , независимо от длины массива продолжать обход не нужно, **что дает лучшую временную сложность $\Omega(1)$** . + +"Худшая временная сложность" соответствует асимптотической верхней границе функции и обозначается нотацией Big $O$ . Соответственно, "лучшая временная сложность" соответствует асимптотической нижней границе функции и обозначается символом $\Omega$ : + +```src +[file]{worst_best_time_complexity}-[class]{}-[func]{find_one} +``` + +Стоит отметить, что на практике мы редко используем лучшую временную сложность, поскольку обычно она достигается лишь с очень малой вероятностью и может вводить в заблуждение. **Худшая временная сложность гораздо практичнее, потому что задает безопасную оценку эффективности** и позволяет уверенно использовать алгоритм. + +Из приведенного выше примера видно, что худшая и лучшая временные сложности возникают только при "особых распределениях данных"; вероятность таких случаев может быть низкой, и они не всегда реально отражают эффективность алгоритма. Напротив, **средняя временная сложность способна показать эффективность алгоритма на случайных входных данных** и обозначается символом $\Theta$ . + +Для некоторых алгоритмов мы можем относительно просто вывести средний случай при случайном распределении данных. Например, в приведенном выше примере входной массив перемешан, а значит вероятность появления элемента $1$ на любом индексе одинакова; следовательно, среднее число итераций алгоритма равно половине длины массива, то есть $n / 2$ , а средняя временная сложность равна $\Theta(n / 2) = \Theta(n)$ . + +Но для более сложных алгоритмов вычислить среднюю временную сложность часто непросто, потому что трудно проанализировать полное математическое ожидание на заданном распределении данных. В таких случаях мы обычно используем худшую временную сложность как критерий оценки эффективности алгоритма. + +!!! question "Почему символ $\Theta$ встречается так редко?" + + Возможно, потому что символ $O$ звучит слишком привычно, и мы часто используем его для обозначения средней временной сложности. Но строго говоря, это некорректно. В этой книге и в других материалах, если встретится выражение вроде "средняя временная сложность $O(n)$", просто понимай его как $\Theta(n)$ . diff --git a/ru/docs/chapter_data_structure/basic_data_types.md b/ru/docs/chapter_data_structure/basic_data_types.md new file mode 100644 index 000000000..ce6d58679 --- /dev/null +++ b/ru/docs/chapter_data_structure/basic_data_types.md @@ -0,0 +1,175 @@ +# Базовые типы данных + +Когда мы говорим о данных в компьютере, нам приходят на ум текст, изображения, видео, звук, 3D-модели и многие другие формы. Хотя эти данные организованы по-разному, все они состоят из различных базовых типов данных. + +**Базовые типы данных - это типы, с которыми CPU может работать напрямую**; в алгоритмах они используются непосредственно и в основном включают следующее. + +- Целочисленные типы `byte` , `short` , `int` , `long` . +- Типы с плавающей точкой `float` , `double` , используемые для представления дробных чисел. +- Символьный тип `char` , используемый для представления букв, знаков препинания и даже эмодзи в разных языках. +- Логический тип `bool` , используемый для представления суждений "да" и "нет". + +**Базовые типы данных хранятся в компьютере в двоичной форме**. Один двоичный разряд равен $1$ биту. В подавляющем большинстве современных операционных систем $1$ байт (byte) состоит из $8$ битов (bit). + +Диапазон значений базовых типов данных зависит от объема занимаемого ими пространства. Ниже в качестве примера используется Java. + +- Целочисленный тип `byte` занимает $1$ байт = $8$ бит и может представлять $2^{8}$ чисел. +- Целочисленный тип `int` занимает $4$ байта = $32$ бита и может представлять $2^{32}$ чисел. + +В таблице ниже перечислены объем памяти, диапазон значений и значения по умолчанию для различных базовых типов данных в Java. Заучивать эту таблицу наизусть не нужно; достаточно иметь общее представление и при необходимости обращаться к ней. + +

Таблица   Объем памяти и диапазоны значений базовых типов данных

+ +| Тип | Обозначение | Объем памяти | Минимальное значение | Максимальное значение | Значение по умолчанию | +| -------- | ----------- | ------------ | ------------------------- | ----------------------- | --------------------- | +| Целые | `byte` | 1 байт | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | +| | `short` | 2 байта | $-2^{15}$ | $2^{15} - 1$ | $0$ | +| | `int` | 4 байта | $-2^{31}$ | $2^{31} - 1$ | $0$ | +| | `long` | 8 байт | $-2^{63}$ | $2^{63} - 1$ | $0$ | +| Вещественные | `float` | 4 байта | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | +| | `double` | 8 байт | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | +| Символы | `char` | 2 байта | $0$ | $2^{16} - 1$ | $0$ | +| Логические | `bool` | 1 байт | $\text{false}$ | $\text{true}$ | $\text{false}$ | + +Обрати внимание: приведенная выше таблица относится именно к базовым типам данных Java. В каждом языке программирования определения типов свои, поэтому объем памяти, диапазон значений и значения по умолчанию могут различаться. + +- В Python целочисленный тип `int` может иметь произвольный размер, ограниченный только доступной памятью; тип `float` использует двойную точность 64 бита; типа `char` нет, а одиночный символ на деле является строкой `str` длины 1. +- В C и C++ размер базовых типов данных явно не зафиксирован и зависит от реализации и платформы. Таблица выше соответствует модели данных LP64 [data model](https://en.cppreference.com/w/cpp/language/types#Properties), применяемой в 64-битных Unix-системах, включая Linux и macOS. +- Размер символа `char` в C и C++ составляет 1 байт, а в большинстве других языков программирования зависит от конкретного способа кодирования символов; подробнее это рассматривается в разделе "Кодирование символов". +- Хотя для представления логического значения достаточно 1 бита ( $0$ или $1$ ), в памяти оно обычно хранится как 1 байт. Это связано с тем, что современные CPU обычно используют 1 байт как минимальную адресуемую единицу памяти. + +Какова же связь между базовыми типами данных и структурами данных? Мы знаем, что структуры данных - это способы организации и хранения данных в компьютере. Подлежащее в этой фразе - "структура", а не "данные". + +Если мы хотим представить "ряд чисел", то естественно подумаем об использовании массива. Это связано с тем, что линейная структура массива может выразить отношения соседства и порядка между числами, а вот то, что именно хранится внутри - целые `int` , вещественные `float` или символы `char` , - к "структуре данных" отношения не имеет. + +Иными словами, **базовые типы данных задают "тип содержимого" данных, а структуры данных задают "способ организации" данных**. Например, в следующем коде мы используем одну и ту же структуру данных (массив) для хранения и представления различных базовых типов данных, включая `int` , `float` , `char` , `bool` и т.д. + +=== "Python" + + ```python title="" + # Инициализируем массивы с использованием разных базовых типов данных + numbers: list[int] = [0] * 5 + decimals: list[float] = [0.0] * 5 + # В Python символы на деле являются строками длины 1 + characters: list[str] = ['0'] * 5 + bools: list[bool] = [False] * 5 + # Списки Python могут свободно хранить разные базовые типы данных и ссылки на объекты + data = [0, 0.0, 'a', False, ListNode(0)] + ``` + +=== "C++" + + ```cpp title="" + // Инициализируем массивы с использованием разных базовых типов данных + int numbers[5]; + float decimals[5]; + char characters[5]; + bool bools[5]; + ``` + +=== "Java" + + ```java title="" + // Инициализируем массивы с использованием разных базовых типов данных + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + boolean[] bools = new boolean[5]; + ``` + +=== "C#" + + ```csharp title="" + // Инициализируем массивы с использованием разных базовых типов данных + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + bool[] bools = new bool[5]; + ``` + +=== "Go" + + ```go title="" + // Инициализируем массивы с использованием разных базовых типов данных + var numbers = [5]int{} + var decimals = [5]float64{} + var characters = [5]byte{} + var bools = [5]bool{} + ``` + +=== "Swift" + + ```swift title="" + // Инициализируем массивы с использованием разных базовых типов данных + let numbers = Array(repeating: 0, count: 5) + let decimals = Array(repeating: 0.0, count: 5) + let characters: [Character] = Array(repeating: "a", count: 5) + let bools = Array(repeating: false, count: 5) + ``` + +=== "JS" + + ```javascript title="" + // Массивы JavaScript могут свободно хранить разные базовые типы данных и объекты + const array = [0, 0.0, 'a', false]; + ``` + +=== "TS" + + ```typescript title="" + // Инициализируем массивы с использованием разных базовых типов данных + const numbers: number[] = []; + const characters: string[] = []; + const bools: boolean[] = []; + ``` + +=== "Dart" + + ```dart title="" + // Инициализируем массивы с использованием разных базовых типов данных + List numbers = List.filled(5, 0); + List decimals = List.filled(5, 0.0); + List characters = List.filled(5, 'a'); + List bools = List.filled(5, false); + ``` + +=== "Rust" + + ```rust title="" + // Инициализируем массивы с использованием разных базовых типов данных + let numbers: Vec = vec![0; 5]; + let decimals: Vec = vec![0.0; 5]; + let characters: Vec = vec!['0'; 5]; + let bools: Vec = vec![false; 5]; + ``` + +=== "C" + + ```c title="" + // Инициализируем массивы с использованием разных базовых типов данных + int numbers[10]; + float decimals[10]; + char characters[10]; + bool bools[10]; + ``` + +=== "Kotlin" + + ```kotlin title="" + // Инициализируем массивы с использованием разных базовых типов данных + val numbers = IntArray(5) + val decinals = FloatArray(5) + val characters = CharArray(5) + val bools = BooleanArray(5) + ``` + +=== "Ruby" + + ```ruby title="" + # Списки Ruby могут свободно хранить разные базовые типы данных и ссылки на объекты + data = [0, 0.0, 'a', false, ListNode(0)] + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%83%D0%B7%D0%B5%D0%BB%D0%BA%D0%BB%D0%B0%D1%81%D1%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20%D1%81%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC%20%D0%BD%D0%B5%D1%81%D0%BA%D0%BE%D0%BB%D1%8C%D0%BA%D0%B8%D1%85%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D1%85%20%D1%82%D0%B8%D0%BF%D0%BE%D0%B2%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20%2A%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20%2A%205%0A%20%20%20%20%23%20%D0%92%20Python%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8B%20%D0%BD%D0%B0%20%D1%81%D0%B0%D0%BC%D0%BE%D0%BC%20%D0%B4%D0%B5%D0%BB%D0%B5%20%D1%8F%D0%B2%D0%BB%D1%8F%D1%8E%D1%82%D1%81%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%D0%BC%D0%B8%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B%201%0A%20%20%20%20characters%20%3D%20%5B%270%27%5D%20%2A%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20%2A%205%0A%20%20%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BA%D0%B8%20%D0%B2%20Python%20%D0%BC%D0%BE%D0%B3%D1%83%D1%82%20%D1%81%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D1%80%D0%B0%D0%B7%D0%BB%D0%B8%D1%87%D0%BD%D1%8B%D0%B5%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B5%20%D1%82%D0%B8%D0%BF%D1%8B%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D0%B8%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BD%D0%B0%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%0A%20%20%20%20data%20%3D%20%5B0%2C%200.0%2C%20%27a%27%2C%20False%2C%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ru/docs/chapter_data_structure/character_encoding.assets/ascii_table.png b/ru/docs/chapter_data_structure/character_encoding.assets/ascii_table.png new file mode 100644 index 000000000..a1c48679a Binary files /dev/null and b/ru/docs/chapter_data_structure/character_encoding.assets/ascii_table.png differ diff --git a/ru/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png b/ru/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png new file mode 100644 index 000000000..a8d414e3f Binary files /dev/null and b/ru/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png differ diff --git a/ru/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png b/ru/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png new file mode 100644 index 000000000..13428adc1 Binary files /dev/null and b/ru/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png differ diff --git a/ru/docs/chapter_data_structure/character_encoding.md b/ru/docs/chapter_data_structure/character_encoding.md new file mode 100644 index 000000000..ad3fc5a9f --- /dev/null +++ b/ru/docs/chapter_data_structure/character_encoding.md @@ -0,0 +1,87 @@ +# Кодирование символов * + +В компьютере все данные хранятся в двоичной форме, и символ `char` не является исключением. Чтобы представлять символы, нам нужно определить "набор символов", задающий взаимно-однозначное соответствие между каждым символом и двоичным числом. Имея такой набор, компьютер может преобразовывать двоичные числа в символы простым поиском по таблице. + +## Набор символов ASCII + +Код ASCII - это самый ранний набор символов; его полное название - American Standard Code for Information Interchange (американский стандартный код обмена информацией). Он использует 7 двоичных битов (нижние 7 битов одного байта) для представления одного символа и способен представлять не более 128 различных символов. Как показано на рисунке ниже, ASCII включает заглавные и строчные английские буквы, цифры 0 ~ 9, некоторые знаки препинания и некоторые управляющие символы (например перевод строки и табуляцию). + +![Таблица ASCII](character_encoding.assets/ascii_table.png) + +Однако **код ASCII может представлять только английский язык**. С глобализацией компьютерных технологий появился набор символов EASCII, способный покрывать больше языков. Он расширяет 7-битную основу ASCII до 8 битов и может представлять 256 различных символов. + +Во всем мире постепенно появились разные наборы EASCII, подходящие для разных регионов. Первые 128 символов в этих наборах одинаковы и соответствуют ASCII, а последние 128 символов определяются по-разному, чтобы удовлетворять потребностям разных языков. + +## Набор символов GBK + +Позже люди обнаружили, что **кода EASCII все равно недостаточно для количества символов во многих языках**. Например, китайских иероглифов существует почти сто тысяч, а в повседневном использовании нужны тысячи. В 1980 году Государственное управление стандартов Китая выпустило набор символов GB2312, включающий 6763 иероглифа, что в основном удовлетворило потребности компьютерной обработки китайского текста. + +Однако GB2312 не умеет работать с некоторыми редкими иероглифами и традиционными формами письма. Набор символов GBK - это расширение GB2312, содержащее в общей сложности 21886 иероглифов. В схеме кодирования GBK символы ASCII представляются одним байтом, а китайские иероглифы - двумя байтами. + +## Набор символов Unicode + +С бурным развитием компьютерной техники наборы символов и стандарты кодирования начали стремительно множиться, и это породило множество проблем. С одной стороны, такие наборы обычно определяли символы только для конкретных языков и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов кодирования; если две машины использовали разные стандарты, при обмене информацией возникали кракозябры. + +Исследователи той эпохи задумались: **если создать достаточно полный набор символов, который включит все языки и знаки мира, разве это не решит проблемы межъязыковой среды и искаженного текста**? Под влиянием этой идеи и появился большой и всеобъемлющий набор символов Unicode. + +Unicode по-китайски называется "единый код" и теоретически способен вместить более миллиона символов. Его цель - собрать символы со всего мира в единый набор символов, предоставить универсальный стандарт для обработки и отображения текстов на разных языках и уменьшить количество проблем с искажением текста, вызванных различиями стандартов кодирования. + +С момента публикации в 1991 году Unicode непрерывно расширялся, добавляя новые языки и символы. По состоянию на сентябрь 2022 года Unicode уже включал 149186 символов, в том числе буквы разных языков, знаки, а также эмодзи. В огромном наборе символов Unicode часто используемые символы занимают 2 байта, а некоторые редкие символы - 3 байта и даже 4 байта. + +Unicode - это универсальный набор символов, который по сути просто присваивает каждому символу номер (так называемую "кодовую точку"), **но не определяет, как именно хранить эти кодовые точки в компьютере**. Тут неизбежно возникает вопрос: если в одном тексте одновременно встречаются кодовые точки Unicode разной длины, как система должна разбирать символы? Например, если дан код длиной 2 байта, как понять, является ли это одним 2-байтовым символом или двумя 1-байтовыми? + +Для этой проблемы **прямолинейное решение состоит в том, чтобы хранить все символы в кодировке одинаковой длины**. Как показано на рисунке ниже, каждый символ в "Hello" занимает 1 байт, а каждый символ в "алгоритм" занимает 2 байта. Мы можем дополнить старшие биты нулями и закодировать все символы в "Hello алгоритм" в виде 2-байтовых единиц. Тогда система сможет считывать по одному символу каждые 2 байта и восстановить эту фразу. + +![Пример кодирования Unicode](character_encoding.assets/unicode_hello_algo.png) + +Однако ASCII уже показал нам, что для кодирования английского текста достаточно 1 байта. Если использовать описанную выше схему, английский текст будет занимать вдвое больше памяти, чем при ASCII, а это очень неэффективно. Поэтому нам нужен более эффективный способ кодирования Unicode. + +## Кодировка UTF-8 + +Сегодня UTF-8 стала самым широко используемым способом кодирования Unicode в мире. **Это кодировка переменной длины**, использующая от 1 до 4 байт на символ в зависимости от его сложности. Символам ASCII нужен только 1 байт, латинским и греческим буквам - 2 байта, часто используемым китайским символам - 3 байта, а некоторым редким символам - 4 байта. + +Правила кодирования UTF-8 не слишком сложны и делятся на два случая. + +- Для символов длиной 1 байт старший бит устанавливается в $0$ , а оставшиеся 7 битов содержат кодовую точку Unicode. Стоит отметить, что символы ASCII занимают первые 128 кодовых точек в наборе Unicode. Иными словами, **кодировка UTF-8 обратно совместима с ASCII**. Это означает, что мы можем использовать UTF-8 для разбора очень старых ASCII-текстов. +- Для символов длиной $n$ байт (где $n > 1$) старшие $n$ битов первого байта устанавливаются в $1$ , а $(n + 1)$-й бит устанавливается в $0$ ; начиная со второго байта, старшие 2 бита каждого байта устанавливаются в $10$ ; все остальные биты используются для заполнения кодовой точки Unicode соответствующего символа. + +На рисунке ниже показана UTF-8-кодировка для строки "Hello алгоритм". Можно заметить, что поскольку старшие $n$ битов установлены в $1$ , система может определить длину символа как $n$ , подсчитав число ведущих единиц. + +Но почему старшие 2 бита всех остальных байтов устанавливаются в $10$ ? На самом деле это $10$ играет роль контрольного маркера. Если система начнет разбирать текст с неверного байта, префикс $10$ поможет быстро обнаружить аномалию. + +Причина выбора $10$ в качестве контрольного маркера в том, что по правилам UTF-8 символ не может иметь старшие два бита, равные $10$ . Это можно доказать от противного: если предположить, что у некоторого символа старшие два бита равны $10$ , то длина такого символа должна быть 1 байт, то есть это ASCII. Но у ASCII старший бит обязан быть $0$ , что противоречит предположению. + +![Пример кодировки UTF-8](character_encoding.assets/utf-8_hello_algo.png) + +Помимо UTF-8, распространены еще два следующих способа кодирования. + +- **Кодировка UTF-16**: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представляются 2 байтами; небольшая часть символов требует 4 байта. Для 2-байтовых символов кодировка UTF-16 совпадает с кодовой точкой Unicode. +- **Кодировка UTF-32**: каждый символ занимает 4 байта. Это означает, что UTF-32 требует больше места, чем UTF-8 и UTF-16, особенно в текстах с большой долей ASCII-символов. + +С точки зрения занимаемого места UTF-8 очень эффективна для английских символов, потому что им нужен всего 1 байт; а для некоторых неанглийских символов (например китайских) UTF-16 может быть эффективнее, потому что ей требуется только 2 байта, тогда как UTF-8 может потребовать 3 байта. + +С точки зрения совместимости у UTF-8 наилучшая универсальность, и многие инструменты и библиотеки в первую очередь поддерживают именно UTF-8. + +## Кодирование символов в языках программирования + +Для большинства языков программирования прошлого строки во время выполнения программы использовали фиксированные по длине кодировки, такие как UTF-16 или UTF-32. При кодировке фиксированной длины строку можно обрабатывать как массив, и такой подход дает следующие преимущества. + +- **Произвольный доступ**: к строкам в UTF-16 легко осуществлять произвольный доступ. UTF-8 же является кодировкой переменной длины, поэтому, чтобы найти $i$ -й символ, нужно пройти от начала строки до этого символа, а это требует $O(n)$ времени. +- **Подсчет длины строки**: аналогично произвольному доступу, вычисление длины строки в UTF-16 - это операция $O(1)$ . А вот вычисление длины строки в UTF-8 требует обхода всей строки. +- **Строковые операции**: многие операции со строками (разделение, конкатенация, вставка, удаление и т.д.) над строками в UTF-16 реализуются проще. При работе с UTF-8 обычно требуются дополнительные вычисления, чтобы не породить некорректную UTF-8-последовательность. + +Вообще говоря, проектирование схем кодирования символов в языках программирования - очень интересная тема, в которой учитывается множество факторов. + +- Тип `String` в Java использует кодировку UTF-16, и каждый символ занимает 2 байта. Это связано с тем, что на раннем этапе проектирования Java считалось, что 16 битов достаточно для представления всех возможных символов. Но это оказалось неверным предположением. Позднее Unicode вышел за пределы 16 битов, поэтому символы в Java теперь могут представляться парой 16-битных значений (так называемой "суррогатной парой"). +- Строки в JavaScript и TypeScript используют UTF-16 по причинам, похожим на Java. Когда Netscape впервые выпустила JavaScript в 1995 году, Unicode еще находился на ранней стадии развития, и 16-битного кодирования тогда было достаточно для представления всех символов Unicode. +- C# использует UTF-16 главным образом потому, что платформа .NET была разработана Microsoft, а многие технологии Microsoft (включая Windows) широко используют именно UTF-16. + +Из-за недооценки общего числа символов перечисленным выше языкам пришлось использовать "суррогатные пары" для представления Unicode-символов длиной больше 16 бит. Это вынужденный компромисс. С одной стороны, в строках с суррогатными парами один символ может занимать 2 байта или 4 байта, из-за чего теряется преимущество кодировки фиксированной длины. С другой стороны, обработка суррогатных пар требует дополнительного кода, что повышает сложность разработки и отладки. + +По этим причинам некоторые языки программирования предложили иные схемы кодирования. + +- `str` в Python использует Unicode и гибкое строковое представление, где длина хранимого символа зависит от наибольшей кодовой точки Unicode в строке. Если все символы строки принадлежат ASCII, каждый символ занимает 1 байт; если есть символы за пределами ASCII, но все они лежат в базовой многоязычной плоскости (BMP), каждый символ занимает 2 байта; если встречаются символы за пределами BMP, каждый символ занимает 4 байта. +- Тип `string` в Go внутри использует кодировку UTF-8. Язык Go также предоставляет тип `rune`, предназначенный для представления одной кодовой точки Unicode. +- Типы `str` и `String` в Rust внутри используют UTF-8. В Rust также есть тип `char`, представляющий одну кодовую точку Unicode. + +Следует помнить, что выше обсуждался способ хранения строк внутри языков программирования, **а это не то же самое, что хранение строк в файлах или передача их по сети**. При файловом хранении и сетевой передаче мы обычно кодируем строки в формате UTF-8, чтобы получить наилучшую совместимость и эффективность по занимаемому месту. diff --git a/ru/docs/chapter_data_structure/classification_logic_structure.png b/ru/docs/chapter_data_structure/classification_logic_structure.png new file mode 100644 index 000000000..a615396f6 Binary files /dev/null and b/ru/docs/chapter_data_structure/classification_logic_structure.png differ diff --git a/ru/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png b/ru/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png new file mode 100644 index 000000000..a615396f6 Binary files /dev/null and b/ru/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png differ diff --git a/ru/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png b/ru/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png new file mode 100644 index 000000000..7b5df8904 Binary files /dev/null and b/ru/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png differ diff --git a/ru/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png b/ru/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png new file mode 100644 index 000000000..7551530d3 Binary files /dev/null and b/ru/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png differ diff --git a/ru/docs/chapter_data_structure/classification_of_data_structure.md b/ru/docs/chapter_data_structure/classification_of_data_structure.md new file mode 100644 index 000000000..efa0d6eea --- /dev/null +++ b/ru/docs/chapter_data_structure/classification_of_data_structure.md @@ -0,0 +1,48 @@ +# Классификация структур данных + +К распространенным структурам данных относятся массивы, связные списки, стеки, очереди, хеш-таблицы, деревья, кучи и графы; их можно классифицировать по двум измерениям: "логическая структура" и "физическая структура". + +## Логическая структура: линейная и нелинейная + +**Логическая структура раскрывает логические связи между элементами данных**. В массивах и связных списках данные располагаются в определенном порядке, отражая линейные отношения между элементами; в деревьях данные иерархически располагаются сверху вниз, проявляя производные отношения между "предками" и "потомками"; графы состоят из вершин и ребер и отражают сложные сетевые связи. + +Как показано на рисунке ниже, логические структуры можно разделить на два больших класса: "линейные" и "нелинейные". Линейные структуры более интуитивны и означают, что данные логически выстроены в линию; нелинейные структуры, напротив, располагаются нелинейно. + +- **Линейные структуры данных**: массивы, связные списки, стеки, очереди, хеш-таблицы; между элементами существует отношение "один к одному". +- **Нелинейные структуры данных**: деревья, кучи, графы, хеш-таблицы. + +Нелинейные структуры данных можно дополнительно разделить на древовидные и сетевые. + +- **Древовидные структуры**: деревья, кучи, хеш-таблицы; между элементами существует отношение "один ко многим". +- **Сетевые структуры**: графы; между элементами существует отношение "многие ко многим". + +![Линейные и нелинейные структуры данных](classification_of_data_structure.assets/classification_logic_structure.png) + +## Физическая структура: непрерывная и разрозненная + +**Во время выполнения алгоритма обрабатываемые данные в основном хранятся в памяти**. На рисунке ниже показана планка памяти компьютера, где каждый черный блок содержит некоторый участок памяти. Мы можем представить память как огромную таблицу Excel, в которой каждая ячейка способна хранить данные определенного размера. + +**Система обращается к данным по адресу памяти соответствующей позиции**. Как показано на рисунке ниже, компьютер по определенному правилу присваивает каждой ячейке в этой таблице номер, чтобы у каждого участка памяти был уникальный адрес. Имея эти адреса, программа может получать доступ к данным, находящимся в памяти. + +![Планка памяти, участок памяти и адрес памяти](classification_of_data_structure.assets/computer_memory_location.png) + +!!! tip + + Стоит отметить, что сравнение памяти с таблицей Excel - это упрощенная аналогия; реальный механизм работы памяти гораздо сложнее и включает такие понятия, как адресное пространство, управление памятью, кэш-механизмы, виртуальная и физическая память. + +Память - общий ресурс для всех программ. Когда некоторый участок памяти занят одной программой, другие программы обычно не могут использовать его одновременно. **Поэтому при проектировании структур данных и алгоритмов память является важным фактором**. Например, пиковое потребление памяти алгоритмом не должно превышать доступную свободную память системы; если непрерывного крупного блока памяти недостаточно, выбранная структура данных должна уметь храниться в разрозненных областях памяти. + +Как показано на рисунке ниже, **физическая структура отражает способ хранения данных в памяти компьютера**; ее можно разделить на хранение в непрерывном пространстве (массивы) и хранение в разрозненном пространстве (связные списки). Физическая структура на нижнем уровне определяет способы доступа к данным, их обновления, вставки и удаления; эти два типа физических структур взаимно дополняют друг друга по временной и пространственной эффективности. + +![Хранение в непрерывном и разрозненном пространстве](classification_of_data_structure.assets/classification_phisical_structure.png) + +Стоит отметить, что **все структуры данных реализуются на основе массивов, связных списков или их комбинации**. Например, стеки и очереди можно реализовать как с помощью массивов, так и с помощью связных списков; а реализация хеш-таблицы может одновременно содержать массивы и связные списки. + +- **Можно реализовать на основе массивов**: стеки, очереди, хеш-таблицы, деревья, кучи, графы, матрицы, тензоры (массивы размерности $\geq 3$ ) и т.д. +- **Можно реализовать на основе связных списков**: стеки, очереди, хеш-таблицы, деревья, кучи, графы и т.д. + +После инициализации длину связного списка все еще можно изменять во время выполнения программы, поэтому его также называют "динамической структурой данных". Длина массива после инициализации неизменна, поэтому его также называют "статической структурой данных". Стоит заметить, что массив может менять длину за счет повторного выделения памяти, тем самым приобретая определенную "динамичность". + +!!! tip + + Если тебе пока трудно понять физическую структуру, рекомендуется сначала прочитать следующую главу, а затем вернуться к этому разделу. diff --git a/ru/docs/chapter_data_structure/classification_phisical_structure.png b/ru/docs/chapter_data_structure/classification_phisical_structure.png new file mode 100644 index 000000000..7b5df8904 Binary files /dev/null and b/ru/docs/chapter_data_structure/classification_phisical_structure.png differ diff --git a/ru/docs/chapter_data_structure/computer_memory_location.png b/ru/docs/chapter_data_structure/computer_memory_location.png new file mode 100644 index 000000000..7551530d3 Binary files /dev/null and b/ru/docs/chapter_data_structure/computer_memory_location.png differ diff --git a/ru/docs/chapter_data_structure/index.md b/ru/docs/chapter_data_structure/index.md new file mode 100644 index 000000000..c650404ef --- /dev/null +++ b/ru/docs/chapter_data_structure/index.md @@ -0,0 +1,9 @@ +# Структуры данных + +![Структуры данных](../assets/covers/chapter_data_structure.jpg) + +!!! abstract + + Структуры данных подобны прочному и разнообразному каркасу. + + Они задают план упорядоченной организации данных, а алгоритмы на этой основе обретают жизнь. diff --git a/ru/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png b/ru/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png new file mode 100644 index 000000000..cfad0fd0b Binary files /dev/null and b/ru/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png differ diff --git a/ru/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png b/ru/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png new file mode 100644 index 000000000..1db583f53 Binary files /dev/null and b/ru/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png differ diff --git a/ru/docs/chapter_data_structure/number_encoding.md b/ru/docs/chapter_data_structure/number_encoding.md new file mode 100644 index 000000000..84d061ee5 --- /dev/null +++ b/ru/docs/chapter_data_structure/number_encoding.md @@ -0,0 +1,150 @@ +# Кодирование чисел * + +!!! tip + + В этой книге разделы, помеченные символом `*`, относятся к дополнительному чтению. Если у тебя мало времени или материал кажется трудным, можно сначала пропустить их и вернуться после изучения обязательных разделов. + +## Прямой, обратный и дополнительный коды + +В таблице из предыдущего раздела мы заметили, что все целочисленные типы могут представлять на одно отрицательное число больше, чем положительных. Например, диапазон `byte` равен $[-128, 127]$ . Это явление выглядит не слишком интуитивно, и его внутренняя причина связана с прямым, обратным и дополнительным кодами. + +Прежде всего нужно отметить, что **числа хранятся в компьютере в форме "дополнительного кода"**. Прежде чем разбирать причины такого решения, сначала дадим определения всем трем способам представления. + +- **Прямой код**: старший бит двоичного представления числа рассматривается как знаковый, где $0$ означает положительное число, а $1$ - отрицательное; остальные биты представляют значение числа. +- **Обратный код**: для положительного числа обратный код совпадает с прямым; для отрицательного числа он получается инверсией всех битов прямого кода, кроме знакового бита. +- **Дополнительный код**: для положительного числа дополнительный код совпадает с прямым; для отрицательного числа он получается добавлением $1$ к его обратному коду. + +На рисунке ниже показаны способы преобразования между прямым, обратным и дополнительным кодами. + +![Преобразования между прямым, обратным и дополнительным кодами](number_encoding.assets/1s_2s_complement.png) + +Прямой код (sign-magnitude), хотя и является самым наглядным, имеет определенные ограничения. С одной стороны, **прямой код отрицательных чисел нельзя напрямую использовать в вычислениях**. Например, при вычислении $1 + (-2)$ в прямом коде результатом будет $-3$ , что, очевидно, неверно. + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline +& = 1000 \; 0011 \newline +& \rightarrow -3 +\end{aligned} +$$ + +Чтобы решить эту проблему, компьютеры ввели обратный код (1's complement). Если сначала преобразовать прямой код в обратный и выполнить вычисление $1 + (-2)$ в обратном коде, а затем перевести результат обратно в прямой код, то получится правильный результат $-1$ . + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 \; \text{(прямой код)} + 1000 \; 0010 \; \text{(прямой код)} \newline +& = 0000 \; 0001 \; \text{(обратный код)} + 1111 \; 1101 \; \text{(обратный код)} \newline +& = 1111 \; 1110 \; \text{(обратный код)} \newline +& = 1000 \; 0001 \; \text{(прямой код)} \newline +& \rightarrow -1 +\end{aligned} +$$ + +С другой стороны, **в прямом коде у нуля есть два представления: $+0$ и $-0$ **. Это означает, что числу ноль соответствуют два разных двоичных кода, что может приводить к неоднозначности. Например, если в условном выражении не различать положительный и отрицательный ноль, можно получить ошибочный результат. А если специально обрабатывать такую неоднозначность, придется вводить дополнительные проверки, что может снизить вычислительную эффективность компьютера. + +$$ +\begin{aligned} ++0 & \rightarrow 0000 \; 0000 \newline +-0 & \rightarrow 1000 \; 0000 +\end{aligned} +$$ + +Как и прямой код, обратный код тоже страдает от неоднозначности положительного и отрицательного нуля, поэтому компьютеры ввели дополнительный код (2's complement). Сначала посмотрим на процесс преобразования отрицательного нуля из прямого кода в обратный, а затем в дополнительный: + +$$ +\begin{aligned} +-0 \rightarrow \; & 1000 \; 0000 \; \text{(прямой код)} \newline += \; & 1111 \; 1111 \; \text{(обратный код)} \newline += 1 \; & 0000 \; 0000 \; \text{(дополнительный код)} \newline +\end{aligned} +$$ + +При добавлении $1$ к обратному коду отрицательного нуля возникает перенос, но длина типа `byte` составляет всего 8 бит, поэтому переполнившаяся в 9-й бит единица отбрасывается. Иными словами, **дополнительный код отрицательного нуля равен $0000 \; 0000$ и совпадает с дополнительным кодом положительного нуля**. Значит, в представлении дополнительного кода существует только один ноль, и проблема неоднозначности положительного и отрицательного нуля тем самым устраняется. + +Остается последний вопрос: диапазон типа `byte` равен $[-128, 127]$ , откуда берется лишнее отрицательное число $-128$ ? Мы замечаем, что у всех целых чисел из интервала $[-127, +127]$ есть соответствующие прямой, обратный и дополнительный коды, а прямой и дополнительный коды можно преобразовывать друг в друга. + +Однако **дополнительный код $1000 \; 0000$ является исключением: у него нет соответствующего прямого кода**. Согласно правилу преобразования, прямой код для этого дополнительного кода должен быть равен $0000 \; 0000$ . Это, очевидно, противоречие, потому что такой прямой код обозначает число $0$ , а его дополнительный код должен совпадать с ним самим. Компьютер просто определяет, что этот особый дополнительный код $1000 \; 0000$ представляет число $-128$ . На самом деле результат вычисления $(-1) + (-127)$ в дополнительном коде как раз и равен $-128$ . + +$$ +\begin{aligned} +& (-127) + (-1) \newline +& \rightarrow 1111 \; 1111 \; \text{(прямой код)} + 1000 \; 0001 \; \text{(прямой код)} \newline +& = 1000 \; 0000 \; \text{(обратный код)} + 1111 \; 1110 \; \text{(обратный код)} \newline +& = 1000 \; 0001 \; \text{(дополнительный код)} + 1111 \; 1111 \; \text{(дополнительный код)} \newline +& = 1000 \; 0000 \; \text{(дополнительный код)} \newline +& \rightarrow -128 +\end{aligned} +$$ + +Ты, вероятно, уже заметил, что все приведенные выше вычисления были операциями сложения. Это намекает на важный факт: **аппаратные схемы внутри компьютера в основном проектируются на основе операций сложения**. Причина в том, что сложение по сравнению с другими операциями (например умножением, делением и вычитанием) проще реализуется на аппаратном уровне, легче распараллеливается и выполняется быстрее. + +Обрати внимание: это не означает, что компьютер умеет только складывать. **Комбинируя сложение с некоторыми базовыми логическими операциями, компьютер может реализовать и другие математические операции**. Например, вычитание $a - b$ можно преобразовать в сложение $a + (-b)$ ; умножение и деление можно свести к многократному сложению или вычитанию. + +Теперь можно подвести итог, почему компьютеры используют дополнительный код: с представлением в дополнительном коде компьютер может использовать одни и те же схемы и операции для сложения положительных и отрицательных чисел, без необходимости проектировать специальные аппаратные схемы для вычитания, и без особой обработки неоднозначности положительного и отрицательного нуля. Это значительно упрощает аппаратную архитектуру и повышает эффективность вычислений. + +Идея дополнительного кода очень изящна; из-за ограничений по объему мы на этом остановимся. Если тебе интересно, стоит изучить эту тему глубже. + +## Кодирование чисел с плавающей точкой + +Внимательный читатель может заметить: `int` и `float` имеют одинаковую длину, по 4 байта , но почему диапазон значений у `float` намного больше, чем у `int` ? Это выглядит парадоксально, ведь `float` должен еще представлять дробные числа, а значит диапазон вроде бы должен быть меньше. + +На самом деле **это связано с тем, что число с плавающей точкой `float` использует другой способ представления**. Обозначим двоичное число длиной 32 бита как: + +$$ +b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 +$$ + +Согласно стандарту IEEE 754, 32-битный `float` состоит из следующих трех частей. + +- Бит знака $\mathrm{S}$ : занимает 1 бит и соответствует $b_{31}$ . +- Биты экспоненты $\mathrm{E}$ : занимают 8 бит и соответствуют $b_{30} b_{29} \ldots b_{23}$ . +- Биты мантиссы $\mathrm{N}$ : занимают 23 бита и соответствуют $b_{22} b_{21} \ldots b_0$ . + +Формула вычисления значения, соответствующего двоичному числу `float`, имеет вид: + +$$ +\text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 +$$ + +Если перейти к десятичной записи, формула вычисления будет такой: + +$$ +\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) +$$ + +Диапазоны значений соответствующих частей таковы: + +$$ +\begin{aligned} +\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline +(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] +\end{aligned} +$$ + +![Пример вычисления float по стандарту IEEE 754](number_encoding.assets/ieee_754_float.png) + +Посмотрим на рисунок выше: если взять пример $\mathrm{S} = 0$ , $\mathrm{E} = 124$ , $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ , то получим: + +$$ +\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 +$$ + +Теперь мы можем ответить на исходный вопрос: **в представлении `float` присутствуют биты экспоненты, поэтому его диапазон значений намного больше, чем у `int`**. Согласно приведенным выше вычислениям, максимально возможное положительное число для `float` равно $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ; если изменить бит знака, получим минимальное отрицательное число. + +**Хотя число с плавающей точкой `float` расширяет диапазон значений, побочным эффектом становится потеря точности**. Целочисленный тип `int` использует все 32 бита для представления числа, и числа распределены равномерно; а из-за существования битов экспоненты у `float` чем больше число, тем больше обычно становится разница между двумя соседними представимыми значениями. + +Как показано в таблице ниже, значения экспоненты $\mathrm{E} = 0$ и $\mathrm{E} = 255$ имеют специальный смысл и **используются для представления нуля, бесконечности, $\mathrm{NaN}$ и т.д.** + +

Таблица   Значение поля экспоненты

+ +| Поле экспоненты E | Поле мантиссы $\mathrm{N} = 0$ | Поле мантиссы $\mathrm{N} \ne 0$ | Формула вычисления | +| ------------------- | ------------------------------ | -------------------------------- | ----------------------------------------------------------------------- | +| $0$ | $\pm 0$ | Денормализованное число | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | +| $1, 2, \dots, 254$ | Нормализованное число | Нормализованное число | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | +| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | + +Стоит отметить, что денормализованные числа заметно повышают точность чисел с плавающей точкой. Наименьшее положительное нормализованное число равно $2^{-126}$ , а наименьшее положительное денормализованное число равно $2^{-126} \times 2^{-23}$ . + +Двойная точность `double` использует способ представления, аналогичный `float` , поэтому здесь мы не будем подробно останавливаться на нем. diff --git a/ru/docs/chapter_data_structure/summary.md b/ru/docs/chapter_data_structure/summary.md new file mode 100644 index 000000000..e17c25b8c --- /dev/null +++ b/ru/docs/chapter_data_structure/summary.md @@ -0,0 +1,66 @@ +# Резюме + +### Ключевые выводы + +- Структуры данных можно классифицировать с двух точек зрения: логической структуры и физической структуры. Логическая структура описывает логические связи между элементами данных, а физическая структура описывает способ хранения данных в памяти компьютера. +- К распространенным логическим структурам относятся линейные, древовидные и сетевые. Обычно мы делим структуры данных по логической структуре на линейные (массивы, связные списки, стеки, очереди) и нелинейные (деревья, графы, кучи). Реализация хеш-таблицы может одновременно включать линейные и нелинейные структуры данных. +- Во время работы программы данные хранятся в памяти компьютера. У каждого участка памяти есть собственный адрес, и программа обращается к данным именно по этим адресам. +- Физическая структура в основном делится на хранение в непрерывном пространстве (массивы) и хранение в разрозненном пространстве (связные списки). Все структуры данных реализуются на основе массивов, связных списков или их комбинации. +- К базовым типам данных в компьютере относятся целые `byte` , `short` , `int` , `long` , числа с плавающей точкой `float` , `double` , символы `char` и логический тип `bool` . Их диапазон значений определяется объемом занимаемого пространства и способом представления. +- Прямой код, обратный код и дополнительный код - это три способа кодирования чисел в компьютере, между которыми можно выполнять взаимные преобразования. В прямом коде старший бит целого числа является знаковым, а остальные биты представляют значение числа. +- Целые числа в компьютере хранятся в виде дополнительного кода. В таком представлении компьютер может одинаково обрабатывать сложение положительных и отрицательных чисел, не проектируя специальную аппаратную схему отдельно для вычитания, и при этом не возникает неоднозначности положительного и отрицательного нуля. +- Кодирование числа с плавающей точкой состоит из 1 бита знака, 8 битов экспоненты и 23 битов мантиссы. Благодаря наличию экспоненты диапазон значений у чисел с плавающей точкой намного больше, чем у целых, но расплачиваться за это приходится точностью. +- ASCII - это самый ранний набор английских символов длиной 1 байт, включающий в общей сложности 127 символов. Набор GBK - распространенный китайский набор символов, включающий более двадцати тысяч иероглифов. Unicode стремится предоставить единый полный стандарт набора символов, включающий символы всех языков мира, чтобы решить проблемы искаженного текста, вызванные несовместимыми способами кодирования. +- UTF-8 - самый популярный способ кодирования Unicode, обладающий очень хорошей универсальностью. Это кодировка переменной длины, хорошо расширяемая и эффективно использующая память. UTF-16 и UTF-32 относятся к кодировкам фиксированной длины. При кодировании китайского текста UTF-16 занимает меньше места, чем UTF-8. Такие языки программирования, как Java и C#, по умолчанию используют UTF-16. + +### Q & A + +**Q**: Почему хеш-таблица одновременно включает линейные и нелинейные структуры данных? + +В основе хеш-таблицы лежит массив, а для разрешения коллизий мы можем использовать "цепочки адресации" (об этом будет рассказано в последующем разделе "Хеш-коллизии"): каждый бакет массива указывает на связный список, а если длина списка превышает некоторый порог, он может быть преобразован в дерево (обычно в красно-черное дерево). + +С точки зрения хранения данных в основе хеш-таблицы находится массив, где каждый слот бакета может содержать либо отдельное значение, либо связный список, либо дерево. Поэтому хеш-таблица действительно может одновременно включать линейные структуры данных (массивы, списки) и нелинейные структуры данных (деревья). + +**Q**: Длина типа `char` равна 1 байту? + +Длина типа `char` определяется используемым в языке программирования способом кодирования. Например, Java, JavaScript, TypeScript и C# используют кодировку UTF-16 (для хранения кодовых точек Unicode), поэтому длина `char` у них равна 2 байтам. + +**Q**: Не является ли двусмысленным утверждение, что структуры данных, реализованные на основе массива, также называются "статическими структурами данных"? Ведь стек тоже поддерживает операции push и pop, а они явно "динамические". + +Стек действительно может поддерживать динамические операции над данными, но сама структура данных при этом остается "статической" (ее длина неизменна). Хотя структуры на основе массива могут динамически добавлять и удалять элементы, их емкость фиксирована. Если количество данных превышает заранее выделенный размер, приходится создавать новый, более крупный массив и копировать в него содержимое старого. + +**Q**: При построении стека (очереди) его размер не задается явно, почему же его относят к "статическим структурам данных"? + +В языках высокого уровня нам не нужно вручную задавать начальную емкость стека (очереди): это автоматически делает сама реализация класса. Например, начальная емкость `ArrayList` в Java обычно равна 10. Кроме того, автоматом реализуется и расширение емкости. Подробнее это рассматривается в последующем разделе о "списках". + +**Q**: Если метод преобразования из прямого кода в дополнительный - это "сначала инвертировать, затем прибавить 1", то обратное преобразование из дополнительного кода в прямой, по идее, должно быть обратной операцией "сначала вычесть 1, затем инвертировать". Почему же дополнительный код также можно перевести в прямой тем же способом "сначала инвертировать, затем прибавить 1"? + +Это связано с тем, что взаимное преобразование прямого и дополнительного кодов по сути является вычислением "дополнения". Сначала дадим определение дополнения: если $a + b = c$ , то говорят, что $a$ является дополнением числа $b$ до $c$ ; аналогично, $b$ является дополнением числа $a$ до $c$ . + +Для двоичного числа длины $n = 4$ со значением $0010$ , если рассматривать его как прямой код (не учитывая знаковый бит), то его дополнительный код получается правилом "сначала инвертировать, затем прибавить 1": + +$$ +0010 \rightarrow 1101 \rightarrow 1110 +$$ + +Мы видим, что сумма прямого и дополнительного кодов равна $0010 + 1110 = 10000$ , то есть дополнительный код $1110$ является "дополнением" прямого кода $0010$ до $10000$ . **Это означает, что описанная выше операция "сначала инвертировать, затем прибавить 1" на самом деле вычисляет дополнение до $10000$ **. + +Тогда чему равно "дополнение" дополнительного кода $1110$ до $10000$ ? Мы снова можем получить его правилом "сначала инвертировать, затем прибавить 1": + +$$ +1110 \rightarrow 0001 \rightarrow 0010 +$$ + +Иначе говоря, прямой и дополнительный коды являются взаимными "дополнениями" друг друга до $10000$ , поэтому и "прямой код -> дополнительный код", и "дополнительный код -> прямой код" можно реализовать одной и той же операцией (сначала инвертировать, затем прибавить 1). + +Разумеется, можно получить прямой код из дополнительного кода $1110$ и обратной операцией, то есть "сначала вычесть 1, затем инвертировать": + +$$ +1110 \rightarrow 1101 \rightarrow 0010 +$$ + +В итоге и "сначала инвертировать, затем прибавить 1", и "сначала вычесть 1, затем инвертировать" - это два эквивалентных способа вычисления дополнения до $10000$ . + +По сути операция "инвертировать" сама по себе вычисляет дополнение до $1111$ (потому что всегда выполняется `прямой код + обратный код = 1111` ); а дополнительный код, получающийся после добавления 1 к обратному коду, и есть дополнение до $10000$ . + +Приведенный выше пример использовал $n = 4$ , но его можно обобщить на двоичные числа любой длины. diff --git a/ru/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png b/ru/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png new file mode 100644 index 000000000..a67cf0f9c Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png differ diff --git a/ru/docs/chapter_divide_and_conquer/binary_search_recur.md b/ru/docs/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 000000000..6d33a6b2e --- /dev/null +++ b/ru/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,45 @@ +# Поисковая стратегия divide and conquer + +Мы уже знаем, что алгоритмы поиска делятся на две большие категории. + +- **Полный перебор**: реализуется через обход структуры данных, временная сложность равна $O(n)$ . +- **Адаптивный поиск**: использует особую организацию данных или априорную информацию, временная сложность может достигать $O(\log n)$ и даже $O(1)$ . + +На практике **алгоритмы поиска с временной сложностью $O(\log n)$ обычно реализуются на основе стратегии divide and conquer**, например двоичный поиск и деревья. + +- На каждом шаге двоичный поиск раскладывает задачу (поиск целевого элемента в массиве) на более мелкую задачу (поиск целевого элемента в одной половине массива), и этот процесс продолжается, пока массив не станет пустым или пока не будет найден целевой элемент. +- Деревья являются типичными представителями идей divide and conquer; в таких структурах данных, как двоичное дерево поиска, AVL-дерево и куча, временная сложность различных операций равна $O(\log n)$ . + +Стратегия divide and conquer для двоичного поиска выглядит следующим образом. + +- **Задача раскладывается на части**: двоичный поиск рекурсивно разбивает исходную задачу (поиск в массиве) на подзадачу (поиск в одной половине массива), и это достигается сравнением среднего элемента с целевым значением. +- **Подзадачи независимы**: в двоичном поиске на каждом шаге обрабатывается только одна подзадача, и она не зависит от других подзадач. +- **Решения подзадач не нужно объединять**: двоичный поиск нацелен на поиск конкретного элемента, поэтому объединять решения подзадач не требуется. Как только подзадача решена, одновременно считается решенной и исходная задача. + +По сути divide and conquer повышает эффективность поиска потому, что при полном переборе за один шаг удается исключить только один вариант, **тогда как при поиске на основе divide and conquer за один шаг можно исключить половину вариантов**. + +### Реализация двоичного поиска на основе divide and conquer + +В предыдущих главах двоичный поиск реализовывался через итерацию. Теперь реализуем его с помощью divide and conquer, то есть через рекурсию. + +!!! question + + Дан отсортированный массив `nums` длины $n$ , в котором все элементы уникальны. Найдите элемент `target` . + +С точки зрения divide and conquer обозначим подзадачу, соответствующую интервалу поиска $[i, j]$ , через $f(i, j)$ . + +Начиная с исходной задачи $f(0, n-1)$ , выполняем двоичный поиск по следующим шагам. + +1. Вычислить середину $m$ интервала поиска $[i, j]$ и с ее помощью исключить половину интервала. +2. Рекурсивно решить подзадачу вдвое меньшего размера; это может быть либо $f(i, m-1)$ , либо $f(m+1, j)$ . +3. Повторять шаг `1.` и шаг `2.` , пока не будет найден `target` или пока интервал не станет пустым. + +На рисунке ниже показан процесс применения divide and conquer для поиска элемента $6$ в массиве. + +![Процесс двоичного поиска в стиле divide and conquer](binary_search_recur.assets/binary_search_recur.png) + +В реализации кода мы объявляем рекурсивную функцию `dfs()` для решения задачи $f(i, j)$ : + +```src +[file]{binary_search_recur}-[class]{}-[func]{binary_search} +``` diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png new file mode 100644 index 000000000..6a400e85d Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png new file mode 100644 index 000000000..7aa03bae6 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png new file mode 100644 index 000000000..13219c39f Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png new file mode 100644 index 000000000..aafafca5d Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png new file mode 100644 index 000000000..248fd8e4a Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png new file mode 100644 index 000000000..9eab251a2 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png new file mode 100644 index 000000000..85e27074d Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png new file mode 100644 index 000000000..886ce1317 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png new file mode 100644 index 000000000..768ff4dd9 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png new file mode 100644 index 000000000..02b797ca6 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png new file mode 100644 index 000000000..d0c9addd0 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png new file mode 100644 index 000000000..24cc07d60 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png new file mode 100644 index 000000000..f3b127917 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png differ diff --git a/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.md new file mode 100644 index 000000000..b09a7f320 --- /dev/null +++ b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -0,0 +1,99 @@ +# Задача построения двоичного дерева + +!!! question + + Даны прямой обход `preorder` и симметричный обход `inorder` некоторого двоичного дерева. Постройте по ним двоичное дерево и верните его корневой узел. Предполагается, что в дереве нет узлов с одинаковыми значениями (как показано на рисунке ниже). + +![Пример данных для построения двоичного дерева](build_binary_tree_problem.assets/build_tree_example.png) + +### Проверка, является ли это задачей divide and conquer + +Исходная задача - построить двоичное дерево по `preorder` и `inorder` - является типичной задачей divide and conquer. + +- **Задача раскладывается на части**: если смотреть с точки зрения divide and conquer, исходную задачу можно разбить на две подзадачи: построение левого поддерева и построение правого поддерева, плюс одно действие: инициализация корневого узла. Для каждого поддерева (подзадачи) можно использовать тот же способ разбиения, пока не будет достигнута наименьшая подзадача (пустое поддерево). +- **Подзадачи независимы**: левое и правое поддеревья независимы друг от друга и не пересекаются. При построении левого поддерева нам нужно смотреть только на ту часть прямого и симметричного обходов, которая соответствует левому поддереву. Для правого поддерева рассуждение аналогично. +- **Решения подзадач можно объединить**: когда левое и правое поддеревья (решения подзадач) уже построены, их можно присоединить к корневому узлу и тем самым получить решение исходной задачи. + +### Как разделить поддеревья + +Из анализа выше видно, что эта задача действительно решается через divide and conquer, **но как именно, имея прямой обход `preorder` и симметричный обход `inorder`, разделить левое и правое поддеревья**? + +По определению и `preorder` , и `inorder` можно разбить на три части. + +- Прямой обход: `[ корневой узел | левое поддерево | правое поддерево ]` , например для дерева на рисунке выше это `[ 3 | 9 | 2 1 7 ]` . +- Симметричный обход: `[ левое поддерево | корневой узел | правое поддерево ]` , например для дерева на рисунке выше это `[ 9 | 3 | 1 2 7 ]` . + +На примере данных с рисунка можно получить результат разбиения по следующим шагам. + +1. Первый элемент прямого обхода, равный 3, является значением корневого узла. +2. Найти индекс корневого узла 3 в `inorder` ; используя этот индекс, можно разбить `inorder` на `[ 9 | 3 | 1 2 7 ]` . +3. По результату разбиения `inorder` нетрудно определить, что число узлов в левом и правом поддеревьях равно 1 и 3 соответственно, а значит, `preorder` можно разбить как `[ 3 | 9 | 2 1 7 ]` . + +![Разбиение поддеревьев в прямом и симметричном обходах](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) + +### Описание интервалов поддеревьев через переменные + +Согласно описанному выше способу разбиения, **мы уже получили интервалы индексов корневого узла, левого и правого поддеревьев в `preorder` и `inorder`**. Чтобы описывать эти интервалы, нам понадобится несколько указателей-переменных. + +- Обозначим индекс корневого узла текущего дерева в `preorder` через $i$ . +- Обозначим индекс корневого узла текущего дерева в `inorder` через $m$ . +- Обозначим интервал индексов текущего дерева в `inorder` через $[l, r]$ . + +Как показано в таблице ниже, этих переменных достаточно для описания индекса корневого узла в `preorder` и интервалов поддеревьев в `inorder` . + +

Таблица   Индексы корневого узла и поддеревьев в прямом и симметричном обходах

+ +| | Индекс корневого узла в `preorder` | Интервал индексов поддерева в `inorder` | +| ---------------- | ---------------------------------- | ---------------------------------------- | +| Текущее дерево | $i$ | $[l, r]$ | +| Левое поддерево | $i + 1$ | $[l, m-1]$ | +| Правое поддерево | $i + 1 + (m - l)$ | $[m+1, r]$ | + +Обратите внимание, что $(m-l)$ в индексе корневого узла правого поддерева означает "число узлов в левом поддереве"; лучше всего понимать это выражение вместе с рисунком ниже. + +![Представление индексных интервалов корня и поддеревьев](build_binary_tree_problem.assets/build_tree_division_pointers.png) + +### Реализация кода + +Чтобы ускорить поиск $m$ , мы используем хеш-таблицу `hmap` для хранения отображения значений массива `inorder` в индексы: + +```src +[file]{build_tree}-[class]{}-[func]{build_tree} +``` + +На рисунке ниже показан рекурсивный процесс построения двоичного дерева: каждый узел создается в фазе "спуска", а каждое ребро (ссылка) формируется в фазе "подъема". + +=== "<1>" + ![Рекурсивный процесс построения двоичного дерева](build_binary_tree_problem.assets/built_tree_step1.png) + +=== "<2>" + ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) + +=== "<3>" + ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) + +=== "<4>" + ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) + +=== "<5>" + ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) + +=== "<6>" + ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) + +=== "<7>" + ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) + +=== "<8>" + ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) + +=== "<9>" + ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) + +Результаты разбиения `preorder` и `inorder` внутри каждого рекурсивного вызова показаны на рисунке ниже. + +![Результаты разбиения в каждом рекурсивном вызове](build_binary_tree_problem.assets/built_tree_overall.png) + +Пусть число узлов дерева равно $n$ ; инициализация каждого узла (то есть выполнение одного рекурсивного вызова `dfs()` ) занимает $O(1)$ времени. **Следовательно, общая временная сложность равна $O(n)$** . + +Хеш-таблица хранит отображение значений `inorder` в индексы, поэтому ее пространственная сложность равна $O(n)$ . В худшем случае, когда двоичное дерево вырождается в связный список, глубина рекурсии достигает $n$ и требует $O(n)$ памяти стека. **Следовательно, общая пространственная сложность также равна $O(n)$** . diff --git a/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png new file mode 100644 index 000000000..51b4d97ee Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png differ diff --git a/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png new file mode 100644 index 000000000..1d3a79cf7 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png differ diff --git a/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png new file mode 100644 index 000000000..564170df6 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png differ diff --git a/ru/docs/chapter_divide_and_conquer/divide_and_conquer.md b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.md new file mode 100644 index 000000000..a46ac66e5 --- /dev/null +++ b/ru/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -0,0 +1,91 @@ +# Алгоритмы "разделяй и властвуй" + +Разделяй и властвуй (divide and conquer) - это очень важная и широко используемая стратегия построения алгоритмов. Обычно она реализуется через рекурсию и включает два этапа: "разделение" и "решение". + +1. **Разделение (этап декомпозиции)**: рекурсивно разбить исходную задачу на две или более подзадачи, пока не будет достигнута наименьшая подзадача. +2. **Решение (этап объединения)**: начиная с уже известных решений наименьших подзадач, снизу вверх объединять решения подзадач и тем самым получать решение исходной задачи. + +Как показано на рисунке ниже, "сортировка слиянием" является одним из типичных примеров применения стратегии "разделяй и властвуй". + +1. **Разделение**: рекурсивно разделить исходный массив (исходную задачу) на два подмассива (подзадачи), пока в подмассиве не останется только один элемент (наименьшая подзадача). +2. **Решение**: снизу вверх объединять упорядоченные подмассивы (решения подзадач), чтобы получить упорядоченный исходный массив (решение исходной задачи). + +![Стратегия divide and conquer в сортировке слиянием](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) + +## Как определить задачу divide and conquer + +Чтобы понять, подходит ли задача для решения методом divide and conquer, обычно можно ориентироваться на следующие критерии. + +1. **Задача раскладывается на части**: исходную задачу можно разбить на более мелкие и похожие подзадачи, причем такое разбиение можно применять рекурсивно. +2. **Подзадачи независимы**: подзадачи не пересекаются, не зависят друг от друга и могут решаться независимо. +3. **Решения подзадач можно объединить**: решение исходной задачи получается объединением решений подзадач. + +Очевидно, что сортировка слиянием удовлетворяет всем трем критериям. + +1. **Задача раскладывается на части**: массив (исходная задача) рекурсивно делится на два подмассива (подзадачи). +2. **Подзадачи независимы**: каждый подмассив можно сортировать отдельно (то есть каждую подзадачу можно решать независимо). +3. **Решения подзадач можно объединить**: два упорядоченных подмассива (решения подзадач) можно объединить в один упорядоченный массив (решение исходной задачи). + +## Повышение эффективности с помощью divide and conquer + +**Стратегия divide and conquer не только позволяет эффективно решать алгоритмические задачи, но и часто повышает эффективность самих алгоритмов**. Именно поэтому быстрая сортировка, сортировка слиянием и пирамидальная сортировка обычно работают быстрее, чем сортировка выбором, пузырьком и вставками. + +Тогда возникает естественный вопрос: **почему divide and conquer повышает эффективность алгоритма и какова логика этого на более глубоком уровне**? Иными словами, почему разбиение большой задачи на несколько подзадач, решение этих подзадач и последующее объединение их решений оказывается эффективнее, чем прямое решение исходной задачи? Этот вопрос можно рассмотреть с двух сторон: через число операций и через параллельные вычисления. + +### Оптимизация числа операций + +Рассмотрим "сортировку пузырьком": для массива длины $n$ ей требуется $O(n^2)$ времени. Предположим, что мы разделим массив на два подмассива в середине, как показано на рисунке ниже. Тогда само разбиение потребует $O(n)$ времени, сортировка каждого подмассива займет $O((n / 2)^2)$ времени, а объединение двух подмассивов потребует еще $O(n)$ времени. Общая временная сложность будет равна: + +$$ +O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) +$$ + +![Сортировка пузырьком до и после разбиения массива](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) + +Теперь рассмотрим следующее неравенство, в котором левая и правая части обозначают общее число операций до разбиения и после него: + +$$ +\begin{aligned} +n^2 & > \frac{n^2}{2} + 2n \newline +n^2 - \frac{n^2}{2} - 2n & > 0 \newline +n(n - 4) & > 0 +\end{aligned} +$$ + +**Это означает, что при $n > 4$ число операций после разбиения становится меньше, а значит, сортировка должна работать быстрее**. При этом важно заметить, что временная сложность после разбиения все еще остается квадратичной, то есть $O(n^2)$ ; уменьшается лишь константный множитель. + +Если пойти дальше и **продолжать делить каждый подмассив пополам**, пока в нем не останется только один элемент, то мы фактически получим "сортировку слиянием", чья временная сложность равна $O(n \log n)$ . + +Можно пойти еще дальше и спросить: **что если задать несколько точек разделения** и равномерно разбить исходный массив на $k$ подмассивов? Такая ситуация очень похожа на "блочную сортировку", которая особенно хорошо подходит для сортировки очень больших объемов данных и теоретически может достигать временной сложности $O(n + k)$ . + +### Оптимизация параллельных вычислений + +Мы знаем, что подзадачи, порождаемые divide and conquer, являются независимыми, **а значит, их обычно можно решать параллельно**. Иначе говоря, divide and conquer не только может уменьшить временную сложность алгоритма, **но и хорошо сочетается с параллельной оптимизацией на уровне системы**. + +Параллельная оптимизация особенно эффективна в среде с несколькими ядрами или несколькими процессорами, потому что система может одновременно обрабатывать разные подзадачи, лучше загружая вычислительные ресурсы и тем самым заметно сокращая общее время работы. + +Например, в показанной ниже "блочной сортировке" большой объем данных равномерно распределяется по блокам. Тогда сортировку каждого блока можно поручить отдельным вычислительным единицам, а после завершения просто объединить результаты. + +![Параллельные вычисления в блочной сортировке](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) + +## Типичные применения divide and conquer + +С одной стороны, divide and conquer можно использовать для решения многих классических алгоритмических задач. + +- **Поиск ближайшей пары точек**: сначала множество точек делится на две части, затем ищется ближайшая пара в каждой части, а затем ближайшая пара, пересекающая границу между двумя частями. +- **Умножение больших чисел**: например, алгоритм Карацубы, который раскладывает умножение больших чисел на несколько умножений и сложений меньших чисел. +- **Умножение матриц**: например, алгоритм Штрассена, который раскладывает умножение больших матриц на несколько умножений и сложений матриц меньшего размера. +- **Задача о Ханойской башне**: задача о Ханойской башне решается рекурсивно и является типичным примером применения divide and conquer. +- **Подсчет инверсий**: если в последовательности предыдущее число больше следующего, то такая пара образует инверсию. Эту задачу можно решить с помощью идей divide and conquer, опираясь на сортировку слиянием. + +С другой стороны, divide and conquer очень широко применяется при проектировании алгоритмов и структур данных. + +- **Двоичный поиск**: двоичный поиск делит отсортированный массив на две части по индексу середины, а затем, в зависимости от результата сравнения целевого значения со средним элементом, исключает одну из половин и повторяет ту же операцию на оставшемся интервале. +- **Сортировка слиянием**: она уже была рассмотрена в начале этого раздела, поэтому не будем повторяться. +- **Быстрая сортировка**: в ней выбирается опорное значение, после чего массив делится на два подмассива: один содержит элементы меньше опорного, а другой - больше. Затем такая же операция повторяется для обеих частей, пока в подмассиве не останется один элемент. +- **Блочная сортировка**: ее основная идея заключается в распределении данных по нескольким блокам, сортировке элементов внутри каждого блока и последующем последовательном извлечении элементов из блоков для построения отсортированного массива. +- **Деревья**: например, двоичные деревья поиска, AVL-деревья, красно-черные деревья, B-деревья, B+ деревья и т.д. Их операции поиска, вставки и удаления можно рассматривать как применение divide and conquer. +- **Кучи**: куча является особым видом полного бинарного дерева, а такие операции, как вставка, удаление и упорядочивание, по сути содержат идеи divide and conquer. +- **Хеш-таблицы**: хотя хеш-таблицы напрямую не используют divide and conquer, некоторые способы разрешения коллизий косвенно опираются на эту стратегию. Например, длинные цепочки в методе цепочек могут преобразовываться в красно-черные деревья для повышения эффективности поиска. + +Нетрудно заметить, что **divide and conquer - это "тихая" алгоритмическая идея**, скрыто присутствующая внутри самых разных алгоритмов и структур данных. diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png new file mode 100644 index 000000000..437eae7d5 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png new file mode 100644 index 000000000..e17ebf4b7 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png new file mode 100644 index 000000000..a554b7fdf Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png new file mode 100644 index 000000000..cd5be7064 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png new file mode 100644 index 000000000..24e78dfa5 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png new file mode 100644 index 000000000..27ec075b4 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png new file mode 100644 index 000000000..bda652ace Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png new file mode 100644 index 000000000..be30dbdc2 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png new file mode 100644 index 000000000..193a3e16c Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png new file mode 100644 index 000000000..5ca9fc6ef Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png new file mode 100644 index 000000000..f514516eb Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png new file mode 100644 index 000000000..e5f0b6978 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png new file mode 100644 index 000000000..3dd928054 Binary files /dev/null and b/ru/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png differ diff --git a/ru/docs/chapter_divide_and_conquer/hanota_problem.md b/ru/docs/chapter_divide_and_conquer/hanota_problem.md new file mode 100644 index 000000000..4f5af6af1 --- /dev/null +++ b/ru/docs/chapter_divide_and_conquer/hanota_problem.md @@ -0,0 +1,97 @@ +# Задача о Ханойской башне + +В задачах сортировки слиянием и построения двоичного дерева мы делили исходную задачу на две подзадачи, каждая из которых имела размер, равный примерно половине исходной задачи. Однако для задачи о Ханойской башне используется другая стратегия разбиения. + +!!! question + + Даны три стержня, обозначенные как `A` , `B` и `C` . В начальном состоянии на стержне `A` находятся $n$ дисков, расположенных сверху вниз в порядке от меньшего к большему. Нужно переместить эти $n$ дисков на стержень `C` , сохранив их исходный порядок (как показано на рисунке ниже). Во время перемещения дисков необходимо соблюдать следующие правила. + + 1. Диск можно снять только с вершины одного стержня и положить только на вершину другого стержня. + 2. За один раз можно перемещать только один диск. + 3. Меньший диск всегда должен лежать на большем. + +![Пример задачи о Ханойской башне](hanota_problem.assets/hanota_example.png) + +**Обозначим задачу о Ханойской башне размера $i$ как $f(i)$** . Например, $f(3)$ означает задачу перемещения 3 дисков со стержня `A` на стержень `C` . + +### Рассмотрим базовые случаи + +Как показано на рисунке ниже, для задачи $f(1)$ , то есть когда имеется только один диск, достаточно просто переместить его напрямую со стержня `A` на стержень `C` . + +=== "<1>" + ![Решение задачи размера 1](hanota_problem.assets/hanota_f1_step1.png) + +=== "<2>" + ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) + +Как показано на рисунке ниже, для задачи $f(2)$ , то есть когда есть два диска, **поскольку меньший диск все время должен лежать на большем, приходится использовать `B` как вспомогательный стержень**. + +1. Сначала переместить верхний маленький диск с `A` на `B` . +2. Затем переместить большой диск с `A` на `C` . +3. Наконец, переместить маленький диск с `B` на `C` . + +=== "<1>" + ![Решение задачи размера 2](hanota_problem.assets/hanota_f2_step1.png) + +=== "<2>" + ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) + +=== "<3>" + ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) + +=== "<4>" + ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) + +Процесс решения задачи $f(2)$ можно кратко описать так: **переместить два диска с `A` на `C` с помощью `B`** . Здесь `C` называется целевым стержнем, а `B` - буферным стержнем. + +### Разбиение на подзадачи + +Для задачи $f(3)$ , то есть когда имеется три диска, ситуация становится немного сложнее. + +Поскольку решения $f(1)$ и $f(2)$ уже известны, можно подойти к задаче с точки зрения divide and conquer и **рассматривать два верхних диска на `A` как единое целое**, выполняя шаги, показанные на рисунке ниже. Так три диска успешно перемещаются с `A` на `C` . + +1. Сделать `B` целевым стержнем, а `C` буферным, и переместить два диска с `A` на `B` . +2. Переместить оставшийся один диск с `A` напрямую на `C` . +3. Сделать `C` целевым стержнем, а `A` буферным, и переместить два диска с `B` на `C` . + +=== "<1>" + ![Решение задачи размера 3](hanota_problem.assets/hanota_f3_step1.png) + +=== "<2>" + ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) + +=== "<3>" + ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) + +=== "<4>" + ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) + +По своей сути **мы разбиваем задачу $f(3)$ на две подзадачи $f(2)$ и одну подзадачу $f(1)$** . Если последовательно решить эти три подзадачи, исходная задача тоже будет решена. Это показывает, что подзадачи независимы и что их решения можно объединить. + +Таким образом, можно сформулировать показанную на рисунке ниже стратегию divide and conquer для задачи о Ханойской башне: исходная задача $f(n)$ разбивается на две подзадачи $f(n-1)$ и одну подзадачу $f(1)$ , которые затем решаются в следующем порядке. + +1. Переместить $n-1$ дисков с `A` на `B` с помощью `C` . +2. Переместить оставшийся $1$ диск напрямую с `A` на `C` . +3. Переместить $n-1$ дисков с `B` на `C` с помощью `A` . + +Для двух подзадач $f(n-1)$ **можно применять тот же способ рекурсивного разбиения**, пока не будет достигнута наименьшая подзадача $f(1)$ . А решение для $f(1)$ уже известно и требует всего одного перемещения. + +![Стратегия divide and conquer для решения задачи о Ханойской башне](hanota_problem.assets/hanota_divide_and_conquer.png) + +### Реализация кода + +В коде мы объявляем рекурсивную функцию `dfs(i, src, buf, tar)` , которая перемещает $i$ верхних дисков со стержня `src` на целевой стержень `tar` с помощью буферного стержня `buf` : + +```src +[file]{hanota}-[class]{}-[func]{solve_hanota} +``` + +Как показано на рисунке ниже, задача о Ханойской башне формирует дерево рекурсии высоты $n$ , в котором каждый узел представляет подзадачу и соответствует одному открытому вызову `dfs()` ; **поэтому временная сложность равна $O(2^n)$ , а пространственная сложность равна $O(n)$** . + +![Дерево рекурсии задачи о Ханойской башне](hanota_problem.assets/hanota_recursive_tree.png) + +!!! quote + + Задача о Ханойской башне происходит из древней легенды. В одном из храмов древней Индии монахи имели три высоких алмазных стержня и $64$ золотых диска разного размера. Монахи непрерывно перекладывали диски и верили, что в тот момент, когда последний диск будет правильно перенесен, мир подойдет к концу. + + Однако даже если бы монахи перемещали по одному диску в секунду, им понадобилось бы примерно $2^{64} \approx 1.84×10^{19}$ секунд, то есть около $585$ миллиардов лет, что намного превышает текущую оценку возраста Вселенной. Поэтому, если легенда и верна, нам, вероятно, пока не о чем беспокоиться. diff --git a/ru/docs/chapter_divide_and_conquer/index.md b/ru/docs/chapter_divide_and_conquer/index.md new file mode 100644 index 000000000..ed69ea202 --- /dev/null +++ b/ru/docs/chapter_divide_and_conquer/index.md @@ -0,0 +1,9 @@ +# Разделяй и властвуй + +![Разделяй и властвуй](../assets/covers/chapter_divide_and_conquer.jpg) + +!!! abstract + + Сложная задача раскладывается слой за слоем, и каждое новое разбиение делает ее проще. + + Принцип "разделяй и властвуй" показывает важный факт: если начать с простого, многое перестает быть сложным. diff --git a/ru/docs/chapter_divide_and_conquer/summary.md b/ru/docs/chapter_divide_and_conquer/summary.md new file mode 100644 index 000000000..11030f7d0 --- /dev/null +++ b/ru/docs/chapter_divide_and_conquer/summary.md @@ -0,0 +1,13 @@ +# Резюме + +### Ключевые выводы + +- Divide and conquer - это распространенная стратегия проектирования алгоритмов, которая включает два этапа: разделение (декомпозицию) и решение (объединение), и обычно реализуется с помощью рекурсии. +- Критерии применимости этой стратегии к задаче включают: возможность разложения задачи, независимость подзадач и возможность объединения их решений. +- Сортировка слиянием является типичным применением divide and conquer: она рекурсивно делит массив на два равных по длине подмассива, пока не останется массив из одного элемента, после чего начинает поэтапное объединение. +- Введение стратегии divide and conquer часто позволяет повысить эффективность алгоритма. С одной стороны, стратегия уменьшает число операций; с другой - после разбиения она способствует параллельной оптимизации на уровне системы. +- Divide and conquer не только помогает решать многие алгоритмические задачи, но и широко используется при проектировании структур данных и алгоритмов, поэтому его можно встретить буквально повсюду. +- По сравнению с полным перебором адаптивный поиск работает эффективнее. Алгоритмы поиска со сложностью $O(\log n)$ обычно реализуются на основе стратегии divide and conquer. +- Двоичный поиск - еще одно типичное применение divide and conquer, в котором отсутствует шаг объединения решений подзадач. Мы можем реализовать двоичный поиск рекурсивно, через divide and conquer. +- В задаче построения двоичного дерева исходная задача построения дерева может быть разбита на две подзадачи: построение левого и правого поддеревьев, а реализуется это через разбиение индексных интервалов прямого и симметричного обходов. +- В задаче о Ханойской башне задача размера $n$ разбивается на две подзадачи размера $n-1$ и одну подзадачу размера $1$ . После последовательного решения этих трех подзадач исходная задача также оказывается решенной. diff --git a/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png new file mode 100644 index 000000000..5333e445e Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png new file mode 100644 index 000000000..d6bccd952 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png new file mode 100644 index 000000000..a3a200e54 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png new file mode 100644 index 000000000..c1c605c61 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_problem_features.md b/ru/docs/chapter_dynamic_programming/dp_problem_features.md new file mode 100644 index 000000000..b60a53d21 --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/dp_problem_features.md @@ -0,0 +1,101 @@ +# Свойства задач динамического программирования + +В предыдущем разделе мы увидели, как динамическое программирование решает исходную задачу через разложение на подзадачи. На самом деле разложение на подзадачи - это общий алгоритмический подход, но в divide and conquer, динамическом программировании и backtracking акценты расставлены по-разному. + +- Алгоритмы divide and conquer рекурсивно раскладывают исходную задачу на несколько независимых подзадач, пока не будет достигнута наименьшая подзадача, а затем в процессе возврата объединяют решения подзадач в решение исходной задачи. +- Динамическое программирование тоже раскладывает задачу рекурсивно, но его главное отличие от divide and conquer в том, что подзадачи здесь зависят друг от друга и в процессе разложения возникает много перекрывающихся подзадач. +- Алгоритм backtracking перебирает все возможные решения через попытки и откат и с помощью обрезки избегает ненужных ветвей поиска. Решение исходной задачи состоит из последовательности решений, и подзадачей можно считать префикс этой последовательности решений. + +На практике динамическое программирование часто применяется для задач оптимизации. Такие задачи не только содержат перекрывающиеся подзадачи, но и обладают еще двумя важными свойствами: оптимальной подструктурой и отсутствием последствий. + +## Оптимальная подструктура + +Немного изменим задачу о подъеме по лестнице, чтобы нагляднее показать понятие оптимальной подструктуры. + +!!! question "Минимальная стоимость подъема по лестнице" + + Дана лестница, по которой можно подниматься на $1$ или на $2$ ступени за раз. На каждой ступени указано неотрицательное целое число, обозначающее цену попадания на эту ступень. Дан массив неотрицательных целых чисел $cost$ , где $cost[i]$ - это цена для ступени $i$ , а $cost[0]$ соответствует земле (начальной позиции). Найдите минимальную суммарную стоимость, необходимую для достижения вершины. + +Как показано на рисунке ниже, если цены для ступеней $1$ , $2$ и $3$ равны соответственно $1$ , $10$ и $1$ , то минимальная стоимость подъема с земли на третью ступень равна $2$ . + +![Минимальная стоимость подъема на 3-ю ступень](dp_problem_features.assets/min_cost_cs_example.png) + +Пусть $dp[i]$ обозначает накопленную стоимость подъема на ступень $i$ . Поскольку на ступень $i$ можно прийти только со ступени $i - 1$ или со ступени $i - 2$ , значение $dp[i]$ может быть либо $dp[i - 1] + cost[i]$ , либо $dp[i - 2] + cost[i]$ . Чтобы минимизировать стоимость, нужно выбрать меньший из этих двух вариантов: + +$$ +dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] +$$ + +Отсюда и возникает смысл оптимальной подструктуры: **оптимальное решение исходной задачи строится из оптимальных решений подзадач**. + +Очевидно, что эта задача обладает оптимальной подструктурой: мы берем лучшее из двух оптимальных решений подзадач $dp[i-1]$ и $dp[i-2]$ и на его основе строим оптимальное решение исходной задачи $dp[i]$ . + +А обладает ли оптимальной подструктурой исходная задача о числе способов подъема по лестнице из прошлого раздела? Формально она не про оптимум, а про подсчет количества. Но если переформулировать ее как "найдите максимальное количество способов", мы неожиданно увидим, что **хотя исходная задача осталась по сути той же, оптимальная подструктура стала явной**: максимальное число способов добраться до ступени $n$ равно сумме максимальных чисел способов добраться до ступеней $n-1$ и $n-2$ . То есть объяснение оптимальной подструктуры в разных задачах может быть довольно гибким. + +Зная уравнение перехода состояния, а также начальные состояния $dp[1] = cost[1]$ и $dp[2] = cost[2]$ , мы можем сразу написать код динамического программирования: + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} +``` + +На рисунке ниже показан процесс динамического программирования для этой задачи. + +![Процесс динамического программирования для минимальной стоимости подъема](dp_problem_features.assets/min_cost_cs_dp.png) + +В этой задаче тоже можно оптимизировать пространство, сжав одномерное состояние в нулевое измерение и тем самым уменьшив пространственную сложность с $O(n)$ до $O(1)$ : + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} +``` + +## Отсутствие последствий + +Отсутствие последствий - одно из ключевых свойств, благодаря которому динамическое программирование вообще может эффективно работать. Его определение таково: **если текущее состояние задано однозначно, то его дальнейшее развитие зависит только от него самого и не зависит от всей истории предыдущих состояний**. + +Для примера снова рассмотрим задачу о лестнице. Если дано состояние $i$ , то из него можно перейти в состояния $i+1$ и $i+2$ , соответствующие прыжкам на $1$ и на $2$ ступени. Чтобы сделать один из этих выборов, не нужно знать, какими были состояния до $i$ ; на будущее влияет только текущее состояние $i$ . + +Однако если добавить в задачу дополнительное ограничение, ситуация изменится. + +!!! question "Подъем по лестнице с ограничением" + + Дана лестница из $n$ ступеней. За один шаг можно подняться на $1$ или на $2$ ступени, **но нельзя два раунда подряд прыгать на $1$ ступень**. Сколькими способами можно добраться до вершины? + +Как показано на рисунке ниже, на третью ступень теперь существует только $2$ допустимых способа добраться: вариант с тремя последовательными прыжками на $1$ не удовлетворяет ограничению и потому отбрасывается. + +![Число способов подняться на 3-ю ступень при наличии ограничения](dp_problem_features.assets/climbing_stairs_constraint_example.png) + +В этой задаче, если в предыдущем раунде был сделан прыжок на $1$ ступень, то в следующем раунде уже обязательно нужно прыгнуть на $2$ ступени. Иными словами, **следующий выбор уже нельзя определить только по текущему состоянию (текущему номеру ступени) - он зависит еще и от предыдущего состояния (с какой ступени мы пришли в прошлый раз)**. + +Нетрудно заметить, что в таком виде задача больше не удовлетворяет свойству отсутствия последствий, а уравнение перехода состояния $dp[i] = dp[i-1] + dp[i-2]$ перестает работать, потому что $dp[i-1]$ соответствует прыжку на $1$ ступень, но при этом включает множество вариантов, где предыдущий раунд тоже был прыжком на $1$ ступень. Такие варианты уже нельзя напрямую учитывать в $dp[i]$ , если мы хотим соблюдать ограничение. + +Поэтому нам нужно расширить определение состояния: **состояние $[i, j]$ означает, что мы находимся на ступени $i$ и в предыдущем раунде прыгнули на $j$ ступеней**, где $j \in \{1, 2\}$ . Такое определение состояния эффективно различает, был ли в прошлом раунде прыжок на $1$ или на $2$ ступени, и позволяет корректно определить, откуда произошло текущее состояние. + +- Если в предыдущем раунде был прыжок на $1$ ступень, то в раунде перед ним мог быть только прыжок на $2$ ступени, то есть $dp[i, 1]$ может перейти только из $dp[i-1, 2]$ . +- Если в предыдущем раунде был прыжок на $2$ ступени, то еще шагом раньше можно было прыгнуть либо на $1$ , либо на $2$ ступени, то есть $dp[i, 2]$ может переходить из $dp[i-2, 1]$ или из $dp[i-2, 2]$ . + +Как показано на рисунке ниже, при таком определении $dp[i, j]$ обозначает число способов для состояния $[i, j]$ . Тогда уравнение перехода состояния имеет вид: + +$$ +\begin{cases} +dp[i, 1] = dp[i-1, 2] \\ +dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] +\end{cases} +$$ + +![Рекуррентная связь с учетом ограничения](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) + +В конце достаточно вернуть $dp[n, 1] + dp[n, 2]$ ; эта сумма и представляет общее число способов добраться до ступени $n$ : + +```src +[file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} +``` + +В этом примере достаточно дополнительно учитывать только одно предыдущее состояние, поэтому после расширения определения состояния задача снова начинает удовлетворять свойству отсутствия последствий. Однако в некоторых задачах "зависимость от прошлого" бывает гораздо серьезнее. + +!!! question "Подъем по лестнице с порождением препятствий" + + Дана лестница из $n$ ступеней. За один шаг можно подняться на $1$ или на $2$ ступени. **При этом, если вы попали на ступень $i$ , система автоматически создает препятствие на ступени $2i$ , и на всех последующих шагах становиться на ступень $2i$ уже нельзя**. Например, если в первых двух раундах вы попали на ступени $2$ и $3$ , то после этого нельзя будет попадать на ступени $4$ и $6$ . Сколько существует способов добраться до вершины? + +В этой задаче следующий прыжок зависит от всех предыдущих состояний, потому что каждый прыжок порождает новое препятствие на более высокой ступени и тем самым влияет на все будущие прыжки. Для задач такого типа динамическое программирование обычно оказывается непригодным. + +Вообще, многие сложные задачи комбинаторной оптимизации (например, задача коммивояжера) не обладают свойством отсутствия последствий. Для таких задач обычно выбирают другие методы - например, эвристический поиск, генетические алгоритмы, обучение с подкреплением и т.д., - чтобы за ограниченное время получить пригодное локально оптимальное решение. diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png new file mode 100644 index 000000000..f1c5a8c1b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png new file mode 100644 index 000000000..700c8efd0 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png new file mode 100644 index 000000000..ce9951201 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png new file mode 100644 index 000000000..e742520f0 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png new file mode 100644 index 000000000..15a41d559 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png new file mode 100644 index 000000000..fc78e4844 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png new file mode 100644 index 000000000..9fa018a3c Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png new file mode 100644 index 000000000..c162ea707 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png new file mode 100644 index 000000000..0efe1bf24 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png new file mode 100644 index 000000000..0ba4cf8d7 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png new file mode 100644 index 000000000..b4e8149b5 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png new file mode 100644 index 000000000..171cf4a3b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png new file mode 100644 index 000000000..21cd7bebe Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png new file mode 100644 index 000000000..8b320b9a3 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png new file mode 100644 index 000000000..ddec501e5 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png new file mode 100644 index 000000000..7fcb6cfc6 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png new file mode 100644 index 000000000..559d1a3de Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png new file mode 100644 index 000000000..1453044e1 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png differ diff --git a/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.md new file mode 100644 index 000000000..99d5e1e8f --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -0,0 +1,183 @@ +# Подход к решению задач динамического программирования + +В двух предыдущих разделах были рассмотрены основные свойства задач динамического программирования. Теперь исследуем два более практических вопроса. + +1. Как определить, является ли некоторая задача задачей динамического программирования? +2. С чего начинать решение такой задачи и как выглядит полный процесс решения? + +## Определение задачи + +В целом, если задача содержит перекрывающиеся подзадачи, оптимальную подструктуру и удовлетворяет свойству отсутствия последствий, то она обычно подходит для решения с помощью динамического программирования. Однако извлечь все эти свойства напрямую из формулировки задачи бывает трудно. Поэтому на практике мы обычно ослабляем требования и **сначала смотрим, подходит ли задача для решения методом backtracking (полного перебора)**. + +**Задачи, подходящие для backtracking, обычно удовлетворяют "модели дерева решений"**. Такие задачи можно описать деревом, где каждый узел представляет одно решение, а каждый путь представляет последовательность решений. + +Иначе говоря, если в задаче есть четко выраженные решения и ответ порождается последовательностью таких решений, то она удовлетворяет модели дерева решений и обычно допускает решение через backtracking. + +Поверх этого у задач динамического программирования есть и некоторые дополнительные "плюсы". + +- В условии задачи фигурируют слова "максимальный", "минимальный", "наибольший", "наименьший" и другие формулировки оптимизации. +- Состояния задачи можно описать списком, многомерной матрицей или деревом, и между состоянием и соседними состояниями существует рекуррентная зависимость. + +Соответственно, существуют и некоторые "минусы". + +- Цель задачи состоит в поиске всех возможных решений, а не одного оптимального решения. +- В формулировке явно присутствуют признаки комбинаторного перечисления, и требуется вернуть сразу много конкретных вариантов. + +Если задача удовлетворяет модели дерева решений и имеет достаточно явные "плюсы", мы можем предположить, что это задача динамического программирования, а затем проверить это предположение уже в процессе решения. + +## Этапы решения задачи + +Конкретный процесс решения задач динамического программирования зависит от природы и сложности задачи, но обычно включает следующие шаги: описание решений, определение состояний, построение таблицы $dp$ , вывод уравнения перехода состояния, определение граничных условий и порядка переходов. + +Чтобы нагляднее показать этот процесс, рассмотрим классическую задачу "минимальная сумма пути". + +!!! question + + Дана двумерная сетка `grid` размера $n \times m$ , в каждой клетке которой записано неотрицательное целое число, означающее стоимость прохождения через эту клетку. Робот стартует из левой верхней клетки и за один шаг может двигаться только вправо или вниз, пока не достигнет правой нижней клетки. Верните минимальную сумму пути от левой верхней клетки до правой нижней. + +На рисунке ниже показан пример, в котором минимальная сумма пути равна $13$ . + +![Пример данных для задачи о минимальной сумме пути](dp_solution_pipeline.assets/min_path_sum_example.png) + +**Шаг 1: понять решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** + +В этой задаче на каждом раунде решение состоит в том, чтобы из текущей клетки сделать один шаг вниз или вправо. Пусть индексы строки и столбца текущей клетки равны $[i, j]$ ; тогда после шага вниз или вправо индексы становятся равными $[i+1, j]$ или $[i, j+1]$ . Значит, состояние должно включать два переменных индекса: строки и столбца, то есть состояние обозначается как $[i, j]$ . + +Подзадача, соответствующая состоянию $[i, j]$ , такова: минимальная сумма пути от стартовой клетки $[0, 0]$ до клетки $[i, j]$ . Ее решение обозначается через $dp[i, j]$ . + +На этом этапе мы получаем двумерную матрицу $dp$ , показанную на рисунке ниже, размер которой совпадает с размером входной сетки `grid` . + +![Определение состояния и таблицы dp](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) + +!!! note + + Как в динамическом программировании, так и в backtracking, решение задачи можно описать как последовательность решений, а состояние образуется всеми переменными решений. Оно должно содержать всю информацию, достаточную для вывода следующего состояния. + + Каждому состоянию соответствует некоторая подзадача, и для хранения решений всех подзадач мы определяем таблицу $dp$ ; каждая независимая переменная состояния становится одним измерением таблицы $dp$ . По сути таблица $dp$ - это отображение от состояния к решению соответствующей подзадачи. + +**Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** + +Для состояния $[i, j]$ возможны только два источника: клетка сверху $[i-1, j]$ и клетка слева $[i, j-1]$ . Следовательно, оптимальная подструктура выглядит так: минимальная сумма пути до $[i, j]$ определяется меньшим из двух значений - минимальной суммы пути до $[i-1, j]$ и минимальной суммы пути до $[i, j-1]$ . + +По этому рассуждению получается уравнение перехода состояния, показанное на рисунке ниже: + +$$ +dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] +$$ + +![Оптимальная подструктура и уравнение перехода состояния](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) + +!!! note + + Опираясь на уже определенную таблицу $dp$ , нужно продумать отношение между исходной задачей и подзадачами и найти способ построить оптимальное решение исходной задачи из оптимальных решений подзадач, то есть найти оптимальную подструктуру. + + Как только оптимальная подструктура найдена, на ее основе можно построить уравнение перехода состояния. + +**Шаг 3: определить граничные условия и порядок переходов** + +В этой задаче состояния в первой строке могут переходить только из клетки слева, а состояния в первом столбце - только из клетки сверху, поэтому первая строка $i = 0$ и первый столбец $j = 0$ образуют граничные условия. + +Как показано на рисунке ниже, поскольку каждая клетка получается из клетки слева и клетки сверху, мы можем проходить матрицу циклами: внешний цикл по строкам, внутренний - по столбцам. + +![Граничные условия и порядок перехода состояний](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) + +!!! note + + В динамическом программировании граничные условия используются для инициализации таблицы $dp$ , а в поиске - для обрезки. + + Смысл порядка перехода состояния в том, чтобы к моменту вычисления текущей подзадачи все более мелкие подзадачи, от которых она зависит, уже были вычислены корректно. + +После этого анализа мы уже можем напрямую написать код динамического программирования. Однако разложение на подзадачи - это мышление "сверху вниз", поэтому с точки зрения мышления более естественно реализовывать задачу в порядке "полный перебор $\rightarrow$ поиск с мемоизацией $\rightarrow$ динамическое программирование". + +### Метод 1: полный перебор + +Начав со состояния $[i, j]$ , мы непрерывно раскладываем его на меньшие состояния $[i-1, j]$ и $[i, j-1]$ . Рекурсивная функция при этом имеет следующие элементы. + +- **Параметры рекурсии**: состояние $[i, j]$ . +- **Возвращаемое значение**: минимальная сумма пути до $[i, j]$ , то есть $dp[i, j]$ . +- **Условие завершения**: когда $i = 0$ и $j = 0$ , возвращается стоимость $grid[0, 0]$ . +- **Обрезка**: если $i < 0$ или $j < 0$ , индекс выходит за границы, и в этом случае возвращается стоимость $+\infty$ , обозначающая невозможность. + +Код реализации: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} +``` + +На рисунке ниже показано дерево рекурсии с корнем в $dp[2, 1]$ ; в нем содержатся перекрывающиеся подзадачи, и их число будет резко расти вместе с размером сетки `grid` . + +По своей сути причина появления перекрывающихся подзадач такова: **существует много разных путей от левого верхнего угла до одной и той же клетки**. + +![Дерево рекурсии полного перебора](dp_solution_pipeline.assets/min_path_sum_dfs.png) + +У каждого состояния есть два выбора - вниз и вправо, а от левого верхнего угла до правого нижнего нужно сделать всего $m + n - 2$ шагов, поэтому худшая временная сложность равна $O(2^{m + n})$ , где $n$ и $m$ - число строк и столбцов сетки соответственно. Заметим, что в этой оценке не учитывается близость к границам сетки: у граничных клеток остается только один выбор, так что фактическое число путей будет несколько меньше. + +### Метод 2: поиск с мемоизацией + +Введем список памяти `mem` того же размера, что и сетка `grid` , для хранения решений всех подзадач и отсечения перекрывающихся подзадач: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} +``` + +Как показано на рисунке ниже, после добавления мемоизации решение каждой подзадачи вычисляется только один раз, поэтому временная сложность определяется общим числом состояний, то есть равна $O(nm)$ . + +![Дерево рекурсии для поиска с мемоизацией](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) + +### Метод 3: динамическое программирование + +Итеративная реализация динамического программирования выглядит так: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} +``` + +На рисунке ниже показан процесс переходов состояний в задаче о минимальной сумме пути. Он проходит по всей сетке, **поэтому временная сложность равна $O(nm)$** . + +Размер массива `dp` равен $n \times m$ , **поэтому пространственная сложность равна $O(nm)$** . + +=== "<1>" + ![Процесс динамического программирования для минимальной суммы пути](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) + +=== "<2>" + ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) + +=== "<3>" + ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) + +=== "<4>" + ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) + +=== "<5>" + ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) + +=== "<6>" + ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) + +=== "<7>" + ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) + +=== "<8>" + ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) + +=== "<9>" + ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) + +=== "<10>" + ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) + +=== "<11>" + ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) + +=== "<12>" + ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) + +### Оптимизация пространства + +Поскольку каждая клетка зависит только от клетки слева и клетки сверху, таблицу $dp$ можно реализовать с помощью одномерного массива, соответствующего одной строке. + +Обратите внимание: поскольку массив `dp` теперь может представлять только одну строку состояний, мы не можем заранее инициализировать состояния первого столбца, а должны обновлять их по мере обхода каждой строки: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} +``` diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png new file mode 100644 index 000000000..7e3dd7b05 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png new file mode 100644 index 000000000..ad1e7cabb Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png new file mode 100644 index 000000000..a7ab4a99a Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png new file mode 100644 index 000000000..672d3573b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png new file mode 100644 index 000000000..6e77864b2 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png new file mode 100644 index 000000000..a0206203c Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png new file mode 100644 index 000000000..26e72721f Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png new file mode 100644 index 000000000..5863be208 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png new file mode 100644 index 000000000..1a64a088e Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png new file mode 100644 index 000000000..e7d33c928 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png new file mode 100644 index 000000000..7e25bb0db Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png new file mode 100644 index 000000000..3afa0c02b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png new file mode 100644 index 000000000..d5169284d Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png new file mode 100644 index 000000000..0616a9084 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png new file mode 100644 index 000000000..370e7732b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png new file mode 100644 index 000000000..e263154ec Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png new file mode 100644 index 000000000..7c8fcc70f Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png new file mode 100644 index 000000000..14064a993 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png differ diff --git a/ru/docs/chapter_dynamic_programming/edit_distance_problem.md b/ru/docs/chapter_dynamic_programming/edit_distance_problem.md new file mode 100644 index 000000000..d5bfa3a4e --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -0,0 +1,129 @@ +# Задача о расстоянии редактирования + +Расстояние редактирования, также называемое расстоянием Левенштейна, обозначает минимальное число правок, необходимых для взаимного преобразования двух строк. Обычно оно используется для измерения сходства двух последовательностей в информационном поиске и обработке естественного языка. + +!!! question + + Даны две строки $s$ и $t$ . Верните минимальное число шагов редактирования, необходимое для преобразования $s$ в $t$ . + + Для строки допускаются три операции редактирования: вставка одного символа, удаление одного символа и замена одного символа на произвольный другой символ. + +Как показано на рисунке ниже, для преобразования `kitten` в `sitting` требуется 3 шага редактирования: 2 операции замены и 1 операция вставки; для преобразования `hello` в `algo` также требуется 3 шага: 2 замены и 1 удаление. + +![Пример данных для задачи о расстоянии редактирования](edit_distance_problem.assets/edit_distance_example.png) + +**Задачу о расстоянии редактирования можно очень естественно описать через модель дерева решений**. Строки соответствуют узлам дерева, а один раунд решения (одна операция редактирования) соответствует одному ребру дерева. + +Как показано на рисунке ниже, если не ограничивать число операций, то каждый узел может порождать множество ребер, и каждое из них соответствует одному из вариантов преобразования. Это означает, что преобразовать `hello` в `algo` можно множеством разных путей. + +С точки зрения дерева решений цель этой задачи - найти кратчайший путь между узлом `hello` и узлом `algo` . + +![Представление задачи о расстоянии редактирования через дерево решений](edit_distance_problem.assets/edit_distance_decision_tree.png) + +### Идея динамического программирования + +**Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** + +На каждом раунде решение состоит в выполнении одной операции редактирования над строкой $s$ . + +Нам нужно, чтобы в ходе выполнения операций размер задачи постепенно уменьшался; только тогда можно строить подзадачи. Пусть длины строк $s$ и $t$ равны соответственно $n$ и $m$ ; сначала рассмотрим последние символы этих строк, то есть $s[n-1]$ и $t[m-1]$ . + +- Если $s[n-1]$ и $t[m-1]$ совпадают, их можно просто пропустить и сразу перейти к сравнению $s[n-2]$ и $t[m-2]$ . +- Если $s[n-1]$ и $t[m-1]$ различны, нужно выполнить над $s$ одну операцию редактирования (вставку, удаление или замену), чтобы последние символы стали одинаковыми, после чего можно перейти к задаче меньшего размера. + +Иначе говоря, каждое решение (операция редактирования), которое мы выполняем над строкой $s$ , меняет те символы, которые еще остаются несопоставленными в строках $s$ и $t$ . Поэтому состояние определяется текущими позициями рассматриваемых символов в $s$ и $t$ , то есть состоянием $[i, j]$ . + +Подзадача, соответствующая состоянию $[i, j]$ , такова: **минимальное число операций редактирования, необходимое для преобразования первых $i$ символов строки $s$ в первые $j$ символов строки $t$**. + +Отсюда получается двумерная таблица $dp$ размера $(i+1) \times (j+1)$ . + +**Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** + +Рассмотрим подзадачу $dp[i, j]$ . Ее последние символы - это $s[i-1]$ и $t[j-1]$ . В зависимости от операции редактирования возможны три случая, показанные на рисунке ниже. + +1. Вставить после $s[i-1]$ символ $t[j-1]$ ; тогда остается подзадача $dp[i, j-1]$ . +2. Удалить $s[i-1]$ ; тогда остается подзадача $dp[i-1, j]$ . +3. Заменить $s[i-1]$ на $t[j-1]$ ; тогда остается подзадача $dp[i-1, j-1]$ . + +![Переходы состояния в задаче о расстоянии редактирования](edit_distance_problem.assets/edit_distance_state_transfer.png) + +Согласно этому анализу оптимальная подструктура такова: минимальное число шагов редактирования для $dp[i, j]$ равно минимуму из трех значений - $dp[i, j-1]$ , $dp[i-1, j]$ и $dp[i-1, j-1]$ - плюс цена текущей операции редактирования $1$ . Значит, уравнение перехода состояния имеет вид: + +$$ +dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 +$$ + +Заметим, что **если символы $s[i-1]$ и $t[j-1]$ совпадают, то редактировать текущий символ не нужно**. В этом случае уравнение перехода состояния имеет вид: + +$$ +dp[i, j] = dp[i-1, j-1] +$$ + +**Шаг 3: определить граничные условия и порядок переходов** + +Когда обе строки пусты, число операций редактирования равно $0$ , то есть $dp[0, 0] = 0$ . Когда строка $s$ пуста, а строка $t$ непуста, минимальное число операций равно длине строки $t$ , то есть вся первая строка инициализируется как $dp[0, j] = j$ . Когда строка $s$ непуста, а строка $t$ пуста, минимальное число операций равно длине строки $s$ , то есть весь первый столбец инициализируется как $dp[i, 0] = i$ . + +Из уравнения перехода видно, что решение $dp[i, j]$ зависит от значений слева, сверху и слева сверху, поэтому всю таблицу $dp$ можно обходить двумя вложенными циклами в прямом порядке. + +### Реализация кода + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp} +``` + +Как показано на рисунке ниже, процесс переходов состояния в задаче о расстоянии редактирования очень похож на процесс в задачах о рюкзаке: в обоих случаях это заполнение двумерной сетки. + +=== "<1>" + ![Процесс динамического программирования для расстояния редактирования](edit_distance_problem.assets/edit_distance_dp_step1.png) + +=== "<2>" + ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) + +=== "<3>" + ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) + +=== "<4>" + ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) + +=== "<5>" + ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) + +=== "<6>" + ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) + +=== "<7>" + ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) + +=== "<8>" + ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) + +=== "<9>" + ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) + +=== "<10>" + ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) + +=== "<11>" + ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) + +=== "<12>" + ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) + +=== "<13>" + ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) + +=== "<14>" + ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) + +=== "<15>" + ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) + +### Оптимизация пространства + +Поскольку $dp[i,j]$ зависит от значения сверху $dp[i-1, j]$ , слева $dp[i, j-1]$ и слева сверху $dp[i-1, j-1]$ , прямой обход после оптимизации памяти теряет значение слева сверху, а обратный обход не позволяет заранее построить значение слева $dp[i, j-1]$ . Значит, оба наивных варианта обхода здесь непригодны. + +Чтобы решить эту проблему, можно использовать переменную `leftup` для временного сохранения значения слева сверху $dp[i-1, j-1]$ ; после этого остается учитывать только верхнее и левое значения. Тогда ситуация становится эквивалентной задаче о полном рюкзаке, и можно выполнять прямой обход. Код приведен ниже: + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} +``` diff --git a/ru/docs/chapter_dynamic_programming/index.md b/ru/docs/chapter_dynamic_programming/index.md new file mode 100644 index 000000000..e8a109ef6 --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/index.md @@ -0,0 +1,9 @@ +# Динамическое программирование + +![Динамическое программирование](../assets/covers/chapter_dynamic_programming.jpg) + +!!! abstract + + Ручьи впадают в реки, а реки вливаются в море. + + Динамическое программирование собирает решения малых задач в ответ на большую задачу и шаг за шагом ведет нас к ее решению. diff --git a/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png new file mode 100644 index 000000000..a5da3474b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png differ diff --git a/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png new file mode 100644 index 000000000..793ac775f Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png differ diff --git a/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png new file mode 100644 index 000000000..2dfcb1554 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png differ diff --git a/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png new file mode 100644 index 000000000..64e9171da Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png new file mode 100644 index 000000000..6ce1be625 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png differ diff --git a/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md new file mode 100644 index 000000000..b781b8d95 --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -0,0 +1,110 @@ +# Первое знакомство с динамическим программированием + +Динамическое программирование (dynamic programming) - это важная алгоритмическая парадигма, которая разбивает задачу на последовательность более мелких подзадач и за счет хранения их решений избегает повторных вычислений, тем самым резко повышая эффективность по времени. + +В этом разделе мы начнем с классического примера: сначала запишем его грубое решение через backtracking, увидим в нем перекрывающиеся подзадачи, а затем постепенно выведем более эффективное решение на основе динамического программирования. + +!!! question "Подъем по лестнице" + + Дана лестница из $n$ ступеней. За один шаг можно подняться на $1$ или на $2$ ступени. Сколькими способами можно добраться до вершины? + +Как показано на рисунке ниже, для лестницы из $3$ ступеней существует $3$ способа добраться до вершины. + +![Число способов подняться на 3-ю ступень](intro_to_dynamic_programming.assets/climbing_stairs_example.png) + +Цель этой задачи - вычислить количество способов. **Поэтому можно попробовать грубо перебрать все варианты с помощью backtracking**. Если представить подъем по лестнице как последовательность решений, то мы начинаем от земли и на каждом раунде выбираем прыжок на $1$ или на $2$ ступени; всякий раз, когда достигаем вершины, увеличиваем число способов на $1$ , а если перескакиваем вершину, обрезаем эту ветвь. Код выглядит так: + +```src +[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} +``` + +## Метод 1: полный перебор + +Backtracking обычно не раскладывает задачу явно на подзадачи; вместо этого он рассматривает решение как последовательность решений, используя попытки и обрезку для поиска всех возможных ответов. + +Попробуем посмотреть на задачу именно как на разложение подзадач. Пусть число способов добраться до ступени $i$ равно $dp[i]$ ; тогда $dp[i]$ - это исходная задача, а ее подзадачи включают: + +$$ +dp[i-1], dp[i-2], \dots, dp[2], dp[1] +$$ + +Поскольку за один раунд можно подняться только на $1$ или на $2$ ступени, стоя на ступени $i$ , в предыдущий раунд мы могли находиться только на ступени $i - 1$ или на ступени $i - 2$ . Иначе говоря, на ступень $i$ можно попасть только со ступени $i -1$ или со ступени $i - 2$ . + +Отсюда получается важный вывод: **число способов добраться до ступени $i - 1$ плюс число способов добраться до ступени $i - 2$ равно числу способов добраться до ступени $i$**. Формула имеет вид: + +$$ +dp[i] = dp[i-1] + dp[i-2] +$$ + +Это означает, что в задаче о подъеме по лестнице между подзадачами существует рекуррентная зависимость, и **решение исходной задачи может быть построено на основе решений подзадач**. Эта связь показана на рисунке ниже. + +![Рекуррентная связь числа способов](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) + +По рекуррентной формуле можно получить решение полного перебора. Начиная с $dp[n]$ , **мы рекурсивно разлагаем большую задачу в сумму двух меньших задач** , пока не дойдем до наименьших подзадач $dp[1]$ и $dp[2]$ . Их решения уже известны: $dp[1] = 1$ и $dp[2] = 2$ , что означает $1$ и $2$ способа подняться соответственно на $1$-ю и $2$-ю ступени. + +Посмотрите на следующий код: как и стандартный backtracking, он относится к поиску в глубину, но выглядит более компактно: + +```src +[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} +``` + +На рисунке ниже показано дерево рекурсии, возникающее при полном переборе. Для задачи $dp[n]$ глубина дерева рекурсии равна $n$ , а временная сложность равна $O(2^n)$ . Экспоненциальный рост взрывообразен: если подать на вход достаточно большое значение $n$ , ожидание станет очень долгим. + +![Дерево рекурсии для подъема по лестнице](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) + +Если посмотреть на рисунок выше, то видно, что **экспоненциальная временная сложность порождается "перекрывающимися подзадачами"**. Например, $dp[9]$ раскладывается в $dp[8]$ и $dp[7]$ , а $dp[8]$ - в $dp[7]$ и $dp[6]$ ; обе ветви содержат подзадачу $dp[7]$ . + +Продолжая это рассуждение, мы видим, что подзадачи порождают все более мелкие перекрывающиеся подзадачи без конца. Подавляющая часть вычислительных ресурсов уходит именно на них. + +## Метод 2: поиск с мемоизацией + +Чтобы ускорить алгоритм, **мы хотим, чтобы каждая перекрывающаяся подзадача вычислялась только один раз**. Для этого объявим массив `mem` для хранения решения каждой подзадачи и будем обрезать повторные вычисления в процессе поиска. + +1. Когда $dp[i]$ вычисляется впервые, мы сохраняем его в `mem[i]` для последующего использования. +2. Когда значение $dp[i]$ требуется снова, мы просто берем его напрямую из `mem[i]` и тем самым избегаем повторного вычисления подзадачи. + +Код приведен ниже: + +```src +[file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} +``` + +Как показано на рисунке ниже, **после введения мемоизации каждая перекрывающаяся подзадача вычисляется только один раз, и временная сложность оптимизируется до $O(n)$** . Это огромный скачок в эффективности. + +![Дерево рекурсии для поиска с мемоизацией](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) + +## Метод 3: динамическое программирование + +**Поиск с мемоизацией - это метод "сверху вниз"** : мы начинаем с исходной задачи (корня), рекурсивно раскладываем более крупные подзадачи на меньшие, пока не достигнем наименьших подзадач с уже известным ответом (листьев). Затем в процессе возврата постепенно собираем решения подзадач и тем самым получаем решение исходной задачи. + +Напротив, **динамическое программирование - это метод "снизу вверх"** : начиная с решений наименьших подзадач, мы итеративно строим решения для более крупных подзадач, пока не получим ответ на исходную задачу. + +Поскольку в динамическом программировании нет этапа возврата, для его реализации достаточно обычных циклов, без рекурсии. В приведенном ниже коде мы инициализируем массив `dp` для хранения решений подзадач; он выполняет ту же роль, что и массив `mem` в мемоизированном поиске: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} +``` + +На рисунке ниже смоделирован процесс выполнения этого кода. + +![Процесс динамического программирования для подъема по лестнице](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) + +Как и в backtracking, в динамическом программировании используется понятие "состояние" для обозначения некоторого этапа решения задачи; каждое состояние соответствует одной подзадаче и ее локально оптимальному решению. Например, в задаче о лестнице состояние определяется текущим номером ступени $i$ . + +На основе сказанного можно подвести несколько часто используемых терминов динамического программирования. + +- Массив `dp` называют таблицей dp, а $dp[i]$ обозначает решение подзадачи, соответствующей состоянию $i$ . +- Состояния, соответствующие наименьшим подзадачам (первая и вторая ступени), называют начальными состояниями. +- Рекуррентную формулу $dp[i] = dp[i-1] + dp[i-2]$ называют уравнением перехода состояния. + +## Оптимизация пространства + +Внимательный читатель мог заметить, что **поскольку $dp[i]$ зависит только от $dp[i-1]$ и $dp[i-2]$ , нам не нужен весь массив `dp` для хранения ответов всех подзадач** ; достаточно двух переменных, которые будут "перекатываться" вперед. Код имеет вид: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} +``` + +Из кода видно, что после отказа от массива `dp` пространственная сложность уменьшается с $O(n)$ до $O(1)$ . + +Во многих задачах динамического программирования текущее состояние зависит лишь от ограниченного числа предыдущих состояний. Тогда можно сохранять только действительно нужные состояния и за счет "уменьшения размерности" экономить память. **Этот прием оптимизации памяти называют "скользящими переменными" или "скользящим массивом"**. diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png new file mode 100644 index 000000000..601afcd7d Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png new file mode 100644 index 000000000..32a7f21d7 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png new file mode 100644 index 000000000..3e83181e4 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png new file mode 100644 index 000000000..d8b7614a2 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png new file mode 100644 index 000000000..2fe7a2d42 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png new file mode 100644 index 000000000..42db7910b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png new file mode 100644 index 000000000..fd2c2b90b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png new file mode 100644 index 000000000..d7b207048 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png new file mode 100644 index 000000000..7a4fb8f28 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png new file mode 100644 index 000000000..60069032f Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png new file mode 100644 index 000000000..a6d9782db Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png new file mode 100644 index 000000000..97ac26b99 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png new file mode 100644 index 000000000..175fa7ef0 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png new file mode 100644 index 000000000..2bafd6a17 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png new file mode 100644 index 000000000..6d4f6ed2f Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png new file mode 100644 index 000000000..9bf359c33 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png new file mode 100644 index 000000000..2540bd01a Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png new file mode 100644 index 000000000..e04b449f8 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png new file mode 100644 index 000000000..94238d709 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png new file mode 100644 index 000000000..c7596e5a5 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png new file mode 100644 index 000000000..292ff249e Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png new file mode 100644 index 000000000..3fa0dba95 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png new file mode 100644 index 000000000..9fb448fc3 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/knapsack_problem.md b/ru/docs/chapter_dynamic_programming/knapsack_problem.md new file mode 100644 index 000000000..d02945147 --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/knapsack_problem.md @@ -0,0 +1,168 @@ +# Задача о рюкзаке 0-1 + +Задача о рюкзаке - это очень хороший вводный пример для динамического программирования и одна из самых типичных форм задач этого класса. У нее существует множество вариантов, например задача о рюкзаке 0-1, задача о полном рюкзаке, задача о многократном рюкзаке и т.д. + +В этом разделе сначала разберем самый распространенный вариант - задачу о рюкзаке 0-1. + +!!! question + + Даны $n$ предметов. Вес $i$-го предмета равен $wgt[i-1]$ , стоимость равна $val[i-1]$ . Также дан рюкзак вместимости $cap$ . Каждый предмет можно выбрать только один раз. Найдите максимальную суммарную стоимость, которую можно поместить в рюкзак при заданной вместимости. + +Как видно на рисунке ниже, поскольку номер предмета $i$ начинается с $1$ , а индексы массива начинаются с $0$ , предмету $i$ соответствуют вес $wgt[i-1]$ и стоимость $val[i-1]$ . + +![Пример данных для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_example.png) + +На задачу о рюкзаке 0-1 можно смотреть как на процесс из $n$ раундов решений: для каждого предмета есть два решения - не класть его в рюкзак или положить в рюкзак. Поэтому задача удовлетворяет модели дерева решений. + +Цель задачи - найти "максимальную суммарную стоимость при ограниченной вместимости рюкзака", значит, с большой вероятностью это задача динамического программирования. + +**Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** + +Для каждого предмета возможны два случая: не класть его в рюкзак, тогда вместимость не меняется; или положить его в рюкзак, тогда оставшаяся вместимость уменьшается. Отсюда получается определение состояния: текущий номер предмета $i$ и текущая вместимость рюкзака $c$ , то есть состояние обозначается как $[i, c]$ . + +Подзадача, соответствующая состоянию $[i, c]$ , такова: **максимальная стоимость, которую можно получить, используя первые $i$ предметов и рюкзак вместимости $c$**. Ее решение обозначается через $dp[i, c]$ . + +Искомым значением является $dp[n, cap]$ , значит, нам нужна двумерная таблица $dp$ размера $(n+1) \times (cap+1)$ . + +**Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** + +После того как мы принимаем решение по предмету $i$ , остается подзадача, связанная с первыми $i-1$ предметами. Здесь возможны два случая. + +- **Не класть предмет $i$** : вместимость рюкзака не меняется, и состояние переходит в $[i-1, c]$ . +- **Положить предмет $i$** : вместимость рюкзака уменьшается на $wgt[i-1]$ , а стоимость увеличивается на $val[i-1]$ , и состояние переходит в $[i-1, c-wgt[i-1]]$ . + +Этот анализ и раскрывает оптимальную подструктуру задачи: **максимальная стоимость $dp[i, c]$ равна лучшему из двух вариантов - не брать предмет $i$ или взять предмет $i$**. Отсюда получается уравнение перехода состояния: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) +$$ + +Нужно учитывать, что если вес текущего предмета $wgt[i - 1]$ превышает оставшуюся вместимость $c$ , то предмет можно только не брать. + +**Шаг 3: определить граничные условия и порядок переходов** + +Когда предметов нет или вместимость рюкзака равна $0$ , максимальная стоимость равна $0$ ; то есть весь первый столбец $dp[i, 0]$ и вся первая строка $dp[0, c]$ заполняются нулями. + +Текущее состояние $[i, c]$ зависит от состояния сверху $[i-1, c]$ и состояния слева сверху $[i-1, c-wgt[i-1]]$ , поэтому достаточно двумя вложенными циклами пройти по всей таблице $dp$ в прямом порядке. + +После этого анализа реализуем по порядку: полный перебор, поиск с мемоизацией и динамическое программирование. + +### Метод 1: полный перебор + +Код поиска содержит следующие элементы. + +- **Параметры рекурсии**: состояние $[i, c]$ . +- **Возвращаемое значение**: решение подзадачи $dp[i, c]$ . +- **Условие завершения**: когда номер предмета выходит за границу, то есть $i = 0$ , или оставшаяся вместимость равна $0$ , рекурсия завершается и возвращается стоимость $0$ . +- **Обрезка**: если вес текущего предмета превышает оставшуюся вместимость рюкзака, то можно только не класть этот предмет. + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs} +``` + +Как показано на рисунке ниже, поскольку каждый предмет создает две ветви поиска - "не брать" и "брать", временная сложность равна $O(2^n)$ . + +Посмотрев на дерево рекурсии, легко заметить наличие перекрывающихся подзадач, например $dp[1, 10]$ и подобных. Когда число предметов растет, вместимость рюкзака велика, а особенно когда много предметов с одинаковым весом, количество перекрывающихся подзадач быстро увеличивается. + +![Дерево полного перебора для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_dfs.png) + +### Метод 2: поиск с мемоизацией + +Чтобы каждая перекрывающаяся подзадача вычислялась только один раз, используем таблицу памяти `mem` для хранения решений подзадач, где `mem[i][c]` соответствует $dp[i, c]$ . + +После введения мемоизации **временная сложность определяется числом подзадач** , то есть равна $O(n \times cap)$ . Код выглядит так: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} +``` + +На рисунке ниже показаны ветви поиска, которые были отсечены благодаря мемоизации. + +![Дерево поиска с мемоизацией для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_dfs_mem.png) + +### Метод 3: динамическое программирование + +По своей сути динамическое программирование здесь - это процесс последовательного заполнения таблицы $dp$ в соответствии с переходами состояний. Код приведен ниже: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp} +``` + +Как показано на рисунке ниже, и временная сложность, и пространственная сложность определяются размером массива `dp` , то есть равны $O(n \times cap)$ . + +=== "<1>" + ![Процесс динамического программирования для задачи о рюкзаке 0-1](knapsack_problem.assets/knapsack_dp_step1.png) + +=== "<2>" + ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) + +=== "<3>" + ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) + +=== "<4>" + ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) + +=== "<5>" + ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) + +=== "<6>" + ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) + +=== "<7>" + ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) + +=== "<8>" + ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) + +=== "<9>" + ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) + +=== "<10>" + ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) + +=== "<11>" + ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) + +=== "<12>" + ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) + +=== "<13>" + ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) + +=== "<14>" + ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) + +### Оптимизация пространства + +Поскольку каждое состояние зависит только от состояния в предыдущей строке, можно использовать два массива, которые будут "перекатываться" вперед, и тем самым уменьшить пространственную сложность с $O(n^2)$ до $O(n)$ . + +Если пойти дальше, можно спросить: можно ли оптимизировать память так, чтобы использовать только один массив? Наблюдение показывает, что каждое состояние зависит от клетки прямо сверху и клетки слева сверху. Предположим, что у нас есть только один массив, и в момент начала обхода строки $i$ он еще хранит состояния строки $i-1$ . + +- Если обходить массив слева направо, то к моменту вычисления $dp[i, j]$ значения слева сверху $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ могут уже быть перезаписаны, и правильный результат перехода состояния получить не удастся. +- Если же обходить массив справа налево, проблема перезаписи не возникает, и переход состояния вычисляется корректно. + +На рисунке ниже показан процесс перехода от строки $i = 1$ к строке $i = 2$ при использовании одного массива. Попробуйте сопоставить его с разницей между прямым и обратным обходом. + +=== "<1>" + ![Процесс динамического программирования после оптимизации памяти для рюкзака 0-1](knapsack_problem.assets/knapsack_dp_comp_step1.png) + +=== "<2>" + ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) + +=== "<3>" + ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) + +=== "<4>" + ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) + +=== "<5>" + ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) + +=== "<6>" + ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) + +В коде для этого достаточно удалить первое измерение массива `dp` , а внутренний цикл заменить на обратный обход: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} +``` diff --git a/ru/docs/chapter_dynamic_programming/summary.md b/ru/docs/chapter_dynamic_programming/summary.md new file mode 100644 index 000000000..be4c09c40 --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/summary.md @@ -0,0 +1,25 @@ +# Резюме + +### Ключевые выводы + +- Динамическое программирование раскладывает задачу на подзадачи и повышает вычислительную эффективность за счет хранения решений этих подзадач и устранения повторных вычислений. +- Если не учитывать затраты времени, то любую задачу динамического программирования можно решить с помощью backtracking (полного перебора), однако в дереве рекурсии возникает множество перекрывающихся подзадач, из-за чего эффективность крайне низка. После введения таблицы памяти можно хранить решения всех уже вычисленных подзадач и гарантировать, что каждая перекрывающаяся подзадача будет вычисляться только один раз. +- Поиск с мемоизацией - это рекурсивный метод "сверху вниз", а соответствующее ему динамическое программирование - это итеративный метод "снизу вверх", похожий на заполнение таблицы. Поскольку текущее состояние обычно зависит только от части локальных состояний, можно убрать одно измерение таблицы $dp$ и тем самым снизить пространственную сложность. +- Разложение на подзадачи - это общий алгоритмический подход, но в divide and conquer, динамическом программировании и backtracking он имеет разные свойства. +- Для задач динамического программирования характерны три главных свойства: перекрывающиеся подзадачи, оптимальная подструктура и отсутствие последствий. +- Если оптимальное решение исходной задачи можно построить из оптимальных решений подзадач, то задача обладает оптимальной подструктурой. +- Отсутствие последствий означает, что для данного состояния его дальнейшее развитие определяется только этим состоянием и не зависит от всех прошлых состояний. Многие задачи комбинаторной оптимизации этим свойством не обладают и потому не могут эффективно решаться с помощью динамического программирования. + +**Задачи о рюкзаке** + +- Задача о рюкзаке - один из самых типичных классов задач динамического программирования; она включает варианты 0-1 рюкзака, полного рюкзака, многократного рюкзака и другие. +- В задаче о рюкзаке 0-1 состояние определяется как максимальная стоимость первых $i$ предметов в рюкзаке вместимости $c$ . Рассматривая два решения - не брать предмет и брать предмет, - можно получить оптимальную подструктуру и вывести уравнение перехода состояния. При оптимизации памяти, поскольку каждое состояние зависит от значения сверху и слева сверху, внутренний цикл нужно выполнять в обратном порядке, чтобы не перезаписать нужное значение. +- В задаче о полном рюкзаке число экземпляров каждого предмета не ограничено, поэтому при выборе предмета переход состояния отличается от варианта 0-1. Поскольку состояние зависит от значения сверху и слева, после оптимизации памяти внутренний цикл следует выполнять в прямом порядке. +- Задача о размене монет - это вариант задачи о полном рюкзаке. Здесь вместо "максимальной стоимости" ищется "минимальное число монет", поэтому в уравнении перехода $\max()$ заменяется на $\min()$ . Кроме того, вместо условия "не превышать вместимость рюкзака" нужно **ровно** набрать целевую сумму, поэтому значение $amt + 1$ используется как обозначение недопустимого решения "сумму набрать нельзя". +- В задаче о размене монет II вместо "минимального числа монет" требуется найти "число комбинаций монет", поэтому в уравнении перехода оператор $\min()$ заменяется на суммирование. + +**Задача о расстоянии редактирования** + +- Расстояние редактирования (расстояние Левенштейна) используется для измерения сходства двух строк и определяется как минимальное число операций редактирования, необходимых для преобразования одной строки в другую; допустимые операции - вставка, удаление и замена. +- В задаче о расстоянии редактирования состояние определяется как минимальное число шагов редактирования, необходимых для преобразования первых $i$ символов строки $s$ в первые $j$ символов строки $t$ . Если $s[i] \ne t[j]$ , то существуют три решения: вставка, удаление и замена, и каждому из них соответствует своя остаточная подзадача. На этой основе выводятся оптимальная подструктура и уравнение перехода состояния. Если же $s[i] = t[j]$ , то редактировать текущий символ не нужно. +- В задаче о расстоянии редактирования состояние зависит от значений сверху, слева и слева сверху. Поэтому после оптимизации памяти ни прямой, ни обратный обход сам по себе не дает корректного перехода состояния. Для решения этой проблемы значение слева сверху временно сохраняется в отдельной переменной, что делает ситуацию эквивалентной задаче о полном рюкзаке и позволяет использовать прямой обход. diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png new file mode 100644 index 000000000..69dd3c79e Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png new file mode 100644 index 000000000..953eada4b Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png new file mode 100644 index 000000000..c6c5e76b3 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png new file mode 100644 index 000000000..d817e2ce2 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png new file mode 100644 index 000000000..9a4656660 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png new file mode 100644 index 000000000..59bec0d90 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png new file mode 100644 index 000000000..3bbf0b373 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png new file mode 100644 index 000000000..08aeaf4a5 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png new file mode 100644 index 000000000..2070edc33 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png new file mode 100644 index 000000000..0956631c6 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png new file mode 100644 index 000000000..df5c40a67 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png new file mode 100644 index 000000000..ad578830e Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png new file mode 100644 index 000000000..a954ea2c9 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png new file mode 100644 index 000000000..c726d6dcf Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png new file mode 100644 index 000000000..3868a7cd6 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png new file mode 100644 index 000000000..b0fbad560 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png new file mode 100644 index 000000000..d703fed87 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png new file mode 100644 index 000000000..4f4122eb5 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png new file mode 100644 index 000000000..fc5450a3a Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png new file mode 100644 index 000000000..fda5126e9 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png new file mode 100644 index 000000000..7f6ffcd67 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png new file mode 100644 index 000000000..74a045763 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png new file mode 100644 index 000000000..49441c8aa Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png new file mode 100644 index 000000000..92084aa64 Binary files /dev/null and b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png differ diff --git a/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md new file mode 100644 index 000000000..4a2ec53ec --- /dev/null +++ b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -0,0 +1,207 @@ +# Задача о полном рюкзаке + +В этом разделе сначала решим еще один распространенный вариант задачи о рюкзаке - полный рюкзак, а затем рассмотрим одну из его типичных специальных форм: задачу о размене монет. + +## Задача о полном рюкзаке + +!!! question + + Даны $n$ предметов. Вес $i$-го предмета равен $wgt[i-1]$ , стоимость равна $val[i-1]$ . Также дан рюкзак вместимости $cap$ . **Каждый предмет можно выбирать многократно**. Найдите максимальную суммарную стоимость, которую можно поместить в рюкзак при заданной вместимости. Пример показан на рисунке ниже. + +![Пример данных для задачи о полном рюкзаке](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) + +### Идея динамического программирования + +Задача о полном рюкзаке очень похожа на задачу о рюкзаке 0-1; **разница состоит только в том, что число выборов каждого предмета не ограничено**. + +- В задаче о рюкзаке 0-1 каждого предмета существует только один экземпляр, поэтому после того как предмет $i$ помещен в рюкзак, выбирать можно только из первых $i-1$ предметов. +- В задаче о полном рюкзаке число экземпляров каждого предмета бесконечно, поэтому после того как предмет $i$ помещен в рюкзак, **выбирать все еще можно из первых $i$ предметов**. + +При этом состояние $[i, c]$ в задаче о полном рюкзаке может изменяться двумя способами. + +- **Не брать предмет $i$** : как и в задаче о рюкзаке 0-1, переход осуществляется в $[i-1, c]$ . +- **Взять предмет $i$** : в отличие от рюкзака 0-1 переход происходит в $[i, c-wgt[i-1]]$ . + +Следовательно, уравнение перехода состояния принимает вид: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) +$$ + +### Реализация кода + +Если сравнить код этой задачи с кодом задачи о рюкзаке 0-1, то окажется, что в переходе состояний меняется только одна деталь: вместо $i-1$ появляется $i$ ; все остальное остается таким же: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} +``` + +### Оптимизация пространства + +Поскольку текущее состояние переходит из состояния слева и состояния сверху, **после оптимизации памяти каждую строку таблицы $dp$ нужно обходить слева направо**. + +Этот порядок обхода как раз противоположен задаче о рюкзаке 0-1. Разницу удобно понять по рисунку ниже. + +=== "<1>" + ![Процесс динамического программирования после оптимизации памяти для полного рюкзака](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) + +=== "<2>" + ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) + +=== "<3>" + ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) + +=== "<4>" + ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) + +=== "<5>" + ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) + +=== "<6>" + ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) + +Код реализации здесь довольно прост: достаточно просто убрать первое измерение массива `dp` : + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} +``` + +## Задача о размене монет + +Задача о рюкзаке представляет собой целый класс задач динамического программирования, у которого есть множество вариантов, и одной из таких вариаций является задача о размене монет. + +!!! question + + Даны $n$ видов монет, номинал монеты $i$ равен $coins[i - 1]$ , а целевая сумма равна $amt$ . **Монеты каждого вида можно брать многократно**. Требуется найти минимальное число монет, которыми можно набрать целевую сумму. Если набрать сумму невозможно, верните $-1$ . Пример показан на рисунке ниже. + +![Пример данных для задачи о размене монет](unbounded_knapsack_problem.assets/coin_change_example.png) + +### Идея динамического программирования + +**Задачу о размене монет можно рассматривать как частный случай задачи о полном рюкзаке** ; между ними существует следующая связь и следующие различия. + +- Эти две задачи можно взаимно переводить друг в друга: "предмет" соответствует "монете", "вес предмета" соответствует "номиналу монеты", а "вместимость рюкзака" соответствует "целевой сумме". +- Цель оптимизации противоположна: в задаче о полном рюкзаке нужно максимизировать стоимость предметов, а в задаче о размене монет - минимизировать число монет. +- В задаче о полном рюкзаке ищется решение, не превышающее вместимость, а в задаче о размене монет требуется **ровно** набрать целевую сумму. + +**Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу $dp$** + +Подзадача, соответствующая состоянию $[i, a]$ , выглядит так: **минимальное число монет из первых $i$ видов, которыми можно набрать сумму $a$**. Решение этой подзадачи обозначается как $dp[i, a]$ . + +Размер двумерной таблицы $dp$ равен $(n+1) \times (amt+1)$ . + +**Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния** + +По сравнению с задачей о полном рюкзаке здесь есть два отличия в уравнении перехода состояния. + +- Нужно искать минимум, а не максимум, поэтому оператор $\max()$ заменяется на $\min()$ . +- Оптимизируемое значение - это число монет, а не суммарная стоимость, поэтому при выборе монеты нужно просто прибавить $1$ . + +$$ +dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) +$$ + +**Шаг 3: определить граничные условия и порядок переходов** + +Когда целевая сумма равна $0$ , минимальное число монет для ее набора равно $0$ , то есть весь первый столбец $dp[i, 0]$ заполняется нулями. + +Когда монет нет, **невозможно набрать никакую целевую сумму $> 0$** ; это и есть недопустимое решение. Чтобы функция $\min()$ в уравнении перехода состояния могла распознавать и отбрасывать такие недопустимые решения, удобно использовать значение $+ \infty$ ; то есть всю первую строку $dp[0, a]$ нужно инициализировать значением $+ \infty$ . + +### Реализация кода + +Большинство языков программирования не предоставляет готовую переменную $+ \infty$ для целых чисел, поэтому обычно приходится заменять ее на максимальное значение типа `int` . Но тогда возникает риск переполнения: операция $+ 1$ в уравнении перехода может переполнить большое число. + +Поэтому здесь мы используем число $amt + 1$ как обозначение недопустимого решения, потому что для набора суммы $amt$ максимум нужно не больше чем $amt$ монет. Перед возвратом результата проверяем, равно ли $dp[n, amt]$ значению $amt + 1$ ; если да, то возвращаем $-1$ , что означает невозможность набрать целевую сумму. Код приведен ниже: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp} +``` + +Как показано на рисунке ниже, процесс динамического программирования для задачи о размене монет очень похож на задачу о полном рюкзаке. + +=== "<1>" + ![Процесс динамического программирования для задачи о размене монет](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) + +=== "<2>" + ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) + +=== "<3>" + ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) + +=== "<4>" + ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) + +=== "<5>" + ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) + +=== "<6>" + ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) + +=== "<7>" + ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) + +=== "<8>" + ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) + +=== "<9>" + ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) + +=== "<10>" + ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) + +=== "<11>" + ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) + +=== "<12>" + ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) + +=== "<13>" + ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) + +=== "<14>" + ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) + +=== "<15>" + ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) + +### Оптимизация пространства + +Оптимизация пространства для задачи о размене монет выполняется так же, как и для полного рюкзака: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} +``` + +## Задача о размене монет II + +!!! question + + Даны $n$ видов монет, номинал монеты $i$ равен $coins[i - 1]$ , а целевая сумма равна $amt$ . Монеты каждого вида можно брать многократно. **Найдите число различных комбинаций монет, которыми можно набрать целевую сумму**. Пример показан на рисунке ниже. + +![Пример данных для задачи о размене монет II](unbounded_knapsack_problem.assets/coin_change_ii_example.png) + +### Идея динамического программирования + +По сравнению с предыдущей задачей теперь целью является число комбинаций. Поэтому подзадача меняется на следующую: **число комбинаций из первых $i$ видов монет, которыми можно набрать сумму $a$**. При этом таблица $dp$ по-прежнему остается двумерной матрицей размера $(n+1) \times (amt + 1)$ . + +Число комбинаций для текущего состояния равно сумме числа комбинаций для двух решений: не брать текущую монету и брать текущую монету. Поэтому уравнение перехода состояния принимает вид: + +$$ +dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] +$$ + +Когда целевая сумма равна $0$ , ее можно набрать, не выбирая ни одной монеты, поэтому весь первый столбец $dp[i, 0]$ нужно инициализировать единицами. Когда монет нет, невозможно набрать никакую сумму $>0$ , поэтому вся первая строка $dp[0, a]$ должна быть заполнена нулями. + +### Реализация кода + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} +``` + +### Оптимизация пространства + +При оптимизации памяти способ остается тем же самым: достаточно убрать измерение, отвечающее за виды монет: + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} +``` diff --git a/ru/docs/chapter_graph/graph.assets/adjacency_list.png b/ru/docs/chapter_graph/graph.assets/adjacency_list.png new file mode 100644 index 000000000..86984a3e1 Binary files /dev/null and b/ru/docs/chapter_graph/graph.assets/adjacency_list.png differ diff --git a/ru/docs/chapter_graph/graph.assets/adjacency_matrix.png b/ru/docs/chapter_graph/graph.assets/adjacency_matrix.png new file mode 100644 index 000000000..b2c7bd832 Binary files /dev/null and b/ru/docs/chapter_graph/graph.assets/adjacency_matrix.png differ diff --git a/ru/docs/chapter_graph/graph.assets/connected_graph.png b/ru/docs/chapter_graph/graph.assets/connected_graph.png new file mode 100644 index 000000000..411830e22 Binary files /dev/null and b/ru/docs/chapter_graph/graph.assets/connected_graph.png differ diff --git a/ru/docs/chapter_graph/graph.assets/directed_graph.png b/ru/docs/chapter_graph/graph.assets/directed_graph.png new file mode 100644 index 000000000..4847123f9 Binary files /dev/null and b/ru/docs/chapter_graph/graph.assets/directed_graph.png differ diff --git a/ru/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png b/ru/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png new file mode 100644 index 000000000..7fe87ba58 Binary files /dev/null and b/ru/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png differ diff --git a/ru/docs/chapter_graph/graph.assets/weighted_graph.png b/ru/docs/chapter_graph/graph.assets/weighted_graph.png new file mode 100644 index 000000000..fdb4b8134 Binary files /dev/null and b/ru/docs/chapter_graph/graph.assets/weighted_graph.png differ diff --git a/ru/docs/chapter_graph/graph.md b/ru/docs/chapter_graph/graph.md new file mode 100644 index 000000000..0299ae3d0 --- /dev/null +++ b/ru/docs/chapter_graph/graph.md @@ -0,0 +1,83 @@ +# Граф + +Граф (graph) - это нелинейная структура данных, состоящая из вершин (vertex) и ребер (edge). Мы можем абстрактно представить граф $G$ как множество вершин $V$ и множество ребер $E$ . В примере ниже показан граф, содержащий 5 вершин и 7 ребер. + +$$ +\begin{aligned} +V & = \{ 1, 2, 3, 4, 5 \} \newline +E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline +G & = \{ V, E \} \newline +\end{aligned} +$$ + +Если рассматривать вершины как узлы, а ребра как ссылки (указатели), соединяющие эти узлы, то граф можно считать структурой данных, выросшей из связного списка. Как показано на рисунке ниже, **по сравнению с линейными отношениями (связный список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой** , а потому и сложнее. + +![Связь между связным списком, деревом и графом](graph.assets/linkedlist_tree_graph.png) + +## Распространенные типы и термины графов + +В зависимости от того, имеют ли ребра направление, графы делятся на неориентированные графы (undirected graph) и ориентированные графы (directed graph) , как показано на рисунке ниже. + +- В неориентированном графе ребро означает "двустороннюю" связь между двумя вершинами, например отношение "друзья" в WeChat или QQ. +- В ориентированном графе ребро имеет направление, то есть ребра $A \rightarrow B$ и $A \leftarrow B$ независимы друг от друга, как, например, отношения "подписка" и "подписчик" в Weibo или Douyin. + +![Ориентированный и неориентированный графы](graph.assets/directed_graph.png) + +В зависимости от того, достижимы ли все вершины друг из друга, граф делится на связный граф (connected graph) и несвязный граф (disconnected graph) , как показано на рисунке ниже. + +- В связном графе, начиная из некоторой вершины, можно добраться до любой другой вершины. +- В несвязном графе, начиная из некоторой вершины, по крайней мере одна вершина оказывается недостижимой. + +![Связный и несвязный графы](graph.assets/connected_graph.png) + +Мы также можем добавить к ребрам переменную "вес" и тем самым получить взвешенный граф (weighted graph) , показанный на рисунке ниже. Например, в мобильных играх вроде Honor of Kings система может вычислять "степень близости" между игроками по времени, проведенному в совместных играх; такую сеть близости можно описать взвешенным графом. + +![Взвешенный и невзвешенный графы](graph.assets/weighted_graph.png) + +Для структуры данных "граф" используются следующие распространенные термины. + +- Смежность (adjacency): если между двумя вершинами существует ребро, то эти вершины называются "смежными". На рисунке выше вершинам 2, 3, 5 смежна вершина 1. +- Путь (path): последовательность ребер, ведущая из вершины A в вершину B, называется "путем" от A до B. На рисунке выше последовательность ребер 1-5-2-4 представляет один из путей от вершины 1 к вершине 4. +- Степень (degree): число ребер, принадлежащих вершине. Для ориентированного графа входящая степень (in-degree) показывает число ребер, ведущих в вершину, а исходящая степень (out-degree) показывает число ребер, исходящих из вершины. + +## Представление графа + +Распространенные способы представления графа включают "матрицу смежности" и "список смежности". Ниже в качестве примера используется неориентированный граф. + +### Матрица смежности + +Пусть число вершин графа равно $n$ ; тогда матрица смежности (adjacency matrix) использует матрицу размера $n \times n$ для представления графа: каждая строка и каждый столбец соответствуют вершине, а элементы матрицы отражают наличие ребра, то есть показывают, существует между двумя вершинами связь или нет. + +Как показано на рисунке ниже, пусть матрица смежности обозначается как $M$ , а список вершин - как $V$ ; тогда элемент матрицы $M[i, j] = 1$ означает, что между вершинами $V[i]$ и $V[j]$ существует ребро, а элемент $M[i, j] = 0$ означает, что ребра между ними нет. + +![Представление графа матрицей смежности](graph.assets/adjacency_matrix.png) + +Матрица смежности обладает следующими особенностями. + +- В простом графе вершина не может соединяться сама с собой, поэтому элементы на главной диагонали матрицы смежности не имеют смысла. +- Для неориентированного графа ребра в двух направлениях эквивалентны, поэтому матрица смежности симметрична относительно главной диагонали. +- Если заменить в матрице смежности значения $1$ и $0$ на веса, то можно представить и взвешенный граф. + +При представлении графа матрицей смежности мы можем напрямую обращаться к элементам матрицы, чтобы получить информацию о ребрах, поэтому операции добавления, удаления, поиска и изменения обладают высокой эффективностью, равной $O(1)$ . Однако пространственная сложность матрицы равна $O(n^2)$ , поэтому она занимает заметный объем памяти. + +### Список смежности + +Список смежности (adjacency list) использует $n$ связанных списков для представления графа, где узлы списка обозначают вершины. $i$-й список соответствует вершине $i$ и хранит все вершины, смежные с ней, то есть все вершины, соединенные с этой вершиной. На рисунке ниже показан пример графа, представленного списком смежности. + +![Представление графа списком смежности](graph.assets/adjacency_list.png) + +Список смежности хранит только реально существующие ребра, а общее число ребер обычно значительно меньше $n^2$ , поэтому этот способ существенно экономит пространство. Однако для поиска ребра в списке смежности нужно проходить по списку, поэтому по времени он уступает матрице смежности. + +Если посмотреть на рисунок выше, можно заметить, что **структура списка смежности очень похожа на "метод цепочек" в хеш-таблице, поэтому для оптимизации эффективности здесь можно использовать сходные идеи**. Например, когда список становится слишком длинным, его можно преобразовать в AVL-дерево или красно-черное дерево, чтобы улучшить временную сложность с $O(n)$ до $O(\log n)$ ; можно также превратить его в хеш-таблицу и снизить сложность до $O(1)$ . + +## Типичные применения графов + +Как показано в таблице ниже, многие реальные системы можно моделировать графами, а соответствующие задачи затем сводить к задачам вычислений на графах. + +

Таблица   Распространенные графы в реальной жизни

+ +| | Вершина | Ребро | Задача вычислений на графе | +| -------- | ------- | -------------------- | -------------------------- | +| Социальные сети | Пользователь | Дружеская связь | Рекомендация потенциальных друзей | +| Линии метро | Станция | Связность между станциями | Рекомендация кратчайшего маршрута | +| Солнечная система | Небесное тело | Гравитационное взаимодействие между телами | Вычисление орбит планет | diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png new file mode 100644 index 000000000..5005d40c1 Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png new file mode 100644 index 000000000..feaeec941 Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png new file mode 100644 index 000000000..043fd13e6 Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png new file mode 100644 index 000000000..ed902db7b Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png new file mode 100644 index 000000000..0902fbe3a Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png new file mode 100644 index 000000000..0e37f6e27 Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png new file mode 100644 index 000000000..300f802fb Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png new file mode 100644 index 000000000..b1f60f65b Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png new file mode 100644 index 000000000..9acbcafa5 Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png differ diff --git a/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png new file mode 100644 index 000000000..7620ec14f Binary files /dev/null and b/ru/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png differ diff --git a/ru/docs/chapter_graph/graph_operations.md b/ru/docs/chapter_graph/graph_operations.md new file mode 100644 index 000000000..b962fc2a6 --- /dev/null +++ b/ru/docs/chapter_graph/graph_operations.md @@ -0,0 +1,86 @@ +# Базовые операции графа + +Базовые операции графа можно разделить на операции над "ребрами" и операции над "вершинами". В двух способах представления - "матрица смежности" и "список смежности" - реализация будет различаться. + +## Реализация на основе матрицы смежности + +Пусть дан неориентированный граф с числом вершин $n$ . Тогда способы реализации различных операций показаны на рисунках ниже. + +- **Добавление или удаление ребра**: достаточно изменить соответствующее ребро в матрице смежности, это требует $O(1)$ времени. Поскольку граф неориентированный, нужно одновременно обновлять ребра в обоих направлениях. +- **Добавление вершины**: в конец матрицы смежности добавляется одна строка и один столбец, которые полностью заполняются нулями; это требует $O(n)$ времени. +- **Удаление вершины**: из матрицы смежности удаляется одна строка и один столбец. В худшем случае, когда удаляются первая строка и первый столбец, приходится "сдвигать вверх-влево" $(n-1)^2$ элементов, поэтому требуется $O(n^2)$ времени. +- **Инициализация**: передаются $n$ вершин, затем инициализируется список вершин `vertices` длины $n$ , что требует $O(n)$ времени; после этого инициализируется матрица смежности `adjMat` размера $n \times n$ , что требует $O(n^2)$ времени. + +=== "Инициализация матрицы смежности" + ![Инициализация матрицы смежности, добавление и удаление ребер и вершин](graph_operations.assets/adjacency_matrix_step1_initialization.png) + +=== "Добавление ребра" + ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) + +=== "Удаление ребра" + ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) + +=== "Добавление вершины" + ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) + +=== "Удаление вершины" + ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) + +Ниже приведен код реализации графа на основе матрицы смежности: + +```src +[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} +``` + +## Реализация на основе списка смежности + +Пусть неориентированный граф содержит в сумме $n$ вершин и $m$ ребер. Тогда различные операции можно реализовать способом, показанным на рисунках ниже. + +- **Добавление ребра**: достаточно добавить ребро в конец списка, соответствующего вершине; это требует $O(1)$ времени. Поскольку граф неориентированный, нужно одновременно добавлять ребра в обоих направлениях. +- **Удаление ребра**: нужно найти и удалить указанное ребро в списке, соответствующем вершине; это требует $O(m)$ времени. В неориентированном графе нужно удалять ребра в обоих направлениях. +- **Добавление вершины**: в список смежности добавляется еще один список, а новая вершина становится его головным узлом; это требует $O(1)$ времени. +- **Удаление вершины**: требуется пройти по всему списку смежности и удалить все ребра, содержащие указанную вершину; это требует $O(n + m)$ времени. +- **Инициализация**: в списке смежности создаются $n$ вершин и $2m$ ребер; это требует $O(n + m)$ времени. + +=== "Инициализация списка смежности" + ![Инициализация списка смежности, добавление и удаление ребер и вершин](graph_operations.assets/adjacency_list_step1_initialization.png) + +=== "Добавление ребра" + ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) + +=== "Удаление ребра" + ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) + +=== "Добавление вершины" + ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) + +=== "Удаление вершины" + ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) + +Ниже приведен код списка смежности. По сравнению с рисунками выше, реальная реализация имеет следующие отличия. + +- Чтобы упростить добавление и удаление вершин, а также упростить код, мы используем список, то есть динамический массив, вместо связного списка. +- Для хранения списка смежности используется хеш-таблица, где `key` - это экземпляр вершины, а `value` - список смежных вершин данной вершины. + +Кроме того, в списке смежности мы используем класс `Vertex` для представления вершины. Причина в следующем: если, как и в матрице смежности, различать вершины по индексам списка, то при удалении вершины с индексом $i$ пришлось бы обходить весь список смежности и уменьшать на $1$ все индексы, большие $i$ , что крайне неэффективно. Если же каждая вершина является уникальным экземпляром `Vertex` , то после удаления одной вершины остальные вершины менять уже не требуется. + +```src +[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} +``` + +## Сравнение эффективности + +Пусть в графе имеется $n$ вершин и $m$ ребер. В таблице ниже сравниваются временная и пространственная эффективность матрицы смежности и списка смежности. Обрати внимание: список смежности (связный список) соответствует реализации из этой статьи, а список смежности (хеш-таблица) означает вариант, где все списки заменены хеш-таблицами. + +

Таблица   Сравнение матрицы смежности и списка смежности

+ +| | Матрица смежности | Список смежности (связный список) | Список смежности (хеш-таблица) | +| ------------ | ----------------- | --------------------------------- | ------------------------------ | +| Проверка смежности | $O(1)$ | $O(n)$ | $O(1)$ | +| Добавление ребра | $O(1)$ | $O(1)$ | $O(1)$ | +| Удаление ребра | $O(1)$ | $O(n)$ | $O(1)$ | +| Добавление вершины | $O(n)$ | $O(1)$ | $O(1)$ | +| Удаление вершины | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| Занимаемая память | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +Если смотреть только на таблицу, может показаться, что список смежности на основе хеш-таблицы является лучшим и по времени, и по памяти. Но на практике операции над ребрами в матрице смежности часто выполняются быстрее, потому что там нужен лишь один доступ к массиву или одно присваивание. В целом матрица смежности воплощает принцип "обмен пространства на время", а список смежности - принцип "обмен времени на пространство". diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs.png new file mode 100644 index 000000000..4b7fd506a Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png new file mode 100644 index 000000000..46cc9149b Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png new file mode 100644 index 000000000..fd085c825 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png new file mode 100644 index 000000000..404bba245 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png new file mode 100644 index 000000000..aebbad5af Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png new file mode 100644 index 000000000..5eb81548f Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png new file mode 100644 index 000000000..3d779047f Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png new file mode 100644 index 000000000..ca96dca76 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png new file mode 100644 index 000000000..46dd1298a Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png new file mode 100644 index 000000000..264b3840c Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png new file mode 100644 index 000000000..e0021dc1b Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png new file mode 100644 index 000000000..8a00d182e Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs.png new file mode 100644 index 000000000..80daae126 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png new file mode 100644 index 000000000..aae144212 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png new file mode 100644 index 000000000..e5035c7d9 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png new file mode 100644 index 000000000..dbfc38675 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png new file mode 100644 index 000000000..35ce55ac6 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png new file mode 100644 index 000000000..46831cec1 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png new file mode 100644 index 000000000..8555d9e73 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png new file mode 100644 index 000000000..1b2b461d3 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png new file mode 100644 index 000000000..f76c5fa89 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png new file mode 100644 index 000000000..4aeff897d Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png new file mode 100644 index 000000000..232361ad5 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png new file mode 100644 index 000000000..372b9c786 Binary files /dev/null and b/ru/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png differ diff --git a/ru/docs/chapter_graph/graph_traversal.md b/ru/docs/chapter_graph/graph_traversal.md new file mode 100644 index 000000000..fb25ee8dc --- /dev/null +++ b/ru/docs/chapter_graph/graph_traversal.md @@ -0,0 +1,140 @@ +# Обход графа + +Дерево представляет отношение "один ко многим", а граф имеет более высокую степень свободы и может выражать произвольные отношения "многие ко многим". Поэтому мы можем рассматривать дерево как частный случай графа. Очевидно, что **операции обхода дерева также являются частным случаем операций обхода графа**. + +И графы, и деревья требуют использования поисковых алгоритмов для реализации обхода. Способы обхода графа также делятся на два типа: обход в ширину и обход в глубину. + +## Обход в ширину + +**Обход в ширину - это способ обхода "от близкого к далекому": начиная с некоторого узла, мы всегда в первую очередь посещаем ближайшие вершины и слой за слоем расширяемся наружу**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, мы сначала обходим все смежные вершины этой вершины, затем все смежные вершины следующей вершины и так далее, пока не будут посещены все вершины. + +![Обход графа в ширину](graph_traversal.assets/graph_bfs.png) + +### Реализация алгоритма + +BFS обычно реализуется с помощью очереди, код приведен ниже. Очередь обладает свойством "первым пришел - первым вышел", что хорошо соответствует идее BFS "от близкого к далекому". + +1. Поместить стартовую вершину обхода `startVet` в очередь и запустить цикл. +2. На каждой итерации цикла извлекать вершину из головы очереди и записывать факт ее посещения, после чего добавлять все смежные вершины этой вершины в хвост очереди. +3. Повторять шаг `2.` до тех пор, пока не будут посещены все вершины. + +Чтобы предотвратить повторный обход вершин, нам нужен хеш-набор `visited` , в котором будет записываться, какие узлы уже посещены. + +!!! tip + + Хеш-набор можно рассматривать как хеш-таблицу, которая хранит только `key` и не хранит `value` . Он позволяет выполнять добавление, удаление, поиск и изменение `key` за $O(1)$ времени. Благодаря уникальности `key` хеш-набор обычно используется, например, для устранения повторов. + +```src +[file]{graph_bfs}-[class]{}-[func]{graph_bfs} +``` + +Код сравнительно абстрактен, поэтому рекомендуется сверяться с рисунками ниже для лучшего понимания. + +=== "<1>" + ![Шаги обхода графа в ширину](graph_traversal.assets/graph_bfs_step1.png) + +=== "<2>" + ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) + +=== "<3>" + ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) + +=== "<4>" + ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) + +=== "<5>" + ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) + +=== "<6>" + ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) + +=== "<7>" + ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) + +=== "<8>" + ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) + +=== "<9>" + ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) + +=== "<10>" + ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) + +=== "<11>" + ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) + +!!! question "Является ли последовательность обхода в ширину единственной?" + + Нет. Обход в ширину требует только соблюдения порядка "от близкого к далекому", **а порядок обхода нескольких вершин на одинаковом расстоянии может произвольно меняться**. Например, на рисунке выше можно поменять местами порядок посещения вершин $1$ и $3$ , а также в произвольном порядке переставить вершины $2$, $4$, $6$ . + +### Анализ сложности + +**Временная сложность**: все вершины по одному разу помещаются в очередь и извлекаются из нее, что требует $O(|V|)$ времени; при обходе смежных вершин, поскольку граф неориентированный, все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени; в сумме получается $O(|V| + |E|)$ . + +**Пространственная сложность**: список `res` , хеш-набор `visited` и очередь `que` в худшем случае могут содержать до $|V|$ вершин, поэтому требуется $O(|V|)$ памяти. + +## Обход в глубину + +**Обход в глубину - это способ обхода, при котором сначала идут до самого конца, а когда дальше идти нельзя, откатываются назад**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, мы выбираем некоторую смежную вершину текущей вершины, идем до упора, затем возвращаемся назад, снова идем до упора и так далее, пока не будут посещены все вершины. + +![Обход графа в глубину](graph_traversal.assets/graph_dfs.png) + +### Реализация алгоритма + +Такой алгоритмический шаблон "дойти до конца и вернуться" обычно реализуется через рекурсию. Подобно обходу в ширину, в обходе в глубину мы также используем хеш-набор `visited` для записи уже посещенных вершин и тем самым избегаем повторного посещения. + +```src +[file]{graph_dfs}-[class]{}-[func]{graph_dfs} +``` + +Алгоритмический процесс обхода в глубину показан на рисунках ниже. + +- **Прямая пунктирная линия обозначает нисходящее рекурсивное развертывание** , то есть запуск нового рекурсивного метода для посещения новой вершины. +- **Изогнутая пунктирная линия обозначает обратный возврат по рекурсии** , то есть данный рекурсивный метод завершился и управление вернулось туда, откуда он был вызван. + +Чтобы лучше понять алгоритм, рекомендуется совместить рисунки ниже с кодом и мысленно проследить весь процесс DFS, включая моменты запуска и возврата каждого рекурсивного вызова. + +=== "<1>" + ![Шаги обхода графа в глубину](graph_traversal.assets/graph_dfs_step1.png) + +=== "<2>" + ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) + +=== "<3>" + ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) + +=== "<4>" + ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) + +=== "<5>" + ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) + +=== "<6>" + ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) + +=== "<7>" + ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) + +=== "<8>" + ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) + +=== "<9>" + ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) + +=== "<10>" + ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) + +=== "<11>" + ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) + +!!! question "Является ли последовательность обхода в глубину единственной?" + + Как и в случае обхода в ширину, последовательность DFS тоже не является единственной. Для заданной вершины допустимо сначала углубиться в любое направление, то есть порядок смежных вершин может быть произвольным, и все такие варианты будут корректными обходами в глубину. + + Если взять в качестве примера обход дерева, то варианты "корень $\rightarrow$ лево $\rightarrow$ право", "лево $\rightarrow$ корень $\rightarrow$ право" и "лево $\rightarrow$ право $\rightarrow$ корень" соответствуют прямому, симметричному и обратному обходам соответственно. Они показывают три разных приоритета обхода, но все они относятся к обходу в глубину. + +### Анализ сложности + +**Временная сложность**: все вершины будут посещены по $1$ разу, что требует $O(|V|)$ времени; все ребра будут посещены по $2$ раза, что требует $O(2|E|)$ времени; суммарно получается $O(|V| + |E|)$ . + +**Пространственная сложность**: число вершин в списке `res` и хеш-наборе `visited` в худшем случае достигает $|V|$ , максимальная глубина рекурсии тоже равна $|V|$ , поэтому требуется $O(|V|)$ памяти. diff --git a/ru/docs/chapter_graph/index.md b/ru/docs/chapter_graph/index.md new file mode 100644 index 000000000..0a7f42391 --- /dev/null +++ b/ru/docs/chapter_graph/index.md @@ -0,0 +1,9 @@ +# Графы + +![Графы](../assets/covers/chapter_graph.jpg) + +!!! abstract + + На жизненном пути мы подобны узлам, соединенным бесчисленными невидимыми ребрами. + + Каждая встреча и каждое расставание оставляют в этой огромной сети свой особый след. diff --git a/ru/docs/chapter_graph/summary.md b/ru/docs/chapter_graph/summary.md new file mode 100644 index 000000000..444410f07 --- /dev/null +++ b/ru/docs/chapter_graph/summary.md @@ -0,0 +1,31 @@ +# Краткие итоги + +### Основные моменты + +- Граф состоит из вершин и ребер и может быть задан как множество вершин и множество ребер. +- По сравнению с линейными отношениями (связный список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой и потому более сложны. +- Ребра ориентированного графа имеют направление, в связном графе любые вершины достижимы друг из друга, а в взвешенном графе каждое ребро несет переменную веса. +- Матрица смежности использует матрицу для представления графа: каждая строка и каждый столбец соответствуют вершине, а элементы матрицы показывают, есть между двумя вершинами ребро или нет. Матрица смежности очень эффективна для операций добавления, удаления, поиска и изменения, но расходует больше памяти. +- Список смежности использует несколько списков для представления графа; $i$-й список соответствует вершине $i$ и хранит все ее смежные вершины. По сравнению с матрицей смежности список смежности экономит пространство, но для поиска ребра в нем приходится обходить список, поэтому по времени он уступает. +- Когда списки в списке смежности становятся слишком длинными, их можно преобразовать в красно-черное дерево или хеш-таблицу, чтобы ускорить поиск. +- С точки зрения алгоритмической идеи матрица смежности отражает принцип "обмен пространства на время", а список смежности - принцип "обмена времени на пространство". +- Графы можно использовать для моделирования различных реальных систем, таких как социальные сети, линии метро и так далее. +- Дерево является частным случаем графа, а обход дерева - частным случаем обхода графа. +- Обход графа в ширину представляет собой способ поиска, который расширяется от ближнего к дальнему и обычно реализуется с помощью очереди. +- Обход графа в глубину представляет собой способ поиска, который сначала идет до самого конца, а затем возвращается назад, когда путь исчерпан; обычно он реализуется на основе рекурсии. + +### Q & A + +**Q**: Что считается путем: последовательность вершин или последовательность ребер? + +Определение в разных языковых версиях Википедии различается: в английской версии путь определяется как "последовательность ребер", а в китайской версии - как "последовательность вершин". В английской версии исходная формулировка выглядит так: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. + +В этой книге путь рассматривается как последовательность ребер, а не как последовательность вершин. Причина в том, что между двумя вершинами может существовать несколько ребер, и в таком случае каждому ребру соответствует свой путь. + +**Q**: Есть ли в несвязном графе вершины, до которых нельзя дойти? + +В несвязном графе, начиная из некоторой вершины, по крайней мере одна вершина оказывается недостижимой. Чтобы обойти весь несвязный граф, нужно задать несколько стартовых точек и обойти все связные компоненты графа. + +**Q**: Есть ли требования к порядку вершин в списке "всех вершин, соединенных с данной вершиной" в списке смежности? + +Порядок может быть произвольным. Но на практике может понадобиться сортировка по определенному правилу, например по порядку добавления вершин или по возрастанию значений вершин; это помогает быстро находить вершины с некоторым экстремальным свойством. diff --git a/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png new file mode 100644 index 000000000..a8a7a6e3f Binary files /dev/null and b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png differ diff --git a/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png new file mode 100644 index 000000000..e52da1aea Binary files /dev/null and b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png differ diff --git a/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png new file mode 100644 index 000000000..2c79c48f0 Binary files /dev/null and b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png differ diff --git a/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png new file mode 100644 index 000000000..a70174d68 Binary files /dev/null and b/ru/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png differ diff --git a/ru/docs/chapter_greedy/fractional_knapsack_problem.md b/ru/docs/chapter_greedy/fractional_knapsack_problem.md new file mode 100644 index 000000000..8fddc3c92 --- /dev/null +++ b/ru/docs/chapter_greedy/fractional_knapsack_problem.md @@ -0,0 +1,52 @@ +# Задача о дробном рюкзаке + +!!! question + + Дано $n$ предметов. Вес предмета $i$ равен $wgt[i-1]$, ценность равна $val[i-1]$, также дан рюкзак вместимостью $cap$. Каждый предмет можно выбрать только один раз, **но разрешается взять лишь часть предмета, а ценность вычисляется пропорционально взятому весу**. Требуется найти максимальную ценность предметов в рюкзаке при ограниченной вместимости. Пример показан на рисунке ниже. + +![Пример данных для задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_example.png) + +Задача о дробном рюкзаке в целом очень похожа на задачу о рюкзаке 0-1: состояние включает текущий предмет $i$ и вместимость $c$, а цель состоит в нахождении максимальной ценности при заданной вместимости рюкзака. + +Отличие в том, что здесь разрешено брать только часть предмета. Как показано на рисунке ниже, **мы можем произвольно делить предмет и вычислять соответствующую ценность пропорционально весу**. + +1. Для предмета $i$ его ценность на единицу веса равна $val[i-1] / wgt[i-1]$, сокращенно - удельная ценность. +2. Если взять часть предмета $i$ весом $w$, то ценность рюкзака увеличится на $w \times val[i-1] / wgt[i-1]$. + +![Ценность предмета на единицу веса](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) + +### Определение жадной стратегии + +Максимизация общей ценности предметов в рюкзаке **по сути равносильна максимизации ценности на единицу веса**. Отсюда естественно выводится следующая жадная стратегия. + +1. Отсортировать предметы по убыванию удельной ценности. +2. Перебирать все предметы и **на каждом шаге жадно выбирать предмет с наибольшей удельной ценностью**. +3. Если оставшейся вместимости рюкзака недостаточно, взять часть текущего предмета, чтобы заполнить рюкзак. + +![Жадная стратегия для задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) + +### Код реализации + +Мы вводим класс `Item`, чтобы можно было сортировать предметы по удельной ценности. Далее циклически выполняем жадный выбор и, когда рюкзак заполнен, выходим и возвращаем ответ: + +```src +[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} +``` + +Встроенный алгоритм сортировки обычно имеет временную сложность $O(\log n)$, а пространственная сложность обычно равна $O(\log n)$ или $O(n)$, в зависимости от конкретной реализации в языке программирования. + +Помимо сортировки, в худшем случае потребуется пройти весь список предметов, **поэтому временная сложность равна $O(n)$**, где $n$ - число предметов. + +Поскольку инициализируется список объектов `Item`, **пространственная сложность равна $O(n)$**. + +### Доказательство корректности + +Используем доказательство от противного. Предположим, что предмет $x$ имеет наибольшую удельную ценность, некоторый алгоритм получил максимальную ценность `res`, но в найденном решении предмет $x$ отсутствует. + +Теперь вынем из рюкзака произвольный предмет единичного веса и заменим его на предмет $x$ того же веса. Поскольку предмет $x$ имеет наибольшую удельную ценность, общая ценность после замены обязательно станет больше `res`. **Это противоречит тому, что `res` является оптимальным решением, а значит оптимальное решение обязательно содержит предмет $x$**. + +Для других предметов в этом решении можно построить аналогичное противоречие. Иными словами, **предметы с большей удельной ценностью всегда являются более выгодным выбором**, а значит жадная стратегия корректна. + +Как показано на рисунке ниже, если рассматривать вес предметов и их удельную ценность как горизонтальную и вертикальную оси двумерной диаграммы, то задачу о дробном рюкзаке можно интерпретировать как «поиск максимальной площади, ограниченной конечным отрезком по горизонтали». Эта аналогия помогает понять корректность жадной стратегии с геометрической точки зрения. + +![Геометрическая интерпретация задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) diff --git a/ru/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png b/ru/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png new file mode 100644 index 000000000..b9338a7cd Binary files /dev/null and b/ru/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png differ diff --git a/ru/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png b/ru/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png new file mode 100644 index 000000000..56810bda9 Binary files /dev/null and b/ru/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png differ diff --git a/ru/docs/chapter_greedy/greedy_algorithm.md b/ru/docs/chapter_greedy/greedy_algorithm.md new file mode 100644 index 000000000..d15ddeaaa --- /dev/null +++ b/ru/docs/chapter_greedy/greedy_algorithm.md @@ -0,0 +1,94 @@ +# Жадный алгоритм + +Жадный алгоритм (greedy algorithm) - это распространенный подход к решению задач оптимизации. Его основная идея состоит в том, чтобы на каждом этапе принятия решения выбирать вариант, который выглядит наилучшим прямо сейчас, то есть жадно принимать локально оптимальные решения в надежде получить глобально оптимальный результат. Жадные алгоритмы лаконичны и эффективны, поэтому широко применяются во многих практических задачах. + +Жадные алгоритмы и динамическое программирование часто используются для решения задач оптимизации. У них есть некоторое сходство, например оба опираются на свойство оптимальной подструктуры, но принципы работы различаются. + +- Динамическое программирование учитывает все решения предыдущих этапов при выборе текущего решения и использует ответы для прошлых подзадач, чтобы построить ответ для текущей подзадачи. +- Жадный алгоритм не учитывает прошлые решения, а просто движется вперед, каждый раз делая жадный выбор, постепенно сужая область задачи, пока она не будет решена. + +Сначала разберем принцип работы жадного алгоритма на примере задачи «размен монет». Эта задача уже встречалась в разделе «задача о полном рюкзаке», поэтому она наверняка вам знакома. + +!!! question + + Дано $n$ видов монет. Номинал монеты $i$ равен $coins[i - 1]$, целевая сумма равна $amt$, причем каждую монету можно брать неограниченное число раз. Требуется найти минимальное число монет, которыми можно набрать целевую сумму. Если набрать сумму невозможно, верните $-1$. + +Жадная стратегия для этой задачи показана на рисунке ниже. Для заданной целевой суммы **мы жадно выбираем монету, которая не превышает ее и находится к ней ближе всего**, и повторяем этот шаг, пока не получим нужную сумму. + +![Жадная стратегия для задачи о размене монет](greedy_algorithm.assets/coin_change_greedy_strategy.png) + +Код реализации выглядит следующим образом: + +```src +[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} +``` + +У вас может невольно вырваться: So clean! Жадный алгоритм решает задачу размена монет всего примерно десятью строками кода. + +## Преимущества и ограничения жадного алгоритма + +**Жадный алгоритм не только прост в действиях и реализации, но и обычно очень эффективен**. В приведенном выше коде обозначим минимальный номинал монеты через $\min(coins)$, тогда жадный выбор выполняется не более чем $amt / \min(coins)$ раз, а временная сложность равна $O(amt / \min(coins))$. Это на порядок меньше, чем временная сложность решения через динамическое программирование $O(n \times amt)$. + +Однако **для некоторых наборов номиналов монет жадный алгоритм не может найти оптимальный ответ**. Ниже показаны два примера. + +- **Положительный пример $coins = [1, 5, 10, 20, 50, 100]$**: для такого набора монет при любом $amt$ жадный алгоритм находит оптимальное решение. +- **Отрицательный пример $coins = [1, 20, 50]$**: пусть $amt = 60$. Жадный алгоритм найдет только комбинацию $50 + 1 \times 10$, то есть всего $11$ монет, тогда как динамическое программирование находит оптимум $20 + 20 + 20$, где требуется лишь $3$ монеты. +- **Отрицательный пример $coins = [1, 49, 50]$**: пусть $amt = 98$. Жадный алгоритм найдет только комбинацию $50 + 1 \times 48$, то есть всего $49$ монет, тогда как динамическое программирование находит оптимум $49 + 49$, где требуется лишь $2$ монеты. + +![Примеры, где жадный алгоритм не находит оптимального решения](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) + +Иными словами, в задаче о размене монет жадный алгоритм не гарантирует нахождение глобально оптимального решения и иногда может приводить к очень плохому ответу. Для этой задачи больше подходит динамическое программирование. + +В общем случае жадный алгоритм применим в двух следующих ситуациях. + +1. **Можно гарантировать нахождение оптимального решения**: в таком случае жадный алгоритм часто является лучшим выбором, поскольку обычно он эффективнее, чем поиск с возвратом и динамическое программирование. +2. **Можно найти приближенно оптимальное решение**: в таком случае жадный алгоритм тоже полезен. Для многих сложных задач поиск глобального оптимума очень труден, и возможность быстро найти субоптимальный ответ уже весьма ценна. + +## Свойства жадного алгоритма + +Тогда возникает вопрос: какие задачи подходят для решения жадным алгоритмом? Или, другими словами, в каких случаях жадный алгоритм может гарантировать оптимальный ответ? + +По сравнению с динамическим программированием условия применения жадного алгоритма строже. В основном нас интересуют два свойства задачи. + +- **Свойство жадного выбора**: только когда локально оптимальный выбор всегда может привести к глобально оптимальному решению, жадный алгоритм способен гарантировать оптимум. +- **Оптимальная подструктура**: оптимальное решение исходной задачи содержит оптимальные решения подзадач. + +Оптимальная подструктура уже обсуждалась в главе «Динамическое программирование», поэтому здесь не будем повторяться. Стоит отметить, что у некоторых задач оптимальная подструктура не столь очевидна, но их все равно можно решать жадным алгоритмом. + +Основное внимание мы уделяем тому, как определить свойство жадного выбора. Хотя формулировка выглядит довольно простой, **на практике для многих задач доказать свойство жадного выбора совсем не легко**. + +Например, в задаче о размене монет легко привести контрпример и опровергнуть свойство жадного выбора, но вот доказать его истинность намного сложнее. Если спросить: **для каких наборов монет можно использовать жадный алгоритм**? - обычно удается дать лишь интуитивный или примерный ответ, а не строгое математическое доказательство. + +!!! quote + + Существует статья, в которой приводится алгоритм со временной сложностью $O(n^3)$ для определения того, можно ли с помощью жадного алгоритма находить оптимальный размен для любой суммы в заданной системе монет. + + Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. + +## Этапы решения задач жадным алгоритмом + +В общем виде процесс решения жадной задачи можно разбить на три шага. + +1. **Анализ задачи**: разобраться в свойствах задачи, включая определение состояний, целевой функции и ограничений. Этот этап присутствует и в поиске с возвратом, и в динамическом программировании. +2. **Определение жадной стратегии**: определить, какой жадный выбор следует делать на каждом шаге. Эта стратегия должна уменьшать размер задачи на каждом этапе и в итоге привести к решению всей задачи. +3. **Доказательство корректности**: обычно требуется доказать, что задача обладает свойством жадного выбора и оптимальной подструктурой. На этом этапе может понадобиться математическое доказательство, например индукция или доказательство от противного. + +Определение жадной стратегии - это ключевой этап решения, но на практике он часто оказывается непростым по следующим причинам. + +- **Жадные стратегии для разных задач сильно различаются**. Для многих задач стратегия довольно очевидна, и до нее можно дойти за счет общих рассуждений и нескольких проб. Но в более сложных задачах жадная стратегия может быть очень скрытой, и тут уже многое зависит от опыта решения задач и алгоритмической подготовки. +- **Некоторые жадные стратегии выглядят убедительно, но оказываются обманчивыми**. Бывает, что мы с уверенностью придумали жадную стратегию, написали код и отправили его на проверку, а часть тестов не проходит. Причина в том, что спроектированная стратегия лишь «частично верна», и описанная выше задача о размене монет - типичный пример. + +Чтобы гарантировать корректность, нужно дать строгое математическое доказательство жадной стратегии, **обычно с использованием доказательства от противного или математической индукции**. + +Однако и доказательство корректности может оказаться непростой задачей. Если идей нет, мы обычно начинаем отлаживать код на тестовых примерах, постепенно меняя и проверяя жадную стратегию. + +## Типичные задачи для жадного алгоритма + +Жадные алгоритмы часто применяются в задачах оптимизации, которые обладают свойством жадного выбора и оптимальной подструктурой. Ниже приведены некоторые типичные задачи, решаемые жадным подходом. + +- **Задача о размене монет**: при некоторых системах монет жадный алгоритм всегда дает оптимальный ответ. +- **Задача о расписании интервалов**: пусть есть несколько задач, каждая выполняется в некотором временном интервале, и требуется завершить как можно больше задач. Если каждый раз выбирать задачу с самым ранним временем окончания, то жадный алгоритм дает оптимальный ответ. +- **Задача о дробном рюкзаке**: дана группа предметов и грузоподъемность. Требуется выбрать предметы так, чтобы их общий вес не превышал ограничение, а общая ценность была максимальной. Если каждый раз выбирать предмет с наилучшим отношением стоимости к весу, то в некоторых случаях жадный алгоритм дает оптимальный ответ. +- **Задача о покупке и продаже акций**: дана история цен акции. Можно совершать несколько сделок, но если акция уже куплена, то до продажи покупать снова нельзя. Цель - получить максимальную прибыль. +- **Код Хаффмана**: это жадный алгоритм для сжатия данных без потерь. Построив дерево Хаффмана и каждый раз объединяя два узла с наименьшей частотой, мы получаем дерево с минимальной взвешенной длиной пути, то есть минимальной длиной кодирования. +- **Алгоритм Дейкстры**: это жадный алгоритм решения задачи о кратчайших путях от заданной исходной вершины до всех остальных вершин. diff --git a/ru/docs/chapter_greedy/index.md b/ru/docs/chapter_greedy/index.md new file mode 100644 index 000000000..836f1a144 --- /dev/null +++ b/ru/docs/chapter_greedy/index.md @@ -0,0 +1,9 @@ +# Жадность + +![Жадность](../assets/covers/chapter_greedy.jpg) + +!!! abstract + + Подсолнух поворачивается к солнцу, постоянно стремясь к наилучшим условиям для роста. + + Жадная стратегия через цепочку простых выборов постепенно приводит к наилучшему ответу. diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png new file mode 100644 index 000000000..13267c97c Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png new file mode 100644 index 000000000..aa6ecdae6 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png new file mode 100644 index 000000000..596998ac9 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png new file mode 100644 index 000000000..85aeb48d2 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png new file mode 100644 index 000000000..a6f9a7825 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png new file mode 100644 index 000000000..446a7c4cc Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png new file mode 100644 index 000000000..a3de64469 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png new file mode 100644 index 000000000..0c4b52233 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png new file mode 100644 index 000000000..77bbc7dbd Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png new file mode 100644 index 000000000..eb7e9a910 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png new file mode 100644 index 000000000..867cb5c1b Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png new file mode 100644 index 000000000..6fba96745 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png new file mode 100644 index 000000000..caaaa25b1 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png new file mode 100644 index 000000000..8bfb4c701 Binary files /dev/null and b/ru/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png differ diff --git a/ru/docs/chapter_greedy/max_capacity_problem.md b/ru/docs/chapter_greedy/max_capacity_problem.md new file mode 100644 index 000000000..154c8108d --- /dev/null +++ b/ru/docs/chapter_greedy/max_capacity_problem.md @@ -0,0 +1,99 @@ +# Задача о максимальной вместимости + +!!! question + + Дан массив $ht$, где каждый элемент обозначает высоту вертикальной перегородки. Любые две перегородки в массиве вместе с пространством между ними образуют контейнер. + + Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина - разностью индексов двух перегородок в массиве. + + Требуется выбрать две перегородки так, чтобы образованный ими контейнер имел максимальную вместимость. Пример показан на рисунке ниже. + +![Пример данных для задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_example.png) + +Контейнер образуется произвольными двумя перегородками, **поэтому состоянием задачи служит пара индексов этих перегородок, обозначим ее как $[i, j]$**. + +Согласно условию, вместимость равна произведению высоты на ширину, где высота определяется короткой перегородкой, а ширина - разностью индексов двух перегородок. Обозначим вместимость через $cap[i, j]$, тогда формула принимает вид: + +$$ +cap[i, j] = \min(ht[i], ht[j]) \times (j - i) +$$ + +Пусть длина массива равна $n$. Тогда число пар перегородок, то есть общее число состояний, равно $C_n^2 = \frac{n(n - 1)}{2}$. Самый прямолинейный подход - **перебрать все состояния**, после чего найти максимальную вместимость. Его временная сложность равна $O(n^2)$. + +### Определение жадной стратегии + +У этой задачи есть и более эффективное решение. Как показано на рисунке ниже, рассмотрим состояние $[i, j]$, где индексы удовлетворяют $i < j$, а высоты - условию $ht[i] < ht[j]$, то есть $i$ - короткая перегородка, а $j$ - длинная. + +![Начальное состояние](max_capacity_problem.assets/max_capacity_initial_state.png) + +Как показано на рисунке ниже, **если в этот момент сдвинуть длинную перегородку $j$ ближе к короткой перегородке $i$, то вместимость обязательно уменьшится**. + +Причина в том, что после смещения длинной перегородки $j$ ширина $j-i$ обязательно станет меньше, а высота определяется короткой перегородкой, поэтому высота либо останется прежней (если $i$ останется короткой перегородкой), либо уменьшится (если сдвинутая $j$ станет короткой перегородкой). + +![Состояние после перемещения длинной перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_long_board.png) + +Рассуждая в обратную сторону, **только сдвигая короткую перегородку $i$ внутрь, мы можем получить шанс увеличить вместимость**. Хотя ширина при этом обязательно уменьшится, **высота может возрасти** (если после перемещения короткая перегородка $i$ станет выше). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается. + +![Состояние после перемещения короткой перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_short_board.png) + +Отсюда и выводится жадная стратегия для этой задачи: инициализировать два указателя по краям контейнера и на каждом шаге сдвигать внутрь указатель, соответствующий короткой перегородке, пока указатели не встретятся. + +На рисунках ниже показан процесс выполнения этой жадной стратегии. + +1. В начальном состоянии указатели $i$ и $j$ стоят на двух концах массива. +2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость. +3. Сравнить высоты перегородок $i$ и $j$, после чего сдвинуть короткую перегородку на одну позицию внутрь. +4. Повторять шаги `2.` и `3.` до тех пор, пока $i$ и $j$ не встретятся. + +=== "<1>" + ![Жадный процесс решения задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_greedy_step1.png) + +=== "<2>" + ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) + +=== "<3>" + ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) + +=== "<4>" + ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) + +=== "<5>" + ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) + +=== "<6>" + ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) + +=== "<7>" + ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) + +=== "<8>" + ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) + +=== "<9>" + ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) + +### Код реализации + +Цикл в коде выполняется не более $n$ раз, **поэтому временная сложность равна $O(n)$**. + +Переменные $i$, $j$, $res$ используют дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**. + +```src +[file]{max_capacity}-[class]{}-[func]{max_capacity} +``` + +### Доказательство корректности + +Жадный алгоритм быстрее полного перебора именно потому, что каждый жадный шаг «пропускает» часть состояний. + +Например, в состоянии $cap[i, j]$ перегородка $i$ является короткой, а $j$ - длинной. Если жадно сдвинуть короткую перегородку $i$ на одну позицию внутрь, то состояния, показанные на рисунке ниже, будут «пропущены». **Это означает, что позже мы уже не сможем проверить вместимость этих состояний**. + +$$ +cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] +$$ + +![Состояния, пропущенные из-за смещения короткой перегородки](max_capacity_problem.assets/max_capacity_skipped_states.png) + +Нетрудно заметить, что **эти пропущенные состояния на самом деле и есть все состояния, в которых длинная перегородка $j$ сдвигается внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно уменьшает вместимость. Иными словами, пропущенные состояния не могут быть оптимальным решением, **поэтому их пропуск не приводит к потере оптимума**. + +Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно эффективна. diff --git a/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png new file mode 100644 index 000000000..315181153 Binary files /dev/null and b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png differ diff --git a/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png new file mode 100644 index 000000000..284e81b55 Binary files /dev/null and b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png differ diff --git a/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png new file mode 100644 index 000000000..c6e5baf71 Binary files /dev/null and b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png differ diff --git a/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png new file mode 100644 index 000000000..6ef5f6811 Binary files /dev/null and b/ru/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png differ diff --git a/ru/docs/chapter_greedy/max_product_cutting_problem.md b/ru/docs/chapter_greedy/max_product_cutting_problem.md new file mode 100644 index 000000000..dc3c1eead --- /dev/null +++ b/ru/docs/chapter_greedy/max_product_cutting_problem.md @@ -0,0 +1,85 @@ +# Задача о максимальном произведении разбиения + +!!! question + + Дан положительный целый $n$. Требуется разложить его в сумму как минимум двух положительных целых чисел и найти максимально возможное произведение всех полученных чисел, как показано на рисунке ниже. + +![Определение задачи о максимальном произведении разбиения](max_product_cutting_problem.assets/max_product_cutting_definition.png) + +Предположим, что мы разбили $n$ на $m$ целочисленных множителей, где $i$-й множитель обозначим через $n_i$, то есть + +$$ +n = \sum_{i=1}^{m}n_i +$$ + +Цель задачи - найти максимальное произведение всех целочисленных множителей, то есть + +$$ +\max(\prod_{i=1}^{m}n_i) +$$ + +Нужно понять: каким должно быть число частей $m$ и какими должны быть значения каждого $n_i$? + +### Определение жадной стратегии + +Из опыта известно, что произведение двух целых чисел часто больше их суммы. Предположим, что мы выделяем из $n$ множитель $2$, тогда произведение равно $2(n-2)$. Сравним это выражение с $n$: + +$$ +\begin{aligned} +2(n-2) & \geq n \newline +2n - n - 4 & \geq 0 \newline +n & \geq 4 +\end{aligned} +$$ + +Как показано на рисунке ниже, когда $n \geq 4$, выделение множителя $2$ увеличивает произведение. **Это означает, что все целые числа, большие либо равные $4$, следует продолжать разбивать**. + +**Жадная стратегия 1**: если в схеме разбиения присутствует множитель $\geq 4$, то его нужно дальше разбивать. В конечной схеме разбиения должны остаться только множители $1$, $2$, $3$. + +![Разбиение увеличивает произведение](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) + +Теперь подумаем, какой множитель является наилучшим. Среди $1$, $2$, $3$ очевидно худшим является $1$, потому что всегда выполняется $1 \times (n-1) < n$, то есть выделение $1$ уменьшает произведение. + +Как показано на рисунке ниже, при $n = 6$ имеем $3 \times 3 > 2 \times 2 \times 2$. **Это означает, что выделять $3$ выгоднее, чем выделять $2$**. + +**Жадная стратегия 2**: в схеме разбиения должно быть не более двух множителей $2$. Потому что три двойки всегда можно заменить двумя тройками и получить большее произведение. + +![Оптимальные множители разбиения](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) + +Итак, получаем следующую жадную стратегию. + +1. Для заданного целого $n$ непрерывно выделять из него множитель $3$, пока остаток не станет равным $0$, $1$ или $2$. +2. Если остаток равен $0$, это означает, что $n$ кратно $3$, и больше ничего делать не нужно. +3. Если остаток равен $2$, дальнейшее разбиение не требуется, его нужно сохранить. +4. Если остаток равен $1$, то поскольку $2 \times 2 > 1 \times 3$, последний множитель $3$ следует заменить на $2$. + +### Код реализации + +Как показано на рисунке ниже, нам не нужен цикл, чтобы выполнять разбиение числа. Можно использовать целочисленное деление вниз, чтобы получить число троек $a$, и операцию взятия остатка, чтобы получить остаток $b$. Тогда имеем: + +$$ +n = 3 a + b +$$ + +Обратите внимание, что для граничного случая $n \leq 3$ необходимо выделить множитель $1$, и тогда произведение равно $1 \times (n - 1)$. + +```src +[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} +``` + +![Метод вычисления максимального произведения разбиения](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) + +**Временная сложность зависит от того, как в языке программирования реализовано возведение в степень**. Если взять Python, то обычно используются три распространенные функции для вычисления степени. + +- Оператор `**` и функция `pow()` имеют временную сложность $O(\log⁡ a)$. +- Функция `math.pow()` внутри вызывает функцию `pow()` из библиотеки C, выполняющую возведение в степень с плавающей точкой, и ее временная сложность равна $O(1)$. + +Переменные $a$ и $b$ занимают дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**. + +### Доказательство корректности + +Используем доказательство от противного и рассмотрим только случай $n \geq 4$. + +1. **Все множители $\leq 3$**: предположим, что в оптимальной схеме разбиения существует множитель $x \geq 4$. Тогда его можно дальше разложить в $2(x-2)$ и получить большее или равное произведение. Это противоречит предположению. +2. **Схема разбиения не содержит $1$**: предположим, что в оптимальной схеме присутствует множитель $1$. Тогда его можно объединить с другим множителем и получить большее произведение. Это противоречит предположению. +3. **Схема разбиения содержит не более двух $2$**: предположим, что в оптимальной схеме присутствуют три двойки. Тогда их можно заменить двумя тройками и получить большее произведение. Это противоречит предположению. diff --git a/ru/docs/chapter_greedy/summary.md b/ru/docs/chapter_greedy/summary.md new file mode 100644 index 000000000..2e620d748 --- /dev/null +++ b/ru/docs/chapter_greedy/summary.md @@ -0,0 +1,14 @@ +# Резюме + +### Ключевые моменты + +- Жадный алгоритм обычно используется для решения задач оптимизации. Его принцип состоит в том, чтобы на каждом этапе принятия решения делать локально оптимальный выбор в надежде получить глобально оптимальный ответ. +- Жадный алгоритм итеративно делает один жадный выбор за другим, на каждом шаге превращая задачу в подзадачу меньшего размера, пока задача не будет полностью решена. +- Жадный алгоритм не только прост в реализации, но и часто обладает высокой эффективностью. По сравнению с динамическим программированием его временная сложность обычно ниже. +- В задаче о размене монет для некоторых наборов монет жадный алгоритм способен гарантировать оптимальный ответ, а для других наборов - нет: он может дать очень плохое решение. +- Задачи, подходящие для жадного алгоритма, обладают двумя ключевыми свойствами: свойством жадного выбора и оптимальной подструктурой. Свойство жадного выбора отражает корректность жадной стратегии. +- Для некоторых сложных задач доказать свойство жадного выбора непросто. Относительно легче найти контрпример и опровергнуть его, как это видно на примере задачи о размене монет. +- Решение жадной задачи обычно состоит из трех шагов: анализ задачи, определение жадной стратегии и доказательство корректности. Из них ключевым является выбор жадной стратегии, а доказательство корректности часто оказывается самым трудным. +- В задаче о дробном рюкзаке, в отличие от задачи о рюкзаке 0-1, разрешено брать часть предмета, поэтому ее можно решать жадным алгоритмом. Корректность жадной стратегии доказывается методом от противного. +- Задачу о максимальной вместимости можно решать полным перебором со временной сложностью $O(n^2)$. Разработав жадную стратегию со сдвигом короткой перегородки внутрь на каждом шаге, временную сложность можно оптимизировать до $O(n)$. +- В задаче о максимальном произведении разбиения мы последовательно выводим две жадные стратегии: все целые числа $\geq 4$ следует дальше разбивать, а оптимальным множителем разбиения является $3$. В коде присутствуют операции возведения в степень, поэтому временная сложность зависит от способа их реализации и обычно равна $O(1)$ или $O(\log n)$. diff --git a/ru/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png b/ru/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png new file mode 100644 index 000000000..d50bd7511 Binary files /dev/null and b/ru/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png differ diff --git a/ru/docs/chapter_hashing/hash_algorithm.md b/ru/docs/chapter_hashing/hash_algorithm.md new file mode 100644 index 000000000..82e89dd60 --- /dev/null +++ b/ru/docs/chapter_hashing/hash_algorithm.md @@ -0,0 +1,410 @@ +# Алгоритмы хеширования + +В двух предыдущих разделах мы рассмотрели принципы работы хеш-таблицы и способы обработки хеш-коллизий. Однако и открытая адресация, и метод цепочек **лишь позволяют хеш-таблице корректно работать при возникновении коллизий, но не уменьшают вероятность появления самих коллизий**. + +Если хеш-коллизии происходят слишком часто, производительность хеш-таблицы резко деградирует. Как показано на рисунке ниже, для хеш-таблицы с методом цепочек в идеальном случае пары ключ-значение равномерно распределены по всем бакетам, и это дает наилучшую эффективность поиска; в худшем же случае все пары ключ-значение оказываются в одном бакете, и временная сложность вырождается до $O(n)$ . + +![Лучший и худший случаи хеш-коллизий](hash_algorithm.assets/hash_collision_best_worst_condition.png) + +**Распределение пар ключ-значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем оно берется по модулю длины массива: + +```shell +index = hash(key) % capacity +``` + +Из этой формулы видно: при фиксированной емкости хеш-таблицы `capacity` **выходное значение определяет именно хеш-алгоритм `hash()` **, а значит, именно он определяет распределение пар ключ-значение в хеш-таблице. + +Это означает, что для уменьшения вероятности хеш-коллизий нам следует сосредоточиться на проектировании хеш-алгоритма `hash()` . + +## Цели хеш-алгоритма + +Чтобы получить структуру данных хеш-таблицы, которая будет одновременно "быстрой и надежной", хеш-алгоритм должен обладать следующими свойствами. + +- **Детерминированность**: для одинакового входа хеш-алгоритм всегда должен выдавать одинаковый результат. Только так хеш-таблица остается надежной. +- **Высокая эффективность**: вычисление хеш-значения должно быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практическая ценность хеш-таблицы. +- **Равномерное распределение**: хеш-алгоритм должен стараться распределять пары ключ-значение в хеш-таблице равномерно. Чем равномернее распределение, тем ниже вероятность хеш-коллизий. + +На практике хеш-алгоритмы используются не только для реализации хеш-таблиц, но и во многих других областях. + +- **Хранение паролей**: чтобы защищать пароли пользователей, система обычно хранит не сами пароли в открытом виде, а их хеш-значения. Когда пользователь вводит пароль, система вычисляет хеш-значение введенного пароля и сравнивает его с сохраненным значением. Если они совпадают, пароль считается правильным. +- **Проверка целостности данных**: отправитель может вычислить хеш-значение данных и отправить его вместе с самими данными; получатель затем вычисляет хеш-значение повторно и сравнивает его с полученным. Если они совпадают, данные считаются целостными. + +Для приложений, связанных с криптографией, чтобы не допустить восстановления исходного пароля по хеш-значению и иных форм обратного анализа, хеш-алгоритм должен обладать более строгими свойствами безопасности. + +- **Односторонность**: по хеш-значению нельзя восстановить какую-либо информацию о входных данных. +- **Устойчивость к коллизиям**: должно быть крайне трудно найти два разных входа, имеющих одинаковое хеш-значение. +- **Эффект лавины**: даже небольшое изменение во входных данных должно приводить к заметному и непредсказуемому изменению результата. + +Обрати внимание: **"равномерное распределение" и "устойчивость к коллизиям" - это два независимых понятия** , и выполнение первого не означает автоматического выполнения второго. Например, при случайном распределении входных `key` хеш-функция `key % 100` может выдавать достаточно равномерное распределение. Однако этот хеш-алгоритм слишком прост: все `key` с одинаковыми двумя последними цифрами будут иметь одинаковый результат, а значит, по хеш-значению можно легко подобрать подходящие `key` и, например, взломать пароль. + +## Проектирование хеш-алгоритма + +Разработка хеш-алгоритма - это сложная задача, в которой нужно учитывать множество факторов. Однако для некоторых нетребовательных сценариев мы можем спроектировать и несколько простых хеш-алгоритмов. + +- **Аддитивный хеш**: складываем ASCII-коды всех символов входной строки и используем полученную сумму как хеш-значение. +- **Мультипликативный хеш**: используем "некоррелированность" умножения; на каждом шаге умножаем текущее значение на константу и добавляем ASCII-код очередного символа. +- **XOR-хеш**: последовательно накапливаем элементы входных данных в одном хеш-значении через операцию XOR. +- **Ротационный хеш**: последовательно накапливаем ASCII-коды символов, причем перед каждым накоплением выполняем циклический сдвиг хеш-значения. + +```src +[file]{simple_hash}-[class]{}-[func]{rot_hash} +``` + +Нетрудно заметить, что последний шаг каждого из этих хеш-алгоритмов - взятие по модулю большого простого числа $1000000007$ , чтобы гарантировать, что хеш-значение остается в разумных границах. Стоит задуматься: почему подчеркивается именно взятие по модулю простого числа, и какие недостатки возникают при использовании составного модуля? Это интересный вопрос. + +Сначала дадим вывод: **использование большого простого числа в качестве модуля позволяет в максимальной степени обеспечивать равномерное распределение хеш-значений**. Поскольку простое число не имеет общих делителей с другими числами, это помогает уменьшить периодические закономерности, возникающие из-за операции взятия остатка, и тем самым снизить число хеш-коллизий. + +Рассмотрим пример. Предположим, мы выбрали составное число $9$ в качестве модуля. Оно делится на $3$ , поэтому все `key` , которые делятся на $3$ , будут отображаться только в три хеш-значения: $0$ , $3$ , $6$ . + +$$ +\begin{aligned} +\text{modulus} & = 9 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} +\end{aligned} +$$ + +Если входные `key` как раз удовлетворяют такому распределению в виде арифметической прогрессии, то хеш-значения начнут скучиваться, а это усугубит хеш-коллизии. Теперь предположим, что мы заменили `modulus` на простое число $13$ ; поскольку между `key` и `modulus` нет общих делителей, равномерность распределения хеш-значений заметно улучшится. + +$$ +\begin{aligned} +\text{modulus} & = 13 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} +\end{aligned} +$$ + +Следует отметить: если можно гарантировать, что `key` распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не так важен - оба варианта способны дать равномерное распределение хеш-значений. Но если в распределении `key` присутствует периодичность, то взятие по модулю составного числа гораздо легче приводит к кластеризации. + +Итак, на практике мы обычно выбираем простое число в качестве модуля, причем это простое число желательно брать достаточно большим, чтобы по возможности убрать периодические закономерности и повысить устойчивость хеш-алгоритма. + +## Распространенные хеш-алгоритмы + +Нетрудно заметить, что описанные выше простые хеш-алгоритмы довольно "хрупкие" и далеки от поставленных целей. Например, сложение и XOR подчиняются коммутативному закону, поэтому аддитивный хеш и XOR-хеш не различают строки, состоящие из одних и тех же символов, но в разном порядке. Это может усиливать хеш-коллизии и даже создавать некоторые проблемы безопасности. + +На практике мы обычно используем стандартные хеш-алгоритмы, такие как MD5, SHA-1, SHA-2 и SHA-3. Они могут отображать входные данные произвольной длины в хеш-значения фиксированной длины. + +На протяжении почти ста лет хеш-алгоритмы непрерывно развивались и оптимизировались. Одни исследователи старались повысить их производительность, а другие исследователи и хакеры сосредоточивались на поиске уязвимостей в их безопасности. В таблице ниже приведены распространенные хеш-алгоритмы, которые часто встречаются в реальных приложениях. + +- MD5 и SHA-1 уже многократно были успешно атакованы, поэтому они выведены из большинства сценариев, где требуется безопасность. +- SHA-256 из семейства SHA-2 является одним из самых надежных хеш-алгоритмов; на сегодняшний день не известно успешных практических атак, поэтому он широко используется в самых разных протоколах и системах безопасности. +- SHA-3 по сравнению с SHA-2 требует меньших затрат на реализацию и обеспечивает более высокую вычислительную эффективность, но на данный момент распространен слабее, чем семейство SHA-2. + +

Таблица   Распространенные хеш-алгоритмы

+ +| | MD5 | SHA-1 | SHA-2 | SHA-3 | +| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | +| Год появления | 1992 | 1995 | 2002 | 2008 | +| Длина вывода | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | +| Хеш-коллизии | Частые | Частые | Редкие | Редкие | +| Уровень безопасности | Низкий, успешно атакован | Низкий, успешно атакован | Высокий | Высокий | +| Применение | Устарел, но еще используется для проверки целостности данных | Устарел | Проверка криптовалютных транзакций, цифровые подписи и т. д. | Может использоваться как замена SHA-2 | + +## Хеш-значения структур данных + +Мы знаем, что `key` в хеш-таблице могут быть целыми числами, вещественными числами, строками и другими типами данных. Языки программирования обычно предоставляют встроенные хеш-алгоритмы для этих типов, чтобы вычислять индексы бакетов в хеш-таблице. Возьмем Python: в нем можно вызвать функцию `hash()` , чтобы вычислить хеш-значения для различных типов данных. + +- Хеш-значение целого числа и булева значения совпадает с самим значением. +- Вычисление хеш-значений для вещественных чисел и строк устроено сложнее; интересующиеся читатели могут изучить это самостоятельно. +- Хеш-значение кортежа получается путем хеширования каждого элемента, а затем объединения этих хеш-значений в одно итоговое значение. +- Хеш-значение объекта обычно строится на основе его адреса в памяти. Если переопределить метод хеширования объекта, можно реализовать вычисление хеша по содержимому. + +!!! tip + + Обрати внимание: определения и способы вычисления встроенных хеш-значений в разных языках программирования отличаются. + +=== "Python" + + ```python title="built_in_hash.py" + num = 3 + hash_num = hash(num) + # Хеш-значение целого числа 3 равно 3 + + bol = True + hash_bol = hash(bol) + # Хеш-значение булевого значения True равно 1 + + dec = 3.14159 + hash_dec = hash(dec) + # Хеш-значение числа 3.14159 равно 326484311674566659 + + str = "Hello Algo" + hash_str = hash(str) + # Хеш-значение строки "Hello Algo" равно 4617003410720528961 + + tup = (12836, "Сяо Ха") + hash_tup = hash(tup) + # Хеш-значение кортежа (12836, "Сяо Ха") равно 1029005403108185979 + + obj = ListNode(0) + hash_obj = hash(obj) + # Хеш-значение объекта узла равно 274267521 + ``` + +=== "C++" + + ```cpp title="built_in_hash.cpp" + int num = 3; + size_t hashNum = hash()(num); + // Хеш-значение целого числа 3 равно 3 + + bool bol = true; + size_t hashBol = hash()(bol); + // Хеш-значение булевого значения 1 равно 1 + + double dec = 3.14159; + size_t hashDec = hash()(dec); + // Хеш-значение числа 3.14159 равно 4614256650576692846 + + string str = "Hello Algo"; + size_t hashStr = hash()(str); + // Хеш-значение строки "Hello Algo" равно 15466937326284535026 + + // В C++ встроенный std::hash() предоставляет вычисление хеша только для базовых типов данных + // Для массивов и объектов хеш-значение обычно приходится реализовывать самостоятельно + ``` + +=== "Java" + + ```java title="built_in_hash.java" + int num = 3; + int hashNum = Integer.hashCode(num); + // Хеш-значение целого числа 3 равно 3 + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + // Хеш-значение булевого значения true равно 1231 + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + // Хеш-значение числа 3.14159 равно -1340954729 + + String str = "Hello Algo"; + int hashStr = str.hashCode(); + // Хеш-значение строки "Hello Algo" равно -727081396 + + Object[] arr = { 12836, "Сяо Ха" }; + int hashTup = Arrays.hashCode(arr); + // Хеш-значение массива [12836, Сяо Ха] равно 1151158 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + // Хеш-значение объекта узла utils.ListNode@7dc5e7b4 равно 2110121908 + ``` + +=== "C#" + + ```csharp title="built_in_hash.cs" + int num = 3; + int hashNum = num.GetHashCode(); + // Хеш-значение целого числа 3 равно 3; + + bool bol = true; + int hashBol = bol.GetHashCode(); + // Хеш-значение булевого значения true равно 1; + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + // Хеш-значение числа 3.14159 равно -1340954729; + + string str = "Hello Algo"; + int hashStr = str.GetHashCode(); + // Хеш-значение строки "Hello Algo" равно -586107568; + + object[] arr = [12836, "Сяо Ха"]; + int hashTup = arr.GetHashCode(); + // Хеш-значение массива [12836, Сяо Ха] равно 42931033; + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + // Хеш-значение объекта узла 0 равно 39053774; + ``` + +=== "Go" + + ```go title="built_in_hash.go" + // В Go нет встроенной функции hash code + ``` + +=== "Swift" + + ```swift title="built_in_hash.swift" + let num = 3 + let hashNum = num.hashValue + // Хеш-значение целого числа 3 равно 9047044699613009734 + + let bol = true + let hashBol = bol.hashValue + // Хеш-значение булевого значения true равно -4431640247352757451 + + let dec = 3.14159 + let hashDec = dec.hashValue + // Хеш-значение числа 3.14159 равно -2465384235396674631 + + let str = "Hello Algo" + let hashStr = str.hashValue + // Хеш-значение строки "Hello Algo" равно -7850626797806988787 + + let arr = [AnyHashable(12836), AnyHashable("Сяо Ха")] + let hashTup = arr.hashValue + // Хеш-значение массива [AnyHashable(12836), AnyHashable("Сяо Ха")] равно -2308633508154532996 + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + // Хеш-значение объекта узла utils.ListNode равно -2434780518035996159 + ``` + +=== "JS" + + ```javascript title="built_in_hash.js" + // В JavaScript нет встроенной функции hash code + ``` + +=== "TS" + + ```typescript title="built_in_hash.ts" + // В TypeScript нет встроенной функции hash code + ``` + +=== "Dart" + + ```dart title="built_in_hash.dart" + int num = 3; + int hashNum = num.hashCode; + // Хеш-значение целого числа 3 равно 34803 + + bool bol = true; + int hashBol = bol.hashCode; + // Хеш-значение булевого значения true равно 1231 + + double dec = 3.14159; + int hashDec = dec.hashCode; + // Хеш-значение числа 3.14159 равно 2570631074981783 + + String str = "Hello Algo"; + int hashStr = str.hashCode; + // Хеш-значение строки "Hello Algo" равно 468167534 + + List arr = [12836, "Сяо Ха"]; + int hashArr = arr.hashCode; + // Хеш-значение массива [12836, Сяо Ха] равно 976512528 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + // Хеш-значение объекта Instance of 'ListNode' равно 1033450432 + ``` + +=== "Rust" + + ```rust title="built_in_hash.rs" + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + // Хеш-значение целого числа 3 равно 568126464209439262 + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + // Хеш-значение булевого значения true равно 4952851536318644461 + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + // Хеш-значение числа 3.14159 равно 2566941990314602357 + + let str = "Hello Algo"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + // Хеш-значение строки "Hello Algo" равно 16092673739211250988 + + let arr = (&12836, &"Сяо Ха"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + // Хеш-значение кортежа (12836, "Сяо Ха") равно 1885128010422702749 + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + // Хеш-значение объекта RefCell { value: ListNode { val: 42, next: None } } равно 15387811073369036852 + ``` + +=== "C" + + ```c title="built_in_hash.c" + // В C нет встроенной функции hash code + ``` + +=== "Kotlin" + + ```kotlin title="built_in_hash.kt" + val num = 3 + val hashNum = num.hashCode() + // Хеш-значение целого числа 3 равно 3 + + val bol = true + val hashBol = bol.hashCode() + // Хеш-значение булевого значения true равно 1231 + + val dec = 3.14159 + val hashDec = dec.hashCode() + // Хеш-значение числа 3.14159 равно -1340954729 + + val str = "Hello Algo" + val hashStr = str.hashCode() + // Хеш-значение строки "Hello Algo" равно -727081396 + + val arr = arrayOf(12836, "Сяо Ха") + val hashTup = arr.hashCode() + // Хеш-значение массива [12836, Сяо Ха] равно 189568618 + + val obj = ListNode(0) + val hashObj = obj.hashCode() + // Хеш-значение объекта узла utils.ListNode@1d81eb93 равно 495053715 + ``` + +=== "Ruby" + + ```ruby title="built_in_hash.rb" + num = 3 + hash_num = num.hash + # Хеш-значение целого числа 3 равно -4385856518450339636 + + bol = true + hash_bol = bol.hash + # Хеш-значение булевого значения true равно -1617938112149317027 + + dec = 3.14159 + hash_dec = dec.hash + # Хеш-значение числа 3.14159 равно -1479186995943067893 + + str = "Hello Algo" + hash_str = str.hash + # Хеш-значение строки "Hello Algo" равно -4075943250025831763 + + tup = [12836, 'Сяо Ха'] + hash_tup = tup.hash + # Хеш-значение кортежа (12836, 'Сяо Ха') равно 1999544809202288822 + + obj = ListNode.new(0) + hash_obj = obj.hash + # Хеш-значение объекта # равно 4302940560806366381 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D1%83%D0%B7%D0%B5%D0%BB%D0%BA%D0%BB%D0%B0%D1%81%D1%81%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%86%D0%B5%D0%BB%D0%BE%D0%B3%D0%BE%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%203%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B1%D1%83%D0%BB%D0%B5%D0%B2%D0%B0%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20True%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%203.14159%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20Algo%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%22Hello%20Algo%22%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836%2C%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D0%BE%D1%80%D1%82%D0%B5%D0%B6%D0%B0%20%2812836%2C%20%27%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%27%29%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%D0%A5%D0%B5%D1%88-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%B0%20%D1%83%D0%B7%D0%BB%D0%B0%20%3CListNode%20object%20at%200x1058fd810%3E%20%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +Во многих языках программирования **в качестве `key` хеш-таблицы можно использовать только неизменяемые объекты** . Если, например, использовать список (динамический массив) как `key` , то после изменения содержимого списка изменится и его хеш-значение, из-за чего мы уже не сможем найти прежнее `value` в хеш-таблице. + +Хотя у пользовательских объектов (например, у узла связного списка) поля являются изменяемыми, сам объект все же может быть хешируемым. **Причина в том, что хеш-значение объекта обычно строится на основе адреса в памяти** : даже если содержимое объекта меняется, его адрес памяти остается прежним, а значит, и хеш-значение не меняется. + +Внимательный читатель мог заметить, что при запуске программы в разных консолях выводимые хеш-значения отличаются. **Это связано с тем, что интерпретатор Python при каждом запуске добавляет в хеш-функцию строк случайную соль (salt)**. Такой подход эффективно защищает от атак типа HashDoS и повышает безопасность хеш-алгоритма. diff --git a/ru/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png b/ru/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png new file mode 100644 index 000000000..6d9f8f5b4 Binary files /dev/null and b/ru/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png differ diff --git a/ru/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png b/ru/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png new file mode 100644 index 000000000..c43f6f583 Binary files /dev/null and b/ru/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png differ diff --git a/ru/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png b/ru/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png new file mode 100644 index 000000000..0564af14c Binary files /dev/null and b/ru/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png differ diff --git a/ru/docs/chapter_hashing/hash_collision.md b/ru/docs/chapter_hashing/hash_collision.md new file mode 100644 index 000000000..41abe3daf --- /dev/null +++ b/ru/docs/chapter_hashing/hash_collision.md @@ -0,0 +1,108 @@ +# Хеш-коллизии + +Как уже говорилось в предыдущем разделе, **в обычных условиях входное пространство хеш-функции намного больше выходного пространства** , поэтому теоретически хеш-коллизии неизбежны. Например, если входное пространство состоит из всех целых чисел, а выходное пространство ограничено размером массива, то неизбежно несколько целых чисел будут отображаться в один и тот же индекс бакета. + +Хеш-коллизии приводят к ошибочным результатам поиска и серьезно влияют на пригодность хеш-таблицы к использованию. Чтобы решить эту проблему, можно при каждом конфликте выполнять расширение хеш-таблицы, пока конфликт не исчезнет. Этот метод прост и груб, но слишком неэффективен, потому что расширение хеш-таблицы требует большого объема переноса данных и вычислений хеш-значений. Чтобы повысить эффективность, можно использовать следующие стратегии. + +1. Улучшить структуру данных хеш-таблицы, **чтобы она могла корректно работать даже при возникновении хеш-коллизий**. +2. Выполнять расширение только тогда, когда это действительно необходимо, то есть когда хеш-коллизии становятся достаточно серьезными. + +Основные способы улучшения структуры хеш-таблицы включают "метод цепочек" и "открытую адресацию". + +## Метод цепочек + +В исходной хеш-таблице каждый бакет может хранить только одну пару ключ-значение. Метод цепочек (separate chaining) превращает отдельный элемент в связный список: пары ключ-значение становятся узлами списка, и все конфликтующие пары ключ-значение хранятся в одном и том же списке. На рисунке ниже показан пример хеш-таблицы, реализованной методом цепочек. + +![Хеш-таблица с методом цепочек](hash_collision.assets/hash_table_chaining.png) + +Методы работы с хеш-таблицей, построенной на основе метода цепочек, меняются следующим образом. + +- **Поиск элемента**: передаем `key` , по хеш-функции получаем индекс бакета, после чего обращаемся к голове списка и обходим список, сравнивая `key` , пока не найдем целевую пару ключ-значение. +- **Добавление элемента**: сначала через хеш-функцию получаем голову списка, затем добавляем узел (пару ключ-значение) в этот список. +- **Удаление элемента**: по результату хеш-функции обращаемся к голове списка, затем обходим список, находим целевой узел и удаляем его. + +Метод цепочек имеет следующие ограничения. + +- **Рост потребления памяти**: связный список содержит указатели на узлы, поэтому по сравнению с массивом он требует больше памяти. +- **Снижение эффективности поиска**: для нахождения нужного элемента нужно линейно обходить связный список. + +Ниже приведена простая реализация хеш-таблицы методом цепочек. Следует обратить внимание на два момента. + +- Для упрощения кода вместо связного списка используется список (динамический массив). В этой реализации хеш-таблица (массив) содержит несколько бакетов, и каждый бакет представляет собой список. +- Ниже включен метод расширения хеш-таблицы. Когда коэффициент загрузки превышает $\frac{2}{3}$ , мы расширяем хеш-таблицу до $2$ раз от прежней емкости. + +```src +[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} +``` + +Следует отметить, что когда связный список становится очень длинным, эффективность поиска $O(n)$ оказывается низкой. **В этом случае список можно преобразовать в "AVL-дерево" или "красно-черное дерево"** , чтобы оптимизировать временную сложность поиска до $O(\log n)$ . + +## Открытая адресация + +Открытая адресация (open addressing) не вводит дополнительных структур данных, а обрабатывает хеш-коллизии с помощью "многократного пробирования"; основные варианты пробирования включают линейное пробирование, квадратичное пробирование и повторное хеширование. + +Ниже на примере линейного пробирования рассмотрим механизм работы хеш-таблицы с открытой адресацией. + +### Линейное пробирование + +Линейное пробирование использует линейный поиск с фиксированным шагом. Его методы работы отличаются от обычной хеш-таблицы. + +- **Вставка элемента**: по хеш-функции вычисляется индекс бакета; если бакет уже занят, то от места конфликта выполняется линейный обход вперед (шаг обычно равен $1$ ), пока не будет найден пустой бакет, после чего элемент вставляется туда. +- **Поиск элемента**: если возник конфликт, то с тем же шагом продолжается линейный обход вперед, пока не будет найден целевой элемент и возвращено `value` ; если встречается пустой бакет, это означает, что искомого элемента в хеш-таблице нет, и возвращается `None` . + +На рисунке ниже показано распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование). Для этой хеш-функции все `key` с одинаковыми двумя последними цифрами отображаются в один и тот же бакет. Благодаря линейному пробированию они по очереди сохраняются в этом бакете и в следующих за ним бакетах. + +![Распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование)](hash_collision.assets/hash_table_linear_probing.png) + +Однако **линейное пробирование легко приводит к "кластеризации"**. Иначе говоря, чем длиннее непрерывная занятая область в массиве, тем выше вероятность новых коллизий в этой области, что еще сильнее способствует росту этой группы и в итоге ухудшает эффективность операций добавления, удаления, поиска и обновления. + +Стоит заметить, что **мы не можем напрямую удалять элементы из хеш-таблицы с открытой адресацией**. Причина в том, что удаление создаст внутри массива пустой бакет `None` , а при поиске элемента линейное пробирование остановится на этом пустом бакете и вернет результат, из-за чего элементы ниже этого бакета уже не смогут быть найдены, и программа может ошибочно посчитать, что их не существует, как показано на рисунке ниже. + +![Проблема поиска после удаления элемента в открытой адресации](hash_collision.assets/hash_table_open_addressing_deletion.png) + +Чтобы решить эту проблему, можно использовать механизм ленивого удаления (lazy deletion): он не удаляет элемент из хеш-таблицы напрямую, **а помечает этот бакет специальной константой `TOMBSTONE` **. В этом механизме и `None` , и `TOMBSTONE` означают пустой бакет, и оба могут быть использованы для размещения пары ключ-значение. Но есть важное различие: при линейном пробировании, встретив `TOMBSTONE` , нужно продолжать обход, потому что ниже него все еще могут существовать пары ключ-значение. + +Однако **ленивое удаление может ускорять деградацию производительности хеш-таблицы**. Это связано с тем, что каждая операция удаления создает новую метку удаления; по мере роста числа `TOMBSTONE` время поиска тоже увеличивается, потому что линейное пробирование может быть вынуждено перескакивать через множество `TOMBSTONE` , прежде чем найдет целевой элемент. + +Поэтому имеет смысл при линейном пробировании запоминать индекс первого встреченного `TOMBSTONE` и затем менять найденный целевой элемент местами с этим `TOMBSTONE` . Преимущество такого подхода в том, что при каждом поиске или добавлении элемент будет перемещаться в бакет, расположенный ближе к его идеальной позиции (начальной точке пробирования), а значит, эффективность поиска улучшится. + +Ниже приведена реализация хеш-таблицы с открытой адресацией (линейное пробирование), включающая ленивое удаление. Чтобы пространство хеш-таблицы использовалось более полно, мы рассматриваем ее как "кольцевой массив": когда обход выходит за конец массива, он возвращается к началу и продолжается. + +```src +[file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} +``` + +### Квадратичное пробирование + +Квадратичное пробирование похоже на линейное пробирование и тоже является одной из распространенных стратегий открытой адресации. При возникновении конфликта оно не пропускает фиксированное число шагов, а переходит на расстояние, равное "квадрату числа попыток", то есть на $1, 4, 9, \dots$ шагов. + +Квадратичное пробирование имеет следующие основные преимущества. + +- Квадратичное пробирование пытается смягчить эффект кластеризации линейного пробирования, так как пропускает расстояния, равные квадрату номера попытки. +- Квадратичное пробирование перепрыгивает на более дальние позиции в поисках свободного места, что помогает распределять данные более равномерно. + +Однако квадратичное пробирование не является идеальным. + +- Кластеризация все равно существует: некоторые позиции по-прежнему занимают чаще других. +- Из-за быстрого роста квадрата квадратичное пробирование может не охватить всю хеш-таблицу, а это означает, что даже при наличии пустых бакетов оно может так до них и не добраться. + +### Повторное хеширование + +Как видно из названия, метод повторного хеширования использует для пробирования несколько хеш-функций $f_1(x)$, $f_2(x)$, $f_3(x)$, $\dots$ . + +- **Вставка элемента**: если хеш-функция $f_1(x)$ вызывает конфликт, то пробуем $f_2(x)$ , и так далее, пока не будет найдено пустое место для вставки элемента. +- **Поиск элемента**: поиск идет в том же порядке хеш-функций, пока не будет найден целевой элемент; если встречается пустая позиция или уже были опробованы все хеш-функции, это означает, что элемента в хеш-таблице нет, и возвращается `None` . + +По сравнению с линейным пробированием метод повторного хеширования меньше подвержен кластеризации, но несколько хеш-функций приносят дополнительные вычислительные затраты. + +!!! tip + + Обрати внимание: у хеш-таблиц с открытой адресацией (линейное пробирование, квадратичное пробирование и повторное хеширование) есть общая проблема: в них нельзя напрямую удалять элементы. + +## Выбор в языках программирования + +Разные языки программирования используют разные стратегии реализации хеш-таблиц. Ниже приведено несколько примеров. + +- Python использует открытую адресацию. В словаре `dict` для пробирования применяются псевдослучайные числа. +- Java использует метод цепочек. Начиная с JDK 1.8, когда длина массива внутри `HashMap` достигает 64, а длина списка достигает 8, этот список преобразуется в красно-черное дерево для повышения производительности поиска. +- Go использует метод цепочек. В Go установлено, что каждый бакет может хранить не более 8 пар ключ-значение; при переполнении подключается overflow-bucket, а когда таких bucket становится слишком много, выполняется специальное расширение того же масштаба, чтобы сохранить производительность. diff --git a/ru/docs/chapter_hashing/hash_map.assets/hash_collision.png b/ru/docs/chapter_hashing/hash_map.assets/hash_collision.png new file mode 100644 index 000000000..0091a0abd Binary files /dev/null and b/ru/docs/chapter_hashing/hash_map.assets/hash_collision.png differ diff --git a/ru/docs/chapter_hashing/hash_map.assets/hash_function.png b/ru/docs/chapter_hashing/hash_map.assets/hash_function.png new file mode 100644 index 000000000..93714db6e Binary files /dev/null and b/ru/docs/chapter_hashing/hash_map.assets/hash_function.png differ diff --git a/ru/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png b/ru/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png new file mode 100644 index 000000000..4dc697688 Binary files /dev/null and b/ru/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png differ diff --git a/ru/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png b/ru/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png new file mode 100644 index 000000000..ec1a1f27e Binary files /dev/null and b/ru/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png differ diff --git a/ru/docs/chapter_hashing/hash_map.md b/ru/docs/chapter_hashing/hash_map.md new file mode 100644 index 000000000..6312742b5 --- /dev/null +++ b/ru/docs/chapter_hashing/hash_map.md @@ -0,0 +1,591 @@ +# Хеш-таблица + +Хеш-таблица (hash table), также называемая таблицей рассеяния, обеспечивает эффективный поиск элементов за счет отображения между ключом `key` и значением `value` . Иначе говоря, если передать в хеш-таблицу ключ `key` , то можно за $O(1)$ времени получить соответствующее значение `value` . + +Как показано на рисунке ниже, пусть есть $n$ студентов, и у каждого из них есть два поля данных: "имя" и "номер студенческого билета". Если мы хотим реализовать запрос вида "ввести номер студенческого билета и вернуть соответствующее имя", то для этого можно использовать показанную ниже хеш-таблицу. + +![Абстрактное представление хеш-таблицы](hash_map.assets/hash_table_lookup.png) + +Помимо хеш-таблицы, функции поиска можно реализовать и через массив, и через связный список. Сравнение их эффективности приведено в таблице ниже. + +- **Добавление элемента**: нужно лишь добавить элемент в конец массива (или списка), что занимает $O(1)$ времени. +- **Поиск элемента**: так как массив (или список) неупорядочен, приходится обходить все элементы, что занимает $O(n)$ времени. +- **Удаление элемента**: сначала нужно найти элемент, затем удалить его из массива (или списка), что занимает $O(n)$ времени. + +

Таблица   Сравнение эффективности поиска элементов

+ +| | Массив | Связный список | Хеш-таблица | +| -------- | ------ | -------------- | ----------- | +| Поиск элемента | $O(n)$ | $O(n)$ | $O(1)$ | +| Добавление элемента | $O(1)$ | $O(1)$ | $O(1)$ | +| Удаление элемента | $O(n)$ | $O(n)$ | $O(1)$ | + +Нетрудно заметить, что **операции чтения, добавления, удаления и обновления в хеш-таблице имеют временную сложность $O(1)$** , то есть выполняются очень эффективно. + +## Основные операции с хеш-таблицей + +К базовым операциям хеш-таблицы относятся инициализация, поиск, добавление пар ключ-значение и удаление пар ключ-значение. Пример кода приведен ниже: + +=== "Python" + + ```python title="hash_map.py" + # Инициализация хеш-таблицы + hmap: dict = {} + + # Операция добавления + # Добавить пару ключ-значение (key, value) в хеш-таблицу + hmap[12836] = "Сяо Ха" + hmap[15937] = "Сяо Ло" + hmap[16750] = "Сяо Суань" + hmap[13276] = "Сяо Фа" + hmap[10583] = "Сяо Я" + + # Операция поиска + # Передать в хеш-таблицу ключ key и получить значение value + name: str = hmap[15937] + + # Операция удаления + # Удалить пару ключ-значение (key, value) из хеш-таблицы + hmap.pop(10583) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* Инициализация хеш-таблицы */ + unordered_map map; + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map[12836] = "Сяо Ха"; + map[15937] = "Сяо Ло"; + map[16750] = "Сяо Суань"; + map[13276] = "Сяо Фа"; + map[10583] = "Сяо Я"; + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + string name = map[15937]; + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.erase(10583); + ``` + +=== "Java" + + ```java title="hash_map.java" + /* Инициализация хеш-таблицы */ + Map map = new HashMap<>(); + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map.put(12836, "Сяо Ха"); + map.put(15937, "Сяо Ло"); + map.put(16750, "Сяо Суань"); + map.put(13276, "Сяо Фа"); + map.put(10583, "Сяо Я"); + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + String name = map.get(15937); + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.remove(10583); + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* Инициализация хеш-таблицы */ + Dictionary map = new() { + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + { 12836, "Сяо Ха" }, + { 15937, "Сяо Ло" }, + { 16750, "Сяо Суань" }, + { 13276, "Сяо Фа" }, + { 10583, "Сяо Я" } + }; + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + string name = map[15937]; + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.Remove(10583); + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* Инициализация хеш-таблицы */ + hmap := make(map[int]string) + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + hmap[12836] = "Сяо Ха" + hmap[15937] = "Сяо Ло" + hmap[16750] = "Сяо Суань" + hmap[13276] = "Сяо Фа" + hmap[10583] = "Сяо Я" + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + name := hmap[15937] + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + delete(hmap, 10583) + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* Инициализация хеш-таблицы */ + var map: [Int: String] = [:] + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map[12836] = "Сяо Ха" + map[15937] = "Сяо Ло" + map[16750] = "Сяо Суань" + map[13276] = "Сяо Фа" + map[10583] = "Сяо Я" + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + let name = map[15937]! + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.removeValue(forKey: 10583) + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* Инициализация хеш-таблицы */ + const map = new Map(); + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map.set(12836, 'Сяо Ха'); + map.set(15937, 'Сяо Ло'); + map.set(16750, 'Сяо Суань'); + map.set(13276, 'Сяо Фа'); + map.set(10583, 'Сяо Я'); + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + let name = map.get(15937); + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.delete(10583); + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* Инициализация хеш-таблицы */ + const map = new Map(); + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map.set(12836, 'Сяо Ха'); + map.set(15937, 'Сяо Ло'); + map.set(16750, 'Сяо Суань'); + map.set(13276, 'Сяо Фа'); + map.set(10583, 'Сяо Я'); + console.info('\nПосле добавления хеш-таблица имеет вид\nKey -> Value'); + console.info(map); + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + let name = map.get(15937); + console.info('\nПо номеру 15937 найдено имя ' + name); + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.delete(10583); + console.info('\nПосле удаления 10583 хеш-таблица имеет вид\nKey -> Value'); + console.info(map); + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* Инициализация хеш-таблицы */ + Map map = {}; + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map[12836] = "Сяо Ха"; + map[15937] = "Сяо Ло"; + map[16750] = "Сяо Суань"; + map[13276] = "Сяо Фа"; + map[10583] = "Сяо Я"; + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + String name = map[15937]; + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.remove(10583); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + use std::collections::HashMap; + + /* Инициализация хеш-таблицы */ + let mut map: HashMap = HashMap::new(); + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map.insert(12836, "Сяо Ха".to_string()); + map.insert(15937, "Сяо Ло".to_string()); + map.insert(16750, "Сяо Суань".to_string()); + map.insert(13279, "Сяо Фа".to_string()); + map.insert(10583, "Сяо Я".to_string()); + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + let _name: Option<&String> = map.get(&15937); + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + let _removed_value: Option = map.remove(&10583); + ``` + +=== "C" + + ```c title="hash_map.c" + // В C нет встроенной хеш-таблицы + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + /* Инициализация хеш-таблицы */ + val map = HashMap() + + /* Операция добавления */ + // Добавить пару ключ-значение (key, value) в хеш-таблицу + map[12836] = "Сяо Ха" + map[15937] = "Сяо Ло" + map[16750] = "Сяо Суань" + map[13276] = "Сяо Фа" + map[10583] = "Сяо Я" + + /* Операция поиска */ + // Передать в хеш-таблицу ключ key и получить значение value + val name = map[15937] + + /* Операция удаления */ + // Удалить пару ключ-значение (key, value) из хеш-таблицы + map.remove(10583) + ``` + +=== "Ruby" + + ```ruby title="hash_map.rb" + # Инициализация хеш-таблицы + hmap = {} + + # Операция добавления + # Добавить пару ключ-значение (key, value) в хеш-таблицу + hmap[12836] = "Сяо Ха" + hmap[15937] = "Сяо Ло" + hmap[16750] = "Сяо Суань" + hmap[13276] = "Сяо Фа" + hmap[10583] = "Сяо Я" + + # Операция поиска + # Передать в хеш-таблицу ключ key и получить значение value + name = hmap[15937] + + # Операция удаления + # Удалить пару ключ-значение (key, value) из хеш-таблицы + hmap.delete(10583) + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%D0%A3%D1%82%D0%B5%D0%BD%D0%BE%D0%BA%22%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BB%D1%8E%D1%87%20key%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%B8%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%B7%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +У хеш-таблицы есть три распространенных способа обхода: обход пар ключ-значение, обход ключей и обход значений. Примеры кода приведены ниже: + +=== "Python" + + ```python title="hash_map.py" + # Обход хеш-таблицы + # Обход пар ключ-значение key->value + for key, value in hmap.items(): + print(key, "->", value) + # Обход только ключей key + for key in hmap.keys(): + print(key) + # Обход только значений value + for value in hmap.values(): + print(value) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение key->value + for (auto kv: map) { + cout << kv.first << " -> " << kv.second << endl; + } + // Обход key->value с помощью итератора + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + ``` + +=== "Java" + + ```java title="hash_map.java" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение key->value + for (Map.Entry kv: map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + // Обход только ключей key + for (int key: map.keySet()) { + System.out.println(key); + } + // Обход только значений value + for (String val: map.values()) { + System.out.println(val); + } + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение Key->Value + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + // Обход только ключей key + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + // Обход только значений value + foreach (string val in map.Values) { + Console.WriteLine(val); + } + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение key->value + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // Обход только ключей key + for key := range hmap { + fmt.Println(key) + } + // Обход только значений value + for _, value := range hmap { + fmt.Println(value) + } + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение Key->Value + for (key, value) in map { + print("\(key) -> \(value)") + } + // Обход только ключей Key + for key in map.keys { + print(key) + } + // Обход только значений Value + for value in map.values { + print(value) + } + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* Обход хеш-таблицы */ + console.info('\nОбход пар ключ-значение Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\nОбход только ключей Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\nОбход только значений Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* Обход хеш-таблицы */ + console.info('\nОбход пар ключ-значение Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\nОбход только ключей Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\nОбход только значений Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение Key->Value + map.forEach((key, value) { + print('$key -> $value'); + }); + + // Обход только ключей Key + map.keys.forEach((key) { + print(key); + }); + + // Обход только значений Value + map.values.forEach((value) { + print(value); + }); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение Key->Value + for (key, value) in &map { + println!("{key} -> {value}"); + } + + // Обход только ключей Key + for key in map.keys() { + println!("{key}"); + } + + // Обход только значений Value + for value in map.values() { + println!("{value}"); + } + ``` + +=== "C" + + ```c title="hash_map.c" + // В C нет встроенной хеш-таблицы + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + /* Обход хеш-таблицы */ + // Обход пар ключ-значение key->value + for ((key, value) in map) { + println("$key -> $value") + } + // Обход только ключей key + for (key in map.keys) { + println(key) + } + // Обход только значений value + for (_val in map.values) { + println(_val) + } + ``` + +=== "Ruby" + + ```ruby title="hash_map.rb" + # Обход хеш-таблицы + # Обход пар ключ-значение key->value + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } + + # Обход только ключей key + hmap.keys.each { |key| puts key } + + # Обход только значений value + hmap.values.each { |val| puts val } + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%D0%A3%D1%82%D0%B5%D0%BD%D0%BE%D0%BA%22%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20%23%20%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%D0%BF%D0%B0%D1%80%D0%B0%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20key-%3Evalue%0A%20%20%20%20for%20key%2C%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%2C%20%22-%3E%22%2C%20value%29%0A%20%20%20%20%23%20%D0%BE%D1%82%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%D0%BA%D0%BB%D1%8E%D1%87%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%D0%BE%D1%82%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%9E%D0%B1%D0%BE%D0%B9%D1%82%D0%B8%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Простая реализация хеш-таблицы + +Сначала рассмотрим самый простой случай: **реализуем хеш-таблицу только с помощью одного массива**. В хеш-таблице каждую пустую ячейку массива мы называем бакетом (bucket), и каждый бакет может хранить одну пару ключ-значение. Следовательно, операция поиска сводится к тому, чтобы найти бакет, соответствующий `key` , и получить из него `value` . + +Но как определить бакет, соответствующий заданному `key` ? Это делается с помощью хеш-функции (hash function). Назначение хеш-функции - отображать большое входное пространство в меньшее выходное пространство. В хеш-таблице входным пространством являются все `key` , а выходным - все бакеты (индексы массива). Иначе говоря, передав `key` на вход, **мы можем через хеш-функцию получить позицию хранения соответствующей пары ключ-значение в массиве**. + +Процесс вычисления хеш-функции для одного `key` включает два шага. + +1. Сначала с помощью некоторого хеш-алгоритма `hash()` вычисляется хеш-значение. +2. Затем хеш-значение берется по модулю числа бакетов (длины массива) `capacity` , чтобы получить бакет (индекс массива) `index` , соответствующий этому `key` . + +```shell +index = hash(key) % capacity +``` + +После этого можно использовать `index` для доступа к соответствующему бакету в хеш-таблице и получения `value` . + +Пусть длина массива `capacity = 100` , а хеш-алгоритм `hash(key) = key` . Тогда легко получить хеш-функцию `key % 100` . На рисунке ниже на примере `key` "номер студенческого билета" и `value` "имя" показан принцип работы хеш-функции. + +![Принцип работы хеш-функции](hash_map.assets/hash_function.png) + +Ниже приведен код простой реализации хеш-таблицы. В нем мы инкапсулируем `key` и `value` в класс `Pair` , чтобы представить пару ключ-значение. + +```src +[file]{array_hash_map}-[class]{array_hash_map}-[func]{} +``` + +## Хеш-коллизии и расширение + +По сути, хеш-функция отображает входное пространство, состоящее из всех `key` , в выходное пространство, состоящее из всех индексов массива, а входное пространство обычно значительно больше выходного. Поэтому **теоретически неизбежно существование ситуации "несколько входов соответствуют одному выходу"**. + +Для хеш-функции из приведенного выше примера, если последние две цифры `key` совпадают, то совпадает и результат хеш-функции. Например, если искать студентов с номерами 12836 и 20336, то получим: + +```shell +12836 % 100 = 36 +20336 % 100 = 36 +``` + +Как показано на рисунке ниже, два номера указывают на одно и то же имя, что, очевидно, неверно. Такую ситуацию, когда нескольким входам соответствует один и тот же выход, называют хеш-коллизией (hash collision). + +![Пример хеш-коллизии](hash_map.assets/hash_collision.png) + +Легко понять, что чем больше емкость хеш-таблицы $n$ , тем ниже вероятность того, что несколько `key` попадут в один и тот же бакет, а значит, тем меньше коллизий. Поэтому **мы можем уменьшать число хеш-коллизий путем расширения хеш-таблицы**. + +Как показано на рисунке ниже, до расширения пары ключ-значение `(136, A)` и `(236, D)` конфликтовали, а после расширения коллизия исчезла. + +![Расширение хеш-таблицы](hash_map.assets/hash_table_reshash.png) + +Подобно расширению массива, расширение хеш-таблицы требует перенести все пары ключ-значение из старой таблицы в новую, а это очень затратно по времени; кроме того, поскольку емкость хеш-таблицы `capacity` изменилась, нам приходится с помощью хеш-функции заново вычислять позиции хранения всех пар ключ-значение, что дополнительно увеличивает вычислительные расходы процесса расширения. Поэтому языки программирования обычно заранее резервируют достаточно большую емкость хеш-таблицы, чтобы избежать частых расширений. + +Коэффициент загрузки (load factor) - важное понятие хеш-таблицы. Он определяется как отношение числа элементов в хеш-таблице к числу бакетов и используется для оценки степени серьезности хеш-коллизий, **а также часто служит условием срабатывания расширения хеш-таблицы**. Например, в Java, когда коэффициент загрузки превышает $0.75$ , система расширяет хеш-таблицу до $2$ раз от исходной емкости. diff --git a/ru/docs/chapter_hashing/index.md b/ru/docs/chapter_hashing/index.md new file mode 100644 index 000000000..20a8ad0ac --- /dev/null +++ b/ru/docs/chapter_hashing/index.md @@ -0,0 +1,9 @@ +# Хеш-таблицы + +![Хеш-таблицы](../assets/covers/chapter_hashing.jpg) + +!!! abstract + + В мире компьютеров хеш-таблица похожа на сообразительного библиотекаря. + + Он умеет вычислять шифр хранения и потому быстро находит нужную книгу. diff --git a/ru/docs/chapter_hashing/summary.md b/ru/docs/chapter_hashing/summary.md new file mode 100644 index 000000000..edf1905a5 --- /dev/null +++ b/ru/docs/chapter_hashing/summary.md @@ -0,0 +1,51 @@ +# Краткие итоги + +### Основные моменты + +- Передав `key` , мы можем получить `value` из хеш-таблицы за $O(1)$ времени, поэтому она очень эффективна. +- К типичным операциям хеш-таблицы относятся поиск, добавление пары ключ-значение, удаление пары ключ-значение и обход хеш-таблицы. +- Хеш-функция отображает `key` в индекс массива, после чего можно обратиться к соответствующему бакету и получить `value` . +- Два разных `key` после хеш-функции могут дать один и тот же индекс массива, что приводит к ошибочному результату поиска; это явление называется хеш-коллизией. +- Чем больше емкость хеш-таблицы, тем ниже вероятность хеш-коллизий. Поэтому хеш-коллизии можно смягчать путем расширения хеш-таблицы. Как и у массива, операция расширения у хеш-таблицы очень затратна. +- Коэффициент загрузки определяется как отношение числа элементов в хеш-таблице к числу бакетов, отражает степень серьезности хеш-коллизий и часто используется как условие запуска расширения хеш-таблицы. +- Метод цепочек превращает одиночный элемент в связный список и хранит все конфликтующие элементы в одном списке. Однако слишком длинный список снижает эффективность поиска, поэтому его можно дополнительно преобразовать в красно-черное дерево. +- Открытая адресация обрабатывает хеш-коллизии за счет многократного пробирования. Линейное пробирование использует фиксированный шаг, его недостатки - невозможность прямого удаления элементов и склонность к кластеризации. Повторное хеширование использует несколько хеш-функций и по сравнению с линейным пробированием меньше подвержено кластеризации, но требует больше вычислений. +- Разные языки программирования выбирают разные стратегии реализации хеш-таблиц. Например, `HashMap` в Java использует метод цепочек, а `Dict` в Python - открытую адресацию. +- Для хеш-таблицы желательно, чтобы хеш-алгоритм был детерминированным, быстрым и обеспечивал равномерное распределение. В криптографии от него дополнительно требуют устойчивости к коллизиям и эффекта лавины. +- В качестве модуля хеш-алгоритмы обычно используют большое простое число, чтобы максимально обеспечить равномерность распределения хеш-значений и снизить число хеш-коллизий. +- К распространенным хеш-алгоритмам относятся MD5, SHA-1, SHA-2 и SHA-3. MD5 часто применяли для проверки целостности файлов, а SHA-2 широко используется в протоколах и приложениях, связанных с безопасностью. +- Языки программирования обычно предоставляют для типов данных встроенные хеш-алгоритмы, чтобы вычислять индексы бакетов в хеш-таблице. Как правило, хешируемыми могут быть только неизменяемые объекты. + +### Q & A + +**Q**: В каких случаях временная сложность хеш-таблицы становится $O(n)$ ? + +Когда хеш-коллизии становятся достаточно серьезными, временная сложность хеш-таблицы деградирует до $O(n)$ . Если хеш-функция спроектирована хорошо, емкость выбрана разумно, а конфликты распределены достаточно равномерно, то временная сложность обычно считается $O(1)$ . При использовании встроенной хеш-таблицы языка программирования мы, как правило, и принимаем ее за $O(1)$ . + +**Q**: Почему бы не использовать хеш-функцию $f(x) = x$ ? Тогда ведь коллизий не будет. + +При хеш-функции $f(x) = x$ каждому элементу соответствует уникальный индекс бакета, и такая структура становится эквивалентна массиву. Однако входное пространство обычно намного больше выходного пространства (длины массива), поэтому последним шагом хеш-функции обычно выступает взятие по модулю длины массива. Иначе говоря, цель хеш-таблицы состоит в том, чтобы отобразить большее пространство состояний в меньшее пространство и при этом обеспечить $O(1)$ поиска. + +**Q**: В основе хеш-таблицы лежат массив, связный список и двоичное дерево. Почему же она может быть быстрее них? + +Во-первых, у хеш-таблицы повышается временная эффективность, но снижается пространственная эффективность. Значительная часть ее памяти остается неиспользованной. + +Во-вторых, она быстрее только в определенных сценариях. Если одну и ту же задачу можно реализовать на массиве или связном списке с той же асимптотикой, то часто такая реализация окажется быстрее, чем хеш-таблица. Причина в том, что вычисление хеш-функции само по себе стоит времени, то есть константа в сложности получается выше. + +Наконец, временная сложность хеш-таблицы тоже может деградировать. Например, при методе цепочек мы все равно выполняем поиск в связном списке или красно-черном дереве, поэтому риск деградации до $O(n)$ сохраняется. + +**Q**: Есть ли у повторного хеширования недостаток "нельзя напрямую удалять элементы"? Можно ли повторно использовать место, помеченное как удаленное? + +Повторное хеширование - это разновидность открытой адресации, а у всех методов открытой адресации есть недостаток: элементы нельзя удалять напрямую, поэтому приходится использовать метку удаления. Пространство, помеченное как удаленное, можно использовать повторно. Когда новый элемент вставляется в хеш-таблицу и в процессе пробирования попадает на такую отмеченную позицию, эта позиция может быть занята новым элементом. Такой подход сохраняет последовательность пробирования и одновременно поддерживает приемлемую эффективность использования памяти. + +**Q**: Почему при линейном пробировании во время поиска элемента вообще возникает хеш-коллизия? + +Во время поиска мы через хеш-функцию находим соответствующий бакет и соответствующую пару ключ-значение, но видим, что `key` не совпадает, а это и означает наличие хеш-коллизии. Поэтому метод линейного пробирования в соответствии с заранее заданным шагом последовательно движется дальше, пока не найдет правильную пару ключ-значение или не убедится, что поиск завершился неудачей. + +**Q**: Почему расширение хеш-таблицы помогает смягчать хеш-коллизии? + +Последний шаг хеш-функции обычно состоит во взятии по модулю длины массива $n$ , чтобы результат попадал в диапазон индексов массива; после расширения длина массива $n$ меняется, а значит, может измениться и индекс, соответствующий данному `key` . Несколько `key` , которые раньше попадали в один бакет, после расширения могут распределиться по нескольким бакетам, и тем самым хеш-коллизии будут ослаблены. + +**Q**: Если нам нужен быстрый доступ, почему бы просто не использовать массив? + +Когда `key` данных - это непрерывные целые числа из маленького диапазона, действительно можно напрямую использовать массив: это просто и эффективно. Но если `key` имеют другой тип данных (например, строки), тогда нужен хеш-алгоритм, который отобразит `key` в индекс массива, а хранение элементов будет выполняться через массив бакетов. Такая структура и называется хеш-таблицей. diff --git a/ru/docs/chapter_heap/build_heap.assets/heapify_operations_count.png b/ru/docs/chapter_heap/build_heap.assets/heapify_operations_count.png new file mode 100644 index 000000000..5014f4b26 Binary files /dev/null and b/ru/docs/chapter_heap/build_heap.assets/heapify_operations_count.png differ diff --git a/ru/docs/chapter_heap/build_heap.md b/ru/docs/chapter_heap/build_heap.md new file mode 100644 index 000000000..b1c676e27 --- /dev/null +++ b/ru/docs/chapter_heap/build_heap.md @@ -0,0 +1,74 @@ +# Построение кучи + +В некоторых случаях мы хотим построить кучу, используя сразу все элементы списка. Этот процесс называется "построением кучи". + +## Реализация через операцию добавления в кучу + +Сначала мы создаем пустую кучу, затем обходим список и для каждого элемента по очереди выполняем "операцию добавления в кучу": сначала помещаем элемент в хвост кучи, а затем выполняем для него упорядочивание "снизу вверх". + +Каждый раз, когда элемент добавляется в кучу, ее длина увеличивается на единицу. Поскольку узлы последовательно добавляются в двоичное дерево сверху вниз, куча строится "сверху вниз". + +Пусть число элементов равно $n$ ; так как каждая операция добавления требует $O(\log{n})$ времени, временная сложность такого построения кучи составляет $O(n \log n)$ . + +## Реализация через обход и упорядочивание + +На самом деле можно реализовать и более эффективный способ построения кучи, который состоит из двух шагов. + +1. Без изменений добавить все элементы списка в кучу; в этот момент свойства кучи еще не выполняются. +2. Обойти кучу в обратном порядке, то есть в порядке, обратном обходу по уровням, и по очереди выполнить упорядочивание "сверху вниз" для каждого нелистового узла. + +**После того как некоторый узел был упорядочен, поддерево с этим узлом в качестве корня становится корректной подкучей**. А поскольку обход выполняется в обратном порядке, куча строится "снизу вверх". + +Причина выбора обратного обхода в том, что он гарантирует: поддеревья ниже текущего узла уже являются корректными подкучами, а значит, упорядочивание текущего узла действительно будет эффективным. + +Стоит отметить, что **листовые узлы не имеют дочерних узлов, поэтому они естественным образом являются корректными подкучами и не требуют упорядочивания**. Как показано в коде ниже, последний нелистовой узел является родителем последнего узла, и именно с него мы начинаем обратный обход и упорядочивание: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{__init__} +``` + +## Анализ сложности + +Теперь попробуем оценить временную сложность второго способа построения кучи. + +- Пусть число узлов полного двоичного дерева равно $n$ , тогда число листовых узлов равно $(n + 1) / 2$ , где $/$ означает целочисленное деление вниз. Следовательно, число узлов, которые нужно упорядочивать, равно $(n - 1) / 2$ . +- В процессе упорядочивания сверху вниз каждый узел в худшем случае может просеяться до листа, поэтому максимальное число итераций равно высоте двоичного дерева $\log n$ . + +Перемножив эти два значения, можно получить временную сложность построения кучи $O(n \log n)$ . **Но эта оценка неточна, потому что мы не учли свойство двоичного дерева: на нижних уровнях узлов гораздо больше, чем на верхних**. + +Далее выполним более точный расчет. Чтобы упростить вычисления, предположим, что дано "идеальное двоичное дерево" высоты $h$ с числом узлов $n$ ; это предположение не повлияет на корректность результата. + +![Число узлов на каждом уровне идеального двоичного дерева](build_heap.assets/heapify_operations_count.png) + +Как показано на рисунке выше, максимальное число итераций упорядочивания "сверху вниз" для некоторого узла равно расстоянию от этого узла до листового узла, а это расстояние как раз и есть "высота узла". Поэтому мы можем просуммировать для каждого уровня выражение "число узлов $\times$ высота узла" и **получить суммарное число итераций упорядочивания для всех узлов**. + +$$ +T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 +$$ + +Чтобы упростить это выражение, воспользуемся школьными знаниями о последовательностях и сначала умножим $T(h)$ на $2$ : + +$$ +\begin{aligned} +T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline +2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline +\end{aligned} +$$ + +Используя метод вычитания со сдвигом, вычтем из нижней строки $2 T(h)$ верхнюю строку $T(h)$ , тогда получим: + +$$ +2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h +$$ + +Из этого выражения видно, что $T(h)$ представляет собой геометрическую прогрессию, поэтому можно напрямую применить формулу суммы и получить временную сложность: + +$$ +\begin{aligned} +T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline +& = 2^{h+1} - h - 2 \newline +& = O(2^h) +\end{aligned} +$$ + +Далее, число узлов идеального двоичного дерева высоты $h$ равно $n = 2^{h+1} - 1$ , поэтому несложно получить сложность $O(2^h) = O(n)$ . Из этого вывода следует, что **построение кучи из входного списка имеет временную сложность $O(n)$ , что очень эффективно**. diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step1.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step1.png new file mode 100644 index 000000000..bbcc81f62 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step1.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step10.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step10.png new file mode 100644 index 000000000..0d50b1abe Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step10.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step2.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step2.png new file mode 100644 index 000000000..6a38e66b1 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step2.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step3.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step3.png new file mode 100644 index 000000000..c137305bb Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step3.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step4.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step4.png new file mode 100644 index 000000000..584073779 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step4.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step5.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step5.png new file mode 100644 index 000000000..d7d6fd1bd Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step5.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step6.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step6.png new file mode 100644 index 000000000..9e9f0403c Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step6.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step7.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step7.png new file mode 100644 index 000000000..cb0cc88af Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step7.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step8.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step8.png new file mode 100644 index 000000000..f39eedd99 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step8.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_pop_step9.png b/ru/docs/chapter_heap/heap.assets/heap_pop_step9.png new file mode 100644 index 000000000..a97ca8bba Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_pop_step9.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step1.png b/ru/docs/chapter_heap/heap.assets/heap_push_step1.png new file mode 100644 index 000000000..cd099ba2c Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step1.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step2.png b/ru/docs/chapter_heap/heap.assets/heap_push_step2.png new file mode 100644 index 000000000..8e280470a Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step2.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step3.png b/ru/docs/chapter_heap/heap.assets/heap_push_step3.png new file mode 100644 index 000000000..6012761c9 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step3.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step4.png b/ru/docs/chapter_heap/heap.assets/heap_push_step4.png new file mode 100644 index 000000000..0c8ad9851 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step4.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step5.png b/ru/docs/chapter_heap/heap.assets/heap_push_step5.png new file mode 100644 index 000000000..95ea01b8f Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step5.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step6.png b/ru/docs/chapter_heap/heap.assets/heap_push_step6.png new file mode 100644 index 000000000..ad7a6e4f3 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step6.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step7.png b/ru/docs/chapter_heap/heap.assets/heap_push_step7.png new file mode 100644 index 000000000..5a34e814c Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step7.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step8.png b/ru/docs/chapter_heap/heap.assets/heap_push_step8.png new file mode 100644 index 000000000..c69cee698 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step8.png differ diff --git a/ru/docs/chapter_heap/heap.assets/heap_push_step9.png b/ru/docs/chapter_heap/heap.assets/heap_push_step9.png new file mode 100644 index 000000000..fa6274e55 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/heap_push_step9.png differ diff --git a/ru/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png b/ru/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png new file mode 100644 index 000000000..5453eb58d Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png differ diff --git a/ru/docs/chapter_heap/heap.assets/representation_of_heap.png b/ru/docs/chapter_heap/heap.assets/representation_of_heap.png new file mode 100644 index 000000000..13171f798 Binary files /dev/null and b/ru/docs/chapter_heap/heap.assets/representation_of_heap.png differ diff --git a/ru/docs/chapter_heap/heap.md b/ru/docs/chapter_heap/heap.md new file mode 100644 index 000000000..fe28867f8 --- /dev/null +++ b/ru/docs/chapter_heap/heap.md @@ -0,0 +1,532 @@ +# Куча + +Куча (heap) - это полное двоичное дерево, удовлетворяющее определенным условиям. Основных типов кучи два, как показано на рисунке ниже. + +- Минимальная куча (min heap): значение любого узла $\leq$ значения его дочерних узлов. +- Максимальная куча (max heap): значение любого узла $\geq$ значения его дочерних узлов. + +![Минимальная куча и максимальная куча](heap.assets/min_heap_and_max_heap.png) + +Куча, являясь частным случаем полного двоичного дерева, обладает следующими свойствами. + +- Узлы самого нижнего уровня заполняются слева, а все остальные уровни заполнены полностью. +- Корневой узел двоичного дерева мы называем "вершиной кучи", а самый правый узел нижнего уровня - "основанием кучи". +- Для максимальной (минимальной) кучи значение элемента на вершине, то есть у корневого узла, является максимальным (минимальным). + +## Распространенные операции с кучей + +Нужно отметить, что многие языки программирования предоставляют не саму кучу, а очередь с приоритетом (priority queue) - абстрактную структуру данных, определяемую как очередь, в которой элементы извлекаются в соответствии с приоритетом. + +На практике **куча обычно используется для реализации очереди с приоритетом, а максимальная куча эквивалентна очереди с приоритетом, в которой элементы извлекаются по убыванию**. С точки зрения использования "очередь с приоритетом" и "куча" можно считать эквивалентными структурами данных. Поэтому в этой книге мы не будем специально различать их и в дальнейшем будем единообразно называть "кучей". + +Распространенные операции с кучей приведены в таблице ниже. Конкретные имена методов зависят от языка программирования. + +

Таблица   Эффективность операций с кучей

+ +| Имя метода | Описание | Временная сложность | +| ----------- | ------------------------------------------------ | ------------------- | +| `push()` | Поместить элемент в кучу | $O(\log n)$ | +| `pop()` | Извлечь элемент с вершины кучи | $O(\log n)$ | +| `peek()` | Получить доступ к вершине кучи (для max / min кучи это соответственно максимум / минимум) | $O(1)$ | +| `size()` | Получить число элементов в куче | $O(1)$ | +| `isEmpty()` | Проверить, пуста ли куча | $O(1)$ | + +В реальных приложениях мы можем напрямую использовать классы кучи, предоставляемые языком программирования, или классы очереди с приоритетом. + +Подобно сортировкам "по возрастанию" и "по убыванию", мы можем переключаться между "минимальной кучей" и "максимальной кучей", изменяя `flag` или модифицируя `Comparator` . Код приведен ниже: + +=== "Python" + + ```python title="heap.py" + # Инициализация минимальной кучи + min_heap, flag = [], 1 + # Инициализация максимальной кучи + max_heap, flag = [], -1 + + # Модуль heapq в Python по умолчанию реализует минимальную кучу + # Если инвертировать знак элемента перед добавлением, то отношение порядка перевернется и так реализуется максимальная куча + # В этом примере flag = 1 соответствует минимальной куче, а flag = -1 - максимальной + + # Добавление элементов в кучу + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + # Получение элемента на вершине кучи + peek: int = flag * max_heap[0] # 5 + + # Извлечение элемента с вершины кучи + # Извлеченные элементы образуют последовательность по убыванию + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + # Получение размера кучи + size: int = len(max_heap) + + # Проверка, пуста ли куча + is_empty: bool = not max_heap + + # Построение кучи из входного списка + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + ``` + +=== "C++" + + ```cpp title="heap.cpp" + /* Инициализация кучи */ + // Инициализация минимальной кучи + priority_queue, greater> minHeap; + // Инициализация максимальной кучи + priority_queue, less> maxHeap; + + /* Добавление элементов в кучу */ + maxHeap.push(1); + maxHeap.push(3); + maxHeap.push(2); + maxHeap.push(5); + maxHeap.push(4); + + /* Получение элемента на вершине кучи */ + int peek = maxHeap.top(); // 5 + + /* Извлечение элемента с вершины кучи */ + // Извлеченные элементы образуют последовательность по убыванию + maxHeap.pop(); // 5 + maxHeap.pop(); // 4 + maxHeap.pop(); // 3 + maxHeap.pop(); // 2 + maxHeap.pop(); // 1 + + /* Получение размера кучи */ + int size = maxHeap.size(); + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.empty(); + + /* Построение кучи из входного списка */ + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + ``` + +=== "Java" + + ```java title="heap.java" + /* Инициализация кучи */ + // Инициализация минимальной кучи + Queue minHeap = new PriorityQueue<>(); + // Инициализация максимальной кучи (достаточно изменить Comparator через lambda) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /* Добавление элементов в кучу */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); + + /* Получение элемента на вершине кучи */ + int peek = maxHeap.peek(); // 5 + + /* Извлечение элемента с вершины кучи */ + // Извлеченные элементы образуют последовательность по убыванию + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 + + /* Получение размера кучи */ + int size = maxHeap.size(); + + /* Проверка, пуста ли куча */ + boolean isEmpty = maxHeap.isEmpty(); + + /* Построение кучи из входного списка */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ``` + +=== "C#" + + ```csharp title="heap.cs" + /* Инициализация кучи */ + // Инициализация минимальной кучи + PriorityQueue minHeap = new(); + // Инициализация максимальной кучи (достаточно изменить Comparer через lambda) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); + + /* Добавление элементов в кучу */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); + + /* Получение элемента на вершине кучи */ + int peek = maxHeap.Peek();//5 + + /* Извлечение элемента с вершины кучи */ + // Извлеченные элементы образуют последовательность по убыванию + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 + + /* Получение размера кучи */ + int size = maxHeap.Count; + + /* Проверка, пуста ли куча */ + bool isEmpty = maxHeap.Count == 0; + + /* Построение кучи из входного списка */ + minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); + ``` + +=== "Go" + + ```go title="heap.go" + // В Go можно построить целочисленную максимальную кучу, реализовав heap.Interface + // Для реализации heap.Interface также нужно реализовать sort.Interface + type intHeap []any + + // Метод Push из heap.Interface, реализует добавление элемента в кучу + func (h *intHeap) Push(x any) { + // Push и Pop используют pointer receiver + // Потому что они не только изменяют содержимое среза, но и его длину + *h = append(*h, x.(int)) + } + + // Метод Pop из heap.Interface, реализует извлечение элемента с вершины кучи + func (h *intHeap) Pop() any { + // Извлекаемый элемент хранится в конце + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last + } + + // Метод Len из sort.Interface + func (h *intHeap) Len() int { + return len(*h) + } + + // Метод Less из sort.Interface + func (h *intHeap) Less(i, j int) bool { + // Для минимальной кучи здесь нужно заменить сравнение на < + return (*h)[i].(int) > (*h)[j].(int) + } + + // Метод Swap из sort.Interface + func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + } + + // Top получает элемент на вершине кучи + func (h *intHeap) Top() any { + return (*h)[0] + } + + /* Driver Code */ + func TestHeap(t *testing.T) { + /* Инициализация кучи */ + // Инициализация максимальной кучи + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* Добавление элементов в кучу */ + // Вызываем методы heap.Interface для добавления элементов + heap.Push(maxHeap, 1) + heap.Push(maxHeap, 3) + heap.Push(maxHeap, 2) + heap.Push(maxHeap, 4) + heap.Push(maxHeap, 5) + + /* Получение элемента на вершине кучи */ + top := maxHeap.Top() + fmt.Printf("Элемент на вершине кучи: %d\n", top) + + /* Извлечение элемента с вершины кучи */ + // Вызываем методы heap.Interface для удаления элементов + heap.Pop(maxHeap) // 5 + heap.Pop(maxHeap) // 4 + heap.Pop(maxHeap) // 3 + heap.Pop(maxHeap) // 2 + heap.Pop(maxHeap) // 1 + + /* Получение размера кучи */ + size := len(*maxHeap) + fmt.Printf("Число элементов в куче: %d\n", size) + + /* Проверка, пуста ли куча */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("Пуста ли куча: %t\n", isEmpty) + } + ``` + +=== "Swift" + + ```swift title="heap.swift" + /* Инициализация кучи */ + // Тип Heap в Swift поддерживает и max-heap, и min-heap, но требует swift-collections + var heap = Heap() + + /* Добавление элементов в кучу */ + heap.insert(1) + heap.insert(3) + heap.insert(2) + heap.insert(5) + heap.insert(4) + + /* Получение элемента на вершине кучи */ + var peek = heap.max()! + + /* Извлечение элемента с вершины кучи */ + peek = heap.removeMax() // 5 + peek = heap.removeMax() // 4 + peek = heap.removeMax() // 3 + peek = heap.removeMax() // 2 + peek = heap.removeMax() // 1 + + /* Получение размера кучи */ + let size = heap.count + + /* Проверка, пуста ли куча */ + let isEmpty = heap.isEmpty + + /* Построение кучи из входного списка */ + let heap2 = Heap([1, 3, 2, 5, 4]) + ``` + +=== "JS" + + ```javascript title="heap.js" + // В JavaScript нет встроенного класса Heap + ``` + +=== "TS" + + ```typescript title="heap.ts" + // В TypeScript нет встроенного класса Heap + ``` + +=== "Dart" + + ```dart title="heap.dart" + // В Dart нет встроенного класса Heap + ``` + +=== "Rust" + + ```rust title="heap.rs" + use std::collections::BinaryHeap; + use std::cmp::Reverse; + + /* Инициализация кучи */ + // Инициализация минимальной кучи + let mut min_heap = BinaryHeap::>::new(); + // Инициализация максимальной кучи + let mut max_heap = BinaryHeap::new(); + + /* Добавление элементов в кучу */ + max_heap.push(1); + max_heap.push(3); + max_heap.push(2); + max_heap.push(5); + max_heap.push(4); + + /* Получение элемента на вершине кучи */ + let peek = max_heap.peek().unwrap(); // 5 + + /* Извлечение элемента с вершины кучи */ + // Извлеченные элементы образуют последовательность по убыванию + let peek = max_heap.pop().unwrap(); // 5 + let peek = max_heap.pop().unwrap(); // 4 + let peek = max_heap.pop().unwrap(); // 3 + let peek = max_heap.pop().unwrap(); // 2 + let peek = max_heap.pop().unwrap(); // 1 + + /* Получение размера кучи */ + let size = max_heap.len(); + + /* Проверка, пуста ли куча */ + let is_empty = max_heap.is_empty(); + + /* Построение кучи из входного списка */ + let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); + ``` + +=== "C" + + ```c title="heap.c" + // В C нет встроенного класса Heap + ``` + +=== "Kotlin" + + ```kotlin title="heap.kt" + /* Инициализация кучи */ + // Инициализация минимальной кучи + var minHeap = PriorityQueue() + // Инициализация максимальной кучи (достаточно изменить Comparator через lambda) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + /* Добавление элементов в кучу */ + maxHeap.offer(1) + maxHeap.offer(3) + maxHeap.offer(2) + maxHeap.offer(5) + maxHeap.offer(4) + + /* Получение элемента на вершине кучи */ + var peek = maxHeap.peek() // 5 + + /* Извлечение элемента с вершины кучи */ + // Извлеченные элементы образуют последовательность по убыванию + peek = maxHeap.poll() // 5 + peek = maxHeap.poll() // 4 + peek = maxHeap.poll() // 3 + peek = maxHeap.poll() // 2 + peek = maxHeap.poll() // 1 + + /* Получение размера кучи */ + val size = maxHeap.size + + /* Проверка, пуста ли куча */ + val isEmpty = maxHeap.isEmpty() + + /* Построение кучи из входного списка */ + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + ``` + +=== "Ruby" + + ```ruby title="heap.rb" + # В Ruby нет встроенного класса Heap + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20min-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20min_heap%2C%20flag%20%3D%20%5B%5D%2C%201%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20max-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20max_heap%2C%20flag%20%3D%20%5B%5D%2C%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%20heapq%20%D0%B2%20Python%20%D0%BF%D0%BE%20%D1%83%D0%BC%D0%BE%D0%BB%D1%87%D0%B0%D0%BD%D0%B8%D1%8E%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D1%83%D0%B5%D1%82%20min-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%20%D0%BF%D0%BE%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%20%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D1%82%D1%80%D0%B8%D1%86%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%2C%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%B8%D1%82%D1%8C%20%D0%BE%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B0%20%D0%B8%20%D1%82%D0%B5%D0%BC%20%D1%81%D0%B0%D0%BC%D1%8B%D0%BC%20%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20max-%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20%23%20%D0%92%20%D1%8D%D1%82%D0%BE%D0%BC%20%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D0%B5%20flag%20%3D%201%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20min-%D0%BA%D1%83%D1%87%D0%B5%2C%20%D0%B0%20flag%20%3D%20-1%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D0%B5%D1%82%20max-%D0%BA%D1%83%D1%87%D0%B5%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BA%D1%83%D1%87%D1%83%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%201%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%203%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%202%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%205%29%0A%20%20%20%20heapq.heappush%28max_heap%2C%20flag%20%2A%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20peek%20%3D%20flag%20%2A%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%B8%D0%B7%20%D0%BA%D1%83%D1%87%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D1%83%D1%8E%D1%82%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%BE%D1%82%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B3%D0%BE%20%D0%BA%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%D0%BC%D1%83%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20%2A%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BA%D1%83%D1%87%D0%B0%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%92%D1%85%D0%BE%D0%B4%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%D0%B8%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BA%D1%83%D1%87%D0%B8%0A%20%20%20%20min_heap%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Реализация кучи + +Ниже реализуется максимальная куча. Чтобы преобразовать ее в минимальную кучу, достаточно инвертировать всю логику сравнений по величине, например заменить $\geq$ на $\leq$ . Заинтересованные читатели могут попробовать реализовать это самостоятельно. + +### Хранение и представление кучи + +В разделе "Двоичные деревья" мы уже говорили, что полное двоичное дерево очень удобно представлять массивом. Поскольку куча как раз и является полным двоичным деревом, **для хранения кучи мы также будем использовать массив**. + +Когда двоичное дерево представляется массивом, элементы массива соответствуют значениям узлов, а индексы - положениям этих узлов в двоичном дереве. **Указатели на узлы реализуются через формулы отображения индексов**. + +Как показано на рисунке ниже, для заданного индекса $i$ индекс левого дочернего узла равен $2i + 1$ , правого дочернего узла - $2i + 2$ , а родительского узла - $(i - 1) / 2$ с округлением вниз. Если индекс выходит за допустимые границы, это означает пустой узел или отсутствие узла. + +![Представление и хранение кучи](heap.assets/representation_of_heap.png) + +Мы можем инкапсулировать формулы отображения индексов в функции, чтобы потом было удобнее ими пользоваться: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{parent} +``` + +### Доступ к элементу на вершине кучи + +Элемент на вершине кучи - это корневой узел двоичного дерева, то есть первый элемент списка: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{peek} +``` + +### Добавление элемента в кучу + +Пусть дан элемент `val` . Сначала мы помещаем его в основание кучи. После добавления свойства кучи могут нарушиться, потому что `val` может оказаться больше, чем другие элементы в куче. **Поэтому необходимо восстановить порядок на пути от вставленного узла к корню** ; эта операция называется heapify, то есть упорядочиванием кучи. + +Рассмотрим ситуацию, когда упорядочивание выполняется **снизу вверх**, начиная от только что вставленного узла. Как показано на рисунках ниже, мы сравниваем значение вставленного узла со значением его родителя; если вставленный узел больше, то меняем их местами. Затем продолжаем выполнять ту же операцию и последовательно восстанавливать корректность всех узлов по пути снизу вверх, пока не выйдем за корень или не встретим узел, для которого обмен не требуется. + +=== "<1>" + ![Шаги добавления элемента в кучу](heap.assets/heap_push_step1.png) + +=== "<2>" + ![heap_push_step2](heap.assets/heap_push_step2.png) + +=== "<3>" + ![heap_push_step3](heap.assets/heap_push_step3.png) + +=== "<4>" + ![heap_push_step4](heap.assets/heap_push_step4.png) + +=== "<5>" + ![heap_push_step5](heap.assets/heap_push_step5.png) + +=== "<6>" + ![heap_push_step6](heap.assets/heap_push_step6.png) + +=== "<7>" + ![heap_push_step7](heap.assets/heap_push_step7.png) + +=== "<8>" + ![heap_push_step8](heap.assets/heap_push_step8.png) + +=== "<9>" + ![heap_push_step9](heap.assets/heap_push_step9.png) + +Пусть общее число узлов равно $n$ , тогда высота дерева равна $O(\log n)$ . Следовательно, максимальное число итераций операции heapify тоже не превышает $O(\log n)$ . Отсюда **временная сложность добавления элемента в кучу равна $O(\log n)$** . Код приведен ниже: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_up} +``` + +### Извлечение элемента с вершины кучи + +Элемент на вершине кучи - это корневой узел двоичного дерева, то есть первый элемент списка. Если просто удалить первый элемент списка, то индексы всех узлов двоичного дерева изменятся, и это сильно затруднит последующее восстановление структуры при помощи heapify. Чтобы по возможности минимизировать изменение индексов элементов, мы используем следующий порядок действий. + +1. Поменять местами элемент на вершине кучи и элемент у основания кучи, то есть поменять корневой узел с самым правым листовым узлом. +2. После обмена удалить основание кучи из списка. Обрати внимание: поскольку обмен уже выполнен, фактически удаляется исходный элемент вершины кучи. +3. Начиная от корневого узла, **выполнить heapify сверху вниз**. + +Как показано на рисунках ниже, **направление операции "heapify сверху вниз" противоположно операции "heapify снизу вверх"**. Мы сравниваем значение корневого узла со значениями двух дочерних узлов, выбираем больший дочерний узел и меняем его местами с корневым узлом. Затем циклически повторяем ту же операцию, пока не выйдем за листовой узел или не встретим узел, который уже не требует обмена. + +=== "<1>" + ![Шаги извлечения элемента с вершины кучи](heap.assets/heap_pop_step1.png) + +=== "<2>" + ![heap_pop_step2](heap.assets/heap_pop_step2.png) + +=== "<3>" + ![heap_pop_step3](heap.assets/heap_pop_step3.png) + +=== "<4>" + ![heap_pop_step4](heap.assets/heap_pop_step4.png) + +=== "<5>" + ![heap_pop_step5](heap.assets/heap_pop_step5.png) + +=== "<6>" + ![heap_pop_step6](heap.assets/heap_pop_step6.png) + +=== "<7>" + ![heap_pop_step7](heap.assets/heap_pop_step7.png) + +=== "<8>" + ![heap_pop_step8](heap.assets/heap_pop_step8.png) + +=== "<9>" + ![heap_pop_step9](heap.assets/heap_pop_step9.png) + +=== "<10>" + ![heap_pop_step10](heap.assets/heap_pop_step10.png) + +Как и операция добавления в кучу, операция извлечения элемента с вершины кучи также имеет временную сложность $O(\log n)$ . Код приведен ниже: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_down} +``` + +## Типичные применения кучи + +- **Очередь с приоритетом**: куча обычно является предпочтительной структурой данных для реализации очереди с приоритетом; добавление и извлечение элементов имеют временную сложность $O(\log n)$ , а построение кучи - $O(n)$ , и все эти операции выполняются очень эффективно. +- **Пирамидальная сортировка**: для заданного набора данных можно построить кучу, а затем непрерывно извлекать из нее элементы, получая отсортированные данные. Однако на практике мы обычно используем более изящную реализацию пирамидальной сортировки; подробности см. в разделе "Пирамидальная сортировка". +- **Получение наибольших $k$ элементов**: это классическая алгоритмическая задача и одновременно типичное применение кучи. Например, выбор 10 самых горячих новостей для списка популярных тем или выбор 10 самых продаваемых товаров. diff --git a/ru/docs/chapter_heap/index.md b/ru/docs/chapter_heap/index.md new file mode 100644 index 000000000..7bd9f1699 --- /dev/null +++ b/ru/docs/chapter_heap/index.md @@ -0,0 +1,9 @@ +# Куча + +![Куча](../assets/covers/chapter_heap.jpg) + +!!! abstract + + Куча похожа на горные вершины: ярусные, волнистые и самые разные по форме. + + Каждая вершина имеет свою высоту, но самая высокая всегда бросается в глаза первой. diff --git a/ru/docs/chapter_heap/summary.md b/ru/docs/chapter_heap/summary.md new file mode 100644 index 000000000..0e4dd33f3 --- /dev/null +++ b/ru/docs/chapter_heap/summary.md @@ -0,0 +1,17 @@ +# Краткие итоги + +### Основные моменты + +- Куча представляет собой полное двоичное дерево и делится на максимальную кучу и минимальную кучу. Элемент на вершине максимальной (минимальной) кучи является наибольшим (наименьшим). +- Очередь с приоритетом определяется как очередь, элементы которой извлекаются в соответствии с приоритетом; обычно ее реализуют с помощью кучи. +- К основным операциям кучи и их временным сложностям относятся: добавление элемента в кучу $O(\log n)$ , извлечение элемента с вершины кучи $O(\log n)$ и доступ к вершине кучи $O(1)$ . +- Полное двоичное дерево очень удобно представлять массивом, поэтому кучу обычно тоже хранят в массиве. +- Операция упорядочивания кучи используется для поддержания свойств кучи и применяется как при добавлении элемента, так и при извлечении элемента. +- Временную сложность построения кучи из $n$ элементов можно оптимизировать до $O(n)$ , что очень эффективно. +- Top-k - это классическая алгоритмическая задача, которую можно эффективно решать с помощью кучи за $O(n \log k)$ . + +### Q & A + +**Q**: Является ли "куча" как структура данных тем же самым понятием, что и "куча" в управлении памятью? + +Это не одно и то же, просто у них случайно совпало название. Куча в памяти компьютерной системы является частью динамического распределения памяти: во время выполнения программы она используется для хранения данных. Программа может запросить определенный объем памяти в куче для хранения сложных структур, таких как объекты и массивы. Когда эти данные больше не нужны, память нужно освободить, чтобы не допустить утечек. По сравнению со стековой памятью управление памятью в куче требует большей осторожности, а неправильное использование может привести к утечкам памяти, висячим указателям и другим проблемам. diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step1.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step1.png new file mode 100644 index 000000000..4c65da3e6 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step1.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step2.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step2.png new file mode 100644 index 000000000..3e48b9643 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step2.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step3.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step3.png new file mode 100644 index 000000000..a8ad57592 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step3.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step4.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step4.png new file mode 100644 index 000000000..2539c08ee Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step4.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step5.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step5.png new file mode 100644 index 000000000..0de604e27 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step5.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step6.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step6.png new file mode 100644 index 000000000..7854af17d Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step6.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step7.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step7.png new file mode 100644 index 000000000..edab0f1a0 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step7.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step8.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step8.png new file mode 100644 index 000000000..0b1132f14 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step8.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_heap_step9.png b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step9.png new file mode 100644 index 000000000..f20ce9889 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_heap_step9.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_sorting.png b/ru/docs/chapter_heap/top_k.assets/top_k_sorting.png new file mode 100644 index 000000000..47e10cbf7 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_sorting.png differ diff --git a/ru/docs/chapter_heap/top_k.assets/top_k_traversal.png b/ru/docs/chapter_heap/top_k.assets/top_k_traversal.png new file mode 100644 index 000000000..5554654f3 Binary files /dev/null and b/ru/docs/chapter_heap/top_k.assets/top_k_traversal.png differ diff --git a/ru/docs/chapter_heap/top_k.md b/ru/docs/chapter_heap/top_k.md new file mode 100644 index 000000000..33e4fc19d --- /dev/null +++ b/ru/docs/chapter_heap/top_k.md @@ -0,0 +1,73 @@ +# Задача Top-k + +!!! question + + Дан неупорядоченный массив `nums` длины $n$ . Требуется вернуть наибольшие $k$ элементов массива. + +Для этой задачи мы сначала покажем два относительно прямолинейных способа решения, а затем более эффективный способ на основе кучи. + +## Метод 1: выбор через обход + +Как показано на рисунке ниже, можно выполнить $k$ проходов по массиву и на каждом проходе извлекать соответственно $1$-й, $2$-й, $\dots$ , $k$-й по величине элемент; временная сложность такого подхода равна $O(nk)$ . + +Этот метод подходит только для случая $k \ll n$ , потому что когда $k$ приближается к $n$ , его временная сложность стремится к $O(n^2)$ , а это уже очень затратно. + +![Поиск наибольших k элементов через обход](top_k.assets/top_k_traversal.png) + +!!! tip + + Когда $k = n$ , мы получаем полную упорядоченную последовательность, и в этот момент задача становится эквивалентной алгоритму "сортировка выбором". + +## Метод 2: сортировка + +Как показано на рисунке ниже, можно сначала отсортировать массив `nums` , а затем вернуть его крайние правые $k$ элементов; временная сложность такого метода равна $O(n \log n)$ . + +Очевидно, что этот способ "делает слишком много", потому что нам нужно только найти наибольшие $k$ элементов, а сортировать остальные элементы совсем не обязательно. + +![Поиск наибольших k элементов через сортировку](top_k.assets/top_k_sorting.png) + +## Метод 3: куча + +Задачу Top-k можно решить гораздо эффективнее с помощью кучи, как показано на рисунках ниже. + +1. Инициализировать минимальную кучу, у которой вершина содержит наименьший элемент. +2. Сначала по очереди поместить в кучу первые $k$ элементов массива. +3. Начиная с элемента номер $k + 1$ , если текущий элемент больше элемента на вершине кучи, то извлечь вершину кучи и поместить в кучу текущий элемент. +4. После завершения обхода в куче будут храниться как раз наибольшие $k$ элементов. + +=== "<1>" + ![Поиск наибольших k элементов с помощью кучи](top_k.assets/top_k_heap_step1.png) + +=== "<2>" + ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) + +=== "<3>" + ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) + +=== "<4>" + ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) + +=== "<5>" + ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) + +=== "<6>" + ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) + +=== "<7>" + ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) + +=== "<8>" + ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) + +=== "<9>" + ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) + +Пример кода приведен ниже: + +```src +[file]{top_k}-[class]{}-[func]{top_k_heap} +``` + +Всего выполняется $n$ операций добавления и извлечения из кучи, а максимальная длина кучи равна $k$ , поэтому временная сложность равна $O(n \log k)$ . Этот метод очень эффективен: когда $k$ мало, временная сложность стремится к $O(n)$ ; когда $k$ велико, она все равно не превышает $O(n \log n)$ . + +Кроме того, этот метод подходит и для сценариев с динамическим потоком данных. При непрерывном поступлении новых данных мы можем продолжать поддерживать содержимое кучи, тем самым динамически обновляя наибольшие $k$ элементов. diff --git a/ru/docs/chapter_hello_algo/index.md b/ru/docs/chapter_hello_algo/index.md new file mode 100644 index 000000000..a2d34022d --- /dev/null +++ b/ru/docs/chapter_hello_algo/index.md @@ -0,0 +1,30 @@ +--- +comments: true +icon: material/rocket-launch-outline +--- + +# Перед началом + +Несколько лет назад я публиковал на LeetCode разборы серии задач "Sword for Offer" и получил поддержку и ободрение от многих читателей. Во время общения с ними чаще всего мне задавали один и тот же вопрос: "как начать изучать алгоритмы". Постепенно этот вопрос начал меня по-настоящему занимать. + +Слепо бросаться в решение задач кажется самым популярным способом: он прост, прямолинеен и действительно работает. Но решение задач похоже на игру в "Сапера": люди с сильными навыками самообучения способны обезвредить мины одну за другой, а тем, у кого не хватает базы, легко набить себе шишки и шаг за шагом отступить под давлением неудач. Полностью проходить учебники тоже принято часто, но для тех, кто готовится к поиску работы, диплом, резюме, письменные тесты и собеседования уже отнимают большую часть сил, и потому толстые книги нередко превращаются в тяжелое испытание. + +Если ты тоже сталкиваешься с такими трудностями, то можно сказать, что эта книга сама "нашла" тебя. Она стала моим ответом на этот вопрос: пусть не идеальным, но как минимум честной и активной попыткой. Эта книга сама по себе не гарантирует оффер, но поможет тебе увидеть "карту знаний" по структурам данных и алгоритмам, понять форму, размер и расположение разных "мин" и освоить разные "способы разминирования". Освоив это, ты сможешь увереннее решать задачи и читать технические материалы, шаг за шагом выстраивая целостную систему знаний. + +Я глубоко согласен со словами профессора Фейнмана: "Knowledge isn't free. You have to pay attention." В этом смысле книга не совсем "бесплатна". Чтобы не подвести то драгоценное "внимание", которое ты ей уделишь, я постараюсь вложить в ее создание максимум собственного "внимания". + +Я хорошо понимаю ограниченность собственных знаний. Хотя материал этой книги уже довольно долго шлифовался, в нем наверняка все еще осталось немало ошибок, поэтому я искренне прошу преподавателей и читателей указывать на неточности и недоработки. + +![Hello Algo](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } + +
+

Hello, Алго!

+
+ +Появление компьютеров радикально изменило мир. Благодаря высокой вычислительной скорости и отличной программируемости они стали идеальной средой для исполнения алгоритмов и обработки данных. Реалистичная графика в играх, интеллектуальные решения в автономном вождении, впечатляющие партии AlphaGo и естественное взаимодействие ChatGPT: все это изящные проявления алгоритмов на компьютере. + +На самом деле еще до появления компьютеров алгоритмы и структуры данных уже существовали во всех уголках мира. Ранние алгоритмы были сравнительно простыми: например, древние способы счета или последовательности действий при изготовлении инструментов. По мере развития цивилизации алгоритмы становились тоньше и сложнее. За мастерством ремесленников, промышленными продуктами, освобождающими производительные силы, и даже за научными законами движения Вселенной почти всегда стоит изобретательная алгоритмическая мысль. + +Точно так же структуры данных встречаются повсюду: от социальных сетей до схем метро многие системы можно моделировать как "граф"; от государства до семьи основные формы общественной организации обладают свойствами "дерева"; зимняя одежда похожа на "стек", где то, что надевают первым, снимают последним; тубус для бадминтонных воланов похож на "очередь", где элементы добавляются с одного конца и извлекаются с другого; словарь похож на "хеш-таблицу", позволяющую быстро находить нужную статью. + +Эта книга стремится с помощью понятных анимированных иллюстраций и исполняемых примеров кода помочь читателю понять ключевые идеи алгоритмов и структур данных и научиться реализовывать их программно. На этой основе книга также пытается показать живые проявления алгоритмов в сложном мире и раскрыть их красоту. Надеюсь, она окажется для тебя полезной. diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png new file mode 100644 index 000000000..d3ff211c4 Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png new file mode 100644 index 000000000..517a5d592 Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png new file mode 100644 index 000000000..258dea930 Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png new file mode 100644 index 000000000..0a9c71b84 Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png new file mode 100644 index 000000000..d056fb746 Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png new file mode 100644 index 000000000..f7bdfd021 Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png new file mode 100644 index 000000000..b16b05d2d Binary files /dev/null and b/ru/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png differ diff --git a/ru/docs/chapter_introduction/algorithms_are_everywhere.md b/ru/docs/chapter_introduction/algorithms_are_everywhere.md new file mode 100644 index 000000000..ce5da44ad --- /dev/null +++ b/ru/docs/chapter_introduction/algorithms_are_everywhere.md @@ -0,0 +1,56 @@ +# Алгоритмы повсюду + +Когда мы слышим слово "алгоритм", мы естественным образом думаем о математике. Однако на деле многие алгоритмы не связаны со сложной математикой, а в гораздо большей степени опираются на базовую логику, которую можно увидеть повсюду в повседневной жизни. + +Прежде чем официально перейти к разговору об алгоритмах, стоит поделиться одним любопытным фактом: **ты уже незаметно для себя освоил множество алгоритмов и привык применять их в повседневной жизни**. Ниже я приведу несколько конкретных примеров, чтобы это показать. + +**Пример 1: поиск в словаре**. В английском словаре слова расположены в алфавитном порядке. Предположим, нам нужно найти слово, начинающееся на букву $r$; обычно это делается так, как показано ниже. + +1. Открой словарь примерно посередине и посмотри, с какой буквы начинается страница; предположим, это буква $m$. +2. Поскольку в алфавите $r$ идет после $m$, первую половину словаря можно отбросить, и область поиска сузится до второй половины. +3. Повторяй шаги `1.` и `2.` до тех пор, пока не найдешь страницу, на которой слово начинается с буквы $r$. + +=== "<1>" + ![Процесс поиска в словаре](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) + +=== "<2>" + ![Бинарный поиск в словаре, шаг 2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) + +=== "<3>" + ![Бинарный поиск в словаре, шаг 3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) + +=== "<4>" + ![Бинарный поиск в словаре, шаг 4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) + +=== "<5>" + ![Бинарный поиск в словаре, шаг 5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) + +Поиск в словаре, обязательный навык для школьников, на самом деле и есть знаменитый алгоритм "двоичного поиска". С точки зрения структур данных словарь можно рассматривать как отсортированный "массив"; с точки зрения алгоритмов последовательность действий при поиске слова в словаре можно считать алгоритмом "двоичного поиска". + +**Пример 2: упорядочивание карт**. Во время игры в карты нам нужно раскладывать карты в руке по возрастанию; процесс выглядит так, как показано ниже. + +1. Раздели карты на "упорядоченную" и "неупорядоченную" части и предположи, что в начальный момент самая левая карта уже стоит на правильном месте. +2. Возьми одну карту из неупорядоченной части и вставь ее в правильную позицию внутри упорядоченной части; после этого две самые левые карты уже будут упорядочены. +3. Повторяй шаг `2.` , каждый раз перенося одну карту из неупорядоченной части в упорядоченную, пока все карты не окажутся в порядке. + +![Процесс сортировки колоды карт](algorithms_are_everywhere.assets/playing_cards_sorting.png) + +Описанный выше способ раскладывать карты по сути является алгоритмом "сортировки вставками", который очень эффективен на небольших наборах данных. Во многих языках программирования во встроенных функциях сортировки тоже можно увидеть этот алгоритм. + +**Пример 3: выдача сдачи**. Предположим, в супермаркете мы купили товар на $69$ и дали кассиру $100$, поэтому он должен вернуть нам $31$ сдачи. Этот процесс можно наглядно представить так, как показано на рисунке ниже. + +1. Доступные варианты - это купюры достоинством меньше $31$, например $1$, $5$, $10$ и $20$. +2. Возьми самую большую купюру из доступных, то есть $20$, тогда останется $31 - 20 = 11$. +3. Возьми самую большую купюру из оставшихся, то есть $10$, тогда останется $11 - 10 = 1$. +4. Возьми самую большую купюру из оставшихся, то есть $1$, тогда останется $1 - 1 = 0$. +5. Выдача сдачи завершена, итоговая комбинация: $20 + 10 + 1 = 31$. + +![Процесс выдачи сдачи](algorithms_are_everywhere.assets/greedy_change.png) + +В описанных шагах на каждом этапе выбирается наилучший вариант из доступных в текущий момент, то есть используется купюра наибольшего номинала; в результате получается рабочая схема выдачи сдачи. С точки зрения структур данных и алгоритмов такой подход называется "жадным" алгоритмом. + +От приготовления еды до межзвездных путешествий почти любое решение задачи связано с алгоритмами. Появление компьютеров позволило нам хранить структуры данных в памяти и писать код, который вызывает CPU и GPU для выполнения алгоритмов. Благодаря этому мы можем переносить реальные задачи в компьютер и решать самые разные сложные проблемы более эффективно. + +!!! tip + + Если ты все еще смутно представляешь себе такие понятия, как структуры данных, алгоритмы, массивы и двоичный поиск, просто продолжай читать. Эта книга постепенно введет тебя в мир понимания структур данных и алгоритмов. diff --git a/ru/docs/chapter_introduction/index.md b/ru/docs/chapter_introduction/index.md new file mode 100644 index 000000000..0690c3b3b --- /dev/null +++ b/ru/docs/chapter_introduction/index.md @@ -0,0 +1,9 @@ +# Введение в алгоритмы + +![Введение в алгоритмы](../assets/covers/chapter_introduction.jpg) + +!!! abstract + + Юная девушка легко кружится в танце среди данных, а подол ее платья струится мелодией алгоритмов. + + Она приглашает тебя присоединиться к танцу: следуй за ее шагами и войди в мир алгоритмов, полный логики и красоты. diff --git a/ru/docs/chapter_introduction/summary.md b/ru/docs/chapter_introduction/summary.md new file mode 100644 index 000000000..1283c7ac4 --- /dev/null +++ b/ru/docs/chapter_introduction/summary.md @@ -0,0 +1,24 @@ +# Резюме + +### Ключевые выводы + +- Алгоритмы повсюду встречаются в повседневной жизни и вовсе не являются чем-то далеким и эзотерическим. На деле мы уже давно незаметно для себя освоили множество алгоритмов и используем их для решения самых разных жизненных задач. +- Принцип поиска в словаре соответствует алгоритму двоичного поиска. Двоичный поиск воплощает важную алгоритмическую идею "разделяй и властвуй". +- Процесс раскладывания карт очень похож на алгоритм сортировки вставками. Сортировка вставками подходит для упорядочивания небольших наборов данных. +- Выдача сдачи по шагам по своей сути является жадным алгоритмом, в котором на каждом этапе выбирается лучшее решение в текущей ситуации. +- Алгоритм - это набор инструкций или шагов, который решает конкретную задачу за конечное время, а структура данных - это способ, которым компьютер организует и хранит данные. +- Структуры данных и алгоритмы тесно связаны. Структуры данных являются основой алгоритмов, а алгоритмы оживляют структуры данных. +- Структуры данных и алгоритмы можно сравнить со сборкой конструктора: детали представляют данные, форма деталей и способ их соединения представляют структуру данных, а шаги сборки соответствуют алгоритму. + +### Q & A + +**Q**: Я программист и в повседневной работе никогда не решал задачи "алгоритмами": распространенные алгоритмы уже инкапсулированы в языках программирования, и ими можно пользоваться напрямую. Значит ли это, что рабочие задачи еще не дошли до уровня, где действительно нужны алгоритмы? + +Если сравнить конкретные рабочие навыки с "приемами" в боевых искусствах, то фундаментальные дисциплины скорее напоминают "внутреннюю силу". + +Я считаю, что смысл изучения алгоритмов (и других фундаментальных дисциплин) не в том, чтобы каждый раз реализовывать их с нуля в работе, а в том, чтобы, опираясь на полученные знания, уметь профессионально реагировать и принимать решения при решении задач, тем самым повышая общее качество работы. Вот простой пример: в каждом языке программирования есть встроенная функция сортировки. + +- Если мы не изучали структуры данных и алгоритмы, то для любых данных, скорее всего, просто отдали бы их этой функции сортировки. Все работает гладко, производительность хорошая, и на первый взгляд никаких проблем нет. +- Но если мы изучали алгоритмы, то знаем, что временная сложность встроенной сортировки равна $O(n \log n)$ ; однако если данные состоят из целых чисел фиксированной разрядности (например, номеров студентов), можно воспользоваться более эффективной "поразрядной сортировкой", снизив сложность до $O(nk)$ , где $k$ - число разрядов. Когда объем данных очень велик, сэкономленное время выполнения может принести заметную пользу, например снизить издержки и улучшить пользовательский опыт. + +В инженерной практике огромное количество задач трудно довести до оптимального решения, и многие из них решаются лишь "примерно". Сложность задачи зависит, с одной стороны, от ее собственной природы, а с другой - от запаса знаний человека, который на нее смотрит. Чем полнее знания и чем больше опыт, тем глубже получается анализ задачи и тем изящнее ее можно решить. diff --git a/ru/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png b/ru/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png new file mode 100644 index 000000000..6059ee7cc Binary files /dev/null and b/ru/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png differ diff --git a/ru/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png b/ru/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png new file mode 100644 index 000000000..0a73c7ddc Binary files /dev/null and b/ru/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png differ diff --git a/ru/docs/chapter_introduction/what_is_dsa.md b/ru/docs/chapter_introduction/what_is_dsa.md new file mode 100644 index 000000000..168bf1803 --- /dev/null +++ b/ru/docs/chapter_introduction/what_is_dsa.md @@ -0,0 +1,53 @@ +# Что такое алгоритм + +## Определение алгоритма + +Алгоритм (algorithm) - это набор инструкций или шагов, который решает конкретную задачу за конечное время. Он обладает следующими свойствами. + +- Задача четко определена и имеет ясные определения входных и выходных данных. +- Алгоритм осуществим и может быть выполнен за конечное число шагов, времени и памяти. +- Каждый шаг имеет однозначный смысл, и при одинаковых входных данных и условиях выполнения результат всегда будет одинаковым. + +## Определение структуры данных + +Структура данных (data structure) - это способ организации и хранения данных, охватывающий само содержимое данных, связи между данными и методы работы с ними. У нее есть следующие цели проектирования. + +- Занимать как можно меньше места, чтобы экономить память компьютера. +- Выполнять операции над данными как можно быстрее, включая доступ, добавление, удаление, обновление и т. д. +- Предоставлять компактное представление данных и логическую информацию, чтобы алгоритмы могли работать эффективно. + +**Проектирование структур данных - это процесс, полный компромиссов**. Если мы хотим улучшить что-то одно, то часто вынуждены уступить в чем-то другом. Ниже приведены два примера. + +- По сравнению с массивами связные списки удобнее для добавления и удаления данных, но жертвуют скоростью доступа к ним. +- По сравнению со связными списками графы предоставляют более богатую логическую информацию, но требуют большего объема памяти. + +## Связь между структурами данных и алгоритмами + +Как показано на рисунке ниже, структуры данных и алгоритмы тесно связаны и сильно зависят друг от друга; это проявляется в следующих трех аспектах. + +- Структуры данных служат фундаментом алгоритмов. Они дают алгоритмам структурированный способ хранения данных и методы работы с ними. +- Алгоритмы оживляют структуры данных. Сами по себе структуры данных лишь хранят информацию, а в сочетании с алгоритмами позволяют решать конкретные задачи. +- Алгоритмы обычно можно реализовать на основе разных структур данных, но эффективность выполнения может сильно различаться, поэтому выбор подходящей структуры данных является ключевым. + +![Связь между структурами данных и алгоритмами](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) + +Структуры данных и алгоритмы похожи на сборку конструктора, показанную на рисунке ниже. В набор конструктора, помимо множества деталей, входит и подробная инструкция по сборке. Если шаг за шагом следовать этой инструкции, можно собрать красивую модель. + +![Сборка конструктора](what_is_dsa.assets/assembling_blocks.png) + +Подробное соответствие между ними показано в таблице ниже. + +

Таблица   Сравнение структур данных и алгоритмов со сборкой конструктора

+ +| Структуры данных и алгоритмы | Сборка конструктора | +| ---------------------------- | ------------------------------------------ | +| Входные данные | Несобранные детали конструктора | +| Структура данных | Организация деталей: форма, размер, способ соединения и т. д. | +| Алгоритм | Последовательность шагов по сборке деталей в целевую форму | +| Выходные данные | Собранная модель конструктора | + +Стоит отметить, что структуры данных и алгоритмы не зависят от конкретного языка программирования. Именно поэтому эта книга может давать реализации на разных языках программирования. + +!!! tip "Принятое сокращение" + + В реальных обсуждениях мы обычно сокращаем выражение "структуры данных и алгоритмы" до просто "алгоритмы". Например, хорошо известные алгоритмические задачи LeetCode на деле одновременно проверяют знания и по структурам данных, и по алгоритмам. diff --git a/ru/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png b/ru/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png new file mode 100644 index 000000000..cc061a0b0 Binary files /dev/null and b/ru/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png differ diff --git a/ru/docs/chapter_preface/about_the_book.md b/ru/docs/chapter_preface/about_the_book.md new file mode 100644 index 000000000..df3c92fae --- /dev/null +++ b/ru/docs/chapter_preface/about_the_book.md @@ -0,0 +1,54 @@ +# Об этой книге + +Этот проект направлен на создание открытого, бесплатного и дружелюбного к новичкам вводного пособия по структурам данных и алгоритмам. + +- Вся книга построена на анимированных иллюстрациях: материал изложен ясно и последовательно, а кривая обучения остается плавной, помогая начинающим постепенно увидеть карту знаний по структурам данных и алгоритмам. +- Исходный код можно запускать одним нажатием, что помогает читателю через практику развивать навыки программирования и понимать, как работают алгоритмы и как устроены структуры данных на базовом уровне. +- Мы призываем читателей учиться друг у друга: задавайте вопросы и делитесь своими наблюдениями в комментариях, чтобы вместе продвигаться вперед через обсуждение и обмен идеями. + +## Целевая аудитория + +Если ты только начинаешь изучать алгоритмы, никогда раньше с ними не сталкивался или уже решал некоторые задачи, но все еще смутно представляешь себе структуры данных и алгоритмы и постоянно колеблешься между "понимаю" и "не понимаю", то эта книга создана именно для тебя! + +Если у тебя уже накопился определенный опыт решения задач и ты знаком с большинством типовых вопросов, книга поможет тебе системно повторить и упорядочить знания об алгоритмах, а исходный код из репозитория можно использовать как "инструментарий для решения задач" или как "алгоритмический словарь". + +Если же ты уже настоящий "гуру" алгоритмов, мы будем рады получить твои ценные замечания или [создавать книгу вместе](https://www.hello-algo.com/chapter_appendix/contribution/). + +!!! success "Предварительные требования" + + Тебе нужна хотя бы базовая подготовка в одном из языков программирования, чтобы читать и писать простой код. + +## Структура содержания + +Основное содержание книги показано на рисунке ниже. + +- **Анализ сложности**: измерения и методы оценки структур данных и алгоритмов. Способы вычисления временной и пространственной сложности, распространенные типы, примеры и т. д. +- **Структуры данных**: способы классификации базовых типов данных и структур данных. Определения, достоинства и недостатки, основные операции, распространенные разновидности, типичные применения и методы реализации массивов, связных списков, стеков, очередей, хеш-таблиц, деревьев, куч, графов и других структур. +- **Алгоритмы**: определения, достоинства и недостатки, эффективность, области применения, этапы решения и примеры задач для поиска, сортировки, разделяй-и-властвуй, поиска с возвратом, динамического программирования и жадных алгоритмов. + +![Основное содержание книги](about_the_book.assets/hello_algo_mindmap.png) + +## Благодарности + +Эта книга непрерывно совершенствуется благодаря совместным усилиям множества участников сообщества open source. Спасибо каждому автору, который вложил свое время и силы; их имена перечислены в порядке, автоматически сгенерированном GitHub: krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, IsChristina, xBLACKICEx, guowei-gong, Cathay-Chen, pengchzn, QiLOL, magentaqin, hello-ikun, JoseHung, qualifier1024, thomasq0, sunshinesDL, L-Super, Guanngxu, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, Shyam-Chen, theNefelibatas, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, MolDuM, Nigh, Dr-XYZ, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, beatrix-chan, DullSword, xjr7670, jiaxianhua, qq909244296, iStig, boloboloda, hts0000, gledfish, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, llql1211, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, GaochaoZhu, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Allen-Scai, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, zhongfq, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai и KawaiiAsh. + +Рецензирование кода для этой книги выполнили coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon и rongyi (в алфавитном порядке). Спасибо им за время и силы: именно они обеспечили единообразие и стандартизацию кода на разных языках. + +Традиционную китайскую версию книги вычитали Shyam-Chen и Dr-XYZ, английскую версию - yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0 и magentaqin, а японскую версию - eltociear. Именно благодаря их постоянному вкладу эта книга может быть полезна более широкому кругу читателей, и мы искренне благодарим их. + +Инструмент генерации ePub-версии этой книги разработал zhongfq. Благодарим его за вклад, который дал читателям более гибкий способ чтения. + +Во время работы над этой книгой мне помогало очень много людей. + +- Спасибо моему наставнику в компании, доктору Ли Си: в одной из бесед ты подтолкнул меня "быстрее начать", и это укрепило мою решимость написать эту книгу; +- Спасибо моей девушке Bubble, первому читателю этой книги: с позиции новичка в алгоритмах ты дала много ценных замечаний, благодаря которым книга стала понятнее для начинающих; +- Спасибо Tengbao, Qibao и Feibao за придуманное ими креативное название книги, которое возвращает нас к теплому воспоминанию о первой строке кода "Hello World!"; +- Спасибо Xiaoquan за профессиональную помощь по вопросам интеллектуальной собственности: она сыграла важную роль в совершенствовании этой открытой книги; +- Спасибо Sutong за прекрасный дизайн обложки и логотипа, а также за терпеливые многочисленные правки, на которые тебя вдохновлял мой перфекционизм; +- Спасибо @squidfunk за советы по верстке и за созданную им открытую тему документации [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master). + +Во время написания книги я прочитал множество учебников и статей по структурам данных и алгоритмам. Эти работы стали для книги прекрасными образцами и помогли обеспечить точность и качество материала. Я искренне благодарю всех преподавателей и предшественников за их выдающийся вклад! + +Эта книга пропагандирует способ обучения, в котором работают и руки, и голова; в этом отношении на меня сильно повлияла [Dive into Deep Learning](https://github.com/d2l-ai/d2l-zh). Я горячо рекомендую эту замечательную работу всем читателям. + +**От всего сердца благодарю моих родителей: именно ваша постоянная поддержка и ободрение дали мне возможность заниматься этим интересным делом**. diff --git a/ru/docs/chapter_preface/index.md b/ru/docs/chapter_preface/index.md new file mode 100644 index 000000000..c7e63cdda --- /dev/null +++ b/ru/docs/chapter_preface/index.md @@ -0,0 +1,9 @@ +# Предисловие + +![Предисловие](../assets/covers/chapter_preface.jpg) + +!!! abstract + + Алгоритмы подобны прекрасной симфонии, а каждая строка кода течет, как мелодия. + + Пусть эта книга мягко зазвучит в твоем сознании и оставит после себя особую и глубокую мелодию. diff --git a/ru/docs/chapter_preface/suggestions.assets/code_md_to_repo.png b/ru/docs/chapter_preface/suggestions.assets/code_md_to_repo.png new file mode 100644 index 000000000..6495e053a Binary files /dev/null and b/ru/docs/chapter_preface/suggestions.assets/code_md_to_repo.png differ diff --git a/ru/docs/chapter_preface/suggestions.assets/download_code.png b/ru/docs/chapter_preface/suggestions.assets/download_code.png new file mode 100644 index 000000000..b37e0d67a Binary files /dev/null and b/ru/docs/chapter_preface/suggestions.assets/download_code.png differ diff --git a/ru/docs/chapter_preface/suggestions.assets/learning_route.png b/ru/docs/chapter_preface/suggestions.assets/learning_route.png new file mode 100644 index 000000000..4ff7a3d01 Binary files /dev/null and b/ru/docs/chapter_preface/suggestions.assets/learning_route.png differ diff --git a/ru/docs/chapter_preface/suggestions.assets/pythontutor_example.png b/ru/docs/chapter_preface/suggestions.assets/pythontutor_example.png new file mode 100644 index 000000000..c76de89f0 Binary files /dev/null and b/ru/docs/chapter_preface/suggestions.assets/pythontutor_example.png differ diff --git a/ru/docs/chapter_preface/suggestions.md b/ru/docs/chapter_preface/suggestions.md new file mode 100644 index 000000000..26de2a6ce --- /dev/null +++ b/ru/docs/chapter_preface/suggestions.md @@ -0,0 +1,241 @@ +# Как пользоваться этой книгой + +!!! tip + + Чтобы получить наилучший опыт чтения, рекомендуется полностью прочитать этот раздел. + +## Соглашения о стиле изложения + +- Разделы, помеченные `*` в заголовке, являются дополнительными и сравнительно более сложными. Если времени мало, их можно пока пропустить. +- Технические термины будут выделяться полужирным шрифтом (в бумажной и PDF-версиях) или подчеркиванием (в веб-версии), например массив (array). Рекомендуется запоминать их, чтобы легче читать техническую литературу. +- Ключевое содержание и итоговые формулировки будут **выделяться полужирным**, и на такие фрагменты стоит обращать особое внимание. +- Слова и выражения со специальным смыслом будут отмечаться "кавычками", чтобы избежать неоднозначности. +- Когда названия различаются между языками программирования, эта книга ориентируется на Python; например, для обозначения "пустого" значения используется `None`. +- В книге частично отказались от строгих правил оформления комментариев в языках программирования ради более компактной верстки. Комментарии в основном делятся на три типа: комментарии-заголовки, содержательные комментарии и многострочные комментарии. + +=== "Python" + + ```python title="" + """Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п.""" + + # Содержательный комментарий: подробно поясняет код + + """ + Многострочный + комментарий + """ + ``` + +=== "C++" + + ```cpp title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Java" + + ```java title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "C#" + + ```csharp title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Go" + + ```go title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Swift" + + ```swift title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "JS" + + ```javascript title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "TS" + + ```typescript title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Dart" + + ```dart title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Rust" + + ```rust title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "C" + + ```c title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. */ + + // Содержательный комментарий: подробно поясняет код + + /** + * Многострочный + * комментарий + */ + ``` + +=== "Ruby" + + ```ruby title="" + ### Комментарий-заголовок: используется для обозначения функций, классов, тестовых примеров и т. п. ### + + # Содержательный комментарий: подробно поясняет код + + # Многострочный + # комментарий + ``` + +## Эффективное обучение с помощью анимированных иллюстраций + +По сравнению с текстом видео и изображения обладают большей информационной плотностью и более четкой структурой, поэтому их легче воспринимать. В этой книге **ключевые и сложные идеи в основном будут показываться в виде анимированных иллюстраций**, а текст будет играть роль пояснения и дополнения. + +Если во время чтения ты встречаешь фрагмент с анимированной иллюстрацией, как на рисунке ниже, **в первую очередь ориентируйся на изображение, а текст используй как дополнение**, соединяя оба источника для понимания материала. + +![Пример анимированной иллюстрации](../index.assets/animation.gif) + +## Углубление понимания через практику кода + +Сопроводительный код этой книги размещен в [репозитории GitHub](https://github.com/krahets/hello-algo). Как показано ниже, **исходный код снабжен тестовыми примерами и может запускаться одним нажатием**. + +Если позволяет время, **рекомендуется самостоятельно перепечатать код**. Если времени на обучение мало, то хотя бы полностью прочитай и запусти весь код. + +По сравнению с простым чтением кода сам процесс его написания обычно дает больше пользы. **Учиться на практике - значит учиться по-настоящему**. + +![Пример запуска кода](../index.assets/running_code.gif) + +Подготовка к запуску кода в основном состоит из трех шагов. + +**Шаг 1: установить локальную среду программирования**. Воспользуйся [руководством](https://www.hello-algo.com/chapter_appendix/installation/) из приложения. Если среда уже установлена, этот шаг можно пропустить. + +**Шаг 2: клонировать или скачать репозиторий с кодом**. Перейди в [репозиторий GitHub](https://github.com/krahets/hello-algo). Если у тебя уже установлен [Git](https://git-scm.com/downloads), репозиторий можно клонировать следующей командой: + +```shell +git clone https://github.com/krahets/hello-algo.git +``` + +Конечно, можно также нажать кнопку "Download ZIP" в месте, показанном на рисунке ниже, напрямую скачать архив с кодом и затем распаковать его локально. + +![Клонирование репозитория и загрузка кода](suggestions.assets/download_code.png) + +**Шаг 3: запустить исходный код**. Как показано на рисунке ниже, для блоков кода, у которых сверху указано имя файла, соответствующий исходный файл можно найти в папке `codes` репозитория. Эти файлы запускаются одним нажатием, что помогает не тратить лишнее время на отладку и сосредоточиться на изучении материала. + +![Блоки кода и соответствующие исходные файлы](suggestions.assets/code_md_to_repo.png) + +Помимо локального запуска, **веб-версия также поддерживает визуальный запуск Python-кода** (на базе [pythontutor](https://pythontutor.com/)). Как показано ниже, можно нажать "Визуализировать выполнение" под блоком кода, чтобы раскрыть окно и наблюдать за выполнением алгоритма; также можно нажать "Полноэкранный режим", чтобы получить более удобный просмотр. + +![Визуальный запуск Python-кода](suggestions.assets/pythontutor_example.png) + +## Совместный рост через вопросы и обсуждения + +Во время чтения книги не стоит легко пропускать те места, которые остались непонятными. **Смело задавай свои вопросы в разделе комментариев**: я и мои друзья постараемся ответить тебе как можно тщательнее, обычно в течение двух дней. + +Как показано на рисунке ниже, в веб-версии у каждой главы внизу есть раздел комментариев. Надеюсь, ты будешь чаще обращать внимание на его содержание. С одной стороны, это поможет увидеть, с какими трудностями сталкиваются другие читатели, восполнить пробелы и подтолкнуть себя к более глубоким размышлениям. С другой стороны, буду рад, если ты щедро ответишь на вопросы других участников, поделишься своими наблюдениями и поможешь им продвинуться вперед. + +![Пример раздела комментариев](../index.assets/comment.gif) + +## Дорожная карта изучения алгоритмов + +В целом процесс изучения структур данных и алгоритмов можно разделить на три этапа. + +1. **Этап 1: введение в алгоритмы**. Нужно познакомиться с особенностями и способами применения разных структур данных, а также изучить принципы, ход работы, назначение и эффективность различных алгоритмов. +2. **Этап 2: решение алгоритмических задач**. Рекомендуется начинать с популярных задач и сначала накопить не менее 100 решенных примеров, чтобы познакомиться с основными типами алгоритмических проблем. На первых порах "забывание знаний" может стать испытанием, но это нормально. Мы можем повторять задачи по "кривой забывания Эббингауза", и обычно после 3-5 циклов повторения материал прочно закрепляется. Рекомендуемые списки задач и планы практики см. в этом [репозитории GitHub](https://github.com/krahets/LeetCode-Book). +3. **Этап 3: построение системы знаний**. В учебной части можно читать статьи по алгоритмам, разбирать каркасы решений и учебники, чтобы постоянно обогащать свою систему знаний. В практической части можно пробовать более продвинутые стратегии, например классификацию по темам, несколько решений одной задачи или одно решение для нескольких задач; соответствующий опыт можно найти в разных сообществах. + +Как показано на рисунке ниже, содержание этой книги в основном покрывает "этап 1" и призвано помочь тебе более эффективно перейти к обучению на этапах 2 и 3. + +![Дорожная карта изучения алгоритмов](suggestions.assets/learning_route.png) diff --git a/ru/docs/chapter_preface/summary.md b/ru/docs/chapter_preface/summary.md new file mode 100644 index 000000000..d3debdaca --- /dev/null +++ b/ru/docs/chapter_preface/summary.md @@ -0,0 +1,10 @@ +# Резюме + +### Ключевые выводы + +- Основная аудитория этой книги - новички в изучении алгоритмов. Если у тебя уже есть определенная база, книга поможет системно повторить знания, а исходный код можно использовать как "инструментарий для решения задач". +- Основное содержание книги состоит из трех частей: анализ сложности, структуры данных и алгоритмы; вместе они охватывают большую часть тем этой области. +- Для начинающих особенно важно на старте прочитать хорошее вводное пособие: это помогает избежать множества лишних обходных путей. +- Анимированные иллюстрации в книге обычно используются для объяснения ключевых и сложных идей. При чтении книги этим материалам стоит уделять больше внимания. +- Практика - лучший способ изучать программирование. Настоятельно рекомендуется запускать исходный код и набирать его самостоятельно. +- В веб-версии книги у каждой главы есть раздел комментариев, где можно в любой момент делиться вопросами и своими мыслями. diff --git a/ru/docs/chapter_reference/index.md b/ru/docs/chapter_reference/index.md new file mode 100644 index 000000000..1ab64b41e --- /dev/null +++ b/ru/docs/chapter_reference/index.md @@ -0,0 +1,25 @@ +--- +icon: material/bookshelf +--- + +# Список литературы + +[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). + +[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). + +[3] Robert Sedgewick, et al. Algorithms (4th Edition). + +[4] Yan Weimin. Data Structures (C Language Edition). + +[5] Deng Junhui. Data Structures (C++ Language Edition, 3rd Edition). + +[6] Mark Allen Weiss; translated by Chen Yue. Data Structures and Algorithm Analysis: Java Description (3rd Edition). + +[7] Cheng Jie. A Plainspoken Guide to Data Structures. + +[8] Wang Zheng. The Beauty of Data Structures and Algorithms. + +[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). + +[10] Aston Zhang, et al. Dive into Deep Learning. diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_example.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_example.png new file mode 100644 index 000000000..ee9285842 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_example.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_ranges.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_ranges.png new file mode 100644 index 000000000..adbcb4fc2 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_ranges.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step1.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step1.png new file mode 100644 index 000000000..b3fc9ec01 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step1.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step2.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step2.png new file mode 100644 index 000000000..3e1f4ca7d Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step2.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step3.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step3.png new file mode 100644 index 000000000..076b262c3 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step3.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step4.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step4.png new file mode 100644 index 000000000..216b4c94f Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step4.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step5.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step5.png new file mode 100644 index 000000000..946a63076 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step5.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step6.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step6.png new file mode 100644 index 000000000..0452971a6 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step6.png differ diff --git a/ru/docs/chapter_searching/binary_search.assets/binary_search_step7.png b/ru/docs/chapter_searching/binary_search.assets/binary_search_step7.png new file mode 100644 index 000000000..52970eda6 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search.assets/binary_search_step7.png differ diff --git a/ru/docs/chapter_searching/binary_search.md b/ru/docs/chapter_searching/binary_search.md new file mode 100644 index 000000000..1b1c2b4b6 --- /dev/null +++ b/ru/docs/chapter_searching/binary_search.md @@ -0,0 +1,83 @@ +# Двоичный поиск + +Двоичный поиск (binary search) - это эффективный алгоритм поиска, основанный на стратегии "разделяй и властвуй". Он использует упорядоченность данных и на каждом шаге сокращает область поиска вдвое, пока не найдет целевой элемент или пока интервал поиска не опустеет. + +!!! question + + Дан массив `nums` длины $n$, элементы которого расположены в порядке возрастания и не повторяются. Найдите и верните индекс элемента `target` в этом массиве. Если массив не содержит этого элемента, верните $-1$ . Пример показан на рисунке ниже. + +![Пример данных для двоичного поиска](binary_search.assets/binary_search_example.png) + +Как показано на рисунке ниже, сначала инициализируем указатели $i = 0$ и $j = n - 1$ , которые указывают на первый и последний элементы массива и задают интервал поиска $[0, n - 1]$ . Обратите внимание: квадратные скобки обозначают замкнутый интервал и включают граничные значения. + +Далее в цикле выполняются следующие два шага. + +1. Вычислить индекс середины $m = \lfloor {(i + j) / 2} \rfloor$ , где $\lfloor \: \rfloor$ означает операцию округления вниз. +2. Сравнить `nums[m]` и `target` , после чего возможны три случая. + 1. Если `nums[m] < target` , это означает, что `target` находится в интервале $[m + 1, j]$ , поэтому выполняется $i = m + 1$ . + 2. Если `nums[m] > target` , это означает, что `target` находится в интервале $[i, m - 1]$ , поэтому выполняется $j = m - 1$ . + 3. Если `nums[m] = target` , значит, элемент `target` найден, поэтому возвращается индекс $m$ . + +Если массив не содержит целевой элемент, область поиска в итоге сузится до пустого интервала. В этом случае возвращается $-1$ . + +=== "<1>" + ![Процесс двоичного поиска](binary_search.assets/binary_search_step1.png) + +=== "<2>" + ![binary_search_step2](binary_search.assets/binary_search_step2.png) + +=== "<3>" + ![binary_search_step3](binary_search.assets/binary_search_step3.png) + +=== "<4>" + ![binary_search_step4](binary_search.assets/binary_search_step4.png) + +=== "<5>" + ![binary_search_step5](binary_search.assets/binary_search_step5.png) + +=== "<6>" + ![binary_search_step6](binary_search.assets/binary_search_step6.png) + +=== "<7>" + ![binary_search_step7](binary_search.assets/binary_search_step7.png) + +Стоит отметить, что поскольку и $i$ , и $j$ имеют тип `int` , **то сумма $i + j$ может выйти за пределы диапазона типа `int`**. Чтобы избежать переполнения, обычно используют формулу $m = \lfloor {i + (j - i) / 2} \rfloor$ для вычисления середины. + +Код приведен ниже: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search} +``` + +**Временная сложность равна $O(\log n)$** : в цикле двоичного поиска интервал каждый раз сокращается вдвое, поэтому число итераций равно $\log_2 n$ . + +**Пространственная сложность равна $O(1)$** : указатели $i$ и $j$ занимают константный объем памяти. + +## Методы представления интервалов + +Помимо описанного выше двойного замкнутого интервала, часто используется и интервал "слева закрыт, справа открыт", который задается как $[0, n)$ , то есть левая граница включается, а правая - нет. В этом представлении интервал $[i, j)$ пуст, когда $i = j$ . + +На основе этого представления можно реализовать двоичный поиск с той же функциональностью: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search_lcro} +``` + +Как показано на рисунке ниже, в этих двух вариантах представления интервала различаются инициализация, условие цикла и операция сужения интервала в алгоритме двоичного поиска. + +Поскольку в записи "двойной замкнутый интервал" обе границы являются закрытыми, операции сужения интервала при помощи указателей $i$ и $j$ тоже получаются симметричными. Из-за этого в таком варианте сложнее допустить ошибку, **поэтому обычно рекомендуется использовать именно запись "двойной замкнутый интервал"**. + +![Два определения интервалов](binary_search.assets/binary_search_ranges.png) + +## Преимущества и ограничения + +Двоичный поиск показывает хорошие результаты и по времени, и по памяти. + +- Двоичный поиск очень эффективен по времени. На больших объемах данных логарифмическая временная сложность дает заметное преимущество. Например, когда размер данных $n = 2^{20}$ , линейный поиск потребует $2^{20} = 1048576$ итераций, тогда как двоичный поиск выполнится всего за $\log_2 2^{20} = 20$ итераций. +- Двоичный поиск не требует дополнительной памяти. По сравнению с алгоритмами поиска, которым нужно внешнее пространство (например, с хеш-поиском), двоичный поиск заметно экономнее по памяти. + +Однако двоичный поиск подходит не для всех ситуаций, и основные причины таковы. + +- Двоичный поиск применим только к упорядоченным данным. Если входные данные неупорядочены, специально сортировать их ради двоичного поиска невыгодно. Это связано с тем, что временная сложность алгоритмов сортировки обычно составляет $O(n \log n)$ , что выше, чем у линейного и двоичного поиска. Если элементы приходится часто вставлять, то для сохранения порядка в массиве их нужно помещать в конкретные позиции, а это требует $O(n)$ времени и тоже обходится дорого. +- Двоичный поиск применим только к массивам. Для него нужен скачкообразный доступ к элементам, а в связном списке такой доступ малоэффективен, поэтому двоичный поиск не подходит для списков и структур данных, построенных на их основе. +- При малом объеме данных линейный поиск работает лучше. В линейном поиске на каждом шаге нужна всего одна операция сравнения; в двоичном поиске требуется 1 сложение, 1 деление, от 1 до 3 сравнений и еще 1 сложение или вычитание, то есть всего от 4 до 6 элементарных операций. Поэтому при небольшом $n$ линейный поиск может оказаться быстрее двоичного. diff --git a/ru/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png b/ru/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png new file mode 100644 index 000000000..d04393b57 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png differ diff --git a/ru/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png b/ru/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png new file mode 100644 index 000000000..30e597b43 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png differ diff --git a/ru/docs/chapter_searching/binary_search_edge.md b/ru/docs/chapter_searching/binary_search_edge.md new file mode 100644 index 000000000..7669ea47f --- /dev/null +++ b/ru/docs/chapter_searching/binary_search_edge.md @@ -0,0 +1,56 @@ +# Двоичный поиск границ + +## Поиск левой границы + +!!! question + + Дан упорядоченный массив `nums` длины $n$, который может содержать повторяющиеся элементы. Верните индекс самого левого элемента `target` в массиве. Если массив не содержит этот элемент, верните $-1$ . + +Вспомним метод поиска точки вставки при двоичном поиске: после завершения поиска указатель $i$ указывает на самый левый `target` , **поэтому поиск точки вставки по сути и есть поиск индекса самого левого `target`**. + +Рассмотрим реализацию поиска левой границы через функцию поиска точки вставки. Обратите внимание: массив может не содержать `target` , и тогда возможны две ситуации. + +- Индекс точки вставки $i$ выходит за границы массива. +- Элемент `nums[i]` не равен `target` . + +Если возникает любая из этих ситуаций, достаточно сразу вернуть $-1$ . Код приведен ниже: + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} +``` + +## Поиск правой границы + +Как тогда найти самый правый `target` ? Самый прямой способ - изменить код, заменив операцию сужения указателя в случае `nums[m] == target` . Мы не будем приводить этот код, заинтересованные читатели могут реализовать его самостоятельно. + +Ниже представлены два более изящных способа. + +### Повторное использование поиска левой границы + +На самом деле функцию поиска самого левого элемента можно использовать и для поиска самого правого элемента. Конкретная идея такова: **преобразовать поиск самого правого `target` в поиск самого левого `target + 1`**. + +Как показано на рисунке ниже, после завершения поиска указатель $i$ указывает на самый левый `target + 1` (если он существует), а указатель $j$ указывает на самый правый `target` , **поэтому достаточно вернуть $j$**. + +![Преобразование поиска правой границы в поиск левой](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) + +Обратите внимание: функция возвращает точку вставки $i$ , поэтому из нее нужно вычесть $1$ , чтобы получить $j$ : + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} +``` + +### Преобразование в поиск элемента + +Мы знаем, что если массив не содержит `target` , то в конце поиска указатели $i$ и $j$ будут указывать соответственно на первый элемент, больший `target` , и на первый элемент, меньший `target` . + +Следовательно, как показано на рисунке ниже, для поиска левой и правой границы можно сконструировать элемент, которого нет в массиве. + +- Поиск самого левого `target` : можно преобразовать в поиск `target - 0.5` и вернуть указатель $i$ . +- Поиск самого правого `target` : можно преобразовать в поиск `target + 0.5` и вернуть указатель $j$ . + +![Преобразование поиска границ в поиск элемента](binary_search_edge.assets/binary_search_edge_by_element.png) + +Код здесь опущен, но стоит обратить внимание на два момента. + +- По условию массив не содержит дробных чисел, поэтому нам не нужно беспокоиться о том, как обрабатывать случай равенства другим элементам массива. +- Поскольку этот метод вводит дробные числа, переменную `target` в функции нужно изменить на тип с плавающей запятой (в Python менять ничего не требуется). diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png new file mode 100644 index 000000000..1251800a0 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png new file mode 100644 index 000000000..8d875e065 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png new file mode 100644 index 000000000..21bf1c5c0 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png new file mode 100644 index 000000000..5172f7f54 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png new file mode 100644 index 000000000..4ef582eeb Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png new file mode 100644 index 000000000..b03ae6bd8 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png new file mode 100644 index 000000000..3687ed28d Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png new file mode 100644 index 000000000..b1454fa1d Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png new file mode 100644 index 000000000..d21c4b089 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png new file mode 100644 index 000000000..bf6fee2c5 Binary files /dev/null and b/ru/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png differ diff --git a/ru/docs/chapter_searching/binary_search_insertion.md b/ru/docs/chapter_searching/binary_search_insertion.md new file mode 100644 index 000000000..45841da3f --- /dev/null +++ b/ru/docs/chapter_searching/binary_search_insertion.md @@ -0,0 +1,91 @@ +# Точка вставки при двоичном поиске + +Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения многих вариаций задачи, например для поиска позиции вставки целевого элемента. + +## Случай без повторяющихся элементов + +!!! question + + Дан упорядоченный массив `nums` длины $n$ и элемент `target` , причем в массиве нет повторяющихся элементов. Нужно вставить `target` в массив `nums` , сохранив порядок. Если элемент `target` уже присутствует в массиве, вставьте его слева от него. Верните индекс, который будет иметь `target` после вставки. Пример показан на рисунке ниже. + +![Пример данных для точки вставки](binary_search_insertion.assets/binary_search_insertion_example.png) + +Если мы хотим переиспользовать код двоичного поиска из предыдущего раздела, нужно ответить на два вопроса. + +**Вопрос 1**: если массив содержит `target` , будет ли индекс вставки совпадать с индексом этого элемента? + +По условию `target` нужно вставить слева от равного элемента, а это означает, что новый `target` занимает место старого `target` . Иначе говоря, **если массив содержит `target` , то индекс вставки совпадает с индексом этого `target`**. + +**Вопрос 2**: если массив не содержит `target` , индекс какого элемента будет точкой вставки? + +Рассмотрим процесс двоичного поиска подробнее: когда `nums[m] < target` , указатель $i$ сдвигается, а значит, приближается к элементу, который больше либо равен `target` . Аналогично указатель $j$ все время приближается к элементу, который меньше либо равен `target` . + +Следовательно, после завершения двоичного поиска обязательно выполняется следующее: указатель $i$ указывает на первый элемент, больший `target` , а указатель $j$ указывает на первый элемент, меньший `target` . **Нетрудно сделать вывод, что если массив не содержит `target` , то индекс вставки равен $i$** . Код приведен ниже: + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} +``` + +## Случай с повторяющимися элементами + +!!! question + + В предыдущей задаче теперь допускается, что массив может содержать повторяющиеся элементы, а все остальные условия остаются без изменений. + +Если в массиве есть несколько элементов `target` , то обычный двоичный поиск сможет вернуть индекс только одного из них, **но не позволит определить, сколько элементов `target` находится слева и справа от него**. + +По условию целевой элемент нужно вставить в самую левую позицию, **поэтому нам нужно найти индекс самого левого `target` в массиве**. На первом этапе можно рассмотреть решение, показанное на рисунке ниже. + +1. Выполнить двоичный поиск и получить индекс любого элемента `target` , обозначив его как $k$ . +2. Начиная с индекса $k$ , линейно двигаться влево и вернуть результат, когда будет найден самый левый `target` . + +![Линейный поиск точки вставки среди повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_naive.png) + +Этот метод применим на практике, однако в нем есть линейный поиск, поэтому его временная сложность равна $O(n)$ . Когда в массиве имеется много повторяющихся `target` , такой подход работает неэффективно. + +Теперь рассмотрим расширение кода двоичного поиска. Как показано на рисунке ниже, общий процесс остается прежним: на каждом шаге мы сначала вычисляем индекс середины $m$ , а затем сравниваем `target` и `nums[m]` , после чего возможны следующие случаи. + +- Когда `nums[m] < target` или `nums[m] > target` , это означает, что `target` еще не найден, поэтому используется стандартная операция сужения интервала в двоичном поиске, **благодаря чему указатели $i$ и $j$ приближаются к `target`**. +- Когда `nums[m] == target` , это означает, что элементы меньше `target` находятся в интервале $[i, m - 1]$ , поэтому мы используем $j = m - 1$ для сужения интервала, **тем самым приближая указатель $j$ к элементам, меньшим `target`**. + +После завершения цикла указатель $i$ будет указывать на самый левый `target` , а указатель $j$ - на первый элемент, меньший `target` , **поэтому индекс $i$ и является точкой вставки**. + +=== "<1>" + ![Шаги поиска точки вставки для повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_step1.png) + +=== "<2>" + ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) + +=== "<3>" + ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) + +=== "<4>" + ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) + +=== "<5>" + ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) + +=== "<6>" + ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) + +=== "<7>" + ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) + +=== "<8>" + ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) + +Если посмотреть на следующий код, то видно, что операции в ветвях `nums[m] > target` и `nums[m] == target` совпадают, поэтому эти две ветви можно объединить. + +Даже в этом случае можно оставить условия развернутыми, потому что так логика выглядит более ясной и код легче читать. + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} +``` + +!!! tip + + Код в этом разделе записан в стиле "двойного замкнутого интервала". При желании можно самостоятельно реализовать вариант "слева закрыт, справа открыт". + +Если смотреть в целом, суть двоичного поиска сводится к тому, что для указателей $i$ и $j$ заранее задаются цели поиска; целью может быть конкретный элемент (например, `target` ), а может быть и диапазон элементов (например, элементы, меньшие `target` ). + +В ходе непрерывного двоичного деления указатели $i$ и $j$ постепенно приближаются к заранее заданной цели. В конце они либо успешно находят ответ, либо останавливаются после выхода за границы. diff --git a/ru/docs/chapter_searching/index.md b/ru/docs/chapter_searching/index.md new file mode 100644 index 000000000..41d043c08 --- /dev/null +++ b/ru/docs/chapter_searching/index.md @@ -0,0 +1,9 @@ +# Поиск + +![Поиск](../assets/covers/chapter_searching.jpg) + +!!! abstract + + Поиск - это приключение в неизвестность: иногда приходится пройти каждый уголок загадочного пространства, а иногда удается быстро зафиксировать цель. + + В этом путешествии каждый новый шаг может привести к ответу, которого мы не ожидали. diff --git a/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png new file mode 100644 index 000000000..eb9ef5735 Binary files /dev/null and b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png differ diff --git a/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png new file mode 100644 index 000000000..85f66af7b Binary files /dev/null and b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png differ diff --git a/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png new file mode 100644 index 000000000..673ca7dbf Binary files /dev/null and b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png differ diff --git a/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png new file mode 100644 index 000000000..039165ea7 Binary files /dev/null and b/ru/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png differ diff --git a/ru/docs/chapter_searching/replace_linear_by_hashing.md b/ru/docs/chapter_searching/replace_linear_by_hashing.md new file mode 100644 index 000000000..5597e233f --- /dev/null +++ b/ru/docs/chapter_searching/replace_linear_by_hashing.md @@ -0,0 +1,47 @@ +# Стратегии хеш-оптимизации + +В алгоритмических задачах **мы часто заменяем линейный поиск на хеш-поиск, чтобы уменьшить временную сложность алгоритма**. Разберем одну задачу, чтобы лучше понять этот прием. + +!!! question + + Дан массив целых чисел `nums` и целевой элемент `target` . Найдите в массиве два элемента, сумма которых равна `target` , и верните их индексы. Подойдет любой корректный ответ. + +## Линейный поиск: обмен времени на пространство + +Рассмотрим прямой перебор всех возможных комбинаций. Как показано на рисунке ниже, мы запускаем два вложенных цикла и на каждом шаге проверяем, равна ли сумма двух целых чисел `target` ; если да, то возвращаем их индексы. + +![Линейный поиск для задачи о двух суммах](replace_linear_by_hashing.assets/two_sum_brute_force.png) + +Код приведен ниже: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_brute_force} +``` + +Временная сложность этого метода равна $O(n^2)$ , а пространственная сложность равна $O(1)$ , поэтому на больших объемах данных он очень медленный. + +## Хеш-поиск: обмен пространства на время + +Рассмотрим вариант с использованием хеш-таблицы, где ключами и значениями будут элементы массива и их индексы. При циклическом обходе массива на каждом шаге выполняются действия, показанные на рисунке ниже. + +1. Проверить, находится ли число `target - nums[i]` в хеш-таблице; если да, то сразу вернуть индексы этих двух элементов. +2. Добавить в хеш-таблицу пару из ключа `nums[i]` и индекса `i` . + +=== "<1>" + ![Вспомогательная хеш-таблица для задачи о двух суммах](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + +=== "<2>" + ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) + +=== "<3>" + ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) + +Код реализации показан ниже, и для него достаточно одного цикла: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_hash_table} +``` + +Благодаря хеш-поиску этот метод снижает временную сложность с $O(n^2)$ до $O(n)$ , существенно повышая эффективность работы. + +Поскольку требуется поддерживать дополнительную хеш-таблицу, пространственная сложность составляет $O(n)$ . **Несмотря на это, в целом данный метод лучше сбалансирован по времени и памяти, поэтому именно он является оптимальным решением этой задачи**. diff --git a/ru/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png b/ru/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png new file mode 100644 index 000000000..21186ea95 Binary files /dev/null and b/ru/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png differ diff --git a/ru/docs/chapter_searching/searching_algorithm_revisited.md b/ru/docs/chapter_searching/searching_algorithm_revisited.md new file mode 100644 index 000000000..8fd5ef1c7 --- /dev/null +++ b/ru/docs/chapter_searching/searching_algorithm_revisited.md @@ -0,0 +1,84 @@ +# Переосмысление алгоритмов поиска + +Алгоритмы поиска (searching algorithm) используются для того, чтобы находить один или несколько элементов, удовлетворяющих определенным условиям, в структурах данных, таких как массивы, списки, деревья или графы. + +Алгоритмы поиска можно разделить на две категории по способу реализации. + +- **Поиск целевого элемента путем обхода структуры данных**, например обход массива, списка, дерева или графа. +- **Эффективный поиск элементов с использованием структуры организации данных или априорной информации**, например двоичный поиск, хеш-поиск и поиск в двоичном дереве поиска. + +Нетрудно заметить, что эти темы уже рассматривались в предыдущих главах, поэтому алгоритмы поиска нам уже знакомы. В этом разделе мы еще раз посмотрим на них, но уже более системно. + +## Полный перебор + +Полный перебор заключается в том, что мы обходим каждый элемент структуры данных, чтобы найти целевой элемент. + +- "Линейный поиск" применяется к линейным структурам данных, таким как массивы и списки. Он начинается с одного конца структуры данных и последовательно проверяет элементы, пока не найдет целевой элемент или пока не достигнет другого конца структуры данных. +- "Поиск в ширину" и "поиск в глубину" - это две стратегии обхода графов и деревьев. Поиск в ширину стартует из начального узла и исследует все узлы текущего уровня, прежде чем переходить к следующему. Поиск в глубину стартует из начального узла, проходит один путь до конца, затем возвращается назад и пробует другие пути, пока не будет полностью пройдена вся структура данных. + +Преимущество полного перебора состоит в его простоте и универсальности, **поскольку он не требует предварительной обработки данных и использования дополнительных структур данных**. + +Однако **временная сложность таких алгоритмов равна $O(n)$** , где $n$ - число элементов, поэтому при больших объемах данных их производительность невысока. + +## Адаптивный поиск + +Адаптивный поиск использует специфические свойства данных (например, упорядоченность), чтобы оптимизировать процесс поиска и тем самым эффективнее находить целевой элемент. + +- "Двоичный поиск" использует упорядоченность данных для эффективного поиска и применим только к массивам. +- "Хеш-поиск" использует хеш-таблицу для построения отображения между поисковыми данными и целевыми данными, благодаря чему запросы выполняются эффективно. +- "Поиск в дереве" ведется в конкретной древовидной структуре (например, в двоичном дереве поиска) и позволяет быстро отсекать узлы на основе сравнения значений, чтобы найти цель. + +Преимущество этих алгоритмов в высокой эффективности, **их временная сложность может достигать $O(\log n)$ и даже $O(1)$** . + +Однако **для использования таких алгоритмов обычно требуется предварительная обработка данных**. Например, для двоичного поиска нужно заранее отсортировать массив, а хеш-поиск и поиск в дереве требуют дополнительных структур данных, поддержание которых тоже отнимает время и память. + +!!! tip + + Адаптивные алгоритмы поиска часто называют алгоритмами поиска в узком смысле, **поскольку они в основном предназначены для быстрого поиска целевого элемента в конкретной структуре данных**. + +## Выбор метода поиска + +Для поиска целевого элемента в наборе данных размера $n$ можно использовать линейный поиск, двоичный поиск, поиск в дереве, хеш-поиск и другие методы. Принципы работы этих методов показаны на рисунке ниже. + +![Различные стратегии поиска](searching_algorithm_revisited.assets/searching_algorithms.png) + +Эффективность и особенности перечисленных методов приведены в таблице ниже. + +

Таблица   Сравнение эффективности алгоритмов поиска

+ +| | Линейный поиск | Двоичный поиск | Поиск в дереве | Хеш-поиск | +| ---------------------------- | -------------- | ------------------- | ------------------- | ------------------- | +| Поиск элемента | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | +| Вставка элемента | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| Удаление элемента | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| Дополнительное пространство | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | +| Предварительная обработка | / | Сортировка $O(n \log n)$ | Построение дерева $O(n \log n)$ | Построение хеш-таблицы $O(n)$ | +| Упорядоченность данных | Не требуется | Требуется | Требуется | Не требуется | + +Выбор алгоритма поиска также зависит от масштаба данных, требований к производительности поиска, а также частоты запросов и обновлений данных. + +**Линейный поиск** + +- Обладает хорошей универсальностью и не требует никакой предварительной обработки данных. Если нужно выполнить только один запрос, то время предварительной обработки для остальных трех методов окажется больше, чем время линейного поиска. +- Подходит для небольших объемов данных, потому что в этом случае влияние временной сложности на эффективность невелико. +- Подходит для сценариев с высокой частотой обновления данных, поскольку этот метод не требует никакого дополнительного обслуживания данных. + +**Двоичный поиск** + +- Подходит для больших наборов данных и демонстрирует стабильную эффективность; его худшая временная сложность равна $O(\log n)$ . +- Объем данных не должен быть слишком большим, потому что массив требует непрерывного участка памяти. +- Не подходит для сценариев с частыми вставками и удалениями данных, так как поддержание массива в отсортированном виде требует больших затрат. + +**Хеш-поиск** + +- Подходит для сценариев, в которых требования к скорости запросов очень высоки; средняя временная сложность равна $O(1)$ . +- Не подходит для сценариев, где требуется упорядоченность данных или поиск по диапазону, потому что хеш-таблица не умеет поддерживать порядок данных. +- Сильно зависит от хеш-функции и стратегии обработки коллизий, поэтому риск деградации производительности сравнительно велик. +- Не подходит для слишком больших объемов данных, так как хеш-таблице требуется дополнительное пространство, чтобы максимально снизить число коллизий и обеспечить хорошую производительность поиска. + +**Поиск в дереве** + +- Подходит для очень больших объемов данных, потому что узлы дерева распределены в памяти и не требуют непрерывного хранения. +- Подходит для сценариев, где нужно поддерживать упорядоченные данные или выполнять поиск по диапазону. +- В процессе постоянных вставок и удалений узлов двоичное дерево поиска может перекоситься, и тогда временная сложность деградирует до $O(n)$ . +- Если использовать AVL-дерево или красно-черное дерево, то все операции могут стабильно выполняться за $O(\log n)$ , но поддержание баланса дерева увеличивает дополнительные накладные расходы. diff --git a/ru/docs/chapter_searching/summary.md b/ru/docs/chapter_searching/summary.md new file mode 100644 index 000000000..fc0a8be4d --- /dev/null +++ b/ru/docs/chapter_searching/summary.md @@ -0,0 +1,10 @@ +# Резюме + +### Ключевые выводы + +- Двоичный поиск опирается на упорядоченность данных и выполняет поиск путем циклического сокращения интервала вдвое. Он требует упорядоченных входных данных и подходит только для массивов или структур данных, реализованных на их основе. +- Полный перебор находит данные путем обхода структуры данных. Линейный поиск подходит для массивов и списков, а поиск в ширину и поиск в глубину подходят для графов и деревьев. Эти алгоритмы универсальны и не требуют предварительной обработки данных, но их временная сложность $O(n)$ сравнительно велика. +- Хеш-поиск, поиск в дереве и двоичный поиск относятся к эффективным методам поиска и позволяют быстро находить целевой элемент в конкретных структурах данных. Такие алгоритмы обладают высокой эффективностью, их временная сложность может достигать $O(\log n)$ и даже $O(1)$ , но обычно им нужны дополнительные структуры данных. +- На практике нужно анализировать размер данных, требования к производительности поиска, а также частоту запросов и обновлений данных, чтобы выбрать подходящий метод поиска. +- Линейный поиск подходит для небольших или часто обновляемых наборов данных; двоичный поиск - для больших отсортированных данных; хеш-поиск - для сценариев с высокими требованиями к скорости запросов и без необходимости поиска по диапазону; поиск в дереве - для больших динамических данных, где нужно поддерживать порядок и выполнять диапазонные запросы. +- Замена линейного поиска на хеш-поиск - это распространенная стратегия ускорения, которая позволяет снизить временную сложность с $O(n)$ до $O(1)$ . diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png new file mode 100644 index 000000000..2d5d9adfc Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png new file mode 100644 index 000000000..039a8b8e0 Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png new file mode 100644 index 000000000..a53e5f0d3 Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png new file mode 100644 index 000000000..ce7418fd9 Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png new file mode 100644 index 000000000..157700015 Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png new file mode 100644 index 000000000..6437d3d63 Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png new file mode 100644 index 000000000..e1aa85a1d Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png new file mode 100644 index 000000000..0041701fb Binary files /dev/null and b/ru/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/bubble_sort.md b/ru/docs/chapter_sorting/bubble_sort.md new file mode 100644 index 000000000..d737997f2 --- /dev/null +++ b/ru/docs/chapter_sorting/bubble_sort.md @@ -0,0 +1,59 @@ +# Сортировка пузырьком + +Сортировка пузырьком (bubble sort) сортирует массив за счет непрерывного сравнения и обмена соседних элементов. Этот процесс напоминает всплытие пузырьков снизу вверх, откуда и произошло название алгоритма. + +Как показано на рисунке ниже, процесс "всплытия" можно смоделировать через операцию обмена элементов: начиная от левого края массива и двигаясь вправо, мы последовательно сравниваем соседние элементы и, если "левый элемент > правый элемент", меняем их местами. После завершения прохода максимальный элемент будет перемещен в самый правый конец массива. + +=== "<1>" + ![Моделирование пузырька через обмен элементов](bubble_sort.assets/bubble_operation_step1.png) + +=== "<2>" + ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) + +=== "<3>" + ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) + +=== "<4>" + ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) + +=== "<5>" + ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) + +=== "<6>" + ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) + +=== "<7>" + ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) + +## Алгоритм + +Пусть длина массива равна $n$ ; тогда шаги сортировки пузырьком показаны на рисунке ниже. + +1. Сначала выполнить один проход "всплытия" по $n$ элементам, **переместив максимальный элемент массива на правильную позицию**. +2. Затем выполнить "всплытие" по оставшимся $n - 1$ элементам, **переместив второй по величине элемент на правильную позицию**. +3. Продолжать по аналогии; после $n - 1$ раундов "всплытия" **первые $n - 1$ по величине элементы окажутся на правильных позициях**. +4. Оставшийся единственный элемент обязательно является минимальным, сортировать его уже не нужно, поэтому сортировка завершена. + +![Процесс сортировки пузырьком](bubble_sort.assets/bubble_sort_overview.png) + +Пример кода: + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort} +``` + +## Оптимизация эффективности + +Мы замечаем, что если в каком-либо раунде "всплытия" не произошло ни одного обмена, значит, массив уже отсортирован и можно сразу вернуть результат. Поэтому можно добавить флаг `flag` для отслеживания этой ситуации и немедленного выхода. + +После такой оптимизации худшая и средняя временные сложности сортировки пузырьком по-прежнему равны $O(n^2)$ ; однако если входной массив уже полностью упорядочен, достигается лучшая временная сложность $O(n)$ . + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n^2)$, алгоритм адаптивен**: длины диапазонов, проходящих "всплытие" в разных раундах, последовательно равны $n - 1$, $n - 2$, $\dots$, $2$, $1$ , а их сумма равна $(n - 1) n / 2$ . После добавления оптимизации с `flag` лучшая временная сложность может достигать $O(n)$ . +- **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: указатели $i$ и $j$ используют константный объем дополнительной памяти. +- **Стабильная сортировка**: поскольку при "всплытии" равные элементы не обмениваются местами. diff --git a/ru/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png b/ru/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png new file mode 100644 index 000000000..202eda7aa Binary files /dev/null and b/ru/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png b/ru/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png new file mode 100644 index 000000000..fd73a63e2 Binary files /dev/null and b/ru/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png differ diff --git a/ru/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png b/ru/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png new file mode 100644 index 000000000..19c7ca677 Binary files /dev/null and b/ru/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png differ diff --git a/ru/docs/chapter_sorting/bucket_sort.md b/ru/docs/chapter_sorting/bucket_sort.md new file mode 100644 index 000000000..56f571ded --- /dev/null +++ b/ru/docs/chapter_sorting/bucket_sort.md @@ -0,0 +1,45 @@ +# Блочная сортировка + +Рассмотренные выше алгоритмы сортировки относятся к "сортировкам на основе сравнений": они упорядочивают данные, сравнивая элементы друг с другом. Временная сложность таких алгоритмов не может быть лучше $O(n \log n)$ . Далее мы рассмотрим несколько "сортировок без сравнений", чья временная сложность может достигать линейного порядка. + +Блочная сортировка (bucket sort) является типичным применением стратегии "разделяй и властвуй". Она задает несколько упорядоченных по диапазонам блоков, каждый блок соответствует некоторому диапазону значений; затем данные равномерно распределяются по блокам, внутри каждого блока выполняется сортировка, а в конце результаты блоков объединяются по порядку. + +## Алгоритм + +Рассмотрим массив длины $n$, элементы которого являются числами с плавающей запятой из диапазона $[0, 1)$ . Процесс блочной сортировки показан на рисунке ниже. + +1. Инициализировать $k$ блоков и распределить $n$ элементов по этим $k$ блокам. +2. Отсортировать каждый блок по отдельности (здесь используется встроенная функция сортировки языка программирования). +3. Объединить результаты в порядке следования блоков от меньшего к большему. + +![Процесс блочной сортировки](bucket_sort.assets/bucket_sort_overview.png) + +Код приведен ниже: + +```src +[file]{bucket_sort}-[class]{}-[func]{bucket_sort} +``` + +## Характеристики алгоритма + +Блочная сортировка подходит для обработки очень больших объемов данных. Например, если вход содержит 1 миллион элементов и из-за ограничений памяти система не может загрузить их все сразу, можно разбить данные на 1000 блоков, отсортировать каждый блок отдельно, а затем объединить результаты. + +- **Временная сложность равна $O(n + k)$** : если элементы распределены по блокам равномерно, то в каждом блоке будет $\frac{n}{k}$ элементов. Если сортировка одного блока требует $O(\frac{n}{k} \log\frac{n}{k})$ времени, то сортировка всех блоков потребует $O(n \log\frac{n}{k})$ времени. **Когда число блоков $k$ достаточно велико, временная сложность приближается к $O(n)$** . На объединение результатов требуется $O(n + k)$ времени, потому что нужно пройти по всем блокам и элементам. В худшем случае все данные попадут в один блок, и если сортировка этого блока использует $O(n^2)$ времени, общая сложность также станет $O(n^2)$ . +- **Пространственная сложность равна $O(n + k)$, сортировка не выполняется на месте**: требуются дополнительные блоки в количестве $k$ и место для всех $n$ элементов внутри них. +- Является ли блочная сортировка стабильной, зависит от того, стабилен ли алгоритм сортировки внутри каждого блока. + +## Как добиться равномерного распределения + +Теоретически временная сложность блочной сортировки может достигать $O(n)$ ; **ключ к этому - как можно более равномерно распределить элементы по блокам**. На практике данные часто распределены неравномерно. Например, если нужно распределить все товары на Taobao по 10 блокам цен, количество товаров дешевле 100 юаней может быть очень большим, а товаров дороже 1000 юаней - очень маленьким. Если просто разбить диапазон цен на 10 равных частей, число товаров в каждом блоке будет сильно различаться. + +Чтобы добиться более равномерного распределения, можно сначала задать грубую линию раздела и приблизительно распределить данные по 3 блокам. **После этого блоки с большим числом товаров можно снова делить на 3 блока и продолжать процесс до тех пор, пока число элементов в каждом блоке не станет примерно одинаковым**. + +Как показано на рисунке ниже, по сути этот метод строит рекурсивное дерево, цель которого - сделать значения в листьях как можно более равномерными. Конечно, совсем не обязательно каждый раз делить данные именно на 3 блока; конкретную схему разбиения можно выбирать в зависимости от свойств данных. + +![Рекурсивное разбиение по блокам](bucket_sort.assets/scatter_in_buckets_recursively.png) + +Если нам заранее известна вероятностная модель распределения цен товаров, **то границы каждого блока можно задавать в соответствии с этим распределением**. Важно отметить, что фактическое распределение данных не обязательно специально измерять - его можно приблизить некоторой вероятностной моделью исходя из свойств данных. + +Как показано на рисунке ниже, если предположить, что цены товаров подчиняются нормальному распределению, то можно разумно задать интервалы цен и тем самым распределить товары по блокам достаточно равномерно. + +![Разбиение блоков по вероятностному распределению](bucket_sort.assets/scatter_in_buckets_distribution.png) diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png new file mode 100644 index 000000000..4257881e4 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png new file mode 100644 index 000000000..8a2c68ac8 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png new file mode 100644 index 000000000..31ba8cbd3 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png new file mode 100644 index 000000000..cf8d61e33 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png new file mode 100644 index 000000000..f24bf64bb Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png new file mode 100644 index 000000000..731071ee8 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png new file mode 100644 index 000000000..a5564c721 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png new file mode 100644 index 000000000..7f2febf23 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png new file mode 100644 index 000000000..350e043a8 Binary files /dev/null and b/ru/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png differ diff --git a/ru/docs/chapter_sorting/counting_sort.md b/ru/docs/chapter_sorting/counting_sort.md new file mode 100644 index 000000000..4d3a46106 --- /dev/null +++ b/ru/docs/chapter_sorting/counting_sort.md @@ -0,0 +1,84 @@ +# Сортировка подсчетом + +Сортировка подсчетом (counting sort) реализует сортировку за счет подсчета количества элементов и обычно используется для массивов целых чисел. + +## Простая реализация + +Сначала рассмотрим простой пример. Дан массив `nums` длины $n$ , элементы которого являются "неотрицательными целыми числами". Общий процесс сортировки подсчетом показан на рисунке ниже. + +1. Пройти по массиву, найти в нем максимальное число, обозначить его как $m$ , а затем создать вспомогательный массив `counter` длины $m + 1$ . +2. **С помощью `counter` подсчитать, сколько раз каждое число встречается в `nums`**; при этом `counter[num]` хранит число вхождений значения `num` . Делается это просто: достаточно пройти по `nums` (пусть текущее число равно `num` ) и на каждом шаге увеличить `counter[num]` на $1$ . +3. **Поскольку индексы массива `counter` изначально упорядочены, можно считать, что все числа уже отсортированы**. Далее остается пройти по `counter` и в соответствии с числом вхождений записать значения обратно в `nums` в порядке возрастания. + +![Процесс сортировки подсчетом](counting_sort.assets/counting_sort_overview.png) + +Код приведен ниже: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort_naive} +``` + +!!! note "Связь между сортировкой подсчетом и блочной сортировкой" + + Если посмотреть на сортировку подсчетом с точки зрения блочной сортировки, то каждый индекс массива `counter` можно рассматривать как отдельный блок, а процесс подсчета - как распределение элементов по соответствующим блокам. По сути, сортировка подсчетом является частным случаем блочной сортировки для целочисленных данных. + +## Полная реализация + +Внимательный читатель мог заметить, что **если входные данные представлены объектами, то описанный выше шаг `3.` перестает работать**. Например, если входными данными являются объекты товаров и мы хотим отсортировать их по цене (полю класса), то описанный алгоритм сможет выдать только отсортированный ряд цен, но не исходные объекты в нужном порядке. + +Как же получить корректный порядок исходных данных? Сначала вычислим "префиксную сумму" массива `counter` . Как следует из названия, префиксная сумма в индексе `i` , обозначаемая как `prefix[i]` , равна сумме первых `i` элементов массива: + +$$ +\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} +$$ + +**Префиксная сумма имеет четкий смысл: `prefix[num] - 1` обозначает индекс последнего вхождения элемента `num` в результирующем массиве `res`**. Это очень важная информация, потому что она указывает, в какую позицию результирующего массива должен попасть каждый элемент. Далее мы проходим исходный массив `nums` в обратном порядке и на каждой итерации для очередного элемента `num` выполняем два действия. + +1. Записать `num` в массив `res` по индексу `prefix[num] - 1` . +2. Уменьшить префиксную сумму `prefix[num]` на $1$ , чтобы получить индекс следующего размещения элемента `num` . + +После завершения прохода массив `res` будет содержать отсортированный результат; остается только переписать `res` обратно в `nums` . Полный процесс сортировки подсчетом показан на рисунке ниже. + +=== "<1>" + ![Шаги сортировки подсчетом](counting_sort.assets/counting_sort_step1.png) + +=== "<2>" + ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) + +=== "<3>" + ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) + +=== "<4>" + ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) + +=== "<5>" + ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) + +=== "<6>" + ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) + +=== "<7>" + ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) + +=== "<8>" + ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) + +Код реализации сортировки подсчетом приведен ниже: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n + m)$, алгоритм не является адаптивным** : необходимо пройти по `nums` и по `counter` , а оба этих прохода занимают линейное время. Обычно выполняется $n \gg m$ , поэтому временная сложность стремится к $O(n)$ . +- **Пространственная сложность равна $O(n + m)$, сортировка не выполняется на месте**: используются массивы `res` и `counter` длины $n$ и $m$ соответственно. +- **Стабильная сортировка**: порядок заполнения `res` идет "справа налево", поэтому обратный проход по `nums` позволяет сохранить относительный порядок равных элементов и тем самым реализовать стабильную сортировку. Вообще говоря, прямой проход по `nums` тоже даст правильный результат сортировки, но он будет нестабильным. + +## Ограничения + +На этом этапе сортировка подсчетом может показаться очень изящной: она позволяет эффективно сортировать данные, опираясь только на подсчет числа вхождений. Однако условия ее применения довольно строгие. + +**Сортировка подсчетом применима только к неотрицательным целым числам**. Чтобы использовать ее для других типов данных, нужно убедиться, что эти данные можно преобразовать в неотрицательные целые числа и что при преобразовании относительный порядок элементов не изменится. Например, для массива целых чисел с отрицательными значениями можно сначала прибавить ко всем числам константу, превратив их в положительные, затем выполнить сортировку и после этого преобразовать значения обратно. + +**Сортировка подсчетом подходит для случаев, когда объем данных велик, но диапазон значений невелик**. Например, в приведенном выше примере $m$ не должно быть слишком большим, иначе будет занято слишком много памяти. А когда $n \ll m$ , сортировка подсчетом использует $O(m)$ времени и может оказаться медленнее, чем алгоритмы сортировки с $O(n \log n)$ . diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png new file mode 100644 index 000000000..a8b8c1432 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png new file mode 100644 index 000000000..8db11fe5f Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png new file mode 100644 index 000000000..9bf3f2926 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png new file mode 100644 index 000000000..6b6e134f2 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png new file mode 100644 index 000000000..55690c269 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png new file mode 100644 index 000000000..9b449611b Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png new file mode 100644 index 000000000..5c2af0a87 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png new file mode 100644 index 000000000..ac0e1a1c1 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png new file mode 100644 index 000000000..95bcfa3b6 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png new file mode 100644 index 000000000..df1b5997b Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png new file mode 100644 index 000000000..2e7113d22 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png new file mode 100644 index 000000000..a5a697065 Binary files /dev/null and b/ru/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png differ diff --git a/ru/docs/chapter_sorting/heap_sort.md b/ru/docs/chapter_sorting/heap_sort.md new file mode 100644 index 000000000..b7434acee --- /dev/null +++ b/ru/docs/chapter_sorting/heap_sort.md @@ -0,0 +1,73 @@ +# Пирамидальная сортировка + +!!! tip + + Перед чтением этого раздела убедитесь, что вы уже изучили главу "Куча". + +Пирамидальная сортировка (heap sort) - это эффективный алгоритм сортировки, основанный на структуре данных "куча". Для его реализации можно использовать уже изученные нами "построение кучи" и "извлечение элементов из кучи". + +1. Подать на вход массив и построить из него мин-кучу; в этот момент минимальный элемент будет находиться в вершине кучи. +2. Непрерывно выполнять извлечение из кучи и по порядку записывать извлеченные элементы - так получится последовательность, отсортированная по возрастанию. + +Хотя этот метод работоспособен, он требует дополнительного массива для хранения извлеченных элементов и потому расходует лишнюю память. На практике обычно используют более изящную реализацию. + +## Алгоритм + +Пусть длина массива равна $n$ ; тогда процесс пирамидальной сортировки показан на рисунке ниже. + +1. Подать на вход массив и построить из него макс-кучу. После этого максимальный элемент окажется в вершине кучи. +2. Обменять элемент в вершине кучи (первый элемент) с элементом внизу кучи (последний элемент). После обмена длина кучи уменьшается на $1$ , а число уже отсортированных элементов увеличивается на $1$ . +3. Начиная с вершины, выполнить просеивание вниз (sift down) сверху вниз. После этого свойство кучи будет восстановлено. +4. Циклически повторять шаг `2.` и шаг `3.` . После $n - 1$ раундов массив будет полностью отсортирован. + +!!! tip + + На самом деле операция извлечения из кучи тоже включает шаг `2.` и шаг `3.` , только дополнительно содержит действие по удалению элемента. + +=== "<1>" + ![Шаги пирамидальной сортировки](heap_sort.assets/heap_sort_step1.png) + +=== "<2>" + ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) + +=== "<3>" + ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) + +=== "<4>" + ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) + +=== "<5>" + ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) + +=== "<6>" + ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) + +=== "<7>" + ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) + +=== "<8>" + ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) + +=== "<9>" + ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) + +=== "<10>" + ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) + +=== "<11>" + ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) + +=== "<12>" + ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) + +В реализации кода используется та же функция просеивания сверху вниз `sift_down()`, что и в главе "Куча". Важно помнить, что длина кучи уменьшается по мере извлечения максимального элемента, поэтому функции `sift_down()` нужно передавать параметр длины $n$ , чтобы указать текущую эффективную длину кучи. Код приведен ниже: + +```src +[file]{heap_sort}-[class]{}-[func]{heap_sort} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n \log n)$, алгоритм не является адаптивным**: построение кучи занимает $O(n)$ времени. Извлечение максимального элемента из кучи имеет временную сложность $O(\log n)$ и выполняется $n - 1$ раз. +- **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: несколько переменных-указателей используют $O(1)$ памяти. Обмен элементов и операции просеивания выполняются прямо в исходном массиве. +- **Нестабильная сортировка**: при обмене вершины кучи и нижнего элемента относительный порядок равных элементов может измениться. diff --git a/ru/docs/chapter_sorting/index.md b/ru/docs/chapter_sorting/index.md new file mode 100644 index 000000000..9db36262e --- /dev/null +++ b/ru/docs/chapter_sorting/index.md @@ -0,0 +1,9 @@ +# Сортировка + +![Сортировка](../assets/covers/chapter_sorting.jpg) + +!!! abstract + + Сортировка упорядочивает хаотичные данные и позволяет быстрее находить закономерности. + + За кажущейся простотой скрывается целая группа алгоритмов с разными достоинствами и ограничениями. diff --git a/ru/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png b/ru/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png new file mode 100644 index 000000000..f3337a49a Binary files /dev/null and b/ru/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png differ diff --git a/ru/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png b/ru/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png new file mode 100644 index 000000000..c0e3b7c8c Binary files /dev/null and b/ru/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/insertion_sort.md b/ru/docs/chapter_sorting/insertion_sort.md new file mode 100644 index 000000000..b59606919 --- /dev/null +++ b/ru/docs/chapter_sorting/insertion_sort.md @@ -0,0 +1,46 @@ +# Сортировка вставками + +Сортировка вставками (insertion sort) - это простой алгоритм сортировки, принцип которого очень похож на ручное упорядочивание колоды карт. + +Точнее говоря, в неотсортированном диапазоне выбирается опорный элемент, после чего он поочередно сравнивается с элементами слева в уже отсортированном диапазоне и вставляется в правильную позицию. + +На рисунке ниже показан процесс вставки элемента в массив. Пусть опорный элемент обозначен как `base` ; нам нужно сдвинуть все элементы от целевого индекса до `base` на одну позицию вправо, а затем присвоить `base` значение в целевом индексе. + +![Одна операция вставки](insertion_sort.assets/insertion_operation.png) + +## Алгоритм + +Общий процесс сортировки вставками показан на рисунке ниже. + +1. В начальном состоянии отсортирован только первый элемент массива. +2. Выбрать второй элемент массива как `base` ; после вставки в правильную позицию **первые 2 элемента массива окажутся отсортированными**. +3. Выбрать третий элемент как `base` ; после вставки в правильную позицию **первые 3 элемента массива окажутся отсортированными**. +4. Продолжать по аналогии; в последнем раунде в качестве `base` берется последний элемент, и после его вставки **все элементы массива будут отсортированы**. + +![Процесс сортировки вставками](insertion_sort.assets/insertion_sort_overview.png) + +Пример кода: + +```src +[file]{insertion_sort}-[class]{}-[func]{insertion_sort} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n^2)$, алгоритм адаптивен**: в худшем случае каждой операции вставки требуется соответственно $n - 1$, $n-2$, $\dots$, $2$, $1$ итераций, а их сумма равна $(n - 1) n / 2$ , поэтому временная сложность равна $O(n^2)$ . Если входные данные уже упорядочены, операция вставки завершается раньше. Когда входной массив полностью отсортирован, сортировка вставками достигает лучшей временной сложности $O(n)$ . +- **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: указатели $i$ и $j$ используют константный объем дополнительной памяти. +- **Стабильная сортировка**: в процессе вставки элементы помещаются справа от равных им элементов, поэтому их относительный порядок не меняется. + +## Преимущества сортировки вставками + +Временная сложность сортировки вставками равна $O(n^2)$ , а у быстрой сортировки, которую мы скоро изучим, временная сложность равна $O(n \log n)$ . Несмотря на более высокую асимптотическую сложность, **на малых объемах данных сортировка вставками обычно работает быстрее**. + +Этот вывод похож на сравнение линейного и двоичного поиска. Алгоритмы уровня $O(n \log n)$ , такие как быстрая сортировка, относятся к алгоритмам на основе стратегии "разделяй и властвуй" и обычно включают больше элементарных вычислений. Когда объем данных мал, значения $n^2$ и $n \log n$ близки друг к другу, поэтому асимптотика не доминирует, а решающим становится число элементарных операций в каждом раунде. + +На практике встроенные функции сортировки во многих языках программирования (например, в Java) используют сортировку вставками. Общая идея такова: для длинных массивов применять алгоритмы сортировки на основе стратегии "разделяй и властвуй", например быструю сортировку; для коротких массивов сразу использовать сортировку вставками. + +Хотя сортировка пузырьком, выбором и вставками имеют одинаковую временную сложность $O(n^2)$ , в реальных задачах **сортировка вставками используется заметно чаще, чем сортировка пузырьком и сортировка выбором**. Основные причины таковы. + +- Сортировка пузырьком основана на обмене элементов, для чего нужна временная переменная и суммарно выполняются 3 элементарные операции; сортировка вставками основана на присваивании элементов и требует всего 1 элементарной операции. Поэтому **вычислительные затраты сортировки пузырьком обычно выше, чем у сортировки вставками**. +- Временная сложность сортировки выбором в любом случае равна $O(n^2)$ . **Если входные данные уже частично упорядочены, сортировка вставками обычно эффективнее сортировки выбором**. +- Сортировка выбором нестабильна, поэтому ее нельзя использовать для многоуровневой сортировки. diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png new file mode 100644 index 000000000..748486e8d Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png new file mode 100644 index 000000000..acf2f8fb0 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png new file mode 100644 index 000000000..1737047ef Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png new file mode 100644 index 000000000..9ef0f8736 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png new file mode 100644 index 000000000..fc17e8d34 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png new file mode 100644 index 000000000..d20106e29 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png new file mode 100644 index 000000000..21743d9b5 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png new file mode 100644 index 000000000..8e88eaaac Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png new file mode 100644 index 000000000..496fbf804 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png new file mode 100644 index 000000000..9f4be45c4 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png new file mode 100644 index 000000000..33588ab87 Binary files /dev/null and b/ru/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png differ diff --git a/ru/docs/chapter_sorting/merge_sort.md b/ru/docs/chapter_sorting/merge_sort.md new file mode 100644 index 000000000..65b11d5ac --- /dev/null +++ b/ru/docs/chapter_sorting/merge_sort.md @@ -0,0 +1,73 @@ +# Сортировка слиянием + +Сортировка слиянием (merge sort) - это алгоритм сортировки на основе стратегии "разделяй и властвуй", который включает этапы "разделения" и "слияния", показанные на рисунке ниже. + +1. **Этап разделения**: массив рекурсивно разбивается от середины, и задача сортировки длинного массива превращается в задачи сортировки более коротких массивов. +2. **Этап слияния**: когда длина подмассива становится равной 1, разделение завершается и начинается слияние; левые и правые короткие упорядоченные массивы непрерывно объединяются в более длинный упорядоченный массив, пока процесс не завершится. + +![Этапы разделения и слияния в сортировке слиянием](merge_sort.assets/merge_sort_overview.png) + +## Алгоритм + +Как показано на рисунке ниже, на этапе "разделения" массив рекурсивно разбивается сверху вниз по середине на два подмассива. + +1. Вычислить середину массива `mid` и рекурсивно разделить левый подмассив (интервал `[left, mid]` ) и правый подмассив (интервал `[mid + 1, right]` ). +2. Рекурсивно повторять шаг `1.` , пока длина подмассива не станет равной 1. + +Этап "слияния" снизу вверх объединяет левый и правый подмассивы в один упорядоченный массив. Следует заметить, что начиная с подмассивов длины 1, каждый подмассив в фазе слияния уже является упорядоченным. + +=== "<1>" + ![Шаги сортировки слиянием](merge_sort.assets/merge_sort_step1.png) + +=== "<2>" + ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) + +=== "<3>" + ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) + +=== "<4>" + ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) + +=== "<5>" + ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) + +=== "<6>" + ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) + +=== "<7>" + ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) + +=== "<8>" + ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) + +=== "<9>" + ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) + +=== "<10>" + ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) + +Нетрудно заметить, что порядок рекурсии в сортировке слиянием совпадает с порядком рекурсии при постфиксном обходе бинарного дерева. + +- **Постфиксный обход**: сначала рекурсивно обходится левое поддерево, затем правое поддерево, а в конце обрабатывается корневой узел. +- **Сортировка слиянием**: сначала рекурсивно обрабатывается левый подмассив, затем правый подмассив, а в конце выполняется слияние. + +Реализация сортировки слиянием показана в коде ниже. Обратите внимание: в `nums` объединяемый интервал равен `[left, right]` , а соответствующий интервал в `tmp` равен `[0, right - left]` . + +```src +[file]{merge_sort}-[class]{}-[func]{merge_sort} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n \log n)$, алгоритм не является адаптивным**: этап разделения создает дерево рекурсии высоты $\log n$ , а суммарное число операций слияния на каждом уровне равно $n$ , поэтому общая временная сложность составляет $O(n \log n)$ . +- **Пространственная сложность равна $O(n)$, сортировка не выполняется на месте**: глубина рекурсии равна $\log n$ , из-за чего требуется $O(\log n)$ памяти под стек вызовов. Для этапа слияния нужен вспомогательный массив, поэтому дополнительно используется $O(n)$ памяти. +- **Стабильная сортировка**: в процессе слияния относительный порядок равных элементов не меняется. + +## Сортировка связного списка + +Для связных списков сортировка слиянием имеет заметное преимущество перед другими алгоритмами сортировки: **пространственную сложность задачи сортировки списка можно оптимизировать до $O(1)$**. + +- **Этап разделения**: работу по разбиению списка можно реализовать с помощью "итерации" вместо "рекурсии", тем самым устранив расход памяти на стек вызовов. +- **Этап слияния**: в связном списке добавление и удаление узлов требует только изменения ссылок (указателей), поэтому при слиянии двух коротких упорядоченных списков в один длинный упорядоченный список не нужно создавать дополнительный список. + +Детали реализации достаточно сложны; заинтересованные читатели могут изучить соответствующие материалы самостоятельно. diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png new file mode 100644 index 000000000..fe106fcde Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png new file mode 100644 index 000000000..23c9821b5 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png new file mode 100644 index 000000000..06676dd31 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png new file mode 100644 index 000000000..28d829ef7 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png new file mode 100644 index 000000000..b6e206d4f Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png new file mode 100644 index 000000000..d8855e6d6 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png new file mode 100644 index 000000000..823139d90 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png new file mode 100644 index 000000000..70065c795 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png new file mode 100644 index 000000000..95b6e6233 Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png b/ru/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png new file mode 100644 index 000000000..66f1a5dec Binary files /dev/null and b/ru/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/quick_sort.md b/ru/docs/chapter_sorting/quick_sort.md new file mode 100644 index 000000000..401f98139 --- /dev/null +++ b/ru/docs/chapter_sorting/quick_sort.md @@ -0,0 +1,100 @@ +# Быстрая сортировка + +Быстрая сортировка (quick sort) - это алгоритм сортировки, основанный на стратегии "разделяй и властвуй"; он работает эффективно и применяется очень широко. + +Ключевая операция быстрой сортировки - это "разделение с опорным элементом". Ее цель такова: выбрать некоторый элемент массива в качестве "опорного" и переместить все элементы меньше опорного влево от него, а все элементы больше опорного - вправо. Конкретный процесс показан на рисунке ниже. + +1. Выбрать самый левый элемент массива как опорный и инициализировать два указателя `i` и `j` , направленные на левую и правую границы массива. +2. Запустить цикл, в котором `i` и `j` ищут соответственно первый элемент, больший опорного, и первый элемент, меньший опорного, после чего эти два элемента меняются местами. +3. Повторять шаг `2.` , пока указатели `i` и `j` не встретятся, а затем обменять опорный элемент с элементом на границе двух подмассивов. + +=== "<1>" + ![Шаги разделения с опорным элементом](quick_sort.assets/pivot_division_step1.png) + +=== "<2>" + ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) + +=== "<3>" + ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) + +=== "<4>" + ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) + +=== "<5>" + ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) + +=== "<6>" + ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) + +=== "<7>" + ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) + +=== "<8>" + ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) + +=== "<9>" + ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) + +После завершения разделения исходный массив разбивается на три части: левый подмассив, опорный элемент и правый подмассив; при этом выполняется условие "любой элемент левого подмассива $\leq$ опорный элемент $\leq$ любой элемент правого подмассива". Следовательно, далее нам нужно лишь отсортировать эти два подмассива. + +!!! note "Стратегия divide and conquer в быстрой сортировке" + + По сути, разделение с опорным элементом сводит задачу сортировки длинного массива к двум задачам сортировки более коротких массивов. + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{partition} +``` + +## Алгоритм + +Общий процесс быстрой сортировки показан на рисунке ниже. + +1. Сначала выполнить "разделение с опорным элементом" для исходного массива и получить неотсортированные левый и правый подмассивы. +2. Затем рекурсивно выполнить "разделение с опорным элементом" для левого и правого подмассивов. +3. Продолжать рекурсию до тех пор, пока длина подмассива не станет равной 1; после этого сортировка всего массива будет завершена. + +![Процесс быстрой сортировки](quick_sort.assets/quick_sort_overview.png) + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n \log n)$, алгоритм не является адаптивным**: в среднем глубина рекурсии при разделении равна $\log n$ , а суммарное число циклов на каждом уровне равно $n$ , поэтому общая сложность составляет $O(n \log n)$ . В худшем случае каждое разделение делит массив длины $n$ на подмассивы длины $0$ и $n - 1$ ; тогда глубина рекурсии достигает $n$ , на каждом уровне выполняется $n$ операций, и общая временная сложность вырождается в $O(n^2)$ . +- **Пространственная сложность равна $O(n)$, сортировка выполняется на месте**: если входной массив полностью отсортирован в обратном порядке, глубина рекурсии достигает худшего случая $n$ , что требует $O(n)$ памяти под стек вызовов. При этом сама сортировка выполняется в исходном массиве без дополнительного массива. +- **Нестабильная сортировка**: на последнем шаге разделения опорный элемент может быть обменян вправо от равного ему элемента. + +## Почему быстрая сортировка быстрая + +Уже по названию понятно, что быстрая сортировка должна иметь преимущества по эффективности. Хотя ее средняя временная сложность совпадает со сложностью "сортировки слиянием" и "пирамидальной сортировки", на практике быстрая сортировка обычно работает быстрее. Основные причины таковы. + +- **Вероятность худшего случая очень мала**: хотя худшая временная сложность быстрой сортировки равна $O(n^2)$ и она не так стабильна, как сортировка слиянием, в подавляющем большинстве случаев она работает за $O(n \log n)$ . +- **Высокая эффективность использования кэша**: при выполнении разделения система может загрузить весь подмассив в кэш, поэтому доступ к элементам оказывается быстрым. Алгоритмы вроде "пирамидальной сортировки" требуют скачкообразного доступа к элементам и таким свойством не обладают. +- **Небольшой константный множитель в сложности**: среди трех перечисленных алгоритмов у быстрой сортировки обычно меньше всего сравнений, присваиваний и обменов. Это похоже на причину, по которой "сортировка вставками" часто быстрее "сортировки пузырьком". + +## Оптимизация выбора опорного элемента + +**На некоторых входных данных временная эффективность быстрой сортировки может ухудшаться**. Рассмотрим крайний случай: входной массив полностью отсортирован в обратном порядке. Поскольку в качестве опорного мы выбираем самый левый элемент, после разделения он будет обменян в самый правый конец массива, из-за чего длина левого подмассива станет $n - 1$ , а длина правого - $0$ . Если рекурсия будет продолжаться таким образом, то после каждого разделения один из подмассивов будет иметь длину $0$ , стратегия divide and conquer потеряет смысл, а быстрая сортировка выродится в нечто близкое к "сортировке пузырьком". + +Чтобы по возможности избежать такого сценария, **мы можем улучшить стратегию выбора опорного элемента в процедуре разделения**. Например, можно выбирать случайный элемент массива как опорный. Однако если не повезет и каждый раз будет выбираться неудачный опорный элемент, производительность все равно останется неудовлетворительной. + +Нужно учитывать, что языки программирования обычно генерируют "псевдослучайные числа". Если специально построить тестовый пример под такую последовательность, эффективность быстрой сортировки все равно может деградировать. + +Чтобы улучшить ситуацию, можно взять три кандидата (обычно первый, последний и средний элементы массива) и **использовать медиану этих трех значений как опорный элемент**. Благодаря этому вероятность того, что опорный элемент окажется "не слишком маленьким и не слишком большим", заметно возрастает. Конечно, можно брать и большее число кандидатов, чтобы еще сильнее повысить устойчивость алгоритма. После этого вероятность деградации временной сложности до $O(n^2)$ существенно уменьшается. + +Пример кода: + +```src +[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} +``` + +## Оптимизация глубины рекурсии + +**На некоторых входных данных быстрая сортировка может занимать слишком много памяти**. Рассмотрим полностью отсортированный входной массив. Пусть длина текущего подмассива в рекурсии равна $m$ ; тогда после каждого разделения будут получаться левый подмассив длины $0$ и правый подмассив длины $m - 1$ . Это означает, что на каждом уровне размер задачи уменьшается совсем немного (лишь на один элемент), а высота дерева рекурсии достигает $n - 1$ , поэтому требуется $O(n)$ памяти под стек вызовов. + +Чтобы избежать накопления стековых кадров, после каждого разделения можно сравнивать длины двух подмассивов и **рекурсивно обрабатывать только более короткий из них**. Поскольку длина короткого подмассива не превысит $n / 2$ , такой подход гарантирует, что глубина рекурсии не превысит $\log n$ , а худшая пространственная сложность будет оптимизирована до $O(\log n)$ . Код приведен ниже: + +```src +[file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} +``` diff --git a/ru/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png b/ru/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png new file mode 100644 index 000000000..4366787b3 Binary files /dev/null and b/ru/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png differ diff --git a/ru/docs/chapter_sorting/radix_sort.md b/ru/docs/chapter_sorting/radix_sort.md new file mode 100644 index 000000000..5b3c7f085 --- /dev/null +++ b/ru/docs/chapter_sorting/radix_sort.md @@ -0,0 +1,41 @@ +# Поразрядная сортировка + +В предыдущем разделе мы познакомились с сортировкой подсчетом: она подходит для случаев, когда объем данных $n$ велик, а диапазон значений $m$ сравнительно мал. Предположим теперь, что нужно отсортировать $n = 10^6$ студенческих идентификаторов, причем каждый идентификатор является $8$-значным числом. Тогда диапазон данных $m = 10^8$ оказывается очень большим; сортировка подсчетом потребует огромного объема памяти, а поразрядная сортировка позволяет этого избежать. + +Поразрядная сортировка (radix sort) по своей основной идее совпадает с сортировкой подсчетом и тоже реализует сортировку через подсчет количества. Поверх этого поразрядная сортировка использует иерархию разрядов числа и последовательно сортирует данные по каждому разряду, получая итоговый упорядоченный результат. + +## Алгоритм + +Рассмотрим пример со студенческими номерами: будем считать, что младший разряд имеет номер $1$ , а старший - номер $8$ . Тогда процесс поразрядной сортировки показан на рисунке ниже. + +1. Инициализировать номер разряда $k = 1$ . +2. Выполнить "сортировку подсчетом" по $k$-му разряду студенческого номера. После этого данные будут упорядочены по $k$-му разряду по возрастанию. +3. Увеличить $k$ на $1$ и вернуться к шагу `2.` , продолжая процесс, пока сортировка не будет выполнена для всех разрядов. + +![Процесс поразрядной сортировки](radix_sort.assets/radix_sort_overview.png) + +Ниже разберем реализацию кода. Для числа $x$ в системе счисления с основанием $d$ получить его $k$-й разряд $x_k$ можно по формуле: + +$$ +x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d +$$ + +где $\lfloor a \rfloor$ обозначает округление числа $a$ вниз, а $\bmod \: d$ означает взятие остатка по модулю $d$ . Для студенческих идентификаторов выполняется $d = 10$ и $k \in [1, 8]$ . + +Кроме того, нам нужно слегка изменить код сортировки подсчетом, чтобы он мог сортировать числа по их $k$-му разряду: + +```src +[file]{radix_sort}-[class]{}-[func]{radix_sort} +``` + +!!! question "Почему сортировка выполняется от младшего разряда к старшему?" + + В последовательных раундах сортировки результаты более позднего раунда перекрывают результаты предыдущего. Например, если после первого раунда получилось $a < b$ , а после второго - $a > b$ , то именно результат второго раунда станет окончательным. Поскольку старшие разряды имеют более высокий приоритет, сначала нужно сортировать по младшим разрядам, а затем по старшим. + +## Характеристики алгоритма + +По сравнению с сортировкой подсчетом поразрядная сортировка подходит для случаев с большим диапазоном чисел, **но только при условии, что данные можно представить в виде чисел фиксированной длины и число разрядов не слишком велико**. Например, числа с плавающей запятой плохо подходят для поразрядной сортировки, потому что число разрядов $k$ слишком велико и может привести к ситуации $O(nk) \gg O(n^2)$ . + +- **Временная сложность равна $O(nk)$, алгоритм не является адаптивным**: пусть объем данных равен $n$ , числа записаны в системе счисления с основанием $d$ , а максимальное число разрядов равно $k$ . Тогда выполнение сортировки подсчетом для одного разряда требует $O(n + d)$ времени, а сортировка по всем $k$ разрядам требует $O((n + d)k)$ времени. Обычно $d$ и $k$ сравнительно малы, поэтому временная сложность стремится к $O(n)$ . +- **Пространственная сложность равна $O(n + d)$, сортировка не выполняется на месте**: как и в сортировке подсчетом, здесь требуются массивы `res` и `counter` длины $n$ и $d$ . +- **Стабильная сортировка**: если сортировка подсчетом стабильна, то и поразрядная сортировка стабильна; если же сортировка подсчетом нестабильна, поразрядная сортировка не может гарантировать корректный результат. diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png new file mode 100644 index 000000000..3ccb833c8 Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png new file mode 100644 index 000000000..cdb8a7fef Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png new file mode 100644 index 000000000..3b16d5906 Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png new file mode 100644 index 000000000..0be7affac Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png new file mode 100644 index 000000000..60b7a66b3 Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png new file mode 100644 index 000000000..298acf350 Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png new file mode 100644 index 000000000..268095edd Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png new file mode 100644 index 000000000..80f3e1edc Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png new file mode 100644 index 000000000..de61be83c Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png new file mode 100644 index 000000000..3cdd7bcd3 Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png new file mode 100644 index 000000000..f7af01c7f Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png new file mode 100644 index 000000000..2ef613b0c Binary files /dev/null and b/ru/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png differ diff --git a/ru/docs/chapter_sorting/selection_sort.md b/ru/docs/chapter_sorting/selection_sort.md new file mode 100644 index 000000000..ff3634905 --- /dev/null +++ b/ru/docs/chapter_sorting/selection_sort.md @@ -0,0 +1,58 @@ +# Сортировка выбором + +Сортировка выбором (selection sort) работает очень просто: запускается цикл, и на каждом шаге из неотсортированного диапазона выбирается минимальный элемент, после чего он переносится в конец уже отсортированного диапазона. + +Пусть длина массива равна $n$ ; тогда процесс сортировки выбором выглядит так, как показано на рисунке ниже. + +1. В начальном состоянии все элементы не отсортированы, то есть неотсортированный диапазон индексов равен $[0, n-1]$ . +2. Выбрать минимальный элемент из диапазона $[0, n-1]$ и поменять его местами с элементом в позиции $0$ . После этого первые 1 элементов массива отсортированы. +3. Выбрать минимальный элемент из диапазона $[1, n-1]$ и поменять его местами с элементом в позиции $1$ . После этого первые 2 элементов массива отсортированы. +4. Продолжать по аналогии. После $n - 1$ раундов выбора и обмена первые $n - 1$ элементов массива будут отсортированы. +5. Оставшийся элемент обязательно является максимальным, сортировать его не нужно, поэтому массив считается отсортированным. + +=== "<1>" + ![Шаги сортировки выбором](selection_sort.assets/selection_sort_step1.png) + +=== "<2>" + ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) + +=== "<3>" + ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) + +=== "<4>" + ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) + +=== "<5>" + ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) + +=== "<6>" + ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) + +=== "<7>" + ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) + +=== "<8>" + ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) + +=== "<9>" + ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) + +=== "<10>" + ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) + +=== "<11>" + ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) + +В коде мы используем $k$ для записи минимального элемента в пределах неотсортированного диапазона: + +```src +[file]{selection_sort}-[class]{}-[func]{selection_sort} +``` + +## Характеристики алгоритма + +- **Временная сложность равна $O(n^2)$, сортировка не является адаптивной**: внешний цикл выполняется $n - 1$ раз; в первом раунде длина неотсортированного диапазона равна $n$ , а в последнем - $2$ , то есть отдельные раунды содержат $n$, $n - 1$, $\dots$, $3$, $2$ проходов внутреннего цикла, их сумма равна $\frac{(n - 1)(n + 2)}{2}$ . +- **Пространственная сложность равна $O(1)$, сортировка выполняется на месте**: указатели $i$ и $j$ используют константный объем дополнительной памяти. +- **Нестабильная сортировка**: как показано на рисунке ниже, элемент `nums[i]` может быть переставлен вправо от другого равного ему элемента, из-за чего их относительный порядок изменится. + +![Пример нестабильности сортировки выбором](selection_sort.assets/selection_sort_instability.png) diff --git a/ru/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png b/ru/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png new file mode 100644 index 000000000..bcaefebe5 Binary files /dev/null and b/ru/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png differ diff --git a/ru/docs/chapter_sorting/sorting_algorithm.md b/ru/docs/chapter_sorting/sorting_algorithm.md new file mode 100644 index 000000000..556f56a00 --- /dev/null +++ b/ru/docs/chapter_sorting/sorting_algorithm.md @@ -0,0 +1,46 @@ +# Алгоритмы сортировки + +Алгоритмы сортировки (sorting algorithm) используются для упорядочивания набора данных по определенному правилу. Они применяются очень широко, потому что упорядоченные данные обычно проще и быстрее искать, анализировать и обрабатывать. + +Как показано на рисунке ниже, данными в алгоритмах сортировки могут быть целые числа, числа с плавающей запятой, символы, строки и другие типы. Критерий сравнения тоже можно задать по-разному, например по величине чисел, по порядку ASCII-кодов символов или по пользовательскому правилу. + +![Примеры типов данных и правил сравнения](sorting_algorithm.assets/sorting_examples.png) + +## Критерии оценки + +**Скорость выполнения**: мы ожидаем, что временная сложность алгоритма сортировки будет как можно ниже, а общее число операций будет как можно меньше (то есть константа во временной сложности будет небольшой). Для больших объемов данных этот критерий особенно важен. + +**Сортировка на месте**: как следует из названия, сортировка на месте выполняется прямо в исходном массиве и не требует дополнительного вспомогательного массива, что позволяет экономить память. Обычно при сортировке на месте переносов данных меньше, а скорость работы выше. + +**Стабильность**: стабильная сортировка после завершения не меняет относительный порядок одинаковых элементов в массиве. + +Стабильность является необходимым условием для многоуровневой сортировки. Предположим, у нас есть таблица со сведениями о студентах, где в первом и втором столбцах записаны имя и возраст. В этом случае нестабильная сортировка может разрушить уже существующий порядок входных данных: + +```shell +# Входные данные уже отсортированы по имени +# (name, age) + ('A', 19) + ('B', 18) + ('C', 21) + ('D', 19) + ('E', 23) + +# Если затем нестабильным алгоритмом отсортировать список по возрасту, +# относительный порядок ('D', 19) и ('A', 19) изменится, +# и свойство упорядоченности по имени будет потеряно + ('B', 18) + ('D', 19) + ('A', 19) + ('C', 21) + ('E', 23) +``` + +**Адаптивность**: адаптивная сортировка умеет использовать уже существующий порядок входных данных, чтобы сократить вычисления и добиться лучшей эффективности. Лучшая временная сложность адаптивных алгоритмов обычно лучше их средней временной сложности. + +**Основанность на сравнении**: сортировка на основе сравнений использует операторы сравнения ($<$, $=$, $>$), чтобы определить относительный порядок элементов и отсортировать массив; ее теоретически лучшая временная сложность равна $O(n \log n)$ . А вот сортировка без сравнений не опирается на операторы сравнения, поэтому может достигать $O(n)$ , но универсальность у нее ниже. + +## Идеальный алгоритм сортировки + +**Быстрый, выполняющийся на месте, стабильный, адаптивный и универсальный**. Очевидно, что на сегодняшний день не существует алгоритма сортировки, который одновременно обладал бы всеми этими свойствами. Поэтому при выборе алгоритма сортировки нужно исходить из конкретных особенностей данных и требований задачи. + +Далее мы последовательно изучим разные алгоритмы сортировки и на основании приведенных выше критериев разберем их преимущества и недостатки. diff --git a/ru/docs/chapter_sorting/sorting_examples.png b/ru/docs/chapter_sorting/sorting_examples.png new file mode 100644 index 000000000..bcaefebe5 Binary files /dev/null and b/ru/docs/chapter_sorting/sorting_examples.png differ diff --git a/ru/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png b/ru/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png new file mode 100644 index 000000000..28bae7893 Binary files /dev/null and b/ru/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png differ diff --git a/ru/docs/chapter_sorting/summary.md b/ru/docs/chapter_sorting/summary.md new file mode 100644 index 000000000..b0c6c4be3 --- /dev/null +++ b/ru/docs/chapter_sorting/summary.md @@ -0,0 +1,47 @@ +# Резюме + +### Ключевые выводы + +- Сортировка пузырьком выполняет сортировку за счет обмена соседних элементов. Если добавить флаг для досрочного выхода, лучшую временную сложность пузырьковой сортировки можно оптимизировать до $O(n)$ . +- Сортировка вставками на каждом раунде вставляет элемент из неотсортированного диапазона в правильную позицию внутри отсортированного диапазона. Хотя ее временная сложность равна $O(n^2)$ , она очень популярна для задач сортировки небольших массивов, поскольку число элементарных операций у нее сравнительно невелико. +- Быстрая сортировка основана на операции разделения с опорным элементом. При неудачном выборе опорного элемента на каждом раунде ее временная сложность может деградировать до $O(n^2)$ . Использование медианы трех элементов или случайного опорного элемента уменьшает вероятность этой деградации. Если всегда рекурсивно обрабатывать более короткий поддиапазон первым, можно эффективно уменьшить глубину рекурсии и оптимизировать пространственную сложность до $O(\log n)$ . +- Сортировка слиянием включает этапы разделения и слияния и служит типичным проявлением стратегии "разделяй и властвуй". Для сортировки массива ей требуется вспомогательный массив, поэтому пространственная сложность равна $O(n)$ ; однако при сортировке связного списка пространственную сложность можно оптимизировать до $O(1)$ . +- Блочная сортировка включает три этапа: распределение данных по блокам, сортировку внутри блоков и объединение результатов. Она тоже отражает стратегию "разделяй и властвуй" и подходит для очень больших объемов данных. Ключ к эффективности блочной сортировки - равномерное распределение данных. +- Сортировка подсчетом является частным случаем блочной сортировки; она реализует сортировку через подсчет числа вхождений данных. Сортировка подсчетом подходит для случаев, когда объем данных велик, но диапазон значений ограничен, и при этом данные можно преобразовать в положительные целые числа. +- Поразрядная сортировка выполняет сортировку данных путем последовательной сортировки по каждому разряду и требует, чтобы данные можно было представить в виде чисел фиксированной разрядности. +- В общем случае нам хотелось бы найти алгоритм сортировки, который одновременно обладал бы высокой эффективностью, стабильностью, свойством выполнения на месте и адаптивностью. Но, как и в других разделах алгоритмов и структур данных, не существует одного алгоритма сортировки, способного удовлетворить всем этим требованиям одновременно. На практике приходится выбирать подходящий алгоритм в зависимости от свойств данных. +- На рисунке ниже сравниваются эффективность, стабильность, выполнение на месте и адаптивность основных алгоритмов сортировки. + +![Сравнение алгоритмов сортировки](summary.assets/sorting_algorithms_comparison.png) + +### Вопросы и ответы + +**В**: В каких случаях стабильность алгоритма сортировки является обязательной? + +В реальных задачах нам может понадобиться сортировать объекты по некоторому атрибуту. Например, у студентов есть два атрибута: имя и рост. Мы хотим выполнить многоуровневую сортировку: сначала отсортировать по имени и получить `(A, 180) (B, 185) (C, 170) (D, 170)` , а затем отсортировать по росту. Если используемый алгоритм сортировки нестабилен, то мы можем получить `(D, 170) (C, 170) (A, 180) (B, 185)` . + +Нетрудно увидеть, что в этом случае студенты D и C поменялись местами, порядок по имени разрушился, а именно этого мы и не хотим. + +**В**: Можно ли поменять местами порядок "поиска справа налево" и "поиска слева направо" в разделении с опорным элементом? + +Нет. Если в качестве опорного элемента выбирается самый левый элемент, необходимо сначала выполнять "поиск справа налево", а уже затем - "поиск слева направо". Этот вывод кажется немного неочевидным, поэтому разберем его подробнее. + +Последний шаг `partition()` - это обмен `nums[left]` и `nums[i]` . После обмена все элементы слева от опорного должны быть `<=` опорного, **а значит, перед этим обменом должно выполняться условие `nums[left] >= nums[i]`**. Если сначала выполнять "поиск слева направо", то в случае, когда не удается найти элемент больше опорного, **цикл завершится в состоянии `i == j` , и при этом может оказаться, что `nums[j] == nums[i] > nums[left]`**. Иными словами, на последнем шаге обмена элемент, больший опорного, будет помещен в начало массива, из-за чего разделение завершится неверно. + +Например, для массива `[0, 0, 0, 0, 1]` , если сначала выполнять "поиск слева направо", после разделения получится `[1, 0, 0, 0, 0]` , а это неправильный результат. + +Если же выбрать `nums[right]` в качестве опорного элемента, то ситуация станет противоположной, и тогда сначала нужно выполнять "поиск слева направо". + +**В**: Почему при оптимизации глубины рекурсии в быстрой сортировке выбор короткого массива гарантирует, что глубина рекурсии не превысит $\log n$ ? + +Глубина рекурсии - это число текущих рекурсивных вызовов, которые еще не завершились. На каждом раунде разделения исходный массив разбивается на два подмассива. После оптимизации глубины рекурсии длина подмассива, в который мы продолжаем рекурсивный спуск, не превышает половины длины исходного массива. Если рассматривать худший случай, когда длина каждый раз становится ровно вдвое меньше, итоговая глубина рекурсии и будет равна $\log n$ . + +В исходной версии быстрой сортировки может происходить последовательный рекурсивный вызов для более длинных массивов; в худшем случае это будут длины $n$ , $n - 1$ , $\dots$ , $2$ , $1$ , а глубина рекурсии окажется равной $n$ . Оптимизация глубины рекурсии как раз и позволяет избежать такого сценария. + +**В**: Если все элементы массива равны, будет ли временная сложность быстрой сортировки равна $O(n^2)$ ? Как справиться с таким вырождением? + +Да. Для этого случая можно рассмотреть разделение массива на три части: элементы меньше опорного, равные опорному и большие опорного. Рекурсию нужно продолжать только для частей меньше и больше опорного. При таком подходе массив, целиком состоящий из одинаковых элементов, будет отсортирован всего за один раунд разделения. + +**В**: Почему худшая временная сложность блочной сортировки равна $O(n^2)$ ? + +В худшем случае все элементы попадут в один и тот же блок. Если затем сортировать этот блок алгоритмом со сложностью $O(n^2)$ , то общая временная сложность тоже станет $O(n^2)$ . diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png new file mode 100644 index 000000000..28a3a5828 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png new file mode 100644 index 000000000..74b605fab Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png new file mode 100644 index 000000000..2dae8fa5a Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png new file mode 100644 index 000000000..32bc76fe6 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png new file mode 100644 index 000000000..7068ded4b Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/deque_operations.png b/ru/docs/chapter_stack_and_queue/deque.assets/deque_operations.png new file mode 100644 index 000000000..55cdad141 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/deque_operations.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png new file mode 100644 index 000000000..3958823e2 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png new file mode 100644 index 000000000..f06f29230 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png new file mode 100644 index 000000000..bd4b412a5 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png new file mode 100644 index 000000000..3fbe296d1 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png new file mode 100644 index 000000000..6ba9b5fdf Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png differ diff --git a/ru/docs/chapter_stack_and_queue/deque.md b/ru/docs/chapter_stack_and_queue/deque.md new file mode 100644 index 000000000..bbe84961c --- /dev/null +++ b/ru/docs/chapter_stack_and_queue/deque.md @@ -0,0 +1,452 @@ +# Двусторонняя очередь + +В очереди мы можем удалять элементы только из головы или добавлять их только в хвост. Как показано на рисунке ниже, двусторонняя очередь (double-ended queue) обеспечивает более высокую гибкость и позволяет выполнять добавление и удаление элементов как с головы, так и с хвоста. + +![Операции двусторонней очереди](deque.assets/deque_operations.png) + +## Основные операции с двусторонней очередью + +Распространенные операции двусторонней очереди приведены в таблице ниже. Конкретные названия методов зависят от используемого языка программирования. + +

Таблица   Эффективность операций двусторонней очереди

+ +| Имя метода | Описание | Временная сложность | +| ------------ | -------------------------------- | ------------------- | +| `push_first()` | Добавить элемент в голову очереди | $O(1)$ | +| `push_last()` | Добавить элемент в хвост очереди | $O(1)$ | +| `pop_first()` | Удалить элемент из головы очереди | $O(1)$ | +| `pop_last()` | Удалить элемент из хвоста очереди | $O(1)$ | +| `peek_first()` | Просмотреть элемент в голове очереди | $O(1)$ | +| `peek_last()` | Просмотреть элемент в хвосте очереди | $O(1)$ | + +Точно так же мы можем напрямую использовать уже реализованные в языках программирования классы двусторонней очереди: + +=== "Python" + + ```python title="deque.py" + from collections import deque + + # Инициализация двусторонней очереди + deq: deque[int] = deque() + + # Поместить элементы в очередь + deq.append(2) # Добавить в хвост + deq.append(5) + deq.append(4) + deq.appendleft(3) # Добавить в голову + deq.appendleft(1) + + # Просмотреть элементы + front: int = deq[0] # Элемент в голове + rear: int = deq[-1] # Элемент в хвосте + + # Извлечь элементы из очереди + pop_front: int = deq.popleft() # Извлечь элемент из головы + pop_rear: int = deq.pop() # Извлечь элемент из хвоста + + # Получить длину двусторонней очереди + size: int = len(deq) + + # Проверить, пуста ли двусторонняя очередь + is_empty: bool = len(deq) == 0 + ``` + +=== "C++" + + ```cpp title="deque.cpp" + /* Инициализация двусторонней очереди */ + deque deque; + + /* Поместить элементы в очередь */ + deque.push_back(2); // Добавить в хвост + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // Добавить в голову + deque.push_front(1); + + /* Просмотреть элементы */ + int front = deque.front(); // Элемент в голове + int back = deque.back(); // Элемент в хвосте + + /* Извлечь элементы из очереди */ + deque.pop_front(); // Извлечь элемент из головы + deque.pop_back(); // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + int size = deque.size(); + + /* Проверить, пуста ли двусторонняя очередь */ + bool empty = deque.empty(); + ``` + +=== "Java" + + ```java title="deque.java" + /* Инициализация двусторонней очереди */ + Deque deque = new LinkedList<>(); + + /* Поместить элементы в очередь */ + deque.offerLast(2); // Добавить в хвост + deque.offerLast(5); + deque.offerLast(4); + deque.offerFirst(3); // Добавить в голову + deque.offerFirst(1); + + /* Просмотреть элементы */ + int peekFirst = deque.peekFirst(); // Элемент в голове + int peekLast = deque.peekLast(); // Элемент в хвосте + + /* Извлечь элементы из очереди */ + int popFirst = deque.pollFirst(); // Извлечь элемент из головы + int popLast = deque.pollLast(); // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + int size = deque.size(); + + /* Проверить, пуста ли двусторонняя очередь */ + boolean isEmpty = deque.isEmpty(); + ``` + +=== "C#" + + ```csharp title="deque.cs" + /* Инициализация двусторонней очереди */ + // В C# двустороннюю очередь обычно моделируют через связный список LinkedList + LinkedList deque = new(); + + /* Поместить элементы в очередь */ + deque.AddLast(2); // Добавить в хвост + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // Добавить в голову + deque.AddFirst(1); + + /* Просмотреть элементы */ + int peekFirst = deque.First.Value; // Элемент в голове + int peekLast = deque.Last.Value; // Элемент в хвосте + + /* Извлечь элементы из очереди */ + deque.RemoveFirst(); // Извлечь элемент из головы + deque.RemoveLast(); // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + int size = deque.Count; + + /* Проверить, пуста ли двусторонняя очередь */ + bool isEmpty = deque.Count == 0; + ``` + +=== "Go" + + ```go title="deque_test.go" + /* Инициализация двусторонней очереди */ + // В Go list обычно используется как двусторонняя очередь + deque := list.New() + + /* Поместить элементы в очередь */ + deque.PushBack(2) // Добавить в хвост + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) // Добавить в голову + deque.PushFront(1) + + /* Просмотреть элементы */ + front := deque.Front() // Элемент в голове + rear := deque.Back() // Элемент в хвосте + + /* Извлечь элементы из очереди */ + deque.Remove(front) // Извлечь элемент из головы + deque.Remove(rear) // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + size := deque.Len() + + /* Проверить, пуста ли двусторонняя очередь */ + isEmpty := deque.Len() == 0 + ``` + +=== "Swift" + + ```swift title="deque.swift" + /* Инициализация двусторонней очереди */ + // В Swift нет встроенного класса двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь + var deque: [Int] = [] + + /* Поместить элементы в очередь */ + deque.append(2) // Добавить в хвост + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) // Добавить в голову + deque.insert(1, at: 0) + + /* Просмотреть элементы */ + let peekFirst = deque.first! // Элемент в голове + let peekLast = deque.last! // Элемент в хвосте + + /* Извлечь элементы из очереди */ + // При моделировании через Array сложность popFirst равна O(n) + let popFirst = deque.removeFirst() // Извлечь элемент из головы + let popLast = deque.removeLast() // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + let size = deque.count + + /* Проверить, пуста ли двусторонняя очередь */ + let isEmpty = deque.isEmpty + ``` + +=== "JS" + + ```javascript title="deque.js" + /* Инициализация двусторонней очереди */ + // В JavaScript нет встроенной двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь + const deque = []; + + /* Поместить элементы в очередь */ + deque.push(2); + deque.push(5); + deque.push(4); + // Обрати внимание: поскольку это массив, метод unshift() имеет сложность O(n) + deque.unshift(3); + deque.unshift(1); + + /* Просмотреть элементы */ + const peekFirst = deque[0]; + const peekLast = deque[deque.length - 1]; + + /* Извлечь элементы из очереди */ + // Обрати внимание: поскольку это массив, метод shift() имеет сложность O(n) + const popFront = deque.shift(); + const popBack = deque.pop(); + + /* Получить длину двусторонней очереди */ + const size = deque.length; + + /* Проверить, пуста ли двусторонняя очередь */ + const isEmpty = size === 0; + ``` + +=== "TS" + + ```typescript title="deque.ts" + /* Инициализация двусторонней очереди */ + // В TypeScript нет встроенной двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь + const deque: number[] = []; + + /* Поместить элементы в очередь */ + deque.push(2); + deque.push(5); + deque.push(4); + // Обрати внимание: поскольку это массив, метод unshift() имеет сложность O(n) + deque.unshift(3); + deque.unshift(1); + + /* Просмотреть элементы */ + const peekFirst: number = deque[0]; + const peekLast: number = deque[deque.length - 1]; + + /* Извлечь элементы из очереди */ + // Обрати внимание: поскольку это массив, метод shift() имеет сложность O(n) + const popFront: number = deque.shift() as number; + const popBack: number = deque.pop() as number; + + /* Получить длину двусторонней очереди */ + const size: number = deque.length; + + /* Проверить, пуста ли двусторонняя очередь */ + const isEmpty: boolean = size === 0; + ``` + +=== "Dart" + + ```dart title="deque.dart" + /* Инициализация двусторонней очереди */ + // В Dart Queue определена как двусторонняя очередь + Queue deque = Queue(); + + /* Поместить элементы в очередь */ + deque.addLast(2); // Добавить в хвост + deque.addLast(5); + deque.addLast(4); + deque.addFirst(3); // Добавить в голову + deque.addFirst(1); + + /* Просмотреть элементы */ + int peekFirst = deque.first; // Элемент в голове + int peekLast = deque.last; // Элемент в хвосте + + /* Извлечь элементы из очереди */ + int popFirst = deque.removeFirst(); // Извлечь элемент из головы + int popLast = deque.removeLast(); // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + int size = deque.length; + + /* Проверить, пуста ли двусторонняя очередь */ + bool isEmpty = deque.isEmpty; + ``` + +=== "Rust" + + ```rust title="deque.rs" + /* Инициализация двусторонней очереди */ + let mut deque: VecDeque = VecDeque::new(); + + /* Поместить элементы в очередь */ + deque.push_back(2); // Добавить в хвост + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // Добавить в голову + deque.push_front(1); + + /* Просмотреть элементы */ + if let Some(front) = deque.front() { // Элемент в голове + } + if let Some(rear) = deque.back() { // Элемент в хвосте + } + + /* Извлечь элементы из очереди */ + if let Some(pop_front) = deque.pop_front() { // Извлечь элемент из головы + } + if let Some(pop_rear) = deque.pop_back() { // Извлечь элемент из хвоста + } + + /* Получить длину двусторонней очереди */ + let size = deque.len(); + + /* Проверить, пуста ли двусторонняя очередь */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="deque.c" + // В C нет встроенной двусторонней очереди + ``` + +=== "Kotlin" + + ```kotlin title="deque.kt" + /* Инициализация двусторонней очереди */ + val deque = LinkedList() + + /* Поместить элементы в очередь */ + deque.offerLast(2) // Добавить в хвост + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) // Добавить в голову + deque.offerFirst(1) + + /* Просмотреть элементы */ + val peekFirst = deque.peekFirst() // Элемент в голове + val peekLast = deque.peekLast() // Элемент в хвосте + + /* Извлечь элементы из очереди */ + val popFirst = deque.pollFirst() // Извлечь элемент из головы + val popLast = deque.pollLast() // Извлечь элемент из хвоста + + /* Получить длину двусторонней очереди */ + val size = deque.size + + /* Проверить, пуста ли двусторонняя очередь */ + val isEmpty = deque.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="deque.rb" + # Инициализация двусторонней очереди + # В Ruby нет встроенной двусторонней очереди, поэтому можно использовать Array как двустороннюю очередь + deque = [] + + # Поместить элементы в очередь + deque << 2 + deque << 5 + deque << 4 + # Обрати внимание: поскольку это массив, метод Array#unshift имеет сложность O(n) + deque.unshift(3) + deque.unshift(1) + + # Просмотреть элементы + peek_first = deque.first + peek_last = deque.last + + # Извлечь элементы из очереди + # Обрати внимание: поскольку это массив, метод Array#shift имеет сложность O(n) + pop_front = deque.shift + pop_back = deque.pop + + # Получить длину двусторонней очереди + size = deque.length + + # Проверить, пуста ли двусторонняя очередь + is_empty = size.zero? + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8E%D1%8E%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20deq.append%282%29%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%83%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20deque%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20front%20%3D%22%2C%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20rear%20%3D%22%2C%20rear%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20pop_front%20%3D%22%2C%20pop_front%29%0A%20%20%20%20print%28%22deque%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20%3D%22%2C%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%2C%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20pop_rear%20%3D%22%2C%20pop_rear%29%0A%20%20%20%20print%28%22deque%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20%3D%22%2C%20deq%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%D0%B9%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B5%D0%B9%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Реализация двусторонней очереди * + +Реализация двусторонней очереди похожа на реализацию обычной очереди: в качестве базовой структуры данных можно выбрать связный список или массив. + +### Реализация на основе двусвязного списка + +Вспомним предыдущий раздел: там мы использовали обычный односвязный список для реализации очереди, потому что он позволяет удобно удалять головной узел (это соответствует операции dequeue) и добавлять новый узел после хвостового узла (это соответствует операции enqueue). + +Для двусторонней очереди и голова, и хвост допускают операции добавления и удаления элементов. Иначе говоря, двусторонняя очередь требует реализации еще одного симметричного направления операций. Поэтому в качестве базовой структуры данных двусторонней очереди мы используем "двусвязный список". + +Как показано на рисунках ниже, мы рассматриваем головной и хвостовой узлы двусвязного списка как голову и хвост двусторонней очереди и одновременно реализуем функции добавления и удаления узлов с обеих сторон. + +=== "LinkedListDeque" + ![Операции enqueue и dequeue для двусторонней очереди на связном списке](deque.assets/linkedlist_deque_step1.png) + +=== "push_last()" + ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) + +=== "push_first()" + ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) + +=== "pop_last()" + ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) + +=== "pop_first()" + ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) + +Код реализации приведен ниже: + +```src +[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} +``` + +### Реализация на основе массива + +Как показано на рисунках ниже, аналогично реализации очереди на массиве мы также можем использовать кольцевой массив для реализации двусторонней очереди. + +=== "ArrayDeque" + ![Операции enqueue и dequeue для двусторонней очереди на массиве](deque.assets/array_deque_step1.png) + +=== "push_last()" + ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) + +=== "push_first()" + ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) + +=== "pop_last()" + ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) + +=== "pop_first()" + ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) + +На основе реализации обычной очереди нужно лишь добавить методы "enqueue в голову" и "dequeue из хвоста": + +```src +[file]{array_deque}-[class]{array_deque}-[func]{} +``` + +## Применение двусторонней очереди + +Двусторонняя очередь сочетает в себе логику стека и очереди, **поэтому она может покрыть все сценарии применения обеих структур и при этом предоставляет более высокую степень свободы**. + +Мы знаем, что функция "undo" в программном обеспечении обычно реализуется с помощью стека: система `push`-ит каждое изменение в стек, а затем использует `pop` для отмены. Однако, учитывая ограниченность системных ресурсов, программы обычно ограничивают число шагов отмены (например, разрешают хранить только $50$ шагов). Когда длина стека превышает $50$, программе нужно удалить элемент с дна стека (то есть с головы очереди). **Но стек не может реализовать такую операцию, и в этом случае его приходится заменять двусторонней очередью**. Обрати внимание: основная логика "undo" по-прежнему следует стековому правилу LIFO, просто двусторонняя очередь позволяет более гибко реализовать некоторые дополнительные механизмы. diff --git a/ru/docs/chapter_stack_and_queue/index.md b/ru/docs/chapter_stack_and_queue/index.md new file mode 100644 index 000000000..1c21ae434 --- /dev/null +++ b/ru/docs/chapter_stack_and_queue/index.md @@ -0,0 +1,9 @@ +# Стек и очередь + +![Стек и очередь](../assets/covers/chapter_stack_and_queue.jpg) + +!!! abstract + + Стек похож на стопку кошек, а очередь - на очередь из кошек. + + Эти две структуры соответственно представляют отношения "последним пришел - первым вышел" и "первым пришел - первым вышел". diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png b/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png new file mode 100644 index 000000000..93f87a3ce Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png b/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png new file mode 100644 index 000000000..b2c6454a9 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png b/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png new file mode 100644 index 000000000..9b7e1c2d0 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png b/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png new file mode 100644 index 000000000..4d5f20928 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png b/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png new file mode 100644 index 000000000..f91e459da Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png b/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png new file mode 100644 index 000000000..cd55a4f8e Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.assets/queue_operations.png b/ru/docs/chapter_stack_and_queue/queue.assets/queue_operations.png new file mode 100644 index 000000000..d944b67ba Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/queue.assets/queue_operations.png differ diff --git a/ru/docs/chapter_stack_and_queue/queue.md b/ru/docs/chapter_stack_and_queue/queue.md new file mode 100644 index 000000000..97d5af0aa --- /dev/null +++ b/ru/docs/chapter_stack_and_queue/queue.md @@ -0,0 +1,423 @@ +# Очередь + +Очередь (queue) - это линейная структура данных, подчиняющаяся правилу "первым пришел - первым вышел". Как видно из названия, очередь моделирует обычную ситуацию ожидания: новые люди непрерывно присоединяются к хвосту очереди, а стоящие в начале по одному уходят. + +Как показано на рисунке ниже, начало очереди называется "головой очереди", а конец - "хвостом очереди"; операцию добавления элемента в хвост называют "enqueue", а операцию удаления элемента из головы - "dequeue". + +![Правило FIFO для очереди](queue.assets/queue_operations.png) + +## Основные операции с очередью + +Распространенные операции с очередью показаны в таблице ниже. Следует учитывать, что названия методов в разных языках могут различаться. Здесь мы используем те же названия, что и для стека. + +

Таблица   Эффективность операций с очередью

+ +| Имя метода | Описание | Временная сложность | +| ---------- | ----------------------------------------- | ------------------- | +| `push()` | Поместить элемент в очередь, то есть добавить его в хвост | $O(1)$ | +| `pop()` | Извлечь элемент из головы очереди | $O(1)$ | +| `peek()` | Просмотреть элемент в голове очереди | $O(1)$ | + +Мы можем напрямую использовать готовые классы очереди, предоставляемые языками программирования: + +=== "Python" + + ```python title="queue.py" + from collections import deque + + # Инициализация очереди + # В Python обычно используют двустороннюю очередь deque как обычную очередь + # Хотя queue.Queue() является "чистой" очередью, она не слишком удобна, поэтому ее не рекомендуют + que: deque[int] = deque() + + # Поместить элементы в очередь + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + + # Просмотреть элемент в голове очереди + front: int = que[0] + + # Извлечь элемент из очереди + pop: int = que.popleft() + + # Получить длину очереди + size: int = len(que) + + # Проверить, пуста ли очередь + is_empty: bool = len(que) == 0 + ``` + +=== "C++" + + ```cpp title="queue.cpp" + /* Инициализация очереди */ + queue queue; + + /* Поместить элементы в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* Просмотреть элемент в голове очереди */ + int front = queue.front(); + + /* Извлечь элемент из очереди */ + queue.pop(); + + /* Получить длину очереди */ + int size = queue.size(); + + /* Проверить, пуста ли очередь */ + bool empty = queue.empty(); + ``` + +=== "Java" + + ```java title="queue.java" + /* Инициализация очереди */ + Queue queue = new LinkedList<>(); + + /* Поместить элементы в очередь */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + + /* Просмотреть элемент в голове очереди */ + int peek = queue.peek(); + + /* Извлечь элемент из очереди */ + int pop = queue.poll(); + + /* Получить длину очереди */ + int size = queue.size(); + + /* Проверить, пуста ли очередь */ + boolean isEmpty = queue.isEmpty(); + ``` + +=== "C#" + + ```csharp title="queue.cs" + /* Инициализация очереди */ + Queue queue = new(); + + /* Поместить элементы в очередь */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + + /* Просмотреть элемент в голове очереди */ + int peek = queue.Peek(); + + /* Извлечь элемент из очереди */ + int pop = queue.Dequeue(); + + /* Получить длину очереди */ + int size = queue.Count; + + /* Проверить, пуста ли очередь */ + bool isEmpty = queue.Count == 0; + ``` + +=== "Go" + + ```go title="queue_test.go" + /* Инициализация очереди */ + // В Go очередь обычно реализуют через list + queue := list.New() + + /* Поместить элементы в очередь */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + + /* Просмотреть элемент в голове очереди */ + peek := queue.Front() + + /* Извлечь элемент из очереди */ + pop := queue.Front() + queue.Remove(pop) + + /* Получить длину очереди */ + size := queue.Len() + + /* Проверить, пуста ли очередь */ + isEmpty := queue.Len() == 0 + ``` + +=== "Swift" + + ```swift title="queue.swift" + /* Инициализация очереди */ + // В Swift нет встроенного класса очереди, поэтому можно использовать Array как очередь + var queue: [Int] = [] + + /* Поместить элементы в очередь */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + + /* Просмотреть элемент в голове очереди */ + let peek = queue.first! + + /* Извлечь элемент из очереди */ + // Поскольку в основе лежит массив, removeFirst имеет сложность O(n) + let pool = queue.removeFirst() + + /* Получить длину очереди */ + let size = queue.count + + /* Проверить, пуста ли очередь */ + let isEmpty = queue.isEmpty + ``` + +=== "JS" + + ```javascript title="queue.js" + /* Инициализация очереди */ + // В JavaScript нет встроенной очереди, поэтому можно использовать Array как очередь + const queue = []; + + /* Поместить элементы в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* Просмотреть элемент в голове очереди */ + const peek = queue[0]; + + /* Извлечь элемент из очереди */ + // В основе лежит массив, поэтому shift() имеет сложность O(n) + const pop = queue.shift(); + + /* Получить длину очереди */ + const size = queue.length; + + /* Проверить, пуста ли очередь */ + const empty = queue.length === 0; + ``` + +=== "TS" + + ```typescript title="queue.ts" + /* Инициализация очереди */ + // В TypeScript нет встроенной очереди, поэтому можно использовать Array как очередь + const queue: number[] = []; + + /* Поместить элементы в очередь */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* Просмотреть элемент в голове очереди */ + const peek = queue[0]; + + /* Извлечь элемент из очереди */ + // В основе лежит массив, поэтому shift() имеет сложность O(n) + const pop = queue.shift(); + + /* Получить длину очереди */ + const size = queue.length; + + /* Проверить, пуста ли очередь */ + const empty = queue.length === 0; + ``` + +=== "Dart" + + ```dart title="queue.dart" + /* Инициализация очереди */ + // В Dart класс Queue является двусторонней очередью и может использоваться как обычная очередь + Queue queue = Queue(); + + /* Поместить элементы в очередь */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + + /* Просмотреть элемент в голове очереди */ + int peek = queue.first; + + /* Извлечь элемент из очереди */ + int pop = queue.removeFirst(); + + /* Получить длину очереди */ + int size = queue.length; + + /* Проверить, пуста ли очередь */ + bool isEmpty = queue.isEmpty; + ``` + +=== "Rust" + + ```rust title="queue.rs" + /* Инициализация двусторонней очереди */ + // В Rust двусторонняя очередь может использоваться как обычная очередь + let mut deque: VecDeque = VecDeque::new(); + + /* Поместить элементы в очередь */ + deque.push_back(1); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + + /* Просмотреть элемент в голове очереди */ + if let Some(front) = deque.front() { + } + + /* Извлечь элемент из очереди */ + if let Some(pop) = deque.pop_front() { + } + + /* Получить длину очереди */ + let size = deque.len(); + + /* Проверить, пуста ли очередь */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="queue.c" + // В C нет встроенной очереди + ``` + +=== "Kotlin" + + ```kotlin title="queue.kt" + /* Инициализация очереди */ + val queue = LinkedList() + + /* Поместить элементы в очередь */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + + /* Просмотреть элемент в голове очереди */ + val peek = queue.peek() + + /* Извлечь элемент из очереди */ + val pop = queue.poll() + + /* Получить длину очереди */ + val size = queue.size + + /* Проверить, пуста ли очередь */ + val isEmpty = queue.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="queue.rb" + # Инициализация очереди + # Встроенная очередь в Ruby (Thread::Queue) не имеет методов peek и traverse, поэтому можно использовать Array как очередь + queue = [] + + # Поместить элементы в очередь + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + + # Просмотреть элемент очереди + peek = queue.first + + # Извлечь элемент из очереди + # Обрати внимание: поскольку это массив, метод Array#shift имеет сложность O(n) + pop = queue.shift + + # Получить длину очереди + size = queue.length + + # Проверить, пуста ли очередь + is_empty = queue.empty? + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%B4%D0%B2%D1%83%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8E%D1%8E%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20deque%20%D0%BE%D0%B1%D1%8B%D1%87%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D1%8E%D1%82%20%D0%BA%D0%B0%D0%BA%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20%23%20%D0%A5%D0%BE%D1%82%D1%8F%20queue.Queue%28%29%20%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%D1%81%D1%82%D0%BE%D1%8F%D1%89%D0%B8%D0%BC%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%BC%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%2C%20%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%D1%81%D1%8F%20%D0%B8%D0%BC%20%D0%BD%D0%B5%20%D1%81%D0%BB%D0%B8%D1%88%D0%BA%D0%BE%D0%BC%20%D1%83%D0%B4%D0%BE%D0%B1%D0%BD%D0%BE%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20que%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%B5%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20front%20%3D%22%2C%20front%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22que%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%3D%22%2C%20que%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Реализация очереди + +Чтобы реализовать очередь, нам нужна такая структура данных, которая позволяет добавлять элементы с одного конца и удалять их с другого; и связный список, и массив этим требованиям удовлетворяют. + +### Реализация на основе связного списка + +Как показано на рисунке ниже, мы можем рассматривать "головной узел" и "хвостовой узел" связного списка как "голову очереди" и "хвост очереди" соответственно, договорившись, что добавлять узлы можно только в хвост, а удалять - только из головы. + +=== "LinkedListQueue" + ![Операции enqueue и dequeue в реализации очереди на связном списке](queue.assets/linkedlist_queue_step1.png) + +=== "push()" + ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) + +=== "pop()" + ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) + +Ниже приведен код реализации очереди на связном списке: + +```src +[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} +``` + +### Реализация на основе массива + +Удаление первого элемента из массива имеет временную сложность $O(n)$ , из-за чего операция dequeue оказывается неэффективной. Однако этого можно избежать с помощью следующего приема. + +Мы можем использовать переменную `front` , указывающую на индекс элемента в голове очереди, и поддерживать переменную `size` , которая хранит длину очереди. Определим `rear = front + size` ; эта формула дает позицию `rear`, указывающую на ячейку сразу после хвоста очереди. + +Исходя из этого, **эффективный диапазон элементов массива равен `[front, rear - 1]`**, а различные операции реализуются, как показано на рисунке ниже. + +- Операция enqueue: записать входной элемент по индексу `rear` и увеличить `size` на 1. +- Операция dequeue: просто увеличить `front` на 1 и уменьшить `size` на 1. + +Можно увидеть, что и enqueue, и dequeue требуют всего одной операции, а значит обе имеют временную сложность $O(1)$ . + +=== "ArrayQueue" + ![Операции enqueue и dequeue в реализации очереди на массиве](queue.assets/array_queue_step1.png) + +=== "push()" + ![array_queue_push](queue.assets/array_queue_step2_push.png) + +=== "pop()" + ![array_queue_pop](queue.assets/array_queue_step3_pop.png) + +Ты можешь заметить еще одну проблему: при непрерывных операциях enqueue и dequeue значения `front` и `rear` оба движутся вправо, и **когда они доходят до конца массива, дальше сдвигаться уже нельзя**. Чтобы решить эту проблему, можно рассматривать массив как "кольцевой массив", у которого начало и конец соединены. + +Для кольцевого массива нужно сделать так, чтобы `front` или `rear`, перешагнув конец массива, сразу возвращались к его началу и продолжали движение. Такую периодичность удобно реализовать с помощью операции взятия остатка, как показано в коде ниже: + +```src +[file]{array_queue}-[class]{array_queue}-[func]{} +``` + +Даже такая реализация очереди остается ограниченной: ее длина неизменяема. Однако это несложно исправить, заменив массив на динамический массив и тем самым введя механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно. + +Выводы сравнения двух реализаций в целом такие же, как и для стека, поэтому здесь мы не будем повторяться. + +## Типичные применения очереди + +- **Заказы на Taobao**. После оформления заказа покупателем заказ попадает в очередь, а затем система обрабатывает заказы по порядку. Во время крупных распродаж, таких как Double 11, за короткое время возникает огромный поток заказов, и высокая конкурентная нагрузка становится ключевой инженерной проблемой. +- **Различные отложенные задачи**. Любой сценарий, где нужно реализовать принцип "кто раньше пришел, тот раньше обслуживается", например очередь заданий принтера или очередь блюд на кухне ресторана, хорошо моделируется очередью, которая эффективно поддерживает нужный порядок обработки. diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png b/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png new file mode 100644 index 000000000..54a271519 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png b/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png new file mode 100644 index 000000000..d8ea26581 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png b/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png new file mode 100644 index 000000000..910783899 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png b/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png new file mode 100644 index 000000000..e3de6b846 Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png b/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png new file mode 100644 index 000000000..e8126df8e Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png b/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png new file mode 100644 index 000000000..6d66c7f1c Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.assets/stack_operations.png b/ru/docs/chapter_stack_and_queue/stack.assets/stack_operations.png new file mode 100644 index 000000000..8dfb8d72e Binary files /dev/null and b/ru/docs/chapter_stack_and_queue/stack.assets/stack_operations.png differ diff --git a/ru/docs/chapter_stack_and_queue/stack.md b/ru/docs/chapter_stack_and_queue/stack.md new file mode 100644 index 000000000..bb1fb6acb --- /dev/null +++ b/ru/docs/chapter_stack_and_queue/stack.md @@ -0,0 +1,430 @@ +# Стек + +Стек (stack) - это линейная структура данных, подчиняющаяся логике "последним пришел - первым вышел". + +Стек можно сравнить со стопкой тарелок на столе. Если разрешено перемещать только одну тарелку за раз, то, чтобы достать тарелку снизу, сначала придется по одной убрать все тарелки сверху. Если заменить тарелки различными элементами (например целыми числами, символами, объектами и т.д.), получится структура данных "стек". + +Как показано на рисунке ниже, верхнюю часть стопки элементов мы называем "вершиной стека", а нижнюю - "основанием стека". Операция добавления элемента на вершину называется "push", а операция удаления верхнего элемента - "pop". + +![Правило LIFO для стека](stack.assets/stack_operations.png) + +## Основные операции со стеком + +Основные операции со стеком показаны в таблице ниже. Конкретные имена методов зависят от используемого языка программирования. Здесь в качестве примера используются распространенные названия `push()` , `pop()` и `peek()` . + +

Таблица   Эффективность операций со стеком

+ +| Метод | Описание | Временная сложность | +| -------- | --------------------------------- | ------------------- | +| `push()` | Поместить элемент в стек (на вершину) | $O(1)$ | +| `pop()` | Извлечь верхний элемент стека | $O(1)$ | +| `peek()` | Просмотреть верхний элемент | $O(1)$ | + +Обычно мы можем просто использовать встроенный стек, предоставляемый языком программирования. Однако в некоторых языках специальный класс стека может отсутствовать. В таком случае можно использовать "массив" или "связный список" этого языка как стек и в логике программы игнорировать операции, не относящиеся к стеку. + +=== "Python" + + ```python title="stack.py" + # Инициализация стека + # В Python нет встроенного класса стека, поэтому можно использовать list как стек + stack: list[int] = [] + + # Поместить элементы в стек + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + # Просмотреть верхний элемент + peek: int = stack[-1] + + # Извлечь элемент + pop: int = stack.pop() + + # Получить длину стека + size: int = len(stack) + + # Проверить, пуст ли стек + is_empty: bool = len(stack) == 0 + ``` + +=== "C++" + + ```cpp title="stack.cpp" + /* Инициализация стека */ + stack stack; + + /* Поместить элементы в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Просмотреть верхний элемент */ + int top = stack.top(); + + /* Извлечь элемент */ + stack.pop(); // Без возвращаемого значения + + /* Получить длину стека */ + int size = stack.size(); + + /* Проверить, пуст ли стек */ + bool empty = stack.empty(); + ``` + +=== "Java" + + ```java title="stack.java" + /* Инициализация стека */ + Stack stack = new Stack<>(); + + /* Поместить элементы в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Просмотреть верхний элемент */ + int peek = stack.peek(); + + /* Извлечь элемент */ + int pop = stack.pop(); + + /* Получить длину стека */ + int size = stack.size(); + + /* Проверить, пуст ли стек */ + boolean isEmpty = stack.isEmpty(); + ``` + +=== "C#" + + ```csharp title="stack.cs" + /* Инициализация стека */ + Stack stack = new(); + + /* Поместить элементы в стек */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + + /* Просмотреть верхний элемент */ + int peek = stack.Peek(); + + /* Извлечь элемент */ + int pop = stack.Pop(); + + /* Получить длину стека */ + int size = stack.Count; + + /* Проверить, пуст ли стек */ + bool isEmpty = stack.Count == 0; + ``` + +=== "Go" + + ```go title="stack_test.go" + /* Инициализация стека */ + // В Go рекомендуется использовать Slice как стек + var stack []int + + /* Поместить элементы в стек */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + + /* Просмотреть верхний элемент */ + peek := stack[len(stack)-1] + + /* Извлечь элемент */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + /* Получить длину стека */ + size := len(stack) + + /* Проверить, пуст ли стек */ + isEmpty := len(stack) == 0 + ``` + +=== "Swift" + + ```swift title="stack.swift" + /* Инициализация стека */ + // В Swift нет встроенного класса стека, поэтому можно использовать Array как стек + var stack: [Int] = [] + + /* Поместить элементы в стек */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + /* Просмотреть верхний элемент */ + let peek = stack.last! + + /* Извлечь элемент */ + let pop = stack.removeLast() + + /* Получить длину стека */ + let size = stack.count + + /* Проверить, пуст ли стек */ + let isEmpty = stack.isEmpty + ``` + +=== "JS" + + ```javascript title="stack.js" + /* Инициализация стека */ + // В JavaScript нет встроенного класса стека, поэтому можно использовать Array как стек + const stack = []; + + /* Поместить элементы в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Просмотреть верхний элемент */ + const peek = stack[stack.length-1]; + + /* Извлечь элемент */ + const pop = stack.pop(); + + /* Получить длину стека */ + const size = stack.length; + + /* Проверить, пуст ли стек */ + const is_empty = stack.length === 0; + ``` + +=== "TS" + + ```typescript title="stack.ts" + /* Инициализация стека */ + // В TypeScript нет встроенного класса стека, поэтому можно использовать Array как стек + const stack: number[] = []; + + /* Поместить элементы в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Просмотреть верхний элемент */ + const peek = stack[stack.length - 1]; + + /* Извлечь элемент */ + const pop = stack.pop(); + + /* Получить длину стека */ + const size = stack.length; + + /* Проверить, пуст ли стек */ + const is_empty = stack.length === 0; + ``` + +=== "Dart" + + ```dart title="stack.dart" + /* Инициализация стека */ + // В Dart нет встроенного класса стека, поэтому можно использовать List как стек + List stack = []; + + /* Поместить элементы в стек */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + + /* Просмотреть верхний элемент */ + int peek = stack.last; + + /* Извлечь элемент */ + int pop = stack.removeLast(); + + /* Получить длину стека */ + int size = stack.length; + + /* Проверить, пуст ли стек */ + bool isEmpty = stack.isEmpty; + ``` + +=== "Rust" + + ```rust title="stack.rs" + /* Инициализация стека */ + // Используем Vec как стек + let mut stack: Vec = Vec::new(); + + /* Поместить элементы в стек */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* Просмотреть верхний элемент */ + let top = stack.last().unwrap(); + + /* Извлечь элемент */ + let pop = stack.pop().unwrap(); + + /* Получить длину стека */ + let size = stack.len(); + + /* Проверить, пуст ли стек */ + let is_empty = stack.is_empty(); + ``` + +=== "C" + + ```c title="stack.c" + // В C нет встроенного стека + ``` + +=== "Kotlin" + + ```kotlin title="stack.kt" + /* Инициализация стека */ + val stack = Stack() + + /* Поместить элементы в стек */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + + /* Просмотреть верхний элемент */ + val peek = stack.peek() + + /* Извлечь элемент */ + val pop = stack.pop() + + /* Получить длину стека */ + val size = stack.size + + /* Проверить, пуст ли стек */ + val isEmpty = stack.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="stack.rb" + # Инициализация стека + # В Ruby нет встроенного класса стека, поэтому можно использовать Array как стек + stack = [] + + # Поместить элементы в стек + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + + # Просмотреть верхний элемент + peek = stack.last + + # Извлечь элемент + pop = stack.pop + + # Получить длину стека + size = stack.length + + # Проверить, пуст ли стек + is_empty = stack.empty? + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20%23%20%D0%92%20Python%20%D0%BD%D0%B5%D1%82%20%D0%B2%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20list%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BA%D0%B0%D0%BA%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B2%20%D1%81%D1%82%D0%B5%D0%BA%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%D1%81%D1%82%D0%B5%D0%BA%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%D0%92%D0%B5%D1%80%D1%85%D0%BD%D0%B8%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%D0%98%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B8%D0%B7%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D0%BD%D1%83%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%D0%94%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D1%82%D0%B5%D0%BA%D0%B0%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%2C%20%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%D0%BB%D0%B8%20%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%9F%D1%83%D1%81%D1%82%20%D0%BB%D0%B8%20%D1%81%D1%82%D0%B5%D0%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## Реализация стека + +Чтобы глубже понять механизм работы стека, попробуем самостоятельно реализовать класс стека. + +Стек подчиняется принципу LIFO, поэтому мы можем добавлять и удалять элементы только на вершине. Однако и массив, и связный список позволяют добавлять и удалять элементы в произвольном месте. **Следовательно, стек можно рассматривать как ограниченный массив или связный список**. Иными словами, мы можем "скрыть" часть нерелевантных операций массива или списка, так чтобы внешняя логика соответствовала свойствам стека. + +### Реализация на основе связного списка + +Если реализовывать стек на основе связного списка, то головной узел списка можно рассматривать как вершину стека, а хвостовой - как основание. + +Как показано на рисунке ниже, для операции push достаточно вставить элемент в голову связного списка. Такой способ вставки называется "вставкой в голову". Для операции pop достаточно удалить головной узел из списка. + +=== "LinkedListStack" + ![Операции push и pop в реализации стека на связном списке](stack.assets/linkedlist_stack_step1.png) + +=== "push()" + ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) + +=== "pop()" + ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) + +Ниже приведен пример кода реализации стека на основе связного списка: + +```src +[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} +``` + +### Реализация на основе массива + +Если реализовывать стек на основе массива, то хвост массива можно рассматривать как вершину стека. Как показано на рисунке ниже, операции push и pop соответствуют добавлению элемента в конец массива и удалению элемента из конца, обе имеют временную сложность $O(1)$ . + +=== "ArrayStack" + ![Операции push и pop в реализации стека на массиве](stack.assets/array_stack_step1.png) + +=== "push()" + ![array_stack_push](stack.assets/array_stack_step2_push.png) + +=== "pop()" + ![array_stack_pop](stack.assets/array_stack_step3_pop.png) + +Поскольку количество элементов, помещаемых в стек, может непрерывно расти, мы можем использовать динамический массив и тем самым не заниматься расширением массива вручную. Ниже приведен пример кода: + +```src +[file]{array_stack}-[class]{array_stack}-[func]{} +``` + +## Сравнение двух реализаций + +**Поддерживаемые операции** + +Обе реализации поддерживают все операции, определенные для стека. Реализация на массиве дополнительно позволяет выполнять произвольный доступ, но это уже выходит за рамки определения стека и обычно не используется. + +**Временная эффективность** + +В реализации на массиве и push, и pop выполняются в заранее выделенной непрерывной памяти, которая хорошо использует локальность кэша, поэтому такие операции обычно эффективнее. Однако если при push емкость массива оказывается превышена, включается механизм расширения, и временная сложность конкретно этой операции push становится $O(n)$ . + +В реализации на связном списке расширение выполняется очень гибко, и проблемы падения эффективности из-за расширения массива здесь нет. Но сама операция push требует инициализации объекта-узла и изменения указателей, поэтому в среднем она немного менее эффективна. Впрочем, если помещаемые в стек элементы уже сами являются объектами-узлами, шаг инициализации можно пропустить и тем самым повысить эффективность. + +Итак, когда элементами, помещаемыми и извлекаемыми из стека, являются базовые типы данных, например `int` или `double` , можно сделать следующие выводы. + +- Стек на основе массива теряет в эффективности в моменты расширения, но поскольку расширение происходит редко, его средняя эффективность выше. +- Стек на основе связного списка может обеспечивать более стабильную производительность. + +**Пространственная эффективность** + +При инициализации списка система выделяет "начальную емкость", которая может превышать реальную потребность. Кроме того, механизм расширения обычно увеличивает емкость по некоторому коэффициенту (например в 2 раза), и расширенная емкость тоже может оказаться больше фактически необходимой. Поэтому **реализация стека на основе массива может приводить к некоторым потерям памяти**. + +Однако, поскольку узлы связного списка должны дополнительно хранить указатели, **узлы списка сами по себе занимают больше пространства**. + +В итоге нельзя просто сказать, какая из реализаций более экономна по памяти; это нужно анализировать в контексте конкретной задачи. + +## Типичные применения стека + +- **Кнопки "назад" и "вперед" в браузере, undo и redo в программах**. Каждый раз, когда мы открываем новую страницу, браузер помещает предыдущую страницу в стек, чтобы по операции "назад" можно было вернуться к ней. Операция "назад" по сути является pop. Если нужно одновременно поддерживать и "назад", и "вперед", то обычно используются два стека. +- **Управление памятью программы**. Каждый раз при вызове функции система помещает на вершину стека стековый кадр, в котором хранится контекст функции. В рекурсивной функции на этапе углубления рекурсии непрерывно выполняются push-операции, а на этапе возврата - pop-операции. diff --git a/ru/docs/chapter_stack_and_queue/summary.md b/ru/docs/chapter_stack_and_queue/summary.md new file mode 100644 index 000000000..6c9267786 --- /dev/null +++ b/ru/docs/chapter_stack_and_queue/summary.md @@ -0,0 +1,31 @@ +# Краткие итоги + +### Основные моменты + +- Стек - это структура данных, следующая правилу "последним пришел - первым вышел", и его можно реализовать с помощью массива или связного списка. +- С точки зрения временной эффективности реализация стека на массиве обычно работает быстрее в среднем, но во время расширения емкости временная сложность отдельной операции push может ухудшаться до $O(n)$ . Напротив, реализация стека на связном списке дает более стабильные характеристики. +- С точки зрения использования памяти реализация стека на массиве может приводить к некоторой потере пространства. Однако следует учитывать, что узлы связного списка занимают больше памяти, чем элементы массива. +- Очередь - это структура данных, следующая правилу "первым пришел - первым вышел", и ее также можно реализовать с помощью массива или связного списка. Сравнение временной и пространственной эффективности для очереди в целом приводит к тем же выводам, что и для стека. +- Двусторонняя очередь - это очередь с более высокой степенью свободы, которая позволяет добавлять и удалять элементы с обеих сторон. + +### Q & A + +**Q**: Реализованы ли кнопки "вперед" и "назад" в браузере с помощью двусвязного списка? + +По сути, функция переходов "вперед/назад" в браузере отражает логику "стека". Когда пользователь открывает новую страницу, она помещается на вершину стека; когда пользователь нажимает кнопку "назад", эта страница снимается с вершины стека. Двусторонняя очередь позволяет удобно реализовать некоторые дополнительные операции, об этом уже упоминалось в разделе "Двусторонняя очередь". + +**Q**: Нужно ли освобождать память узла после извлечения его из стека? + +Если извлеченный узел еще понадобится, память освобождать не нужно. Если он больше не нужен, то в языках `Java` и `Python` есть автоматический сборщик мусора, поэтому ручное освобождение памяти не требуется; в `C` и `C++` память нужно освобождать вручную. + +**Q**: Двусторонняя очередь выглядит как два соединенных стека. Для чего она нужна? + +Двусторонняя очередь похожа на комбинацию стека и очереди или на два соединенных стека. Она выражает логику "стек + очередь", поэтому может покрыть все применения стека и очереди и при этом остается более гибкой. + +**Q**: Как именно реализуются отмена (undo) и повтор (redo)? + +Используются два стека: стек `A` для отмены и стек `B` для повтора. + +1. Каждый раз, когда пользователь выполняет действие, это действие помещается в стек `A` , а стек `B` очищается. +2. Когда пользователь выполняет "undo", последнее действие извлекается из стека `A` и помещается в стек `B` . +3. Когда пользователь выполняет "redo", последнее действие извлекается из стека `B` и помещается обратно в стек `A` . diff --git a/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png new file mode 100644 index 000000000..f1387c98f Binary files /dev/null and b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png differ diff --git a/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png new file mode 100644 index 000000000..c88639fe2 Binary files /dev/null and b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png differ diff --git a/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png new file mode 100644 index 000000000..6a5631681 Binary files /dev/null and b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png differ diff --git a/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png new file mode 100644 index 000000000..93f94c910 Binary files /dev/null and b/ru/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png differ diff --git a/ru/docs/chapter_tree/array_representation_of_tree.md b/ru/docs/chapter_tree/array_representation_of_tree.md new file mode 100644 index 000000000..f2c1034d6 --- /dev/null +++ b/ru/docs/chapter_tree/array_representation_of_tree.md @@ -0,0 +1,160 @@ +# Представление двоичного дерева массивом + +В представлении через связную структуру единицей хранения двоичного дерева является узел `TreeNode` , а между узлами существуют связи через указатели. В предыдущем разделе были рассмотрены основные операции двоичного дерева в таком представлении. + +Возникает вопрос: можно ли представить двоичное дерево с помощью массива? Ответ: да. + +## Представление идеального двоичного дерева + +Сначала разберем простой случай. Если дана идеальная двоичная структура и все ее узлы хранятся в массиве в порядке обхода по уровням, то каждому узлу будет соответствовать единственный индекс массива. + +Из свойств обхода по уровням можно вывести "формулу соответствия" между индексом родителя и индексами дочерних узлов: **если индекс некоторого узла равен $i$ , то индекс его левого дочернего узла равен $2i + 1$ , а правого - $2i + 2$** . На рисунке ниже показано соответствие между индексами разных узлов. + +![Представление идеального двоичного дерева массивом](array_representation_of_tree.assets/array_representation_binary_tree.png) + +**Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре** . Имея любой узел в массиве, мы можем по формуле получить доступ к его левому и правому дочерним узлам. + +## Представление произвольного двоичного дерева + +Идеальное двоичное дерево - лишь частный случай; в обычной двоичной структуре на промежуточных уровнях часто существует множество `None` . Поскольку последовательность обхода по уровням не содержит этих `None` , мы не можем по одной лишь этой последовательности определить их количество и расположение. **Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева**. + +Как показано на рисунке ниже, для неполной двоичной структуры описанный выше способ представления массивом уже перестает работать. + +![Одной последовательности обхода по уровням соответствуют разные двоичные структуры](array_representation_of_tree.assets/array_representation_without_empty.png) + +Чтобы решить эту проблему, **мы можем явно записывать все `None` в последовательности обхода по уровням** . Как показано на рисунке ниже, после такой обработки последовательность обхода по уровням уже сможет однозначно задавать двоичное дерево. Пример кода приведен ниже: + +=== "Python" + + ```python title="" + # Представление двоичного дерева массивом + # Используем None для обозначения пустых позиций + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ``` + +=== "C++" + + ```cpp title="" + /* Представление двоичного дерева массивом */ + // Используем максимальное значение int, INT_MAX, для обозначения пустых позиций + vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Java" + + ```java title="" + /* Представление двоичного дерева массивом */ + // Используя обертку Integer для int, можно применять null для обозначения пустых позиций + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* Представление двоичного дерева массивом */ + // Используя nullable-тип int? , можно применять null для обозначения пустых позиций + int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Go" + + ```go title="" + /* Представление двоичного дерева массивом */ + // Используем срез типа any, чтобы можно было применять nil для обозначения пустых позиций + tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + ``` + +=== "Swift" + + ```swift title="" + /* Представление двоичного дерева массивом */ + // Используя nullable-тип Int? , можно применять nil для обозначения пустых позиций + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "JS" + + ```javascript title="" + /* Представление двоичного дерева массивом */ + // Используем null для обозначения пустых позиций + let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "TS" + + ```typescript title="" + /* Представление двоичного дерева массивом */ + // Используем null для обозначения пустых позиций + let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Dart" + + ```dart title="" + /* Представление двоичного дерева массивом */ + // Используя nullable-тип int? , можно применять null для обозначения пустых позиций + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Rust" + + ```rust title="" + /* Представление двоичного дерева массивом */ + // Используем None для обозначения пустых позиций + let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; + ``` + +=== "C" + + ```c title="" + /* Представление двоичного дерева массивом */ + // Используем максимальное значение int для обозначения пустых позиций, поэтому узлы не должны принимать значение INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Представление двоичного дерева массивом */ + // Используем null для обозначения пустых позиций + val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + ``` + +=== "Ruby" + + ```ruby title="" + ### Представление двоичного дерева массивом ### + # Используем nil для обозначения пустых позиций + tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +![Представление произвольного двоичного дерева массивом](array_representation_of_tree.assets/array_representation_with_empty.png) + +Стоит отметить, что **полное двоичное дерево очень удобно представлять массивом** . Если вспомнить определение полного двоичного дерева, то `None` появляются только на самом нижнем уровне и справа, **а значит, все `None` обязательно находятся в конце последовательности обхода по уровням**. + +Это означает, что при представлении полного двоичного дерева массивом можно не хранить все `None` , что очень удобно. На рисунке ниже приведен пример. + +![Представление полного двоичного дерева массивом](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) + +Ниже приведен код реализации двоичного дерева, представленного массивом. Он включает следующие операции. + +- Для заданного узла получить его значение, левого дочернего узла, правого дочернего узла и родительский узел. +- Получить последовательности прямого, симметричного, обратного обходов и обхода по уровням. + +```src +[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} +``` + +## Преимущества и ограничения + +Представление двоичного дерева массивом имеет в основном следующие преимущества. + +- Массив хранится в непрерывной области памяти, хорошо работает с кешем и обеспечивает высокую скорость доступа и обхода. +- Не нужно хранить указатели, поэтому память расходуется экономнее. +- Разрешается произвольный доступ к узлам. + +Однако у представления массивом есть и некоторые ограничения. + +- Для хранения массива требуется непрерывная область памяти, поэтому такой способ не подходит для деревьев с очень большим объемом данных. +- Добавление и удаление узлов приходится реализовывать через вставку и удаление элементов массива, а это не слишком эффективно. +- Когда в двоичном дереве имеется большое число `None` , доля действительно полезных данных в массиве оказывается низкой, и эффективность использования пространства падает. diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png new file mode 100644 index 000000000..93b0617f0 Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png new file mode 100644 index 000000000..ee86a2490 Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png new file mode 100644 index 000000000..00528037d Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png new file mode 100644 index 000000000..0c2ae54db Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png new file mode 100644 index 000000000..3a0b2795c Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png new file mode 100644 index 000000000..78c77404c Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png new file mode 100644 index 000000000..981a8b3da Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png new file mode 100644 index 000000000..426362f32 Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png new file mode 100644 index 000000000..15028da52 Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png new file mode 100644 index 000000000..1072ade09 Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png new file mode 100644 index 000000000..9a022a93e Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png differ diff --git a/ru/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png b/ru/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png new file mode 100644 index 000000000..4474707cf Binary files /dev/null and b/ru/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png differ diff --git a/ru/docs/chapter_tree/avl_tree.md b/ru/docs/chapter_tree/avl_tree.md new file mode 100644 index 000000000..c39664f8e --- /dev/null +++ b/ru/docs/chapter_tree/avl_tree.md @@ -0,0 +1,358 @@ +# AVL-дерево * + +В разделе "Двоичное дерево поиска" мы упоминали, что после многократных операций вставки и удаления узлов двоичное дерево поиска может выродиться в связный список. В таком случае временная сложность всех операций ухудшается с $O(\log n)$ до $O(n)$ . + +Как показано на рисунке ниже, после двух операций удаления узлов это двоичное дерево поиска вырождается в связный список. + +![Деградация AVL-дерева после удаления узлов](avl_tree.assets/avltree_degradation_from_removing_node.png) + +Другой пример: если в идеальное двоичное дерево, показанное на рисунке ниже, вставить два узла, то дерево сильно наклонится влево, а временная сложность поиска тоже ухудшится. + +![Деградация AVL-дерева после вставки узлов](avl_tree.assets/avltree_degradation_from_inserting_node.png) + +В 1962 году Г. М. Adelson-Velsky и Е. М. Landis в статье "An algorithm for the organization of information" предложили AVL-дерево. В статье подробно описан набор операций, гарантирующий, что при непрерывном добавлении и удалении узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне $O(\log n)$ . Иначе говоря, в сценариях, где часто выполняются вставка, удаление, поиск и изменение, AVL-дерево всегда поддерживает эффективную работу с данными и потому имеет высокую практическую ценность. + +## Распространенные термины AVL-дерева + +AVL-дерево одновременно является и двоичным деревом поиска, и сбалансированным двоичным деревом, то есть одновременно удовлетворяет всем свойствам обеих этих структур. Поэтому AVL-дерево является разновидностью сбалансированного двоичного дерева поиска (balanced binary search tree). + +### Высота узла + +Поскольку операции AVL-дерева требуют получать высоту узла, нам нужно добавить в класс узла переменную `height` : + +=== "Python" + + ```python title="" + class TreeNode: + """Класс узла AVL-дерева""" + def __init__(self, val: int): + self.val: int = val # Значение узла + self.height: int = 0 # Высота узла + self.left: TreeNode | None = None # Ссылка на левого дочернего узла + self.right: TreeNode | None = None # Ссылка на правого дочернего узла + ``` + +=== "C++" + + ```cpp title="" + /* Класс узла AVL-дерева */ + struct TreeNode { + int val{}; // Значение узла + int height = 0; // Высота узла + TreeNode *left{}; // Левый дочерний узел + TreeNode *right{}; // Правый дочерний узел + TreeNode() = default; + explicit TreeNode(int x) : val(x){} + }; + ``` + +=== "Java" + + ```java title="" + /* Класс узла AVL-дерева */ + class TreeNode { + public int val; // Значение узла + public int height; // Высота узла + public TreeNode left; // Левый дочерний узел + public TreeNode right; // Правый дочерний узел + public TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* Класс узла AVL-дерева */ + class TreeNode(int? x) { + public int? val = x; // Значение узла + public int height; // Высота узла + public TreeNode? left; // Ссылка на левого дочернего узла + public TreeNode? right; // Ссылка на правого дочернего узла + } + ``` + +=== "Go" + + ```go title="" + /* Структура узла AVL-дерева */ + type TreeNode struct { + Val int // Значение узла + Height int // Высота узла + Left *TreeNode // Ссылка на левого дочернего узла + Right *TreeNode // Ссылка на правого дочернего узла + } + ``` + +=== "Swift" + + ```swift title="" + /* Класс узла AVL-дерева */ + class TreeNode { + var val: Int // Значение узла + var height: Int // Высота узла + var left: TreeNode? // Левый дочерний узел + var right: TreeNode? // Правый дочерний узел + + init(x: Int) { + val = x + height = 0 + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Класс узла AVL-дерева */ + class TreeNode { + val; // Значение узла + height; // Высота узла + left; // Указатель на левого дочернего узла + right; // Указатель на правого дочернего узла + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Класс узла AVL-дерева */ + class TreeNode { + val: number; // Значение узла + height: number; // Высота узла + left: TreeNode | null; // Указатель на левого дочернего узла + right: TreeNode | null; // Указатель на правого дочернего узла + constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Класс узла AVL-дерева */ + class TreeNode { + int val; // Значение узла + int height; // Высота узла + TreeNode? left; // Левый дочерний узел + TreeNode? right; // Правый дочерний узел + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Структура узла AVL-дерева */ + struct TreeNode { + val: i32, // Значение узла + height: i32, // Высота узла + left: Option>>, // Левый дочерний узел + right: Option>>, // Правый дочерний узел + } + + impl TreeNode { + /* Конструктор */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* Структура узла AVL-дерева */ + typedef struct TreeNode { + int val; + int height; + struct TreeNode *left; + struct TreeNode *right; + } TreeNode; + + /* Конструктор */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Класс узла AVL-дерева */ + class TreeNode(val _val: Int) { // Значение узла + val height: Int = 0 // Высота узла + val left: TreeNode? = null // Левый дочерний узел + val right: TreeNode? = null // Правый дочерний узел + } + ``` + +=== "Ruby" + + ```ruby title="" + ### Класс узла AVL-дерева ### + class TreeNode + attr_accessor :val # Значение узла + attr_accessor :height # Высота узла + attr_accessor :left # Ссылка на левого дочернего узла + attr_accessor :right # Ссылка на правого дочернего узла + + def initialize(val) + @val = val + @height = 0 + end + end + ``` + +"Высота узла" означает расстояние от этого узла до самого удаленного листового узла, то есть число пройденных "ребер". Особенно важно помнить, что высота листового узла равна $0$ , а высота пустого узла равна $-1$ . Мы создадим две вспомогательные функции: одну для получения высоты узла, другую для ее обновления: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{update_height} +``` + +### Баланс-фактор узла + +Баланс-фактор (balance factor) узла определяется как высота левого поддерева минус высота правого поддерева; при этом баланс-фактор пустого узла считается равным $0$ . Мы также инкапсулируем получение баланс-фактора в отдельную функцию, чтобы потом было удобнее ее использовать: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} +``` + +!!! tip + + Пусть баланс-фактор равен $f$ ; тогда для любого узла AVL-дерева выполняется $-1 \le f \le 1$ . + +## Вращения AVL-дерева + +Особенность AVL-дерева заключается в операции "вращения", которая позволяет заново сбалансировать разбалансированный узел, не нарушая последовательность симметричного обхода двоичного дерева. Иначе говоря, **операция вращения одновременно сохраняет свойство "двоичного дерева поиска" и возвращает дерево в состояние "сбалансированного двоичного дерева"**. + +Узлы, для которых абсолютное значение баланс-фактора больше $1$ , мы называем "разбалансированными узлами". В зависимости от вида разбаланса вращения делятся на четыре типа: правое вращение, левое вращение, сначала левое затем правое, и сначала правое затем левое. Ниже разберем их подробно. + +### Правое вращение + +Как показано на рисунках ниже, под узлом указан его баланс-фактор. Если двигаться снизу вверх, то первым разбалансированным узлом в двоичном дереве будет "узел 3". Рассмотрим поддерево с этим узлом в качестве корня, обозначим данный узел как `node` , его левого дочернего узла как `child` и выполним "правое вращение". После завершения правого вращения поддерево снова станет сбалансированным и при этом сохранит свойство двоичного дерева поиска. + +=== "<1>" + ![Шаги правого вращения](avl_tree.assets/avltree_right_rotate_step1.png) + +=== "<2>" + ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) + +=== "<3>" + ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) + +=== "<4>" + ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) + +Как показано на рисунке ниже, когда у узла `child` есть правый дочерний узел, который мы обозначим как `grand_child` , в правое вращение нужно добавить еще один шаг: сделать `grand_child` левым дочерним узлом `node` . + +![Правое вращение при наличии grand_child](avl_tree.assets/avltree_right_rotate_with_grandchild.png) + +"Поворот вправо" - это лишь образное описание; в реальности он реализуется через изменение указателей узлов. Код приведен ниже: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} +``` + +### Левое вращение + +Соответственно, если рассмотреть "зеркальную" версию приведенного выше разбалансированного двоичного дерева, то понадобится выполнить "левое вращение", показанное на рисунке ниже. + +![Левое вращение](avl_tree.assets/avltree_left_rotate.png) + +По той же причине, когда у узла `child` есть левый дочерний узел, который обозначим как `grand_child` , в левое вращение также требуется добавить шаг: сделать `grand_child` правым дочерним узлом `node` . + +![Левое вращение при наличии grand_child](avl_tree.assets/avltree_left_rotate_with_grandchild.png) + +Можно заметить, что **операции правого и левого вращения логически зеркально симметричны, и два вида разбаланса, которые они исправляют, тоже симметричны**. Поэтому, опираясь на эту симметрию, достаточно заменить в коде правого вращения все `left` на `right` , а все `right` на `left` , чтобы получить реализацию левого вращения: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} +``` + +### Сначала левое, затем правое вращение + +Для разбалансированного узла 3 на рисунке ниже ни одно лишь левое вращение, ни одно лишь правое вращение не способны вернуть поддерево в баланс. В этом случае нужно сначала выполнить "левое вращение" для `child` , а затем выполнить "правое вращение" для `node` . + +![Сначала левое, затем правое вращение](avl_tree.assets/avltree_left_right_rotate.png) + +### Сначала правое, затем левое вращение + +Как показано на рисунке ниже, для зеркальной ситуации предыдущего разбалансированного двоичного дерева нужно сначала выполнить "правое вращение" для `child` , а затем "левое вращение" для `node` . + +![Сначала правое, затем левое вращение](avl_tree.assets/avltree_right_left_rotate.png) + +### Выбор вращения + +Четыре вида разбаланса, показанные на рисунке ниже, по одному соответствуют рассмотренным выше случаям; для них соответственно требуются правое вращение, сначала левое затем правое, сначала правое затем левое и левое вращение. + +![Четыре случая вращений AVL-дерева](avl_tree.assets/avltree_rotation_cases.png) + +Как показано в таблице ниже, мы определяем, какому из этих четырех случаев соответствует разбалансированный узел, по знаку баланс-фактора самого разбалансированного узла и по знаку баланс-фактора дочернего узла на более высокой стороне. + +

Таблица   Условия выбора для четырех случаев вращений

+ +| Баланс-фактор разбалансированного узла | Баланс-фактор дочернего узла | Какое вращение использовать | +| -------------------------------------- | ---------------------------- | --------------------------- | +| $> 1$ (левостороннее дерево) | $\geq 0$ | Правое вращение | +| $> 1$ (левостороннее дерево) | $<0$ | Сначала левое, затем правое | +| $< -1$ (правостороннее дерево) | $\leq 0$ | Левое вращение | +| $< -1$ (правостороннее дерево) | $>0$ | Сначала правое, затем левое | + +Для удобства мы инкапсулируем операцию вращения в отдельную функцию. **С помощью этой функции можно выполнить корректное вращение для любой ситуации разбаланса и снова привести узел в сбалансированное состояние**. Код приведен ниже: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{rotate} +``` + +## Распространенные операции AVL-дерева + +### Вставка узла + +Операция вставки узла в AVL-дерево по основному процессу похожа на вставку в двоичное дерево поиска. Единственная разница состоит в том, что после вставки в AVL-дерево на пути от вставленного узла к корню может появиться цепочка разбалансированных узлов. Поэтому **начиная от этого узла, мы должны выполнять вращения снизу вверх, чтобы вернуть в баланс все разбалансированные узлы**. Код приведен ниже: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} +``` + +### Удаление узла + +Аналогично, на основе метода удаления узла из двоичного дерева поиска нужно добавить вращения снизу вверх, чтобы восстановить баланс всех разбалансированных узлов. Код приведен ниже: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} +``` + +### Поиск узла + +Операция поиска узла в AVL-дереве совпадает с поиском в двоичном дереве поиска, поэтому здесь она повторно не рассматривается. + +## Типичные применения AVL-дерева + +- Организация и хранение больших массивов данных, особенно в сценариях с частым поиском и относительно редкими вставками и удалениями. +- Использование при построении индексных систем в базах данных. +- Красно-черное дерево тоже является распространенным видом сбалансированного двоичного дерева поиска. По сравнению с AVL-деревом условия баланса у красно-черного дерева мягче, поэтому при вставке и удалении требуется меньше вращений, а средняя эффективность операций добавления и удаления выше. diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png b/ru/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png new file mode 100644 index 000000000..7eb767d5b Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png new file mode 100644 index 000000000..c5c1b30de Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png new file mode 100644 index 000000000..49e5144e7 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_insert.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_insert.png new file mode 100644 index 000000000..26f6b8bbf Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_insert.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png new file mode 100644 index 000000000..b344dce1b Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png new file mode 100644 index 000000000..a39ddfdc4 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png new file mode 100644 index 000000000..b7fa9143f Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png new file mode 100644 index 000000000..efd772865 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png new file mode 100644 index 000000000..0ae00b2ac Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png new file mode 100644 index 000000000..189acb6c9 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png new file mode 100644 index 000000000..adbe100e9 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png new file mode 100644 index 000000000..0d0aee43c Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png new file mode 100644 index 000000000..2fa5f6228 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png new file mode 100644 index 000000000..23d1666c9 Binary files /dev/null and b/ru/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png differ diff --git a/ru/docs/chapter_tree/binary_search_tree.md b/ru/docs/chapter_tree/binary_search_tree.md new file mode 100644 index 000000000..31f1d0ef7 --- /dev/null +++ b/ru/docs/chapter_tree/binary_search_tree.md @@ -0,0 +1,129 @@ +# Двоичное дерево поиска + +Как показано на рисунке ниже, двоичное дерево поиска (binary search tree) удовлетворяет следующим условиям. + +1. Для корневого узла все значения в левом поддереве меньше значения корневого узла, а все значения в правом поддереве больше значения корневого узла. +2. Левое и правое поддеревья любого узла также являются двоичными деревьями поиска, то есть тоже удовлетворяют условию `1.` . + +![Двоичное дерево поиска](binary_search_tree.assets/binary_search_tree.png) + +## Операции с двоичным деревом поиска + +Мы инкапсулируем двоичное дерево поиска в класс `BinarySearchTree` и объявляем переменную-член `root` , которая указывает на корневой узел дерева. + +### Поиск узла + +Для заданного целевого значения узла `num` можно выполнить поиск, опираясь на свойства двоичного дерева поиска. Как показано на рисунках ниже, мы объявляем узел `cur` , стартуем от корня дерева `root` и циклически сравниваем значения `cur.val` и `num` . + +- Если `cur.val < num` , это означает, что целевой узел находится в правом поддереве `cur` , поэтому выполняем `cur = cur.right` . +- Если `cur.val > num` , это означает, что целевой узел находится в левом поддереве `cur` , поэтому выполняем `cur = cur.left` . +- Если `cur.val = num` , это означает, что целевой узел найден, и мы выходим из цикла, возвращая этот узел. + +=== "<1>" + ![Пример поиска узла в двоичном дереве поиска](binary_search_tree.assets/bst_search_step1.png) + +=== "<2>" + ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) + +=== "<3>" + ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) + +=== "<4>" + ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) + +Операция поиска в двоичном дереве поиска работает по тому же принципу, что и бинарный поиск: на каждом шаге она отбрасывает половину вариантов. Число итераций не превосходит высоты двоичного дерева, а когда дерево сбалансировано, требуется $O(\log n)$ времени. Пример кода приведен ниже: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} +``` + +### Вставка узла + +Пусть дан элемент `num` , который нужно вставить. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево < корень < правое поддерево", процесс вставки выглядит следующим образом. + +1. **Найти позицию для вставки**: как и в операции поиска, начиная от корня, мы циклически спускаемся вниз в зависимости от соотношения между текущим значением узла и `num` , пока не выйдем за листовой узел (то есть не дойдем до `None` ). +2. **Вставить узел в найденную позицию**: инициализировать узел `num` и поставить его на место этого `None` . + +![Вставка узла в двоичное дерево поиска](binary_search_tree.assets/bst_insert.png) + +В реализации кода нужно обратить внимание на следующие два момента. + +- Двоичное дерево поиска не допускает дублирующихся узлов, иначе его определение будет нарушено. Поэтому если вставляемый узел уже существует в дереве, вставка не выполняется и функция сразу возвращается. +- Чтобы реализовать вставку, нам нужно использовать узел `pre` для сохранения узла предыдущей итерации цикла. Тогда, когда обход дойдет до `None` , мы сможем получить его родителя и завершить вставку. + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} +``` + +Как и поиск узла, вставка узла требует $O(\log n)$ времени. + +### Удаление узла + +Сначала нужно найти в двоичном дереве целевой узел, а затем удалить его. Как и при вставке, после удаления необходимо сохранить свойство двоичного дерева поиска: "левое поддерево < корень < правое поддерево". Поэтому в зависимости от числа дочерних узлов у удаляемого узла, то есть для случаев со степенью 0, 1 и 2, выполняются разные операции удаления. + +Как показано на рисунке ниже, когда степень удаляемого узла равна $0$ , это значит, что узел является листом и может быть удален напрямую. + +![Удаление узла в двоичном дереве поиска (степень 0)](binary_search_tree.assets/bst_remove_case1.png) + +Как показано на рисунке ниже, когда степень удаляемого узла равна $1$ , достаточно заменить удаляемый узел его дочерним узлом. + +![Удаление узла в двоичном дереве поиска (степень 1)](binary_search_tree.assets/bst_remove_case2.png) + +Когда степень удаляемого узла равна $2$ , мы уже не можем удалить его напрямую и должны использовать для замены другой узел. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево $<$ корень $<$ правое поддерево", **этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева**. + +Предположим, мы выбираем минимальный узел правого поддерева, то есть следующий узел в симметричном обходе. Тогда процесс удаления выглядит так. + +1. Найти следующий узел в "последовательности симметричного обхода" для удаляемого узла и обозначить его как `tmp` . +2. Значением `tmp` перезаписать значение удаляемого узла, а затем рекурсивно удалить узел `tmp` из дерева. + +=== "<1>" + ![Удаление узла в двоичном дереве поиска (степень 2)](binary_search_tree.assets/bst_remove_case3_step1.png) + +=== "<2>" + ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) + +=== "<3>" + ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) + +=== "<4>" + ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) + +Операция удаления узла также требует $O(\log n)$ времени, где поиск удаляемого узла стоит $O(\log n)$ , а получение следующего узла симметричного обхода также требует $O(\log n)$ . Пример кода приведен ниже: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} +``` + +### Упорядоченность симметричного обхода + +Как показано на рисунке ниже, симметричный обход двоичного дерева следует порядку "лево $\rightarrow$ корень $\rightarrow$ право", а двоичное дерево поиска удовлетворяет соотношению "левый дочерний узел $<$ корень $<$ правый дочерний узел". + +Это означает, что при симметричном обходе двоичного дерева поиска мы всегда сначала будем посещать следующий минимальный узел, и отсюда получается важное свойство: **последовательность симметричного обхода двоичного дерева поиска является возрастающей**. + +Используя это свойство возрастающей последовательности симметричного обхода, мы можем получить отсортированные данные из двоичного дерева поиска всего за $O(n)$ времени, без дополнительной сортировки, что очень эффективно. + +![Последовательность симметричного обхода двоичного дерева поиска](binary_search_tree.assets/bst_inorder_traversal.png) + +## Эффективность двоичного дерева поиска + +Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок, поэтому его производительность стабильна и высока. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска. + +

Таблица   Сравнение эффективности массива и дерева поиска

+ +| | Неупорядоченный массив | Двоичное дерево поиска | +| -------- | ---------------------- | ---------------------- | +| Поиск элемента | $O(n)$ | $O(\log n)$ | +| Вставка элемента | $O(1)$ | $O(\log n)$ | +| Удаление элемента | $O(n)$ | $O(\log n)$ | + +В идеальном случае двоичное дерево поиска является "сбалансированным", и тогда любой узел можно найти за $\log n$ итераций. + +Однако если в двоичное дерево поиска непрерывно вставлять и удалять узлы, оно может выродиться в связный список, как показано на рисунке ниже. Тогда временная сложность различных операций тоже вырождается до $O(n)$ . + +![Деградация двоичного дерева поиска](binary_search_tree.assets/bst_degradation.png) + +## Типичные применения двоичного дерева поиска + +- Используется как многоуровневый индекс в системах, обеспечивая эффективный поиск, вставку и удаление. +- Служит базовой структурой данных для некоторых поисковых алгоритмов. +- Применяется для хранения потока данных в отсортированном состоянии. diff --git a/ru/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png b/ru/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png new file mode 100644 index 000000000..4b74f1c44 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png new file mode 100644 index 000000000..b38ca2680 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png new file mode 100644 index 000000000..d49d6d7f8 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png new file mode 100644 index 000000000..507db3dc3 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png new file mode 100644 index 000000000..d7e5fd63e Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png b/ru/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png new file mode 100644 index 000000000..90e653016 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/full_binary_tree.png b/ru/docs/chapter_tree/binary_tree.assets/full_binary_tree.png new file mode 100644 index 000000000..26bb60c87 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/full_binary_tree.png differ diff --git a/ru/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png b/ru/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png new file mode 100644 index 000000000..a3ba5298c Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png differ diff --git a/ru/docs/chapter_tree/binary_tree.md b/ru/docs/chapter_tree/binary_tree.md new file mode 100644 index 000000000..1dafa53a7 --- /dev/null +++ b/ru/docs/chapter_tree/binary_tree.md @@ -0,0 +1,674 @@ +# Двоичное дерево + +Двоичное дерево (binary tree) - это нелинейная структура данных, представляющая отношения порождения между "предками" и "потомками" и отражающая логику "разделения надвое". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла. + +=== "Python" + + ```python title="" + class TreeNode: + """Класс узла двоичного дерева""" + def __init__(self, val: int): + self.val: int = val # Значение узла + self.left: TreeNode | None = None # Ссылка на левого дочернего узла + self.right: TreeNode | None = None # Ссылка на правого дочернего узла + ``` + +=== "C++" + + ```cpp title="" + /* Структура узла двоичного дерева */ + struct TreeNode { + int val; // Значение узла + TreeNode *left; // Указатель на левого дочернего узла + TreeNode *right; // Указатель на правого дочернего узла + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + }; + ``` + +=== "Java" + + ```java title="" + /* Класс узла двоичного дерева */ + class TreeNode { + int val; // Значение узла + TreeNode left; // Ссылка на левого дочернего узла + TreeNode right; // Ссылка на правого дочернего узла + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* Класс узла двоичного дерева */ + class TreeNode(int? x) { + public int? val = x; // Значение узла + public TreeNode? left; // Ссылка на левого дочернего узла + public TreeNode? right; // Ссылка на правого дочернего узла + } + ``` + +=== "Go" + + ```go title="" + /* Структура узла двоичного дерева */ + type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode + } + /* Конструктор */ + func NewTreeNode(v int) *TreeNode { + return &TreeNode{ + Left: nil, // Указатель на левого дочернего узла + Right: nil, // Указатель на правого дочернего узла + Val: v, // Значение узла + } + } + ``` + +=== "Swift" + + ```swift title="" + /* Класс узла двоичного дерева */ + class TreeNode { + var val: Int // Значение узла + var left: TreeNode? // Ссылка на левого дочернего узла + var right: TreeNode? // Ссылка на правого дочернего узла + + init(x: Int) { + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* Класс узла двоичного дерева */ + class TreeNode { + val; // Значение узла + left; // Указатель на левого дочернего узла + right; // Указатель на правого дочернего узла + constructor(val, left, right) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* Класс узла двоичного дерева */ + class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; // Значение узла + this.left = left === undefined ? null : left; // Ссылка на левого дочернего узла + this.right = right === undefined ? null : right; // Ссылка на правого дочернего узла + } + } + ``` + +=== "Dart" + + ```dart title="" + /* Класс узла двоичного дерева */ + class TreeNode { + int val; // Значение узла + TreeNode? left; // Ссылка на левого дочернего узла + TreeNode? right; // Ссылка на правого дочернего узла + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* Структура узла двоичного дерева */ + struct TreeNode { + val: i32, // Значение узла + left: Option>>, // Ссылка на левого дочернего узла + right: Option>>, // Ссылка на правого дочернего узла + } + + impl TreeNode { + /* Конструктор */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* Структура узла двоичного дерева */ + typedef struct TreeNode { + int val; // Значение узла + int height; // Высота узла + struct TreeNode *left; // Указатель на левого дочернего узла + struct TreeNode *right; // Указатель на правого дочернего узла + } TreeNode; + + /* Конструктор */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* Класс узла двоичного дерева */ + class TreeNode(val _val: Int) { // Значение узла + val left: TreeNode? = null // Ссылка на левого дочернего узла + val right: TreeNode? = null // Ссылка на правого дочернего узла + } + ``` + +=== "Ruby" + + ```ruby title="" + ### Класс узла двоичного дерева ### + class TreeNode + attr_accessor :val # Значение узла + attr_accessor :left # Ссылка на левого дочернего узла + attr_accessor :right # Ссылка на правого дочернего узла + + def initialize(val) + @val = val + end + end + ``` + +Каждый узел имеет две ссылки (указателя), которые соответственно указывают на левого дочернего узла (left-child node) и правого дочернего узла (right-child node); данный узел называется родительским узлом (parent node) для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется левым поддеревом (left subtree) этого узла; аналогично определяется правое поддерево (right subtree). + +**В двоичном дереве, кроме листовых узлов, все остальные узлы содержат дочерние узлы и непустые поддеревья**. Как показано на рисунке ниже, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него". + +![Родительский узел, дочерние узлы и поддеревья](binary_tree.assets/binary_tree_definition.png) + +## Распространенные термины двоичного дерева + +Распространенные термины двоичного дерева показаны на рисунке ниже. + +- Корневой узел (root node): узел, расположенный на верхнем уровне двоичного дерева и не имеющий родительского узла. +- Листовой узел (leaf node): узел без дочерних узлов; оба его указателя направлены на `None` . +- Ребро (edge): отрезок, соединяющий два узла, то есть ссылка (указатель) между узлами. +- Уровень (level) узла: увеличивается сверху вниз; уровень корневого узла равен 1 . +- Степень (degree) узла: число дочерних узлов данного узла. В двоичном дереве возможны степени 0, 1, 2 . +- Высота (height) двоичного дерева: число ребер от корневого узла до самого удаленного листового узла. +- Глубина (depth) узла: число ребер от корневого узла до данного узла. +- Высота (height) узла: число ребер от самого удаленного листового узла до данного узла. + +![Распространенные термины двоичного дерева](binary_tree.assets/binary_tree_terminology.png) + +!!! tip + + Обрати внимание: обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 . + +## Базовые операции двоичного дерева + +### Инициализация двоичного дерева + +Как и в связном списке, сначала инициализируются узлы, а затем между ними строятся ссылки (указатели). + +=== "Python" + + ```python title="binary_tree.py" + # Инициализация двоичного дерева + # Инициализация узлов + n1 = TreeNode(val=1) + n2 = TreeNode(val=2) + n3 = TreeNode(val=3) + n4 = TreeNode(val=4) + n5 = TreeNode(val=5) + # Построение ссылок (указателей) между узлами + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* Инициализация двоичного дерева */ + // Инициализация узлов + TreeNode* n1 = new TreeNode(1); + TreeNode* n2 = new TreeNode(2); + TreeNode* n3 = new TreeNode(3); + TreeNode* n4 = new TreeNode(4); + TreeNode* n5 = new TreeNode(5); + // Построение ссылок (указателей) между узлами + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Java" + + ```java title="binary_tree.java" + // Инициализация узлов + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Построение ссылок (указателей) между узлами + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* Инициализация двоичного дерева */ + // Инициализация узлов + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // Построение ссылок (указателей) между узлами + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* Инициализация двоичного дерева */ + // Инициализация узлов + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // Построение ссылок (указателей) между узлами + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + // Инициализация узлов + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // Построение ссылок (указателей) между узлами + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* Инициализация двоичного дерева */ + // Инициализация узлов + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // Построение ссылок (указателей) между узлами + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* Инициализация двоичного дерева */ + // Инициализация узлов + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // Построение ссылок (указателей) между узлами + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* Инициализация двоичного дерева */ + // Инициализация узлов + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // Построение ссылок (указателей) между узлами + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + // Инициализация узлов + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // Построение ссылок (указателей) между узлами + n1.borrow_mut().left = Some(n2.clone()); + n1.borrow_mut().right = Some(n3); + n2.borrow_mut().left = Some(n4); + n2.borrow_mut().right = Some(n5); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* Инициализация двоичного дерева */ + // Инициализация узлов + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // Построение ссылок (указателей) между узлами + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + // Инициализация узлов + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // Построение ссылок (указателей) между узлами + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + # Инициализация двоичного дерева + # Инициализация узлов + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # Построение ссылок (указателей) между узлами + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%20%28%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B8%29%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### Вставка и удаление узлов + +Как и в связном списке, вставка и удаление узлов в двоичном дереве могут выполняться через изменение указателей. На рисунке ниже приведен пример. + +![Вставка и удаление узлов в двоичном дереве](binary_tree.assets/binary_tree_add_remove.png) + +=== "Python" + + ```python title="binary_tree.py" + # Вставка и удаление узлов + p = TreeNode(0) + # Вставить узел P между n1 -> n2 + n1.left = p + p.left = n2 + # Удалить узел P + n1.left = n2 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* Вставка и удаление узлов */ + TreeNode* P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1->left = P; + P->left = n2; + // Удалить узел P + n1->left = n2; + // Освободить память + delete P; + ``` + +=== "Java" + + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + // Удалить узел P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* Вставка и удаление узлов */ + TreeNode P = new(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + // Удалить узел P + n1.left = n2; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* Вставка и удаление узлов */ + // Вставить узел P между n1 -> n2 + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + // Удалить узел P + n1.Left = n2 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + let P = TreeNode(x: 0) + // Вставить узел P между n1 -> n2 + n1.left = P + P.left = n2 + // Удалить узел P + n1.left = n2 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* Вставка и удаление узлов */ + let P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + // Удалить узел P + n1.left = n2; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* Вставка и удаление узлов */ + const P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + // Удалить узел P + n1.left = n2; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* Вставка и удаление узлов */ + TreeNode P = new TreeNode(0); + // Вставить узел P между n1 -> n2 + n1.left = P; + P.left = n2; + // Удалить узел P + n1.left = n2; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + let p = TreeNode::new(0); + // Вставить узел P между n1 -> n2 + n1.borrow_mut().left = Some(p.clone()); + p.borrow_mut().left = Some(n2.clone()); + // Удалить узел p + n1.borrow_mut().left = Some(n2); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* Вставка и удаление узлов */ + TreeNode *P = newTreeNode(0); + // Вставить узел P между n1 -> n2 + n1->left = P; + P->left = n2; + // Удалить узел P + n1->left = n2; + // Освободить память + free(P); + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + val P = TreeNode(0) + // Вставить узел P между n1 -> n2 + n1.left = P + P.left = n2 + // Удалить узел P + n1.left = n2 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + # Вставка и удаление узлов + _p = TreeNode.new(0) + # Вставить узел _p между n1 -> n2 + n1.left = _p + _p.left = n2 + # Удалить узел + n1.left = n2 + ``` + +??? pythontutor "Визуализация выполнения" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BB%D0%B5%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%B4%D0%BE%D1%87%D0%B5%D1%80%D0%BD%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B4%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%D0%9F%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B8%D1%82%D1%8C%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20%D1%83%D0%B7%D0%BB%D0%B0%D0%BC%D0%B8%20%28%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B8%29%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D0%B8%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%BE%D0%B2%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%D0%92%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%20%D0%BC%D0%B5%D0%B6%D0%B4%D1%83%20n1%20-%3E%20n2%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D1%83%D0%B7%D0%B5%D0%BB%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +!!! tip + + Обрати внимание: вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие. + +## Распространенные типы двоичных деревьев + +### Идеальное двоичное дерево + +Как показано на рисунке ниже, идеальное двоичное дерево (perfect binary tree) полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна $0$ , а у всех остальных узлов степень равна $2$ ; если высота дерева равна $h$ , то общее число узлов равно $2^{h+1} - 1$ , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления. + +!!! tip + + Обрати внимание: в китайскоязычном сообществе идеальное двоичное дерево часто называют полностью заполненным двоичным деревом. + +![Идеальное двоичное дерево](binary_tree.assets/perfect_binary_tree.png) + +### Полное двоичное дерево + +Как показано на рисунке ниже, полное двоичное дерево (complete binary tree) допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Обрати внимание: идеальное двоичное дерево тоже является полным двоичным деревом. + +![Полное двоичное дерево](binary_tree.assets/complete_binary_tree.png) + +### Строгое двоичное дерево + +Как показано на рисунке ниже, строгое двоичное дерево (full binary tree) имеет у всех нелистовых узлов ровно двух дочерних узлов. + +![Строгое двоичное дерево](binary_tree.assets/full_binary_tree.png) + +### Сбалансированное двоичное дерево + +Как показано на рисунке ниже, в сбалансированном двоичном дереве (balanced binary tree) для любого узла абсолютное значение разности высот левого и правого поддеревьев не превышает 1 . + +![Сбалансированное двоичное дерево](binary_tree.assets/balanced_binary_tree.png) + +## Вырождение двоичного дерева + +На рисунке ниже показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список". + +- Идеальное двоичное дерево соответствует лучшему случаю и позволяет полностью раскрыть преимущества двоичного дерева с точки зрения "разделяй и властвуй". +- Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до $O(n)$ . + +![Лучший и худший случаи структуры двоичного дерева](binary_tree.assets/binary_tree_best_worst_cases.png) + +Как показано в таблице ниже, в лучшем и худшем случаях число листовых узлов, общее число узлов, высота и другие характеристики двоичного дерева достигают максимума или минимума. + +

Таблица   Лучший и худший случаи структуры двоичного дерева

+ +| | Идеальное двоичное дерево | Связный список | +| --------------------------- | ------------------------- | -------------- | +| Число узлов на уровне $i$ | $2^{i-1}$ | $1$ | +| Число листьев у дерева высоты $h$ | $2^h$ | $1$ | +| Общее число узлов у дерева высоты $h$ | $2^{h+1} - 1$ | $h + 1$ | +| Высота дерева с $n$ узлами | $\log_2 (n+1) - 1$ | $n - 1$ | diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png new file mode 100644 index 000000000..eff233bc8 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png new file mode 100644 index 000000000..6ae37dc32 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png new file mode 100644 index 000000000..8b84c7155 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png new file mode 100644 index 000000000..a0a074d59 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png new file mode 100644 index 000000000..9f2799a3e Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png new file mode 100644 index 000000000..34ce7e7b3 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png new file mode 100644 index 000000000..94afcfb82 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png new file mode 100644 index 000000000..f45987fc4 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png new file mode 100644 index 000000000..b81309db4 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png new file mode 100644 index 000000000..1a65ea9bd Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png new file mode 100644 index 000000000..c3ab18fd0 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png new file mode 100644 index 000000000..46a30526b Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png new file mode 100644 index 000000000..daeb297c2 Binary files /dev/null and b/ru/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png differ diff --git a/ru/docs/chapter_tree/binary_tree_traversal.md b/ru/docs/chapter_tree/binary_tree_traversal.md new file mode 100644 index 000000000..692a73f8c --- /dev/null +++ b/ru/docs/chapter_tree/binary_tree_traversal.md @@ -0,0 +1,89 @@ +# Обход двоичного дерева + +С точки зрения физической структуры дерево представляет собой разновидность структуры данных на основе связей, поэтому его обход выполняется через последовательный доступ к узлам по указателям. Однако дерево является нелинейной структурой данных, а значит, его обход сложнее, чем обход связного списка, и для него требуется использовать поисковые алгоритмы. + +К распространенным способам обхода двоичного дерева относятся обход по уровням, прямой обход, симметричный обход и обратный обход. + +## Обход по уровням + +Как показано на рисунке ниже, обход по уровням (level-order traversal) проходит двоичное дерево сверху вниз по уровням и на каждом уровне посещает узлы слева направо. + +По своей сути обход по уровням относится к обходу в ширину (breadth-first traversal), также называемому поиском в ширину (breadth-first search, BFS); он отражает идею "расширяться слой за слоем наружу". + +![Обход двоичного дерева по уровням](binary_tree_traversal.assets/binary_tree_bfs.png) + +### Код реализации + +Обход в ширину обычно реализуется с помощью "очереди". Очередь подчиняется правилу "первым пришел - первым вышел", а обход в ширину подчиняется правилу "продвигаться по уровням", поэтому стоящая за ними идея согласована. Код реализации приведен ниже: + +```src +[file]{binary_tree_bfs}-[class]{}-[func]{level_order} +``` + +### Анализ сложности + +- **Временная сложность равна $O(n)$** : все узлы посещаются по одному разу, поэтому требуется $O(n)$ времени, где $n$ - число узлов. +- **Пространственная сложность равна $O(n)$** : в худшем случае, то есть для полной двоичной деревообразной структуры, до достижения самого нижнего уровня в очереди одновременно может находиться до $(n + 1) / 2$ узлов, что требует $O(n)$ памяти. + +## Прямой, симметричный и обратный обходы + +Соответственно, прямой, симметричный и обратный обходы относятся к обходу в глубину (depth-first traversal), также называемому поиском в глубину (depth-first search, DFS); он отражает идею "сначала идти до конца, затем откатываться и продолжать". + +На рисунке ниже показан принцип работы обхода двоичного дерева в глубину. **Обход в глубину похож на то, как будто мы обходим всю двоичную структуру по внешнему контуру** , и у каждого узла встречаем три позиции, соответствующие прямому, симметричному и обратному обходам. + +![Прямой, симметричный и обратный обходы двоичного дерева поиска](binary_tree_traversal.assets/binary_tree_dfs.png) + +### Код реализации + +Поиск в глубину обычно реализуется через рекурсию: + +```src +[file]{binary_tree_dfs}-[class]{}-[func]{post_order} +``` + +!!! tip + + Поиск в глубину можно реализовать и итеративно; заинтересованные читатели могут изучить это самостоятельно. + +На рисунках ниже показан рекурсивный процесс прямого обхода двоичного дерева. Его можно разделить на две противоположные части: "вход в рекурсию" и "возврат". + +1. "Вход в рекурсию" означает запуск нового вызова функции; в этом процессе программа переходит к следующему узлу. +2. "Возврат" означает завершение вызова функции и возврат назад, то есть текущий узел уже полностью обработан. + +=== "<1>" + ![Рекурсивный процесс прямого обхода](binary_tree_traversal.assets/preorder_step1.png) + +=== "<2>" + ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) + +=== "<3>" + ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) + +=== "<4>" + ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) + +=== "<5>" + ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) + +=== "<6>" + ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) + +=== "<7>" + ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) + +=== "<8>" + ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) + +=== "<9>" + ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) + +=== "<10>" + ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) + +=== "<11>" + ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) + +### Анализ сложности + +- **Временная сложность равна $O(n)$** : все узлы посещаются по одному разу, поэтому требуется $O(n)$ времени. +- **Пространственная сложность равна $O(n)$** : в худшем случае, когда дерево вырождается в связный список, глубина рекурсии достигает $n$ , и система тратит $O(n)$ памяти на стек вызовов. diff --git a/ru/docs/chapter_tree/index.md b/ru/docs/chapter_tree/index.md new file mode 100644 index 000000000..3ee570bac --- /dev/null +++ b/ru/docs/chapter_tree/index.md @@ -0,0 +1,9 @@ +# Деревья + +![Деревья](../assets/covers/chapter_tree.jpg) + +!!! abstract + + Высокое дерево полно жизни: мощные корни, густая крона и раскидистые ветви. + + Оно наглядно показывает нам форму данных, построенную на принципе "разделяй и властвуй". diff --git a/ru/docs/chapter_tree/summary.md b/ru/docs/chapter_tree/summary.md new file mode 100644 index 000000000..03b37baea --- /dev/null +++ b/ru/docs/chapter_tree/summary.md @@ -0,0 +1,54 @@ +# Краткие итоги + +### Основные моменты + +- Двоичное дерево - это нелинейная структура данных, отражающая логику "разделения надвое". Каждый узел двоичного дерева содержит значение и два указателя, которые соответственно ведут к левому и правому дочерним узлам. +- Для любого узла двоичного дерева дерево, образованное его левым (правым) дочерним узлом и всеми нижележащими узлами, называется левым (правым) поддеревом этого узла. +- К связанным с двоичным деревом терминам относятся корневой узел, листовой узел, уровень, степень, ребро, высота, глубина и так далее. +- Инициализация двоичного дерева, вставка узлов и удаление узлов похожи по способу реализации на операции со связным списком. +- К распространенным видам двоичного дерева относятся идеальное двоичное дерево, полное двоичное дерево, строгое двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево - наиболее желательное состояние, а связный список - худший случай после вырождения. +- Двоичное дерево можно представить массивом: значения узлов и пустые позиции располагаются в порядке обхода по уровням, а связи между родителем и детьми реализуются через отображение индексов. +- Обход двоичного дерева по уровням является методом поиска в ширину; он отражает идею "расширяться слой за слоем наружу" и обычно реализуется через очередь. +- Прямой, симметричный и обратный обходы относятся к поиску в глубину; они отражают идею "сначала дойти до конца, затем откатиться и продолжить" и обычно реализуются рекурсивно. +- Двоичное дерево поиска - это эффективная структура данных для поиска элементов; его поиск, вставка и удаление имеют временную сложность $O(\log n)$ . Когда двоичное дерево поиска вырождается в связный список, все эти сложности деградируют до $O(n)$ . +- AVL-дерево, также называемое сбалансированным двоичным деревом поиска, с помощью вращений гарантирует, что после постоянных вставок и удалений узлов дерево остается сбалансированным. +- Вращения AVL-дерева включают правое вращение, левое вращение, сначала правое затем левое и сначала левое затем правое. После вставки или удаления узла AVL-дерево выполняет вращения снизу вверх, чтобы снова восстановить баланс. + +### Q & A + +**Q**: Для двоичного дерева, состоящего из одного узла, высота дерева и глубина корня обе равны $0$ ? + +Да, потому что высота и глубина обычно определяются как "число пройденных ребер". + +**Q**: Вставка и удаление в двоичном дереве обычно выполняются в составе набора операций. Что именно означает этот "набор операций"? Можно ли понимать это как освобождение ресурсов у дочерних узлов ресурса? + +Возьмем в качестве примера двоичное дерево поиска: операция удаления узла делится на три случая, и каждый из этих случаев требует нескольких последовательных шагов работы с узлами. + +**Q**: Почему у DFS для двоичного дерева есть три порядка: прямой, симметричный и обратный? Для чего они нужны? + +Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы - это три способа обхода двоичного дерева, с помощью которых можно получить результаты в определенном порядке. Например, в двоичном дереве поиска, где соблюдается отношение `значение левого дочернего узла < значение корня < значение правого дочернего узла` , если обходить дерево с приоритетом "лево $\rightarrow$ корень $\rightarrow$ право", то получится упорядоченная последовательность узлов. + +**Q**: Правое вращение работает с отношениями между `node` , `child` и `grand_child` . А связь между `node` и его исходным родителем разве не нужно поддерживать? После правого вращения она ведь не оборвется? + +На это нужно смотреть с точки зрения рекурсии. В правое вращение `right_rotate(root)` передается корень поддерева, а затем через `return child` возвращается корень этого поддерева уже после вращения. Соединение между новым корнем поддерева и его родителем восстанавливается после возврата функции и не входит в обязанности самой операции правого вращения. + +**Q**: В C++ функции делятся на `private` и `public` . Какая логика стоит за этим? Почему `height()` и `updateHeight()` помещают в разные области видимости? + +Главный критерий - область использования метода. Если метод нужен только внутри класса, его следует проектировать как `private` . Например, самостоятельный вызов `updateHeight()` пользователем не имеет смысла: это лишь один из шагов внутри вставки или удаления. А `height()` используется для чтения высоты узла, подобно `vector.size()` , поэтому его разумно делать `public` . + +**Q**: Как построить двоичное дерево поиска из набора входных данных? Важен ли выбор корневого узла? + +Да, важен. Способ построения дерева уже показан в методе `build_tree()` в коде двоичного дерева поиска. Что касается выбора корня, обычно входные данные сортируют, берут средний элемент как корень, а затем рекурсивно строят левое и правое поддеревья. Это позволяет в наибольшей степени сохранить баланс дерева. + +**Q**: Нужно ли в Java всегда использовать `equals()` для сравнения строк? + +В Java для базовых типов `==` используется, чтобы сравнивать, равны ли значения двух переменных. Для ссылочных типов логика у этих двух способов уже разная. + +- `==` : сравнивает, ссылаются ли две переменные на один и тот же объект, то есть совпадает ли их адрес в памяти. +- `equals()`: сравнивает, равны ли значения двух объектов. + +Поэтому если нужно сравнить значения, то следует использовать `equals()` . Но строки, инициализированные как `String a = "hi"; String b = "hi";` , хранятся в строковом пуле констант и указывают на один и тот же объект, поэтому в таком случае `a == b` тоже может дать истинный результат при сравнении содержимого. + +**Q**: До достижения самого нижнего уровня при обходе в ширину число узлов в очереди равно $2^h$ ? + +Да. Например, для полного двоичного дерева высоты $h = 2$ общее число узлов равно $n = 7$ , а число узлов на нижнем уровне равно $4 = 2^h = (n + 1) / 2$ . diff --git a/ru/docs/index.assets/animation.gif b/ru/docs/index.assets/animation.gif new file mode 100644 index 000000000..641e677ef Binary files /dev/null and b/ru/docs/index.assets/animation.gif differ diff --git a/ru/docs/index.assets/comment.gif b/ru/docs/index.assets/comment.gif new file mode 100644 index 000000000..a6c5b8444 Binary files /dev/null and b/ru/docs/index.assets/comment.gif differ diff --git a/ru/docs/index.assets/running_code.gif b/ru/docs/index.assets/running_code.gif new file mode 100644 index 000000000..5c77187f7 Binary files /dev/null and b/ru/docs/index.assets/running_code.gif differ diff --git a/ru/docs/index.html b/ru/docs/index.html new file mode 100644 index 000000000..229242b84 --- /dev/null +++ b/ru/docs/index.html @@ -0,0 +1,357 @@ + +
+ + +
+
+
+ + + +
+ +
+

+ Учебник по структурам данных и алгоритмам с анимированными схемами и кодом, готовым к запуску в один клик +

+ + + + + Начать чтение + + + + + + + Репозиторий кода + +
+ +
+ + + +
+
+
+ + +
+
+ +

500 анимированных схем, код на 14 языках программирования и 3000 ответов сообщества помогут вам быстро войти в мир структур данных и алгоритмов.

+
+
+ + +
+
+

Рекомендации

+
+
+

«Понятная вводная книга по структурам данных и алгоритмам, которая ведет читателя через практическое обучение шаг за шагом. Очень рекомендую всем начинающим.»

+

—— Junhui Deng, профессор факультета компьютерных наук Университета Цинхуа

+
+
+

«Если бы у меня была “Hello Algo”, когда я изучал структуры данных и алгоритмы, учиться было бы в десять раз проще!»

+

—— Mu Li, Senior Principal Scientist, Amazon

+
+
+
+
+ + +
+
+
+
+
+
+ + + +

Анимированные схемы

+
+

Материал изложен ясно и последовательно, поэтому вход в тему получается плавным.

+

"A picture is worth a thousand words."
«Одна схема стоит тысячи слов»

+
+
+ Animation example +
+ +
+ Running code example +
+
+
+ + + +

Запуск в один клик

+
+

Код на более чем десяти языках можно запускать и визуализировать прямо в книге.

+

"Talk is cheap. Show me the code."
«Меньше слов, больше кода»

+
+
+
+ +
+
+
+
+ + + +

Учимся вместе

+
+

Добро пожаловать к обсуждениям и вопросам: продвигаться вместе проще.

+

"Learning by teaching."
«Обучая других, учишься сам»

+
+
+ Comments example +
+ +
+
+ + +
+ +
+ + +
+
+ +
+

Автор

+ +
+ + +
+

Переводчики

+

Русская печатная версия была переведена И. А. Шевкун, а русская онлайн-версия была вычитана Yuyan Huang. Благодарим их за вклад!

+
+
+ Translator: shevkun +
И. А. Шевкун +
+
+ Translator: yuyanhuang +
Yuyan Huang +
+
+
+ + +
+

Переводчики кода

+

Многоязычные версии кода в этой книге были подготовлены при участии следующих переводчиков. Благодарим их за время и вклад!

+ +
+ + +
+

Участники проекта

+

Книга постоянно совершенствуется благодаря совместным усилиям более чем 200 участников сообщества. Спасибо им за время и вклад!

+ + Contributors + +
+
+
diff --git a/ru/docs/index.md b/ru/docs/index.md new file mode 100644 index 000000000..c016e3195 --- /dev/null +++ b/ru/docs/index.md @@ -0,0 +1,5 @@ +# Hello Algo + +Учебник по структурам данных и алгоритмам с анимированными иллюстрациями и готовым к запуску кодом. + +[Начать чтение](chapter_hello_algo/) diff --git a/ru/mkdocs.yml b/ru/mkdocs.yml new file mode 100644 index 000000000..99c4b1e85 --- /dev/null +++ b/ru/mkdocs.yml @@ -0,0 +1,179 @@ +# Config inheritance +INHERIT: ../mkdocs.yml + +# Project information +site_name: Hello Algo +site_url: https://www.hello-algo.com/ru/ +site_description: "Учебник по структурам данных и алгоритмам с анимированными иллюстрациями и готовым к запуску кодом" +docs_dir: ../build/ru/docs +site_dir: ../site/ru +# Repository +edit_uri: tree/main/ru/docs +version: 1.3.0 + +# Configuration +theme: + custom_dir: ../build/overrides + language: ru + font: + text: Noto Sans + palette: + - scheme: default + primary: white + accent: teal + toggle: + icon: material/theme-light-dark + name: Темная тема + - scheme: slate + primary: black + accent: teal + toggle: + icon: material/theme-light-dark + name: Светлая тема + +extra: + status: + new: Недавно добавлено + +# Page tree +nav: + - Перед началом: + - chapter_hello_algo/index.md + - Глава 0. Предисловие: + # [icon: material/book-open-outline] + - chapter_preface/index.md + - 0.1 Об этой книге: chapter_preface/about_the_book.md + - 0.2 Как пользоваться этой книгой: chapter_preface/suggestions.md + - 0.3 Резюме: chapter_preface/summary.md + - Глава 1. Знакомство с алгоритмами: + # [icon: material/calculator-variant-outline] + - chapter_introduction/index.md + - 1.1 Алгоритмы повсюду: chapter_introduction/algorithms_are_everywhere.md + - 1.2 Что такое структуры данных и алгоритмы: chapter_introduction/what_is_dsa.md + - 1.3 Резюме: chapter_introduction/summary.md + - Глава 2. Анализ сложности: + # [icon: material/timer-sand] + - chapter_computational_complexity/index.md + - 2.1 Оценка эффективности алгоритмов: chapter_computational_complexity/performance_evaluation.md + - 2.2 Итерация и рекурсия: chapter_computational_complexity/iteration_and_recursion.md + - 2.3 Временная сложность: chapter_computational_complexity/time_complexity.md + - 2.4 Пространственная сложность: chapter_computational_complexity/space_complexity.md + - 2.5 Резюме: chapter_computational_complexity/summary.md + - Глава 3. Структуры данных: + # [icon: material/shape-outline] + - chapter_data_structure/index.md + - 3.1 Классификация структур данных: chapter_data_structure/classification_of_data_structure.md + - 3.2 Базовые типы данных: chapter_data_structure/basic_data_types.md + - 3.3 Кодирование чисел *: chapter_data_structure/number_encoding.md + - 3.4 Кодирование символов *: chapter_data_structure/character_encoding.md + - 3.5 Резюме: chapter_data_structure/summary.md + - Глава 4. Массив и связный список: + # [icon: material/view-list-outline] + - chapter_array_and_linkedlist/index.md + - 4.1 Массив: chapter_array_and_linkedlist/array.md + - 4.2 Связный список: chapter_array_and_linkedlist/linked_list.md + - 4.3 Список: chapter_array_and_linkedlist/list.md + - 4.4 Память и кеш *: chapter_array_and_linkedlist/ram_and_cache.md + - 4.5 Резюме: chapter_array_and_linkedlist/summary.md + - Глава 5. Стек и очередь: + # [icon: material/stack-overflow] + - chapter_stack_and_queue/index.md + - 5.1 Стек: chapter_stack_and_queue/stack.md + - 5.2 Очередь: chapter_stack_and_queue/queue.md + - 5.3 Двусторонняя очередь: chapter_stack_and_queue/deque.md + - 5.4 Резюме: chapter_stack_and_queue/summary.md + - Глава 6. Хеширование: + # [icon: material/table-search] + - chapter_hashing/index.md + - 6.1 Хеш-таблица: chapter_hashing/hash_map.md + - 6.2 Хеш-коллизии: chapter_hashing/hash_collision.md + - 6.3 Хеш-алгоритмы: chapter_hashing/hash_algorithm.md + - 6.4 Резюме: chapter_hashing/summary.md + - Глава 7. Дерево: + # [icon: material/graph-outline] + - chapter_tree/index.md + - 7.1 Двоичное дерево: chapter_tree/binary_tree.md + - 7.2 Обход двоичного дерева: chapter_tree/binary_tree_traversal.md + - 7.3 Представление дерева массивом: chapter_tree/array_representation_of_tree.md + - 7.4 Двоичное дерево поиска: chapter_tree/binary_search_tree.md + - 7.5 AVL-дерево *: chapter_tree/avl_tree.md + - 7.6 Резюме: chapter_tree/summary.md + - Глава 8. Куча: + # [icon: material/family-tree] + - chapter_heap/index.md + - 8.1 Куча: chapter_heap/heap.md + - 8.2 Построение кучи: chapter_heap/build_heap.md + - 8.3 Задача Top-K: chapter_heap/top_k.md + - 8.4 Резюме: chapter_heap/summary.md + - Глава 9. Граф: + # [icon: material/graphql] + - chapter_graph/index.md + - 9.1 Граф: chapter_graph/graph.md + - 9.2 Базовые операции над графами: chapter_graph/graph_operations.md + - 9.3 Обход графа: chapter_graph/graph_traversal.md + - 9.4 Резюме: chapter_graph/summary.md + - Глава 10. Поиск: + # [icon: material/text-search] + - chapter_searching/index.md + - 10.1 Двоичный поиск: chapter_searching/binary_search.md + - 10.2 Точка вставки двоичного поиска: chapter_searching/binary_search_insertion.md + - 10.3 Граничные случаи двоичного поиска: chapter_searching/binary_search_edge.md + - 10.4 Стратегия оптимизации через хеширование: chapter_searching/replace_linear_by_hashing.md + - "10.5 Алгоритмы поиска: новый взгляд": chapter_searching/searching_algorithm_revisited.md + - 10.6 Резюме: chapter_searching/summary.md + - Глава 11. Сортировка: + # [icon: material/sort-ascending] + - chapter_sorting/index.md + - 11.1 Алгоритмы сортировки: chapter_sorting/sorting_algorithm.md + - 11.2 Сортировка выбором: chapter_sorting/selection_sort.md + - 11.3 Пузырьковая сортировка: chapter_sorting/bubble_sort.md + - 11.4 Сортировка вставкой: chapter_sorting/insertion_sort.md + - 11.5 Быстрая сортировка: chapter_sorting/quick_sort.md + - 11.6 Сортировка слиянием: chapter_sorting/merge_sort.md + - 11.7 Пирамидальная сортировка: chapter_sorting/heap_sort.md + - 11.8 Блочная сортировка: chapter_sorting/bucket_sort.md + - 11.9 Сортировка подсчетом: chapter_sorting/counting_sort.md + - 11.10 Поразрядная сортировка: chapter_sorting/radix_sort.md + - 11.11 Резюме: chapter_sorting/summary.md + - Глава 12. Разделяй и властвуй: + # [icon: material/set-split] + - chapter_divide_and_conquer/index.md + - 12.1 Алгоритмы разделяй и властвуй: chapter_divide_and_conquer/divide_and_conquer.md + - 12.2 Стратегия поиска разделяй и властвуй: chapter_divide_and_conquer/binary_search_recur.md + - 12.3 Задача построения двоичного дерева: chapter_divide_and_conquer/build_binary_tree_problem.md + - 12.4 Задача о Ханойской башне: chapter_divide_and_conquer/hanota_problem.md + - 12.5 Резюме: chapter_divide_and_conquer/summary.md + - Глава 13. Поиск с возвратом: + # [icon: material/map-marker-path] + - chapter_backtracking/index.md + - 13.1 Алгоритм поиска с возвратом: chapter_backtracking/backtracking_algorithm.md + - 13.2 Задача о перестановках: chapter_backtracking/permutations_problem.md + - 13.3 Задача о сумме подмножеств: chapter_backtracking/subset_sum_problem.md + - 13.4 Задача о $n$ ферзях: chapter_backtracking/n_queens_problem.md + - 13.5 Резюме: chapter_backtracking/summary.md + - Глава 14. Динамическое программирование: + # [icon: material/table-pivot] + - chapter_dynamic_programming/index.md + - 14.1 Введение в динамическое программирование: chapter_dynamic_programming/intro_to_dynamic_programming.md + - 14.2 Свойства задач динамического программирования: chapter_dynamic_programming/dp_problem_features.md + - 14.3 Подход к решению задач динамического программирования: chapter_dynamic_programming/dp_solution_pipeline.md + - 14.4 Задача о рюкзаке 0-1: chapter_dynamic_programming/knapsack_problem.md + - 14.5 Задача о неограниченном рюкзаке: chapter_dynamic_programming/unbounded_knapsack_problem.md + - 14.6 Задача о расстоянии редактирования: chapter_dynamic_programming/edit_distance_problem.md + - 14.7 Резюме: chapter_dynamic_programming/summary.md + - Глава 15. Жадность: + # [icon: material/head-heart-outline] + - chapter_greedy/index.md + - 15.1 Жадный алгоритм: chapter_greedy/greedy_algorithm.md + - 15.2 Задача о дробном рюкзаке: chapter_greedy/fractional_knapsack_problem.md + - 15.3 Задача о максимальной вместимости: chapter_greedy/max_capacity_problem.md + - 15.4 Задача о максимальном произведении разбиения: chapter_greedy/max_product_cutting_problem.md + - 15.5 Резюме: chapter_greedy/summary.md + - Глава 16. Приложение: + # [icon: material/help-circle-outline] + - chapter_appendix/index.md + - 16.1 Установка среды программирования: chapter_appendix/installation.md + - 16.2 Присоединяйтесь к созданию книги: chapter_appendix/contribution.md + - 16.3 Глоссарий: chapter_appendix/terminology.md + - Список литературы: + - chapter_reference/index.md diff --git a/zh-hant/README.md b/zh-hant/README.md index bfccef360..502da89f2 100644 --- a/zh-hant/README.md +++ b/zh-hant/README.md @@ -45,6 +45,8 @@ English日本語 + | + Русский

## 關於本書