mirror of
https://github.com/krahets/hello-algo.git
synced 2026-10-21 05:25:19 +08:00
docs(ru): polish arrays, linked lists and searching explanations (#1895)
* placeholder * 0413更新 * 0413删除comments * 第四章第一节进行一对一修改,发现几个核心问题,后续不再重复叙述。具体问题参见具体说明 因为该版本不会被用在最后的校对输出中,后续不再复制纸质版原文,只标注出现不一致的部分 1.非程序部分大量出现文本和纸质版不一致,已全部修改,AI翻译版本没有纸质版自然 2.程序部分python部分就和纸质版不一致,但是其他程序和AI翻译的python一致,建议让AI先搬运纸质版python的翻译,再加工其他的部分,翻译水平尚可。 3.程序部分AI翻译版本不稳定,大部分使用动宾结构表达,少部分使用动名词结构,出现少量语法错误。纸质版大多使用动名词结构 4.相对中文在线版格式未出现问题,该部分表现较好 * 第四章后续章节未精确修改和俄语版不一致的部分,因为从第一节可以看出,AI基本上都是自己翻的。 在这里标出了所有应该修改的部分 除了第一节以外新增的问题: 1.程序部分出现语法错误 2.单复数问题在程序和标题中出现多次,正文中更是屡见不鲜 3.程序部分,python错了之后,后续的程序全部参照python一起错,建议统一修改,还是参照python但是一起修改掉 * 第十章第一节整体检查 1.正文部分和纸质版的重叠仍然基本没有,都是AI翻译 2.程序部分除了python和纸质版翻译不一致以外,出现较为严重的术语错误 3.接下来的四节内容我将不再展示正文部分的不一致,以上已经可以充分说明了 * 第十章后续章节梳理(该部分仍然绝大部分的内容都和纸质版完全不一致,从措辞到表达方法都不一致,包括正文也包括程序) 考虑到还需要进行AI设计,在此没有重复标注 其他问题: 1.正文中有一些细节,比如说引号的删除、大小写的确认、 2.程序上也出现了术语错误和语法错误 * docs(ru): refine wording after sentence-level Chinese comparison --------- Co-authored-by: Veronica <[email protected]> Co-authored-by: krahets <[email protected]>
This commit is contained in:
co-authored by
Veronica
krahets
parent
f81f725c30
commit
817a24d7ef
@@ -1,8 +1,8 @@
|
||||
# Массив
|
||||
|
||||
<u>Массив (array)</u> - это линейная структура данных, в которой элементы одного типа хранятся в непрерывной области памяти. Положение элемента в массиве называется его <u>индексом (index)</u>. На рисунке ниже показаны основные понятия, связанные с массивом, и способ его хранения.
|
||||
<u>Массив (array)</u> представляет собой линейную структуру данных, в которой элементы одного типа хранятся в непрерывной области памяти. Положение элемента в массиве называется его <u>индексом (index)</u>. На рисунке ниже изображены основные понятия и способ хранения массивов.
|
||||
|
||||

|
||||

|
||||
|
||||
## Основные операции с массивом
|
||||
|
||||
@@ -132,13 +132,13 @@
|
||||
|
||||
### Доступ к элементам
|
||||
|
||||
Элементы массива хранятся в непрерывной области памяти, что упрощает вычисление их адресов. Зная адрес массива в памяти (то есть адрес первого элемента) и индекс некоторого элемента, мы можем вычислить адрес этого элемента по формуле, показанной на рисунке ниже, и напрямую обратиться к нему.
|
||||
Элементы массива хранятся в непрерывной области памяти, что упрощает вычисление их адресов. Зная адрес массива (адрес первого элемента) и индекс элемента, можно вычислить адрес этого элемента по формуле, показанной на рисунке ниже, и получить к нему доступ.
|
||||
|
||||

|
||||
|
||||
Как видно на рисунке выше, индекс первого элемента массива равен $0$ , и это кажется не слишком интуитивным, ведь естественнее было бы начинать счет с $1$ . Однако с точки зрения формулы адресации **индекс по сути является смещением относительно адреса памяти**. Смещение первого элемента равно $0$ , поэтому индекс $0$ полностью логичен.
|
||||
Как видно из рисунка выше, индекс первого элемента массива равен $0$ , что может показаться неочевидным, так как отсчет с $1$ кажется более естественным. Однако с точки зрения формулы вычисления адреса **индекс является смещением адреса в памяти**. Смещение адреса первого элемента равно $0$ , поэтому и его индекс равен $0$.
|
||||
|
||||
Доступ к элементам массива очень эффективен: любой элемент массива можно получить за $O(1)$ времени.
|
||||
Доступ к элементам массива осуществляется очень эффективно, так как позволяет за время $O(1)$ произвольно обращаться к любому элементу.
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{random_access}
|
||||
@@ -146,11 +146,11 @@
|
||||
|
||||
### Вставка элемента
|
||||
|
||||
Элементы массива в памяти расположены вплотную друг к другу, и между ними нет места для размещения новых данных. Как показано на рисунке ниже, если мы хотим вставить элемент в середину массива, то все элементы после этой позиции нужно сдвинуть на одну позицию вправо, а затем записать новое значение в освободившийся индекс.
|
||||
Элементы массива в памяти расположены вплотную, между ними нет места для хранения других данных. Для вставки элемента в середину массива необходимо сдвинуть все последующие элементы на одну позицию вправо, а затем присвоить значение элементу по заданному индексу, как показано на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Стоит отметить, что длина массива фиксирована, поэтому вставка нового элемента неизбежно приведет к потере элемента на конце массива. Решение этой проблемы мы оставим для обсуждения в разделе о «списках».
|
||||
Стоит отметить, что длина массива фиксирована, поэтому вставка элемента неизбежно приведет к потере элемента на конце массива. Решение этой проблемы будет рассмотрено в разделе «Список».
|
||||
|
||||
```src
|
||||
[file]{array}-[class]{}-[func]{insert}
|
||||
@@ -158,11 +158,11 @@
|
||||
|
||||
### Удаление элемента
|
||||
|
||||
Аналогично, как показано на рисунке ниже, если нужно удалить элемент по индексу $i$ , то все элементы после индекса $i$ необходимо сдвинуть на одну позицию влево.
|
||||
Аналогично для удаления элемента по индексу $i$ необходимо сдвинуть все элементы после индекса $i$ на одну позицию влево, как показано на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Обрати внимание: после удаления исходный последний элемент становится бессмысленным, поэтому специально изменять его не требуется.
|
||||
Обратите внимание, что после удаления элемента последний элемент становится бессмысленным, поэтому его можно не изменять.
|
||||
|
||||
```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-кода символа в качестве индекса, а соответствующий элемент хранить в соответствующем месте массива.
|
||||
- **Машинное обучение**: в нейронных сетях широко используются операции линейной алгебры между векторами, матрицами и тензорами, которые реализуются в виде массивов. Массивы являются наиболее часто используемой структурой данных в программировании нейронных сетей.
|
||||
- **Реализация структур данных**: массивы могут использоваться для реализации стека, очереди, хеш-таблицы, кучи, графа и других структур данных. Например, представление графа в виде матрицы смежности фактически является двумерным массивом.
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
Конструкция связного списка позволяет хранить отдельные узлы в разных местах памяти, и их адреса вовсе не обязаны быть последовательными.
|
||||
|
||||

|
||||

|
||||
|
||||
Как видно на рисунке выше, базовой единицей связного списка является объект <u>узел (node)</u>. Каждый узел содержит две части данных: значение узла и ссылку на следующий узел.
|
||||
|
||||
- Первый узел связного списка называется головным узлом, а последний - хвостовым узлом.
|
||||
- Хвостовой узел указывает на пустое значение, что в 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
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
## Типичные применения связных списков
|
||||
|
||||
Односвязные списки обычно используются для реализации стеков, очередей, хеш-таблиц и графов.
|
||||
|
||||
- **Стеки и очереди**: если операции вставки и удаления выполняются на одном конце связного списка, он проявляет свойства LIFO, соответствующие стеку. Если вставка происходит на одном конце, а удаление на другом, он проявляет свойства FIFO, соответствующие очереди.
|
||||
- **Хеш-таблицы**: метод цепочек - один из основных способов разрешения коллизий в хеш-таблицах. В этом подходе все конфликтующие элементы помещаются в связный список.
|
||||
- **Графы**: список смежности - это распространенный способ представления графа, при котором каждой вершине графа соответствует связный список, а каждый элемент этого списка представляет другую вершину, соединенную с данной.
|
||||
- **Стек и очередь**: если операции вставки и удаления выполняются на одном конце связного списка, он проявляет свойства LIFO, соответствующие стеку. Если вставка происходит на одном конце, а удаление на другом, он проявляет свойства FIFO, соответствующие очереди.
|
||||
- **Хеш-таблица**: метод цепочек - один из основных способов разрешения коллизий в хеш-таблицах. В этом подходе все конфликтующие элементы помещаются в связный список.
|
||||
- **Граф**: список смежности - это распространенный способ представления графа, при котором каждой вершине графа соответствует связный список, а каждый элемент этого списка представляет другую вершину, соединенную с данной.
|
||||
|
||||
Двусвязные списки обычно используются там, где нужен быстрый доступ как к предыдущему, так и к следующему элементу.
|
||||
|
||||
@@ -698,5 +698,5 @@
|
||||
|
||||
Циклические списки часто применяются в сценариях, требующих циклических операций, например при планировании ресурсов в операционной системе.
|
||||
|
||||
- **Алгоритм циклического распределения кванта времени**: в операционных системах round-robin scheduling - это распространенный алгоритм планирования CPU, который циклически обходит набор процессов. Каждому процессу выделяется квант времени, и когда он исчерпан, CPU переключается на следующий процесс. Такую циклическую операцию удобно реализовать с помощью кольцевого списка.
|
||||
- **Буферы данных**: в некоторых реализациях буферов данных также могут использоваться циклические списки. Например, в аудио- и видеоплеерах поток данных может делиться на несколько буферных блоков и помещаться в кольцевой список для обеспечения непрерывного воспроизведения.
|
||||
- **Алгоритм циклического планирования**: в операционных системах алгоритм циклического планирования - это распространенный алгоритм планирования CPU, который циклически обходит набор процессов. Каждому процессу выделяется квант времени, и когда он исчерпан, CPU переключается на следующий процесс. Такую циклическую операцию удобно реализовать с помощью кольцевого списка.
|
||||
- **Буфер данных**: в некоторых реализациях буферов данных также могут использоваться циклические списки. Например, в аудио- и видеоплеерах поток данных может делиться на несколько буферных блоков и помещаться в кольцевой список для обеспечения непрерывного воспроизведения.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<u>Список (list)</u> - это абстрактное понятие структуры данных, обозначающее упорядоченную коллекцию элементов, которая поддерживает доступ к элементам, их изменение, добавление, удаление и обход, не требуя от пользователя учитывать ограничения по емкости. Список может быть реализован как на основе связного списка, так и на основе массива.
|
||||
|
||||
- Связный список естественным образом можно рассматривать как список: он поддерживает операции добавления, удаления, поиска и изменения элементов и может гибко расширяться динамически.
|
||||
- Массив тоже поддерживает операции добавления, удаления, поиска и изменения элементов, но из-за неизменяемости длины его можно считать лишь списком с ограниченной длиной.
|
||||
- Связные списки естественным образом можно рассматривать как списки, поддерживающие операции добавления, удаления, поиска и изменения элементов, а также динамическое расширение.
|
||||
- Массивы также поддерживают операции добавления, удаления, поиска и изменения элементов, но из-за фиксированной длины их можно рассматривать только как списки с ограничением по длине.
|
||||
|
||||
Когда список реализуется с помощью массива, **неизменяемость длины снижает его практическую полезность**. Причина в том, что мы обычно не можем заранее точно знать, сколько данных нужно хранить, а значит, трудно выбрать подходящую длину списка. Если длина слишком мала, она может не покрыть реальные потребности. Если слишком велика, будет зря расходоваться память.
|
||||
|
||||
@@ -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]{}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
Компьютерную систему хранения можно представить в виде пирамиды, показанной на рисунке ниже. Чем ближе устройство хранения к вершине пирамиды, тем оно быстрее, тем меньше его емкость и тем выше его стоимость. Такая многоуровневая конструкция возникла не случайно, а стала результатом тщательных инженерных компромиссов.
|
||||
|
||||
- **Жесткий диск трудно заменить оперативной памятью**. Во-первых, данные в оперативной памяти исчезают после отключения питания, поэтому она не подходит для долговременного хранения. Во-вторых, память стоит в разы дороже жесткого диска, что мешает ее широкому применению.
|
||||
- **Кэш не может одновременно быть и очень большим, и очень быстрым**. По мере роста емкости кэшей L1, L2 и L3 их физический размер увеличивается, расстояние до ядра CPU становится больше, время передачи данных растет, а задержка доступа к элементам увеличивается. При текущем уровне технологий многоуровневая структура кэша является лучшим балансом между емкостью, скоростью и стоимостью.
|
||||
- **Большая емкость и высокая скорость кэша трудно совместимы**. По мере роста емкости кэшей L1, L2 и L3 их физический размер увеличивается, расстояние до ядра CPU становится больше, время передачи данных растет, а задержка доступа к элементам увеличивается. При текущем уровне технологий многоуровневая структура кэша является лучшим балансом между емкостью, скоростью и стоимостью.
|
||||
|
||||

|
||||

|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -51,17 +51,17 @@
|
||||
|
||||
Чтобы добиться как можно большей эффективности, кэш использует следующие механизмы загрузки данных.
|
||||
|
||||
- **Строки кэша**: кэш хранит и загружает данные не по одному байту, а строками кэша. По сравнению с передачей по байтам это гораздо эффективнее.
|
||||
- **Механизм предвыборки**: процессор старается предсказать шаблон доступа к данным (например последовательный доступ, доступ с фиксированным шагом и т.д.) и на основе этого шаблона заранее загружает данные в кэш, повышая вероятность попадания.
|
||||
- **Пространственная локальность**: если к некоторым данным уже обратились, то велика вероятность, что в ближайшее время понадобятся и соседние данные. Поэтому, загружая некоторые данные, кэш часто подгружает и окружающие их данные.
|
||||
- **Временная локальность**: если к данным уже обратились, то высока вероятность, что к ним снова обратятся в ближайшем будущем. Кэш использует это свойство, сохраняя недавно использованные данные.
|
||||
- **Кэш-линия**: кэш хранит и загружает данные не по одному байту, а целыми кэш-линиями. По сравнению с передачей по байтам передача кэш-линий более эффективна.
|
||||
- **Механизм предвыборки**: процессор пытается предсказать шаблоны доступа к данным (например, последовательный доступ, доступ с фиксированным шагом и т. д.) и заранее загружает данные в кэш в соответствии с этими шаблонами, чтобы повысить коэффициент попадания.
|
||||
- **Пространственная локальность**: если к данным был осуществлен доступ, то, вероятно, в ближайшее время будет осуществлен доступ и к данным, находящимся поблизости. Поэтому при загрузке данных кэш также загружает данные, находящиеся рядом, чтобы повысить коэффициент попадания.
|
||||
- **Временная локальность**: если к данным был осуществлен доступ, то в ближайшем будущем, вероятно, к ним будет осуществлен повторный доступ. Кэш использует этот принцип, сохраняя недавно запрашиваемые данные, чтобы повысить коэффициент попадания.
|
||||
|
||||
На практике **массивы и связные списки по-разному используют кэш**, и это проявляется в нескольких аспектах.
|
||||
|
||||
- **Занимаемое пространство**: элементы связного списка занимают больше места, чем элементы массива, поэтому в кэше помещается меньше полезных данных.
|
||||
- **Строки кэша**: данные списка разбросаны по памяти, а кэш загружает данные строками, поэтому доля бесполезно загружаемых данных оказывается выше.
|
||||
- **Механизм предвыборки**: шаблон доступа к данным у массивов более предсказуем, чем у списков, то есть системе легче угадать, какие данные понадобятся следующими.
|
||||
- **Пространственная локальность**: массив хранится в компактной области памяти, поэтому данные рядом с уже загруженными с большей вероятностью скоро будут использованы.
|
||||
- **Кэш-линии**: данные связного списка разбросаны по памяти, а кэш загружает данные строками, поэтому доля бесполезно загружаемых данных оказывается выше.
|
||||
- **Механизм предвыборки**: шаблоны доступа к данным у массивов более предсказуемы, чем у связных списков, то есть системе легче угадать, какие данные понадобятся следующими.
|
||||
- **Пространственная локальность**: массивы хранятся в компактной области памяти, поэтому данные рядом с уже загруженными с большей вероятностью скоро будут использованы.
|
||||
|
||||
В целом **массивы имеют более высокий коэффициент попадания в кэш, поэтому по эффективности операций они обычно превосходят связные списки**. Именно поэтому при решении алгоритмических задач структуры данных на основе массивов часто оказываются предпочтительнее.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Двоичный поиск
|
||||
|
||||
<u>Двоичный поиск (binary search)</u> - это эффективный алгоритм поиска, основанный на стратегии «разделяй и властвуй». Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или пока интервал поиска не опустеет.
|
||||
<u>Двоичный поиск (binary search)</u> - это эффективный алгоритм поиска, основанный на стратегии «разделяй и властвуй». Он использует упорядоченность данных, сокращая на каждом шаге область поиска вдвое, пока не будет найден целевой элемент или область поиска не станет пустой.
|
||||
|
||||
!!! question
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
Как показано на рисунке ниже, после завершения поиска указатель $i$ указывает на самый левый `target + 1` (если он существует), а указатель $j$ указывает на самый правый `target` , **поэтому достаточно вернуть $j$**.
|
||||
|
||||

|
||||

|
||||
|
||||
Обратите внимание: функция возвращает точку вставки $i$ , поэтому из нее нужно вычесть $1$ , чтобы получить $j$ :
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Дан упорядоченный массив `nums` длины $n$ и элемент `target` , причем в массиве нет повторяющихся элементов. Нужно вставить `target` в массив `nums` , сохранив порядок. Если элемент `target` уже присутствует в массиве, вставьте его слева от него. Верните индекс, который будет иметь `target` после вставки. Пример показан на рисунке ниже.
|
||||
|
||||

|
||||

|
||||
|
||||
Если мы хотим переиспользовать код двоичного поиска из предыдущего раздела, нужно ответить на два вопроса.
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
1. Выполнить двоичный поиск и получить индекс любого элемента `target` , обозначив его как $k$ .
|
||||
2. Начиная с индекса $k$ , линейно двигаться влево и вернуть результат, когда будет найден самый левый `target` .
|
||||
|
||||

|
||||

|
||||
|
||||
Этот метод применим на практике, однако в нем есть линейный поиск, поэтому его временная сложность равна $O(n)$ . Когда в массиве имеется много повторяющихся `target` , такой подход работает неэффективно.
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
После завершения цикла указатель $i$ будет указывать на самый левый `target` , а указатель $j$ - на самый правый элемент, меньший `target` , **поэтому индекс $i$ и является точкой вставки**.
|
||||
|
||||
=== "<1>"
|
||||

|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
Рассмотрим прямой перебор всех возможных комбинаций. Как показано на рисунке ниже, мы запускаем два вложенных цикла и на каждом шаге проверяем, равна ли сумма двух целых чисел `target`. Если да, то возвращаем их индексы.
|
||||
|
||||

|
||||

|
||||
|
||||
Код приведен ниже:
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
2. Добавить в хеш-таблицу пару из ключа `nums[i]` и индекса `i` .
|
||||
|
||||
=== "<1>"
|
||||

|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
Полный перебор заключается в том, что мы обходим каждый элемент структуры данных, чтобы найти целевой элемент.
|
||||
|
||||
- «Линейный поиск» применяется к линейным структурам данных, таким как массивы и списки. Он начинается с одного конца структуры данных и последовательно проверяет элементы, пока не найдет целевой элемент или пока не достигнет другого конца структуры данных.
|
||||
- «Обход в ширину» и «обход в глубину» - это две стратегии обхода графов и деревьев. Обход в ширину стартует из начального узла и исследует все узлы текущего уровня, прежде чем переходить к следующему. Обход в глубину стартует из начального узла, проходит один путь до конца, затем возвращается назад и пробует другие пути, пока не будет полностью пройдена вся структура данных.
|
||||
- Линейный поиск применяется к линейным структурам данных, таким как массивы и связные списки. Он начинается с одного конца структуры данных и последовательно проверяет элементы, пока не найдет целевой элемент или пока не достигнет другого конца структуры данных.
|
||||
- Обход в ширину и обход в глубину - это две стратегии обхода графов и деревьев. Обход в ширину стартует из начального узла и исследует все узлы текущего уровня, прежде чем переходить к следующему. Обход в глубину стартует из начального узла, проходит один путь до конца, затем возвращается назад и пробует другие пути, пока не будет полностью пройдена вся структура данных.
|
||||
|
||||
Преимущество полного перебора состоит в его простоте и универсальности, **поскольку он не требует предварительной обработки данных и использования дополнительных структур данных**.
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
Адаптивный поиск использует специфические свойства данных (например, упорядоченность), чтобы оптимизировать процесс поиска и тем самым эффективнее находить целевой элемент.
|
||||
|
||||
- «Двоичный поиск» использует упорядоченность данных для эффективного поиска и применим только к массивам.
|
||||
- «Хеш-поиск» использует хеш-таблицу для построения отображения между поисковыми данными и целевыми данными, благодаря чему запросы выполняются эффективно.
|
||||
- «Поиск в дереве» ведется в конкретной древовидной структуре (например, в двоичном дереве поиска) и позволяет быстро отсекать узлы на основе сравнения значений, чтобы найти цель.
|
||||
- Двоичный поиск использует упорядоченность данных для эффективного поиска и применим только к массивам.
|
||||
- Хеш-поиск использует хеш-таблицу, связывая поисковые данные с целевыми данными в виде пар «ключ — значение», благодаря чему запросы выполняются эффективно.
|
||||
- Поиск в дереве ведется в конкретной древовидной структуре (например, в двоичном дереве поиска) и позволяет быстро отсекать узлы на основе сравнения значений, чтобы найти цель.
|
||||
|
||||
Преимущество этих алгоритмов заключается в высокой эффективности: **их временная сложность может достигать $O(\log n)$ и даже $O(1)$** .
|
||||
|
||||
|
||||
Reference in New Issue
Block a user