Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
delphi / песни о паскале.pdf
Скачиваний:
66
Добавлен:
26.03.2016
Размер:
5.16 Mб
Скачать

Глава 43 Сортировка по-взрослому

процедуру, но в другое место — следующее за вызовом. Окончательный выход из процедуры в главную программу случится лишь по завершении сортировки всего массива.

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

Разгадка в том, что при каждом входе в процедуру для её параметров и локальных переменных выделяется новый участок памяти. Теперь это будут уже другие параметры и локальные переменные, но с прежними названиями. Однако предыдущие их значения не теряются, а сохраняются в памяти, называемой стеком

(Stack).

Что такое стек и как он работает? Случалось ли вам паковать рюкзак или глубокую сумку? Тогда вы знакомы со стеком. Всё, что уложено в рюкзак, будет извлекаться из него в обратном порядке. Так же устроен и стек: при каждом вызове процедуры память для параметров и локальных переменных выделяется на его вершине. Эти новые значения временно закрывают предыдущие значения параметров, и так происходит при каждом входе в процедуру.

При выходе из неё последние значения параметров и локальных переменных удаляются с вершины стека, и тогда вновь открываются ранее скрытые. Так процедура «вспоминает» о неоконченной работе, и продолжает действия с параметрами, сохраненными в стеке ранее. В некоторый момент стек пустеет (когда все вещи из рюкзака вынуты), и тогда происходит окончательный выход из процедуры в главную программу.

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

Но стоп! О стеке поговорим позже, а сейчас займемся делом. Введите программу P_43_2 и убедитесь в её правильной работе.

Алгоритмы, на старт!

Теперь в нашем распоряжении есть три процедуры сортировки, не устроить ли состязание между ними? На старт вызываются:

BubbleSort — «пузырьковая» сортировка,

FarmSort — «фермерская» сортировка,

QuickSort — быстрая сортировка.

Время «спортсменов» мы будем засекать не по часам. И вы знаете, почему: мы сравниваем алгоритмы, а не компьютеры. В 42-й главе, где сравнивались

324

Глава 43 Сортировка по-взрослому

алгоритмы поиска, мы оценивали время по количеству выполненных шагов. Поступим и здесь похожим образом. Вспомним, в чем состоит сортировка? — в сравнениях и перестановках. И много-много раз... Значит, трудоемкость сортировки можно оценить количеством этих двух операций — сравнений и перестановок, надо их только посчитать.

Что ж, дело нехитрое, сейчас посчитаем, — перед вами программа для наших опытов (P_43_3). Количество сравнений и перестановок будем накапливать в переменных C1 и C2. Обратите внимание на их тип — EXTENDED, — это переменные действительного типа. Почему не длинное целое? При сортировке больших массивов может потребоваться столь много операций, что не хватит целочисленной переменной, — она переполнится, «лопнет», и результат исказится. Потому выбран тип EXTENDED.

Далее в программе следуют знакомые вам процедуры сортировки, — это наши «спортсмены». В их тела «вживлены» дополнительные операторы для подсчета сравнений (C1) и перестановок (C2), — они мною подчеркнуты. Наконец, главная программа, — она вызывает по очереди каждую из процедур и печатает количество сравнений и перестановок. Здесь равные условия для соревнующихся создаются загодя сформированным случайным массивом Arr0, который копируется в массив Arr перед каждой сортировкой.

Вам осталось лишь задать размер массива константой CSize, скомпилировать и запустить программу.

325

 

 

 

 

 

Глава 43

 

 

 

 

 

 

Сортировка по-взрослому

 

 

 

 

 

 

{ P_43_3

- Сравнение алгоритмов сортировки }

 

 

const CSize=100;

{ размер массивов }

 

 

type

TNumbers = array [1..CSize] of Integer;

 

 

var

Arr0 : TNumbers;

{ несортированный массив-заготовка }

 

 

 

Arr

: TNumbers;

{ сортируемый массив }

 

 

 

C1, C2 : extended;

{ счетчики сравнений и перестановок }

 

{ BubbleSort "пузырьковая" сортировка } procedure BubbleSort(var arg: TNumbers); var i, j, t: Integer;

begin

for i:= 1 to CSize-1 do

for j:= 1 to CSize-i do begin C1:=C1+1; { подсчет сравнений } if arg[j] > arg[j+1] then begin

C2:=C2+1; { подсчет перестановок }

t:= arg[j]; arg[j]:= arg[j+1]; arg[j+1]:= t;

end;

end;

end;

{ FarmSort – «фермерская» сортировка } procedure FarmSort(var arg: TNumbers); var L, R, T: Integer;

begin

for L := 1 to CSize-1 do

for R := CSize downto L+1 do begin C1:=C1+1; { подсчет сравнений } if arg[L] > arg[R] then begin

C2:=C2+1; { подсчет перестановок }

T:= arg[L]; arg[L]:= arg[R]; arg[R]:= T;

end;

end;

end;

{ QuickSort – Быстрая сортировка }

procedure QuickSort(var arg: TNumbers; aL, aR: Integer);

var

L, R, Mid, T: Integer;

begin

 

 

L:= aL;

R:= aR;

326

Глава 43 Сортировка по-взрослому

Mid:= (arg[L] + arg[(L + R) div 2] + arg[R]) div 3; repeat

while arg[L] < Mid do begin L:=L+1; C1:=C1+1 end; while arg[R] > Mid do begin R:=R-1; C1:=C1+1 end; if L <= R then begin

if arg[L]>arg[R] then begin

C2:=C2+1; { подсчет перестановок }

t:= arg[L]; arg[L]:= arg[R]; arg[R]:= t;

end;

L:=L+1; R:=R-1;

end;

until L > R;

if R > aL then QuickSort(arg, aL, R); if L < aR then QuickSort(arg, L, aR);

end;

const CFName = 'P_43_3.out';

var i: integer;

 

 

 

 

F: text;

 

 

 

 

begin

 

 

 

 

Assign(F,CFName);

 

Rewrite(F);

 

for i:=1 to CSize do Arr0[i]:=1+Random(10000);

Writeln(F, 'Размер массива = ', CSize);

 

Writeln(F, 'Алгоритм

Количество

Количество');

Writeln(F, 'сортировки

сравнений

перестановок');

C1:=0; C2:=0;

 

{ очистить счетчики }

 

Arr:= Arr0;

 

{ заполнить сортируемый массив }

BubbleSort(Arr);

 

 

 

 

Writeln(F, 'Пузырьковая:', C1:12:0, C2:12:0);

C1:=0; C2:=0;

 

{ очистить счетчики }

 

Arr:= Arr0;

 

{ заполнить сортируемый массив }

FarmSort(Arr);

 

 

 

 

Writeln(F, '«Фермерская»

:', C1:12:0, C2:12:0);

C1:=0; C2:=0;

 

{ очистить счетчики }

 

Arr:= Arr0;

 

{ заполнить сортируемый массив }

QuickSort(Arr, 1, CSize);

 

 

Writeln(F, 'Быстрая

:', C1:12:0, C2:12:0);

Writeln('OK !');

Readln;

 

 

Close(F);

 

 

 

 

end.

 

 

 

 

327

Глава 43 Сортировка по-взрослому

Вот что получилось для массива из 1000 элементов.

Размер массива

= 1000

 

Алгоритм

 

Количество

Количество

сортировки

 

сравнений

перестановок

Пузырьковая:

499500

248061

Фермерская :

499500

80887

Быстрая

:

5871

2417

 

 

 

 

Проведя три опыта с массивами из 100, 1000 и 10000 элементов, я получил результаты, представленные в двух табличках. Что сказать по этому поводу?

Табл. 9 – Количество сравнений в разных методах сортировки

Размер массива

Пузырьковая

Фермерская

Быстрая

сортировка

сортировка

сортировка

 

 

 

 

 

 

 

 

 

 

 

 

 

100

 

4

950

 

4

950

 

417

1

000

 

499

500

 

499

500

5

871

10

000

49

995

000

49

995

000

79

839

Из табл. 9 следует, что по количеству сравнений «фермерская» сортировка не лучше «пузырька». Зато быстрая сортировка оправдывает свое название, — выигрыш составляет от 10 до 600 раз! И чем больше массив, тем заметней этот разрыв.

Табл. 10 – Количество перестановок в разных методах сортировки

Размер массива

Пузырьковая

Фермерская

Быстрая

сортировка

сортировка

сортировка

 

 

 

 

 

 

 

 

 

 

 

 

 

100

 

2

305

 

 

805

 

141

1

000

 

248

061

 

80

887

2

417

10

000

24

903

994

6

154

077

31

011

А что с количеством перестановок (табл. 10)? Здесь «фермерская» сортировка всё же в 3 — 4 раза обгоняет «пузырёк». Так что фермеру Лефту в сметливости не откажешь. И всё же этому «спортсмену» далеко до изобретения Райта! В сравнении с «пузырьком» выигрыш стократный, и стремительно растёт с ростом размера массива. Слава, слава фермеру Райту! Кстати, историки выяснили его настоящее имя: это англичанин Чарльз Хоар.

328

Глава 43 Сортировка по-взрослому

BubbleSort

FarmSort

QuickSort

Рис. 96 – Кто здесь чемпион?

Итак, с чемпионом все ясно (рис. 96), но в чем секрет его успеха? Сравним чемпиона с отставшими. Чем больше массив, тем дольше он сортируется, — это справедливо для любого алгоритма. Долгие методы обрабатывают весь массив N раз, где N — размер массива. Поэтому их трудоёмкость пропорциональна произведению N•N. По этой формуле вычисляют площадь квадрата, и такую зависимость называют квадратичной.

Иное дело — чемпион. Дробя массив на мелкие участки, быстрый алгоритм уменьшает число прохождений всего массива до величины Log2N. Поэтому его трудоемкость растёт пропорционально произведению N•Log2N. Опять логарифм? Да, мы помним его по двоичному поиску. Логарифм числа растёт очень медленно, и это объясняет чемпионство QuickSort (рис. 97).

Трудоемкость

Квадратичная

N • N

Разница в трудоемкости

N • Log2N

Размер массива N

Рис. 97 – Рост трудоемкости сортировки с ростом размера массива

329

Соседние файлы в папке delphi