0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Алгоритмы сортировки данных широко используются в программировании для решения различных задач. В этой статье мы рассмотрим несколько основных алгоритмов сортировки данных в массиве.</p>
1
<p>Алгоритмы сортировки данных широко используются в программировании для решения различных задач. В этой статье мы рассмотрим несколько основных алгоритмов сортировки данных в массиве.</p>
2
<p>Для начала давайте вспомним, что<strong>массив</strong>- это структура данных, которая хранит набор значений. Компоненты массива идентифицируются по индексу либо набору индексов, которые принимают целые значения из некоторого непрерывного заданного диапазона.</p>
2
<p>Для начала давайте вспомним, что<strong>массив</strong>- это структура данных, которая хранит набор значений. Компоненты массива идентифицируются по индексу либо набору индексов, которые принимают целые значения из некоторого непрерывного заданного диапазона.</p>
3
<p>Но прежде чем идти дальше и говорить про алгоритмы сортировки, давайте вспомним про метод Swap. Мы введём этот метод для упрощения и улучшения читаемости кода. Он нужен, чтобы менять значения местами в массиве по индексу.</p>
3
<p>Но прежде чем идти дальше и говорить про алгоритмы сортировки, давайте вспомним про метод Swap. Мы введём этот метод для упрощения и улучшения читаемости кода. Он нужен, чтобы менять значения местами в массиве по индексу.</p>
4
void Swap(T[] items, int left, int right) { if (left != right) { T temp = items[left]; items[left] = items[right]; items[right] = temp; } }<p>Что же, теперь можно приступать и к рассмотрению алгоритмов сортировки. Начнём с пузырьковой.</p>
4
void Swap(T[] items, int left, int right) { if (left != right) { T temp = items[left]; items[left] = items[right]; items[right] = temp; } }<p>Что же, теперь можно приступать и к рассмотрению алгоритмов сортировки. Начнём с пузырьковой.</p>
5
<h2>Пузырьковая сортировка данных</h2>
5
<h2>Пузырьковая сортировка данных</h2>
6
<p><strong>Сортировка пузырьком</strong>считается самым простым алгоритмом сортировки. В данном случае алгоритм проходит по массиву несколько раз, причём на каждом этапе происходит перемещение в конец массива самого большого из неотсортированных значений.</p>
6
<p><strong>Сортировка пузырьком</strong>считается самым простым алгоритмом сортировки. В данном случае алгоритм проходит по массиву несколько раз, причём на каждом этапе происходит перемещение в конец массива самого большого из неотсортированных значений.</p>
7
<p>Представим, что у нас есть массив целых чисел:</p>
7
<p>Представим, что у нас есть массив целых чисел:</p>
8
<p>Во время первого прохода по массиву сравниваются значения 3 и 7. Так как семь больше, всё остаётся в первоначальном виде. Далее сравниваются 7 и 4. Т. к. четыре меньше, цифры меняются местами:</p>
8
<p>Во время первого прохода по массиву сравниваются значения 3 и 7. Так как семь больше, всё остаётся в первоначальном виде. Далее сравниваются 7 и 4. Т. к. четыре меньше, цифры меняются местами:</p>
9
<p>В общем, процесс повторяется, пока 7 не дойдёт практически до конца. Почему практически? Потому что, как вы уже наверняка догадались, последний элемент - это 8, который больше семи, поэтому обмен не происходит. Всё чрезвычайно просто:</p>
9
<p>В общем, процесс повторяется, пока 7 не дойдёт практически до конца. Почему практически? Потому что, как вы уже наверняка догадались, последний элемент - это 8, который больше семи, поэтому обмен не происходит. Всё чрезвычайно просто:</p>
10
<p>Однако пока обмен происходит, сортировка продолжается, в результате чего перемещается 6:</p>
10
<p>Однако пока обмен происходит, сортировка продолжается, в результате чего перемещается 6:</p>
11
<p>При очередном проходе обмен не выполняется, т. к. все значения массива расположены по порядку. В итоге алгоритм сортировки пузырьком заканчивает свою работу.</p>
11
<p>При очередном проходе обмен не выполняется, т. к. все значения массива расположены по порядку. В итоге алгоритм сортировки пузырьком заканчивает свою работу.</p>
12
public void Sort(T[] items) { bool swapped; do { swapped = false; for (int i = 1; i < items.Length; i++) { if (items[i - 1].CompareTo(items[i]) > 0) { Swap(items, i - 1, i); swapped = true; } } } while (swapped != false); }<h2>Сортировка данных вставками</h2>
12
public void Sort(T[] items) { bool swapped; do { swapped = false; for (int i = 1; i < items.Length; i++) { if (items[i - 1].CompareTo(items[i]) > 0) { Swap(items, i - 1, i); swapped = true; } } } while (swapped != false); }<h2>Сортировка данных вставками</h2>
13
<p>Эта сортировка работает путём прохождения по массиву и перемещения нужного значения в его начало. Важно помнить, что сортировка обрабатывает элементы массива по порядку. Т. к. алгоритм работает слева направо, становится очевидно, что всё, что находится слева от текущего индекса, - отсортировано. Давайте посмотрим на увеличение отсортированной части массива с каждым последующим проходом:</p>
13
<p>Эта сортировка работает путём прохождения по массиву и перемещения нужного значения в его начало. Важно помнить, что сортировка обрабатывает элементы массива по порядку. Т. к. алгоритм работает слева направо, становится очевидно, что всё, что находится слева от текущего индекса, - отсортировано. Давайте посмотрим на увеличение отсортированной части массива с каждым последующим проходом:</p>
14
<p>По ходу работы отсортированная часть массива растёт, таким образом, в конечном итоге, массив становится упорядоченным.</p>
14
<p>По ходу работы отсортированная часть массива растёт, таким образом, в конечном итоге, массив становится упорядоченным.</p>
15
<p>Приведём пример. Вот неотсортированный массив:</p>
15
<p>Приведём пример. Вот неотсортированный массив:</p>
16
<p>Алгоритм сортировки начнёт работать с индекса "ноль" и значения "три". Т. к. это 1-й индекс, массив до него включительно будет считаться уже отсортированным.</p>
16
<p>Алгоритм сортировки начнёт работать с индекса "ноль" и значения "три". Т. к. это 1-й индекс, массив до него включительно будет считаться уже отсортированным.</p>
17
<p>Потом перейдём к семёрке. Семь больше любого значения в отсортированной части, значит, осуществляется переход к последующему элементу. Отметим, что на прошедшем этапе были отсортированы компоненты с индексами 0..1, про компоненты с индексами 2..n мы пока ничего не знаем.</p>
17
<p>Потом перейдём к семёрке. Семь больше любого значения в отсортированной части, значит, осуществляется переход к последующему элементу. Отметим, что на прошедшем этапе были отсортированы компоненты с индексами 0..1, про компоненты с индексами 2..n мы пока ничего не знаем.</p>
18
<p>Теперь алгоритм проверяет четвёрку. Четыре меньше, чем 7, поэтому переносится на другую, более правильную позицию, которая находится в отсортированной части массива. Позиция определяется методом FindInsertionIndex. Метод сравнивает переданное значение (в нашем случае это 4) с каждым значением из отсортированной части и так до тех пор, пока не будет найдено место для вставки.</p>
18
<p>Теперь алгоритм проверяет четвёрку. Четыре меньше, чем 7, поэтому переносится на другую, более правильную позицию, которая находится в отсортированной части массива. Позиция определяется методом FindInsertionIndex. Метод сравнивает переданное значение (в нашем случае это 4) с каждым значением из отсортированной части и так до тех пор, пока не будет найдено место для вставки.</p>
19
<p>Так для вставки был определён индекс 1. Вставка осуществляется методом Insert. Вставляемое значение удаляется из массива, все остальные цифры сдвигаются вправо, начиная с индекса для вставки. Вот как стал выглядеть массив после сортировки:</p>
19
<p>Так для вставки был определён индекс 1. Вставка осуществляется методом Insert. Вставляемое значение удаляется из массива, все остальные цифры сдвигаются вправо, начиная с индекса для вставки. Вот как стал выглядеть массив после сортировки:</p>
20
<p>Итог работы алгоритма сортировки вставками очевиден:</p>
20
<p>Итог работы алгоритма сортировки вставками очевиден:</p>
21
public void Sort(T[] items) { int sortedRangeEndIndex = 1; while (sortedRangeEndIndex < items.Length) { if (items[sortedRangeEndIndex].CompareTo(items[sortedRangeEndIndex - 1]) < 0) { int insertIndex = FindInsertionIndex(items, items[sortedRangeEndIndex]); Insert(items, insertIndex, sortedRangeEndIndex); } sortedRangeEndIndex++; } } private int FindInsertionIndex(T[] items, T valueToInsert) { for (int index = 0; index < items.Length; index++) { if (items[index].CompareTo(valueToInsert) > 0) { return index; } } throw new InvalidOperationException("The insertion index was not found"); } private void Insert(T[] itemArray, int indexInsertingAt, int indexInsertingFrom) { // itemArray = 0 1 2 4 5 6 3 7 // insertingAt = 3 // insertingFrom = 6 // // Действия: // 1: Сохраняем текущий индекс в temp // 2: Меняем indexInsertingAt на indexInsertingFrom // 3: Меняем indexInsertingAt на indexInsertingFrom в позиции +1 // Сдвигаем элементы влево на один. // 4: Записываем temp на позицию в массиве + 1. // Шаг 1. T temp = itemArray[indexInsertingAt]; // Шаг 2. itemArray[indexInsertingAt] = itemArray[indexInsertingFrom]; // Шаг 3. for (int current = indexInsertingFrom; current > indexInsertingAt; current--) { itemArray[current] = itemArray[current - 1]; } // Шаг 4. itemArray[indexInsertingAt + 1] = temp; }<h2>Сортировка данных выбором</h2>
21
public void Sort(T[] items) { int sortedRangeEndIndex = 1; while (sortedRangeEndIndex < items.Length) { if (items[sortedRangeEndIndex].CompareTo(items[sortedRangeEndIndex - 1]) < 0) { int insertIndex = FindInsertionIndex(items, items[sortedRangeEndIndex]); Insert(items, insertIndex, sortedRangeEndIndex); } sortedRangeEndIndex++; } } private int FindInsertionIndex(T[] items, T valueToInsert) { for (int index = 0; index < items.Length; index++) { if (items[index].CompareTo(valueToInsert) > 0) { return index; } } throw new InvalidOperationException("The insertion index was not found"); } private void Insert(T[] itemArray, int indexInsertingAt, int indexInsertingFrom) { // itemArray = 0 1 2 4 5 6 3 7 // insertingAt = 3 // insertingFrom = 6 // // Действия: // 1: Сохраняем текущий индекс в temp // 2: Меняем indexInsertingAt на indexInsertingFrom // 3: Меняем indexInsertingAt на indexInsertingFrom в позиции +1 // Сдвигаем элементы влево на один. // 4: Записываем temp на позицию в массиве + 1. // Шаг 1. T temp = itemArray[indexInsertingAt]; // Шаг 2. itemArray[indexInsertingAt] = itemArray[indexInsertingFrom]; // Шаг 3. for (int current = indexInsertingFrom; current > indexInsertingAt; current--) { itemArray[current] = itemArray[current - 1]; } // Шаг 4. itemArray[indexInsertingAt + 1] = temp; }<h2>Сортировка данных выбором</h2>
22
<p>Сортировка выбором - некий гибрид между сортировкой вставками и пузырьковой сортировкой. Давайте посмотрим, как работает эта сортировка на нашем массиве:</p>
22
<p>Сортировка выбором - некий гибрид между сортировкой вставками и пузырьковой сортировкой. Давайте посмотрим, как работает эта сортировка на нашем массиве:</p>
23
<p>Во время первого же прохода алгоритм посредством метода FindIndexOfSmallestFromIndex пробует найти самое меньшее значение для перемещения его в начало массива.</p>
23
<p>Во время первого же прохода алгоритм посредством метода FindIndexOfSmallestFromIndex пробует найти самое меньшее значение для перемещения его в начало массива.</p>
24
<p>Так как в нашем примере массив небольшой, мы легко скажем, что это "три", да и вообще, эта цифра уже находится там, где надо. После второго прохода мы получим следующее:</p>
24
<p>Так как в нашем примере массив небольшой, мы легко скажем, что это "три", да и вообще, эта цифра уже находится там, где надо. После второго прохода мы получим следующее:</p>
25
<p>И ещё после 2-х проходов алгоритм сортировки завершит работу:</p>
25
<p>И ещё после 2-х проходов алгоритм сортировки завершит работу:</p>
26
public void Sort(T[] items) { int sortedRangeEnd = 0; while (sortedRangeEnd < items.Length) { int nextIndex = FindIndexOfSmallestFromIndex(items, sortedRangeEnd); Swap(items, sortedRangeEnd, nextIndex); sortedRangeEnd++; } } private int FindIndexOfSmallestFromIndex(T[] items, int sortedRangeEnd) { T currentSmallest = items[sortedRangeEnd]; int currentSmallestIndex = sortedRangeEnd; for (int i = sortedRangeEnd + 1; i < items.Length; i++) { if (currentSmallest.CompareTo(items[i]) > 0) { currentSmallest = items[i]; currentSmallestIndex = i; } } return currentSmallestIndex; }<h2>Сортировка данных слиянием</h2>
26
public void Sort(T[] items) { int sortedRangeEnd = 0; while (sortedRangeEnd < items.Length) { int nextIndex = FindIndexOfSmallestFromIndex(items, sortedRangeEnd); Swap(items, sortedRangeEnd, nextIndex); sortedRangeEnd++; } } private int FindIndexOfSmallestFromIndex(T[] items, int sortedRangeEnd) { T currentSmallest = items[sortedRangeEnd]; int currentSmallestIndex = sortedRangeEnd; for (int i = sortedRangeEnd + 1; i < items.Length; i++) { if (currentSmallest.CompareTo(items[i]) > 0) { currentSmallest = items[i]; currentSmallestIndex = i; } } return currentSmallestIndex; }<h2>Сортировка данных слиянием</h2>
27
<p>Предыдущее алгоритмы - линейные (имея квадратичную сложность, они используют мало памяти). Сортировка слиянием соответствует принципу "Divide and conquer" ("разделяй и властвуй"). Она работает путём разделения крупной задачи на более мелкие, которые решаются проще.</p>
27
<p>Предыдущее алгоритмы - линейные (имея квадратичную сложность, они используют мало памяти). Сортировка слиянием соответствует принципу "Divide and conquer" ("разделяй и властвуй"). Она работает путём разделения крупной задачи на более мелкие, которые решаются проще.</p>
28
<h4>Отвлечёмся на минутку</h4>
28
<h4>Отвлечёмся на минутку</h4>
29
<p>Представьте, что вы работаете на крыше или стройплощадке, и у вас повредился электрокабель, от которого запитывается важный инструмент. Строительные и кровельные кабели очень длинные и часто достигают 100 и более метров. Вам нужно срочно окончить работу, но у вас нет средств диагностики, чтобы починить провод. Неопытные работники просто прекращают все действия, доложив начальству. Мастера-сдельщики режут кабель пополам, получая в 99 % случаев 50 метров работающего провода. Если нужно, они режут пополам и неработающую часть, что позволяет либо достаточно быстро найти место внешнего повреждения, внимательно изучив четверть кабеля (25 м), либо получить в итоге 75 метров, которых хватит для выполнения большинства строительных задач. Всё, что потребуется, - нож и моток изоленты.</p>
29
<p>Представьте, что вы работаете на крыше или стройплощадке, и у вас повредился электрокабель, от которого запитывается важный инструмент. Строительные и кровельные кабели очень длинные и часто достигают 100 и более метров. Вам нужно срочно окончить работу, но у вас нет средств диагностики, чтобы починить провод. Неопытные работники просто прекращают все действия, доложив начальству. Мастера-сдельщики режут кабель пополам, получая в 99 % случаев 50 метров работающего провода. Если нужно, они режут пополам и неработающую часть, что позволяет либо достаточно быстро найти место внешнего повреждения, внимательно изучив четверть кабеля (25 м), либо получить в итоге 75 метров, которых хватит для выполнения большинства строительных задач. Всё, что потребуется, - нож и моток изоленты.</p>
30
<p>Пример достаточно отдалённый, но всё же помогает понять, что такое сортировка слиянием. При выполнении этого алгоритма массив делится пополам до тех пор, пока каждый участок не будет иметь длину в один элемент. Далее участки сливаются (возвращаются на место), но уже в правильном порядке.</p>
30
<p>Пример достаточно отдалённый, но всё же помогает понять, что такое сортировка слиянием. При выполнении этого алгоритма массив делится пополам до тех пор, пока каждый участок не будет иметь длину в один элемент. Далее участки сливаются (возвращаются на место), но уже в правильном порядке.</p>
31
<p>Итак, наш массив:</p>
31
<p>Итак, наш массив:</p>
32
<p>Он делится наполовину:</p>
32
<p>Он делится наполовину:</p>
33
<p>И потом опять, и опять наполовину:</p>
33
<p>И потом опять, и опять наполовину:</p>
34
<p>Потом сливание/соединение в верном порядке:</p>
34
<p>Потом сливание/соединение в верном порядке:</p>
35
<p>И результат:</p>
35
<p>И результат:</p>
36
<p>Алгоритм работает путём реализации следующих операций: 1. Рекурсивное разделение массива на группы с помощью метода Sort. 2. Слияние в верном порядке через метод Merge.</p>
36
<p>Алгоритм работает путём реализации следующих операций: 1. Рекурсивное разделение массива на группы с помощью метода Sort. 2. Слияние в верном порядке через метод Merge.</p>
37
<p>Сортировка слиянием делит и склеивает массив вне зависимости от того, был ли он изначально отсортирован либо нет. Это значит, что данный алгоритм - не самое оптимальное решение, если наш массив уже частично упорядочен и отсортирован (производительность сортировки слиянием может быть ниже, чем у линейного алгоритма).</p>
37
<p>Сортировка слиянием делит и склеивает массив вне зависимости от того, был ли он изначально отсортирован либо нет. Это значит, что данный алгоритм - не самое оптимальное решение, если наш массив уже частично упорядочен и отсортирован (производительность сортировки слиянием может быть ниже, чем у линейного алгоритма).</p>
38
public void Sort(T[] items) { if (items.Length <= 1) { return; } int leftSize = items.Length / 2; int rightSize = items.Length - leftSize; T[] left = new T[leftSize]; T[] right = new T[rightSize]; Array.Copy(items, 0, left, 0, leftSize); Array.Copy(items, leftSize, right, 0, rightSize); Sort(left); Sort(right); Merge(items, left, right); } private void Merge(T[] items, T[] left, T[] right) { int leftIndex = 0; int rightIndex = 0; int targetIndex = 0; int remaining = left.Length + right.Length; while(remaining > 0) { if (leftIndex >= left.Length) { items[targetIndex] = right[rightIndex++]; } else if (rightIndex >= right.Length) { items[targetIndex] = left[leftIndex++]; } else if (left[leftIndex].CompareTo(right[rightIndex]) < 0) { items[targetIndex] = left[leftIndex++]; } else { items[targetIndex] = right[rightIndex++]; } targetIndex++; remaining--; } }<h2>Быстрая сортировка данных</h2>
38
public void Sort(T[] items) { if (items.Length <= 1) { return; } int leftSize = items.Length / 2; int rightSize = items.Length - leftSize; T[] left = new T[leftSize]; T[] right = new T[rightSize]; Array.Copy(items, 0, left, 0, leftSize); Array.Copy(items, leftSize, right, 0, rightSize); Sort(left); Sort(right); Merge(items, left, right); } private void Merge(T[] items, T[] left, T[] right) { int leftIndex = 0; int rightIndex = 0; int targetIndex = 0; int remaining = left.Length + right.Length; while(remaining > 0) { if (leftIndex >= left.Length) { items[targetIndex] = right[rightIndex++]; } else if (rightIndex >= right.Length) { items[targetIndex] = left[leftIndex++]; } else if (left[leftIndex].CompareTo(right[rightIndex]) < 0) { items[targetIndex] = left[leftIndex++]; } else { items[targetIndex] = right[rightIndex++]; } targetIndex++; remaining--; } }<h2>Быстрая сортировка данных</h2>
39
<p>Быстрая сортировка тоже представляет собой алгоритм, отвечающий типу "разделяй и властвуй". Сортировка данных происходит рекурсивно и поэтапно: 1. Выбирается ключевой индекс, массив делится по нему на 2 части. Выбор осуществляется разными способами, мы же возьмём случайное число. 2. Все элементы, которые больше ключевого, перемещаются в правую часть массива, которые меньше - в левую. Теперь ключевой элемент больше любого значения слева и меньше любого значения справа. 3. Первые два шага повторяются до полной сортировки массива.</p>
39
<p>Быстрая сортировка тоже представляет собой алгоритм, отвечающий типу "разделяй и властвуй". Сортировка данных происходит рекурсивно и поэтапно: 1. Выбирается ключевой индекс, массив делится по нему на 2 части. Выбор осуществляется разными способами, мы же возьмём случайное число. 2. Все элементы, которые больше ключевого, перемещаются в правую часть массива, которые меньше - в левую. Теперь ключевой элемент больше любого значения слева и меньше любого значения справа. 3. Первые два шага повторяются до полной сортировки массива.</p>
40
<p>Смотрим на работу алгоритма:</p>
40
<p>Смотрим на работу алгоритма:</p>
41
<p>Выбираем ключевой элемент случайным образом:</p>
41
<p>Выбираем ключевой элемент случайным образом:</p>
42
int pivotIndex = _pivotRng.Next(left, right);<p>И переносим значения справа от ключевого индекса, располагая их в верном положении (используем метод partition).</p>
42
int pivotIndex = _pivotRng.Next(left, right);<p>И переносим значения справа от ключевого индекса, располагая их в верном положении (используем метод partition).</p>
43
<p>Потом повторяем этот процесс и для левой части. Рекурсивно вызываем метод quicksort для каждой из частей. Ключевым элементом слева становится пятерка - она меняет свой индекс при перемещении значений. Важно не забывать, что нас интересует в первую очередь именно ключевое значение, а не его индекс.</p>
43
<p>Потом повторяем этот процесс и для левой части. Рекурсивно вызываем метод quicksort для каждой из частей. Ключевым элементом слева становится пятерка - она меняет свой индекс при перемещении значений. Важно не забывать, что нас интересует в первую очередь именно ключевое значение, а не его индекс.</p>
44
<p>И снова быстрая сортировка:</p>
44
<p>И снова быстрая сортировка:</p>
45
<p>И опять:</p>
45
<p>И опять:</p>
46
<p>В итоге работа алгоритма завершается.</p>
46
<p>В итоге работа алгоритма завершается.</p>
47
Random _pivotRng = new Random(); public void Sort(T[] items) { quicksort(items, 0, items.Length - 1); } private void quicksort(T[] items, int left, int right) { if (left < right) { int pivotIndex = _pivotRng.Next(left, right); int newPivot = partition(items, left, right, pivotIndex); quicksort(items, left, newPivot - 1); quicksort(items, newPivot + 1, right); } } private int partition(T[] items, int left, int right, int pivotIndex) { T pivotValue = items[pivotIndex]; Swap(items, pivotIndex, right); int storeIndex = left; for (int i = left; i < right; i++) { if (items[i].CompareTo(pivotValue) < 0) { Swap(items, i, storeIndex); storeIndex += 1; } } Swap(items, storeIndex, right); return storeIndex; }<p>Статья подготовлена специально для OTUS по материалам "<a>Sorting Algorithms</a>".</p>
47
Random _pivotRng = new Random(); public void Sort(T[] items) { quicksort(items, 0, items.Length - 1); } private void quicksort(T[] items, int left, int right) { if (left < right) { int pivotIndex = _pivotRng.Next(left, right); int newPivot = partition(items, left, right, pivotIndex); quicksort(items, left, newPivot - 1); quicksort(items, newPivot + 1, right); } } private int partition(T[] items, int left, int right, int pivotIndex) { T pivotValue = items[pivotIndex]; Swap(items, pivotIndex, right); int storeIndex = left; for (int i = left; i < right; i++) { if (items[i].CompareTo(pivotValue) < 0) { Swap(items, i, storeIndex); storeIndex += 1; } } Swap(items, storeIndex, right); return storeIndex; }<p>Статья подготовлена специально для OTUS по материалам "<a>Sorting Algorithms</a>".</p>
48
<p><em>Хотите знать больше? Записывайтесь на курс "Алгоритмы для разработчиков"!</em></p>
48
<p><em>Хотите знать больше? Записывайтесь на курс "Алгоритмы для разработчиков"!</em></p>
49
49