= 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%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,%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%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%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%D1%80%D0%B0%D0%B7%D0%BB%D0%B8%D1%87%D0%BD%D1%8B%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*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%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%D1%84%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%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%D0%BE%D0%B9%201%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%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,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
\ No newline at end of file
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..395e31c22
--- /dev/null
+++ b/ru/docs/chapter_data_structure/character_encoding.md
@@ -0,0 +1,63 @@
+```markdown
+# Кодирование символов *
+
+В компьютере все данные хранятся в виде двоичных чисел, и символы `char` не являются исключением. Для представления символов необходимо создать «набор символов», определяющий взаимно однозначное соответствие между каждым символом и двоичным числом. Имея набор символов, компьютер может выполнять преобразование двоичных чисел в символы путем поиска в таблице.
+
+## Набор символов ASCII
+
+Код ASCII является самым ранним набором символов, его полное название — American Standard Code for Information Interchange (Американский стандартный код для обмена информацией). Он использует 7-битные двоичные числа (младшие 7 бит одного байта) для представления символа и может представлять максимум 128 различных символов. Как показано на рисунке ниже, код ASCII включает прописные и строчные буквы английского алфавита, цифры 0~9, некоторые знаки препинания, а также управляющие символы (такие как символ новой строки и табуляции).
+
+
+
+Однако **код ASCII может представлять только английский язык**. С глобализацией компьютеров появился набор символов EASCII, способный представлять больше языков. Он расширил 7-битный ASCII до 8 бит и может представлять 256 различных символов.
+
+По всему миру последовательно появились различные наборы символов EASCII, подходящие для разных регионов. Первые 128 символов этих наборов унифицированы как код ASCII, а последние 128 символов определены по-разному для удовлетворения потребностей различных языков.
+
+## Набор символов GBK
+
+Позже люди обнаружили, что **код EASCII все еще не может удовлетворить требования к количеству символов во многих языках**. Например, существует почти сто тысяч китайских иероглифов, из которых несколько тысяч используются в повседневной жизни. В 1980 году Главное управление стандартизации Китая выпустило набор символов GB2312, который включал 6763 китайских иероглифа и в основном удовлетворял потребности компьютерной обработки китайских иероглифов.
+
+Однако GB2312 не мог обрабатывать некоторые редкие иероглифы и традиционные китайские символы. Набор символов GBK был получен путем расширения GB2312 и включает в общей сложности 21886 китайских иероглифов. В схеме кодирования GBK символы ASCII представлены одним байтом, а китайские иероглифы — двумя байтами.
+
+## Набор символов Unicode
+
+С бурным развитием компьютерных технологий наборы символов и стандарты кодирования расцвели пышным цветом, что привело ко многим проблемам. С одной стороны, эти наборы символов обычно определяли только символы конкретного языка и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов наборов символов, и если два компьютера использовали разные стандарты кодирования, при передаче информации возникала кракозябра.
+
+Исследователи того времени думали: **если создать достаточно полный набор символов, включающий все языки и символы со всего мира, разве это не решит проблемы многоязычной среды и кракозябры**? Под влиянием этой идеи появился большой и всеобъемлющий набор символов Unicode.
+
+Unicode на китайском языке называется «统一码» (унифицированный код), теоретически может вместить более 1 миллиона символов. Он стремится включить символы со всего мира в единый набор символов, предоставляя универсальный набор символов для обработки и отображения текстов на различных языках, уменьшая проблемы с кракозяброй, возникающие из-за различий в стандартах кодирования.
+
+С момента выпуска в 1991 году Unicode постоянно пополняется новыми языками и символами. По состоянию на сентябрь 2022 года Unicode уже включает 149186 символов, включая символы различных языков, знаки и даже эмодзи. В огромном наборе символов Unicode часто используемые символы занимают 2 байта, а некоторые редкие символы занимают 3 или даже 4 байта.
+
+Unicode является универсальным набором символов, по сути присваивающим каждому символу номер (называемый «кодовой точкой»), **но он не определяет, как эти кодовые точки символов должны храниться в компьютере**. Мы не можем не задаться вопросом: когда кодовые точки Unicode различной длины одновременно появляются в тексте, как система анализирует символы? Например, при заданном коде длиной 2 байта, как система определяет, является ли это одним 2-байтовым символом или двумя 1-байтовыми символами?
+
+Для вышеуказанных проблем **прямым решением является хранение всех символов в виде кодов одинаковой длины**. Как показано на рисунке ниже, каждый символ в «Hello» занимает 1 байт, каждый символ в «算法» занимает 2 байта. Мы можем закодировать все символы в «Hello 算法» длиной 2 байта, заполнив старшие биты нулями. Таким образом, система может анализировать один символ каждые 2 байта и восстановить содержание этой фразы.
+
+
+
+Однако код 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, распространенные методы кодирования включают следующие два.
+
+- **Кодирование UTF-16**: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представлены 2 байтами; небольшое количество символов требует 4 байта. Для 2-байтовых символов кодирование UTF-16 равно кодовой точке Unicode.
+- **Кодирование UTF-32**: каждый символ использует 4 байта. Это означает, что UTF-32 занимает больше места, чем UTF-8 и UTF-16, особенно для текстов с высокой долей символов ASCII.
+
+С точки зрения занимаемого пространства хранения использование UTF-8 для представления английских символов очень эффективно, поскольку требуется только 1 байт;
+```
\ No newline at end of file
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..aad4c4ae1
--- /dev/null
+++ b/ru/docs/chapter_data_structure/classification_of_data_structure.md
@@ -0,0 +1,47 @@
+# Классификация структур данных
+
+К распространенным структурам данных относятся массивы, списки, стеки, очереди, хеш-таблицы, деревья, кучи, графы. Их можно классифицировать по двум измерениям: логической структуре и физической структуре.
+
+## Логическая структура: линейная и нелинейная
+
+**Логическая структура раскрывает логические отношения между элементами данных**. В массивах и списках данные расположены в определенном порядке, что отражает линейные отношения между данными. В деревьях данные расположены сверху вниз по уровням, что демонстрирует отношения «предок» и «потомок». Графы состоят из узлов и ребер, отражая сложные сетевые отношения.
+
+Логические структуры делятся на две большие категории: линейные и нелинейные, как показано на рис. 3.1. Линейные структуры более интуитивно понятны, поскольку в них данные расположены линейно и логически связаны. Нелинейные структуры, наоборот, представляют собой нелинейное расположение элементов данных.
+
+- **Линейные структуры данных**: массивы, списки, стеки, очереди, хеш-таблицы, в которых элементы связаны последовательно один к одному.
+
+- **Нелинейные структуры данных**: деревья, кучи, графы, хеш-таблицы.
+
+Нелинейные структуры данных можно дополнительно разделить на древовидные и сетевые.
+
+- **Древовидные структуры**: деревья, кучи, хеш-таблицы, в которых элементы связаны один ко многим.
+
+- **Сетевые структуры**: графы, в которых элементы связаны многие ко многим.
+
+
+
+## Физическая структура: непрерывная и разреженная
+
+**Во время выполнения программы обрабатываемые данные в основном хранятся в памяти**. На рис. 3.2 изображен модуль оперативной памяти компьютера, где каждый черный чип содержит определенный участок памяти. Память можно представить как огромную таблицу Excel, где каждая ячейка может хранить данные определенного размера.
+
+**Система обращается к данным в целевой позиции через адреса памяти**. Компьютер присваивает каждой ячейке таблицы номер по определенным правилам, чтобы обеспечить уникальный адрес памяти для каждого участка, как показано на рис. 3.2. Благодаря этим адресам программа может обращаться к данным в памяти.
+
+
+
+!!! tip
+
+ Память является общим ресурсом для всех программ, и когда участок памяти занят одной программой, он обычно не может быть одновременно использован другими программами.
+
+**Поэтому в процессе проектирования структур данных и алгоритмов память занимает важное место**. Например, пиковое использование памяти алгоритмом не должно превышать оставшуюся свободную память системы. Если не хватает непрерывных больших участков памяти, выбранная структура данных должна уметь располагаться в разреженных участках памяти.
+
+**Физическая структура отражает способ хранения данных в памяти компьютера** и делится на хранение в непрерывном пространстве (массивы) и хранение в разреженном пространстве (списки), как показано на рис. 3.3. Физическая структура на низком уровне определяет методы доступа, обновления, добавления и удаления данных. Обе физические структуры демонстрируют взаимодополняющие характеристики в отношении временной и пространственной эффективности.
+
+
+
+Следует отметить, что **все структуры данных реализуются на основе массивов, связных списков или их комбинации**. Например, стек и очередь можно реализовать как с использованием массивов, так и с использованием связных списков. Реализация хеш-таблицы может включать как массивы, так и связные списки.
+
+- **На основе массивов можно реализовать**: стек, очередь, хеш-таблицу, дерево, кучу, граф, матрицу, тензор (массив размерностью ≥ 3) и др.
+
+- **На основе связных списков можно реализовать**: стек, очередь, хеш-таблицу, дерево, кучу, граф и др.
+
+Связный список после инициализации может изменять свою длину в процессе выполнения программы, поэтому его также называют динамической структурой данных. Массив после инициализации имеет неизменную длину, поэтому его называют статической структурой данных. Следует отметить, что массив может изменять свою длину путем перераспределения памяти, что придает ему определенную динамичность.
\ No newline at end of file
diff --git a/ru/docs/chapter_data_structure/index.md b/ru/docs/chapter_data_structure/index.md
new file mode 100644
index 000000000..c36098c00
--- /dev/null
+++ b/ru/docs/chapter_data_structure/index.md
@@ -0,0 +1,9 @@
+# Структуры данных
+
+
+
+!!! abstract
+
+ Структуры данных подобны прочному и разнообразному каркасу.
+
+ Они предоставляют план для упорядоченной организации данных, на основе которого алгоритмы оживают.
\ No newline at end of file
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..5dd5f302e
--- /dev/null
+++ b/ru/docs/chapter_data_structure/number_encoding.md
@@ -0,0 +1,72 @@
+# Кодирование чисел *
+
+!!! tip
+
+ В этой книге разделы, отмеченные символом *, являются необязательными для чтения. Если у вас ограничено время или возникают трудности с пониманием, вы можете пропустить их и вернуться к ним после изучения обязательных разделов.
+
+## Прямой, обратный и дополнительный коды
+
+В таблице из предыдущего раздела можно заметить, что во всех целочисленных типах отрицательных чисел на одно больше, чем положительных. Например, диапазон значений `byte` составляет $[-128, 127]$. Этот факт кажется не совсем интуитивным, и его внутренняя причина связана с концепциями прямого, обратного и дополнительного кодов.
+
+Прежде всего необходимо отметить, что **числа хранятся в компьютере в виде дополнительного кода**. Прежде чем проанализировать причины этого, сначала дадим определения всем трем кодам.
+
+- **Прямой код**: старший бит двоичного представления числа рассматривается как знак, где $0$ обозначает положительное число, $1$ -- отрицательное, остальные биты представляют значение числа.
+- **Обратный код**: обратный код положительного числа совпадает с его прямым кодом, обратный код отрицательного числа получается инверсией всех битов прямого кода, кроме знакового.
+- **Дополнительный код**: дополнительный код положительного числа совпадает с его прямым кодом, дополнительный код отрицательного числа получается добавлением $1$ к его обратному коду.
+
+На рис. 3.4 изображены методы преобразования прямого, обратного и дополнительного кодов между собой.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Кодирование чисел с плавающей запятой
+
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_data_structure/summary.md b/ru/docs/chapter_data_structure/summary.md
new file mode 100644
index 000000000..61957fa2b
--- /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 -- это часто используемая китайская кодировка, включающая более 20 тысяч иероглифов. Unicode стремится предоставить полный стандарт набора символов, включающий символы различных языков мира, тем самым решая проблему искажения текста из-за несогласованности методов кодирования символов.
+- UTF-8 -- самый популярный метод кодирования Unicode с очень хорошей универсальностью. Это метод кодирования переменной длины с хорошей расширяемостью, эффективно повышающий эффективность использования памяти. UTF-16 и UTF-32 -- это методы кодирования фиксированной длины. При кодировании китайских символов UTF-16 занимает меньше места, чем UTF-8. Такие языки программирования, как Java и C#, по умолчанию используют кодировку UTF-16.
+
+### Вопросы и ответы
+
+**В**: Почему хеш-таблица одновременно включает линейные и нелинейные структуры данных?
+
+В основе хеш-таблицы лежит массив, а для решения коллизий хеширования мы можем использовать «метод цепочек» (об этом будет рассказано в последующей главе «Коллизии хеширования»): каждая ячейка массива указывает на связный список, и когда длина списка превышает определенный порог, он может быть преобразован в дерево (обычно красно-черное дерево).
+
+С точки зрения хранения, в основе хеш-таблицы лежит массив, где каждая ячейка может содержать значение, связный список или дерево. Поэтому хеш-таблица может одновременно включать линейные структуры данных (массивы, связные списки) и нелинейные структуры данных (деревья).
+
+**В**: Длина типа `char` составляет 1 байт?
+
+Длина типа `char` определяется методом кодирования, используемым языком программирования. Например, Java, JavaScript, TypeScript, C# используют кодировку UTF-16 (сохраняют кодовые точки Unicode), поэтому длина типа `char` составляет 2 байта.
+
+**В**: Не является ли неоднозначным называть структуры данных, реализованные на основе массивов, «статическими структурами данных»? Стек также может выполнять операции выталкивания и проталкивания, эти операции являются «динамическими».
+
+Стек действительно может реализовывать динамические операции с данными, но структура данных остается «статической» (длина неизменна). Хотя структуры данных на основе массивов могут динамически добавлять или удалять элементы, их емкость фиксирована. Если объем данных превышает предварительно выделенный размер, необходимо создать новый больший массив и скопировать содержимое старого массива в новый.
+
+**В**: При создании стека (очереди) не указывается его размер, почему они являются «статическими структурами данных»?
+
+В языках программирования высокого уровня нам не нужно вручную указывать начальную емкость стека (очереди), эта работа выполняется автоматически внутри класса. Например, начальная емкость `ArrayList` в Java обычно составляет 10. Кроме того, операция расширения также реализуется автоматически. Подробнее см. в последующей главе «Списки».
+
+**В**: Метод преобразования прямого кода в дополнительный -- «сначала инвертировать, затем добавить 1», тогда преобразование дополнительного кода в прямой должно быть обратной операцией «сначала вычесть 1, затем инвертировать», но дополнительный код также можно преобразовать в прямой через «сначала инвертировать, затем добавить 1», почему так?
+
+Это связано с тем, что взаимное преобразование прямого и дополнительного кодов фактически является процессом вычисления «дополнения». Сначала дадим определение дополнения: предположим, что $a + b = c$, тогда мы называем $a$ дополнением $b$ до $c$, и наоборот, $b$ является дополнением $a$ до $c$.
+
+Дано двоичное число $0010$ длиной $n = 4$ бита. Если рассматривать это число как прямой код (не учитывая знаковый бит), то его дополнительный код получается через «сначала инвертировать, затем добавить 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$ может быть обобщен на двоичные числа любой разрядности.
\ No newline at end of file
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..fb3ffb4d0
--- /dev/null
+++ b/ru/docs/chapter_divide_and_conquer/binary_search_recur.md
@@ -0,0 +1,49 @@
+# Стратегия поиска «разделяй и властвуй»
+
+Мы уже знаем, что алгоритмы поиска делятся на две большие категории.
+
+- **Полный перебор**: реализуется путем обхода структуры данных, временная сложность составляет *O*(*n*).
+
+- **Адаптивный поиск**: использует особую организацию данных или априорную информацию, временная сложность может достигать *O*(log *n*) или даже *O*(1).
+
+На практике **алгоритмы поиска с временной сложностью** *O*(log *n*) **обычно реализуются на основе стратегии «разделяй и властвуй»**, например двоичный поиск и деревья.
+
+- **Двоичный поиск** на каждом шаге разбивает задачу (поиск целевого элемента в массиве) на более мелкую задачу (поиск целевого элемента в половине массива). Этот процесс продолжается до тех пор, пока массив не станет пустым или не будет найден целевой элемент.
+
+- **Деревья** являются представителями стратегии «разделяй и властвуй». В структурах данных, таких как двоичное дерево поиска, АВЛ-дерево, куча и др., временная сложность различных операций составляет *O*(log *n*).
+
+Стратегия «разделяй и властвуй» для двоичного поиска выглядит следующим образом.
+
+- **Задачу можно разбить**: двоичный поиск рекурсивно разбивает исходную задачу (поиск в массиве) на подзадачи (поиск в половине массива), что достигается сравнением среднего элемента с целевым элементом.
+
+- **Подзадачи независимы**: в двоичном поиске на каждом этапе обрабатывается только одна подзадача, которая не зависит от других подзадач.
+
+- **Решения подзадач не требуют объединения**: двоичный поиск направлен на поиск конкретного элемента, поэтому объединять решения подзадач не требуется. Когда подзадача решена, исходная задача также считается решенной.
+
+Стратегия «разделяй и властвуй» повышает эффективность поиска, поскольку при грубом поиске на каждом этапе можно исключить только один вариант, **тогда как при поиске «разделяй и властвуй» на каждом этапе можно исключить половину вариантов**.
+
+### Реализация двоичного поиска на основе стратегии «разделяй и властвуй»
+
+В предыдущих главах двоичный поиск был реализован на основе итераций. Теперь мы реализуем его на основе принципа «разделяй и властвуй» (рекурсии).
+
+!!! question
+
+ Дан упорядоченный массив `nums` длиной $n$, в котором все элементы уникальны. Необходимо найти элемент `target`.
+
+Для применения стратегии «разделяй и властвуй» обозначим подзадачу для поискового интервала $[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` или интервал не станет пустым.
+
+На рис. 12.4 иллюстрируется процесс применения стратегии «разделяй и властвуй» при двоичном поиске элемента 6 в массиве.
+
+
+
+В коде реализации объявляется рекурсивная функция `dfs()` для решения задачи $f(i, j)$:
+
+```src
+[file]{binary_search_recur}-[class]{}-[func]{binary_search}
+```
\ No newline at end of file
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..f98024024
--- /dev/null
+++ b/ru/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
@@ -0,0 +1,95 @@
+# Задача построения бинарного дерева
+
+!!! question
+
+ Даны прямой обход `preorder` и центральный обход `inorder` бинарного дерева. Необходимо построить бинарное дерево и вернуть корневой узел. Предполагается, что в бинарном дереве нет узлов с повторяющимися значениями (как показано на рисунке ниже).
+
+
+
+### Определение задачи для метода «разделяй и властвуй»
+
+Исходная задача определяется как построение бинарного дерева из `preorder` и `inorder`, что является типичной задачей для метода «разделяй и властвуй».
+
+- **Задачу можно разбить**: с точки зрения стратегии «разделяй и властвуй», мы можем разделить исходную задачу на две подзадачи: построение левого поддерева и построение правого поддерева, плюс одна операция: инициализация корневого узла. Для каждого поддерева (подзадачи) мы можем повторно использовать тот же метод разбиения, разделяя его на более мелкие поддеревья (подзадачи), пока не достигнем наименьшей подзадачи (пустое поддерево).
+- **Подзадачи независимы**: левое и правое поддеревья независимы друг от друга, между ними нет пересечений. При построении левого поддерева нам нужно обращать внимание только на части центрального и прямого обходов, соответствующие левому поддереву. То же самое для правого поддерева.
+- **Решения подзадач можно объединить**: как только мы получим левое и правое поддеревья (решения подзадач), мы можем связать их с корневым узлом и получить решение исходной задачи.
+
+### Как разделить поддеревья
+
+Согласно приведенному выше анализу, эту задачу можно решить с помощью стратегии «разделяй и властвуй», **но как разделить левое и правое поддеревья с помощью прямого обхода `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 ]`.
+
+
+
+### Описание интервалов поддеревьев с помощью переменных
+
+Согласно приведенному выше методу разбиения, **мы уже получили индексные интервалы корневого узла, левого поддерева и правого поддерева в `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)$ в индексе корневого узла правого поддерева означает "количество узлов в левом поддереве", рекомендуется понять это с помощью следующего рисунка.
+
+
+
+### Реализация кода
+
+Для повышения эффективности поиска $m$ мы используем хеш-таблицу `hmap` для хранения соответствия между элементами массива `inorder` и их индексами:
+
+```src
+[file]{build_tree}-[class]{}-[func]{build_tree}
+```
+
+На следующем рисунке показан рекурсивный процесс построения бинарного дерева. Каждый узел создается в процессе "спуска" рекурсии, а каждое ребро (ссылка) устанавливается в процессе "подъема".
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+Результаты разбиения прямого обхода `preorder` и центрального обхода `inorder` в каждой рекурсивной функции показаны на следующем рисунке.
+
+ -- это важная и распространенная стратегия в алгоритмах. Обычно она реализуется с помощью рекурсии и включает два этапа: разделение и объединение.
+
+1. **Разделение (этап разбиения)**: рекурсивное разбиение исходной задачи на две или более подзадачи до тех пор, пока не будет достигнута наименьшая подзадача.
+2. **Объединение (этап слияния)**: начиная с решения наименьших подзадач, снизу вверх объединяются решения всех других подзадач, чтобы построить решение исходной задачи.
+
+Сортировка слиянием является типичным примером применения стратегии «разделяй и властвуй» (см. рис. 12.1).
+
+1. **Разделение**: рекурсивное разбиение исходного массива (исходной задачи) на два подмассива (подзадачи) до тех пор, пока в подмассивах не останется по одному элементу (наименьшая подзадача).
+2. **Объединение**: снизу вверх объединяются упорядоченные подмассивы (решения подзадач), чтобы получить упорядоченный исходный массив (решение исходной задачи).
+
+
+
+## Определение задачи для метода «разделяй и властвуй»
+
+Чтобы определить, подходит ли задача для решения методом «разделяй и властвуй», можно использовать следующие критерии:
+
+1. **Задачу можно разбить**: исходную задачу можно разбить на более мелкие, аналогичные подзадачи, которые можно рекурсивно разделить аналогичным образом.
+2. **Подзадачи независимы**: подзадачи не пересекаются, не зависят друг от друга и могут быть решены независимо.
+3. **Решения подзадач можно объединить**: решение исходной задачи получается путем объединения решений подзадач.
+
+Очевидно, что сортировка слиянием соответствует этим трем критериям.
+
+1. **Задачу можно разбить**: рекурсивное разбиение массива (исходной задачи) на два подмассива (подзадачи).
+2. **Подзадачи независимы**: каждый подмассив можно отсортировать независимо (подзадачи можно решить независимо).
+3. **Решения подзадач можно объединить**: два упорядоченных подмассива (решения подзадач) можно объединить в один упорядоченный массив (решение исходной задачи).
+
+## Повышение эффективности с помощью стратегии «разделяй и властвуй»
+
+**Стратегия «разделяй и властвуй» позволяет не только эффективно решать алгоритмические задачи, но и повышать эффективность алгоритмов**. Алгоритмы быстрой сортировки, сортировки слиянием и пирамидальной сортировки быстрее, чем сортировка выбором, пузырьком и вставками, именно благодаря применению стратегии «разделяй и властвуй».
+
+Возникает вопрос: **почему метод «разделяй и властвуй» повышает эффективность алгоритма, в чем его основная логика**? Иными словами, почему разбиение большой задачи на несколько подзадач, решение этих подзадач и объединение их решений в решение исходной задачи оказывается более эффективным, чем непосредственное решение исходной задачи? Этот вопрос можно обсудить с точки зрения количества операций и параллельных вычислений.
+
+### Оптимизация количества операций
+
+Возьмем, к примеру, сортировку пузырьком, которая требует времени $O(n^2)$ для обработки массива длиной $n$. Предположим, что мы разделили массив на два подмассива, как показано на рис. 12.2. Тогда разбиение потребует времени $O(n)$, сортировка каждого подмассива -- $O((n/2)^2)$, а объединение двух подмассивов -- $O(n)$. Общая временная составит:
+
+$$
+O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n)
+$$
+
+
+
+Далее, решим следующее неравенство, в котором левая и правая части представляют общее количество операций до и после разбиения соответственно:
+
+$$
+\begin{aligned}
+n^2 & > \frac{n^2}{2} + 2n \\
+n^2 - \frac{n^2}{2} - 2n & > 0 \\
+n(n - 4) & > 0
+\end{aligned}
+$$
+
+**Это означает, что при $n > 4$ количество операций после разбиения меньше, и эффективность сортировки должна быть выше**. Обратите внимание, что временная сложность после разбиения остается квадратичной $O(n^2)$, но постоянный коэффициент в сложности уменьшается.
+
+**Если продолжить разбиение подмассивов пополам, пока в них не останется по одному элементу**, то получится сортировка слиянием, временная сложность которой составляет $O(n \log n)$.
+
+А что, **если мы установим несколько дополнительных точек разделения** и равномерно разделим исходный массив на $k$ подмассивов? Эта ситуация очень похожа на блочную сортировку, которая хорошо подходит для сортировки очень больших объемов данных, и теоретически ее временная сложность может достигать $O(n + k)$.
+
+### Оптимизация параллельных вычислений
+
+Известно, что подзадачи, созданные методом «разделяй и властвуй», независимы друг от друга, **поэтому их обычно можно решать параллельно**. Таким образом, этот метод не только снижает временную сложность алгоритма, **но и способствует параллельной оптимизации операционной системы**.
+
+Параллельная оптимизация особенно эффективна в многоядерной или многопроцессорной среде, поскольку система может одновременно обрабатывать несколько подзадач, более полно используя вычислительные ресурсы, что значительно сокращает общее время выполнения.
+
+Например, в блочной сортировке, изображенной на рис. 12.3, огромный объем данных равномерно распределяется по блокам. Задачи сортировки всех блоков можно распределить по вычислительным единицам, а затем объединить результаты.
+
+
+
+## Типичные сценарии применения стратегии «разделяй и властвуй»
+
+С одной стороны, стратегию «разделяй и властвуй» можно использовать для решения многих классических алгоритмических задач.
+
+- **Поиск ближайшей пары точек**: этот алгоритм сначала делит множество точек на две части, затем находит ближайшую пару точек в каждой части, а затем находит ближайшую пару точек, охватывающую обе части.
+- **Умножение больших чисел**: например, алгоритм Карацубы, который разлагает умножение больших чисел на несколько операций умножения и сложения меньших чисел.
+- **Умножение матриц**: например, алгоритм Штрассена, который разлагает умножение больших матриц на несколько операций умножения и сложения матриц меньшего размера.
+- **Задача о Ханойских башнях**: эту задачу можно решить с помощью рекурсии, что является типичным применением стратегии «разделяй и властвуй».
+- **Задача о количестве инверсий**: если в последовательности предыдущее число больше последующего, то эти два числа образуют инверсию. Задачу о количестве инверсий можно решить с помощью подхода «разделяй и властвуй» и сортировки слиянием.
+
+С другой стороны, стратегия «разделяй и властвуй» широко применяется в разработке алгоритмов и структур данных.
+
+- **Двоичный поиск**: такой поиск делит отсортированный массив на две части по индексу среднего элемента. Затем, в зависимости от результата сравнения целевого значения со средним элементом, решает, какую половину исключить, и выполняет ту же операцию на оставшейся части.
+- **Сортировка слиянием**: уже была рассмотрена в начале этого раздела, не будем еще раз повторяться.
+- **Быстрая сортировка**: эта сортировка выбирает опорное значение, затем делит массив на два подмассива, элементы одного из которых меньше опорного значения, а элементы другого -- больше. Затем выполняет ту же операцию с обеими частями, пока в подмассиве не останется один элемент.
+- **Блочная сортировка**: основная идея этой сортировки заключается в распределении данных по нескольким блокам и сортировке элементов в каждом из них. Затем происходит последовательное извлечение элементов из каждого блока для построения отсортированного массива.
+- **Деревья**: например, двоичные деревья поиска, АВЛ-дерево, красно-черное дерево, B-дерево, дерево B+ и т. д. Операции поиска, вставки и удаления в них можно рассматривать как применение стратегии «разделяй и властвуй».
+- **Кучи**: куча -- это особый вид полного двоичного дерева, и такие операции, как вставка, удаление и упорядочивание, фактически подразумевают использование метода «разделяй и властвуй».
+- **Хеш-таблицы**: хотя хеш-таблицы напрямую не применяют подход «разделяй и властвуй», некоторые решения для разрешения коллизий в хешировании косвенно используют эту стратегию. Например, длинные цепочки в методе цепной адресации преобразуются в красно-черные деревья для повышения эффективности поиска.
+
+Можно сказать, **что стратегия «разделяй и властвуй» -- это своего рода «скрытая» алгоритмическая идея**, присутствующая в различных алгоритмах и структурах данных.
\ No newline at end of file
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..8feab8076
--- /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. Меньший диск всегда должен находиться над большим диском.
+
+
+
+**Обозначим задачу о Ханойских башнях размера $i$ как $f(i)$**. Например, $f(3)$ представляет задачу о Ханойских башнях по перемещению $3$ дисков с `A` на `C`.
+
+### Рассмотрение базового случая
+
+Как показано на рисунке ниже, для задачи $f(1)$, то есть когда имеется только один диск, мы можем просто переместить его с `A` на `C`.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+Как показано на рисунке ниже, для задачи $f(2)$, то есть когда имеется два диска, **поскольку необходимо всегда соблюдать условие, что меньший диск находится над большим, нужно использовать `B` для завершения перемещения**.
+
+1. Сначала переместить верхний меньший диск с `A` на `B`.
+2. Затем переместить больший диск с `A` на `C`.
+3. Наконец, переместить меньший диск с `B` на `C`.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+Процесс решения задачи $f(2)$ можно обобщить следующим образом: **переместить два диска с `A` на `C`, используя `B`**. При этом `C` называется целевым стержнем, а `B` -- буферным стержнем.
+
+### Разбиение на подзадачи
+
+Для задачи $f(3)$, то есть когда имеется три диска, ситуация становится немного сложнее.
+
+Поскольку решения $f(1)$ и $f(2)$ уже известны, мы можем мыслить с точки зрения стратегии «разделяй и властвуй», **рассматривая два верхних диска на `A` как единое целое**, и выполнить шаги, показанные на рисунке ниже. Таким образом, три диска успешно перемещаются с `A` на `C`.
+
+1. Сделать `B` целевым стержнем, а `C` -- буферным стержнем, и переместить два диска с `A` на `B`.
+2. Переместить оставшийся один диск с `A` непосредственно на `C`.
+3. Сделать `C` целевым стержнем, а `A` -- буферным стержнем, и переместить два диска с `B` на `C`.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+По сути, **мы разбиваем задачу $f(3)$ на две подзадачи $f(2)$ и одну подзадачу $f(1)$**. После последовательного решения этих трех подзадач исходная задача также решается. Это показывает, что подзадачи независимы, и их решения можно объединить.
+
+Таким образом, мы можем обобщить стратегию «разделяй и властвуй» для решения задачи о Ханойских башнях, показанную на рисунке ниже: разбить исходную задачу $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)$ известно -- требуется только одна операция перемещения.
+
+
+
+### Реализация кода
+
+В коде мы объявляем рекурсивную функцию `dfs(i, src, buf, tar)`, которая перемещает $i$ дисков с верхушки стержня `src` на целевой стержень `tar`, используя буферный стержень `buf`:
+
+```src
+[file]{hanota}-[class]{}-[func]{solve_hanota}
+```
+
+Как показано на рисунке ниже, задача о Ханойских башнях формирует рекурсивное дерево высотой $n$, где каждый узел представляет подзадачу, соответствующую вызову функции `dfs()`, **поэтому временная сложность составляет $O(2^n)$, а пространственная сложность -- $O(n)$**.
+
+
+
+!!! quote
+
+ Задача о Ханойских башнях происходит из древней легенды. В храме в древней Индии у монахов было три высоких алмазных стержня и $64$ золотых диска разного размера. Монахи постоянно перемещали диски, веря, что в тот момент, когда последний диск будет правильно размещен, этот мир закончится.
+
+ Однако, даже если монахи будут перемещать диски каждую секунду, потребуется примерно $2^{64} \approx 1.84×10^{19}$ секунд, что составляет около $5850$ миллиардов лет, что намного превышает текущую оценку возраста Вселенной. Поэтому, если эта легенда правдива, нам не стоит беспокоиться о конце света.
\ No newline at end of file
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..1d65ee750
--- /dev/null
+++ b/ru/docs/chapter_divide_and_conquer/index.md
@@ -0,0 +1,9 @@
+# Разделяй и властвуй
+
+
+
+!!! abstract
+
+ Сложная задача разбивается на уровни, и каждое разбиение делает её проще.
+
+ Разделяй и властвуй раскрывает важный факт: начиная с простого, всё перестаёт быть сложным.
\ No newline at end of file
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..ccf3031e6
--- /dev/null
+++ b/ru/docs/chapter_divide_and_conquer/summary.md
@@ -0,0 +1,13 @@
+# Резюме
+
+### Ключевые моменты
+
+- «Разделяй и властвуй» — это распространенная стратегия проектирования алгоритмов, которая включает два этапа: разделение и объединение, и обычно реализуется на основе рекурсии.
+- Критерии для определения задачи, подходящей для метода «разделяй и властвуй», включают: можно ли разбить задачу, независимы ли подзадачи, можно ли объединить решения подзадач.
+- Сортировка слиянием является типичным применением стратегии «разделяй и властвуй», она рекурсивно разбивает массив на два подмассива равной длины, пока не останется один элемент, а затем начинает объединять их уровень за уровнем для завершения сортировки.
+- Применение стратегии «разделяй и властвуй» часто может повысить эффективность алгоритма. С одной стороны, эта стратегия уменьшает количество операций; с другой стороны, разделение способствует параллельной оптимизации системы.
+- «Разделяй и властвуй» может не только решать многие алгоритмические задачи, но и широко применяется в проектировании структур данных и алгоритмов, встречаясь повсеместно.
+- По сравнению с полным перебором адаптивный поиск более эффективен. Алгоритмы поиска с временной сложностью $O(\log n)$ обычно реализуются на основе стратегии «разделяй и властвуй».
+- Двоичный поиск является еще одним типичным применением стратегии «разделяй и властвуй», он не включает этап объединения решений подзадач. Мы можем реализовать двоичный поиск с помощью рекурсивного разделения.
+- В задаче построения двоичного дерева построение дерева (исходная задача) можно разделить на построение левого и правого поддеревьев (подзадачи), что достигается путем разделения индексных интервалов прямого и центрального обходов.
+- В задаче о Ханойских башнях задачу размера $n$ можно разделить на две подзадачи размера $n-1$ и одну подзадачу размера $1$. После последовательного решения этих трех подзадач исходная задача также решается.
\ No newline at end of file
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..f96282c48
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/dp_problem_features.md
@@ -0,0 +1,88 @@
+# Особенности задач динамического программирования
+
+В предыдущем разделе мы изучили, как динамическое программирование решает исходную задачу путем разложения на подзадачи. На самом деле разложение на подзадачи -- это универсальный алгоритмический подход, который по-разному применяется в методах «разделяй и властвуй», динамическом программировании и поиске с возвратом.
+
+- Алгоритм «разделяй и властвуй» рекурсивно делит исходную задачу на несколько независимых подзадач до самых минимальных и в процессе обратного хода объединяет решения всех подзадач.
+- Динамическое программирование также осуществляет рекурсивное разбиение задачи. Основное отличие от алгоритмов «разделяй и властвуй» заключается в том, что подзадачи в динамическом программировании взаимозависимы, и в процессе разбиения возникает множество перекрывающихся подзадач.
+- Алгоритмы поиска с возвратом исчерпывают все возможные решения методом проб и возвратов, осекая ненужные ветви поиска с помощью обрезки. Решение исходной задачи состоит из серии шагов принятия решений, каждый шаг можно рассматривать как подзадачу.
+
+На практике динамическое программирование часто используется для решения задач оптимизации, которые не только содержат перекрывающиеся подзадачи, но и обладают двумя другими важными свойствами: оптимальной подструктурой и отсутствием последействия.
+
+## Оптимальная подструктура
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Отсутствие последействия
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..7bf759cf0
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/dp_solution_pipeline.md
@@ -0,0 +1,183 @@
+# Подход к решению задач динамического программирования
+
+В предыдущих двух разделах были представлены основные характеристики задач динамического программирования. Теперь давайте рассмотрим два более практических вопроса.
+
+1. Как определить, является ли задача задачей динамического программирования?
+2. С чего начать решение задачи динамического программирования и каковы полные шаги?
+
+## Определение задачи
+
+В целом, если задача содержит перекрывающиеся подзадачи, оптимальную подструктуру и удовлетворяет свойству отсутствия последействия, то она обычно подходит для решения методом динамического программирования. Однако эти характеристики трудно извлечь непосредственно из описания задачи. Поэтому мы обычно ослабляем условия и **сначала наблюдаем, подходит ли задача для решения методом поиска с возвратом (полного перебора)**.
+
+**Задачи, подходящие для решения методом поиска с возвратом, обычно удовлетворяют "модели дерева решений"**. Такие задачи можно описать с помощью древовидной структуры, где каждый узел представляет решение, а каждый путь представляет последовательность решений.
+
+Другими словами, если задача содержит явную концепцию принятия решений и решение формируется через серию решений, то она удовлетворяет модели дерева решений и обычно может быть решена методом поиска с возвратом.
+
+На этой основе задачи динамического программирования имеют некоторые дополнительные "плюсы" для определения.
+
+- Задача содержит описание оптимизации, такое как максимум (минимум) или наибольшее (наименьшее) количество.
+- Состояние задачи может быть представлено списком, многомерной матрицей или деревом, и между состоянием и окружающими его состояниями существует рекуррентная зависимость.
+
+Соответственно, существуют и некоторые "минусы".
+
+- Цель задачи — найти все возможные решения, а не найти оптимальное решение.
+- В описании задачи есть явные признаки перестановок и комбинаций, требующие возврата конкретных множественных решений.
+
+Если задача удовлетворяет модели дерева решений и имеет достаточно явные "плюсы", мы можем предположить, что это задача динамического программирования, и проверить это в процессе решения.
+
+## Шаги решения задачи
+
+Процесс решения задач динамического программирования может различаться в зависимости от природы и сложности задачи, но обычно следует следующим шагам: описание решения, определение состояния, построение таблицы $dp$, вывод уравнения перехода состояния, определение граничных условий и т.д.
+
+Для более наглядной демонстрации шагов решения мы используем классическую задачу "минимальная сумма пути".
+
+!!! question
+
+ Дана двумерная сетка $n \times m$ `grid`, каждая ячейка сетки содержит неотрицательное целое число, представляющее стоимость этой ячейки. Робот начинает с верхней левой ячейки и может двигаться только вниз или вправо на один шаг за раз, пока не достигнет нижней правой ячейки. Верните минимальную сумму пути от верхней левой до нижней правой ячейки.
+
+На рисунке ниже показан пример, где минимальная сумма пути для данной сетки равна $13$.
+
+
+
+**Первый шаг: обдумать решение на каждом раунде, определить состояние и получить таблицу $dp$**
+
+Решение на каждом раунде в этой задаче — сделать один шаг вниз или вправо из текущей ячейки. Пусть индексы строки и столбца текущей ячейки равны $[i, j]$, тогда после шага вниз или вправо индексы становятся $[i+1, j]$ или $[i, j+1]$. Следовательно, состояние должно включать две переменные: индекс строки и индекс столбца, обозначаемые как $[i, j]$.
+
+Подзадача, соответствующая состоянию $[i, j]$: минимальная сумма пути от начальной точки $[0, 0]$ до $[i, j]$, решение обозначается как $dp[i, j]$.
+
+Таким образом, мы получаем двумерную матрицу $dp$, показанную на рисунке ниже, размер которой совпадает с входной сеткой `grid`.
+
+
+
+!!! note
+
+ Процесс динамического программирования и поиска с возвратом можно описать как последовательность решений, а состояние состоит из всех переменных решения. Оно должно содержать все переменные, описывающие прогресс решения задачи, и содержать достаточно информации для вывода следующего состояния.
+
+ Каждое состояние соответствует подзадаче, и мы определяем таблицу $dp$ для хранения решений всех подзадач. Каждая независимая переменная состояния является одним измерением таблицы $dp$. По сути, таблица $dp$ — это отображение между состояниями и решениями подзадач.
+
+**Второй шаг: найти оптимальную подструктуру и вывести уравнение перехода состояния**
+
+Для состояния $[i, j]$ оно может быть достигнуто только из верхней ячейки $[i-1, j]$ или левой ячейки $[i, j-1]$. Следовательно, оптимальная подструктура: минимальная сумма пути до $[i, j]$ определяется меньшей из минимальной суммы пути до $[i, j-1]$ и минимальной суммы пути до $[i-1, j]$.
+
+На основе приведенного выше анализа можно вывести уравнение перехода состояния, показанное на рисунке ниже:
+
+$$
+dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
+$$
+
+
+
+!!! note
+
+ На основе определенной таблицы $dp$ подумайте о связи между исходной задачей и подзадачами, найдите способ построения оптимального решения исходной задачи через оптимальные решения подзадач, то есть оптимальную подструктуру.
+
+ Как только мы найдем оптимальную подструктуру, мы можем использовать ее для построения уравнения перехода состояния.
+
+**Третий шаг: определить граничные условия и порядок перехода состояний**
+
+В этой задаче состояния в первой строке могут быть получены только из состояния слева, состояния в первом столбце могут быть получены только из состояния сверху, поэтому первая строка $i = 0$ и первый столбец $j = 0$ являются граничными условиями.
+
+Как показано на рисунке ниже, поскольку каждая ячейка переходит из левой и верхней ячеек, мы используем циклы для обхода матрицы: внешний цикл обходит строки, внутренний цикл обходит столбцы.
+
+
+
+!!! note
+
+ Граничные условия в динамическом программировании используются для инициализации таблицы $dp$, а в поиске используются для обрезки.
+
+ Суть порядка перехода состояний заключается в том, чтобы при вычислении решения текущей задачи все более мелкие подзадачи, от которых она зависит, уже были правильно вычислены.
+
+На основе приведенного выше анализа мы уже можем напрямую написать код динамического программирования. Однако декомпозиция подзадач — это подход "сверху вниз", поэтому реализация в порядке "полный перебор $\rightarrow$ мемоизация поиска $\rightarrow$ динамическое программирование" более соответствует мышлению.
+
+### Метод первый: полный перебор
+
+Начиная с состояния $[i, j]$, постоянно разбиваем на более мелкие состояния $[i-1, j]$ и $[i, j-1]$. Рекурсивная функция включает следующие элементы.
+
+- **Параметры рекурсии**: состояние $[i, j]$.
+- **Возвращаемое значение**: минимальная сумма пути от $[0, 0]$ до $[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`.
+
+По сути, причина перекрывающихся подзадач заключается в том, что **существует несколько путей из верхнего левого угла к определенной ячейке**.
+
+
+
+Каждое состояние имеет два выбора: вниз и вправо. Для перехода из верхнего левого угла в нижний правый требуется всего $m + n - 2$ шагов, поэтому в худшем случае временная сложность составляет $O(2^{m + n})$, где $n$ и $m$ — количество строк и столбцов сетки соответственно. Обратите внимание, что этот метод расчета не учитывает ситуацию вблизи границ сетки. Когда достигается граница сетки, остается только один выбор, поэтому фактическое количество путей будет несколько меньше.
+
+### Метод второй: мемоизация поиска
+
+Мы вводим список запоминания `mem` того же размера, что и сетка `grid`, для записи решений каждой подзадачи и обрезки перекрывающихся подзадач:
+
+```src
+[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem}
+```
+
+Как показано на рисунке ниже, после введения мемоизации все подзадачи нужно вычислить только один раз, поэтому временная сложность зависит от общего количества состояний, то есть размера сетки $O(nm)$.
+
+
+
+### Метод третий: динамическое программирование
+
+Реализация решения динамического программирования на основе итераций показана в коде ниже:
+
+```src
+[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp}
+```
+
+На рисунке ниже показан процесс перехода состояний минимальной суммы пути, который обходит всю сетку, **поэтому временная сложность составляет $O(nm)$**.
+
+Размер массива `dp` равен $n \times m$, **поэтому пространственная сложность составляет $O(nm)$**.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+=== "<11>"
+ 
+
+=== "<12>"
+ 
+
+### Оптимизация пространства
+
+Поскольку каждая ячейка связана только с ячейкой слева и сверху, мы можем использовать только одномерный массив для реализации таблицы $dp$.
+
+Обратите внимание, что поскольку массив `dp` может представлять только одну строку состояний, мы не можем заранее инициализировать состояния первого столбца, а обновляем их при обходе каждой строки:
+
+```src
+[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp}
+```
\ No newline at end of file
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..62db0b593
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/edit_distance_problem.md
@@ -0,0 +1,101 @@
+# Задача о редакционном расстоянии
+
+Редакционное расстояние, также называемое расстоянием Левенштейна, указывает минимальное количество изменений, необходимых для взаимного преобразования двух строк, и обычно используется в информационном поиске и обработке естественного языка для измерения сходства двух последовательностей.
+
+!!! question
+
+ Даны две строки $s$ и $t$, верните минимальное количество шагов редактирования, необходимых для преобразования $s$ в $t$.
+
+ Вы можете выполнять три операции редактирования в строке: вставить символ, удалить символ, заменить символ на любой другой символ.
+
+Как показано на рисунке ниже, для преобразования `kitten` в `sitting` требуется 3 шага редактирования, включая 2 операции замены и 1 операцию добавления; для преобразования `hello` в `algo` требуется 3 шага, включая 2 операции замены и 1 операцию удаления.
+
+
+
+**Задачу о редакционном расстоянии можно естественным образом объяснить с помощью модели дерева решений**. Строки соответствуют узлам дерева, один раунд решения (одна операция редактирования) соответствует ребру дерева.
+
+Как показано на рисунке ниже, без ограничения операций каждый узел может породить множество ребер, каждое ребро соответствует одной операции, что означает, что существует множество возможных путей преобразования от `hello` к `algo`.
+
+С точки зрения дерева решений цель этой задачи -- найти кратчайший путь между узлом `hello` и узлом `algo`.
+
+
+
+### Подход динамического программирования
+
+**Первый шаг: обдумать решение на каждом раунде, определить состояние, чтобы получить таблицу $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$. Следовательно, состояние -- это текущие рассматриваемые $i$-й и $j$-й символы в $s$ и $t$, обозначаемые как $[i, j]$.
+
+Состояние $[i, j]$ соответствует подзадаче: **минимальное количество шагов редактирования, необходимых для изменения первых $i$ символов $s$ на первые $j$ символов $t$**.
+
+Таким образом, получаем двумерную таблицу $dp$ размером $(i+1) \times (j+1)$.
+
+**Второй шаг: найти оптимальную подструктуру, затем вывести уравнение перехода состояния**
+
+Рассмотрим подзадачу $dp[i, j]$, символы в конце соответствующих двух строк -- это $s[i-1]$ и $t[j-1]$, можно разделить на три случая, показанные на рисунке ниже, в зависимости от различных операций редактирования.
+
+1. Добавить $t[j-1]$ после $s[i-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]$.
+
+
+
+На основе приведенного выше анализа можно получить оптимальную подструктуру: минимальное количество шагов редактирования $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]
+$$
+
+**Третий шаг: определить граничные условия и порядок перехода состояния**
+
+Когда обе строки пусты, количество шагов редактирования равно $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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
\ No newline at end of file
diff --git a/ru/docs/chapter_dynamic_programming/index.md b/ru/docs/chapter_dynamic_programming/index.md
new file mode 100644
index 000000000..32a1e5c73
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/index.md
@@ -0,0 +1,3 @@
+# Динамическое программирование
+
+{width="3.2760411198600177in" height="4.239583333333333in"}
\ No newline at end of file
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..4ceef3df4
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
@@ -0,0 +1,100 @@
+# Динамическое программирование
+
+*Динамическое программирование* является важной парадигмой в алгоритмах. Ее суть заключается в разбиении задачи на серию более мелких подзадач. Сохранение решений подзадач позволяет избежать повторных вычислений, что значительно повышает временную эффективность.
+
+В этом разделе мы начнем с классического примера и сначала представим его решение методом перебора. Мы понаблюдаем за наличием перекрывающихся подзадач, а затем постепенно выведем более эффективное решение с использованием динамического программирования.
+
+!!! question "Подъем по лестнице"
+
+ Дана лестница с $n$ ступенями. На каждом шаге можно подниматься на $1$ или $2$ ступени. Сколько существует способов добраться до вершины лестницы?
+
+Как показано на рисунке ниже, для лестницы с тремя ступенями существует три способа добраться до вершины.
+
+
+
+Цель этой задачи -- найти количество способов, **и можно попробовать использовать для ее решения метод поиска с возвратом**. Более конкретно -- можно представить подъем по лестнице как процесс многократного выбора: начать с пола, на каждом этапе выбирать подъем на одну или две ступени, при достижении вершины лестницы количество способов увеличивается на 1, а при превышении вершины происходит обрезка. Ниже приведен код реализации.
+
+```src
+[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack}
+```
+
+## Первый метод: полный перебор
+
+Алгоритм поиска с возвратом обычно не разбивает задачу явным образом, а рассматривает ее решение как серию шагов принятия решений, исследуя пути обхода и выполняя обрезку.
+
+Можно попытаться проанализировать эту задачу с точки зрения разбиения. Пусть для достижения $i$-й ступени существует $dp[i]$ способов, тогда $dp[i]$ является исходной задачей, а ее подзадачи включают следующие:
+
+$$
+dp[i-1], dp[i-2], \dots, dp[2], dp[1]
+$$
+
+На каждом этапе можно подниматься только на одну или две ступени, поэтому перед на $i$-й ступенью мы находились либо на $(i - 1)$-й, либо на $(i - 2)$-й ступени. Другими словами, на $i$-ю ступень можно перейти только с $(i - 1)$-й или $(i - 2)$-й ступени.
+
+Отсюда следует важный вывод: **количество способов добраться до** $(i - 1)$-**й ступени плюс количество способов добраться до** $(i - 2)$-**й ступени равно количеству способов добраться до** $i$-**й ступени**. Формула выглядит следующим образом:
+
+$$
+dp[i] = dp[i-1] + dp[i-2]
+$$
+
+Это означает, что в задаче подъема по лестнице между подзадачами существует рекуррентная зависимость, и **решение исходной задачи можно построить из решений подзадач**. На рисунке ниже демонстрируется эта рекуррентная зависимость.
+
+
+
+Можно получить решение методом полного перебора на основе рекуррентной формулы. Начиная с $dp[n]$, **большая задача рекурсивно разбивается на сумму двух меньших задач**, пока не будут достигнуты минимальные подзадачи $dp[1]$ и $dp[2]$, для которых возвращаются известные решения: $dp[1] = 1$, $dp[2] = 2$. То есть для достижения 1-й и 2-й ступеней существует 1 и 2 способа соответственно.
+
+Рассмотрим следующий код, который, как и стандартный код поиска с возвратом, относится к поиску в глубину, но является более лаконичным.
+
+```src
+[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs}
+```
+
+На рисунке ниже изображено рекурсивное дерево, образованное полным перебором. Для задачи $dp[n]$ глубина рекурсивного дерева равна $n$, а временная сложность составляет $O(2^n)$. Экспоненциальный рост приводит к взрывному увеличению, и при вводе достаточно большого $n$ можно столкнуться с длительной работой алгоритма.
+
+
+
+Как видно из рисунка, **экспоненциальная временная сложность вызвана перекрывающимися подзадачами**. Например, $dp[9]$ разбивается на $dp[8]$ и $dp[7]$, $dp[8]$ разбивается на $dp[7]$ и $dp[6]$ -- обе задачи содержат подзадачу $dp[7]$. Таким образом, в подзадачах содержатся более мелкие перекрывающиеся подзадачи, и большая часть вычислительных ресурсов тратится на их обработку.
+
+## Второй метод: мемоизация поиска
+
+Для повышения эффективности алгоритма **необходимо, чтобы все перекрывающиеся подзадачи вычислялись только один раз**. Для этого мы объявим массив 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)$, это является значительным скачком.
+
+
+
+## Третий метод: динамическое программирование
+
+**Мемоизация поиска -- это метод «сверху вниз»**: мы начинаем с исходной задачи (корневой узел) и рекурсивно разбиваем более крупные подзадачи на более мелкие, пока не достигнем минимальных подзадач с известным решением (листовые узлы). Затем через возврат поэтапно собираем решения подзадач, чтобы построить решение исходной задачи.
+
+В отличие от этого подхода **динамическое программирование представляет собой метод «снизу вверх»**: начиная с решения минимальных подзадач, итеративно строится решение более крупных подзадач, пока не будет получено решение исходной задачи.
+
+Поскольку динамическое программирование не включает этап возврата, оно реализуется с использованием циклов и итераций, без необходимости в рекурсии. В следующем коде мы инициализируем массив dp для хранения решений подзадач, который выполняет ту же функцию запоминания, что и массив mem в мемоизации поиска.
+
+```src
+[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp}
+```
+
+На рисунке ниже иллюстрируется процесс выполнения приведенного выше кода.
+
+
+
+Как и в алгоритмах поиска с возвратом, в динамическом программировании используется концепция состояния для обозначения определенной стадии решения задачи. Каждое состояние соответствует подзадаче и соответствующему локальному оптимальному решению. Например, состояние задачи подъема по лестнице определяется текущей ступенью $i$.
+
+На основе этого можно обобщить часто используемые термины динамического программирования.
+
+- Массив `dp` называется таблицей dp, $dp[i]$ обозначает решение подзадачи, соответствующей состоянию $i$.
+- Состояния, соответствующие минимальным подзадачам (1-я и 2-я ступени лестницы), называются начальными состояниями.
+- Рекуррентное соотношение $dp[i] = dp[i-1] + dp[i-2]$ называется уравнением перехода состояния.
+
+## Оптимизация пространства
+
+Внимательный читатель может заметить, что, **поскольку** $dp[i]$ **зависит только от** $dp[i-1]$ **и** $dp[i-2]$, **нам не нужно использовать целый массив** `dp` **для хранения всех решений подзадач**
\ No newline at end of file
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..0195e9f13
--- /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 как процесс, состоящий из $n$ раундов принятия решений, где для каждого предмета существует два решения: не класть в рюкзак и положить в рюкзак. Таким образом, эта задача удовлетворяет модели дерева решений.
+
+Цель этой задачи — найти "максимальную ценность предметов, которые можно поместить в рюкзак при ограниченной вместимости", поэтому с большой вероятностью это задача динамического программирования.
+
+**Шаг первый: обдумать решение на каждом раунде, определить состояние и получить таблицу $dp$**
+
+Для каждого предмета: если не класть его в рюкзак, вместимость рюкзака не изменяется; если положить в рюкзак, вместимость рюкзака уменьшается. Отсюда получаем определение состояния: текущий номер предмета $i$ и вместимость рюкзака $c$, обозначаемые как $[i, c]$.
+
+Состояние $[i, c]$ соответствует подзадаче: **максимальная ценность первых $i$ предметов в рюкзаке вместимостью $c$**, обозначаемая как $dp[i, c]$.
+
+Необходимо найти $dp[n, cap]$, поэтому требуется двумерная таблица $dp$ размером $(n+1) \times (cap+1)$.
+
+**Шаг второй: найти оптимальную подструктуру и вывести уравнение перехода состояния**
+
+После принятия решения относительно предмета $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$, то можно выбрать только вариант не класть предмет в рюкзак.
+
+**Шаг третий: определить граничные условия и порядок перехода состояний**
+
+Когда нет предметов или вместимость рюкзака равна $0$, максимальная ценность равна $0$, то есть первый столбец $dp[i, 0]$ и первая строка $dp[0, c]$ равны $0$.
+
+Текущее состояние $[i, c]$ переходит из состояния сверху $[i-1, c]$ и состояния слева сверху $[i-1, c-wgt[i-1]]$, поэтому с помощью двух вложенных циклов можно последовательно обойти всю таблицу $dp$.
+
+На основе приведенного выше анализа мы далее последовательно реализуем решения методом полного перебора, мемоизации поиска и динамического программирования.
+
+### Первый метод: полный перебор
+
+Код поиска включает следующие элементы:
+
+- **Параметры рекурсии**: состояние $[i, c]$.
+- **Возвращаемое значение**: решение подзадачи $dp[i, c]$.
+- **Условие завершения**: когда номер предмета выходит за границы $i = 0$ или оставшаяся вместимость рюкзака равна $0$, завершить рекурсию и вернуть ценность $0$.
+- **Обрезка**: если текущий вес предмета превышает оставшуюся вместимость рюкзака, можно выбрать только вариант не класть предмет в рюкзак.
+
+```src
+[file]{knapsack}-[class]{}-[func]{knapsack_dfs}
+```
+
+Как показано на рисунке ниже, поскольку каждый предмет порождает две ветви поиска (не выбирать и выбирать), временная сложность составляет $O(2^n)$.
+
+Наблюдая за деревом рекурсии, легко заметить наличие перекрывающихся подзадач, например $dp[1, 10]$ и других. Когда предметов много, вместимость рюкзака велика, особенно когда много предметов одинакового веса, количество перекрывающихся подзадач значительно увеличивается.
+
+
+
+### Второй метод: мемоизация поиска
+
+Чтобы гарантировать, что перекрывающиеся подзадачи вычисляются только один раз, мы используем список мемоизации `mem` для записи решений подзадач, где `mem[i][c]` соответствует $dp[i, c]$.
+
+После введения мемоизации **временная сложность зависит от количества подзадач**, то есть $O(n \times cap)$. Код реализации приведен ниже:
+
+```src
+[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem}
+```
+
+На рисунке ниже показаны ветви поиска, которые были обрезаны при мемоизации поиска.
+
+
+
+### Третий метод: динамическое программирование
+
+Динамическое программирование по сути представляет собой процесс заполнения таблицы $dp$ при переходе состояний. Код приведен ниже:
+
+```src
+[file]{knapsack}-[class]{}-[func]{knapsack_dp}
+```
+
+Как показано на рисунке ниже, временная сложность и пространственная сложность определяются размером массива `dp`, то есть $O(n \times cap)$.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+=== "<11>"
+ 
+
+=== "<12>"
+ 
+
+=== "<13>"
+ 
+
+=== "<14>"
+ 
+
+### Оптимизация пространства
+
+Поскольку каждое состояние зависит только от состояний предыдущей строки, мы можем использовать два массива для последовательного продвижения, снижая пространственную сложность с $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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+В реализации кода нам нужно только удалить первое измерение $i$ массива `dp` и изменить внутренний цикл на обратный порядок обхода:
+
+```src
+[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp}
+```
\ No newline at end of file
diff --git a/ru/docs/chapter_dynamic_programming/summary.md b/ru/docs/chapter_dynamic_programming/summary.md
new file mode 100644
index 000000000..ae9c9472b
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/summary.md
@@ -0,0 +1,25 @@
+# Резюме
+
+### Ключевые моменты
+
+- Динамическое программирование разбивает задачу на подзадачи и за счет сохранения решений подзадач избегает повторных вычислений, повышая эффективность вычислений.
+- Без учета времени все задачи динамического программирования можно решить методом поиска с возвратом (полным перебором), но в дереве рекурсии существует большое количество перекрывающихся подзадач, что крайне неэффективно. Введение списка мемоизации позволяет сохранять решения всех вычисленных подзадач, тем самым гарантируя, что перекрывающиеся подзадачи вычисляются только один раз.
+- Мемоизация поиска -- это рекурсивный метод «сверху вниз», в то время как соответствующее динамическое программирование -- это итеративный метод «снизу вверх», который подобен «заполнению таблицы». Поскольку текущее состояние зависит только от некоторых локальных состояний, мы можем исключить одно измерение таблицы $dp$, тем самым снижая пространственную сложность.
+- Разбиение на подзадачи -- это универсальный алгоритмический подход, который имеет различные свойства в методах «разделяй и властвуй», динамическом программировании и поиске с возвратом.
+- Задачи динамического программирования обладают тремя основными свойствами: перекрывающиеся подзадачи, оптимальная подструктура и отсутствие последействия.
+- Если оптимальное решение исходной задачи можно построить из оптимальных решений подзадач, то она обладает оптимальной подструктурой.
+- Отсутствие последействия означает, что для данного состояния его будущее развитие зависит только от этого состояния и не зависит от всех прошлых состояний. Многие задачи комбинаторной оптимизации не обладают свойством отсутствия последействия и не могут быть быстро решены с помощью динамического программирования.
+
+**Задача о рюкзаке**
+
+- Задача о рюкзаке -- одна из самых типичных задач динамического программирования, имеющая варианты: рюкзак 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]$, редактирование текущего символа не требуется.
+- В задаче о редакционном расстоянии состояние зависит от состояний сверху, слева и слева сверху, поэтому после оптимизации пространства ни прямой, ни обратный обход не позволяют правильно выполнить переход состояния. Для этого мы используем переменную для временного хранения состояния слева сверху, тем самым преобразуя задачу к эквивалентной задаче о полном рюкзаке, что позволяет выполнять прямой обход после оптимизации пространства.
\ No newline at end of file
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..49413b554
--- /dev/null
+++ b/ru/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
@@ -0,0 +1,180 @@
+# Задача о неограниченном рюкзаке
+
+
+
+
+## Задача о неограниченном рюкзаке
+
+
+
+
+
+
+
+### Подход динамического программирования
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Реализация кода
+
+
+
+
+
+
+
+### Оптимизация пространства
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Задача размена монет
+
+
+
+
+
+
+
+
+
+
+### Подход динамического программирования
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Реализация кода
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Оптимизация пространства
+
+
+
+
+
+
+
+## Задача размена монет II
+
+
+
+
+
+
+
+### Подход динамического программирования
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Реализация кода
+
+
+
+
+### Оптимизация пространства
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_graph/graph.md b/ru/docs/chapter_graph/graph.md
new file mode 100644
index 000000000..ca0c0b823
--- /dev/null
+++ b/ru/docs/chapter_graph/graph.md
@@ -0,0 +1,83 @@
+# Графы
+
+Граф -- это нелинейная структура данных, состоящая из вершин (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}
+$$
+
+Если рассматривать вершины как узлы, а ребра как ссылки (указатели), соединяющие узлы, то граф можно рассматривать как расширенный список. Как показано на рисунке ниже, **по сравнению с линейными отношениями (список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой** и, следовательно, являются более сложными.
+
+
+
+## Основные типы и понятия графов
+
+В зависимости от наличия направления у ребер графы делятся на неориентированные (undirected graph) и ориентированные (directed graph), как показано на рисунке ниже.
+
+- В неориентированном графе ребро представляет собой двустороннюю связь между двумя вершинами, например дружеские отношения в социальных сетях.
+- В ориентированном графе ребро имеет направление. То есть ребра $A \rightarrow B$ и $A \leftarrow B$ независимы друг от друга, например отношения подписки--подписчики.
+
+
+
+Если все вершины связаны, то граф называется связным (connected graph), иначе -- несвязным (disconnected graph), как показано на рисунке ниже.
+
+- В связном графе из любой вершины можно достичь любой другой вершины.
+- В несвязном графе существуют по крайней мере две вершины, между которыми нет пути.
+
+
+
+Можно также добавить к ребрам переменную «вес», получив взвешенный граф (weighted graph), как показано на рисунке ниже. Например, в мобильных играх, таких как Honor of Kings, система рассчитывает близость между игроками на основе времени совместной игры. Такую сеть близости можно представить в виде взвешенного графа.
+
+
+
+Со структурой данных графа связаны следующие основные понятия.
+
+- Смежность (adjacency): если между двумя вершинами существует ребро, они называются смежными. На рисунке выше вершины, смежные с вершиной 1, -- это вершины 2, 3 и 5.
+- Путь (path): последовательность ребер от вершины A до вершины B называется путем от A до B. На рисунке выше последовательность ребер 1-5-2-4 является путем от вершины 1 до вершины 4.
+- Степень (degree): количество ребер, присоединенных к вершине. Для ориентированного графа входящая степень (in-degree) показывает, сколько ребер ведет к данной вершине, а исходящая степень (out-degree) показывает, сколько ребер выходит из данной вершины.
+
+## Представление графа
+
+Графы можно представить с помощью «матрицы смежности» и «списка смежности». Рассмотрим пример с неориентированным графом.
+
+### Матрица смежности
+
+Пусть количество вершин графа равно $n$, матрица смежности (adjacency matrix) представляет граф в виде матрицы размером $n \times n$, где каждая строка (столбец) соответствует вершине, а элементы матрицы обозначают наличие ребра. Значение $1$ соответствует наличию ребра между двумя вершинами, значение $0$ -- отсутствию.
+
+Обозначим матрицу смежности как $M$, а список вершин как $V$. Тогда элемент матрицы $M[i, j] = 1$ указывает на наличие ребра между вершинами $V[i]$ и $V[j]$, в противном случае элемент матрицы $M[i, j] = 0$.
+
+
+
+Матрица смежности обладает следующими свойствами.
+
+- В простом графе вершина не может быть соединена с самой собой, поэтому элементы на главной диагонали матрицы смежности не имеют значения.
+- Для неориентированного графа ребра в обоих направлениях эквивалентны, поэтому матрица смежности симметрична относительно главной диагонали.
+- Заменив элементы матрицы смежности с $1$ и $0$ на веса ребер, можно представить взвешенный граф.
+
+Используя матрицу смежности для представления графа, можно напрямую обращаться к элементам матрицы для получения информации о ребрах, что делает операции добавления, удаления, поиска и изменения достаточно эффективными с временной сложностью $O(1)$. Однако пространственная сложность матрицы составляет $O(n^2)$, что требует значительных затрат памяти.
+
+### Список смежности
+
+Список смежности (adjacency list) представляет граф с помощью $n$ списков, где узлы списка представляют вершины. $i$-й список соответствует вершине $i$ и содержит все смежные вершины (вершины, соединенные с данной вершиной). На рисунке ниже показан пример графа, представленного с помощью списка смежности.
+
+
+
+В списке смежности хранятся только существующие ребра, а общее количество ребер обычно значительно меньше $n^2$, что делает его более экономичным по памяти. Однако для поиска ребра в списке смежности необходимо просматривать список, что делает его менее эффективным по времени по сравнению с матрицей смежности.
+
+Как видно из рисунка выше, **структура списка смежности очень похожа на цепную адресацию в хеш-таблицах, поэтому можно использовать аналогичные методы для оптимизации эффективности**. Например, если список длинный, его можно преобразовать в АВЛ-дерево или красно-черное дерево, чтобы повысить временную эффективность с $O(n)$ до $O(\log n)$. Также можно преобразовать список в хеш-таблицу, чтобы снизить временную сложность до $O(1)$.
+
+## Типичные сценарии применения графов
+
+Многие реальные системы можно моделировать с помощью графов, а соответствующие задачи могут быть сведены к задачам вычисления на графах.
+
+ Таблица Типичные графы в реальной жизни
+
+| | Вершина | Ребро | Задача вычисления на графе |
+| ------------------- | --------------- | -------------------------------------- | ------------------------------------- |
+| Социальные сети | Пользователи | Дружеские связи | Рекомендации потенциальных друзей |
+| Линии метро | Станции | Связь между станциями | Рекомендации по кратчайшему маршруту |
+| Солнечная система | Небесные тела | Взаимодействие гравитации между телами | Расчет орбит планет |
\ No newline at end of file
diff --git a/ru/docs/chapter_graph/graph_operations.md b/ru/docs/chapter_graph/graph_operations.md
new file mode 100644
index 000000000..e497a292a
--- /dev/null
+++ b/ru/docs/chapter_graph/graph_operations.md
@@ -0,0 +1,54 @@
+# Основные операции с графами
+
+Основные операции с графами можно разделить на операции с ребрами и операции с вершинами. В зависимости от способа представления (матрица смежности или список смежности) реализация будет различаться.
+
+## Реализация на основе матрицы смежности
+
+Ниже приведены операции для заданного неориентированного графа с количеством вершин *n*. Способы реализации показаны на рис. 9.7.
+
+- **Добавление или удаление ребра**: достаточно изменить соответствующее ребро в матрице смежности за время *O*(1). Поскольку граф неориентированный, необходимо обновить ребра в обоих направлениях.
+
+- **Добавление вершины**: в конец матрицы смежности добавляется строка и столбец, которые заполняются нулями. Временная сложность равна *O*(*n*).
+
+- **Удаление вершины**: удаляется строка и столбец из матрицы смежности. В худшем случае при удалении первой строки и столбца необходимо переместить (*n* − 1)² элементов влево вверх, что занимает время *O*(*n*²).
+
+- **Инициализация**: передается *n* вершин, инициализируется список вершин vertices длиной *n* за время *O*(*n*). Инициализируется матрица смежности adjMat размером *n*×*n* за время *O*(*n*²).
+
+=== "Инициализация матрицы смежности"
+ 
+
+=== "Добавление ребра"
+ 
+
+=== "Удаление ребра"
+ 
+
+=== "Добавление вершины"
+ 
+
+=== "Удаление вершины"
+ 
+
+Ниже приведен код реализации графа на основе матрицы смежности.
+
+```src
+[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{}
+```
+
+## Реализация на основе списка смежности
+
+Ниже приведены описания операций для неориентированного графа с общим количеством вершин *n* и ребер *m*. Способы реализации показаны на рис. 9.8.
+
+- **Добавление ребра**: достаточно добавить ребро в конец связного списка, соответствующего вершине за время *O*(1). Поскольку граф неориентированный, необходимо добавить ребра в обоих направлениях.
+
+- **Удаление ребра**: необходимо найти и удалить указанное ребро в связном списке, соответствующем вершине, за время *O*(*m*). В неориентированном графе необходимо удалить ребра в обоих направлениях.
+
+- **Добавление вершины**: добавляется связный список в список смежности, а новая вершина становится головным узлом списка. Требуется время *O*(1).
+
+- **Удаление вершины**: необходимо пройтись по всему списку смежности и удалить все ребра, содержащие указанную вершину. Требуется время *O*(*n* + *m*).
+
+- **Инициализация**: в списке смежности создается *n* вершин и *2m* ребер. Требуется время *O*(*n* + *m*).
+
+```src
+[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{}
+```
\ No newline at end of file
diff --git a/ru/docs/chapter_graph/graph_traversal.md b/ru/docs/chapter_graph/graph_traversal.md
new file mode 100644
index 000000000..c3e32cd7a
--- /dev/null
+++ b/ru/docs/chapter_graph/graph_traversal.md
@@ -0,0 +1,116 @@
+# Обход графа
+
+Дерево представляет отношение «один ко многим», в то время как граф обладает большей свободой и может представлять любые отношения «многие ко многим». Таким образом, **мы можем рассматривать дерево как частный случай графа**. Очевидно, что **операция обхода дерева также является частным случаем операции обхода графа**.
+
+Как графы, так и деревья требуют применения алгоритмов поиска для выполнения операций обхода. Способы обхода графа также можно разделить на два типа: обход в ширину и обход в глубину.
+
+## Обход в ширину
+
+**Обход в ширину — это способ обхода от ближайших к дальним вершинам, начиная с некоторой вершины, всегда приоритетно посещая ближайшие вершины и расширяясь слой за слоем**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, сначала обходятся все смежные вершины этой вершины, затем обходятся все смежные вершины следующей вершины и так далее, пока все вершины не будут посещены.
+
+
+
+### Реализация алгоритма
+
+BFS обычно реализуется с помощью очереди, код показан ниже. Очередь обладает свойством «первым пришел — первым вышел», что соответствует идее BFS «от ближайших к дальним».
+
+1. Добавить начальную вершину обхода `startVet` в очередь и начать цикл.
+2. В каждой итерации цикла извлечь вершину из начала очереди и записать посещение, затем добавить все смежные вершины этой вершины в конец очереди.
+3. Повторять шаг `2.` до тех пор, пока все вершины не будут посещены.
+
+Чтобы предотвратить повторный обход вершин, необходимо использовать хеш-множество `visited` для записи того, какие узлы уже были посещены.
+
+!!! tip
+
+ Хеш-множество можно рассматривать как хеш-таблицу, которая хранит только `key`, но не хранит `value`. Оно может выполнять операции добавления, удаления, поиска и изменения `key` за время $O(1)$. Благодаря уникальности `key`, хеш-множество обычно используется для удаления дубликатов данных и в других сценариях.
+
+```src
+[file]{graph_bfs}-[class]{}-[func]{graph_bfs}
+```
+
+Код довольно абстрактный, рекомендуется сопоставить его с рисунком ниже для более глубокого понимания.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+=== "<11>"
+ 
+
+!!! question "Является ли последовательность обхода в ширину уникальной?"
+
+ Нет. Обход в ширину требует только порядка обхода «от ближайших к дальним», **а порядок обхода нескольких вершин на одинаковом расстоянии может быть произвольным**. Например, на рисунке выше порядок посещения вершин $1$, $3$ может быть изменен, порядок посещения вершин $2$, $4$, $6$ также может быть произвольным.
+
+### Анализ сложности
+
+**Временная сложность**: Все вершины будут добавлены в очередь и извлечены из нее один раз, что требует $O(|V|)$ времени; в процессе обхода смежных вершин, поскольку граф неориентированный, все ребра будут посещены $2$ раза, что требует $O(2|E|)$ времени; в целом используется $O(|V| + |E|)$ времени.
+
+**Пространственная сложность**: Список `res`, хеш-множество `visited`, очередь `que` могут содержать максимум $|V|$ вершин, используя $O(|V|)$ пространства.
+
+## Обход в глубину
+
+**Обход в глубину — это способ обхода, при котором приоритетно идут до конца, а когда пути нет, возвращаются назад**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, посещается одна из смежных вершин текущей вершины, пока не будет достигнут конец, затем происходит возврат, снова идут до конца и возвращаются, и так далее, пока все вершины не будут обойдены.
+
+
+
+### Реализация алгоритма
+
+Эта парадигма алгоритма «идти до конца и возвращаться» обычно реализуется на основе рекурсии. Подобно обходу в ширину, при обходе в глубину также необходимо использовать хеш-множество `visited` для записи посещенных вершин, чтобы избежать повторного посещения вершин.
+
+```src
+[file]{graph_dfs}-[class]{}-[func]{graph_dfs}
+```
+
+Процесс алгоритма обхода в глубину показан на рисунке ниже.
+
+- **Прямая пунктирная линия представляет рекурсию вниз**, указывая на то, что был открыт новый рекурсивный метод для посещения новой вершины.
+- **Изогнутая пунктирная линия представляет возврат назад**, указывая на то, что этот рекурсивный метод уже вернулся, возвращаясь к позиции, где был открыт этот метод.
+
+Для более глубокого понимания рекомендуется объединить рисунок ниже с кодом и мысленно смоделировать (или нарисовать) весь процесс DFS, включая то, когда открывается и когда возвращается каждый рекурсивный метод.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
\ No newline at end of file
diff --git a/ru/docs/chapter_graph/index.md b/ru/docs/chapter_graph/index.md
new file mode 100644
index 000000000..23b05514f
--- /dev/null
+++ b/ru/docs/chapter_graph/index.md
@@ -0,0 +1,9 @@
+# Графы
+
+
+
+!!! abstract
+
+ В жизненном путешествии мы подобны узлам, связанным бесчисленными невидимыми рёбрами.
+
+ Каждая встреча и расставание оставляют уникальный след в этой огромной сетевой структуре.
\ No newline at end of file
diff --git a/ru/docs/chapter_graph/summary.md b/ru/docs/chapter_graph/summary.md
new file mode 100644
index 000000000..edac0e409
--- /dev/null
+++ b/ru/docs/chapter_graph/summary.md
@@ -0,0 +1,31 @@
+# Резюме
+
+### Основные моменты
+
+- Граф состоит из вершин и ребер и может быть представлен как множество вершин и множество ребер.
+- По сравнению с линейными отношениями (список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой и, следовательно, являются более сложными.
+- В ориентированном графе ребра имеют направление, в связном графе любая вершина достижима, во взвешенном графе каждое ребро содержит переменную веса.
+- Матрица смежности использует матрицу для представления графа, где каждая строка (столбец) соответствует вершине, элементы матрицы представляют ребра, используя $1$ или $0$ для обозначения наличия или отсутствия ребра между двумя вершинами. Матрица смежности очень эффективна в операциях добавления, удаления, поиска и изменения, но требует значительных затрат памяти.
+- Список смежности использует несколько связных списков для представления графа, где $i$-й список соответствует вершине $i$ и содержит все смежные вершины этой вершины. Список смежности более экономичен по памяти по сравнению с матрицей смежности, но менее эффективен по времени, так как требуется просматривать список для поиска ребра.
+- Когда связный список в списке смежности становится слишком длинным, его можно преобразовать в красно-черное дерево или хеш-таблицу для повышения эффективности поиска.
+- С точки зрения алгоритмического подхода, матрица смежности воплощает принцип "обмена пространства на время", а список смежности воплощает принцип "обмена времени на пространство".
+- Графы можно использовать для моделирования различных реальных систем, таких как социальные сети, линии метро и т. д.
+- Дерево является частным случаем графа, обход дерева также является частным случаем обхода графа.
+- Обход графа в ширину — это метод поиска, который расширяется слой за слоем от ближайших к дальним вершинам, обычно реализуется с помощью очереди.
+- Обход графа в глубину — это метод поиска, который идет до конца, а затем возвращается назад, когда дальше идти некуда, часто реализуется на основе рекурсии.
+
+### Вопросы и ответы
+
+**В**: Путь — это последовательность вершин или последовательность ребер?
+
+В разных языковых версиях Википедии определения различаются: в английской версии "путь — это последовательность ребер", а в китайской версии "путь — это последовательность вершин". Вот оригинальный текст из английской версии: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.
+
+В данном тексте путь рассматривается как последовательность ребер, а не последовательность вершин. Это связано с тем, что между двумя вершинами может существовать несколько ребер, и каждое ребро соответствует отдельному пути.
+
+**В**: Будут ли в несвязном графе вершины, которые невозможно обойти?
+
+В несвязном графе, начиная с определенной вершины, существует по крайней мере одна вершина, которую невозможно достичь. Для обхода несвязного графа необходимо установить несколько начальных точек, чтобы обойти все связные компоненты графа.
+
+**В**: В списке смежности должен ли быть определенный порядок "всех вершин, соединенных с данной вершиной"?
+
+Порядок может быть произвольным. Однако в практических приложениях может потребоваться сортировка по определенным правилам, например, в порядке добавления вершин или в порядке возрастания значений вершин и т. д., что помогает быстро находить вершины "с определенным экстремальным значением".
\ No newline at end of file
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..d272b6788
--- /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*. Каждый предмет можно выбрать только один раз, **но можно выбрать часть предмета, при этом стоимость рассчитывается пропорционально выбранной массе**. Требуется найти максимальную стоимость предметов в рюкзаке при ограниченной вместимости. Пример показан на рисунке ниже.
+
+
+
+Задача о дробном рюкзаке и задача о рюкзаке 0-1 в целом очень похожи: состояние включает текущий предмет *i* и вместимость *c*, цель -- найти максимальную стоимость при ограниченной вместимости рюкзака.
+
+Отличие в том, что в данной задаче допускается выбирать часть предмета. Как показано на рисунке ниже, **можно произвольно разделять предметы и рассчитывать соответствующую стоимость пропорционально массе**.
+
+1. Для предмета *i* его стоимость на единицу массы равна *val*[*i* - 1]/*wgt*[*i* - 1], сокращенно -- удельная стоимость.
+2. Предположим, что в рюкзак помещена часть предмета *i* массой *w*, тогда увеличение стоимости рюкзака составит *w* × *val*[*i* - 1]/*wgt*[*i* - 1].
+
+
+
+### Определение жадной стратегии
+
+Максимизация общей стоимости предметов в рюкзаке, по сути, является максимизацией стоимости предметов на единицу массы. Из этого можно вывести жадную стратегию, изображенную на рисунке ниже.
+
+1. Отсортировать предметы по убыванию стоимости на единицу массы.
+2. Перебирать все предметы и **жадно выбирать на каждом этапе предмет с наивысшей стоимостью на единицу массы**.
+3. Если оставшейся вместимости рюкзака недостаточно, использовать часть текущего предмета для заполнения рюкзака.
+
+
+
+### Реализация кода
+
+Мы создали класс предмета `Item` для сортировки предметов по стоимости на единицу массы. Выполняем цикл жадного выбора, прерываем его и возвращаем решение, когда рюкзак заполнен:
+
+```src
+[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack}
+```
+
+Временная сложность встроенных алгоритмов сортировки обычно составляет *O*(*n* log *n*), пространственная сложность обычно *O*(log *n*) или *O*(*n*), в зависимости от конкретной реализации в языке программирования.
+
+Помимо сортировки, в худшем случае необходимо перебрать весь список предметов, **поэтому временная сложность составляет *O*(*n*)**, где *n* -- количество предметов.
+
+Поскольку инициализируется список объектов `Item`, **пространственная сложность составляет *O*(*n*)**.
+
+### Доказательство корректности
+
+Используем метод доказательства от противного. Предположим, что предмет *x* имеет наивысшую стоимость на единицу массы, и некоторый алгоритм находит максимальную стоимость `res`, но это решение не содержит предмет *x*.
+
+Теперь извлечем из рюкзака единицу массы любого предмета и заменим ее единицей массы предмета *x*. Поскольку предмет *x* имеет наивысшую стоимость на единицу массы, общая стоимость после замены обязательно будет больше `res`. **Это противоречит тому, что `res` является оптимальным решением, следовательно, оптимальное решение обязательно должно содержать предмет *x***.
+
+Для других предметов в этом решении мы также можем построить подобное противоречие. В общем, **предметы с большей стоимостью на единицу массы всегда являются более оптимальным выбором**, что доказывает эффективность жадной стратегии.
+
+Как показано на рисунке ниже, если рассматривать массу предмета и стоимость предмета на единицу массы как горизонтальную и вертикальную оси двумерного графика соответственно, то задача о дробном рюкзаке может быть преобразована в «нахождение максимальной площади в ограниченном интервале горизонтальной оси». Эта аналогия может помочь понять эффективность жадной стратегии с геометрической точки зрения.
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_greedy/greedy_algorithm.md b/ru/docs/chapter_greedy/greedy_algorithm.md
new file mode 100644
index 000000000..ccd6f6af6
--- /dev/null
+++ b/ru/docs/chapter_greedy/greedy_algorithm.md
@@ -0,0 +1,91 @@
+# Жадные алгоритмы
+
+Жадный алгоритм -- это распространенный метод решения задач оптимизации. Его основная идея заключается в том, чтобы на каждом этапе принятия решения выбирать наиболее оптимальный на данный момент вариант, т. е. с жадностью принимать локально оптимальные решения в надежде получить глобально оптимальное решение. Жадные алгоритмы просты и эффективны, и они находят широкое применение в решении многих практических задач.
+
+Жадные алгоритмы и динамическое программирование часто используются для решения задач оптимизации. Между ними есть некоторые сходства, например оба метода зависят от свойств оптимальной подструктуры, но их принципы работы различны.
+
+- Динамическое программирование для получения текущего решения учитывает все предыдущие решения и использует решения предыдущих подзадач для построения решения текущей подзадачи.
+- Жадный алгоритм не учитывает предыдущие решения, а просто движется вперед, делая жадные выборы и постепенно сокращая область задачи, пока она не будет решена.
+
+Чтобы лучше понять принцип работы жадного алгоритма, рассмотрим его применение к задаче о размене монет. Она уже была рассмотрена в разделе «Задача о полном рюкзаке», и, вероятно, вы с ней уже знакомы.
+
+!!! question
+
+ Дано *n* видов монет, номинал *i*-й монеты равен *coins*[*i* - 1], целевая сумма *amt*, каждый вид монет можно выбирать многократно. Найти минимальное количество монет, необходимое для получения целевой суммы. Если получить целевую сумму невозможно, вернуть -1.
+
+Жадная стратегия, применяемая в этой задаче, показана на следующем рисунке. Для заданной целевой суммы **мы жадно выбираем монету, которая не превышает и наиболее близка к этой сумме**, и повторяем этот шаг, пока не будет достигнута целевая сумма.
+
+
+
+Ниже приведен код реализации.
+
+```src
+[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy}
+```
+
+Вы можете невольно воскликнуть: «Эврика!» Жадный алгоритм решает задачу размена монет всего за десяток строк кода.
+
+## Преимущества и ограничения жадных алгоритмов
+
+**Жадные алгоритмы не только просты в реализации, но и обычно очень эффективны**. Если в приведенном выше коде обозначить минимальный номинал монеты как *min*(*coins*), то жадный выбор выполняется не более *amt* / *min*(*coins*) раз. Тогда временная сложность составляет *O*(*amt* / *min*(*coins*)). Это на порядок меньше временной сложности решения с использованием динамического программирования *O*(*n* × *amt*).
+
+Однако **для некоторых комбинаций номиналов монет жадный алгоритм не сможет найти оптимальное решение**. На следующем рисунке приведены два примера.
+
+- **Положительный пример** *coins* = [1, 5, 10, 20, 50, 100]: при данной комбинации монет для любого *amt* жадный алгоритм сможет найти оптимальное решение.
+- **Отрицательный пример** *coins* = [1, 20, 50]: если *amt* = 60, жадный алгоритм найдет комбинацию 50 + 1 × 10, всего 11 монет. Но динамическое программирование может найти оптимальное решение 20 + 20 + 20, всего 3 монеты.
+- **Отрицательный пример** *coins* = [1, 49, 50]: если *amt* = 98, жадный алгоритм найдет комбинацию 50 + 1 × 48, всего 49 монет. Но динамическое программирование может найти оптимальное решение 49 + 49, всего 2 монеты.
+
+
+
+Таким образом, для задачи размена монет жадный алгоритм не гарантирует нахождение глобально оптимального решения и может привести к очень плохому решению. Для решения этой задачи лучше подходит динамическое программирование.
+
+В общем случае жадные алгоритмы применимы в следующих двух ситуациях:
+
+1. **можно гарантировать нахождение оптимального решения**: в этом случае жадный алгоритм часто является лучшим выбором, так как он обычно более эффективен, чем методы обратного поиска и динамического программирования;
+2. **можно найти приближенное оптимальное решение**: в этом случае жадный алгоритм также применим. Для многих сложных задач поиск глобально оптимального решения очень затруднителен, и возможность найти субоптимальное решение с высокой эффективностью является весьма хорошим результатом.
+
+## Свойства жадных алгоритмов
+
+Итак, возникает вопрос: какие задачи подходят для решения с помощью жадного алгоритма? Или, иначе говоря, в каких случаях жадный алгоритм может гарантировать нахождение оптимального решения?
+
+По сравнению с динамическим программированием условия применения жадного алгоритма более строгие, и они в основном сосредоточены на двух свойствах задачи.
+
+- **Свойство жадного выбора**: жадный алгоритм может гарантировать получение оптимального решения только в случае, если локально оптимальный выбор всегда приводит к глобально оптимальному решению.
+- **Оптимальная подструктура**: оптимальное решение исходной задачи содержит оптимальное решение подзадачи.
+
+Оптимальная подструктура уже была рассмотрена в главе «Динамическое программирование», поэтому здесь не будем повторяться. Стоит отметить, что оптимальная подструктура некоторых задач не всегда очевидна, но их все же можно решить с помощью жадного алгоритма.
+
+Основное внимание уделяется методам определения свойства жадного выбора. Хотя его описание кажется простым, **на практике доказательство этого свойства для многих задач является сложной задачей**.
+
+Например, в задаче о размене монет мы можем легко привести контрпример для опровержения свойства жадного выбора. Однако доказательство его истинности значительно сложнее. На вопрос «**При каких условиях можно использовать жадный алгоритм для решения задачи размена монет**?» обычно мы можем дать лишь интуитивный или примерный ответ, но не можем предоставить строгое математическое доказательство.
+
+!!! quote
+
+ Существует статья, в которой представлен алгоритм временной сложности *O*(*n*³) для определения того, можно ли использовать жадный алгоритм для нахождения оптимального решения для любой суммы при данной комбинации монет.
+
+ Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.
+
+## Этапы решения задач жадным алгоритмом
+
+Процесс решения жадных задач можно разделить на следующие три этапа:
+
+1. **анализ задачи**: изучение и понимание характеристик задачи, включая определение состояния, цели оптимизации и ограничения. Этот этап также присутствует в методах поиска с возвратом и динамического программирования;
+2. **определение жадной стратегии**: определение того, как делать жадный выбор на каждом шаге. Эта стратегия позволяет уменьшать размер задачи на каждом шаге и в конечном итоге решить всю задачу;
+3. **доказательство корректности**: обычно требуется доказать наличие свойства жадного выбора и оптимальной подструктуры задачи. Этот этап может потребовать использования математических доказательств, таких как метод математической индукции или доказательство от противного.
+
+Определение жадной стратегии является ключевым этапом решения задачи, но его реализация может быть непростой по следующим причинам.
+
+- **Жадные стратегии для различных задач могут значительно различаться**. Для многих задач жадная стратегия очевидна, и ее можно определить с помощью общего размышления и эмпирических проб. Однако для некоторых сложных задач жадная стратегия может оказаться очень скрытой, что потребует значительного опыта в решении задач и навыков работы с алгоритмами.
+- **Некоторые жадные стратегии могут быть обманчивыми**. Бывает, жадная стратегия разработана с полной уверенностью в ее правильности, код написан и отправлен на выполнение. Но оказывается, что некоторые тестовые примеры не проходят проверку на корректность. Это происходит потому, что разработанная жадная стратегия является лишь частично правильной, как в случае с задачей о размене монет, описанной выше.
+
+Для обеспечения корректности необходимо провести строгое математическое доказательство жадной стратегии, **обычно с использованием метода доказательства от противного или метода математической индукции**.
+
+Тем не менее доказательство корректности может оказаться непростой задачей. Если нет ясности, обычно выбирается отладка кода на основе тестовых примеров с постепенной модификацией и проверкой жадной стратегии.
+
+## Типичные задачи для жадного алгоритма
+
+Жадный алгоритм часто применяется в задачах оптимизации, удовлетворяющих свойству жадного выбора и оптимальной подструктуре. Ниже перечислены некоторые типичные задачи для жадного алгоритма.
+
+- **Задача о размене монет**: при некоторых комбинациях монет жадный алгоритм всегда может получить оптимальное решение.
+- **Задача о расписании интервалов**: пусть у вас есть несколько задач, каждая из которых выполняется в течение определенного времени, и ваша цель -- выполнить как можно больше задач. Если каждый раз выбирать задачу с наименьшим временем окончания, то жадный алгоритм может дать оптимальное решение.
+- **Задача о дробном рюкзаке**: дана группа предметов и вместимость. Ваша цель -- выбрать группу предметов так, чтобы
\ No newline at end of file
diff --git a/ru/docs/chapter_greedy/index.md b/ru/docs/chapter_greedy/index.md
new file mode 100644
index 000000000..26dfe239e
--- /dev/null
+++ b/ru/docs/chapter_greedy/index.md
@@ -0,0 +1,9 @@
+# Жадность
+
+
+
+!!! abstract
+
+ Подсолнух поворачивается к солнцу, постоянно стремясь к максимальному росту.
+
+ Жадная стратегия через серию простых выборов постепенно приводит к наилучшему ответу.
\ No newline at end of file
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..24ddc589e
--- /dev/null
+++ b/ru/docs/chapter_greedy/max_capacity_problem.md
@@ -0,0 +1,99 @@
+# Задача о максимальной вместимости
+
+!!! question
+
+ Дан массив $ht$, каждый элемент которого представляет высоту вертикальной перегородки. Любые две перегородки в массиве и пространство между ними могут образовать контейнер.
+
+ Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина — разностью индексов двух перегородок в массиве.
+
+ Необходимо выбрать в массиве две перегородки так, чтобы вместимость образованного контейнера была максимальной, и вернуть максимальную вместимость. Пример показан на рисунке ниже.
+
+
+
+Контейнер образован любыми двумя перегородками, **поэтому состоянием в данной задаче являются индексы двух перегородок, обозначим их как $[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$ — длинная перегородка.
+
+
+
+Как показано на рисунке ниже, **если сейчас переместить длинную перегородку $j$ ближе к короткой перегородке $i$, вместимость обязательно уменьшится**.
+
+Это происходит потому, что после перемещения длинной перегородки $j$ ширина $j-i$ определенно уменьшится; а высота определяется короткой перегородкой, поэтому высота может только остаться неизменной ($i$ остается короткой перегородкой) или уменьшиться (перемещенная $j$ становится короткой перегородкой).
+
+
+
+Рассуждая в обратном направлении, **только сжимая короткую перегородку $i$ внутрь, мы можем увеличить вместимость**. Потому что хотя ширина определенно уменьшится, **высота может увеличиться** (перемещенная короткая перегородка $i$ может стать длиннее). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается.
+
+
+
+Таким образом, можно вывести жадную стратегию для данной задачи: инициализировать два указателя по краям контейнера, на каждом шаге сжимать внутрь указатель, соответствующий короткой перегородке, пока два указателя не встретятся.
+
+На рисунке ниже показан процесс выполнения жадной стратегии.
+
+1. В начальном состоянии указатели $i$ и $j$ находятся на противоположных концах массива.
+2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость.
+3. Сравнить высоты перегородок $i$ и $j$ и переместить короткую перегородку внутрь на одну позицию.
+4. Циклически выполнять шаги `2.` и `3.`, пока $i$ и $j$ не встретятся.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+### Реализация кода
+
+Код выполняет не более $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]
+$$
+
+
+
+При наблюдении обнаруживается, что **эти пропущенные состояния фактически являются всеми состояниями при перемещении длинной перегородки $j$ внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно приводит к уменьшению вместимости. То есть пропущенные состояния не могут быть оптимальным решением, **их пропуск не приведет к потере оптимального решения**.
+
+Приведенный выше анализ показывает, что операция перемещения короткой перегородки является «безопасной», жадная стратегия эффективна.
\ No newline at end of file
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..f8b174bb2
--- /dev/null
+++ b/ru/docs/chapter_greedy/max_product_cutting_problem.md
@@ -0,0 +1,85 @@
+# Задача о максимальном произведении при разбиении
+
+!!! question
+
+ Дано положительное целое число $n$, которое нужно разбить на сумму как минимум двух положительных целых чисел. Найдите максимальное произведение всех целых чисел после разбиения, как показано на рисунке ниже.
+
+
+
+Предположим, что мы разбиваем $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$ должны быть разбиты**.
+
+**Жадная стратегия один**: Если в схеме разбиения содержится множитель $\geq 4$, то он должен быть разбит дальше. В итоговой схеме разбиения должны появляться только три множителя: $1$, $2$, $3$.
+
+
+
+Далее подумаем, какой множитель является оптимальным. Среди трех множителей $1$, $2$, $3$ очевидно, что $1$ -- наихудший, потому что $1 \times (n-1) < n$ всегда верно, то есть выделение $1$ фактически приводит к уменьшению произведения.
+
+Как показано на рисунке ниже, когда $n = 6$, имеем $3 \times 3 > 2 \times 2 \times 2$. **Это означает, что выделение $3$ лучше, чем выделение $2$**.
+
+**Жадная стратегия два**: В схеме разбиения должно быть не более двух $2$. Потому что три $2$ всегда можно заменить на два $3$, получив таким образом большее произведение.
+
+
+
+Исходя из вышесказанного, можно вывести следующую жадную стратегию:
+
+1. Для входного целого числа $n$ постоянно выделяем из него множитель $3$, пока остаток не станет равным $0$, $1$ или $2$.
+2. Когда остаток равен $0$, это означает, что $n$ кратно $3$, поэтому никаких действий не требуется.
+3. Когда остаток равен $2$, не продолжаем разбиение, сохраняем его.
+4. Когда остаток равен $1$, поскольку $2 \times 2 > 1 \times 3$, следует заменить последнюю $3$ на $2$.
+
+### Реализация кода
+
+Как показано на рисунке ниже, нам не нужно использовать цикл для разбиения целого числа, а можно использовать операцию целочисленного деления для получения количества $3$ -- $a$, и операцию взятия остатка для получения остатка $b$, при этом имеем:
+
+$$
+n = 3 a + b
+$$
+
+Обратите внимание, что для граничного случая $n \leq 3$ необходимо выделить $1$, произведение равно $1 \times (n - 1)$.
+
+```src
+[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting}
+```
+
+
+
+**Временная сложность зависит от метода реализации операции возведения в степень в языке программирования**. Возьмем 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$**: Предположим, что в оптимальной схеме разбиения содержится три $2$, тогда их определенно можно заменить на два $3$, произведение будет больше. Это противоречит предположению.
\ No newline at end of file
diff --git a/ru/docs/chapter_greedy/summary.md b/ru/docs/chapter_greedy/summary.md
new file mode 100644
index 000000000..3c8d0dd5f
--- /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)$.
\ No newline at end of file
diff --git a/ru/docs/chapter_hashing/hash_algorithm.md b/ru/docs/chapter_hashing/hash_algorithm.md
new file mode 100644
index 000000000..65c0de8e1
--- /dev/null
+++ b/ru/docs/chapter_hashing/hash_algorithm.md
@@ -0,0 +1,93 @@
+# Хеш-алгоритмы
+
+В предыдущих двух разделах были рассмотрены принципы работы хеш-таблиц и методы обработки хеш-коллизий. Однако ни открытая адресация, ни цепная адресация **не могут уменьшить возникновение хеш-коллизий, они лишь обеспечивают нормальную работу хеш-таблицы при возникновении коллизий**.
+
+Если хеш-коллизии возникают слишком часто, производительность хеш-таблицы резко ухудшается. Как показано на рисунке ниже, для хеш-таблицы с цепной адресацией в идеальном случае пары ключ-значение равномерно распределены по всем корзинам, достигая наилучшей эффективности поиска; в худшем случае все пары ключ-значение хранятся в одной корзине, и временная сложность деградирует до $O(n)$.
+
+
+
+**Распределение пар ключ-значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем берется остаток от деления на длину массива:
+
+```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.
+
+ Таблица
+
+
+```src
+[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{}
+```
+
+
+
+
+## Открытая адресация
+
+
+
+
+
+
+
+### Линейная проба
+
+
+
+### Квадратичная проба
+
+
+
+### Множественное хеширование
+
+
+
+## Выбор языка программирования
+
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_hashing/hash_map.md b/ru/docs/chapter_hashing/hash_map.md
new file mode 100644
index 000000000..5a854f120
--- /dev/null
+++ b/ru/docs/chapter_hashing/hash_map.md
@@ -0,0 +1,131 @@
+# Хеш-таблицы
+
+Хеш-таблица реализует эффективный поиск элементов через установление соответствия между ключом `key` и значением `value`. Более конкретно, передав ключ в хеш-таблицу, можно получить соответствующее значение за время $O(1)$.
+
+Пусть имеется $n$ студентов, у каждого из которых есть имя и номер. Если нужно реализовать функцию «ввести номер студента и получить соответствующее имя», то можно использовать хеш-таблицу, как показано на рисунке ниже.
+
+
+
+Помимо хеш-таблиц, функцией поиска также обладают массивы и связные списки. Сравнение их эффективности приведено в таблице ниже.
+
+- **Добавление элемента**: достаточно добавить элемент в конец массива (списка) за время $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)
+ ```
+
+
+
+??? 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%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%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)
+ ```
+
+
+
+??? 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%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%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%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%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
+
+## Простая реализация хеш-таблицы
+
+Рассмотрим самый простой случай, **когда хеш-таблица реализуется с помощью одного массива**. В хеш-таблице каждый пустой слот массива называется корзиной, и каждая корзина может хранить одну пару ключ--значение. Таким образом, операция поиска заключается в нахождении корзины, соответствующей ключу `key`, и получении значения `value` из нее.
+
+Как определить корзину, соответствующую ключу? Это осуществляется с помощью хеш-функции. Хеш-функция предназначена для отображения большого входного пространства в меньшее выходное пространство. В хеш-таблице входное пространство -- это все ключи, а выходное пространство -- это все корзины (индексы массива). Другими словами, **передав ключ в хеш-функцию, можно определить место хранения пары ключ--значение в массиве**.
+
+Процесс вычисления хеш-функции для ключа включает следующие этапы:
+
+1. вычисление хеш-значения с помощью некоторого хеш-алгоритма `hash()`;
+2. взятие остатка от деления хеш-значения на количество корзин `capacity` (длину массива) для получения индекса массива `index`, соответствующего ключу `key`.
+
+```shell
+index = hash(key) % capacity
+```
+
+После этого можно использовать значение `index` для доступа к соответствующей корзине в хеш-таблице и получения значения `value`.
+
+Предположим, что длина массива `capacity = 100`, хеш-алгоритм `hash(key) = key`. Тогда хеш-функция будет иметь вид `key % 100`. На рисунке ниже показан принцип работы хеш-функции на примере ключа «номер» и значения «имя» для студента.
+
+
+
+В коде ниже реализуется простая хеш-таблица. Здесь `key` и `value` заключены в класс `Pair` для представления пары ключ--значение.
+
+```src
+[file]{array_hash_map}-[class]{array_hash_map}-[func]{}
+```
+
+## Хеш-коллизии и расширение
+
+По своей сути хеш-функция выполняет отображение входного пространства, состоящего из всех ключей, в выходное пространство, состоящее из всех индексов массива. Причем входное пространство зачастую значительно больше выходного. Следовательно, **теоретически неизбежна ситуация, когда несколько входов соответствуют одному выходу**.
+
+Для хеш-функции в примере выше, если последние две цифры ключа совпадают, результат хеш-функции также совпадает. Например, при запросе студентов с номерами 12836 и 20336 мы получим:
+
+```shell
+12836 % 100 = 36
+20336 % 100 = 36
+```
+
+Оба номера указывают на одно и то же имя, что очевидно неверно. Такую ситуацию, когда несколько входов соответствуют одному выходу, называют хеш-коллизией.
+
+
+
+Логично предположить, что чем больше емкость хеш-таблицы $n$, тем ниже вероятность распределения нескольких ключей в одну корзину и тем меньше коллизий. Поэтому **можно уменьшить количество хеш-коллизий, увеличивая емкость хеш-таблицы**.
+
+Как показано на рисунке ниже, до увеличения емкости пары ключ--значение `(136, A)` и `(236, D)` попадали в одну корзину, а после увеличения емкости коллизия исчезла.
+
+
+
+Подобно увеличению емкости массива, увеличение емкости хеш-таблицы требует переноса всех пар ключ--значение из старой хеш-таблицы в новую, что является очень затратной по времени операцией. Кроме того, поскольку емкость хеш-таблицы изменяется, необходимо заново вычислять местоположение хранения всех пар ключ--значение с помощью хеш-функции, что еще больше увеличивает вычислительные затраты процесса расширения. Поэтому в языках программирования обычно резервируется достаточно большая емкость хеш-таблицы, чтобы избежать частого увеличения.
+
+Коэффициент заполнения является важным понятием для хеш-таблицы. Он определяется как количество элементов в хеш-таблице, деленное на количество корзин, и используется для оценки степени серьезности хеш-коллизий, **а также часто служит условием для увеличения емкости хеш-таблицы**. Например, в Java, когда коэффициент заполнения превышает $0.75$, система увеличивает емкость хеш-таблицы в $2$ раза.
\ No newline at end of file
diff --git a/ru/docs/chapter_hashing/index.md b/ru/docs/chapter_hashing/index.md
new file mode 100644
index 000000000..c61a92c8a
--- /dev/null
+++ b/ru/docs/chapter_hashing/index.md
@@ -0,0 +1,12 @@
+```markdown
+# Хеш-таблицы
+
+
+
+!!! abstract
+
+ *Хеш-таблица* реализует эффективный поиск элементов через установление соответствия между ключом key и значением value. Более конкретно, передав ключ в хеш-таблицу, можно получить соответствующее значение за время *O*(1).
+
+ Пусть имеется *n* студентов, у каждого из которых есть имя и номер. Если нужно реализовать функцию «ввести номер студента и получить соответствующее имя», то можно использовать хеш-таблицу, как показано на рис. 6.1.
+
+```
\ No newline at end of file
diff --git a/ru/docs/chapter_hashing/summary.md b/ru/docs/chapter_hashing/summary.md
new file mode 100644
index 000000000..730dcc529
--- /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 часто используется в приложениях и протоколах безопасности.
+- Языки программирования обычно предоставляют встроенные хеш-алгоритмы для типов данных, используемые для вычисления индекса корзины в хеш-таблице. Как правило, только неизменяемые объекты являются хешируемыми.
+
+### Вопросы и ответы
+
+**В**: В каких случаях временная сложность хеш-таблицы составляет $O(n)$?
+
+Когда хеш-коллизии достаточно серьезны, временная сложность хеш-таблицы может деградировать до $O(n)$. Когда хеш-функция хорошо спроектирована, емкость установлена разумно и коллизии распределены равномерно, временная сложность составляет $O(1)$. При использовании встроенных хеш-таблиц языков программирования обычно считается, что временная сложность составляет $O(1)$.
+
+**В**: Почему бы не использовать хеш-функцию $f(x) = x$? Тогда не будет коллизий.
+
+При хеш-функции $f(x) = x$ каждый элемент соответствует уникальному индексу корзины, что эквивалентно массиву. Однако входное пространство обычно значительно больше выходного пространства (длины массива), поэтому последний шаг хеш-функции часто заключается в взятии остатка от деления на длину массива. Другими словами, цель хеш-таблицы — отобразить большое пространство состояний в меньшее пространство и обеспечить эффективность поиска $O(1)$.
+
+**В**: Хеш-таблица в основе реализована на массивах, связных списках, бинарных деревьях, но почему её эффективность может быть выше?
+
+Во-первых, временная эффективность хеш-таблицы повышается, но пространственная эффективность снижается. Хеш-таблица имеет значительную часть неиспользуемой памяти.
+
+Во-вторых, временная эффективность повышается только в определенных сценариях использования. Если функциональность может быть реализована с той же временной сложностью с использованием массива или связного списка, то обычно это быстрее, чем хеш-таблица. Это связано с тем, что вычисление хеш-функции требует затрат, и константа временной сложности больше.
+
+Наконец, временная сложность хеш-таблицы может деградировать. Например, при цепной адресации мы выполняем операцию поиска в связном списке или красно-черном дереве, что все еще имеет риск деградации до времени $O(n)$.
+
+**В**: Имеет ли множественное хеширование недостаток невозможности прямого удаления элементов? Может ли пространство, помеченное как удаленное, быть использовано повторно?
+
+Множественное хеширование является одним из видов открытой адресации, и все методы открытой адресации имеют недостаток невозможности прямого удаления элементов, требуя удаления с пометкой. Пространство, помеченное как удаленное, может быть использовано повторно. Когда новый элемент вставляется в хеш-таблицу и хеш-функция находит позицию, помеченную как удаленная, эта позиция может быть использована новым элементом. Это позволяет сохранить последовательность зондирования хеш-таблицы и обеспечить коэффициент использования пространства хеш-таблицы.
+
+**В**: Почему при линейном зондировании возникают хеш-коллизии при поиске элемента?
+
+При поиске с помощью хеш-функции находится соответствующая корзина и пара ключ-значение, и обнаруживается, что `key` не совпадает, что означает наличие хеш-коллизии. Поэтому метод линейного зондирования будет последовательно искать вниз в соответствии с заранее установленным шагом, пока не найдет правильную пару ключ-значение или не сможет найти и не выйдет из поиска.
+
+**В**: Почему увеличение емкости хеш-таблицы может уменьшить хеш-коллизии?
+
+Последний шаг хеш-функции часто заключается в взятии остатка от деления на длину массива $n$ (взятие остатка), чтобы выходное значение попало в диапазон индексов массива; после увеличения емкости длина массива $n$ изменяется, и индекс, соответствующий `key`, также может измениться. Несколько `key`, которые ранее попадали в одну корзину, после увеличения емкости могут быть распределены в несколько корзин, что уменьшает хеш-коллизии.
+
+**В**: Если нужна эффективная запись и чтение, то почему бы не использовать просто массив?
+
+Когда `key` данных представляет собой непрерывные целые числа в небольшом диапазоне, можно использовать массив напрямую — это просто и эффективно. Но когда `key` имеет другой тип (например, строка), необходимо использовать хеш-функцию для отображения `key` в индекс массива, а затем использовать массив корзин для хранения элементов. Такая структура и есть хеш-таблица.
\ No newline at end of file
diff --git a/ru/docs/chapter_heap/build_heap.md b/ru/docs/chapter_heap/build_heap.md
new file mode 100644
index 000000000..9874afbb2
--- /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)$. **Но этот расчет неточен, поскольку мы не учли тот факт, что количество узлов на нижних уровнях двоичного дерева намного больше, чем на верхних**.
+
+Далее выполним более точный расчет. Чтобы упростить вычисления, предположим, что дано "совершенное двоичное дерево" с количеством узлов $n$ и высотой $h$. Это предположение не повлияет на правильность результата вычислений.
+
+
+
+Как показано на рисунке выше, максимальное количество итераций "упорядочивания узла сверху вниз" равно расстоянию от этого узла до листового узла, а это расстояние и есть "высота узла". Следовательно, можно просуммировать "количество узлов × высота узла" для каждого уровня, **получив общее количество итераций упорядочивания для всех узлов**.
+
+$$
+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}
+$$
+
+Используя метод разностного вычитания, вычтем верхнее выражение $T(h)$ из нижнего $2 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)$, что очень эффективно**.
\ No newline at end of file
diff --git a/ru/docs/chapter_heap/heap.md b/ru/docs/chapter_heap/heap.md
new file mode 100644
index 000000000..48d322ecb
--- /dev/null
+++ b/ru/docs/chapter_heap/heap.md
@@ -0,0 +1,200 @@
+# Куча
+
+Куча (heap) -- это полное двоичное дерево, удовлетворяющее определенным условиям, и делится на два основных типа, как показано на рисунке ниже.
+
+- Минимальная куча (min heap): значение любого узла ≤ значений его дочерних узлов.
+- Максимальная куча (max heap): значение любого узла ≥ значений его дочерних узлов.
+
+
+
+Куча, как частный случай полного двоичного дерева, обладает следующими свойствами:
+
+- узлы на самом нижнем уровне заполняются слева, остальные уровни полностью заполнены;
+- корневой узел двоичного дерева называется вершиной кучи, а самый правый узел на нижнем уровне -- основанием кучи;
+- для максимальной (минимальной) кучи значение элемента на вершине (т. е. корневом узле) является наибольшим (наименьшим).
+
+## Основные операции с кучей
+
+Следует отметить, что многие языки программирования содержат приоритетную очередь (priority queue), которая является абстрактной структурой данных, определяемой как очередь с приоритетной сортировкой.
+
+На практике **куча часто используется для реализации приоритетной очереди, где максимальная куча соответствует приоритетной очереди, из которой элементы извлекаются в порядке убывания**. С точки зрения использования приоритетную очередь и кучу можно считать эквивалентными структурами данных. Поэтому в данной книге они не различаются и называются просто кучей.
+
+Основные операции с кучей представлены в таблице ниже, названия методов в разных языках программирования могут отличаться.
+
+ Таблица Эффективность операций с кучей
+
+| Метод | Описание | Временная сложность |
+| ----------- | ---------------------------------------------------------- | ------------------- |
+| `push()` | Вставка элемента в кучу | $O(\log n)$ |
+| `pop()` | Извлечение элемента с вершины кучи | $O(\log n)$ |
+| `peek()` | Доступ к элементу на вершине кучи (макс./мин. значение) | $O(1)$ |
+| `size()` | Получение количества элементов в куче | $O(1)$ |
+| `isEmpty()` | Проверка кучи на пустоту | $O(1)$ |
+
+В реальных приложениях можно напрямую использовать классы кучи (или приоритетной очереди), предоставляемые языком программирования.
+
+Подобно сортировочным алгоритмам по возрастанию и по убыванию, можно установить флаг или изменить компаратор для преобразования минимальной кучи в максимальную и наоборот. Ниже приведен пример кода.
+
+=== "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)
+ ```
+
+
+
+
+## Реализация кучи
+
+Ниже приведена реализация максимальной кучи. Для преобразования в минимальную кучу достаточно инвертировать все логические сравнения (например, заменить ≥ на ≤). Заинтересованные читатели могут реализовать это самостоятельно.
+
+### Хранение и представление кучи
+
+В разделе «Двоичные деревья» упоминалось, что полные двоичные деревья удобно представлять в виде массива. **Поскольку куча является таким деревом, для ее хранения будем использовать массив**.
+
+При использовании массива для представления двоичного дерева элементы представляют значения узлов, а индексы -- их положение в дереве. **Указатели на узлы реализуются через формулы индексации**.
+
+Как показано на рисунке ниже, для заданного индекса массива $i$ индекс левого дочернего узла равен $2i + 1$, правого -- $2i + 2$, а индекс родительского узла -- $(i - 1) / 2$ (целочисленное деление вниз). Выход за пределы индексации обозначает пустой узел или его отсутствие.
+
+
+
+Формулы индексации можно для удобства использования обернуть в функции.
+
+```src
+[file]{my_heap}-[class]{max_heap}-[func]{parent}
+```
+
+### Доступ к элементу на вершине кучи
+
+Элемент на вершине кучи -- это корневой узел двоичного дерева, т. е. первый элемент списка.
+
+```src
+[file]{my_heap}-[class]{max_heap}-[func]{peek}
+```
+
+### Вставка элемента в кучу
+
+Нам дан элемент `val`, который сначала добавляется в основание кучи. После добавления условия корректности кучи могут быть нарушены, поскольку элемент `val` может быть больше других элементов кучи. **Поэтому необходимо восстановить порядок на пути от вставленного узла до корневого узла**. Эта операция называется упорядочиванием кучи (heapify).
+
+Рассмотрим **выполнение упорядочивания кучи снизу вверх**, начиная с узла, который был добавлен. Как показано на рисунке ниже, необходимо сравнивать значения вставленного узла и его родительского узла. Если вставленный узел больше, они меняются местами. Затем продолжается выполнение этой операции с исправлением каждого узла кучи снизу вверх, пока не будет достигнут корневой узел или не встретится узел, который не требует обмена.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+Пусть общее количество узлов равно $n$, тогда высота дерева будет $O(\log n)$. Из этого следует, что максимальное количество циклов операции упорядочивания кучи также будет $O(\log n)$. **Тогда и временная сложность операции добавления элемента в кучу составит $O(\log n)$**. Ниже приведен код реализации.
+
+```src
+[file]{my_heap}-[class]{max_heap}-[func]{sift_up}
+```
+
+### Извлечение элемента с вершины кучи
+
+Элемент на вершине кучи является корневым узлом двоичного дерева, т. е. первым элементом списка. Если просто удалить первый элемент из списка, индексы всех узлов в двоичном дереве изменятся, что затруднит дальнейшее исправление с помощью упорядочивания кучи. Чтобы минимизировать изменения индексов элементов, используется следующий порядок действий:
+
+1. обмен вершины кучи с элементом в основании кучи (обмен корневого узла с самым правым листовым узлом);
+2. после обмена удаляется элемент в основании кучи из списка (обратите внимание, что фактически удаляется исходный элемент на вершине кучи, так как они были поменяны);
+3. **упорядочивание кучи сверху вниз**, начиная с корневого узла.
+
+**Направление операции упорядочивания кучи сверху вниз противоположно операции упорядочивания кучи снизу вверх**, как показано на рисунке ниже. Значение корневого узла сравнивается со значениями его двух дочерних узлов, и самый большой дочерний узел обменивается с корневым узлом. Затем эта операция выполняется циклически, пока не будет достигнут листовой узел или не встретится узел, который не требует обмена.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+Подобно операции добавления элемента в кучу, временная сложность операции извлечения элемента с вершины кучи также составляет $O(\log n)$. Ниже приведен код реализации.
+
+```src
+[file]{my_heap}-[class]{max_heap}-[func]{sift_down}
+```
+
+## Типичные применения кучи
+
+- **Приоритетная очередь**: куча обычно является предпочтительной структурой данных для реализации приоритетной очереди, операции вставки и извлечения имеют временную сложность $O(\log n)$, а операция построения кучи -- $O(n)$, все эти операции очень эффективны.
+- **Пирамидальная сортировка**: для заданного набора данных можно построить кучу, а затем последовательно извлекать элементы, получая отсортированные данные. Однако обычно используется более элегантный способ реализации пирамидальной сортировки, подробности см. в разделе «Пирамидальная сортировка».
+- **Получение наибольших $k$ элементов**: это классическая алгоритмическая задача и типичное применение, например, выбор 10 самых популярных новостей для горячих тем в Weibo, выбор 10 самых продаваемых товаров и т. д.
\ No newline at end of file
diff --git a/ru/docs/chapter_heap/index.md b/ru/docs/chapter_heap/index.md
new file mode 100644
index 000000000..666e9bd40
--- /dev/null
+++ b/ru/docs/chapter_heap/index.md
@@ -0,0 +1,3 @@
+# Куча
+
+{width="3.8072911198600177in" height="4.927083333333333in"}
\ No newline at end of file
diff --git a/ru/docs/chapter_heap/summary.md b/ru/docs/chapter_heap/summary.md
new file mode 100644
index 000000000..1a6aa8198
--- /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)$.
+
+### Вопросы и ответы
+
+**В:** Являются ли «куча» в структурах данных и «куча» в управлении памятью одним и тем же понятием?
+
+Это не одно и то же понятие, просто они случайно называются одинаково -- «куча». Куча в памяти компьютерной системы является частью динамического распределения памяти, которую программа может использовать для хранения данных во время выполнения. Программа может запросить определенный объем памяти кучи для хранения сложных структур, таких как объекты и массивы. Когда эти данные больше не нужны, программа должна освободить эту память, чтобы предотвратить утечки памяти. По сравнению с памятью стека, управление и использование памяти кучи требует большей осторожности, неправильное использование может привести к утечкам памяти и висячим указателям.
\ No newline at end of file
diff --git a/ru/docs/chapter_heap/top_k.md b/ru/docs/chapter_heap/top_k.md
new file mode 100644
index 000000000..97678db0b
--- /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)$, что очень затратно по времени.
+
+
+
+!!! tip
+
+ При $k = n$ мы получаем полную упорядоченную последовательность, что эквивалентно алгоритму «сортировка выбором».
+
+## Метод 2: сортировка
+
+Как показано на рисунке ниже, можно сначала отсортировать массив `nums`, а затем вернуть $k$ крайних правых элементов. Временная сложность составляет $O(n \log n)$.
+
+Очевидно, что этот метод выполняет задачу «с избытком», поскольку нам нужно найти только $k$ наибольших элементов, а не сортировать остальные элементы.
+
+
+
+## Метод 3: куча
+
+Задачу Top-k можно решить более эффективно с использованием кучи. Процесс показан на рисунке ниже.
+
+1. Инициализируется минимальная куча, элемент на вершине которой является наименьшим.
+2. Сначала первые $k$ элементов массива последовательно добавляются в кучу.
+3. Начиная с элемента $k + 1$, если текущий элемент больше элемента на вершине кучи, элемент с вершины извлекается, а текущий элемент добавляется в кучу.
+4. После завершения обхода в куче сохраняются $k$ наибольших элементов.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+Пример кода приведен ниже:
+
+```src
+[file]{top_k}-[class]{}-[func]{top_k_heap}
+```
+
+Всего выполняется $n$ раундов добавления и извлечения элементов, максимальная длина кучи равна $k$, поэтому временная сложность составляет $O(n \log k)$. Этот метод очень эффективен: при малых $k$ временная сложность стремится к $O(n)$; при больших $k$ временная сложность не превышает $O(n \log n)$.
+
+Кроме того, этот метод подходит для сценариев использования с динамическими потоками данных. При непрерывном добавлении данных можно постоянно поддерживать элементы в куче, обеспечивая динамическое обновление $k$ наибольших элементов.
\ No newline at end of file
diff --git a/ru/docs/chapter_hello_algo/index.md b/ru/docs/chapter_hello_algo/index.md
new file mode 100644
index 000000000..a35a72d85
--- /dev/null
+++ b/ru/docs/chapter_hello_algo/index.md
@@ -0,0 +1,30 @@
+---
+comments: true
+icon: material/rocket-launch-outline
+---
+
+# Введение
+
+Несколько лет назад я поделился на LeetCode серией решений задач "剑指 Offer", что получило много поддержки и одобрения от читателей. В процессе общения с читателями самым частым вопросом был "как начать изучать алгоритмы". Постепенно я заинтересовался этим вопросом.
+
+Слепое решение задач кажется самым популярным методом — простым, прямым и эффективным. Однако решение задач похоже на игру в "сапёра": люди с сильными способностями к самообучению могут успешно обезвредить все мины одну за другой, в то время как те, у кого недостаточно базовых знаний, могут получить множество ударов и отступить под натиском неудач. Чтение учебников также является распространённой практикой, но для тех, кто ищет работу, дипломная работа, рассылка резюме, подготовка к письменным и устным экзаменам уже отнимают большую часть энергии, и изучение толстых книг часто становится сложной задачей.
+
+Если вы также сталкиваетесь с подобными трудностями, то вам повезло, что эта книга "нашла" вас. Эта книга — мой ответ на этот вопрос, и даже если это не оптимальное решение, то, по крайней мере, это активная попытка. Хотя эта книга не поможет вам сразу получить оффер, она поможет вам исследовать "карту знаний" структур данных и алгоритмов, познакомит вас с формой, размером и расположением различных "мин", научит вас различным "методам разминирования". Обладая этими навыками, я верю, что вы сможете более уверенно решать задачи и читать литературу, постепенно выстраивая полную систему знаний.
+
+Я полностью согласен со словами профессора Фейнмана: "Knowledge isn't free. You have to pay attention." В этом смысле эта книга не совсем "бесплатна". Чтобы не подвести ваше драгоценное "внимание", уделённое этой книге, я приложу все усилия и максимум "внимания" для создания этой книги.
+
+Я осознаю свои ограниченные знания и опыт, и хотя содержание книги прошло определённую шлифовку, в нём наверняка всё ещё есть много ошибок. Прошу всех преподавателей и студентов критиковать и исправлять.
+
+{ class="cover-image" }
+
+
+
Hello, Алгоритмы!
+
+
+Появление компьютеров принесло огромные изменения в мир. Благодаря высокой скорости вычислений и отличной программируемости они стали идеальным средством для выполнения алгоритмов и обработки данных. Будь то реалистичная графика в видеоиграх, интеллектуальные решения в автономном вождении, блестящие партии AlphaGo или естественное взаимодействие ChatGPT — все эти приложения являются изящным воплощением алгоритмов на компьютерах.
+
+На самом деле, ещё до появления компьютеров алгоритмы и структуры данных уже существовали в различных уголках мира. Ранние алгоритмы были относительно простыми, например, древние методы счёта и этапы изготовления инструментов. С развитием цивилизации алгоритмы постепенно становились более точными и сложными. От искусного мастерства ремесленников до промышленных продуктов, освобождающих производительные силы, и до научных законов функционирования вселенной — почти за каждой обыденной или поразительной вещью скрываются изящные алгоритмические идеи.
+
+Точно так же структуры данных повсюду: от больших социальных сетей до малых линий метро — многие системы можно смоделировать как "граф"; от большой страны до маленькой семьи — основные организационные формы общества демонстрируют характеристики "дерева"; зимняя одежда похожа на "стек" — то, что надели первым, снимаем последним; тубус для бадминтона подобен "очереди" — с одного конца кладём, с другого достаём; словарь похож на "хеш-таблицу", позволяющую быстро найти нужное слово.
+
+Эта книга стремится через ясные и понятные анимированные иллюстрации и исполняемые примеры кода помочь читателям понять основные концепции алгоритмов и структур данных и научиться реализовывать их через программирование. На этой основе книга стремится раскрыть яркое проявление алгоритмов в сложном мире, показать красоту алгоритмов. Надеюсь, эта книга сможет вам помочь!
\ No newline at end of file
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..f6c377631
--- /dev/null
+++ b/ru/docs/chapter_introduction/algorithms_are_everywhere.md
@@ -0,0 +1,57 @@
+# Алгоритмы повсюду
+
+Говоря об алгоритмах, естественно вспомнить о математике. Однако на самом деле многие алгоритмы не связаны со сложной математикой, а больше полагаются на базовую логику, которая повсеместно встречается в нашей повседневной жизни.
+
+Прежде чем углубиться в обсуждение алгоритмов, стоит упомянуть интересный факт: **вы уже точно освоили множество алгоритмов и привыкли применять их в повседневной жизни**. Далее приведем несколько конкретных примеров, чтобы подтвердить этот факт.
+
+**Пример 1**: **поиск в словаре**. В словаре все слова упорядочены по алфавиту. Предположим, нам нужно найти слово, начинающееся на букву «r». Обычно для этого нужно выполнить следующие действия (см. рис. 1.1):
+
+1. открыть словарь примерно на половине страниц и посмотреть, какая буква является первой на этой странице, -- предположим, это буква «m»;
+2. поскольку в алфавите буква «r» идет после «m», исключаем первую половину словаря, и область поиска сужается до второй половины;
+3. продолжаем повторять шаги 1 и 2, пока не найдем страницу, где первой буквой слов будет «r».
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+Навык поиска в словаре, которым владеет каждый школьник, на самом деле является известным алгоритмом двоичного поиска. С точки зрения структуры данных словарь можно рассматривать как отсортированный массив. С точки зрения алгоритма последовательность операций по поиску в словаре можно считать двоичным поиском.
+
+**Пример 2**: **упорядочивание карт**. Во время игры в карты необходимо каждый раз упорядочивать карты в руке от меньшего к большему. Для этого нужно выполнить следующие действия (см. рис. 1.2):
+
+1. разделить карты на упорядоченную и неупорядоченную части, предполагая, что изначально самая левая карта уже упорядочена;
+2. из неупорядоченной части извлечь одну карту и вставить ее в правильное место в упорядоченной части. После этого две самые левые карты станут упорядоченными;
+3. повторять шаг 2, каждый раз перемещая одну карту из неупорядоченной части в упорядоченную, пока все карты не станут упорядоченными.
+
+
+
+Метод упорядочивания карт по своей сути является алгоритмом сортировки вставками, который весьма эффективен при обработке небольших наборов данных. Многие функции сортировки в библиотеках программирования используют именно этот алгоритм.
+
+**Пример 3**: **сдача**. Предположим, что в супермаркете мы купили товар стоимостью 69 руб. и дали кассиру купюру в 100 руб. Кассир должен вернуть нам 31 руб. Для этого ему нужно выполнить действия, показанные на рис. 1.3.
+
+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 руб.
+
+
+
+В этих шагах мы на каждом этапе выбираем наилучший вариант (используя купюры наибольшего номинала), в итоге получая оптимальную схему сдачи. С точки зрения структуры данных и алгоритмов этот метод по своей сути является жадным алгоритмом.
+
+От приготовления блюда до межзвездных путешествий − решение практически любой задачи неразрывно связано с алгоритмами. Появление компьютеров позволило нам с помощью программирования хранить структуры данных в памяти, а также писать код для вызовов к центральному и графическому процессору для выполнения алгоритмов. Таким образом, мы можем переносить задачи из реальной жизни в компьютер, решая различные сложные проблемы более эффективно.
+
+!!! tip
+
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_introduction/index.md b/ru/docs/chapter_introduction/index.md
new file mode 100644
index 000000000..3d48198ad
--- /dev/null
+++ b/ru/docs/chapter_introduction/index.md
@@ -0,0 +1,9 @@
+# Введение в алгоритмы
+
+
+
+!!! abstract
+
+ Девушка танцует, переплетаясь с данными, на подоле её платья развеваются мелодии алгоритмов.
+
+ Она приглашает вас на танец, следуйте за её шагами и войдите в мир алгоритмов, полный логики и красоты.
\ No newline at end of file
diff --git a/ru/docs/chapter_introduction/summary.md b/ru/docs/chapter_introduction/summary.md
new file mode 100644
index 000000000..519063e5e
--- /dev/null
+++ b/ru/docs/chapter_introduction/summary.md
@@ -0,0 +1,30 @@
+# Резюме
+
+### Основные выводы
+
+- Алгоритмы повсеместно присутствуют в нашей повседневной жизни и не являются недосягаемыми сложными знаниями. На самом деле мы уже освоили множество алгоритмов, которые помогают решать различные жизненные задачи.
+
+- Принцип поиска в словаре соответствует алгоритму двоичного поиска. Бинарный поиск иллюстрирует важную идею алгоритмов «разделяй и властвуй».
+
+- Процесс сортировки карт в колоде очень похож на алгоритм сортировки вставками, который хорошо подходит для сортировки небольших наборов данных.
+
+- Процесс размена валюты по своей сути является жадным алгоритмом, в котором на каждом этапе принимается наилучшее на данный момент решение.
+
+- Алгоритм представляет собой набор инструкций или шагов, предназначенных для решения конкретной задачи в ограниченное время, а структура данных -- это способ организации и хранения данных в компьютере.
+
+- Структуры данных и алгоритмы тесно связаны. Структуры данных являются основой для алгоритмов, а алгоритмы оживляют структуры данных.
+
+- Структуры данных и алгоритмы можно сравнить с конструктором: детали конструктора представляют данные, их форма и способы соединения -- структуры данных, а этапы сборки конструктора соответствуют алгоритмам.
+
+### Вопросы и ответы
+
+**Вопрос**. Я программист, и я никогда не использовал алгоритмы для решения задач в своей повседневной работе, поскольку часто используемые алгоритмы уже встроены в языки программирования и их можно использовать напрямую. Означает ли это, что задачи, с которыми мы сталкиваемся на работе, не требуют применения алгоритмов?
+
+Если сравнить конкретные профессиональные навыки с приемами в боевых искусствах, то базовые дисциплины скорее напоминают «внутреннюю силу».
+
+Я считаю, что изучение алгоритмов (и других базовых дисциплин) важно не для того, чтобы реализовывать их с нуля в работе, а для того, чтобы на основе полученных знаний принимать профессиональные решения и оценки при решении задач, тем самым повышая общее качество работы. Простой пример: каждый язык программирования имеет встроенные функции сортировки.
+
+- Если бы мы не изучали структуры данных и алгоритмы, то, получив любые данные, мы, возможно, просто передали бы их этой функции сортировки. Все работает гладко, производительность хорошая, и на первый взгляд проблем нет.
+- Однако если мы изучили алгоритмы, то знаем, что временная сложность встроенной функции сортировки составляет $O(n \log n)$. Если же данные представлены целыми числами фиксированной разрядности (например, номерами студентов), то можно использовать более эффективный метод поразрядной сортировки, снизив временную сложность до $O(nk)$, где $k$ -- это количество разрядов. При больших объемах данных экономия времени выполнения может привести к значительным преимуществам, таким как снижение затрат и улучшение пользовательского опыта.
+
+В инженерной практике множество задач трудно решить оптимальным образом, и многие из них решаются «как-то». Сложность задачи зависит как от ее природы, так и от уровня знаний и опыта человека, который ее анализирует. Чем более полными знаниями и большим опытом обладает человек, тем глубже он может проанализировать проблему и тем изящнее может быть ее решение.
\ No newline at end of file
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..8f17e6ed8
--- /dev/null
+++ b/ru/docs/chapter_introduction/what_is_dsa.md
@@ -0,0 +1,49 @@
+# Что такое алгоритм
+
+## Определение алгоритма
+
+*Алгоритм* -- это набор инструкций или шагов, предназначенных для решения конкретной задачи за ограниченное время, обладающий следующими свойствами:
+
+- задача четко определена, включает ясные определения входных и выходных данных;
+- обладает осуществимостью, может быть выполнен за ограниченное количество шагов, времени и памяти;
+- каждый шаг имеет определенное значение, при одинаковых входных данных и условиях выполнения результат всегда будет одинаковым.
+
+## Определение структуры данных
+
+*Структура данных* -- это способ организации и хранения данных, включающий содержимое данных, их взаимосвязи и методы операций с ними. Структура данных преследует следующие цели:
+
+- минимизацию занимаемого пространства для экономии памяти компьютера;
+- максимально быструю обработку данных, включая доступ, добавление, удаление и обновление данных;
+- обеспечение простого представления данных и логической информации для эффективного выполнения алгоритмов.
+
+**Проектирование структуры данных** -- **это процесс, полный компромиссов**. Если вы хотите улучшить один аспект, часто приходится идти на уступки в другом. Приведем два примера.
+
+- Связный список, по сравнению с массивом, более удобен для добавления и удаления данных, но имеет проблемы со скоростью доступа к данным.
+- Граф, по сравнению со связным списком, предоставляет более богатую логическую информацию, но требует большего объема памяти.
+
+## Взаимосвязь структур данных и алгоритмов
+
+Структуры данных и алгоритмы тесно взаимосвязаны, что проявляется в следующих трех аспектах (см. рис. 1.4):
+
+- структуры данных являются основой алгоритмов. Они обеспечивают структурированное хранение данных и методы их обработки;
+- алгоритмы оживляют структуры данных. Сами по себе структуры данных лишь хранят информацию, но в сочетании с алгоритмами они позволяют решать конкретные задачи;
+- алгоритмы можно реализовать на основе различных структур данных, однако эффективность их выполнения может значительно различаться. Поэтому выбор подходящей структуры данных является ключевым фактором.
+
+
+
+Структуры данных и алгоритмы подобны конструктору, как показано на рис. 1.5. Комплект конструктора, помимо множества деталей, содержит также подробную инструкцию по сборке. Следуя этой инструкции шаг за шагом, можно собрать красивую модель.
+
+
+
+Подробное описание аналогии с конструктором представлено в табл. 1.1.
+
+**Таблица 1.1.** Сравнение структур данных и алгоритмов с конструктором
+
+| Структуры данных и алгоритмы | Конструктор |
+| -------------- | ---------------------------------------- |
+| Входные данные | Несобранные детали конструктора |
+| Структура данных | Организация деталей конструктора, включая форму, размер, способы соединения и т. д. |
+| Алгоритм | Последовательность действий по сборке деталей в целевую модель |
+| Выходные данные | Собранная модель конструктора |
+
+Стоит отметить, что структуры данных и алгоритмы не зависят от языка программирования. Именно поэтому данная книга предлагает их реализации на различных языках.
\ No newline at end of file
diff --git a/ru/docs/chapter_paperbook/index.md b/ru/docs/chapter_paperbook/index.md
new file mode 100644
index 000000000..8f1cfb708
--- /dev/null
+++ b/ru/docs/chapter_paperbook/index.md
@@ -0,0 +1,99 @@
+```markdown
+---
+comments: true
+icon: material/book-open-page-variant
+status: new
+---
+
+# Бумажная книга
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Преимущества и недостатки
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Ссылка для покупки
+
+
+
+
+
+
+
+## Послесловие
+
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
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..f73bbcb01
--- /dev/null
+++ b/ru/docs/chapter_preface/about_the_book.md
@@ -0,0 +1,35 @@
+# О книге
+
+Этот проект задуман как открытое, бесплатное и дружелюбное к новичкам введение в структуры данных и алгоритмы.
+
+- В книге используются анимационные иллюстрации. Материал изложен ясно и последовательно, что облегчает освоение и помогает начинающим построить «карту знаний» по структурам данных и алгоритмам.
+- Исходный код можно запустить одним кликом, что позволяет тренироваться, развивать навыки программирования и формировать понимание принципов работы алгоритмов и реализации структур данных на фундаментальном уровне.
+- Мы призываем к взаимопомощи читателей: задавайте вопросы и делитесь идеями в комментариях. Обсуждения помогают двигаться вперед всем вместе.
+
+## Целевая аудитория
+
+Если вы новичок в алгоритмах, никогда с ними не сталкивались или у вас есть некоторый опыт решения задач, но еще нет четкого понимания структур данных и алгоритмов, эта книга создана специально для вас!
+
+Если у вас уже есть определенный опыт решения задач и вы знакомы с большинством типов задач, эта книга поможет вам освежить и систематизировать знания об алгоритмах. Исходный код может служить набором инструментов для решения задач или алгоритмическим словарем.
+
+Если вы владеете алгоритмами на экспертном уровне, мы будем рады вашим ценным советам или совместному участию в создании книги.
+
+!!! success "Предварительные условия"
+
+ Вам необходимо владеть основами программирования хотя бы на одном языке, уметь читать и писать простой код.
+
+## Структура книги
+
+Основное содержание книги представлено на рисунке ниже.
+
+- **Анализ сложности**: критерии и методы оценки структур данных и алгоритмов. Методы расчета временной и пространственной сложности, распространенные типы, примеры и т. д.
+- **Структуры данных**: классификация основных типов данных и структур данных. Определение, преимущества и недостатки, основные операции, распространенные типы, типичные приложения и методы реализации массивов, списков, стеков, очередей, хеш-таблиц, деревьев, куч, графов и т. д.
+- **Алгоритмы**: определение, преимущества и недостатки, эффективность, области применения, этапы решения и примеры задач для поиска, сортировки, алгоритма «разделяй и властвуй», обратного поиска, динамического программирования, жадных алгоритмов и т. д.
+
+
+
+## Благодарности
+
+Эта книга постоянно совершенствуется благодаря совместным усилиям множества участников открытого сообщества. Благодарим каждого автора, вложившего свое время и силы. Имена перечислены в порядке, автоматически сгенерированном GitHub: krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, msk397, gvenusleo, khoaxuantu, RiverTwilight, rongyi, gyt95, zhuoqinyue, K3v123, Zuoxun, mingXta, hello-ikun, FangYuan33, GN-Yu, yuelinxin, longsizhuo, Cathay-Chen, guowei-gong, xBLACKICEx, IsChristina, JoseHung, qualifier1024, QiLOL, pengchzn, Guanngxu, L-Super, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, theNefelibatas, longranger2, cy-by-side, xiongsp, JeffersonHuang, Transmigration-zhou, magentaqin, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, Shyam-Chen, nanlei, hongyun-robot, Phoenix0415, MolDuM, Nigh, he-weilai, junminhong, mgisr, iron-irax, yd-j, XiaChuerwu, XC-Zero, seven1240, SamJin98, wodray, reeswell, NI-SW, Horbin-Magician, Enlightenus, xjr7670, YangXuanyi, DullSword, boloboloda, iStig, qq909244296, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, liuxjerry, lucaswangdev, lyl625760, hts0000, gledfish, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, luluxia, xb534, bitsmi, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, steventimes, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, ZhongGuanbin, shanghai-Jerry, JackYanghellobobo, Javesun99, lipusheng, BlindTerran, ShiMaRing, FreddieLi, FloranceYeh, iFleey, fanchenggang, gltianwen, goerll, Dr-XYZ, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, fanenr, eagleanurag, LifeGoesOnionOnionOnion, 52coder, foursevenlove, KorsChen, hezhizhen, linzeyan, ZJKung, GaochaoZhu, hopkings2008, yang-le, Evilrabbit520, Turing-1024-Lee, thomasq0, Suremotoo, Allen-Scai, Risuntsy, Richard-Zhang1019, qingpeng9802, primexiao, nidhoggfgg, 1ch0, MwumLi, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai и 0130w.
+
+Рецензирование кода книги выполнили coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon и rongyi (в алфавитном порядке). Благодарим их за потраченное время и усилия, которые
\ No newline at end of file
diff --git a/ru/docs/chapter_preface/index.md b/ru/docs/chapter_preface/index.md
new file mode 100644
index 000000000..0c9ead9a7
--- /dev/null
+++ b/ru/docs/chapter_preface/index.md
@@ -0,0 +1,13 @@
+# Введение
+
+{width="4.60417760279965in" height="5.958333333333333in"}
+
+1. **О книге**
+
+> Этот проект задуман как открытое, бесплатное и дружелюбное к новичкам введение в структуры данных и алгоритмы.
+
+1. В книге используются анимационные иллюстрации. Материал изложен ясно и последовательно, что облегчает освоение и помогает начинаю- щим построить «карту знаний» по структурам данных и алгоритмам.
+
+2. Исходный код можно запустить одним кликом, что позволяет трениро- ваться, развивать навыки программирования и формировать понима- ние принципов работы алгоритмов и реализации структур данных на фундаментальном уровне.
+
+3. Мы призываем к взаимопомощи читателей: задавайте вопросы и дели- тесь идеями в комментариях. Обсуждения помогают двигаться вперед всем вместе.
\ No newline at end of file
diff --git a/ru/docs/chapter_preface/suggestions.md b/ru/docs/chapter_preface/suggestions.md
new file mode 100644
index 000000000..e74771d2b
--- /dev/null
+++ b/ru/docs/chapter_preface/suggestions.md
@@ -0,0 +1,267 @@
+# Как использовать эту книгу
+
+!!! 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=""
+ ### Заголовочные комментарии, используются для обозначения функций, классов, тестовых примеров и т.д. ###
+
+ # Содержательные комментарии, используются для пояснения кода.
+
+ # Многострочные
+ # комментарии.
+ ```
+
+## Эффективное обучение с помощью анимированных иллюстраций
+
+Видео и изображения обладают более высокой плотностью информации и структурированностью по сравнению с текстом, что облегчает понимание. В этой книге **ключевые и сложные моменты в основном представлены в виде анимированных иллюстраций**, а текстовая информация служит пояснением и дополнением.
+
+Если какой-либо раздел в книге сопровождается анимационной иллюстрацией, как показано ниже, **используйте иллюстрацию в качестве основного источника информации, а текст -- в качестве вспомогательного**.
+
+
+
+## Углубление понимания через практику с кодом
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Совместный рост через вопросы и обсуждения
+
+
+
+
+
+
+
+
+
+
+## Путь изучения алгоритмов
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_preface/summary.md b/ru/docs/chapter_preface/summary.md
new file mode 100644
index 000000000..2a862c003
--- /dev/null
+++ b/ru/docs/chapter_preface/summary.md
@@ -0,0 +1,10 @@
+# Резюме
+
+### Основные моменты
+
+- Основная аудитория этой книги -- начинающие изучать алгоритмы. Если у вас уже есть определенный опыт, эта книга поможет вам систематизировать знания об алгоритмах. Исходный код может служить набором инструментов для решения задач.
+- Содержание книги включает три основные части: анализ сложности, структуры данных и алгоритмы, охватывая большинство тем в этой области.
+- Для новичков в алгоритмах чтение вводной книги на начальном этапе обучения имеет решающее значение и может помочь избежать многих ошибок.
+- Анимационные иллюстрации в книге обычно используются для представления ключевых и сложных моментов. При чтении книги следует уделять этим материалам больше внимания.
+- Практика -- лучший способ изучения программирования. Настоятельно рекомендуется запускать исходный код и писать код самостоятельно.
+- В веб-версии книги под каждой главой есть раздел комментариев, где вы можете в любое время делиться своими вопросами и идеями.
\ No newline at end of file
diff --git a/ru/docs/chapter_reference/index.md b/ru/docs/chapter_reference/index.md
new file mode 100644
index 000000000..1030d4456
--- /dev/null
+++ b/ru/docs/chapter_reference/index.md
@@ -0,0 +1,27 @@
+```markdown
+---
+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] 严蔚敏. 数据结构(C 语言版).
+
+[5] 邓俊辉. 数据结构(C++ 语言版,第三版).
+
+[6] 马克 艾伦 维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版).
+
+[7] 程杰. 大话数据结构.
+
+[8] 王争. 数据结构与算法之美.
+
+[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).
+
+[10] Aston Zhang, et al. Dive into Deep Learning.
+```
\ No newline at end of file
diff --git a/ru/docs/chapter_searching/binary_search.md b/ru/docs/chapter_searching/binary_search.md
new file mode 100644
index 000000000..2ae8093ed
--- /dev/null
+++ b/ru/docs/chapter_searching/binary_search.md
@@ -0,0 +1,83 @@
+# Двоичный поиск
+
+Двоичный (бинарный) поиск -- это эффективный алгоритм поиска, основанный на стратегии «разделяй и властвуй». Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или область поиска не станет пустой.
+
+!!! question
+
+ Дан массив `nums` длиной $n$, элементы которого расположены в порядке возрастания и не повторяются. Необходимо найти и вернуть индекс элемента `target` в этом массиве. Если массив не содержит этот элемент, вернуть $-1$. Пример показан на рисунке ниже.
+
+
+
+Как показано на рисунке ниже, сначала инициализируются указатели $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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+Следует отметить, что, поскольку $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$ также симметричны. Это снижает вероятность ошибок, поэтому обычно **рекомендуется использовать запись «двойной замкнутый интервал»**.
+
+
+
+## Преимущества и ограничения
+
+Двоичный поиск обладает хорошей производительностью как по времени, так и по пространству.
+
+- Двоичный поиск отличается высокой эффективностью по времени. При большом объеме данных логарифмическая временная сложность имеет значительное преимущество. Например, при размере данных $n = 2^{20}$ линейный поиск требует $2^{20} = 1048576$ итераций, тогда как двоичный поиск -- всего $\log_2 2^{20} = 20$ итераций.
+- Двоичный поиск, в отличие от некоторых других алгоритмов поиска (например, хеш-поиска), не требует дополнительного пространства и поэтому более экономичен в плане использования памяти.
+
+Тем не менее двоичный поиск не подходит для всех случаев по следующим основным причинам.
+
+- Двоичный поиск применим только к упорядоченным данным. Если входные данные неупорядоченные, то их сортировка специально для использования двоичного поиска не оправдана. Это связано с тем, что временная сложность алгоритмов сортировки обычно составляет $O(n \log n)$, что выше, чем у линейного и двоичного поиска. В сценариях с частыми добавлениями элементов для поддержания упорядоченности массива необходимо вставлять элементы в определенные позиции, что имеет временную сложность $O(n)$ и также является весьма затратным.
+- Двоичный поиск применим только к массивам, поскольку требует скачкообразного (непрерывного) доступа к элементам. В связных списках выполнение скачкообразного доступа менее эффективно, поэтому такой поиск не подходит для применения в связных списках и структурах данных, основанных на них.
+- При небольших объемах данных линейный поиск более эффективен. В линейном поиске на каждом этапе требуется только одна операция сравнения; в двоичном поиске требуется одна операция сложения, одна операция деления, от одной до трех операций сравнения и одна операция сложения (вычитания), всего от четырех до шести элементарных операций. Поэтому, когда объем данных $n$ невелик, линейный поиск оказывается быстрее двоичного.
\ No newline at end of file
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..16616fdc0
--- /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$.
+
+
+
+Обратите внимание, что возвращаемая точка вставки -- это $i$, поэтому необходимо вычесть из нее $1$, чтобы получить $j$:
+
+```src
+[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge}
+```
+
+### Преобразование в поиск элемента
+
+Мы знаем, что когда массив не содержит `target`, в конечном итоге $i$ и $j$ будут указывать соответственно на первый элемент, больший и меньший `target`.
+
+Поэтому, как показано на рисунке ниже, мы можем создать элемент, которого не существует в массиве, для поиска левой и правой границ.
+
+- Поиск самого левого `target`: можно преобразовать в поиск `target - 0.5` и вернуть указатель $i$.
+- Поиск самого правого `target`: можно преобразовать в поиск `target + 0.5` и вернуть указатель $j$.
+
+
+
+Код здесь опущен, следует отметить следующие два момента:
+
+- Данный массив не содержит дробных чисел, что означает, что нам не нужно беспокоиться о том, как обрабатывать случай равенства.
+- Поскольку этот метод вводит дробные числа, необходимо изменить тип переменной `target` в функции на тип с плавающей точкой (в Python изменения не требуются).
\ No newline at end of file
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..b7ab57a0d
--- /dev/null
+++ b/ru/docs/chapter_searching/binary_search_insertion.md
@@ -0,0 +1,91 @@
+# Вставка с использованием двоичного поиска
+
+Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения множества других задач, таких как поиск позиции для вставки целевого элемента.
+
+## Случай без повторяющихся элементов
+
+!!! question
+
+ Дан упорядоченный массив `nums` длиной $n$ и элемент `target`, массив не содержит повторяющихся элементов. Необходимо вставить `target` в массив `nums`, сохранив его упорядоченность. Если массив уже содержит элемент `target`, вставить его слева от существующего. Вернуть индекс `target` в массиве после вставки. Пример показан на рисунке ниже.
+
+
+
+Если требуется повторно использовать код двоичного поиска из предыдущего раздела, необходимо ответить на следующие два вопроса:
+
+**Вопрос первый**: если массив содержит `target`, является ли индекс вставки индексом этого элемента?
+
+Условие задачи требует вставить `target` слева от равного элемента, т. е. новый `target` заменяет старое положение `target`. То есть **если массив уже содержит `target`, индекс вставки совпадает с индексом этого `target`**.
+
+**Вопрос второй**: если массив не содержит `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`.
+
+
+
+Это рабочий метод, но он включает линейный поиск, поэтому его временная сложность составляет $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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+Рассмотрим следующий код: ветви условий `nums[m] > target` и `nums[m] == target` выполняют одинаковые операции, поэтому их можно объединить.
+
+Тем не менее мы можем оставить условия развернутыми, поскольку логика становится более ясной и читаемой.
+
+```src
+[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion}
+```
+
+!!! tip
+
+ Код в этом разделе использует запись «двойной замкнутый интервал». Заинтересованные читатели могут самостоятельно реализовать вариант «левозамкнутый правооткрытый интервал».
+
+В целом двоичный поиск заключается в установке целей поиска для указателей $i$ и $j$. Целью может быть конкретный элемент (например, `target`) или диапазон элементов (например, элементы, меньшие `target`).
+
+В процессе непрерывного циклического деления указатели $i$ и $j$ постепенно приближаются к заранее установленной цели. В конечном итоге они либо успешно находят ответ, либо останавливаются после пересечения границ.
\ No newline at end of file
diff --git a/ru/docs/chapter_searching/index.md b/ru/docs/chapter_searching/index.md
new file mode 100644
index 000000000..feb1c6b3b
--- /dev/null
+++ b/ru/docs/chapter_searching/index.md
@@ -0,0 +1,9 @@
+# Поиск
+
+
+
+!!! abstract
+
+ Поиск — это путешествие в неизвестность, где нам может потребоваться обойти каждый уголок таинственного пространства, а может быть, мы сможем быстро найти цель.
+
+ В этом путешествии поиска каждое исследование может привести к неожиданному ответу.
\ No newline at end of file
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..96c000b73
--- /dev/null
+++ b/ru/docs/chapter_searching/replace_linear_by_hashing.md
@@ -0,0 +1,47 @@
+# Стратегия оптимизации с использованием хеширования
+
+В задачах по алгоритмам **мы часто снижаем временную сложность алгоритма, заменяя линейный поиск на хеш-поиск**. Давайте углубим понимание на примере алгоритмической задачи.
+
+!!! question
+
+ Дан массив целых чисел `nums` и целевой элемент `target`. Необходимо найти в массиве два элемента, "сумма" которых равна `target`, и вернуть их индексы в массиве. Можно вернуть любое решение.
+
+## Линейный поиск: обмен времени на пространство
+
+Рассмотрим прямой перебор всех возможных комбинаций. Как показано на рисунке ниже, мы запускаем двойной цикл, на каждой итерации проверяя, равна ли сумма двух целых чисел `target`, и если да, возвращаем их индексы.
+
+
+
+Код выглядит следующим образом:
+
+```src
+[file]{two_sum}-[class]{}-[func]{two_sum_brute_force}
+```
+
+Временная сложность этого метода составляет $O(n^2)$, пространственная сложность -- $O(1)$. При больших объемах данных это очень затратно по времени.
+
+## Хеш-поиск: обмен пространства на время
+
+Рассмотрим использование хеш-таблицы, где ключами и значениями будут элементы массива и их индексы соответственно. Проходим по массиву в цикле, на каждой итерации выполняя шаги, показанные на рисунке ниже.
+
+1. Проверяем, находится ли число `target - nums[i]` в хеш-таблице. Если да, сразу возвращаем индексы этих двух элементов.
+2. Добавляем в хеш-таблицу пару ключ-значение: `nums[i]` и индекс `i`.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+Реализация кода приведена ниже, требуется только один цикл:
+
+```src
+[file]{two_sum}-[class]{}-[func]{two_sum_hash_table}
+```
+
+Этот метод с помощью хеш-поиска снижает временную сложность с $O(n^2)$ до $O(n)$, значительно повышая эффективность выполнения.
+
+Поскольку необходимо поддерживать дополнительную хеш-таблицу, пространственная сложность составляет $O(n)$. **Несмотря на это, данный метод обеспечивает более сбалансированную общую эффективность по времени и пространству, поэтому он является оптимальным решением для этой задачи**.
\ No newline at end of file
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..b8faf5589
--- /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$ мы можем использовать линейный поиск, двоичный поиск, поиск в дереве, хеш-поиск и другие методы для поиска целевого элемента. Принцип работы каждого метода показан на рисунке ниже.
+
+
+
+Эффективность операций и характеристики вышеуказанных методов приведены в следующей таблице.
+
+ Таблица Сравнение эффективности алгоритмов поиска
+
+| | Линейный поиск | Двоичный поиск | Поиск в дереве | Хеш-поиск |
+| ------------------------ | -------------- | ------------------ | ------------------ | --------------- |
+| Поиск элемента | $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)$, но операции по поддержанию баланса дерева добавляют дополнительные затраты.
\ No newline at end of file
diff --git a/ru/docs/chapter_searching/summary.md b/ru/docs/chapter_searching/summary.md
new file mode 100644
index 000000000..5ae33ac5f
--- /dev/null
+++ b/ru/docs/chapter_searching/summary.md
@@ -0,0 +1,10 @@
+# Резюме
+
+### Основные моменты
+
+- Двоичный поиск основан на упорядоченности данных и постепенно сокращает область поиска вдвое с помощью цикла. Он требует упорядоченных входных данных и применим только к массивам или структурам данных, реализованным на основе массивов.
+- Поиск методом перебора осуществляется путем обхода структуры данных для определения местоположения данных. Линейный поиск применим к массивам и связным спискам, поиск в ширину и поиск в глубину применимы к графам и деревьям. Эти алгоритмы обладают хорошей универсальностью, не требуют предварительной обработки данных, но имеют высокую временную сложность $O(n)$.
+- Хеш-поиск, поиск по дереву и двоичный поиск относятся к эффективным методам поиска, которые могут быстро находить целевые элементы в определенных структурах данных. Эти алгоритмы высокоэффективны, временная сложность может достигать $O(\log n)$ и даже $O(1)$, но обычно требуют дополнительных структур данных.
+- На практике необходимо проводить конкретный анализ таких факторов, как объем данных, требования к производительности поиска, частота запросов и обновлений данных, чтобы выбрать подходящий метод поиска.
+- Линейный поиск подходит для небольших или часто обновляемых данных; двоичный поиск подходит для больших упорядоченных данных; хеш-поиск подходит для данных с высокими требованиями к эффективности запросов и без необходимости диапазонных запросов; поиск по дереву подходит для больших динамических данных, требующих поддержания порядка и диапазонных запросов.
+- Замена линейного поиска хеш-поиском является распространенной стратегией оптимизации времени выполнения, которая может снизить временную сложность с $O(n)$ до $O(1)$.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/bubble_sort.md b/ru/docs/chapter_sorting/bubble_sort.md
new file mode 100644
index 000000000..4b1970d38
--- /dev/null
+++ b/ru/docs/chapter_sorting/bubble_sort.md
@@ -0,0 +1,89 @@
+# Сортировка пузырьком
+
+> *Сортировка пузырьком* реализует сортировку путем последовательного срав- нения и обмена соседних элементов. Этот процесс напоминает подъем пу- зырьков со дна на поверхность, отсюда и такое название.
+>
+> Процесс поднятия пузырька можно смоделировать операцией обмена элементов: начиная с самого левого конца массива, производится последо- вательное сравнение соседних элементов, и, если левый элемент \> правый элемент, они меняются местами, как показано на рис. 11.4. После заверше- ния прохода наибольший элемент будет перемещен в самый правый конец массива.
+
+
+
+
+
+> **Рис. 11.4.** Моделирование поднятия пузырька с помощью обмена элементов. Шаги 1--4
+>
+> 
+
+
+
+> **Рис. 11.4.** *Окончание*. Шаги 5--7
+
+## Алгоритм
+
+> Пусть дан массив длиной *n*, тогда сортировка пузырьком выглядит следующим образом (см. рис. 11.5):
+
+1) сначала выполняется пузырек для *n* элементов, **перемещая наиболь- ший элемент в правильное положение**;
+
+2) затем выполняется пузырек для оставшихся *n* -- 1 элементов, **переме- щая второй по величине элемент в правильное положение**;
+
+3) таким образом, после *n* -- 1 итераций пузырька первые *n* -- 1 **наиболь- ших элементов перемещены в правильные положения**;
+
+4) единственный оставшийся элемент обязательно является наименьшим, поэтому сортировка массива завершена.
+
+> 
+>
+> **Рис. 11.5.** Процесс сортировки пузырьком
+>
+> Ниже приведен пример кода.
+>
+> \# === File: bubble_sort.py === 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\]
+
+## Оптимизация эффективности
+
+> Если в какой-либо итерации пузырька не выполняется ни одной операции об- мена, это означает, что массив уже отсортирован, и можно сразу вернуть ре- зультат. Поэтому можно добавить флаг flag для отслеживания этой ситуации, и как только она возникнет, немедленно выйти из цикла.
+>
+> После оптимизации наихудшая и средняя временные сложности сортиров- ки пузырьком остаются *O*(*n*2); однако, если входной массив полностью отсо- ртирован, можно достичь лучшей временной сложности *O*(*n*).
+>
+> \# === File: bubble_sort.py ===
+>
+> 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 \# В этой итерации \"пузырька\" не было обмена, выход из цикла.
+
+## Характеристики алгоритма
+
+- **Временная сложность** *O*(*n*2), **адаптивная сортировка**: длина масси- ва, проходящего каждую итерацию пузырька, последовательно равна *n* -- 1, *n* -- 2, \..., 2, 1. Сумма этих значений равна (*n* -- 1)*n*/2. После вве- дения оптимизации с флагом лучшая временная сложность может до- стигать *O*(*n*).
+
+- **Пространственная сложность** *O*(1), **сортировка на месте**: указатели *i* и *j* используют дополнительную память постоянного размера.
+
+- **Стабильная сортировка**: поскольку при сортировке пузырьком равные элементы не меняются местами.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/bucket_sort.md b/ru/docs/chapter_sorting/bucket_sort.md
new file mode 100644
index 000000000..82c021e1a
--- /dev/null
+++ b/ru/docs/chapter_sorting/bucket_sort.md
@@ -0,0 +1,47 @@
+```markdown
+# Сортировка корзинами
+
+Несколько предыдущих алгоритмов сортировки относятся к "алгоритмам сортировки на основе сравнения", которые реализуют сортировку путем сравнения размеров элементов. Временная сложность таких алгоритмов сортировки не может превысить $O(n \log n)$. Далее мы рассмотрим несколько "алгоритмов сортировки не на основе сравнения", временная сложность которых может достигать линейного порядка.
+
+Сортировка корзинами (bucket sort) является типичным применением стратегии "разделяй и властвуй". Она работает путем создания нескольких корзин с упорядоченными размерами, каждая корзина соответствует диапазону данных, равномерно распределяя данные по различным корзинам; затем выполняется сортировка внутри каждой корзины; наконец, все данные объединяются в порядке корзин.
+
+## Процесс алгоритма
+
+Рассмотрим массив длиной $n$, элементы которого являются числами с плавающей запятой в диапазоне $[0, 1)$. Процесс сортировки корзинами показан на рисунке ниже.
+
+1. Инициализируется $k$ корзин, $n$ элементов распределяются по $k$ корзинам.
+2. Для каждой корзины выполняется сортировка отдельно (здесь используется встроенная функция сортировки языка программирования).
+3. Результаты объединяются в порядке от меньшей корзины к большей.
+
+
+
+Код приведен ниже:
+
+```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 + k)$, сортировка не на месте**: требуется дополнительное пространство для $k$ корзин и в общей сложности $n$ элементов.
+- Стабильность сортировки корзинами зависит от того, является ли стабильным алгоритм сортировки элементов внутри корзин.
+
+## Как реализовать равномерное распределение
+
+Временная сложность сортировки корзинами теоретически может достигать $O(n)$, **ключевым моментом является равномерное распределение элементов по корзинам**, поскольку реальные данные часто распределены неравномерно. Например, если мы хотим равномерно распределить все товары на Taobao по ценовым диапазонам в 10 корзин, но распределение цен товаров неравномерно: очень много товаров дешевле 100 юаней, очень мало дороже 1000 юаней. Если разделить ценовой диапазон равномерно на 10 частей, разница в количестве товаров в корзинах будет очень большой.
+
+Для реализации равномерного распределения мы можем сначала установить приблизительную границу раздела, грубо распределив данные по 3 корзинам. **После завершения распределения корзины с большим количеством товаров продолжают делиться на 3 корзины, пока количество элементов во всех корзинах не станет примерно равным**.
+
+Как показано на рисунке ниже, этот метод по сути создает рекурсивное дерево, цель которого -- сделать значения листовых узлов максимально равномерными. Конечно, не обязательно каждый раз делить данные на 3 корзины, конкретный способ деления можно гибко выбирать в зависимости от характеристик данных.
+
+
+
+Если мы заранее знаем распределение вероятностей цен товаров, **можно установить ценовые границы каждой корзины на основе распределения вероятностей данных**. Стоит отметить, что распределение данных не обязательно специально статистически определять, можно также использовать определенную вероятностную модель для аппроксимации на основе характеристик данных.
+
+Как показано на рисунке ниже, мы предполагаем, что цены товаров подчиняются нормальному распределению, что позволяет разумно установить ценовые интервалы и таким образом равномерно распределить товары по корзинам.
+
+
+```
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/counting_sort.md b/ru/docs/chapter_sorting/counting_sort.md
new file mode 100644
index 000000000..4f88a142a
--- /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` в порядке возрастания количества вхождений каждого числа.
+
+
+
+Код выглядит следующим образом:
+
+```src
+[file]{counting_sort}-[class]{}-[func]{counting_sort_naive}
+```
+
+!!! note "Связь между сортировкой подсчетом и блочной сортировкой"
+
+ С точки зрения блочной сортировки можно рассматривать каждый индекс массива подсчета `counter` в сортировке подсчетом как блок, а процесс подсчета количества — как распределение каждого элемента в соответствующий блок. По сути, сортировка подсчетом является частным случаем блочной сортировки для целочисленных данных.
+
+## Полная реализация
+
+Внимательный читатель мог заметить, что **если входные данные являются объектами, вышеуказанный шаг `3.` становится неэффективным**. Предположим, что входные данные — это объекты товаров, и мы хотим отсортировать товары по цене (переменной-члену класса), а вышеуказанный алгоритм может дать только результат сортировки цен.
+
+Как же получить результат сортировки исходных данных? Сначала вычислим "префиксную сумму" `counter`. Как следует из названия, префиксная сумма `prefix[i]` по индексу `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` содержит отсортированный результат, и в конце можно заменить исходный массив `nums` массивом `res`. На следующем рисунке показан полный процесс сортировки подсчетом.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+Код реализации сортировки подсчетом показан ниже:
+
+```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)$.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/heap_sort.md b/ru/docs/chapter_sorting/heap_sort.md
new file mode 100644
index 000000000..546c73dbf
--- /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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+=== "<11>"
+ 
+
+=== "<12>"
+ 
+
+В реализации кода мы используем ту же функцию просеивания сверху вниз `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)$ пространства. Обмен элементов и операция просеивания выполняются в исходном массиве.
+- **Нестабильная сортировка**: при обмене элемента на вершине кучи с элементом в основании кучи относительное положение равных элементов может измениться.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/index.md b/ru/docs/chapter_sorting/index.md
new file mode 100644
index 000000000..4f5487c8d
--- /dev/null
+++ b/ru/docs/chapter_sorting/index.md
@@ -0,0 +1,9 @@
+# Сортировка
+
+
+
+#### Алгоритмы сортировки
+
+> *Алгоритмы сортировки* используются для упорядочивания набора данных в определенном порядке. Они имеют широкое применение, поскольку упоря- доченные данные обычно можно более эффективно анализировать, обрабаты- вать и выполнять в них поиск.
+>
+> Типы данных в алгоритмах сортировки могут быть целыми числами, числа- ми с плавающей запятой, символами или строками, как показано на рис. 11.1. Правила сортировки могут быть установлены в зависимости от потребностей, например по величине чисел, порядку ASCII-кодов символов или произволь- ным пользовательским правилам.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/insertion_sort.md b/ru/docs/chapter_sorting/insertion_sort.md
new file mode 100644
index 000000000..23dbf25db
--- /dev/null
+++ b/ru/docs/chapter_sorting/insertion_sort.md
@@ -0,0 +1,55 @@
+# Сортировка вставками
+
+> *Сортировка вставками* -- это простой алгоритм сортировки, работа которого схожа с процессом ручной сортировки карт в колоде.
+>
+> Более конкретно: в неотсортированном сегменте выбирается опорный эле- мент, который сравнивается по величине с элементами в отсортированном сегменте слева и вставляется на правильное место.
+>
+> На рис. 11.6 иллюстрируется процесс вставки элемента в массив. Пусть опорный элемент обозначен как base, необходимо сдвинуть все элементы от целевого индекса до base вправо на одну позицию, затем присвоить base целе- вому индексу.
+
+
+
+> **Рис. 11.6.** Операция одиночной вставки
+
+## Алгоритм
+
+> Процесс сортировки вставками выглядит следующим образом (см. рис. 11.7):
+
+1) в начальном состоянии первый элемент массива уже отсортирован;
+
+2) выбирается второй элемент массива в качестве base, **после его вставки на правильное место первые два элемента массива отсортированы**;
+
+3) выбирается третий элемент в качестве base, **после его вставки на пра- вильное место первые три элемента массива отсортированы**;
+
+4) таким образом, в последнем раунде выбирается последний элемент в качестве base, **после его вставки на правильное место все элементы отсортированы**.
+
+
+
+> **Рис. 11.7.** Процесс сортировки вставками
+
+```src
+[file]{insertion_sort}-[class]{}-[func]{insertion_sort}
+```
+
+## Характеристики алгоритма
+
+- **Временная сложность** *O*(*n*²), **адаптивная сортировка**: в наихудшем случае каждая операция вставки требует соответственно *n* -- 1, *n* -- 2, ..., 2, 1 итераций цикла, сумма которых равна (*n* -- 1)*n*/2, следовательно, временная сложность составляет *O*(*n*²). При работе с отсортированными данными операция вставки завершается досрочно. Когда входной массив полностью отсортирован, сортировка вставками достигает лучшей временной сложности *O*(*n*).
+
+- **Пространственная сложность** *O*(1), **сортировка на месте**: указатели *i* и *j* используют дополнительное пространство постоянного размера.
+
+- **Стабильная сортировка**: в процессе операции вставки элемент вставляется справа от равных ему элементов, не изменяя их порядок.
+
+## Преимущества сортировки вставками
+
+Временная сложность сортировки вставками составляет *O*(*n*²), в то время как временная сложность быстрой сортировки, которую мы скоро изучим, составляет *O*(*n* log *n*). Несмотря на более высокую временную сложность, **при небольших объемах данных сортировка вставками обычно работает быстрее**.
+
+Этот вывод аналогичен выводу о применимости линейного и бинарного поиска. Алгоритмы класса *O*(*n* log *n*)*, такие как быстрая сортировка, относятся к алгоритмам сортировки, основанным на стратегии "разделяй и властвуй", и часто включают больше элементарных вычислительных операций. При небольших объемах данных значения *n*² и *n* log *n* достаточно близки, сложность не играет доминирующей роли, и количество элементарных операций в каждом раунде становится решающим фактором.
+
+Фактически, многие языки программирования (например, Java) используют сортировку вставками во встроенных функциях сортировки, общая идея такова: для длинных массивов применяются алгоритмы сортировки, основанные на стратегии "разделяй и властвуй", например быстрая сортировка; для коротких массивов непосредственно используется сортировка вставками.
+
+Хотя временная сложность сортировки пузырьком, сортировки выбором и сортировки вставками составляет *O*(*n*²), на практике **частота использования сортировки вставками значительно выше, чем сортировки пузырьком и сортировки выбором**, главным образом по следующим причинам:
+
+- Сортировка пузырьком реализуется на основе обмена элементов, требует использования временной переменной и включает 3 элементарные операции; сортировка вставками реализуется на основе присваивания элементов и требует только 1 элементарную операцию. Следовательно, **вычислительные затраты сортировки пузырьком обычно выше, чем у сортировки вставками**.
+
+- Временная сложность сортировки выбором в любом случае составляет *O*(*n*²). **При работе с частично отсортированными данными сортировка вставками обычно более эффективна, чем сортировка выбором**.
+
+- Сортировка выбором нестабильна и не может применяться для многоуровневой сортировки.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/merge_sort.md b/ru/docs/chapter_sorting/merge_sort.md
new file mode 100644
index 000000000..7bf1ba41e
--- /dev/null
+++ b/ru/docs/chapter_sorting/merge_sort.md
@@ -0,0 +1,73 @@
+# Сортировка слиянием
+
+Сортировка слиянием (merge sort) — это алгоритм сортировки, основанный на стратегии «разделяй и властвуй», который включает этапы «разделения» и «слияния», показанные на рисунке ниже.
+
+1. **Этап разделения**: путем рекурсии массив постоянно делится пополам от средней точки, превращая задачу сортировки длинного массива в задачу сортировки коротких массивов.
+2. **Этап слияния**: когда длина подмассива становится равной 1, разделение прекращается и начинается слияние, при котором два более коротких упорядоченных массива постоянно объединяются в один более длинный упорядоченный массив, пока процесс не завершится.
+
+
+
+## Алгоритм
+
+Как показано на рисунке ниже, «этап разделения» рекурсивно делит массив пополам от средней точки сверху вниз.
+
+1. Вычисляется средняя точка массива `mid`, рекурсивно разделяется левый подмассив (диапазон `[left, mid]`) и правый подмассив (диапазон `[mid + 1, right]`).
+2. Рекурсивно выполняется шаг `1.`, пока длина диапазона подмассива не станет равной 1, после чего процесс прекращается.
+
+«Этап слияния» объединяет левый и правый подмассивы в один упорядоченный массив снизу вверх. Важно отметить, что слияние начинается с подмассивов длиной 1, и каждый подмассив на этапе слияния уже упорядочен.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+Можно заметить, что порядок рекурсии при сортировке слиянием совпадает с постфиксным обходом бинарного дерева.
+
+- **Постфиксный обход**: сначала рекурсивно обходится левое поддерево, затем правое поддерево, и наконец обрабатывается корневой узел.
+- **Сортировка слиянием**: сначала рекурсивно обрабатывается левый подмассив, затем правый подмассив, и наконец выполняется слияние.
+
+Реализация сортировки слиянием показана в следующем коде. Обратите внимание, что диапазон слияния для `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).
+
+- **Этап разделения**: можно использовать «итерацию» вместо «рекурсии» для реализации разделения связного списка, тем самым экономя пространство стекового кадра, используемое рекурсией.
+- **Этап слияния**: в связном списке операции добавления и удаления узлов могут быть реализованы простым изменением ссылок (указателей), поэтому этап слияния (объединение двух коротких упорядоченных связных списков в один длинный упорядоченный связный список) не требует создания дополнительного связного списка.
+
+Конкретные детали реализации довольно сложны, заинтересованные читатели могут изучить соответствующие материалы для дальнейшего обучения.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/quick_sort.md b/ru/docs/chapter_sorting/quick_sort.md
new file mode 100644
index 000000000..40da8f9be
--- /dev/null
+++ b/ru/docs/chapter_sorting/quick_sort.md
@@ -0,0 +1,96 @@
+# Быстрая сортировка
+
+Быстрая сортировка (quick sort) — это алгоритм сортировки, основанный на стратегии «разделяй и властвуй», который работает эффективно и широко применяется.
+
+Ключевой операцией быстрой сортировки является «разделение с опорным элементом», цель которой: выбрать некоторый элемент массива в качестве «опорного числа», переместить все элементы меньше опорного числа влево от него, а элементы больше опорного числа — вправо от него. Конкретно, процесс разделения с опорным элементом показан на следующем рисунке.
+
+1. Выбирается самый левый элемент массива в качестве опорного числа, инициализируются два указателя `i` и `j`, указывающие на оба конца массива.
+2. Устанавливается цикл, в каждом раунде которого используются `i` (`j`) для поиска первого элемента больше (меньше) опорного числа, затем эти два элемента меняются местами.
+3. Цикл выполняет шаг `2.` до тех пор, пока `i` и `j` не встретятся, после чего опорное число меняется местами с границей между двумя подмассивами.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+После завершения разделения с опорным элементом исходный массив разделяется на три части: левый подмассив, опорное число, правый подмассив, причем выполняется условие «любой элемент левого подмассива $\leq$ опорное число $\leq$ любой элемент правого подмассива». Следовательно, далее нам нужно только отсортировать эти два подмассива.
+
+!!! note "Стратегия «разделяй и властвуй» быстрой сортировки"
+
+ Суть разделения с опорным элементом заключается в упрощении задачи сортировки более длинного массива до задачи сортировки двух более коротких массивов.
+
+```src
+[file]{quick_sort}-[class]{quick_sort}-[func]{partition}
+```
+
+## Процесс алгоритма
+
+Общий процесс быстрой сортировки показан на следующем рисунке.
+
+1. Сначала для исходного массива выполняется одно «разделение с опорным элементом», получая неотсортированные левый и правый подмассивы.
+2. Затем для левого и правого подмассивов рекурсивно выполняется «разделение с опорным элементом».
+3. Рекурсия продолжается до тех пор, пока длина подмассива не станет равной 1, что завершает сортировку всего массива.
+
+
+
+```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*²).
+- **Пространственная сложность** *O*(*n*), **сортировка на месте**: в случае полностью обратного порядка входного массива достигается наихудшая глубина рекурсии *n*, используется *O*(*n*) пространства стека. Операция сортировки выполняется на исходном массиве без использования дополнительных массивов.
+- **Нестабильная сортировка**: на последнем шаге разделения с опорным элементом опорное число может быть перемещено вправо от равных ему элементов.
+
+## Почему быстрая сортировка быстрая
+
+Из названия видно, что быстрая сортировка должна иметь определенные преимущества в эффективности. Хотя средняя временная сложность быстрой сортировки такая же, как у «сортировки слиянием» и «пирамидальной сортировки», обычно быстрая сортировка более эффективна, главным образом по следующим причинам.
+
+- **Вероятность возникновения худшего случая очень мала**: хотя худшая временная сложность быстрой сортировки составляет *O*(*n*²) и не так стабильна, как сортировка слиянием, в подавляющем большинстве случаев быстрая сортировка может работать с временной сложностью *O*(*n* log *n*).
+- **Высокая эффективность использования кэша**: при выполнении операции разделения с опорным элементом система может загрузить весь подмассив в кэш, поэтому доступ к элементам более эффективен. В то время как алгоритмы типа «пирамидальной сортировки» требуют скачкообразного доступа к элементам, что лишает их этого свойства.
+- **Малый константный коэффициент сложности**: среди вышеупомянутых трех алгоритмов быстрая сортировка имеет наименьшее общее количество операций сравнения, присваивания, обмена и т. д. Это похоже на причину, по которой «сортировка вставками» быстрее «сортировки пузырьком».
+
+## Оптимизация опорного числа
+
+**Временная эффективность быстрой сортировки может снижаться при некоторых входных данных**. Приведем крайний пример: предположим, что входной массив полностью обратно отсортирован, и поскольку мы выбираем самый левый элемент в качестве опорного числа, то после завершения разделения с опорным элементом опорное число меняется местами с самым правым концом массива, что приводит к длине левого подмассива *n* -- 1 и длине правого подмассива 0. При такой рекурсии после каждого раунда разделения с опорным элементом один подмассив имеет длину 0, стратегия «разделяй и властвуй» становится неэффективной, и быстрая сортировка вырождается в форму, близкую к «сортировке пузырьком».
+
+Чтобы максимально избежать такой ситуации, **мы можем оптимизировать стратегию выбора опорного числа при разделении с опорным элементом**. Например, мы можем случайно выбрать элемент в качестве опорного числа. Однако, если не повезет и каждый раз будет выбрано неидеальное опорное число, эффективность все равно будет неудовлетворительной.
+
+Следует отметить, что языки программирования обычно генерируют «псевдослучайные числа». Если мы создадим специальный тестовый пример для последовательности псевдослучайных чисел, эффективность быстрой сортировки все равно может ухудшиться.
+
+Для дальнейшего улучшения мы можем выбрать три элемента-кандидата в массиве (обычно это первый, последний и средний элементы массива) и **использовать медиану этих трех элементов-кандидатов в качестве опорного числа**. Таким образом, вероятность того, что опорное число будет «ни слишком маленьким, ни слишком большим», значительно возрастет. Конечно, мы также можем выбрать больше элементов-кандидатов для дальнейшего повышения надежности алгоритма. При использовании этого метода вероятность ухудшения временной сложности до *O*(*n*²) значительно снижается.
+
+Пример кода приведен ниже:
+
+```src
+[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition}
+```
+
+## Оптимизация глубины рекурсии
+
+**При некоторых входных данных быстрая сортировка может занимать больше пространства**. Возьмем в качестве примера полностью отсортированный входной массив: пусть длина подмассива в рекурсии равна *m*, каждая операция разделения с опорным элементом будет создавать левый подмассив длиной 0 и правый подмассив длиной *m* -- 1, это означает, что размер задачи, уменьшаемый на каждом уровне рекурсивного вызова, очень мал (уменьшается только на один элемент), высота дерева рекурсии достигнет *n* -- 1, в этом случае потребуется пространство стека размером *O*(*n*).
+
+Чтобы предотвратить накопление пространства стека, мы
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/radix_sort.md b/ru/docs/chapter_sorting/radix_sort.md
new file mode 100644
index 000000000..03d225161
--- /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.` и продолжаем итерацию до тех пор, пока все разряды не будут отсортированы.
+
+
+
+Рассмотрим реализацию кода. Для числа $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$ соответственно.
+- **Стабильная сортировка**: когда сортировка подсчетом стабильна, поразрядная сортировка также стабильна; когда сортировка подсчетом нестабильна, поразрядная сортировка не может гарантировать получение правильного результата сортировки.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/selection_sort.md b/ru/docs/chapter_sorting/selection_sort.md
new file mode 100644
index 000000000..cae3f9eab
--- /dev/null
+++ b/ru/docs/chapter_sorting/selection_sort.md
@@ -0,0 +1,55 @@
+# Сортировка выбором
+
+> Принцип работы *сортировки выбором* весьма прост: запускается цикл, в каж- дой итерации которого из неотсортированной части массива выбирается наи- меньший элемент и помещается в конец отсортированной части.
+>
+> Пусть длина массива равна *n*, алгоритм сортировки выбором заключается в следующем (см. рис. 11.2):
+
+1) в начальном состоянии все элементы не отсортированы, т. е. неотсорти- рованный (индексный) диапазон равен \[0, *n* -- 1\];
+
+2) выбирается наименьший элемент из диапазона \[0, *n* -- 1\] и меняется ме- стами с элементом с индексом 0. После этого первый элемент массива отсортирован;
+
+3) выбирается наименьший элемент из диапазона \[1, *n* -- 1\] и меняется ме- стами с элементом с индексом 1. После этого первые два элемента мас- сива отсортированы;
+
+4) таким образом, после *n* -- 1 итераций выбора и обмена первые *n* -- 1 эле- ментов массива отсортированы;
+
+5) единственный оставшийся элемент обязательно является наибольшим, поэтому сортировка массива завершена.
+
+
+
+> **Рис. 11.2.** Этапы сортировки выбором. Шаги 1--3
+>
+> 
+
+
+
+> **Рис. 11.2.** *Продолжение*. Шаги 4--6
+>
+> 
+
+
+
+> **Рис. 11.2.** *Продолжение*. Шаги 7--9
+>
+> 
+
+
+
+> **Рис. 11.2.** *Окончание*. Шаги 10--11
+>
+> В приведенном ниже коде реализации используется переменная *k* для запи- си индекса наименьшего элемента в неотсортированном диапазоне.
+
+```src
+[file]{selection_sort}-[class]{}-[func]{selection_sort}
+```
+
+## Характеристики алгоритма
+
+- **Временная сложность** *O*(*n*2), **неадаптивная сортировка**: внешний цикл выполняется *n* -- 1 раз, длина неотсортированного диапазона на первой итерации равна *n*, на последней -- 2, т. е. каждый внешний цикл включает *n*, *n* -- 1, \..., 3, 2 итераций внутреннего цикла, сумма которых равна (*n* -- 1)(*n* + 2)/2.
+
+- **Пространственная сложность** *O*(1), **сортировка на месте**: указате- ли *i* и *j* используют дополнительное пространство постоянного раз- мера.
+
+- **Нестабильная сортировка**: как показано на рис. 11.3, элемент nums\[i\] может быть перемещен вправо от равного ему элемента, что изменяет их относительный порядок.
+
+
+
+> **Рис. 11.3.** Пример нестабильности сортировки выбором
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/sorting_algorithm.md b/ru/docs/chapter_sorting/sorting_algorithm.md
new file mode 100644
index 000000000..81aa7c1a9
--- /dev/null
+++ b/ru/docs/chapter_sorting/sorting_algorithm.md
@@ -0,0 +1,46 @@
+# Алгоритмы сортировки
+
+*Алгоритмы сортировки* используются для упорядочивания набора данных в определенном порядке. Они имеют широкое применение, поскольку упорядоченные данные обычно можно более эффективно анализировать, обрабатывать и выполнять в них поиск.
+
+Типы данных в алгоритмах сортировки могут быть целыми числами, числами с плавающей запятой, символами или строками, как показано на рис. 11.1. Правила сортировки могут быть установлены в зависимости от потребностей, например по величине чисел, порядку ASCII-кодов символов или произвольным пользовательским правилам.
+
+
+
+## Критерии оценки
+
+**Эффективность выполнения**: ожидается, что временная сложность алгоритма сортировки будет как можно ниже, а общее количество операций -- минимальным (уменьшение константного множителя во временной сложности). Для больших объемов данных эффективность выполнения особенно важна.
+
+**Местность**: как следует из названия, сортировка на месте осуществляется путем непосредственной работы с исходным массивом без использования дополнительных вспомогательных массивов, что позволяет экономить память. Обычно операции перемещения данных при сортировке на месте малочисленны, а скорость выполнения выше.
+
+**Стабильность**: стабильная сортировка сохраняет относительный порядок равных элементов в массиве после завершения сортировки.
+
+Стабильная сортировка является необходимым условием для многоуровневой сортировки. Предположим, что у нас есть таблица с информацией о студентах, где 1-й и 2-й столбцы -- это имя и возраст соответственно. В этом случае нестабильная сортировка может привести к потере упорядоченности входных данных.
+
+```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*), но ее универсальность относительно ниже.
+
+## Идеальный алгоритм сортировки
+
+**Быстрый**, **на месте**, **стабильный**, **адаптивный**, **с хорошей универсальностью**. Очевидно, что до сих пор нет алгоритма сортировки, сочетающего все эти характеристики. Поэтому при выборе алгоритма необходимо учитывать особенности данных и требования задачи.
+
+Далее мы изучим различные алгоритмы сортировки и проанализируем их достоинства и недостатки на основе вышеуказанных критериев оценки.
\ No newline at end of file
diff --git a/ru/docs/chapter_sorting/summary.md b/ru/docs/chapter_sorting/summary.md
new file mode 100644
index 000000000..008b3cc9b
--- /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)$.
+- Блочная сортировка включает три шага: распределение данных по блокам, сортировка внутри блоков и объединение результатов. Она также воплощает стратегию "разделяй и властвуй" и подходит для случаев с очень большим объемом данных. Ключ к блочной сортировке заключается в равномерном распределении данных.
+- Сортировка подсчетом является частным случаем блочной сортировки, она реализует сортировку путем подсчета количества появлений данных. Сортировка подсчетом подходит для случаев с большим объемом данных, но ограниченным диапазоном данных, и требует, чтобы данные могли быть преобразованы в положительные целые числа.
+- Поразрядная сортировка реализует сортировку данных путем последовательной сортировки по разрядам, требуя, чтобы данные могли быть представлены в виде чисел с фиксированным количеством разрядов.
+- В целом, мы хотим найти алгоритм сортировки, обладающий такими преимуществами, как высокая эффективность, стабильность, сортировка на месте и адаптивность. Однако, как и в случае с другими структурами данных и алгоритмами, не существует алгоритма сортировки, который одновременно удовлетворял бы всем этим условиям. В практических приложениях необходимо выбирать подходящий алгоритм сортировки в зависимости от характеристик данных.
+- На рисунке ниже сравниваются эффективность, стабильность, местность и адаптивность основных алгоритмов сортировки.
+
+
+
+### Вопросы и ответы
+
+**В:** В каких случаях стабильность алгоритма сортировки является необходимой?
+
+В реальности мы можем сортировать объекты по какому-либо их атрибуту. Например, у студентов есть два атрибута: имя и рост, и мы хотим реализовать многоуровневую сортировку: сначала отсортировать по имени, получив `(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)$.
\ No newline at end of file
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..df7a71d6f
--- /dev/null
+++ b/ru/docs/chapter_stack_and_queue/deque.md
@@ -0,0 +1,388 @@
+# Двусторонняя очередь
+
+В очереди мы можем удалять элементы только из головы или добавлять элементы в хвост. Как показано на рисунке ниже, двусторонняя очередь (double-ended queue) предоставляет большую гибкость, позволяя выполнять операции добавления или удаления элементов как в голове, так и в хвосте.
+
+
+
+## Основные операции с двусторонней очередью
+
+Основные операции с двусторонней очередью представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования.
+
+ Таблица Эффективность операций двусторонней очереди
+
+| Метод | Описание | Временная сложность |
+| -------------- | -------------------------------- | ------------------- |
+| `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%B0%D1%86%D0%B8%D1%8F%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%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%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%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%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%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%BB%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%83%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%D0%94%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,%20deq%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%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%D0%AD%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%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%B5%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%D0%AD%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%D0%AD%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%20rear%20%3D%22,%20rear%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%BE%D0%B2%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%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%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%0A%20%20%20%20print%28%22%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%20%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20%20pop_front%20%3D%22,%20pop_front%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%D0%B8%D0%B7%20%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D1%8B%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%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%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%0A%20%20%20%20print%28%22%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%20%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20%20pop_rear%20%3D%22,%20pop_rear%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%D0%B8%D0%B7%20%D1%85%D0%B2%D0%BE%D1%81%D1%82%D0%B0%20deque%20%3D%22,%20deq%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%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,%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%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%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%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%D0%94%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%D0%BF%D1%83%D1%81%D1%82%D0%B0%20%3D%22,%20is_empty%29&cumulative=false&curIn
\ No newline at end of file
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..faf20ecac
--- /dev/null
+++ b/ru/docs/chapter_stack_and_queue/index.md
@@ -0,0 +1,11 @@
+```markdown
+# Стек и очередь
+
+
+
+!!! abstract
+
+ Стек можно сравнить со стопкой котиков, а очередь — с котиками, стоящими в очереди.
+
+ Они представляют логику «первый вошел — последний вышел» и «первый пришел — первый вышел» соответственно.
+```
\ No newline at end of file
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..726fc8540
--- /dev/null
+++ b/ru/docs/chapter_stack_and_queue/queue.md
@@ -0,0 +1,423 @@
+# Очередь
+
+Очередь (queue) -- это линейная структура данных, следующая правилу «первый пришел -- первый вышел». Как следует из названия, очередь моделирует реальную очередь, когда новые элементы постоянно добавляются в конец очереди, а элементы в начале очереди покидают ее последовательно.
+
+Как показано на рисунке ниже, начало очереди называется "голова очереди", а конец -- "хвост очереди". Операция добавления элемента в конец очереди называется "добавление в очередь", а удаление элемента из начала очереди -- "удаление из очереди".
+
+
+
+## Основные операции с очередью
+
+Основные операции с очередью представлены в таблице ниже. Следует отметить, что имена методов могут различаться в зависимости от языка программирования. Здесь используются те же названия методов, что и для стека.
+
+ Таблица Эффективность операций с очередью
+
+| Метод | Описание | Временная сложность |
+| -------- | ------------------------------------------------------------- | ------------------- |
+| `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 и обхода, можно использовать 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%B0%D1%86%D0%B8%D1%8F%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%0A%20%20%20%20%23%20%D0%92%20Python%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%D0%B5%D1%82%D1%81%D1%8F%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%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%20deque%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%BF%D0%BE%D0%BB%D0%BD%D0%BE%D1%86%D0%B5%D0%BD%D0%BD%D1%8B%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%BE%D0%BD%20%D0%BD%D0%B5%20%D0%BE%D1%87%D0%B5%D0%BD%D1%8C%20%D1%83%D0%B4%D0%BE%D0%B1%D0%B5%D0%BD%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%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%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%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C%20que%20%3D%22,%20que%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%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%83%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%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%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%20front%20%3D%22,%20front%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%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%A3%D0%B4%D0%B0%D0%BB%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,%20pop%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%20que%20%3D%22,%20que%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%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,%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%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8%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%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,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+## Реализация очереди
+
+Для реализации очереди необходима структура данных, позволяющая добавлять элементы с одного конца и удалять с другого. Этим требованиям соответствуют как связный список, так и массив.
+
+### Реализация на основе связного списка
+
+Как показано на рисунке ниже, можно считать "головной узел" и "хвостовой узел" связного списка соответственно "головой очереди" и "хвостом очереди". При этом в хвост очереди можно только добавлять узлы, а из головы очереди -- только удалять узлы.
+
+=== "LinkedListQueue"
+ 
+
+=== "push()"
+ 
+
+=== "pop()"
+ 
+
+Ниже приведен код реализации очереди на основе связного списка:
+
+```src
+[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{}
+```
+
+### Реализация на основе массива
+
+Удаление первого элемента в массиве имеет временную сложность $O(n)$, что снижает эффективность операции удаления из очереди. Однако можно использовать следующий изящный метод, чтобы избежать этой проблемы.
+
+Можно использовать переменную `front`, указывающую на индекс элемента в голове очереди, и поддерживать переменную `size` для записи длины очереди. Определим `rear = front + size`, эта формула вычисляет `rear`, указывающий на позицию после элемента в хвосте очереди.
+
+При таком подходе **действительный интервал элементов в массиве составляет `[front, rear - 1]`**, различные операции реализуются следующим образом:
+
+- Операция добавления в очередь: присваивает входной элемент индексу `rear` и увеличивает `size` на 1.
+- Операция удаления из очереди: только увеличивает `front` на 1 и уменьшает `size` на 1.
+
+Как видно, операции добавления и удаления требуют только одного действия, временная сложность обеих составляет $O(1)$.
+
+=== "ArrayQueue"
+ 
+
+=== "push()"
+ 
+
+=== "pop()"
+ 
+
+Можно заметить проблему: в процессе непрерывного добавления и удаления элементов `front` и `rear` постоянно смещаются вправо, **и когда они достигают конца массива, дальнейшее перемещение становится невозможным**. Для решения этой проблемы можно рассматривать массив как "кольцевой массив" с соединенными концами.
+
+Для кольцевого массива необходимо, чтобы `front` или `rear` при выходе за пределы конца массива возвращались к началу для продолжения обхода. Эта периодическая закономерность может быть реализована с помощью "операции взятия остатка", как показано в коде ниже:
+
+```src
+[file]{array_queue}-[class]{array_queue}-[func]{}
+```
+
+Приведенная выше реализация очереди все еще имеет ограничение: ее длина неизменна. Однако эту проблему несложно решить, заменив массив на динамический массив, что позволит ввести механизм расширения. Заинтересованные читатели могут попробовать реализовать это самостоятельно.
+
+Выводы при сравнении двух реализаций аналогичны выводам для стека, поэтому здесь не повторяются.
+
+## Типичные сценарии применения очереди
+
+- **Заказы на Taobao**. После размещения заказа покупателем заказ добавляется в очередь, после чего система последовательно обрабатывает заказы из очереди. Во время распродажи "Двойной одиннадцатый" за короткое время генерируется огромное количество заказов, и высокая параллельность становится ключевой проблемой, которую необходимо решить инженерам.
+- **Различные задачи в очереди**. Любой сценарий, требующий реализации функции "первым пришел -- первым обслужен", например, очередь задач принтера, очередь выдачи блюд в ресторане и т. д. Очередь в этих сценариях эффективно поддерживает порядок обработки.
\ No newline at end of file
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..ac8f8e368
--- /dev/null
+++ b/ru/docs/chapter_stack_and_queue/stack.md
@@ -0,0 +1,430 @@
+# Стек
+
+Стек (stack) -- это линейная структура данных, которая следует логике «первый вошел -- последний вышел».
+
+Стек можно сравнить со стопкой тарелок на столе: чтобы достать тарелку снизу, нужно сначала убрать все тарелки сверху. Заменив тарелки на элементы различных типов (например, целые числа, символы, объекты и т. д.), мы получим структуру данных, называемую стеком.
+
+Как показано на рисунке ниже, верх стопки элементов называется вершиной стека, а низ -- основанием стека. Операция добавления элемента на вершину стека называется вставка, а удаление элемента с вершины -- извлечение.
+
+
+
+## Основные операции со стеком
+
+Основные операции со стеком представлены в таблице ниже, конкретные имена методов зависят от используемого языка программирования. Здесь в качестве примера используются распространенные имена `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%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%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%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+## Реализация стека
+
+Чтобы глубже понять механизм работы стека, попробуем реализовать собственный класс стека.
+
+Стек следует принципу «первый вошел -- последний вышел», поэтому добавление и удаление элементов возможно только на вершине стека. Однако в массивах и связных списках элементы можно добавлять и удалять в любом месте, **поэтому стек можно рассматривать как ограниченный массив или связный список**. Иными словами, можно скрыть часть операций массива или связного списка, чтобы их внешняя логика соответствовала характеристикам стека.
+
+### Реализация на основе связного списка
+
+При использовании для реализации стека связного списка можно считать головной узел связного списка вершиной стека, а хвостовой узел -- основанием стека.
+
+Как показано на рисунке ниже, для операции вставки элемента достаточно вставить его в начало связного списка. Этот метод вставки узла называется вставка в голову. Для операции извлечения элемента достаточно удалить головной узел из связного списка.
+
+=== "LinkedListStack"
+ 
+
+=== "push()"
+ 
+
+=== "pop()"
+ 
+
+Ниже приведен пример кода для реализации стека на основе связного списка:
+
+```src
+[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
+```
+
+### Реализация на основе массива
+
+При использовании для реализации стека массива можно считать конец массива вершиной стека. Как показано на рисунке ниже, операции вставки и извлечения соответствуют добавлению и удалению элементов в конце массива. Временная сложность этих операций составляет $O(1)$.
+
+=== "ArrayStack"
+ 
+
+=== "push()"
+ 
+
+=== "pop()"
+ 
+
+Поскольку количество вставляемых элементов может постоянно увеличиваться, можно использовать динамический массив, чтобы не заниматься расширением массива самостоятельно. Ниже приведен пример кода:
+
+```src
+[file]{array_stack}-[class]{array_stack}-[func]{}
+```
+
+## Сравнение двух реализаций
+
+**Поддерживаемые операции**
+
+Обе реализации поддерживают все операции, определенные для стека. Реализация на основе массива дополнительно поддерживает произвольный доступ, но это выходит за рамки определения стека, поэтому обычно не используется.
+
+**Временная сложность**
+
+В реализации на основе массива операции добавления и удаления элемента выполняются в заранее выделенной непрерывной памяти, что обеспечивает хорошую локальность кеша и, следовательно, высокую эффективность. Однако, если при добавлении элемента превышается емкость массива, срабатывает механизм расширения, что приводит к увеличению временной сложности данной операции до $O(n)$.
+
+В реализации на основе связного списка расширение происходит очень гибко, и не возникает проблемы снижения эффективности, как в случае расширения массива. Однако операция добавления элемента требует инициализации объекта узла и изменения указателя, что делает ее относительно менее эффективной. Тем не менее, если добавляемый элемент уже является объектом узла, можно избежать шага инициализации, что повысит эффективность.
+
+Таким образом, если элементы операций добавления и удаления являются примитивными типами данных, такими как `int` или `double`, можно сделать следующие выводы:
+
+- Стек, реализованный на основе массива, при срабатывании механизма расширения теряет в эффективности, но, так как расширение является редкой операцией, средняя эффективность выше.
+- Стек, реализованный на основе связного списка, обеспечивает более стабильную эффективность.
+
+**Пространственная сложность**
+
+При инициализации массива система выделяет для него начальную емкость, которая может превышать фактические потребности. Кроме того, механизм расширения обычно осуществляется с определенным коэффициентом (например, в 2 раза), и емкость после расширения также может превышать фактические потребности. Поэтому **стек, реализованный на основе массива, может приводить к некоторым потерям пространства**.
+
+Однако, так как узлы связного списка требуют дополнительного хранения указателей, **занимаемое ими пространство сравнительно больше**.
+
+Таким образом, нельзя однозначно определить, какая реализация более экономична в плане памяти, необходимо анализировать конкретные ситуации.
+
+## Типичные сценарии применения стека
+
+- **Возврат и переход вперед в браузере, отмена и повтор в программном обеспечении**. Каждый раз, когда открывается новая веб-страница, браузер выполняет добавление предыдущей страницы в стек, что позволяет вернуться к ней с помощью операции возврата. Операция возврата фактически является выполнением удаления из стека. Если требуется поддержка как возврата, так и перехода вперед, необходимо использовать два стека.
+- **Управление памятью программы**. Каждый раз при вызове функции система добавляет на вершину стека фрейм для записи контекстной информации функции. В рекурсивных функциях на этапе нисходящей рекурсии постоянно выполняется добавление в стек, а на этапе восходящей рекурсии -- удаление из стека.
\ No newline at end of file
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..0506cc279
--- /dev/null
+++ b/ru/docs/chapter_stack_and_queue/summary.md
@@ -0,0 +1,31 @@
+# Резюме
+
+### Ключевые моменты
+
+- Стек -- это структура данных, следующая принципу «первый вошел -- последний вышел», которая может быть реализована с помощью массива или связного списка.
+- С точки зрения временной эффективности, реализация стека на основе массива имеет более высокую среднюю эффективность, но во время расширения временная сложность одной операции вставки ухудшается до $O(n)$. В сравнении с этим, реализация стека на основе связного списка обеспечивает более стабильную эффективность.
+- С точки зрения пространственной эффективности, реализация стека на основе массива может привести к определенной степени потери пространства. Но следует отметить, что память, занимаемая узлами связного списка, больше, чем элементами массива.
+- Очередь -- это структура данных, следующая принципу «первый пришел -- первый вышел», которая также может быть реализована с помощью массива или связного списка. В сравнении временной и пространственной эффективности выводы для очереди аналогичны выводам для стека.
+- Двусторонняя очередь -- это очередь с большей степенью свободы, которая позволяет добавлять и удалять элементы с обоих концов.
+
+### Вопросы и ответы
+
+**В**: Реализованы ли функции перехода вперед и назад в браузере с помощью двусвязного списка?
+
+Функции перехода вперед и назад в браузере по сути являются проявлением «стека». Когда пользователь посещает новую страницу, эта страница добавляется на вершину стека; когда пользователь нажимает кнопку «назад», эта страница извлекается с вершины стека. Использование двусторонней очереди позволяет удобно реализовать некоторые дополнительные операции, что упоминается в разделе «Двусторонняя очередь».
+
+**В**: Нужно ли освобождать память узла после извлечения из стека?
+
+Если извлеченный узел потребуется в дальнейшем, то освобождать память не нужно. Если он больше не понадобится, то в таких языках, как `Java` и `Python`, есть автоматическая сборка мусора, поэтому освобождать память вручную не требуется; в `C` и `C++` необходимо освобождать память вручную.
+
+**В**: Двусторонняя очередь похожа на два стека, соединенных вместе. Каково ее назначение?
+
+Двусторонняя очередь похожа на комбинацию стека и очереди или на два стека, соединенных вместе. Она представляет логику стека + очереди, поэтому может реализовать все применения стека и очереди, и при этом более гибка.
+
+**В**: Как именно реализованы отмена (undo) и повтор (redo)?
+
+Используются два стека: стек `A` для отмены и стек `B` для повтора.
+
+1. Каждый раз, когда пользователь выполняет операцию, эта операция помещается в стек `A`, а стек `B` очищается.
+2. Когда пользователь выполняет «отмену», последняя операция извлекается из стека `A` и помещается в стек `B`.
+3. Когда пользователь выполняет «повтор», последняя операция извлекается из стека `B` и помещается в стек `A`.
\ No newline at end of file
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..642da8828
--- /dev/null
+++ b/ru/docs/chapter_tree/array_representation_of_tree.md
@@ -0,0 +1,68 @@
+# Представление двоичного дерева с помощью массивом
+
+При представлении в виде списка единицей хранения двоичного дерева является узел `TreeNode`, а узлы соединяются между собой указателями. В предыдущем разделе были рассмотрены основные операции с двоичным деревом, представленным в виде списка.
+
+Можно ли представить двоичное дерево с помощью массива? Ответ положительный.
+
+## Представление идеального двоичного дерева
+
+Сначала рассмотрим простой пример. Если дано идеальное двоичное дерево и все его узлы хранятся в массиве в порядке обхода по уровням, то каждому узлу соответствует уникальный индекс массива.
+
+На основе свойств обхода по уровням можно вывести формулу отображения между индексом родительского узла и индексами дочерних узлов: **если индекс узла равен $i$, то индекс его левого дочернего узла равен $2i + 1$, а индекс правого дочернего узла равен $2i + 2$**. На рисунке ниже показаны отношения отображения между индексами различных узлов.
+
+
+
+
+**Роль формулы отображения эквивалентна ссылкам на узлы (указателям) в списке**. Для любого узла в массиве мы можем получить доступ к его левому (правому) дочернему узлу с помощью формулы отображения.
+
+## Представление произвольного двоичного дерева
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Преимущества и ограничения
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_tree/avl_tree.md b/ru/docs/chapter_tree/avl_tree.md
new file mode 100644
index 000000000..70a6484cb
--- /dev/null
+++ b/ru/docs/chapter_tree/avl_tree.md
@@ -0,0 +1,358 @@
+# AVL-дерево *
+
+В главе "Двоичное дерево поиска" мы упоминали, что после многократных операций вставки и удаления двоичное дерево поиска может выродиться в список. В этом случае временная сложность всех операций ухудшится с $O(\log n)$ до $O(n)$.
+
+Как показано на рисунке ниже, после двух операций удаления узлов это двоичное дерево поиска выродится в список.
+
+
+
+Например, в идеальном двоичном дереве, показанном на рисунке ниже, после вставки двух узлов дерево сильно наклонится влево, и временная сложность операции поиска также ухудшится.
+
+
+
+В 1962 году Г. М. Адельсон-Вельский и Е. М. Ландис в статье "An algorithm for the organization of information" предложили AVL-дерево. В статье подробно описана серия операций, обеспечивающих, что после непрерывного добавления и удаления узлов AVL-дерево не вырождается, благодаря чему временная сложность различных операций сохраняется на уровне $O(\log n)$. Другими словами, в сценариях, требующих частых операций вставки, удаления, поиска и изменения, 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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+Как показано на рисунке ниже, когда узел `child` имеет правый дочерний узел (обозначим его как `grand_child`), необходимо добавить один шаг в правый поворот: сделать `grand_child` левым дочерним узлом `node`.
+
+
+
+"Поворот вправо" — это образное выражение, на самом деле это реализуется путем изменения указателей узлов, код показан ниже:
+
+```src
+[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate}
+```
+
+### Левый поворот
+
+Соответственно, если рассмотреть "зеркальное отражение" вышеупомянутого несбалансированного двоичного дерева, необходимо выполнить "левый поворот", показанный на рисунке ниже.
+
+
+
+Аналогично, как показано на рисунке ниже, когда узел `child` имеет левый дочерний узел (обозначим его как `grand_child`), необходимо добавить один шаг в левый поворот: сделать `grand_child` правым дочерним узлом `node`.
+
+
+
+Можно заметить, что **операции правого и левого поворотов логически зеркально симметричны, и две ситуации несбалансированности, которые они решают, также симметричны**. Основываясь на симметрии, нам нужно только заменить все `left` на `right` и все `right` на `left` в коде реализации правого поворота, чтобы получить код реализации левого поворота:
+
+```src
+[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate}
+```
+
+### Сначала левый поворот, затем правый поворот
+
+Для несбалансированного узла 3 на рисунке ниже использование только левого или правого поворота не может восстановить баланс поддерева. В этом случае необходимо сначала выполнить "левый поворот" для `child`, а затем "правый поворот" для `node`.
+
+
+
+### Сначала правый поворот, затем левый поворот
+
+Как показано на рисунке ниже, для зеркальной ситуации вышеупомянутого несбалансированного двоичного дерева необходимо сначала выполнить "правый поворот" для `child`, а затем "левый поворот" для `node`.
+
+
+
+### Выбор поворота
+
+На рисунке ниже показаны четыре ситуации несбалансированности, соответствующие вышеупомянутым случаям, для которых требуются операции правого поворота, сначала левого поворота затем правого поворота, сначала правого поворота затем левого поворота и левого поворота соответственно.
+
+
+
+Как показано в таблице ниже, мы определяем, к какой ситуации на рисунке выше относится несбалансированный узел, путем оценки знака коэффициента балансировки несбалансированного узла и коэффициента балансировки дочернего узла с большей высотой.
+
+ Таблица Условия выбора четырех типов поворотов
+
+| Коэффициент балансировки несбалансированного узла | Коэффициент балансировки дочернего узла | Применяемый метод поворота |
+| ------------------ | ---------------- | ---------------- |
+| $> 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-деревом условия балансировки красно-черного дерева более мягкие, для операций вставки и удаления узлов требуется меньше операций поворота, средняя эффективность операций добавления и удаления узлов выше.
\ No newline at end of file
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..a9bbdbb08
--- /dev/null
+++ b/ru/docs/chapter_tree/binary_search_tree.md
@@ -0,0 +1,105 @@
+# Двоичное дерево поиска
+
+Как показано на рисунке ниже, двоичное дерево поиска (binary search tree) удовлетворяет следующим условиям.
+
+1. Для корневого узла значения всех узлов в левом поддереве $<$ значения корневого узла $<$ значения всех узлов в правом поддереве.
+2. Левое и правое поддеревья любого узла также являются двоичными деревьями поиска, т. е. также удовлетворяют условию `1.`.
+
+
+
+## Операции с двоичным деревом поиска
+
+Мы инкапсулируем двоичное дерево поиска в класс `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>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+Операция поиска в двоичном дереве поиска работает по тому же принципу, что и алгоритм бинарного поиска: в каждом раунде исключается половина случаев. Количество циклов не превышает высоты двоичного дерева, когда двоичное дерево сбалансировано, используется $O(\log n)$ времени. Пример кода приведен ниже:
+
+```src
+[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search}
+```
+
+### Вставка узла
+
+Для заданного элемента `num`, который необходимо вставить, чтобы сохранить свойство двоичного дерева поиска "левое поддерево < корневой узел < правое поддерево", процесс вставки выглядит следующим образом.
+
+1. **Поиск позиции для вставки**: аналогично операции поиска, начиная с корневого узла, циклически выполняем поиск вниз в зависимости от соотношения между значением текущего узла и `num`, пока не выйдем за пределы листового узла (достигнем `None`), после чего выходим из цикла.
+2. **Вставка узла в эту позицию**: инициализируем узел `num` и помещаем этот узел на место `None`.
+
+
+
+В реализации кода необходимо обратить внимание на следующие два момента.
+
+- Двоичное дерево поиска не допускает существования дублирующихся узлов, иначе это нарушит его определение. Поэтому, если узел, который необходимо вставить, уже существует в дереве, вставка не выполняется и происходит прямой возврат.
+- Для реализации вставки узла нам необходимо использовать узел `pre` для сохранения узла из предыдущего раунда цикла. Таким образом, когда мы достигаем `None`, мы можем получить его родительский узел и завершить операцию вставки узла.
+
+```src
+[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert}
+```
+
+Как и при поиске узла, вставка узла использует $O(\log n)$ времени.
+
+### Удаление узла
+
+Сначала находим целевой узел в двоичном дереве, затем удаляем его. Аналогично вставке узла, нам необходимо обеспечить, чтобы после завершения операции удаления свойство двоичного дерева поиска "левое поддерево < корневой узел < правое поддерево" по-прежнему выполнялось. Поэтому, в зависимости от количества дочерних узлов целевого узла, мы различаем 3 случая: 0, 1 и 2, и выполняем соответствующую операцию удаления узла.
+
+Как показано на рисунке ниже, когда степень удаляемого узла равна $0$, это означает, что узел является листовым и может быть удален напрямую.
+
+
+
+Как показано на рисунке ниже, когда степень удаляемого узла равна $1$, достаточно заменить удаляемый узел его дочерним узлом.
+
+
+
+Когда степень удаляемого узла равна $2$, мы не можем удалить его напрямую, а должны использовать другой узел для замены этого узла. Чтобы сохранить свойство двоичного дерева поиска "левое поддерево $<$ корневой узел $<$ правое поддерево", **этим узлом может быть минимальный узел правого поддерева или максимальный узел левого поддерева**.
+
+Предположим, мы выбираем минимальный узел правого поддерева (следующий узел в симметричном обходе), тогда процесс удаления выглядит следующим образом.
+
+1. Находим узел, следующий за удаляемым узлом в "последовательности симметричного обхода", обозначим его как `tmp`.
+2. Заменяем значение удаляемого узла значением `tmp` и рекурсивно удаляем узел `tmp` в дереве.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+Операция удаления узла также использует $O(\log n)$ времени, где поиск удаляемого узла требует $O(\log n)$ времени, получение узла-преемника в симметричном обходе требует $O(\log n)$ времени. Пример кода приведен ниже:
+
+```src
+[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove}
+```
+
+### Упорядоченность симметричного обхода
+
+Как показано на рисунке ниже, симметричный обход двоичного дерева следует порядку обхода "левый $\rightarrow$ корневой $\rightarrow$ правый", а двоичное дерево поиска удовлетворяет соотношению размеров "левый дочерний узел $<$ корневой узел $<$ правый дочерний узел".
+
+Это означает, что при выполнении симметричного обхода двоичного дерева поиска всегда сначала обходится следующий наименьший узел, что приводит к важному свойству: **последовательность симметричного обхода двоичного дерева поиска является возрастающей**.
+
+Используя свойство возрастания симметричного обхода, мы можем получить упорядоченные данные в двоичном дереве поиска всего за $O(n)$ времени, без необходимости выполнения дополнительных операций сортировки, что очень эффективно.
+
+
\ No newline at end of file
diff --git a/ru/docs/chapter_tree/binary_tree.md b/ru/docs/chapter_tree/binary_tree.md
new file mode 100644
index 000000000..ec5d959a6
--- /dev/null
+++ b/ru/docs/chapter_tree/binary_tree.md
@@ -0,0 +1,620 @@
+# Двоичные деревья
+
+Двоичное (бинарное) дерево (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 и все узлы ниже него».
+
+
+
+## Основные понятия двоичного дерева
+
+Основные понятия двоичного дерева показаны на рисунке ниже.
+
+- Корневой узел (root node): узел, находящийся на верхнем уровне дерева и не имеющий родительского узла.
+- Листовой узел (leaf node): узел, не имеющий дочерних узлов, оба его указателя указывают на `None`.
+- Ребро (edge): отрезок, соединяющий два узла, т. е. ссылка (указатель) узла.
+- Уровень узла (level): увеличивается сверху вниз, уровень корневого узла равен 1.
+- Степень узла (degree): количество дочерних узлов узла. В двоичном дереве степень может быть 0, 1 или 2.
+- Высота двоичного дерева (height): количество ребер от корневого узла до самого удаленного листового узла.
+- Глубина узла (depth): количество ребер от корневого узла до данного узла.
+- Высота узла (height): количество ребер от самого удаленного листового узла до данного узла.
+
+
+
+!!! 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%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%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%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%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%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%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%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%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
+
+### Вставка и удаление узлов
+
+Подобно спискам, в двоичном дереве вставку и удаление узлов можно выполнять путем изменения указателей. На рисунке ниже приведен пример.
+
+
+
+=== "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%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%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%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%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%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%
\ No newline at end of file
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..b624d98df
--- /dev/null
+++ b/ru/docs/chapter_tree/binary_tree_traversal.md
@@ -0,0 +1,88 @@
+# Обход двоичного дерева
+
+С физической точки зрения дерево является структурой данных, основанной на связном списке, поэтому его обход осуществляется последовательным доступом к узлам через указатели. Однако, будучи нелинейной структурой данных, обход дерева сложнее, чем обход связного списка, и требует использования алгоритмов поиска.
+
+Наиболее распространенные методы обхода двоичного дерева включают обход по уровням, прямой, симметричный и обратный обходы.
+
+## Обход по уровням
+
+Обход по уровням осуществляется сверху вниз, выполняется последовательный обход двоичного дерева с посещением узлов на каждом уровне слева направо, как показано на рис. 7.9.
+
+Обход по уровням по своей сути является обходом в ширину, также называемым поиском в ширину, который характеризуется постепенно расширяющимся кольцом от центра к периферии.
+
+
+
+### Код реализации
+
+Обход в ширину обычно реализуется с использованием очереди. Очередь следует принципу «первый вошел -- первый вышел», а обход в ширину -- принципу «поэтапное продвижение», что делает их концептуально схожими. Ниже приведен код реализации.
+
+```src
+[file]{binary_tree_bfs}-[class]{}-[func]{level_order}
+```
+
+### Анализ сложности
+
+**Временная сложность** *O*(*n*): каждый узел посещается один раз, что занимает *O*(*n*) времени выполнения, где *n* -- количество узлов.
+
+**Пространственная сложность** *O*(*n*): в худшем случае, т. е. в полном двоичном дереве, до достижения самого нижнего уровня в очереди может находиться одновременно (*n* + 1)/2 узлов, что занимает *O*(*n*) пространства.
+
+## Прямой, симметричный и обратный обходы
+
+Прямой, симметричный и обратный обходы относятся к обходам в глубину, также называемым поиск в глубину, который характеризуется подходом «сначала до конца, затем возврат и продолжение».
+
+На рис. 7.10 демонстрируется принцип работы обхода в глубину для двоичного дерева. **Обход в глубину можно представить как обход двоичного дерева по периметру**, при этом на каждом узле встречаются три позиции, соответствующие прямому, симметричному и обратному обходам.
+
+
+
+### Код реализации
+
+Поиск в глубину обычно реализуется на основе рекурсии.
+
+```src
+[file]{binary_tree_dfs}-[class]{}-[func]{post_order}
+```
+
+На рис. 7.11 демонстрируется рекурсивный процесс прямого обхода двоичного дерева, который можно разделить на два противоположных этапа: рекурсия и возврат.
+
+1. Рекурсия означает начало нового метода, в процессе которого программа посещает следующий узел.
+
+2. Возврат означает возвращение функции, что указывает на завершение посещения текущего узла.
+
+=== "<1>"
+ 
+
+=== "<2>"
+ 
+
+=== "<3>"
+ 
+
+=== "<4>"
+ 
+
+=== "<5>"
+ 
+
+=== "<6>"
+ 
+
+=== "<7>"
+ 
+
+=== "<8>"
+ 
+
+=== "<9>"
+ 
+
+=== "<10>"
+ 
+
+=== "<11>"
+ 
+
+### Анализ сложности
+
+**Временная сложность** *O*(*n*): все узлы посещаются один раз, что занимает *O*(*n*) времени.
+
+**Пространственная сложность** *O*(*n*): в худшем случае, когда дерево вырождается в список, глубина рекурсии достигает *n*, система занимает *O*(*n*) пространства стека.
\ No newline at end of file
diff --git a/ru/docs/chapter_tree/index.md b/ru/docs/chapter_tree/index.md
new file mode 100644
index 000000000..ece9a2504
--- /dev/null
+++ b/ru/docs/chapter_tree/index.md
@@ -0,0 +1,10 @@
+```markdown
+# Деревья
+
+
+
+!!! abstract
+
+ Двоичное (бинарное) дерево -- это нелинейная структура данных, представляющая отношения между предками и потомками и отражающая логику «разделяй и властвуй».
+
+```
\ No newline at end of file
diff --git a/ru/docs/chapter_tree/summary.md b/ru/docs/chapter_tree/summary.md
new file mode 100644
index 000000000..9930030fc
--- /dev/null
+++ b/ru/docs/chapter_tree/summary.md
@@ -0,0 +1,54 @@
+# Резюме
+
+### Основные моменты
+
+- Двоичное дерево — это нелинейная структура данных, отражающая логику «разделяй и властвуй». Каждый узел двоичного дерева содержит значение и два указателя, указывающие на его левый и правый дочерние узлы.
+- Для заданного узла дерева дерево, образованное его левым (правым) дочерним узлом и всеми узлами ниже него, называется левым (правым) поддеревом этого узла.
+- Основные понятия двоичного дерева включают корневой узел, листовой узел, уровень, степень, ребро, высоту и глубину.
+- Операции инициализации двоичного дерева, вставки и удаления узлов аналогичны операциям со связными списками.
+- Распространенные типы двоичных деревьев включают идеальное двоичное дерево, совершенное двоичное дерево, полное двоичное дерево и сбалансированное двоичное дерево. Идеальное двоичное дерево является наиболее оптимальным состоянием, а связный список — наихудшим вырожденным состоянием.
+- Двоичное дерево может быть представлено с помощью массива, при этом значения узлов и пустые позиции располагаются в порядке обхода по уровням, а указатели реализуются на основе отношений индексов между родительскими и дочерними узлами.
+- Обход двоичного дерева по уровням является методом поиска в ширину, который отражает способ обхода «постепенное расширение кольцами», обычно реализуемый с помощью очереди.
+- Прямой, симметричный и обратный обходы относятся к поиску в глубину, который отражает способ обхода «сначала до конца, затем возврат и продолжение», обычно реализуемый с помощью рекурсии.
+- Двоичное дерево поиска — это эффективная структура данных для поиска элементов, временная сложность операций поиска, вставки и удаления составляет $O(\log n)$. Когда двоичное дерево поиска вырождается в связный список, временная сложность всех операций ухудшается до $O(n)$.
+- AVL-дерево, также называемое сбалансированным двоичным деревом поиска, обеспечивает сохранение баланса дерева после непрерывных операций вставки и удаления узлов с помощью операций поворота.
+- Операции поворота AVL-дерева включают правый поворот, левый поворот, сначала правый затем левый поворот, сначала левый затем правый поворот. После вставки или удаления узла AVL-дерево выполняет операции поворота снизу вверх, чтобы восстановить баланс дерева.
+
+### Вопросы и ответы
+
+**В**: Для двоичного дерева с одним узлом высота дерева и глубина корневого узла равны $0$?
+
+Да, поскольку высота и глубина обычно определяются как «количество пройденных ребер».
+
+**В**: Вставка и удаление в двоичном дереве обычно выполняются набором операций. Что означает «набор операций»? Можно ли понимать это как освобождение ресурсов дочерних узлов?
+
+Возьмем в качестве примера двоичное дерево поиска: операция удаления узла требует обработки трех различных случаев, и в каждом случае необходимо выполнить несколько шагов операций с узлами.
+
+**В**: Почему обход DFS двоичного дерева имеет три порядка: прямой, симметричный и обратный, и для чего они используются?
+
+Подобно прямому и обратному обходу массива, прямой, симметричный и обратный обходы — это три метода обхода двоичного дерева, с помощью которых мы можем получить результат обхода в определенном порядке. Например, в двоичном дереве поиска, поскольку размер узлов удовлетворяет условию `значение левого дочернего узла < значение корневого узла < значение правого дочернего узла`, если мы обходим дерево в приоритете «левый $\rightarrow$ корень $\rightarrow$ правый», мы можем получить упорядоченную последовательность узлов.
+
+**В**: Операция правого поворота обрабатывает отношения между несбалансированными узлами `node`, `child`, `grand_child`. Нужно ли поддерживать связь между родительским узлом `node` и самим `node`? Не разорвется ли она после операции правого поворота?
+
+Нужно рассматривать эту проблему с точки зрения рекурсии. Операция правого поворота `right_rotate(root)` принимает корневой узел поддерева, и в конце `return child` возвращает корневой узел поддерева после поворота. Связь между корневым узлом поддерева и его родительским узлом устанавливается после возврата из функции и не входит в область поддержки операции правого поворота.
+
+**В**: В C++ функции разделены на `private` и `public`. Какие соображения здесь учитываются? Почему функции `height()` и `updateHeight()` размещены в `public` и `private` соответственно?
+
+В основном это зависит от области использования метода. Если метод используется только внутри класса, то он проектируется как `private`. Например, отдельный вызов `updateHeight()` пользователем не имеет смысла, это всего лишь один из шагов в операциях вставки и удаления. А `height()` — это доступ к высоте узла, аналогично `vector.size()`, поэтому он установлен как `public` для удобства использования.
+
+**В**: Как построить двоичное дерево поиска из набора входных данных? Важен ли выбор корневого узла?
+
+Да, метод построения дерева уже представлен в методе `build_tree()` в коде двоичного дерева поиска. Что касается выбора корневого узла, мы обычно сортируем входные данные, затем выбираем средний элемент в качестве корневого узла и рекурсивно строим левое и правое поддеревья. Это позволяет максимально обеспечить сбалансированность дерева.
+
+**В**: В Java обязательно ли использовать метод `equals()` для сравнения строк?
+
+В Java для базовых типов данных `==` используется для сравнения значений двух переменных. Для ссылочных типов эти два символа работают по-разному.
+
+- `==`: используется для сравнения того, указывают ли две переменные на один и тот же объект, т. е. одинаковы ли их позиции в памяти.
+- `equals()`: используется для сравнения значений двух объектов.
+
+Поэтому, если нужно сравнить значения, следует использовать `equals()`. Однако строки, инициализированные через `String a = "hi"; String b = "hi";`, хранятся в пуле строковых констант и указывают на один и тот же объект, поэтому также можно использовать `a == b` для сравнения содержимого двух строк.
+
+**В**: До достижения самого нижнего уровня при обходе в ширину количество узлов в очереди равно $2^h$?
+
+Да, например, для полного двоичного дерева высоты $h = 2$ общее количество узлов $n = 7$, тогда количество узлов на нижнем уровне $4 = 2^h = (n + 1) / 2$.
\ No newline at end of file
diff --git a/ru/docs/index.md b/ru/docs/index.md
new file mode 100644
index 000000000..203c2aede
--- /dev/null
+++ b/ru/docs/index.md
@@ -0,0 +1,3 @@
+
+
+# Hello 算法
\ No newline at end of file