Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ВНЕШНИЕ СОРТИРОВКИ.docx
Скачиваний:
19
Добавлен:
26.09.2019
Размер:
57.85 Кб
Скачать

Естественное слияние

Сортировка, при которой всегда сливаются две самые длинные из возможных серий, называется естественным слиянием. В сортировке естественным слиянием объединяются серии максимальной, а не заранее фиксированной длины. Признаком конца серии в этом случае является результат сравнения двух соседних элементов, если они не упорядочены, то серия закончилась. В каждом проходе число серий уменьшается в Nраз (если количество серий во вспомогательных файлах одинаково), а общее число пересылок не более, чем n × [log n]. Ожидаемое число сравнений значительно больше, поскольку кроме сравнений, необходимых для слияния, требуются дополнительные сравнения для определения конца серии.

Так же как и простое слияние, сортировка естественным слиянием может быть двухпутевой или многопутевой, двухфазной или однофазной.

Обратим внимание на то, что сортировка проходит эффективно, если количество серий, распределенных на вспомогательные файлы, приблизительно одинаковое. Поскольку признаком конца серии в естественном слиянии является результат сравнения двух соседних элементов, две или несколько серий, распределенных друг за другом во вспомогательный файл, могут объединиться в одну

Естественное слияние, у которого после фазы распределения количество серий во вспомогательных файлах отличается друг от друга не более чем на единицу, называется сбалансированным, в противном случае речь идет о несбалансированном слиянии. Чтобы в сбалансированном слиянии серии распределялись корректно, необходимо во время записи очередной серии в файл выполнять следующую проверку: если серия является продолжением предыдущей, то записать в этот файл не одну, а две серии. 

Не сбалансированное:

По сравнению с простым,меняются следующие процедуры:

Двухпутевое:

Procedure StartRead;

Begin

Reset(f);

FEof:=system.eof(f);

Feor:=system.eor;

If not FEof then read(f,fElem);

End;

Procedure StartRun

f.eor:=F.Eof;

procedure Copy

begin

y.Elem:=self.Elem;

write(y.f,y.Elem);

f.eof:=sustem.eof(f);

if not f.eof then read(f,f.elem);

f.eor:=f.eof or (f.elem<y.elem);

end;

Многофазная сортировка.

сначала происходит начальное распределение данных, а затем используется (N 1)-путевое слияние, то есть все время серии берутся из N — 1 файлов и сливаются в один пустой. Чтобы (N  1)-путевое слияние стало возможным, необходимо вначале распределить данные особым образом: распределение происходит на N — 1 вспомогательный файл, при этом количество серий в каждом из файлов зависит от количества вспомогательных файлов и количества серий в исходном файле. Поскольку последний параметр заранее не известен, то распределение серий происходит по уровням. Если в исходном файле одна серия, то на нулевом уровне все данные должны быть записаны в один файл. На всех остальных уровнях количество серий, которое должно быть распределено на каждый из вспомогательных файлов, определяется формулами:

 

 

где L — номер уровня, а N — количество вспомогательных файлов.

Нетрудно заметить, что количество серий в исходном файле может отличаться от такого идеального распределения. В этом случае считается, что в распоряжении имеются фиктивные (пустые) серии, причем их столько, что сумма пустых и реальных серий равна идеальной сумме. Заметим, что слияние пустых серий из всех N — 1 источников означает, что никакого реального слияния не происходит, вместо него в выходную последовательность записывается результирующая пустая серия. Отсюда можно заключить, что пустые серии нужно как можно более равномерно распределять по N — 1 последовательностям. Алгоритм "горизонтального распределения пустых серий", позволяет эффективно распределить серии.

1. Подсчитать число серий, которое должно быть распределено в каждый из вспомогательных файлов на данном уровне (aiL i = 1, ..., N  1).

2. Подсчитать количество серий, которое надо дописать в каждый из вспомогательных файлов на данном уровне, чтобы получить число серий, равное aiL: diL = aiL - aiL-1. После того как это число определено, считаем, что в каждый из вспомогательных файлов записано diL пустых серий.

3. Пока не кончился исходный файл и пока не исчерпаны все пустые серии на данном уровне, заменяем пустую серию на реальную; при этом записываем ее в тот файл, в котором количество пустых серий максимально.

4. Если исходный файл не кончился, осуществляется переход на следующий уровень: L L + 1 и переход к пункту 1.

Когда все данные распределены, происходит слияние данных: сначала сливаются данные из вспомогательных файлов с номерами 1, 2, ..., N 1 в N-ый вспомогательный файл, при этом файл с номером N 1 станет пустым (слияние на L-ом уровне). Далее номер уровня уменьшается и происходит слияние файлов с номерами 1, 2, ..., N — 1 в (N  1)-ый файл, т. е. слияние на L  1 уровне. Так продолжается до тех пор, пока номер уровня не станет равным 0. Заметим, что во время слияния на очередном уровне, если не кончились пустые серии, то в первую очередь объединяются они, поскольку их объединение требует меньше затрат.

Procedure Select (номер файла,куда пойдет запись реальной серии)

Var I,z:integer;

Begin

If d[j]<d[j+1] then inc(j)

Else begin

If d[j]= 0 then begin \\ нет фиктивн серий

Level:=level+1;

Z:=a[1];

For i:=1 to N-1 do begin

D[i]:=z+a[i+1]-a[i];

A[i]:=z+a[i+1]; end; end;

J:=1; end;

D[j]:=d[j]-1; end;

BEGIN

F0:=TSequance.Create (‘1.tmmp’);

For i:= 1 to N do

F[j]:=TSequance.Create (IntToStr(i)+’.tmp’)

Level:=1;

For i:=1 to n-1 do begin

A[i]:=1; d[i]:=1;

F[i].startWrite; end;

A[n]:=0; d[n]:=0;

F0.startRead; j:=1; \\ куда пишем серию

Repeat

Select; f0.CopyRun(f[j]);

Until f0.eof or (j=n-1);

While not f0.eof do begin

Select;

If f[j].elem<=f0.elem then begin

F0.copyrun(f[j]);

If f0.eof then inc(d[j]);

Else f0.copyrun(f[j]); end;

Else f0.copyrun(f[j]); end;

For i:=1 to n-1 do begin

F[i].StartRead; t[i]:=I; end;

T[n]:=n;

Repeat

Z:=a[n-1];

D[n]:=0;

F[t[n]].startwrite;

Repeat

K:=0;

For i:=1 to n-1 do

If d[i]>0 then dec(d[i])

Else begin

Inc(k); ta[k]:=i; end;

If k=0 then \\ во всех есть фиктив серии

Inc(d[n]);

Else repeat

i:=1; mx:=1; \\ мин в карте индексов

min:=f[ta[1]];

while (i<k) do begin

inc(i); x:=f[ta[i]].elem;

if x<min then begin

min:=x; mx:=I; end; end;

f[ta[mx]].copy(f[t[n]]);

if f[ta[mx]].eor then begin

ta[mx]:=ta[k]; dec(k); end;

until k=0;

dec(z);

until z=0;

f[t[n]].startread;

tn:=t[n]; dn:=d[n]; i:=a[n-1];

for i:= n downto 2 do begin

t[i]:=t[i-1]; d[i]:=d[i-1];

a[i]:=a[i-1]-z; end;

t[1]:=tn; d[1]:=dn; a[1]:=z;

f[t[n]].startwrite;

level:=level-1;

until level=0;

f[t[1]].copyAll(f0);

for i:=1 to N do begin

f[i].clear; f[i].Erase;

f[i].destroy; end;

fo.destroy;

END

Каскадная сортировка

похожа на многофазную сортировку. Отличие заключается в самом процессе слияния: сначала проводится (N  1) — путевое слияние в N-ый файл до тех пор, пока файл с номером N 1 не опустеет; затем (N-ый файл уже не затрагивается) проводится (N — 2) — путевое слияние в (N 1)-ый файл; затем (N 3) — путевое в (N 2)-ый файл; ...; потом двухпутевое слияние в третий файл, а в конце копирование из файла с номером 2 в первый файл. Следующий проход работает по аналогичной схеме, только в обратном направлении. Поскольку слияние в каскадной сортировке отличается от слияния в многофазной сортировке, то и начальное распределение данных осуществляется по-другому — количество серий в каждом из вспомогательных файлов должно быть другим. Оно определяется в соответствии с формулами:

 

 

или 

 

Н. Вирт пишет: "Хотя такая схема выглядит хуже многофазного слияния, поскольку некоторые последовательности "простаивают", и есть просто операции копирования, тем не менее, она, что удивительно, оказывается лучше многофазной сортировки при очень больших файлах и шести или более последовательностях". Заметим, что при хорошей реализации алгоритма каскадной сортировки, от копирования можно избавиться, используя карты индексов.