
- •Сортировки
- •Введение
- •Формулировка задачи сортировки
- •Простейшие методы сортировки
- •Алгоритм линейной сортировки (метод прямого выбора)
- •1 Способ 2 способ
- •Алгоритм сортировки обменом (метод "Пузырька")
- •Усовершенствованная "пузырьковая" сортировка
- •"Шейкер" - сортировка
- •Сортировка подсчетом
- •Алгоритм сортировки вставками (метод прямого включения)
- •Размещение путем сравнения и обмена (просеивание)
- •Размещение путем поиска места и вставки
- •Более сложные и более эффективные методы сортировки
- •Алгоритм сортировки Шелла (метод h-сортировки)
- •Обменная сортировка с разделением (сортировка Хоара)
- •Сортировка методом слияний
- •Простое слияние
- •Естественное двухпутевое слияние
- •Рекурсивный алгоритм слияния
- •Слияние списков
- •Алгоритм сортировки бинарными вставками
- •Сортировка с помощью двоичного включения
- •Лексикографическая сортировка
- •Топологическая сортировка
- •Поразрядная сортировка
- •Пирамидальная сортировка
- •Рекурсивная сортировка
- •Сравнительная характеристика методов сортировки
- •Классификация задач с применением сортировок
- •1. Задачи заполнения
- •2. Задачи анализа
- •3. Задачи поиска
- •4. Задачи перестановки
- •Литература
Сортировка методом слияний
Существует еще один метод сортировки элементов массива, эффективность которого сравнительно велика, - метод слияний.
Исторически метод слияния является одним из первых методов, предложенных для сортировки на ЭВМ еще в 1954 году, автором его является Джон фон Нейман.
Метод сортировки "слиянием" состоит в разбиении данного массива на несколько частей, которые сортируются по отдельности и впоследствии "сливаются" в одну.
Пусть массив а [1..n] разбивается на части длиной k, тогда первая часть - a[1], a[2], ..., a[k], вторая- a[k+l], a[k+2],..., a[2k] и так далее. Если n не делится на k, то в последней части будет менее k элементов.
После того как массивы-части упорядочены, можно объединить их в упорядоченные массивы-части, состоящие не более чем из 2k элементов, которые далее объединить в упорядоченные массивы длиной не более 4k,и так далее, пока не получится один упорядоченный массив.
Таким образом, чтобы получить отсортированный массив этим методом, нужно многократно " сливать" два упорядоченных отрезка массива в один упорядоченный отрезок. При этом другие части массива не затрагиваются.
В более узком смысле слияние означает объединение двух или более упорядоченных массивов в один упорядоченный массив. Например, слияние двух массивов – 5 8 9 и 1 3 4 10 дает массив 1 3 4 5 8 9 10.
Сделать это можно сравнив два наименьших элемента, вывести наименьший, а затем повторить эту процедуру. Необходимо позаботиться о действиях на случай когда исчерпается один из массивов.
Рассмотрим этот метод на примере указанных двух массивов (5, 8, 9) и (1, 3, 4, 5, 8, 9, 10).
5 8 9
1 3 4 10
выбираем 1
5 8 9
3 4 10
выбираем 3
5 8 9
4 10
выбираем 4
5 8 9
10
выбираем 5
8 9
10
выбираем 8
9
10
выбираем 9
пустой массив
10
выбираем 10
Слияние намного более простая процедура, чем сортировка, так как общий объем работы по существу пропорционален количеству элементов m+n, но при этом используется дополнительный массив.
Алгоритм двухпутевого слияния реализован в следующей программе для отсортированных массивов x[1..m], y[1..n] с результирующим массивом z[1..n+m]. Небольшое упрощение введено добавлением в конец исходных файлов искусственных стражей xm+1=yn+1=.
Procedure Merge(x:vectorm;y:vectorn; var z: vectornm);
Var i,j,k: integer;
Begin
i:=1; j:=1;
x[m+1]:=Maxint; y[n+1]:=Maxint;
for k:=1 to n+m do
if x[i]<=y[j] then begin z[k]:=x[i]; inc(i) end
else begin z[k]:=y[j]; inc(j) end;
end;
Простое слияние
Алгоритм слияния, предложенный фон Нейманом, упорядочивает пары соседних чисел. Затем упорядоченные пары сливаются в упорядоченные четверки, восьмерки и т.д. Для хранения упорядоченных серий используется вспомогательный массив Y.
X 1 7 3 6 4 2 8 5
Y 1 7 3 6 2 4 5 8
X 1 3 6 7 2 4 5 8
Y 1 2 3 4 5 6 7 8
Этот алгоритм называется иногда вверх-направленным слиянием, учитывая направление сливаемых пар.
При слиянии можно выбирать не соседние пары, а элементы, стоящие на концах массива. Алгоритм простого двухпутевого слияния берет элементы с двух концов исходного массива, сливает их в упорядоченные пары и отправляет по очереди в два конца выходного массива.
X 1 7 3 6 4 2 8 5
Y 1 5 2 3 6 4 8 7
X 1 5 7 8 6 4 3 2
Y 1 2 3 4 5 6 7 8
Чтобы не запутаться в двух виртуальных массивах, будем использовать один массив двойной длины array[1..2*n]. При этом сначала исходный массив находится в первой половине, в выходной – во второй, а затем наоборот.
Заведем булевскую переменную Up, которая определяет направление слияния: true – из первой половины во вторую, false – из второй половины в первую. Индексы i, j - отвечают за место, из которого берутся элементы для слияния, а k, l – за место куда пересылаются элементы. Переменная P определяет длину сливаемых серий (1, 2, 4, …).
Procedure merge(var x: vector);
Var up: boolean;
i, j, k, l: integer;
P: integer;
Begin
Up:=true;
p:=1;
Repeat
if up then
Begin
i:=1;j:=n; k:=n+1; l:=2*n;
end
else
Begin
i:=n+1; j:=2*n; k:=1; l:=n
end;
{слияние p-серии из i, j в к, l}
Up:=not up;
P:=2*p
Until p>=n;
End;
Слияние происходит следующим образом: сначала пересылка идет по адресу k, при этом индекс увеличивается на единицу, затем по адресу l, и индекс уменьшается на 1. Для упрощения будем считать, что пересылка всегда идет по адресу k с шагом h, но после каждого слияния будем чередовать h (либо +1, либо -1), а также менять местами значения k и l.
h:=1; m:=n;
Repeat
q:=p; r:=p; m:=m-2*p;
{слияние q элементов из i и r элементов из j}
h:=-h;
{обмен k, l};
Until m=0;
Сама процедура слияния должна предусмотреть копирование оставшегося остатка:
While (q<>0) and(r<>0) do
if x[i]<x[j]
then begin x[k]:=x[i]; k:=k+h; i:=i+1; q:=q-1 end
Else begin x[k]:=x[j]; k:=k+h; j:=j-1; r:=r-1 end;
{копирование остатков из i или j}
Окончательный текст программы
Procedure merge(var x: vector);
Var up: boolean;
I, j, k, l, t: integer;
P, h, q, r, m: integer;
Begin
Up:=true;
p:=1;
Repeat
m:=n; {общее число элементов во
входных последовательностях,
которые осталось слить}
H:=1;
if up
then
Begin
i:=1;j:=n; k:=n+1; l:=2*n;
end
else
Begin
i:=n+1; j:=2*n; k:=1; l:=n
end;
Repeat
if m>= p then q:=p
else q:=m; m:=m-q;
if m>= p then r:=p
else r:=m; m:=m-r;
While (q<>0) and(r<>0) do
if x[i]<x[j]
then
begin
x[k]:=x[i]; k:=k+h; i:=i+1; q:=q-1
end
else
begin
x[k]:=x[j]; k:=k+h;
j:=j-1; r:=r-1
end;
{копирование остатков}
While r<>0 do
Begin
A[k]:=a[j]; k:=k+h;
j:=j-1; r:=r-1
End;
While q<>0 do
Begin
A[k]:=a[i]; k:=k+h;
i:=i+1; q:=q-1
End;
H:=-h; T:=k; k:=l; l:=t;
Until m=0;
Up:=not up;
P:=2*p
Until p>=n;
if not up then
for i:=1 to n do x[i]:=x[i+n]
End;
Поскольку на каждом проходе p удваивается, то сортировка требует [log2N] проходов. При каждом проходе перемещаются все N элементов. Поэтому характеристика M для сортировки слиянием оценивается как N*[log2N].
Число сравнений C меньше, чем М, так как при копировании остатков сравнения не проводятся.