Selection Sort (Сортировка выбором)
Краткое описание
Простой алгоритм сортировки, который разделяет массив на отсортированную и неотсортированную части. На каждом шаге находит минимальный элемент в неотсортированной части и помещает его в конец отсортированной части. Отличается минимальным количеством операций записи (не более n swap).
Когда использовать
- Маленькие массивы — где квадратичная сложность не критична (обычно до 50 элементов)
- Ограниченная память — алгоритм использует O(1) дополнительной памяти (in-place сортировка)
- Минимизация операций записи — когда запись в память дорогая (например, работа с диском или ROM), так как selection sort делает минимальное количество swap-операций (не более n)
- Образовательные цели — для понимания базовых принципов сортировки
- Простота реализации — когда важна понятность кода и не требуется высокая производительность
Когда не стоит использовать:
- Большие массивы — существуют намного более эффективные алгоритмы (Quick Sort, Merge Sort)
- Нужна адаптивность — алгоритм всегда O(n²), даже на отсортированных данных
- Требуется стабильность — Selection Sort не стабильный алгоритм
С объяснением логики работы
/**
* Сортировка выбором (Selection Sort)
* Принцип: разделяем массив на две части - отсортированную (слева) и неотсортированную (справа).
* На каждом шаге находим минимальный элемент в неотсортированной части и перемещаем его в конец отсортированной части.
*/
function selectionSort(arr) {
// Внешний цикл проходит по всем элементам массива, кроме последнего
// Последний элемент не нужно проверять, так как он автоматически окажется на своем месте
for (let i = 0; i < arr.length - 1; i++) {
// Предполагаем, что текущий элемент (arr[i]) — это минимальный в неотсортированной части
// minIndex будет хранить индекс самого маленького элемента
let minIndex = i;
// Внутренний цикл ищет минимальный элемент в оставшейся неотсортированной части
// Начинаем с i+1, так как элементы до i уже отсортированы
for (let j = i + 1; j < arr.length; j++) {
// Если нашли элемент меньше текущего минимума
if (arr[j] < arr[minIndex]) {
// Обновляем индекс минимального элемента
minIndex = j;
}
}
// После завершения внутреннего цикла minIndex содержит индекс самого маленького элемента
// Проверяем, нужно ли делать обмен
// Если минимальный элемент не находится уже на позиции i, меняем их местами
if (minIndex !== i) {
// Обмен элементов используя деструктуризацию массива (ES6 синтаксис)
// Это эквивалентно временной переменной: temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp;
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
// Теперь arr[i] содержит правильный элемент для этой позиции
// Граница между отсортированной и неотсортированной частью сдвинулась на одну позицию вправо
}
// Возвращаем отсортированный массив
// Примечание: массив сортируется "на месте" (in-place), но возврат удобен для цепочки вызовов
return arr;
}
// Пример использования
const numbers = [64, 25, 12, 22, 11];
console.log("Исходный массив:", numbers);
selectionSort(numbers);
console.log("Отсортированный массив:", numbers);
С анализом сложностей
/**
* Сортировка выбором (Selection Sort)
*
* ВРЕМЕННАЯ СЛОЖНОСТЬ:
* - Лучший случай: O(n²) — даже если массив уже отсортирован, алгоритм всё равно проверяет все элементы
* - Средний случай: O(n²) — стандартный случай с произвольным порядком элементов
* - Худший случай: O(n²) — когда массив отсортирован в обратном порядке
*
* ПРОСТРАНСТВЕННАЯ СЛОЖНОСТЬ:
* - O(1) — требуется только константное количество дополнительной памяти для переменных (i, j, minIndex, temp)
* - Сортировка происходит "на месте" (in-place), без создания дополнительных массивов
*
* КОЛИЧЕСТВО ОПЕРАЦИЙ:
* - Сравнения: всегда (n-1) + (n-2) + ... + 1 = n(n-1)/2 ≈ n²/2
* - Обмены (swaps): в худшем случае O(n), в лучшем O(0)
*/
function selectionSort(arr) {
// Внешний цикл выполняется (n-1) раз
// Сложность внешнего цикла: O(n)
for (let i = 0; i < arr.length - 1; i++) {
// Инициализация minIndex — O(1) операция
let minIndex = i;
// Внутренний цикл выполняется (n-i-1) раз для каждой итерации внешнего цикла
// Итерация 1: (n-1) сравнений
// Итерация 2: (n-2) сравнений
// ...
// Итерация n-1: 1 сравнение
// Общее количество: (n-1) + (n-2) + ... + 1 = n(n-1)/2
// Сложность: O(n²)
for (let j = i + 1; j < arr.length; j++) {
// Сравнение — O(1) операция
// Выполняется n(n-1)/2 раз за всю работу алгоритма
if (arr[j] < arr[minIndex]) {
// Присваивание — O(1) операция
minIndex = j;
}
}
// Проверка и обмен — O(1) операция
// Обмен выполняется максимум (n-1) раз за всю работу (один раз на итерацию внешнего цикла)
// Это делает selection sort эффективным, когда операция записи дорогая
if (minIndex !== i) {
// Swap операция — O(1) по времени, использует O(1) дополнительной памяти
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
// ИТОГОВАЯ СЛОЖНОСТЬ:
// Время: O(n) * O(n) = O(n²) — два вложенных цикла
// Память: O(1) — только переменные i, j, minIndex
return arr;
}
// Примеры для демонстрации одинаковой сложности во всех случаях:
// Худший случай: обратно отсортированный массив
const worst = [5, 4, 3, 2, 1];
// Лучший случай: уже отсортированный массив
// Важно: даже здесь будет O(n²), так как алгоритм всё равно проверяет все элементы
const best = [1, 2, 3, 4, 5];
// Средний случай: произвольный порядок
const average = [3, 1, 4, 5, 2];