Лекция 11 февраля 2011 г.
Дополнительно о сортировках
Далее будет рассмотрено несколько видов сортировок, которые работают статистически быстрее всех ранее изученных. Слово «статистически» означает, что быстрота наблюдается в большинстве случаев заполнения массивов случайными значениями. Однако в некоторых, редких, т.н. вырожденных случаях, быстрые сортировки могут утратить свою скорость. Примером вырождения можно считать расположение элементов массива в порядке, обратном требуемому.
В последующих текстах предполагается, что сортируемый массив объявлен в виде
mA: array[1..2*nMax] of single;
где nMax – константа.
Элементы, требующие упорядочения (по возрастанию) занимают места с 1-го по nMax-е. Следующие nMax мест зарезервированы под временное хранение данных.
Быстрая сортировка
procedure RecursiveQuickSort;
procedure InnerSort(L, R: longint);
var
i, j: longint;
w, x: single;
begin
i:=L; j:=R;
x:=mA[(L+R) div 2];
repeat
while mA[i]<x do i:=i+1;
Inc(nC);
while x<mA[j] do j:=j-1;
if i<=j then
begin
w:=mA[i]; mA[i]:=mA[j]; mA[j]:=w;
Inc(i); Dec(j);
end
until i>j;
if L<j then InnerSort(L, j);
if i<R then InnerSort(i, R);
end; // InnerSort
begin
tStart:=Time;
InnerSort(1, n);
tFinish:=Time;
tElapsed:=(tFinish-tStart)*86400.0;
end;
Сортировка слиянием
Идея сортировки состоит в следующем.
Сортируемый массив из элементов делится пополам (ясно, что это имеет смысл при ). Каждая из его половин сортируется, затем две отсортированные половины «сливаются», образуя отсортированное целое.
Фрагмент процедуры, выполняющей такую сортировку, может выглядеть так:
i:=1; // Место, с которого начинается левая половина
q := (n+1) div 2;
j:=q+1; // Место, с которого начинается правая половина
k:=n; // Место, после которого временно помещается «слитый массив»
СортировкаЛевойПоловины;
СортировкаПравойПоловины;
while (i<=q) and (j<=n) do
// Цикл выполняется, пока обе половины ещё не иссякли
begin
Inc(k);
if mA[i]<mA[j] then
begin mA[k]:=mA[i]; Inc(i); end
else
begin mA[k]:=mA[j]; Inc(j); end;
end;
while i<=q do
// Цикл выполняется, пока левая половина не иссякла
// (правая половина исчерпана)
begin
Inc(k); mA[k]:=mA[i]; Inc(i);
end;
while j<=r do
// Цикл выполняется, пока правая половина не иссякла
// (левая половина исчерпана)
begin
Inc(k); mA[k]:=mA[j]; Inc(j);
end;
for i:=1 to n do
// Цикл выполняется для переброски массива из временного места в постоянное
mA[i]:=mA[i+n];
end;
Ясно, что вызов процедур
СортировкаЛевойПоловины;
СортировкаПравойПоловины;
лучше заменить на рекурсивный вызов самой процедуры, которую мы обсуждаем. Тривиальным случаем следует считать тот, которому соответствует требование отсортировать набор из одного элемента массива.
Ниже приведен полный текст процедуры MergeSort – процедуры сортировки слиянием.
procedure MergeSort;
procedure Merge(p, q, r: longint);
var
i, j, k: longint;
begin
i:=p;
j:=q+1;
k:=n;
while (i<=q) and (j<=r) do
begin
Inc(k);
if mA[i]<mA[j] then
begin
mA[k]:=mA[i];
Inc(i);
end
else
begin
mA[k]:=mA[j];
Inc(j);
end;
Inc(nM);
end;
while i<=q do
begin
Inc(k);
mA[k]:=mA[i];
Inc(i);
end;
while j<=r do
begin
Inc(k);
mA[k]:=mA[j];
Inc(j);
end;
k:=n;
for i:=p to r do
begin
Inc(k);
mA[i]:=mA[k];
end;
end;
procedure MergeSortAux(p, r: longint);
var
q: longint;
begin
if p>=r then exit;
q:=(p+r) div 2;
MergeSortAux(p, q);
MergeSortAux(q+1, r);
Merge(p, q, r);
end;
begin
MergeSortAux(1, n);
end;
Сортировка естественным слиянием
Идея сортировки состоит в следующем.
Сортируемый массив из элементов разбивается на подмассивы, каждый из которых «случайно» оказался отсортированным. Пусть имеется в виду сортировка по возрастанию Тогда правая граница подмассива – это индекс элемента массива, который является последним или после которого возрастание «вдруг» сменяется убыванием.
Такой частично упорядоченный массив получается простым соединением в одну длинную цепь таких наборов элементов, где каждый набор уже был сознательно отсортирован.
Даже если массив не упорядочивался частично, вероятность, что все отсортированные подмассивы состоят из одного элемента, достаточно мала.
Слушателям предлагается самостоятельно оценить, чему именно равна такая вероятность.
Пример.
Исходный массив:
3 5 16 18 1 13 17 2 9 11 4 12 7 10 15
Поиск упорядоченных подмассивов и их слияние (по два)
Ниже приведен полный текст процедуры NaturalMergeSort – процедуры сортировки слиянием.
procedure NaturalMergeSort;
var
i, nB: longint;
mB: array[0..2] of longint;
bBound, bSorted: boolean;
procedure Merge(p, q, r: longint);
var
i, j, k: longint;
begin
i:=p;
j:=q+1;
k:=n;
while (i<=q) and (j<=r) do
begin
Inc(k);
Inc(nC);
if mA[i]<mA[j] then
begin
mA[k]:=mA[i];
Inc(i);
end
else
begin
mA[k]:=mA[j];
Inc(j);
end;
Inc(nM);
end;
while i<=q do
begin
Inc(k);
mA[k]:=mA[i];
Inc(nM);
Inc(i);
end;
while j<=r do
begin
Inc(k);
mA[k]:=mA[j];
Inc(nM);
Inc(j);
end;
k:=n;
for i:=p to r do
begin
Inc(k);
mA[i]:=mA[k];
end;
end;
begin
while True do
begin
nB:=0;
mB[0]:=1;
bSorted:=True;
for i:=1 to n do
begin
bBound:=False;
if i=n then
bBound:=True
else
if mA[i]>mA[i+1] then
begin
bBound:=True;
bSorted:=False;
end;
if bBound then
begin
Inc(nB);
mB[nB]:=i;
end;
if nB=2 then
begin
Merge(mB[0], mB[1], mB[2]);
nB:=0;
mB[0]:=mB[2];
end;
end;
if bSorted then Break;
end;
end;
Сортировка слиянием в файле
Ниже приведен текст процедуры MergeSortInFile, проводящей сортировку в файле с именем FileOfSingleName. Процедура похожа на процедуру MergeSort, некоторые из отличий пояснены комментариями (синим цветом).
procedure MergeSortInFile;
const
FileOfSingleName='p0.dat';
FileOfSingleNameAux='p0.tmp';
var
F0, F1, F2: file of single;
procedure Merge(p, q, r: longint);
var
i, j, k: longint;
mAi, mAj: single;
begin
Reset(F1);
Reset(F2);
Reset(F0);
i:=p;
j:=q+1;
// k:=n;
Seek(F1, i-1);
Read(F1, mAi);
Seek(F2, j-1);
Read(F2, mAj);
while (i<=q) and (j<=r) do
begin
// k:=k+1;
if mAi<mAj then
begin
Write(F0, mAi);
// mA[k]:=mA[i];
Inc(i);
if i<=q then Read(F1, mAi);
end
else
begin
Write(F0, mAj);
// mA[k]:=mA[j];
Inc(j);
if j<=r then Read(F2, mAj);
end;
end;
while i<=q do
begin
// k:=k+1;
// mA[k]:=mA[i];
Write(F0, mAi);
Inc(i);
if i<=q then Read(F1, mAi);
end;
while j<=r do
begin
// k:=k+1;
// mA[k]:=mA[j];
Write(F0, mAj);
Inc(j);
if j<=r then Read(F2, mAj);
end;
Close(F0);
Close(F2);
Close(F1);
Reset(F0);
Reset(F1);
i:=p;
Seek(F1, i-1);
// k:=n;
for i:=p to r do
begin
// k:=k+1;
// mA[i]:=mA[k];
Read(F0, mAi);
Write(F1, mAi);
end;
Close(F1);
Close(F0);
end;
procedure MergeSortAux(p, r: longint);
var
q: longint;
begin
if p>=r then exit;
q:=(p+r) div 2;
MergeSortAux(p, q);
MergeSortAux(q+1, r);
Merge(p, q, r);
end;
begin
Assign(F1, FileOfSingleName);
Assign(F2, FileOfSingleName);
Assign(F0, FileOfSingleNameAux);
MergeSortAux(1, n);
end;
Файлы без типа (напоминание)
Var
F: File;
Процедуры и функции для работы с файлами без типа
procedure Assign(var F: File);
procedure Close (var F: File);
procedure Seek(var F: File, RecordNumber: Longint);
procedure Truncate(var F: File);
procedure Read(var F: File; <Список ввода>);
procedure Write(var F: File; <Список вывода>);
function Eof(var F: File): boolean;
Отличия:
procedure Reset(var F: File [, RecordSize: Longint]);
procedure Rewrite(var F: File [, RecordSize: Longint]);
Если параметр RecordSize указан, он задаёт длину записи (в байтах).
Если он не указан, считается, что RecordSize=128. Вот так.
Новые процедуры:
procedure BlockRead(var F: File; var Buffer; Count: Integer
[; var AmtTransferred: Integer]);
Параметр Buffer – имя любой переменной (например, имя большого массива), в которую будут читаться данные из файла.
Параметр Count – количество записей, которое следует прочесть из файла. Таким образом, будет предпринята попытка прочесть Count* RecordSize байт из файла. Если в переменную Buffer такое количество не вместится (т.е. будет испорчена память за переменной Buffer), ответственность за тяжкие последствия несёт программист.
Параметр AmtTransferred, если он присутствует, показывает, сколько в действительности записей удалось прочитать. AmtTransferred < Count в том случае, если… . Впрочем, сообразите сами.
procedure BlockWrite(var F: File; var Buffer; Count: Integer
[; var AmtTransferred: Integer]);
Параметр Buffer – имя любой переменной, из которой будут записываться данные в файл.
С прочими параметрами, надеюсь, всё ясно.