diff --git a/ru/docs/chapter_array_and_linkedlist/array.md b/ru/docs/chapter_array_and_linkedlist/array.md index 6b5b18a38..b0812762f 100644 --- a/ru/docs/chapter_array_and_linkedlist/array.md +++ b/ru/docs/chapter_array_and_linkedlist/array.md @@ -1,8 +1,8 @@ # Массив -Массив (array) - это линейная структура данных, в которой элементы одного типа хранятся в непрерывной области памяти. Положение элемента в массиве называется его индексом (index). На рисунке ниже показаны основные понятия, связанные с массивом, и способ его хранения. +Массив (array) представляет собой линейную структуру данных, в которой элементы одного типа хранятся в непрерывной области памяти. Положение элемента в массиве называется его индексом (index). На рисунке ниже изображены основные понятия и способ хранения массивов. -![Определение массива и способ хранения](array.assets/array_definition.png) +![Определение и способ хранения массива](array.assets/array_definition.png) ## Основные операции с массивом @@ -132,13 +132,13 @@ ### Доступ к элементам -Элементы массива хранятся в непрерывной области памяти, что упрощает вычисление их адресов. Зная адрес массива в памяти (то есть адрес первого элемента) и индекс некоторого элемента, мы можем вычислить адрес этого элемента по формуле, показанной на рисунке ниже, и напрямую обратиться к нему. +Элементы массива хранятся в непрерывной области памяти, что упрощает вычисление их адресов. Зная адрес массива (адрес первого элемента) и индекс элемента, можно вычислить адрес этого элемента по формуле, показанной на рисунке ниже, и получить к нему доступ. ![Вычисление адреса элемента массива](array.assets/array_memory_location_calculation.png) -Как видно на рисунке выше, индекс первого элемента массива равен $0$ , и это кажется не слишком интуитивным, ведь естественнее было бы начинать счет с $1$ . Однако с точки зрения формулы адресации **индекс по сути является смещением относительно адреса памяти**. Смещение первого элемента равно $0$ , поэтому индекс $0$ полностью логичен. +Как видно из рисунка выше, индекс первого элемента массива равен $0$ , что может показаться неочевидным, так как отсчет с $1$ кажется более естественным. Однако с точки зрения формулы вычисления адреса **индекс является смещением адреса в памяти**. Смещение адреса первого элемента равно $0$ , поэтому и его индекс равен $0$. -Доступ к элементам массива очень эффективен: любой элемент массива можно получить за $O(1)$ времени. +Доступ к элементам массива осуществляется очень эффективно, так как позволяет за время $O(1)$ произвольно обращаться к любому элементу. ```src [file]{array}-[class]{}-[func]{random_access} @@ -146,11 +146,11 @@ ### Вставка элемента -Элементы массива в памяти расположены вплотную друг к другу, и между ними нет места для размещения новых данных. Как показано на рисунке ниже, если мы хотим вставить элемент в середину массива, то все элементы после этой позиции нужно сдвинуть на одну позицию вправо, а затем записать новое значение в освободившийся индекс. +Элементы массива в памяти расположены вплотную, между ними нет места для хранения других данных. Для вставки элемента в середину массива необходимо сдвинуть все последующие элементы на одну позицию вправо, а затем присвоить значение элементу по заданному индексу, как показано на рисунке ниже. ![Пример вставки элемента в массив](array.assets/array_insert_element.png) -Стоит отметить, что длина массива фиксирована, поэтому вставка нового элемента неизбежно приведет к потере элемента на конце массива. Решение этой проблемы мы оставим для обсуждения в разделе о «списках». +Стоит отметить, что длина массива фиксирована, поэтому вставка элемента неизбежно приведет к потере элемента на конце массива. Решение этой проблемы будет рассмотрено в разделе «Список». ```src [file]{array}-[class]{}-[func]{insert} @@ -158,11 +158,11 @@ ### Удаление элемента -Аналогично, как показано на рисунке ниже, если нужно удалить элемент по индексу $i$ , то все элементы после индекса $i$ необходимо сдвинуть на одну позицию влево. +Аналогично для удаления элемента по индексу $i$ необходимо сдвинуть все элементы после индекса $i$ на одну позицию влево, как показано на рисунке ниже. ![Пример удаления элемента из массива](array.assets/array_remove_element.png) -Обрати внимание: после удаления исходный последний элемент становится бессмысленным, поэтому специально изменять его не требуется. +Обратите внимание, что после удаления элемента последний элемент становится бессмысленным, поэтому его можно не изменять. ```src [file]{array}-[class]{}-[func]{remove} @@ -170,13 +170,13 @@ В целом операции вставки и удаления в массиве имеют следующие недостатки. -- **Высокая временная сложность**: средняя временная сложность и вставки, и удаления равна $O(n)$ , где $n$ - длина массива. -- **Потеря элементов**: поскольку длина массива неизменяема, после вставки элементы, выходящие за пределы длины массива, будут потеряны. +- **Высокая временная сложность**: средняя временная сложность операций вставки и удаления в массиве составляет $O(n)$ , где $n$ - длина массива. +- **Потеря элементов**: так как длина массива фиксирована, при вставке элемента элементы, выходящие за пределы длины массива, теряются. - **Потери памяти**: можно заранее инициализировать более длинный массив и использовать только его переднюю часть. Тогда теряемые при вставке элементы на конце не будут нести смысла, но такой подход приводит к лишнему расходу памяти. ### Обход массива -В большинстве языков программирования массив можно обходить как по индексу, так и напрямую перебирая каждый элемент: +В большинстве языков программирования массив можно обходить как по индексам, так и непосредственно по элементам. ```src [file]{array}-[class]{}-[func]{traverse} @@ -184,9 +184,9 @@ ### Поиск элемента -Чтобы найти заданный элемент в массиве, нужно пройти по массиву и на каждой итерации проверять, совпадает ли значение. Если совпадает, вернуть соответствующий индекс. +Для поиска заданного элемента в массиве необходимо обойти массив и на каждой итерации проверить, совпадает ли значение элемента с искомым. Если совпадает, вернуть соответствующий индекс. -Поскольку массив - это линейная структура данных, такая операция поиска называется линейным поиском. +Поскольку массив является линейной структурой данных, этот процесс называется линейным поиском. ```src [file]{array}-[class]{}-[func]{find} @@ -194,9 +194,9 @@ ### Расширение массива -В сложной системной среде программа не может гарантировать, что память сразу после массива доступна, поэтому безопасно расширить емкость массива невозможно. Поэтому в большинстве языков программирования **длина массива неизменяема**. +В сложных системных средах нельзя гарантировать, что ячейки памяти, расположенные после массива, являются свободными. Это делает невозможным безопасное расширение его размера. Поэтому в большинстве языков программирования **длина массива фиксирована**. -Если мы хотим расширить массив, нужно заново создать больший массив и затем по одному скопировать в него элементы исходного массива. Это операция с временной сложностью $O(n)$ , и при больших массивах она очень затратна. Соответствующий код показан ниже: +Если необходимо увеличить массив, нужно создать новый, больший массив и последовательно скопировать в него элементы исходного массива. Эта операция имеет сложность $O(n)$ и при больших массивах занимает много времени. Пример кода представлен ниже. ```src [file]{array}-[class]{}-[func]{extend} @@ -204,24 +204,24 @@ ## Преимущества и ограничения массива -Массив хранится в непрерывной области памяти, и все его элементы имеют один и тот же тип. Такой подход содержит богатую априорную информацию, которую система может использовать для оптимизации эффективности операций с этой структурой данных. +Массивы хранятся в непрерывном пространстве памяти, а их элементы имеют одинаковый тип. Этот подход содержит богатую априорную информацию, которую система может использовать для оптимизации эффективности операций с данной структурой данных. -- **Высокая пространственная эффективность**: массив выделяет для данных непрерывный блок памяти без дополнительного структурного накладного расхода. -- **Поддержка произвольного доступа**: массив позволяет обращаться к любому элементу за $O(1)$ времени. +- **Высокая эффективность использования пространства**: массивы выделяют непрерывные блоки памяти для данных без дополнительных структурных затрат. +- **Поддержка произвольного доступа**: массивы позволяют получить доступ к любому элементу за время $O(1)$. - **Локальность кэша**: при обращении к элементу массива компьютер загружает не только сам элемент, но и соседние данные, что позволяет использовать кэш для ускорения последующих операций. -Непрерывное хранение данных - это палка о двух концах, и у него есть следующие ограничения. +Непрерывное хранение в памяти – это палка о двух концах, имеющая следующие ограничения. -- **Низкая эффективность вставки и удаления**: когда элементов в массиве много, вставка и удаление требуют сдвига большого количества элементов. -- **Неизменяемая длина**: после инициализации длина массива фиксирована. Расширение массива требует копирования всех данных в новый массив, что стоит дорого. -- **Потери памяти**: если выделенный массив больше, чем реально необходимо, лишнее пространство пропадает впустую. +- **Низкая эффективность вставки и удаления**: при большом количестве элементов в массиве операции вставки и удаления требуют перемещения множества элементов. +- **Неизменная длина**: после инициализации длина массива фиксируется, а увеличение массива требует копирования всех данных в новый массив, что влечет за собой значительные затраты. +- **Потери памяти**: если размер выделенного массива превышает фактические потребности, избыточное пространство оказывается потраченным впустую. ## Типичные применения массива -Массив - это базовая и очень распространенная структура данных. Он часто используется как в различных алгоритмах, так и при реализации более сложных структур данных. +Массивы – это базовая и распространенная структура данных, часто используемая в различных алгоритмах и для реализации сложных структур данных. - **Произвольный доступ**: если мы хотим случайным образом выбирать некоторые образцы, можно сохранить их в массиве и сгенерировать случайную последовательность индексов для выборки. -- **Сортировка и поиск**: массив - самая распространенная структура данных для алгоритмов сортировки и поиска. Быстрая сортировка, сортировка слиянием, двоичный поиск и многие другие алгоритмы в основном работают именно с массивами. -- **Таблица поиска**: когда нужно быстро находить элемент или его соответствие, массив можно использовать как таблицу поиска. Например, если мы хотим реализовать отображение символов в коды ASCII, можно использовать значение ASCII как индекс, а соответствующий элемент хранить по этой позиции массива. -- **Машинное обучение**: в нейронных сетях широко используются операции линейной алгебры над векторами, матрицами и тензорами, и все эти данные строятся в форме массивов. Массив - самая часто используемая структура данных в программировании нейросетей. -- **Реализация структур данных**: массивы можно использовать для реализации стеков, очередей, хеш-таблиц, куч, графов и других структур данных. Например, матрица смежности графа по сути является двумерным массивом. +- **Сортировка и поиск**: массивы являются наиболее часто используемой структурой данных для алгоритмов сортировки и поиска. Быстрая сортировка, сортировка слиянием, двоичный поиск и другие алгоритмы в основном работают с массивами. +- **Таблица поиска**: когда необходимо быстро найти элемент или его соответствие, можно использовать массив в качестве таблицы поиска. Например, для реализации отображения символов в ASCII-коды можно использовать значение ASCII-кода символа в качестве индекса, а соответствующий элемент хранить в соответствующем месте массива. +- **Машинное обучение**: в нейронных сетях широко используются операции линейной алгебры между векторами, матрицами и тензорами, которые реализуются в виде массивов. Массивы являются наиболее часто используемой структурой данных в программировании нейронных сетей. +- **Реализация структур данных**: массивы могут использоваться для реализации стека, очереди, хеш-таблицы, кучи, графа и других структур данных. Например, представление графа в виде матрицы смежности фактически является двумерным массивом. diff --git a/ru/docs/chapter_array_and_linkedlist/linked_list.md b/ru/docs/chapter_array_and_linkedlist/linked_list.md index 161acba56..19b44b0d1 100644 --- a/ru/docs/chapter_array_and_linkedlist/linked_list.md +++ b/ru/docs/chapter_array_and_linkedlist/linked_list.md @@ -6,12 +6,12 @@ Конструкция связного списка позволяет хранить отдельные узлы в разных местах памяти, и их адреса вовсе не обязаны быть последовательными. -![Определение связного списка и способ хранения](linked_list.assets/linkedlist_definition.png) +![Определение и способ хранения связного списка](linked_list.assets/linkedlist_definition.png) Как видно на рисунке выше, базовой единицей связного списка является объект узел (node). Каждый узел содержит две части данных: значение узла и ссылку на следующий узел. - Первый узел связного списка называется головным узлом, а последний - хвостовым узлом. -- Хвостовой узел указывает на пустое значение, что в Java, C++ и Python обозначается как `null` , `nullptr` и `None` соответственно. +- Хвостовой узел указывает на пустое значение, которое в Java, C++ и Python обозначается как `null` , `nullptr` и `None` соответственно. - В языках, поддерживающих указатели, таких как C, C++, Go и Rust, упомянутую выше ссылку следует заменить на указатель. Как показано в коде ниже, узел связного списка `ListNode` хранит не только значение, но и дополнительную ссылку (указатель). Поэтому **при одинаковом объеме данных связный список занимает больше памяти, чем массив**. @@ -468,8 +468,8 @@ | | Массив | Связный список | | ------------- | ---------------------------------------------- | ----------------------- | | Способ хранения | Непрерывная область памяти | Разрозненная область памяти | -| Расширение емкости | Длина неизменяема | Гибкое расширение | -| Эффективность памяти | Элементы занимают меньше памяти, но возможны потери пространства | Элементы занимают больше памяти | +| Расширение емкости | Длина фиксирована | Возможность гибкого расширения | +| Эффективность памяти | Элементы занимают меньше памяти, но могут расходовать пространство впустую | Элементы занимают больше памяти | | Доступ к элементу | $O(1)$ | $O(n)$ | | Добавление элемента | $O(n)$ | $O(1)$ | | Удаление элемента | $O(n)$ | $O(1)$ | @@ -479,7 +479,7 @@ Как показано на рисунке ниже, существует три распространенных типа связных списков. - **Односвязный список**: это обычный связный список, рассмотренный выше. Узел односвязного списка содержит значение и ссылку на следующий узел. Первый узел называется головным, последний - хвостовым, и хвост указывает на `None` . -- **Циклический список**: если заставить хвостовой узел односвязного списка указывать на головной, то есть соединить хвост с головой, получится циклический список. В циклическом списке любой узел можно рассматривать как головной. +- **Кольцевой (циклический) список**: если заставить хвостовой узел односвязного списка указывать на головной, то есть соединить хвост с головой, получится циклический список. В циклическом списке любой узел можно рассматривать как головной. - **Двусвязный список**: по сравнению с односвязным списком двусвязный хранит ссылки в двух направлениях. Определение узла двусвязного списка включает как ссылку на следующий узел, так и ссылку на предыдущий узел. По сравнению с односвязным списком двусвязный более гибок и позволяет обходить список в обе стороны, но за это приходится платить дополнительной памятью. === "Python" @@ -680,15 +680,15 @@ end ``` -![Распространенные типы связных списков](linked_list.assets/linkedlist_common_types.png) +![Виды связных списков](linked_list.assets/linkedlist_common_types.png) ## Типичные применения связных списков Односвязные списки обычно используются для реализации стеков, очередей, хеш-таблиц и графов. -- **Стеки и очереди**: если операции вставки и удаления выполняются на одном конце связного списка, он проявляет свойства LIFO, соответствующие стеку. Если вставка происходит на одном конце, а удаление на другом, он проявляет свойства FIFO, соответствующие очереди. -- **Хеш-таблицы**: метод цепочек - один из основных способов разрешения коллизий в хеш-таблицах. В этом подходе все конфликтующие элементы помещаются в связный список. -- **Графы**: список смежности - это распространенный способ представления графа, при котором каждой вершине графа соответствует связный список, а каждый элемент этого списка представляет другую вершину, соединенную с данной. +- **Стек и очередь**: если операции вставки и удаления выполняются на одном конце связного списка, он проявляет свойства LIFO, соответствующие стеку. Если вставка происходит на одном конце, а удаление на другом, он проявляет свойства FIFO, соответствующие очереди. +- **Хеш-таблица**: метод цепочек - один из основных способов разрешения коллизий в хеш-таблицах. В этом подходе все конфликтующие элементы помещаются в связный список. +- **Граф**: список смежности - это распространенный способ представления графа, при котором каждой вершине графа соответствует связный список, а каждый элемент этого списка представляет другую вершину, соединенную с данной. Двусвязные списки обычно используются там, где нужен быстрый доступ как к предыдущему, так и к следующему элементу. @@ -698,5 +698,5 @@ Циклические списки часто применяются в сценариях, требующих циклических операций, например при планировании ресурсов в операционной системе. -- **Алгоритм циклического распределения кванта времени**: в операционных системах round-robin scheduling - это распространенный алгоритм планирования CPU, который циклически обходит набор процессов. Каждому процессу выделяется квант времени, и когда он исчерпан, CPU переключается на следующий процесс. Такую циклическую операцию удобно реализовать с помощью кольцевого списка. -- **Буферы данных**: в некоторых реализациях буферов данных также могут использоваться циклические списки. Например, в аудио- и видеоплеерах поток данных может делиться на несколько буферных блоков и помещаться в кольцевой список для обеспечения непрерывного воспроизведения. +- **Алгоритм циклического планирования**: в операционных системах алгоритм циклического планирования - это распространенный алгоритм планирования CPU, который циклически обходит набор процессов. Каждому процессу выделяется квант времени, и когда он исчерпан, CPU переключается на следующий процесс. Такую циклическую операцию удобно реализовать с помощью кольцевого списка. +- **Буфер данных**: в некоторых реализациях буферов данных также могут использоваться циклические списки. Например, в аудио- и видеоплеерах поток данных может делиться на несколько буферных блоков и помещаться в кольцевой список для обеспечения непрерывного воспроизведения. diff --git a/ru/docs/chapter_array_and_linkedlist/list.md b/ru/docs/chapter_array_and_linkedlist/list.md index 637f13bd0..3ee1dd3ce 100644 --- a/ru/docs/chapter_array_and_linkedlist/list.md +++ b/ru/docs/chapter_array_and_linkedlist/list.md @@ -2,8 +2,8 @@ Список (list) - это абстрактное понятие структуры данных, обозначающее упорядоченную коллекцию элементов, которая поддерживает доступ к элементам, их изменение, добавление, удаление и обход, не требуя от пользователя учитывать ограничения по емкости. Список может быть реализован как на основе связного списка, так и на основе массива. -- Связный список естественным образом можно рассматривать как список: он поддерживает операции добавления, удаления, поиска и изменения элементов и может гибко расширяться динамически. -- Массив тоже поддерживает операции добавления, удаления, поиска и изменения элементов, но из-за неизменяемости длины его можно считать лишь списком с ограниченной длиной. +- Связные списки естественным образом можно рассматривать как списки, поддерживающие операции добавления, удаления, поиска и изменения элементов, а также динамическое расширение. +- Массивы также поддерживают операции добавления, удаления, поиска и изменения элементов, но из-за фиксированной длины их можно рассматривать только как списки с ограничением по длине. Когда список реализуется с помощью массива, **неизменяемость длины снижает его практическую полезность**. Причина в том, что мы обычно не можем заранее точно знать, сколько данных нужно хранить, а значит, трудно выбрать подходящую длину списка. Если длина слишком мала, она может не покрыть реальные потребности. Если слишком велика, будет зря расходоваться память. @@ -15,7 +15,7 @@ ### Инициализация списка -Обычно используются два способа инициализации: без начальных значений и с начальными значениями: +Обычно используются два метода инициализации: без начальных значений и с заданием начальных значений: === "Python" @@ -284,7 +284,7 @@ ### Вставка и удаление элементов -В отличие от массива список позволяет свободно добавлять и удалять элементы. Добавление элемента в конец списка имеет временную сложность $O(1)$ , но операции вставки и удаления по-прежнему имеют ту же эффективность, что и у массива, то есть $O(n)$ . +В отличие от массива в списке можно свободно добавлять и удалять элементы. Сложность добавления элемента в конец списка составляет $O(1)$ , но вставка и удаление элементов имеют ту же сложность $O(n)$, что и в массиве. === "Python" @@ -538,7 +538,7 @@ ### Обход списка -Как и массив, список можно обходить как по индексам, так и напрямую по элементам. +Как и массив, список можно обходить по индексу или напрямую по элементам. === "Python" @@ -739,7 +739,7 @@ ### Конкатенация списков -Создав новый список `nums1` , мы можем присоединить его к хвосту исходного списка. +Создав новый список `nums1` , мы можем присоединить его в конец исходного списка. === "Python" @@ -954,7 +954,7 @@ - **Начальная емкость**: выбрать разумную начальную емкость внутреннего массива. В этом примере мы берем 10. - **Учет количества элементов**: объявить переменную `size` , которая будет хранить текущее число элементов в списке и обновляться в реальном времени при вставке и удалении элементов. С помощью этой переменной можно определять конец списка и понимать, требуется ли расширение. -- **Механизм расширения**: если при вставке элементов емкость списка исчерпана, нужно выполнить расширение. Для этого сначала создается больший массив с учетом коэффициента расширения, а затем все элементы текущего массива по порядку переносятся в новый. В этом примере мы считаем, что каждый раз массив расширяется в 2 раза. +- **Механизм расширения**: если при вставке элемента емкость списка исчерпана, нужно выполнить расширение. Для этого сначала создается больший массив с учетом коэффициента расширения, а затем все элементы текущего массива по порядку переносятся в новый. В этом примере мы считаем, что каждый раз массив расширяется в 2 раза. ```src [file]{my_list}-[class]{my_list}-[func]{} diff --git a/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md index 64da65142..1850354f4 100644 --- a/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md +++ b/ru/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -21,9 +21,9 @@ Компьютерную систему хранения можно представить в виде пирамиды, показанной на рисунке ниже. Чем ближе устройство хранения к вершине пирамиды, тем оно быстрее, тем меньше его емкость и тем выше его стоимость. Такая многоуровневая конструкция возникла не случайно, а стала результатом тщательных инженерных компромиссов. - **Жесткий диск трудно заменить оперативной памятью**. Во-первых, данные в оперативной памяти исчезают после отключения питания, поэтому она не подходит для долговременного хранения. Во-вторых, память стоит в разы дороже жесткого диска, что мешает ее широкому применению. -- **Кэш не может одновременно быть и очень большим, и очень быстрым**. По мере роста емкости кэшей L1, L2 и L3 их физический размер увеличивается, расстояние до ядра CPU становится больше, время передачи данных растет, а задержка доступа к элементам увеличивается. При текущем уровне технологий многоуровневая структура кэша является лучшим балансом между емкостью, скоростью и стоимостью. +- **Большая емкость и высокая скорость кэша трудно совместимы**. По мере роста емкости кэшей L1, L2 и L3 их физический размер увеличивается, расстояние до ядра CPU становится больше, время передачи данных растет, а задержка доступа к элементам увеличивается. При текущем уровне технологий многоуровневая структура кэша является лучшим балансом между емкостью, скоростью и стоимостью. -![Система хранения данных компьютера](ram_and_cache.assets/storage_pyramid.png) +![Система хранения данных в компьютере](ram_and_cache.assets/storage_pyramid.png) !!! tip @@ -51,17 +51,17 @@ Чтобы добиться как можно большей эффективности, кэш использует следующие механизмы загрузки данных. -- **Строки кэша**: кэш хранит и загружает данные не по одному байту, а строками кэша. По сравнению с передачей по байтам это гораздо эффективнее. -- **Механизм предвыборки**: процессор старается предсказать шаблон доступа к данным (например последовательный доступ, доступ с фиксированным шагом и т.д.) и на основе этого шаблона заранее загружает данные в кэш, повышая вероятность попадания. -- **Пространственная локальность**: если к некоторым данным уже обратились, то велика вероятность, что в ближайшее время понадобятся и соседние данные. Поэтому, загружая некоторые данные, кэш часто подгружает и окружающие их данные. -- **Временная локальность**: если к данным уже обратились, то высока вероятность, что к ним снова обратятся в ближайшем будущем. Кэш использует это свойство, сохраняя недавно использованные данные. +- **Кэш-линия**: кэш хранит и загружает данные не по одному байту, а целыми кэш-линиями. По сравнению с передачей по байтам передача кэш-линий более эффективна. +- **Механизм предвыборки**: процессор пытается предсказать шаблоны доступа к данным (например, последовательный доступ, доступ с фиксированным шагом и т. д.) и заранее загружает данные в кэш в соответствии с этими шаблонами, чтобы повысить коэффициент попадания. +- **Пространственная локальность**: если к данным был осуществлен доступ, то, вероятно, в ближайшее время будет осуществлен доступ и к данным, находящимся поблизости. Поэтому при загрузке данных кэш также загружает данные, находящиеся рядом, чтобы повысить коэффициент попадания. +- **Временная локальность**: если к данным был осуществлен доступ, то в ближайшем будущем, вероятно, к ним будет осуществлен повторный доступ. Кэш использует этот принцип, сохраняя недавно запрашиваемые данные, чтобы повысить коэффициент попадания. На практике **массивы и связные списки по-разному используют кэш**, и это проявляется в нескольких аспектах. - **Занимаемое пространство**: элементы связного списка занимают больше места, чем элементы массива, поэтому в кэше помещается меньше полезных данных. -- **Строки кэша**: данные списка разбросаны по памяти, а кэш загружает данные строками, поэтому доля бесполезно загружаемых данных оказывается выше. -- **Механизм предвыборки**: шаблон доступа к данным у массивов более предсказуем, чем у списков, то есть системе легче угадать, какие данные понадобятся следующими. -- **Пространственная локальность**: массив хранится в компактной области памяти, поэтому данные рядом с уже загруженными с большей вероятностью скоро будут использованы. +- **Кэш-линии**: данные связного списка разбросаны по памяти, а кэш загружает данные строками, поэтому доля бесполезно загружаемых данных оказывается выше. +- **Механизм предвыборки**: шаблоны доступа к данным у массивов более предсказуемы, чем у связных списков, то есть системе легче угадать, какие данные понадобятся следующими. +- **Пространственная локальность**: массивы хранятся в компактной области памяти, поэтому данные рядом с уже загруженными с большей вероятностью скоро будут использованы. В целом **массивы имеют более высокий коэффициент попадания в кэш, поэтому по эффективности операций они обычно превосходят связные списки**. Именно поэтому при решении алгоритмических задач структуры данных на основе массивов часто оказываются предпочтительнее. diff --git a/ru/docs/chapter_searching/binary_search.md b/ru/docs/chapter_searching/binary_search.md index 9bc9756b2..2cbb4e4fd 100644 --- a/ru/docs/chapter_searching/binary_search.md +++ b/ru/docs/chapter_searching/binary_search.md @@ -1,6 +1,6 @@ # Двоичный поиск -Двоичный поиск (binary search) - это эффективный алгоритм поиска, основанный на стратегии «разделяй и властвуй». Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или пока интервал поиска не опустеет. +Двоичный поиск (binary search) - это эффективный алгоритм поиска, основанный на стратегии «разделяй и властвуй». Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или область поиска не станет пустой. !!! question diff --git a/ru/docs/chapter_searching/binary_search_edge.md b/ru/docs/chapter_searching/binary_search_edge.md index 543c88354..80f57202f 100644 --- a/ru/docs/chapter_searching/binary_search_edge.md +++ b/ru/docs/chapter_searching/binary_search_edge.md @@ -31,7 +31,7 @@ Как показано на рисунке ниже, после завершения поиска указатель $i$ указывает на самый левый `target + 1` (если он существует), а указатель $j$ указывает на самый правый `target` , **поэтому достаточно вернуть $j$**. -![Преобразование поиска правой границы в поиск левой](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) +![Преобразование поиска правой границы в поиск левой границы](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) Обратите внимание: функция возвращает точку вставки $i$ , поэтому из нее нужно вычесть $1$ , чтобы получить $j$ : diff --git a/ru/docs/chapter_searching/binary_search_insertion.md b/ru/docs/chapter_searching/binary_search_insertion.md index a31e7f879..839f5e4b1 100644 --- a/ru/docs/chapter_searching/binary_search_insertion.md +++ b/ru/docs/chapter_searching/binary_search_insertion.md @@ -8,7 +8,7 @@ Дан упорядоченный массив `nums` длины $n$ и элемент `target` , причем в массиве нет повторяющихся элементов. Нужно вставить `target` в массив `nums` , сохранив порядок. Если элемент `target` уже присутствует в массиве, вставьте его слева от него. Верните индекс, который будет иметь `target` после вставки. Пример показан на рисунке ниже. -![Пример данных для точки вставки](binary_search_insertion.assets/binary_search_insertion_example.png) +![Пример данных для двоичного поиска точки вставки](binary_search_insertion.assets/binary_search_insertion_example.png) Если мы хотим переиспользовать код двоичного поиска из предыдущего раздела, нужно ответить на два вопроса. @@ -39,7 +39,7 @@ 1. Выполнить двоичный поиск и получить индекс любого элемента `target` , обозначив его как $k$ . 2. Начиная с индекса $k$ , линейно двигаться влево и вернуть результат, когда будет найден самый левый `target` . -![Линейный поиск точки вставки среди повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_naive.png) +![Линейный поиск точки вставки для повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_naive.png) Этот метод применим на практике, однако в нем есть линейный поиск, поэтому его временная сложность равна $O(n)$ . Когда в массиве имеется много повторяющихся `target` , такой подход работает неэффективно. @@ -51,7 +51,7 @@ После завершения цикла указатель $i$ будет указывать на самый левый `target` , а указатель $j$ - на самый правый элемент, меньший `target` , **поэтому индекс $i$ и является точкой вставки**. === "<1>" - ![Шаги поиска точки вставки для повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_step1.png) + ![Этапы двоичного поиска точки вставки для повторяющихся элементов](binary_search_insertion.assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) diff --git a/ru/docs/chapter_searching/replace_linear_by_hashing.md b/ru/docs/chapter_searching/replace_linear_by_hashing.md index eef69af9c..2271157b3 100644 --- a/ru/docs/chapter_searching/replace_linear_by_hashing.md +++ b/ru/docs/chapter_searching/replace_linear_by_hashing.md @@ -10,7 +10,7 @@ Рассмотрим прямой перебор всех возможных комбинаций. Как показано на рисунке ниже, мы запускаем два вложенных цикла и на каждом шаге проверяем, равна ли сумма двух целых чисел `target`. Если да, то возвращаем их индексы. -![Линейный поиск для задачи о двух суммах](replace_linear_by_hashing.assets/two_sum_brute_force.png) +![Линейный поиск для нахождения двух чисел, сумма которых равна заданному значению](replace_linear_by_hashing.assets/two_sum_brute_force.png) Код приведен ниже: @@ -28,7 +28,7 @@ 2. Добавить в хеш-таблицу пару из ключа `nums[i]` и индекса `i` . === "<1>" - ![Вспомогательная хеш-таблица для задачи о двух суммах](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + ![Использование вспомогательной хеш-таблицы для нахождения двух чисел, сумма которых равна заданному значению](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) === "<2>" ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) diff --git a/ru/docs/chapter_searching/searching_algorithm_revisited.md b/ru/docs/chapter_searching/searching_algorithm_revisited.md index 5a94b33c2..076be94ba 100644 --- a/ru/docs/chapter_searching/searching_algorithm_revisited.md +++ b/ru/docs/chapter_searching/searching_algorithm_revisited.md @@ -13,8 +13,8 @@ Полный перебор заключается в том, что мы обходим каждый элемент структуры данных, чтобы найти целевой элемент. -- «Линейный поиск» применяется к линейным структурам данных, таким как массивы и списки. Он начинается с одного конца структуры данных и последовательно проверяет элементы, пока не найдет целевой элемент или пока не достигнет другого конца структуры данных. -- «Обход в ширину» и «обход в глубину» - это две стратегии обхода графов и деревьев. Обход в ширину стартует из начального узла и исследует все узлы текущего уровня, прежде чем переходить к следующему. Обход в глубину стартует из начального узла, проходит один путь до конца, затем возвращается назад и пробует другие пути, пока не будет полностью пройдена вся структура данных. +- Линейный поиск применяется к линейным структурам данных, таким как массивы и связные списки. Он начинается с одного конца структуры данных и последовательно проверяет элементы, пока не найдет целевой элемент или пока не достигнет другого конца структуры данных. +- Обход в ширину и обход в глубину - это две стратегии обхода графов и деревьев. Обход в ширину стартует из начального узла и исследует все узлы текущего уровня, прежде чем переходить к следующему. Обход в глубину стартует из начального узла, проходит один путь до конца, затем возвращается назад и пробует другие пути, пока не будет полностью пройдена вся структура данных. Преимущество полного перебора состоит в его простоте и универсальности, **поскольку он не требует предварительной обработки данных и использования дополнительных структур данных**. @@ -24,9 +24,9 @@ Адаптивный поиск использует специфические свойства данных (например, упорядоченность), чтобы оптимизировать процесс поиска и тем самым эффективнее находить целевой элемент. -- «Двоичный поиск» использует упорядоченность данных для эффективного поиска и применим только к массивам. -- «Хеш-поиск» использует хеш-таблицу для построения отображения между поисковыми данными и целевыми данными, благодаря чему запросы выполняются эффективно. -- «Поиск в дереве» ведется в конкретной древовидной структуре (например, в двоичном дереве поиска) и позволяет быстро отсекать узлы на основе сравнения значений, чтобы найти цель. +- Двоичный поиск использует упорядоченность данных для эффективного поиска и применим только к массивам. +- Хеш-поиск использует хеш-таблицу, связывая поисковые данные с целевыми данными в виде пар «ключ — значение», благодаря чему запросы выполняются эффективно. +- Поиск в дереве ведется в конкретной древовидной структуре (например, в двоичном дереве поиска) и позволяет быстро отсекать узлы на основе сравнения значений, чтобы найти цель. Преимущество этих алгоритмов заключается в высокой эффективности: **их временная сложность может достигать $O(\log n)$ и даже $O(1)$** .