Сортировки. Квадратичные сортировки (сортировка выбором, «пузырьковая сортировка»). Сортировка подсчётом. Быстрая сортировка QuickSort.
Алгоритмы сортировки - это методы для упорядочения элементов массива в каком-либо порядке.
Алгоритмы сортировки оцениваются по скорости выполнения и количествую используемой памяти.
Время (скорость выполнения) - основной параметр, он измеряется относительно количества элементов исходного массива. Например: $O(n)$ - время выполнения растёт пропорционально количеству элементов, $O(n^2)$ - пропорционально квадрату количества элементов.
При описании всех алгоритмов: N или $n$ - количество элементов в массиве.
Квадратичные сортировки, сортировка "Пузырьком"
Это самая простая сортировка, её следует применять когда у вас немного элементов (до десятков тысяч). Её сложность $O(n^2)$, т.е. количество операций растёт как квадрат от количества элементов.
Шаги алгоритма:
- Считываем исходный массив в память.
- Пока происходят изменения в массиве: сравниваем каждые два соседних элемента, и если они не стоят не в том порядке, меняем их местами.
- Теперь массив отсортирован, выводим его.
{$APPTYPE CONSOLE}
var
N : Integer; { Количество элементов в массиве }
A : array [1..5000] of Integer; { Сортируемый массив }
i : Integer; { Переменная цикла }
temp : Integer; { Переменная для обмена местами двух элементов в массиве }
Changes : Boolean; { Есть ли изменения? }
begin
{ Ввод исходного массива из файла }
Reset(Input,'bubble.in');
Read(N); { Читаем количество элементов в массиве }
for i:=1 to N do Read(A[i]); { Читаем сам массив }
{ Сортировка }
repeat
Changes := false; { Пока изменений нет :) }
for i:=1 to N-1 do { Пробегаем по массиву сравнивая соседние элементы }
if A[i]>A[i+1] then begin { Если больший элемент слева, а должен быть справа }
temp := A[i]; { Меняем элементы местами при помощи временной переменной temp }
A[i] := A[i+1];
A[i+1] := temp;
Changes := true; { Изменения произошли! }
end;
until not Changes; { Заканчиваем когда нет изменений (значит, все элементы уже по-порядку) }
{ Вывод отсортированного массива в файл }
Rewrite(Output,'bubble.out');
for i:=1 to N-1 do Write(A[i],' ');
Writeln(A[N])
end.
Модификация с максимумами:
В этой сортировке мы пробегаем массив N раз, каждый раз перемещая в конец массива самый большой при сортировке по возрастанию (или самый маленький при сортировке по убыванию) элемент.
Шаги алгоритма:
- Считываем исходный массив в память.
- Переносим в N-ый элемент максимум среди элементов 1..N.
- Переносим в N-1-ый элемент максимум среди 1..N-1.
- и так далее (делаем это N раз).
- Теперь массив отсортирован, выводим его.
{$APPTYPE CONSOLE}
var
N : Integer; { Количество элементов в массиве }
A : array [1..5000] of Integer; { Сортируемый массив }
i,j : Integer; { Переменные цикла }
temp : Integer; { Переменная для обмена местами двух элементов в массиве }
begin
{ Ввод исходного массива из файла }
...
{ Сортировка }
for i:=N downto 1 do { A[i] должно быть больше чем все A[j] слева от него }
for j:=1 to i-1 do { Проверяем все A[j] }
if A[j] > A[i] then begin { Если какое-то A[j] больше чем A[i], то меняем их местами }
temp := A[i]; { Меняем элементы местами при помощи временной переменной temp }
A[i] := A[j];
A[j] := temp;
end;
{ Вывод отсортированного массива в файл }
...
end.
Другой вариант, легче для запоминания:
{ Сортировка }
for i := 1 to N-1 do
for j := i+1 to N do
if A[j] < A[i] then begin
temp:=A[i]; { Меняем элементы A[i] и A[j] местами }
A[i]:=A[j];
A[j]:=temp;
end;
Ну или можно вообще не запоминать как меняются индексы и писать i от 1 до N и j от 1 до N.
Менять местами элементы можно без использования временной переменной, сложениями/вычитаниями (и - исходное значение):
$A_j'=A_i+A_j$;
$A_i'=A_j'-A_i=A_i+A_j-A_i=A_j$;
$A_j''=A_j'-A_i'=A_i+A_j-A_j=A_i$
A[j] := A[i] + A[j]; { A[j] := A[i]_и + A[j]_и }
A[i] := A[j] - A[i]; { A[i] := A[i]_и + A[j]_и - A[i]_и = A[j]_и }
A[j] := A[j] - A[i]; { A[j] := A[i]_и + A[j]_и - A[j]_и = A[i]_и }
Или при помощи операции XOR (используя $x \oplus x =0$):
$A_i'=A_i \oplus A_j$;
$A_j'=A_i' \oplus A_j = A_i \oplus A_j \oplus A_j = A_i$;
$A_i''=A_i' \oplus A_j'= A_i \oplus A_j \oplus A_i = A_j$
A[i] := A[i] xor A[j]; A[j] := A[i] xor A[j]; A[i] := A[i] xor A[j];
Сортировка подсчётом
Когда диапазон чисел которые нужно отсортировать невелик по сравнению с их количеством, проще (и быстрее всего) посчитать количество элементов каждого вида прямо при чтении входного файла и вывести их "по видам".
{$APPTYPE CONSOLE}
var
N : Integer; { Количество элементов в массиве }
Ai : Integer; { Элементы сортируемого массива }
P : array [1..1000] of Integer; { P[i] - количество элементов со значением i }
i : Integer; { Переменная цикла }
begin
{ Ввод исходного массива из файла }
Reset(Input,'sort.in');
Read(N); { Читаем количество элементов в массиве }
for i:=1 to N do begin
Read(Ai); { Читаем элементы массива }
{ Считаем количество элементов каждого вида прямо при чтении файла }
P[Ai] := P[Ai] + 1; { Увеличиваем P[Ai] - количество элементов равных Ai }
end;
{ Вывод отсортированного массива в файл }
Rewrite(Output,'sort.out');
for Ai:=1 to 1000 do { Пробегаем по всем возможным значениям }
for i:=1 to P[Ai] do { Выводим P[Ai] элементов Ai }
Write(Ai,' ');
end.
"Быстрая сортировка" QuickSort
Один из быстрых известных универсальных алгоритмов сортировки массивов (в среднем $O(n \log n)$ обменов при упорядочении $n$ элементов).
Описание алгоритма:
- выбрать элемент, называемый опорным.
- сравнить все остальные элементы с опорным, на основании сравнения разбить множество на три — «меньшие опорного», «равные» и «большие», расположить их в порядке меньшие-равные-большие.
- повторить рекурсивно для «меньших» и «больших».

var A : array [1..1000000] of Integer; { Сортируемый массив }
procedure QuickSort( left,right : Integer );
var i,j : Integer; m,temp: Integer;
begin
i := left;
j := right;
m := A[(left + right) div 2]; { Медиана }
m := A[random(right-left+1) + left]; { Рандомизированный вариант QuickSort }
{ При выборе разделяющего элемента случайным образом практически невозможно попасть на худший случай }
repeat
while A[i] < m do inc(i);
while m < A[j] do dec(j);
if i<=j then begin
{ Меняем A[i] и A[j] местами }
temp := A[i];
A[i] := A[j];
A[j] := temp;
inc(i); dec(j);
end;
until i>j;
if left < j then QuickSort(left,j); { Сортировка левого куска }
if i < right then QuickSort(i,right); { Сортировка правого куска }
end;
var
N : Integer; { Количество элементов в массиве }
i : Integer; { Переменная цикла }
begin
{ Ввод исходного массива из файла }
Reset(Input,'bubble.in');
Read(N); { Читаем количество элементов в массиве }
for i:=1 to N do Read(A[i]); { Читаем сам массив }
{ Сортировка элементов с 1 по N }
QuickSort(1, N);
{ Вывод отсортированного массива в файл }
Rewrite(Output,'bubble.out');
for i:=1 to N-1 do Write(A[i],' ');
Writeln(A[N]);
end.
Реализация на C/C++
#include <stdio.h>
#include <iostream>
using namespace std;
// Сортируемый массив
int A[1000000];
// Случайное целое число в заданном диапазоне
int rand(int low, int high){
return rand() % (high - low + 1) + low;
}
// Поменять местами 2 элемента массива i-ый и j-ый
void swap(int i, int j){
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
// Быстрая сортировка от элемента left до элемента right
void qsort(int left, int right){
// Массивы из 1-ого элемента сортировать незачем
if(left >= right) return;
int l = left, r = right; // Левая и правая границы
// Выбираем разделяющий элемент - может быть любым,
// но выбор случайного элемента устойчив к худшим случаям
int m = A[rand(left,right)];
do {
while(A[l] < m) l++; // Двигаем левую границу
while(A[r] > m) r--; // Двигаем правую границу
if(l <= r){ swap(l,r); l++; r--; } // Меняем элементы местами
} while (l <= r);
qsort(left,r); // Сортируем левую половину массива
qsort(l,right); // Сортируем правую половину массива
}
int main(){
freopen("qsort.in","r",stdin);
freopen("qsort.out","w",stdout);
// Ввод исходных данных
int N;
cin >> N;
for(int i=0;i<N;i++) cin >> A[i];
// Сортировка
qsort(0,N-1);
// Вывод отсортированного массива
for(int i=0;i<N;i++) cout << A[i] << " ";
// Код возврата
return 0;
}
Самая короткая рекурсивная реализация QSort на Python
# -*- utf-8 -*-
# Различные реализации QSort (сортировки Хоара)
from random import *
def qsort(A):
""" Самая коротка рекурсивная реализация qsort на Python """
# Если длина массива 0 или 1, то сразу возвращаем его как есть
if len(A) <= 1:
return A
# Выбираем разделяющий элемент
y = choice(A)
# Делим массив на три куска и вызываем qsort для них
return qsort([x for x in A if x < y]) + [x for x in A if x == y] + qsort([x for x in A if x > y])
# Генерируем случайный массив
A = [randint(-10 ** 10, 10 ** 10) for i in range(100000)]
assert qsort(A) == sorted(A)
Сортировка слиянием - MergeSort
Алгоритм сортировки, который упорядочивает списки (или другие структуры данных, доступ к элементам которых можно получать только последовательно) в определённом порядке. Эта сортировка — хороший пример использования принципа «разделяй и властвуй». Сначала задача разбивается на несколько подзадач меньшего размера. Затем эти задачи решаются с помощью рекурсивного вызова или непосредственно, если их размер достаточно мал. Наконец, их решения комбинируются, и получается решение исходной задачи.
3 этапа алгоритма:
- Сортируемый массив разбивается на две части примерно одинакового размера;
- Каждая из получившихся частей сортируется отдельно, например — тем же самым алгоритмом;
- Два упорядоченных массива половинного размера соединяются в один.
#include <assert.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
const int N = 100000; // Количество элементов в массиве
int A[N]; // Сортируемый массив
int T[N]; // Временный массив - буфер
// left - левая граница сортируемого участка
// right - правая граница
void MergeSort( int left, int right ){
// Если в массиве один элемент - сортировать нечего => сразу выходим
if(left >= right) return;
// Делим массив на 2 равные половинки
int m = (left + right) / 2; // Среднее арифметическое
MergeSort(left,m); // Сортируем левую половину
MergeSort(m+1,right); // Сортируем правую половину
// Объединяем результаты - операция Merge (слияние)
int l = left, r = m+1;
for(int i = left; i< = right; i++){
if(l > m) // Если левая половинка кончилась, то берём только из правой
T[i] = A[r++];
else if(r > right) // Если правая половинка кончилась, то берём только из правой
T[i] = A[l++];
else if(A[l] < A[r]) // Если обе не кончились, то берём минимальный элемент
T[i] = A[l++];
else
T[i] = A[r++];
}
// Копируем обратно из временно буфера в A
for(int i = left; i<=right; i++)
A[i] = T[i];
// Проверка, что всё верно отсортировано
for(int i = left; i< = right-1; i++)
assert( A[i] <= A[i+1] );
}
int main() {
// Заполняем случайным числами массив A
for(int i=0;i<N;i++) A[i] = rand() % 1000;
// Вызов сортировки всех элементов
MergeSort(0,N-1);
// Вывод отсортированного массива
for(int i=0;i<N;i++) cout << A[i] << " ";
cout << endl;
return 0;
}
Вариант с использованием шаблонов:
// a - сортируемый массив, его левая граница lb, правая граница ub
template<class T>
void mergeSort(T a[], long lb, long ub) {
long split; // индекс, по которому делим массив
if (lb < ub) { // если есть более 1 элемента
split = (lb + ub)/2; // Центр обрабатываемого куска массива
mergeSort(a, lb, split); // сортировать левую половину
mergeSort(a, split+1, last);// сортировать правую половину
merge(a, lb, split, ub); // слить результаты в общий массив
}
}
template<class T>
void merge(T a[], long lb, long split, long ub) {
// Слияние упорядоченных частей массива в буфер temp
// с дальнейшим переносом содержимого temp в a[lb]...a[ub]
// текущая позиция чтения из первой последовательности a[lb]...a[split]
long pos1=lb;
// текущая позиция чтения из второй последовательности a[split+1]...a[ub]
long pos2=split+1;
// текущая позиция записи в temp
long pos3=0;
T *temp = new T[ub-lb+1];
// идет слияние, пока есть хоть один элемент в каждой последовательности
while (pos1 <= split && pos2 <= ub) {
if (a[pos1] < a[pos2])
temp[pos3++] = a[pos1++];
else
temp[pos3++] = a[pos2++];
}
// одна последовательность закончилась -
// копировать остаток другой в конец буфера
while (pos2 <= ub) // пока вторая последовательность непуста
temp[pos3++] = a[pos2++];
while (pos1 <= split) // пока первая последовательность непуста
temp[pos3++] = a[pos1++];
// скопировать буфер temp в a[lb]...a[ub]
for (pos3 = 0; pos3 < ub-lb+1; pos3++)
a[lb+pos3] = temp[pos3];
delete temp[ub-lb+1];
}
Реализация на Delphi
var
N : integer; { Количество элементов }
A, T: array [1 .. 1000000] of integer; { Сортируемый массив }
Inversions: int64 = 0; { Для подсчёта числа инверсий }
procedure MSort(l, r: integer);
var
e, i, j, k: integer;
begin
if l >= r then
exit;
k := (l + r) div 2; { Средний элемент }
MSort(l, k);
MSort(k + 1, r);
i := l; { Первая половина массива }
j := k + 1; { Вторая половина массива }
{ Слияние }
for e := l to r do begin { Куда записываем результат }
if i > k then begin
T[e] := A[j];
Inc(j);
Inc(Inversions,k-i+1); { Подсчёт инверсий }
end else if j > r then begin
T[e] := A[i];
Inc(i);
end else if A[i] <= A[j] then begin
T[e] := A[i];
Inc(i);
end else begin
T[e] := A[j];
Inc(j);
Inc(Inversions,k-i+1); { Подсчёт инверсий }
end;
end;
{ Обратное копирование из T }
for e := l to r do
A[e] := T[e];
end;
Heap - куча
Бинарное дерево - у каждого родителя максимум 2 потомка
Предок: $Parent = \frac{i}{2}$. Потомки: левый $l = 2i$, правый $r = 2i+1$.
Основное свойство кучи, любая функция, которая допускает линейное упорядочивание.

HeapSort - сортировка при помощи кучи
{$APPTYPE CONSOLE}
uses SysUtils;
var
N : integer; { Размер кучи }
A : array [1..1000000] of int64;
size : Integer;
{ Обмен местами элементов с индексами i и j }
procedure Swap( i,j : integer );
var T : int64;
begin
T := A[i];
A[i] := A[j];
A[j] := T;
end;
procedure Heapify( i:integer );
var r,l : integer;
begin
l := i*2; { Левый потомок }
r := i*2+1; { Правый потомок }
if l <= N then
if A[l] > A[i] then begin
Swap(l,i);
Heapify(l);
end;
if r <= N then
if A[r] > A[i] then begin
Swap(r,i);
Heapify(r);
end;
end;
{ Построение кучи }
procedure BuildHeap;
var i: integer;
begin
for i := N downto 1 do
Heapify(i);
end;
{ Сортировка кучей }
procedure HeapSort;
begin
BuildHeap;
size := N;
while N > 1 do begin
Swap(N,1); { Меняем местами максимальный элемент и элемент в конце кучи }
Dec(N); { Уменьшаем кучу }
Heapify(1); { Восстанавливаем основное свойство кучи }
end;
end;
var i : integer;
begin
Reset(Input,'heapsort.in');
Rewrite(Output,'heapsort.out');
{ Чтение исходных данных }
Read(N);
for i := 1 to N do
Read(A[i]);
{ Вызов сортировки }
HeapSort;
{ Запись отсортированного массива }
for i := 1 to size do
Write(A[i],' ');
end.
#include <assert.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
const int N = 80;
// Массив, который на время станет двоичной кучей, чтобы потом стать отсортированным массивом
int A[N];
// Текущий размер кучи, т.е. сколько первых элементов массива A сейчас являются двоичной кучей
int HeapSize;
// Поменять местами 2 элемента массива i-ый и j-ый
inline void swap(int i, int j){
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
// Выполняется ли основное свойство кучи?
bool heap_function(int parent, int child){
return A[parent] > A[child];
}
// Мы хотим сверху получить самый минимум
// parent - корневой элемент
// Мы обновили элемент parent и хотим чтобы куча снова была кучей
void heap(int parent){
// -= Левый потомок =-
int left = 2*parent + 1;
if(left < HeapSize) // Левый потомок есть
if(!heap_function(parent,left)){ // Проверка основного свойства кучи
// Меняем их местами
swap(left,parent);
heap(left); // Проталкиваем значение дальше
}
// -= Правый потомок =-
int right = 2*parent + 2;
if(right < HeapSize) // Правый потомок есть
if(!heap_function(parent,right)){ // Проверка основного
// Меняем их местами
swap(right,parent);
heap(right);
}
}
void HeapSort(){
HeapSize = N;
// Строим кучу
for(int i=N-1;i>=0;i--)
heap(i);
// Выводим очередной элемент
for(int i=0;i<N;i++){
// Ставим элемент с вершины кучи в конец массива
swap(0,HeapSize-1);
// Уменьшаем кучу
HeapSize--;
// Упорядочиваем вершину кучи
heap(0);
}
// Проверяем, что массив отсортирован
for(int i=0;i<N-1;i++)
assert( A[i] <= A[i+1] );
}
int main() {
// Заполняем случайным числами массив A
for(int i=0;i<N;i++) A[i] = rand() % 1000;
// Вызов сортировки
HeapSort();
// Вывод отсортированного массива
for(int i=0;i<N;i++) cout << A[i] << " ";
cout << endl;
return 0;
}
Алгоритмы сортировки в стандартных библиотеках
Использование qsort в C/C++
В C/C++ в STL (Standard Template Library, которую, как правило, можно использовать на олимпиадах) доступна библиотека algorithm в которой в числе прочего есть реализованная процедура сортировки sort.
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <string.h>
#include <vector>
#include <algorithm>
#define MaxN 5000 using namespace std; int a[MaxN + 3]; int n; int main() { freopen("bubble.in", "rt", stdin); // Открываем входной файл freopen("bubble.out", "wt", stdout); // Открываем выходной файл // Чтение исходных данных scanf("%d", &n); // Чтение количества элементов int i; for(i = 0; i < n; i++) scanf ("%d", &a[i]); // Вызов процедуры сортировки sort(a, a + n); // Вывод отсортированного массива в файл for(i = 0; i < n - 1; i++) printf("%d ", a[i]); printf("%d\n", a[n - 1]); return 0; }
В Python у списков (list) есть встроенный метод sort
# -*- coding: windows-1251 -*-
import sys
sys.stdin = open('bubble.in') # Открываем входной файл на чтение
sys.stdout = open('bubble.out', 'w') # Открываем выхолной файл на запись
# Ввод исходных данных
N = input() # Считываем из первой строки целое число
A = map(int,raw_input().split()) # Считываем строку, делим её на элементы и каждый приводим к типу целое
# Сортировка массива
A.sort()
# Вывод массива
for i in A:
print i,