Чтение онлайн

на главную - закладки

Жанры

Фундаментальные алгоритмы и структуры данных в Delphi

Бакнелл Джулиан М.

Шрифт:

Это улучшение, несмотря на кажущуюся медлительность, на практике работает быстрее, чем стандартная быстрая сортировка. Надо признать, увеличение скорости незначительно, но, тем не менее, ощутимо.

Теперь оставим в покое алгоритм выбора базового элемента и перейдем к рассмотрению улучшений, касающихся других аспектов быстрой сортировки. Быстрая сортировка - это рекурсивный алгоритм, и уже было показано, каким образом легко избавиться от одного из рекурсивных вызовов. Исключение второго рекурсивного вызова потребует больших усилий, но это может быть оправдано, поскольку позволит устранить некоторые вызовы, установку

стека и т.п.

Рассмотрим рекурсивный вызов. Задаются четыре параметра, два из которых постоянны, а два других от вызова к вызову могут изменяться. Постоянные параметры - aList и aCompare, переменные параметры - aFirst и aSecond. Рекурсивный вызов можно устранить за счет использования явного стека, который предназначен для заталкивания и выталкивания переменных параметров. При этом цикл будет выполняться до тех пор, пока стек не будет пустым.

Листинг 5.17. Быстрая сортировка со случайным выбором базового элемента

procedure QSNR(aList : TList;

aFirst : integer;

aLast : integer;

aCompare : TtdCompareFunc);

var

L, R : integer;

Pivot : pointer;

Temp : pointer;

Stack : array [0..63] of integer;

{позволяет разместить до 2 миллиардов элементов}

SP : integer;

begin

{инициализировать стек}

Stack[0] := aFirst;

Stack[1] := aLast;

SP := 2;

while (SP <> 0) do

begin

{вытолкнуть верхний подфайл}

dec(SP, 2);

aFirst := Stack[SP];

aLast := Stack[SP+1];

{пока в списке есть хотя бы два элемента}

while (aFirst < aLast) do

begin

{в качестве базового выбирается средний элемент}

Pivot := aList.List^[ (aFirst+aLast) div 2];

{задать начальные значения индексов и приступить к разбиению списка}

L := pred(aFirst);

R := succ(aLast);

while true do

begin

repeat

dec(R);

until (aCompare(aList.List^[R], Pivot) <=0);

repeat

inc(L);

until (aCompare(aList.List^[L], Pivot) >=0);

if (L >= R) then

Break;

Temp := aList.List^ [L];

aList.List^[L] := aList.List^[R];

aList.List^[R] :=Temp;

end;

{затолкнуть больший подфайл в стек и повторить цикл для меньшего подфайла}

if (R - aFirst) < (aLast - R) then begin

Stack [SP] :=succ(R);

Stack[SP+1] := aLast;

inc(SP, 2);

aLast := R;

end

else begin

Stack[SP] := aFirst;

Stack [SP+1] :=R;

inc(SP, 2);

aFirst := succ(R);

end;

end;

end;

end;

procedure TDQuickSortNoRecurse( aList : TList;

aFirst : integer;

aLast : integer;

aCompare : TtdCompareFunc);

begin

TDValidateListRange(aList, aFirst, aLast, 'TDQuickSortNoRecurse');

QSNR(aList, aFirst, aLast, aCompare);

end;

И в этом листинге код состоит из двух процедур: процедуры-драйвера и процедуры собственно сортировки. Таким образом, общая схема работы алгоритма осталась неизменной, но в этом случае внутренняя процедура, QSNR, вызывается только один раз.

В процедуре QSNR объявляется стек Stack для хранения 64 элементов типа longint и указатель на стек SP, который будет указывать

на начало стека. Комментарий жизнерадостно утверждает, что размера стека будет достаточно для хранения 2 миллиардов элементов. Через несколько минут мы докажем справедливость комментария. В начале процедуры в стек записываются переданные процедуре начальный и конечный индексы. Предполагается, что на индекс первого элемента указывает указатель стека, а индекс последнего элемента хранится в позиции SP+1. После записи индексов указатель стека перемещается на 2 позиции. (В реализации алгоритма может использоваться два стека: один для индексов aFirst, а второй - для индексов aLast. При этом для обоих стеков будет нужен только один указатель.)

Далее начинается выполнение цикла while, которое завершается, когда стек опустеет, что эквивалентно равенству SP=0.

В цикле из стека выталкиваются переменные aFirst и aLast и значение указателя стека уменьшается на 2. После этого мы входим в цикл, который присутствует и в стандартной быстрой сортировке. Он повторяется до тех пор, пока значение индекса aFirst не превысит значение индекса aLast. Заключительные операторы в цикле, где ранее находился рекурсивный вызов процедуры сортировки, представляют собой интересный блок кода. К этому моменту времени базовый элемент находится на своем месте, и подсписок успешно разбит на две части. Определяем, какая из частей длиннее и записываем ее в стек (т.е. заталкиваем в стек значения индексов его первого и последнего элемента) и переходим к меньшей части.

Давайте на минутку задумаемся, что происходит. Если нам несказанно повезло, и для каждого подсписка в качестве базового элемента были выбраны их действительные медианы, то размеры подсписков будут составлять ровно половину размера подсписка более высокого уровня. Если в исходном списке было, например, 32 элемента, он будет разбит на 2 подсписка по 16 элементов, каждый из которых, в свою очередь, будет разбит еще на два подсписка по 8 элементов и т.д. Таким образом, максимальная глубина вложения подсписков в стеке будет равна пяти, поскольку 2(^5^)=32. Подумайте над этим. Мы затолкнем в стек подсписок из 16 элементов, разобьем второй такой же список на два списка по 8 элементов, затолкнем в стек один из списков длиной 8 элементов, а второй разобьем на два подсписка по 4 элемента и т.д. Пока мы дойдем до подсписка с одним элементом, в стеке уже будут находиться подсписок из 16 элементов, подсписок из 8 элементов, подсписок из 4 элементов, подсписок из 2 элементов и подсписок из 1 элемента. Пять уровней. Таким образом, для сортировки списка, содержащего 2 миллиарда элементов, будет достаточно 32 уровней (как это указано в комментарии к процедуре QSNR), если, конечно, каждый раз мы будем удачно выбирать базовый элемент.

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

Поделиться:
Популярные книги

Блуждающие огни 4

Панченко Андрей Алексеевич
4. Блуждающие огни
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Блуждающие огни 4

Я сделаю это сама

Кальк Салма
1. Магический XVIII век
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Я сделаю это сама

Флеш Рояль

Тоцка Тала
Детективы:
триллеры
7.11
рейтинг книги
Флеш Рояль

Боярышня Дуняша

Меллер Юлия Викторовна
1. Боярышня
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Боярышня Дуняша

Газлайтер. Том 8

Володин Григорий
8. История Телепата
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Газлайтер. Том 8

Леди для короля. Оборотная сторона короны

Воронцова Александра
3. Королевская охота
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Леди для короля. Оборотная сторона короны

На границе империй. Том 10. Часть 1

INDIGO
Вселенная EVE Online
Фантастика:
космическая фантастика
попаданцы
5.00
рейтинг книги
На границе империй. Том 10. Часть 1

Черный Маг Императора 5

Герда Александр
5. Черный маг императора
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Черный Маг Императора 5

Невест так много. Дилогия

Завойчинская Милена
Невест так много
Любовные романы:
любовно-фантастические романы
7.62
рейтинг книги
Невест так много. Дилогия

Идеальный мир для Социопата 3

Сапфир Олег
3. Социопат
Фантастика:
боевая фантастика
6.17
рейтинг книги
Идеальный мир для Социопата 3

Повелитель механического легиона. Том VIII

Лисицин Евгений
8. Повелитель механического легиона
Фантастика:
технофэнтези
аниме
фэнтези
5.00
рейтинг книги
Повелитель механического легиона. Том VIII

Наследник павшего дома. Том I

Вайс Александр
1. Расколотый мир
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Наследник павшего дома. Том I

Крещение огнем

Сапковский Анджей
5. Ведьмак
Фантастика:
фэнтези
9.40
рейтинг книги
Крещение огнем

Камень Книга двенадцатая

Минин Станислав
12. Камень
Фантастика:
боевая фантастика
городское фэнтези
аниме
фэнтези
5.00
рейтинг книги
Камень Книга двенадцатая