5.5. Демонстрация - Реализация quicksort
Шаг 1
Реализация быстрой сортировки сложнее сортировки пузырьком, однако ничего страшного в ней нет. Напоминаем, что нам предстоит написать несколько вещей: 1. Разделение массива на точке поворота. 2. Перемещение элементов больше неё направо, меньше — налево. 3. Вызов сортировки на каждой из этих частей.
/*
quickSort([5, 3, 2, 1]) => [1, 2, 3, 5]
quickSort([1, 2, 3]) => [1, 2, 3]
*/
function quickSort(array) {
return array;
}
Шаг 2
Начнём с первой части нашего алгоритма — функции, которая будет «раскидывать» элементы вокруг поворотной точки. Её мы будем выбирать в нашем массиве случайным образом. Сначала напишем функцию-хелпер, которая поможет нам искать индекс этой точки.
/*
quickSort([5, 3, 2, 1]) => [1, 2, 3, 5]
quickSort([1, 2, 3]) => [1, 2, 3]
*/
function quickSort(array) {
return array;
}
function random(min, max) {
const interval = max - min; // Интервал, в котором могут находится наши числа
const shift = min; // Поиск рандомного числа будет начинаться не с нуля, а с min
return Math.round(Math.random() * interval + shift);
}
Шаг 3
Теперь можно написать и «функцию-раскидыватель». Рассказать принцип её работы проще по уже готовому коду, так что обратите внимание на комментарии к ней!
/*
quickSort([5, 3, 2, 1]) => [1, 2, 3, 5]
quickSort([1, 2, 3]) => [1, 2, 3]
*/
function quickSort(array) {
return array;
}
function random(min, max) {
const interval = max - min; // Интервал, в котором могут находится наши числа
const shift = min; // Поиск рандомного числа будет начинаться не с нуля, а с min
return Math.round(Math.random() * interval + shift);
}
// Так как в подразбиениях после рекурсивного вызова сортировки мы будем работать не с целым массивом а его частями, сразу сделаем дополнительные параметры для их определения
function partition(array, left, right) {
// Находим значение, вокруг которого будем размещать элементы
const pivot = array[random(left, right)];
// как и в бинпоиске, будем сходиться с краев в центр, пока не просмотрим все элементы
while (left < right) {
// Пока слева встречаются только числа меньше поворотного...
while (array[left] < pivot) {
// ... просто двигаем левый указатель вправо, ведь с этими числами ничего делать не надо
left++;
}
// Пока справа встречаются только числа больше поворотного...
while (array[right] > pivot) {
// ... просто двигаем правый указатель влево, ведь с этими числами ничего делать не надо
right--;
}
// А как только оба указателя показывают на элементы, которые должны быть в противоположных частях И мы всё ещё не сошлись к центру...
if (left <= right) {
// ...меняем их местами и не забываем двигать оба указателя, так как теперь оба числа на своём месте
[array[left], array[right]] = [array[right], array[left]];
left++;
right--;
}
}
// Возвращаем место, где оказался элемент, равный нашей точке поворота
return left;
}
Шаг 4
Осталось дописать непосредственно сортировку. Напомним, что нам нужно лишь вызывать разбиение массива и эту же сортировку на каждом из разбиений, пока мы не «спустимся» к разбиениям из одного элемента. Кстати, в движке V8, который запускает JavaScript внутри Chrome, подобная реализация сортировки используется в Array.prototype.sort() для сортировки массивов из 23 и более элементов. Для меньших разбиения и рекурсия — слишком долго, поэтому для них используется сортировка вставкой, о которой мы узнаем в следующей главе!
import {logIteration} from "./logger";
/*
quickSort([5, 3, 2, 1]) => [1, 2, 3, 5]
quickSort([1, 2, 3]) => [1, 2, 3]
*/
// Тут нам тоже нужно определить дополнительные параметры, как и в разбиении, чтобы нормально работать с подмассивами
function quickSort(array, left, right) {
// если мы работаем с целым массивом и нам не передали края сортировки, определим их сами
left = left ?? 0;
right = right ?? array.length - 1;
// разбиваем наш массив на подмассивы вокруг точки поворота
const pivotIndex = partition(array, left, right);
logIteration(array, array[pivotIndex], left, right);
// если в левом подмассиве больше одного элемента, то сортируем его
if (left < pivotIndex - 1) {
quickSort(array, left, pivotIndex - 1);
}
// то же и с правым
if (pivotIndex < right) {
quickSort(array, pivotIndex, right);
}
return array;
}
function random(min, max) {
const interval = max - min; // Интервал, в котором могут находится наши числа
const shift = min; // Поиск рандомного числа будет начинаться не с нуля, а с min
return Math.round(Math.random() * interval + shift);
}
// Так как в подразбиениях после рекурсивного вызова сортировки мы будем работать не с целым массивом а его частями, сразу сделаем дополнительные параметры для их определения
function partition(array, left, right) {
// Находим значение, вокруг которого будем размещать элементы
const pivot = array[random(left, right)];
// как и в бинпоиске, будем сходиться с краев в центр, пока не просмотрим все элементы
while (left < right) {
// Пока слева встречаются только числа меньше поворотного...
while (array[left] < pivot) {
// ... просто двигаем левый указатель вправо, ведь с этими числами ничего делать не надо
left++;
}
// Пока справа встречаются только числа больше поворотного...
while (array[right] > pivot) {
// ... просто двигаем правый указатель влево, ведь с этими числами ничего делать не надо
right--;
}
// А как только оба указателя показывают на элементы, которые должны быть в противоположных частях И мы всё ещё не сошлись к центру...
if (left <= right) {
// ...меняем их местами и не забываем двигать оба указателя, так как теперь оба числа на своём месте
[array[left], array[right]] = [array[right], array[left]];
left++;
right--;
}
}
// Возвращаем место, где оказался элемент, равный нашей точке поворота
return left;
}
quickSort([56, 87, 18, 92, 42, 31, 44, 82, 36, 91]);
File logger
const results = document.getElementById('results')
export function logIteration(array, pivot, start, end) {
const result = document.createElement('li');
result.innerHTML = `[${array.join(', ')}], разделение вокруг ${pivot}, с ${start} по ${end} индексы`;
results.appendChild(result);
}
de>