Скачиваний:
83
Добавлен:
01.05.2014
Размер:
3.82 Mб
Скачать

 

 

 

 

 

 

 

 

 

 

Original pages: 188-217 111

 

LD1

INPUT, 1(L)

C0

p

Lp

 

 

 

 

 

 

J1P

L3P

C0

К L3, если p > 0.

 

 

 

 

 

L5

ST2

INPUT,3(L)

B0

L5. Закончить подсписок. Ls

q.

 

ENT3

0,4

B0

s

t.

 

 

 

 

 

 

ENT4

0,2

D0

t

q.

 

 

 

 

 

 

LD2

INPUT,2(L)

D0

q

Lq.

 

 

 

 

 

 

J2P

*-2

D0

Повторить, если q > 0.

 

 

 

JMP

L8

B0

К L8

j

 

j

 

 

L6

ST2

INPUT,3(ABSL)

C00

L6. Продвинуть q.

Ls

q.

 

 

 

 

 

ENT3

0,2

C00

s

q.

 

 

 

 

 

 

LD2

INPUT,2(L)

C00

q

Lq.

 

 

 

 

 

 

J2P

L3Q

C00

К L3, если q > 0.

 

 

 

 

 

L7

ST1

INPUT,3(L)

B00

L7. Закончить подсписок. Ls

p.

 

ENT3

0,4

B00

s

t.

 

 

 

 

 

 

ENT4

0,1

D00

t

p.

 

 

 

 

 

 

LD1

INPUT, 1(L)

D00

p

Lp.

 

 

 

 

 

 

J1P

*-2

D00

Повторить, если p > 0.

−p.

 

L8

ENN1

0,1

B

L8. Конец просмотра? p

 

 

ENN2

0,2

B

q

−q.

 

 

 

 

 

 

J2NZ

L3Q

B

К L3, если q 6= 0.

 

 

 

 

 

 

ST1

INPUT,3(ABSL)

A

jLsj

p.

 

 

 

 

 

 

STZ

INPUT,4(ABSL)

A

jLtj 0.

 

 

 

 

 

L2

ENT3

0

A + 1

L2. Начать новый просмотр, s

0.

 

ENT4

N+1

A + 1

t

N + 1.

 

 

 

 

 

 

LD1

INPUT (L)

A + 1

p

Ls.

 

 

 

 

 

 

LD2

INPUT+N+1(L)

A + 1

q

Lt.

 

 

 

 

 

 

J2NZ

L3Q

A + 1

К L3, если q 6= 0.

 

 

 

 

 

Время работы этой программы можно оценить при помощи методов, которыми мы уже не раз

пользовались (см. упр. 13, 14); в среднем оно равно приблизительно (10N log N + 4:92N) единиц p 2

с небольшим стандартным отклонением порядка N. В упр. 15 показано, что за счет некоторого удлинения программы можно сократить время примерно до 9N log2 N.

Итак, в случае внутреннего слияния связанное распределение памяти имеет бесспорные преимущества перед последовательным распределением: требуется меньше памяти, и программа работает на 10–20% быстрее. Аналогичные алгоритмы опубликованы Л. Дж. Вудрамом [IBM Systems J., 8 (1969), 189–203] и А. Д. Вудаллом [Сотр. J., 13 (1970), 110–111].

 

 

 

 

Упражнения

1.

[20] Обобщите алгоритм M на k-путевое слияние исходных файлов xi1 : : : ximi при i = 1; 2,

 

: : : , k.

 

 

 

2.

[М24] Считая, что все

 

m+n

возможных расположений m элементов x среди n элементов y рав-

 

 

 

m

 

 

новероятны, найдите математическое ожидание и стандартное отклонение числа выполнений

 

 

 

 

 

 

шага M2 в алгоритме M. Чему равны максимальное и минимальное значения этой величины?

>3.

[20] (Изменение.) Даны записи R1, : : : , RM и R10 , : : :, RN0 , ключи которых различны и упорядо-

чены, т. е. K1 < : : : < KM и K10 < : : : < KN0 . Как нужно изменить алгоритм M, чтобы в результате слияния получался файл, в котором отсутствуют записи Ri первого файла, если во втором файле тоже есть запись с таким же ключом?

4.[21] В тексте отмечено, что сортировку слиянием можно рассматривать как обобщение сортировки вставками. Покажите, что метод слияний имеет непосредственное отношение и к выбору из дерева (воспользуйтесь в качестве иллюстрации рис. 23).

>5. [21] Докажите, что в шагах N6 и N10 переменные i и j не могут быть равны. (Поэтому в этих шагах проверка на случай возможного перехода к N13 необязательна.)

6. [22] Найдите такую перестановку K1K2 : : :K16 множества f1; 2; : : :; 16g, что

K2 > K3; K4 > K5; K6 > K7; K8 > K9;

K10 < K11; K12 < K13; K14 < K15;

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

112 Original pages: 188-217

после первого просмотра останутся по меньшей мере четыре отрезка, после второго просмотра— два отрезка, и сортировка, как правило, не завершится раньше окончания третьего просмотра. Каким же образом можно обойтись всего двумя просмотрами?)

7.[16] Найдите точную формулу, выражающую число просмотров алгоритма S в виде функции от N.

8.[22] Предполагается, что во время работы алгоритма переменные q и r представляют длины неслитых частей отрезков, обрабатываемых в данный момент; в начале работы как q, так и r устанавливаются равными p, в то время как отрезки не всегда имеют такую длину. Почему же алгоритм тем не менее работает правильно?

9.[24] Напишите MIX-программу для алгоритма S. Выразите частоту выполнения каждой команды через величины, подобные A, B0, B00, C0, : : : в программе L.

10.[25] (Д. А. Белл.) Покажите, что простое двухпутевое слияние файлов с последовательно расположенными элементами можно выполнить, имея всего 32N ячеек памяти, а не 2N, как в алгоритме S.

11.[21] Является ли алгоритм L алгоритмом ”устойчивой” сортировки?

>12.

[22] Измените шаг L1 алгоритма L так, чтобы двухпутевое слияние стало ”естественным”, извле-

 

кая пользу из наличия возрастающих отрезков в исходном файле. (В частности, если исходные

 

данные уже упорядочены, то шаг L2 завершит работу алгоритма сразу же после выполнения

 

измененного вами шага L1.)

 

 

>13.

[М34] Проанализируйте среднее время работы программы L подобно тому, как мы анализирова-

 

ли другие алгоритмы в этой главе. Дайте толкование величинам A, B, B0, : : :и объясните, как

 

вычислить их точные средние значения. Сколько времени затратит программа L на сортировку

 

16 чисел в табл. 3?

 

 

14.

[М24] Пусть двоичное представление числа N—это 2e1 + 2e2 + + 2et , где e1 > e2 > : : : > et 0,

 

t

 

. Докажите, что максимальное число сравнений ключей, выполняемых алгоритмом L, равно

 

 

1et

+ 1 k t(ek + k − 1)2

ek

.

15.

1 − 2

 

 

 

 

промоделировать вручную работу алгоритма L, то обнаружится, что в нем иногда вы-

 

[20] ЕслиP

 

 

 

полняются лишние операции; примерно в половине случаев не нужны присваивания jLsj p,

 

jLsj

 

q в шагах L4 и L6, поскольку мы имеем Ls = p (или q) всякий раз, когда возвращаемся из

 

шага L4 (или L6) к L3. Как улучшить программу L, избавившись от этих лишних присваиваний?

16.[28] Разработайте алгоритм слияния списков, подобный алгоритму L, но основанный на трехпутевом слиянии.

17.[20] (Дж. Мак-Карти.) Пусть двоичное представление числа N такое же, как в упр. 14, и предположим, что дано N записей, организованных в t упорядоченных подфайлов, имеющих размеры соответственно 2e1 , 2e2, : : :, 2et . Покажите, как можно сохранить такое состояние при добавлении (N +1)-й записи и N N +1. (Полученный алгоритм можно назвать ”оперативной” сортировкой слиянием.)

18.[40] (М. А. Кронрод.) Можно ли отсортировать файл из N записей, содержащий всего два отрезка:

K1 : : : KM и KM+1 : : : KN;

За O(N) операций в памяти с произвольным доступом, используя лишь небольшое дополнительное пространство памяти фиксированного размера, не зависящего от M и N? (Все алгоритмы слияния, описанные в этом пункте, используют дополнительное пространство памяти, пропорциональное N.)

19.[26] Рассмотрим железнодорожный разъезд с n ”стеками”, как показано на рис. 31 при n = 5; такой разъезд имеет некоторое отношение к алгоритмам сортировки с nпросмотрами. В упр. с 2.2.1– 2 по 2.2.1–5 мы рассмотрели разъезды с одним стеком. Вы видели, что если с правого конца поступает N вагонов, то слева может появиться сравнительно небольшое количество из N всевозможных перестановок этих вагонов.

Предположим, что в разъезд с n стеками справа поступает 2n вагонов. Докажите, что при помощи подходящей последовательности операций слева можно получить любую из 2n! всевозможных перестановок этих вагонов. (Каждый стек достаточно велик, и при необходимости в него можно поместить все вагоны).

20.[47] В обозначениях упр. 2.2.1–4 при помощи разъездов с n стеками можно получить не более anN перестановок N элементов; следовательно, для

Picture:

Рис. 31.Железнодорожный разъезд с пятью ”стеками”.

получения всех N! перестановок требуется не менее log N!= logaN log4 N стеков. В упр. 19 показано, что нужно не более dlog2 Ne стеков. Какова истинная скорость роста необходимого числа стеков при N ! 1?

Original pages: 188-217 113

21.[23] (Э. Дж. Смит.) Объясните, как можно обобщить алгоритм L, чтобы он, помимо сортировки, вычислял также число инверсий в исходной перестановке.

5.2.5. Распределяющая сортировка

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

Предположим, нам нужно отсортировать колоду из 52 игральных карт. Определим упорядочение по старшинству (достоинству) карт в масти

T < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < B < D < K;

а также по масти

| < } < ~ < ♠

Одна карта предшествует другой, если либо (i) она младше по масти, либо (ii) масти обеих карт одинаковы, но она младше по достоинству. (Это частный случай лексикографического упорядочения на множестве упорядоченных пар предметов; ср. с упр. 5–2.) Таким образом,

T| < 2| < < K| < T} < < D♠ < K♠

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

Но существует более быстрый способ! Сначала разложить карты в 13 стопок лицевой стороной вверх по их достоинству. Затем собрать все стопки вместе: снизу тузы, затем двойки, тройки и т. д. и сверху короли (лицевой стороной вверх). Перевернуть колоду рубашками вверх и снова разложить, на этот раз в четыре стопки по масти. Сложив вместе полученные стопки так, чтобы внизу были трефы, затем бубны, черви и пики, получим упорядоченную колоду карт.

Та же идея годится и для сортировки числовых и буквенных данных. Почему же она правильна? Потому что (в нашем примере с игральными картами), если две карты при последнем раскладе попали в разные стопки, то они имеют разные масти, так что карта с меньшей мастью младше. Если же карты одной масти, то они уже находятся в нужном порядке благодаря предварительной сортировке. Иначе говоря, при втором раскладе в каждой из четырех стопок карты будут расположены в возрастающем порядке. Это доказательство можно обобщить и показать, что таким способом можно отсортировать любое множество с лексикографическим упорядочением; подробности см. в упр. 5–2 (в начале главы).

Только что описанный метод сортировки сразу не очевиден, и не ясно, кто же первый обнаружил, что он так удобен. В брошюре на 19 страницах под названием ”The Inventory Simplified”, опубликованной отделением фирмы IBM Tabulating Machines Company в 1923 г., представлен интересный цифровой метод вычисления сумм произведений на сортировальной машине. Пусть, например, нужно перемножить два числа, пробитых соответственно в колонках 1–10 и в колонках 23–25, и вычислить сумму таких произведений для большого числа карт. Тогда сначала можно отсортировать карты по колонке 25 и найти при помощи счетно-аналитической машины величины a1, a2, : : : a9, где ak—сумма чисел из колонок 1–10 по всем карточкам, на которых в колонке 25 пробита цифра k. Затем можно отсортировать карты по колонке 24 и найти аналогичные суммы b1, b2, : : :, b9, а потом по колонке 23, получив величины c1, c2, : : : , c9. Легко видеть, что искомая сумма произведений равна

a1 + 2a2 + + 9a9 + 10b1 + 20b2 + + 90b9 + 100c1 + 200c2 + + 900c9:

Такой перфокарточный метод табулирования естественным образом приводит к идее поразрядной сортировки ”сначала-по-младшей-цифре”, так что, по-видимому, она впервые стала известна операторам этих машин. Первая опубликованная ссылка на этот метод содержится в ранней работе Л. Дж. Комри, посвященной обсуждению перфокарточного оборудования [Transactions of the Office Machinery Users’ Assoc., Ltd. (1929), 25–37, особенно стр. 28].

Чтобы выполнить поразрядную сортировку с помощью ЭВМ, необходимо решить, как представлять стопки. Пусть имеется M стопок; можно было бы выделить M областей памяти и пересылать каждую исходную запись в соответствующую область. Но это решение нас не удовлетворяет, потому

114Original pages: 188-217

что в каждой области должно быть достаточно места для хранения N элементов, и тогда потребуется пространство под (M +1)N записей. Такая чрезмерная потребность в памяти заставляла большинство программистов отказываться от применения поразрядной сортировки на вычислительных машинах, пока X. Сьюворд [дипломная работа, M.I.Т. Digital Computer Laboratory Report R-232 (Cambridge Mass: 1954), 25–28] не показал, что того же эффекта можно добиться, имея в распоряжении пространство всего под 2N записей и M счетчиков. Сделав один предварительный просмотр данных, можно просто посчитать, сколько элементов попадет в каждую область; это даст нам возможность точно распределить память под стопки. Мы уже применяли эту идею при распределяющей сортировке (алгоритм 5.2D).

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

Если у нас имеется десятичная машина, а ключи—12-разрядные числа и если N весьма велико, то можно выбрать M = 1000 (считая три десятичные цифры за одну в системе счисления с основанием 1000); независимо от величины N сортировка будет выполнена за четыре просмотра. Аналогично, если бы имелась двоичная машина, а ключи—40-битовые двоичные числа, то можно положить M = 1024 и также завершить сортировку за четыре просмотра. Фактически каждый просмотр состоит из трех частей (подсчет, распределение памяти, перемещение); Фрэнд [JACM, 3 (1956), 151] предложил комбинировать два из этих трех действий, добавив еще M ячеек: накапливать значения счетчиков для (k + 1)-го просмотра одновременно с перемещением во время k-го просмотра.

Втабл. 1 показано применение поразрядной сортировки к нашим 16 ключам при M = 10. При таких малых N поразрядная сортировка, как правило, не особенно полезна, так что этот маленький пример предназначен главным образом для того, чтобы продемонстрировать достаточность метода, а не его эффективность.

 

 

 

 

 

 

 

Таблица 1

Область ввода:

Поразрядная сортировка

 

 

 

503 087 512 061 908 170 897 275 653 426 154 509 612 677 765 703

Счетчики для младших цифр:

1 1 2 3 1

2

1

3

1

1

Соответствующее распределение памяти:

1 2 4 7 8

10

11

14

15

16

Вспомогательная область: 170 061 512 612 503 653 703 154 275 765 426 087 897 677 908 509

Счетчики для средних цифр:

4 2 1 0 0 2

2

3

1

1

Соответствующее распределение памяти:

4 6 7 7 7 9

11

14

15

16

Область ввода;

503 703 908 509 512 612 426 653 154 061 765 170 275 677 087 897

Счетчики для старших цифр:

2 2 1 0 1 3

3

2

1

1

Соответствующее распределение памяти:

2 4 5 5 6 9

12

14

15

16

Вспомогательная область:

061 087 154 170 275 426 503 509 512 612 653 677 703 765 897 908

Искушенный ”современный” читатель заметит, однако, что идея счетчиков для распределения памяти привязана к ”старомодным” понятиям о последовательном представлении данных; нам же известно, что специально для работы с множеством таблиц переменной длины придумано связанное распределение. Поэтому для поразрядной сортировки естественно будет воспользоваться связанными структурами данных. Так как каждая стопка просматривается последовательно, то все, что нам нужно,— иметь при каждом элементе одну-единственную ссылку на следующий элемент. Кроме того, никогда не придется перемещать записи: достаточно скорректировать связи—и можно смело двигаться дальше по спискам. Объем необходимой памяти равен (1 + ")N + 2"M записей, где "— пространство, занимаемое одним полем связи. Довольно интересны формальные подробности этой процедуры, поскольку они дают прекрасный пример типичных манипуляций со структурами данных, соединяющих в себе последовательное и связанное распределение памяти.

Picture:

Рис. 32. Поразрядная сортировка списка.

Алгоритм R. (Поразрядная сортировка списка.) Предполагается, что каждая из записей R1, : : : , RN содержит поле связи LINK, а ключи представляют собой последовательность из p элементов

(ap; : : : ; a2; a1); 0 ai < M;

Original pages: 188-217 115

и отношение порядка—лексикографическое, т. е.

(ap; : : : ; a2; a1) < (bp; : : : ; b2; b1)

тогда и только тогда, когда существует такой индекс j, 1 j p, что

ai = bi при i > j, но aj < bj.

Ключи можно представлять себе, в частности, как числа, записанные в системе счисления с основа-

нием M:

apMp−1 + + a2M + a1;

ив этом случае лексикографическое отношение порядка соответствует обычному упорядочению множества неотрицательных чисел. Ключи также могут быть цепочками букв алфавита и т. д.

Во время сортировки формируются M ”стопок” подобно тому, как это делается в сортировальной машине для перфокарт. Стопки фактически представляют собой очереди в смысле гл. 2, поскольку мы связываем их вместе таким образом, что они всегда просматриваются по принципу ”первым включается—первым исключается”. Для каждой стопки имеются две переменные-указатели: TOP[i]

иBOTM[i], 0 i < M, и, как и в гл. 2, предполагается, что

 

 

LINK(LOC(BOTM([i]))) BOTM[i]:

R1

[Цикл по k.] Вначале установить P LOC(RN ), указатель на последнюю запись. Затем выполнить

 

шаги с R2 по R6 при k = 1, 2, : : : , p (шаги с R2 по R6 составляют один ”просмотр”) и завершить

 

работу алгоритма. Переменная P будет указывать на запись с наименьшим ключом, LINK(P)—

 

на запись со следующим по величине ключом, LINK(LINK(P))—на следующую и т.д.; поле LINK

 

последней записи будет равно .

 

R2

[Опустошить стопки.] При 0 i < M установить TOP[i]

LOC(BOTM[i]) и BOTM[i] .

R3

[Выделить k-ю цифру ключа.] Пусть KEY(P) — ключ записи, на которую указывает P,— равен

 

(ap; : : : ; a2; a1); установить i ak, k-я младшая цифра этого ключа.

R4

[Скорректировать связи.] Установить LINK(TOP[i]) P, затем TOP[i] P.

R5

[Перейти к следующей записи.] Если k = 1 (первый просмотр) и если P = LOC(Rj) при неко-

 

тором j 6= 1, то установить P

LOC(Rj−1) и возвратиться к шагу R3. Если k > 1 (не первый

 

просмотр), то установить P

LINK(P) и возвратиться к R3, если P 6= .

R6

[Выполнить алгоритм H.] (Теперь мы уже распределили все элементы по стопкам.) Выполнить

 

приведенный ниже алгоритм H, который сцепляет отдельные ”стопки” в один список, подготав-

 

ливая их к следующему просмотру. Затем установить P

BOTM[0], указатель на первый элемент

 

 

 

 

объединенного списка. (См. упр. 3.)

 

 

 

 

 

 

Алгоритм H. (Сцепление очередей.) Из M данных очередей со связями, удовлетворяющими соглашениям алгоритма R, данный алгоритм создает одну очередь, меняя при этом не более M связей. В результате BOTM[0] указывает на первый элемент, и стопка 0 предшествует стопке 1, : : : , предшествует

стопке (M − 1).

 

 

 

H1

[Начальная установка.] Установить i

0.

 

 

H2

[Указатель на вершину стопки.] Установить P TOP[i].

H3

[Следующая стопка.] Увеличить i на 1. Если i = M, то установить LINK(P) и завершить работу

 

алгоритма.

 

 

 

H4

[Стопка пуста?] Если BOTM[i] = , тo возвратиться к .

H5

[Сцепить стопки.] Установить LINK(P)

BOTM[i]. Возвратиться к H2.

 

 

 

На рис. 33 показано содержимое стопок после каждого из трех просмотров, выполняемых при сортировке наших 16 чисел с M = 10. Алгоритм R очень просто запрограммировать для машины MIX, если только найти удобный способ изменять от просмотра к просмотру действия в шагах R3 и R5. В следующей программе этого удалось добиться, не жертвуя скоростью внутреннего цикла, путем предварительной записи двух команд в тело программы. Заметим, чтоTOP[i]и BOTM[i]можно упаковать в одно слово.

Picture:

Рис. 33. Поразрядная сортировка с использованием связанного распределе-

 

ния памяти (показано содержимое всех десяти стопок после каждого просмот-

 

ра).

116 Original pages: 188-217

Программа R. (Поразрядная сортировка списков.) Предполагается, что исходные ключи в ячейках от INPUT + 1 до INPUT + N содержат p = 3 компоненты (a3, a2, a1), хранящиеся соответственно в полях (1 : 1), (2 : 2) и (3 : 3). (Таким образом, считается, что значение M меньше или равно размеру байта машины MIX.) В поле (4 : 5) записи хранится связь LINK. Пусть TOP[i] PILES + i(l : 2) и BOTM[i] PILES + i(4 : 5) при 0 i < M. Удобно указывать в связи положение относительно ячейки INPUT, так что LOC(BOTM[i]) = PILES + i − INPUT; чтобы избежать появления отрицательных связей, нужно расположить таблицу PILES после таблицы INPUT. Значения индексных регистров: rI1 P, rI2 i, rI3 3 − k, rI4 TOP[i]; во время работы алгоритма H rI2 i − M.

LINK

EQU

4:5

 

 

 

 

 

TOP

EQU

1:2

 

 

 

 

 

START ENT1

N

1

R1. Цикл по k. P LOC(RN).

 

ENT3

2

1

k

1.

 

 

2H

ENT2

M-1

3

R2. Опустошить стопки.

 

 

ENTA

PILES-INPUT,2

3M

LOC(BOTM[i])

 

 

STA

PILES,2 (TOP)

3M

 

 

! TOP[i]

 

 

STZ

PILES,2(LINK)

3M

BOTM[i]

.

 

 

DEC2

1

3M

M > i 0.

 

 

J2NN

*-4

3M

 

 

LDA

R3SW,3

3

 

 

 

 

 

STA

3F

3

Изменить команды

 

 

LDA

R5SW,3

3

для k-го просмотра.

 

 

STA

5F

3

 

 

 

 

[LD2

INPUT, 1(3:3)]

 

R3. Выделить k-ю цифру ключа.

4H

LD4

PILES,2 (TOP)

3N

R4. Скорректировать связи.

 

 

ST1

INPUT,4(LINK)

3N

LINK(TOP[i]) P.

 

 

ST1

PILES,2(TOP)

3N

TOP[i] P.

 

5H

[DEC1

1]

 

R5. Перейти к следующей записи.

 

J1NZ

3B

3N

К R3, если просмотр закончен.

6H

ENN2

М

3

R6. Выполнить алгоритм Н.

 

 

JMP

7F

3

К Н2 с i

0.

 

R3SW

LD2

INPUT, 1(1:1)

N

Команда для R3 при k = 3.

 

 

LD2

INPUT, 1(2:2)

N

Команда для R3 при k = 2.

 

 

LD2

INPUT, 1(3:3)

N

Команда для R3 при k = 1.

 

R5SW

LD1

INPUT, 1(LINK)

N

Команда для R5 при k = 3.

 

 

LD1

INPUT, 1(LINK)

N

Команда для R5 при k = 2.

 

 

DEC1

1

N

Команда для R5 при k = 1.

 

9H

LDA

PILES+M, 2(LINK)

3M − 3

Н4.Стопка пуста?

 

 

JAZ

8F

3M − 3

К НЗ, если BOTM[i] =

 

 

STA

INPUT, 1(LINK)

3M − 3 − E H5. Сцепить стопки LINK(P)

BOTM[i].

7H

LD1

PILES+M, 2(TOP)

3M − E

H2. Указатель на вершину стопки.

8H

INC2

1

3M

H3. Следующая стопка, i

i + 1.

 

J2NZ

9B

3M

К Н4, если i 6= M.

 

 

STZ

INPUT, 1(LINK)

3

LINK(P)

.

 

 

LD1

PILES (LINK)

3

P

BOTM[0].

 

 

DEC3

1

3

1 k 3

 

 

 

J3NN

2B

3

 

 

Время работы программы R равно 32N + 48M + 38 − 4E, где N—число исходных записей, M— основание системы счисления (число стопок), а E—число встретившихся пустых стопок. Сравнение с другими программами, построенными на основе аналогичных предположений (программы 5.2.1М, 5.2.4L), говорит явно в пользу программы R. Время работы p-проходного варианта программы R равно (11p − 1)N + O(pM) единиц; критический фактор, влияющий на время работы,—внутренний цикл, который содержит пять обращений к памяти и один переход. Для типичной вычислительной машины M = br и p = dt=re, где t— число цифр в ключах, представленных в системе счисления с основанием b; с ростом r убывает p, так что можно воспользоваться нашими формулами для определения ”наилучшего” значения r.

Единственная переменная величина в формуле времени работы—это E—число пустых стопок, обнаруженных в шаге Н4. Предположим, что все MN последовательностей цифр M-ичной системы счисления равновероятны. Из изучения ”покер-теста” в п. 3.3.2D мы умеем вычислять вероятность

Original pages: 188-217 117

того, что в каждом просмотре встретится ровно M − r пустых стопок; она равна

M M

: : : M

 

r

 

N

 

(

− 1)MN(

 

+ 1)

r

;

 

 

n o

где Nr —число Стирлинга второго рода. Согласно упр. 5,

E= minmax(M − N; 0)p; aveM(1 − M1 )Np; max(M − 1)p :

Впоследние годы появляется все больше ”трубопроводных”, или ”магистральных” вычислительных машин. Эти машины имеют несколько арифметических устройств и схему ”опережения”, так что обращения к памяти и вычисления могут в значительной степени совмещаться во времени; но эффективность таких машин заметно понижается при наличии условных переходов, если только эти переходы не происходят почти всегда в одном и том же направлении. Внутренний цикл поразрядной сортировки хорошо приспособлен для таких машин, поскольку это простое итеративное вычисление, типичное ”пережевывание чисел”. Поэтому для магистральных машин поразрядная сортировка обычно бывает наиболее эффективным методом из всех известных методов внутренней сортировки, при условии что N не слишком мало и ключи не слишком длинные.

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

аслева направо. Поэтому давайте возвратимся к идее поразрядной сортировки, в которой ключи просматриваются, начиная со старших цифр (СЦ), а не с младших цифр (МЦ).

Мы уже отмечали, что СЦ-поразрядная сортировка естественным образом приходит на ум. В самом деле, нетрудно понять, почему при сортировке почты в отделениях связи пользуются именно этим методом. Большое количество писем можно отсортировать по отдельным мешкам, соответствующим географическим областям; теперь каждый мешок содержит уже меньшее количество писем, которые можно независимо сортировать по другим мешкам, соответствующим все меньшим и меньшим географическим районам. (Разумеется, прежде чем подвергать письма дальнейшей сортировке, их можно переправить поближе к месту назначения.) Этот принцип ”разделяй и властвуй” весьма привлекателен, и единственная причина его непригодности для сортировки перфокарт в том, что большое количество стопок приводит к путанице. Этим же явлением объясняется относительная эффективность алгоритма R (хотя здесь сначала рассматриваются МЦ), потому что нам никогда не приходится работать более чем с M стопками и стопки приходится сцеплять всего p раз. С другой стороны, нетрудно построить СЦ-поразрядный метод с использованием связанного распределения памяти с отрицательными связями для обозначение границ между стопками, как в алгоритме 5.2.4L. (См. упр. 10.)

Пожалуй, наилучший компромиссный выход указал М. Д. Макларен [JACM, 13 (1966), 404– 411], который предложил использовать МЦ-сортировку, как в алгоритме R, но лишь в применении к старшим цифрам. Это не будет полной сортировкой файла, но в результате файл становится почти упорядоченным, т. е. в нем остается очень мало инверсий, так что для завершения сортировки можно воспользоваться методом простых вставок. Наш анализ алгоритма 5.2.1М применим и к-этой ситуации; если ключи распределены равномерно, то после сортировки файла по старшим p цифрам в нем останется в среднем

14N(N − 1)M−p

инверсий. [См. формулу (5.2.1–14) и упр. 5.2.1–38.] Макларен вычислил среднее число обращений к памяти, приходящееся на один обрабатываемый элемент, и оказалось, что оптимальный выбор значений M и p (в предположении, что M—степень двойки, ключи равномерно распределены и N=Mp 0:1, так что отклонения от равномерного распределения приемлемы) описывается следующей таблицей:

N =

100

1000

5000

10000

50000

100000

Наилучшее M =

32

128

256

512

1024

1024

Наилучшее p =

2

2

2

2

2

2

 

 

18:5

18:2

18:2

18:1

18:0

(N) = 19:3

 

 

Здесь (N)—среднее число обращений к памяти на один сортируемый элемент; эта величина огра-

 

p

ничена при N ! 1, если взять p = 2 и M > N, так что среднее время сортировки есть O(N), а

118Original pages: 188-217

не O(N log N). Этот метод является усовершенствованием метода вставок в несколько списков (алгоритм 5.2.1М), который, по существу, представляет собой случай p = 1. В упр. 12 приводится интересная процедура Макларена для окончательного переразмещения после частичной сортировки файла с использованием списков.

Если воспользоваться методами алгоритма 5.2D и упр. 5.2-13, то можно обойтисьpбез полей связи; при этом в дополнение к памяти, занятой самими записями, потребуется всего O( N) ячеек. Если исходные данные распределены равномерно, то среднее время сортировки пропорционально N.

Упражнения

>1. [20] Алгоритм из упр. 5.2–13 показывает, как можно выполнить распределяющую сортировку, имея пространство памяти всего под N записей (и M полей счетчиков), а не под 2N записей. Приводит ли эта идея к усовершенствованию алгоритма поразрядной сортировки, проиллюстрированного в табл. 1?

2.[13] Является ли алгоритм R алгоритмом ”устойчивой” сортировки?

3.[15] Объясните, почему в алгоритме H переменной BOTM[0] присваивается значение указателя на первую запись в ”объединенной” очереди, несмотря на то что стопка 0 могла быть пустой.

>4. [23] Во время работы алгоритма R все M стопок хранятся в виде связанных очередей (первым включается—первым исключается). Исследуйте идею связывания элементов стопок как в стеке. (На рис. 33 стрелки пойдут не вверх, а вниз, и таблица BOTM станет не нужна.) Покажите, что если сцеплять стопки в соответствующем порядке, то может получиться правильный метод

сортировки. Будет ли этот алгоритм более простым или более быстрым?

P

5.[M24] Пусть gMN (z) = pMNkzk, где pMNk— вероятность того, что после случайного просмотра поразрядной сортировки, разложившего N элементов на M стопок, получилось ровно k пустых стопок. (a) Покажите, что gM;N+1(z) = gMN(z) + ((1 − z)=M)gMN0 (z). (b) Найдите при помощи указанного соотношения простые выражения для математического ожидания и дисперсии этого распределения вероятностей как функций от M и N.

6.[20] Какие изменения необходимо внести в программу R, чтобы она сортировала не трехбайтовые ключи, а восьмибайтовые? Считается, что старшие байты ключа Ki хранятся в ячейке KEY+i(1 : 5), а три младших байта, как и раньше,—в ячейке INPUT + i(1 : 3). Каково время работы программы с этими изменениями?

7.[20] Обсудите, в чем состоит сходство и отличие алгоритма R и алгоритма обменной поразрядной сортировки (алгоритм 5.2.2R).

>8. [20] В алгоритмах поразрядной сортировки, обсуждавшихся в тексте, предполагалось, что все сортируемые ключи неотрицательны. Какие изменения следует внести в эти алгоритмы в том случае, когда ключами могут быть и отрицательные числа, представленные в дополнительном или обратном коде?

9.[20] (Продолжение упр. 8.) Какие изменения нужно внести в эти алгоритмы в случае, когда ключами являются числа, представленные в виде абсолютной величины со знаком?

10.[30] Сконструируйте алгоритм поразрядной сортировки ”сначала-по-старшей-цифре”, использующий связанное распределение. (Так как размер подфайлов все уменьшается, то разумно уменьшить M, а для сортировки коротких файлов применить не поразрядную сортировку.)

11.[16] Перестановка шестнадцати исходных чисел, показанная в табл. 1, содержит вначале 41 инверсию. После завершения сортировки инверсий, разумеется, нет совсем. Сколько инверсий осталось бы в файле, если бы мы пропустили первый просмотр, а выполнили бы поразрядную сортировку лишь по цифрам десятков и сотен? Сколько инверсий останется, если пропустить как первый, так и второй просмотры?

12.[24] (М. Д. Макларен.) Предположим, алгоритм R применили только к p старшим цифрам реальных ключей; тогда файл, если читать его по порядку, указанному связями, почти отсортирован, но ключи, у которых старшие p цифр совпадают, могут быть неупорядочены. Придумайте алгоритм переразмещения записей на том же месте так, чтобы ключи расположились по порядку: K1 K2 : : : KN. [Указание: частный случай, когда файл полностью отсортирован, можно найти в ответе к упр. 5.2–12, его можно скомбинировать с простыми вставками без потери эффективности, так как в файле осталось мало инверсий.]

13.[40] Реализуйте метод внутренней сортировки, предложенный в тексте в конце этого пункта, получив программу сортировки случайных данных за O(N) единиц времени, требующую всего O(N) дополнительных ячеек памяти.

14.[22] Последовательность игральных карт можно отсортировать в возрастающем порядке: Т 2 : : :В Д К от верхней карты к нижней, за два просмотра, раскладывая карты каждый раз лишь в две стопки: разложите карты лицевой стороной вниз в две стопки, содержащие соответственно Т 2 9

Original pages: 218-294 119

3 10 и 4 В 5 6 Д К 7 8 (от нижней карты к верхней); затем положите вторую стопку поверх первой, поверните колоду лицевой стороной вверх и разложите в две стопки Т 2 3 4 5 6 7 8 и 9 10 В Д К. Соедините эти две стопки и поверните их лицевой стороной вверх. Колода отсортирована.

Докажите, что приведенную выше последовательность карт нельзя отсортировать в убывающем порядке: К Д В : : :2 Т, от верхней карты к нижней, за два просмотра, даже если разрешено раскладывать карты в три стопки. (Сдавать карты, нужно всегда сверху колоды, поворачивая их при раздаче рубашкой вверх. На рисунке верхняя карта колоды изображена справа, а нижняя—слева.)

15.[М25] Рассмотрите задачу из упр. 14 в случае, когда карты раздаются лицевой стороной вверх, а не вниз. Таким образом, один просмотр можно потратить на преобразование возрастающего порядка в убывающий. Сколько нужно просмотров?

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

Чарльз Бэббидж (1864)

5.3. ОПТИМАЛЬНАЯ СОРТИРОВКА

Теперь, когда мы проанализировали такое множество методов внутренней сортировки, пришло время обратиться к более общему вопросу: какой метод внутренней сортировки наилучший? Существует ли такой верхний предел скорости сортировки, которого бы не мог достичь ни один программист, как бы искусен он ни был?

Разумеется, наилучшего возможного способа сортировки нет; мы должны точно определить, что понимать под словом ”наилучший”, но не существует наилучшего возможного способа определить слово ”наилучший”. Аналогичные вопросы об оптимальности алгоритмов мы обсуждали в п. 4.3.3, 4.6.3 и 4.6.4, где рассматривалось умножение с высокой точностью и вычисление полиномов. В каждом случае, для того чтобы выполнялись условия ”достаточности”, т. е. чтобы задача стала разрешимой, необходимо было сформулировать довольно простое определение алгоритма, ”наилучшего из возможных”. И в каждом случае перед нами вставали интереснейшие задачи, настолько сложные, что они до сих пор полностью не решены. Так же обстоит дело и с сортировкой: были получены некоторые интересные результаты, но осталось еще много интригующих вопросов, на которые до сих пор нет ответов.

Изучение внутреннего механизма методов сортировки обычно было направлено на минимизацию числа сравнений ключей при сортировке n элементов, или слиянии m элементов с n элементами, или выборе t-гo наибольшего элемента из неупорядоченного набора n элементов. В п. 5.3.1, 5.3.2 и 5.3.3 эти вопросы обсуждаются в общем случае; в п. 5.3.4 рассматриваются аналогичные вопросы с интересным ограничением: последовательность сравнений должна быть, по существу, заранее фиксирована. Некоторые другие типы интересных теоретических вопросов, связанных с оптимальной сортировкой, можно найти в упражнениях к п. 5.3.4 и в обсуждении внешней сортировки в п. 5.4.4.

5.3.1. Сортировка с минимальным числом сравнений

Очевидно, минимальное число сравнений ключей, необходимое для сортировки n элементов, равно нулю, поскольку, как мы видели, существуют методы поразрядной сортировки, в которых вообще не выполняется сравнений. В самом деле, можно написать MIX-программы, способные сортировать и не содержащие тем не менее ни одной команды условного перехода! (См. упр. 5-6 в начале этой главы.) Мы также встречались с несколькими методами сортировки, которые, по существу, были основаны на сравнении ключей, но время работы которых на деле определялось другими факторами, такими, как перемещение данных, вспомогательные операции и т. д.

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

Чтобы исключить поразрядную сортировку, где совсем не выполняется сравнений, ограничимся обсуждением методов сортировки, основанных только на абстрактном линейном отношении порядка ”<” между ключами, рассмотренном в начале этой главы. Для простоты мы также ограничим свое обсуждение случаем различных ключей, а это значит, что при любом сравнении ключей Ki и Kj

120Original pages: 218-294

возможны лишь два исхода: либо Ki < Kj, либо Ki > Kj. (Распространение этой теории на общий случай, когда допускаются равные ключи, см. в упр. от 3 до 12.)

Задачу сортировки посредством сравнений можно сформулировать также другими эквивалентными способами. Если есть n грузов и весы с двумя чашами, то каково минимальное число взвешиваний, необходимое для того, чтобы расположить грузы по порядку в соответствии с весом, если в каждой чаше весов помещается только один груз? Или же, если в некотором турнире участвуют n игроков, то каково наименьшее число игр, достаточное для того, чтобы распределить места между соревнующимися в предположении, что силы игроков можно линейно упорядочить (ничейные результаты не допускаются).

Методы сортировки n элементов, удовлетворяющие указанным ограничениям, можно представить посредством структуры расширенного бинарного дерева, такого, как показано на рис. 34. Каждый внутренний узел (изображенный в виде кружочка) содержит два индекса ”i : j” и означает сравнение ключей Ki и Kj. Левое поддерево этого узла соответствует последующим сравнениям, которые необходимо выполнить, если Ki < Kj, а правое поддерево—тем действиям, которые необходимо предпринять в случае Ki > Kj. Каждый внешний узел дерева (изображенный в виде прямоугольника) содержит перестановку a1 a2 : : :an

Picture:

Рис. 34. Дерево сравнений для сортировки трех элементов.

множества f1; 2; : : :; n g, обозначающую тот факт, что было установлено упорядочение

Ka1 < Ka2 < : : : < Kan:

(Если взглянуть на путь от корня к этому внешнему узлу, то каждое из n−1 соотношений Kai < Kai+1, где 1 i < n, будет результатом некоторого сравнения ai : ai+1 или ai+1 : ai на этом пути.)

Так, на рис. 34 представлен метод сортировки, согласно которому нужно сначала сравнить K1 с K2; если K1 > K2, то продолжать (двигаясь по правому поддереву) сравнивать K2 с K3, а затем, если K2 < K3, сравнить K1 с K3; наконец, если K1 > K3, становится ясно, что K2 < K3 < K1. Реальный алгоритм сортировки обычно будет также перемещать ключи по файлу, но нас интересуют только сравнения, поэтому мы игнорируем все перемещения данных. При сравнении Ki с Kj в этом дереве всегда имеются в виду исходные ключи Ki и Kj, а не те ключи, которые могли занять i-ю и j-ю позиции в файле в результате перемещения записей.

Возможны и избыточные сравнения; например, на рис. 35 нет необходимости сравнивать 3 : 1, поскольку из неравенств K1 < K2 и K2 < K3 следует K1 < K3. Никакая перестановка не может соответствовать левому поддереву узла < 3 : 1 > на рис. 35, так что эта часть алгоритма никогда не будет выполняться! Поскольку нас интересует минимальное число сравнений, то можно считать, что избыточных сравнений не производится; с этого момента мы будем иметь дело со структурой расширенного бинарного дерева, в котором каждому внешнему узлу соответствует некоторая перестановка. Все перестановки исходных ключей возможны, и каждая перестановка определяет единственный путь от корня к внешнему узлу; отсюда вытекает, что в дереве сравнений для сортировки n элементов без избыточных сравнений имеется ровно n! внешних узлов.

Picture: Рис. 35. Пример избыточного сравнения.

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

Пусть S(n)—минимальное число сравнений, достаточное для сортировки n элементов. Если все внутренние узлы в дереве сравнений располагаются на уровнях < k, то очевидно, что в дереве не может быть более 2k узлов. Следовательно, полагая k = S(n), имеем

n! 2S(n):

Поскольку S(n)—целое число, то можно записать эту формулу иначе, получив нижнюю оценку:

S(n) dlog2 n!e:

 

 

(1)

Заметим, что по формуле Стирлинга

 

 

 

dlog2 n!e = n log2 n − n=(ln2) +

1

log2 n + O(1);

(2)

 

2

Соседние файлы в папке Дональд Кнут. Искусство программирования. т.3