
6.3. Сортировка вставками
Метод сортировки, который часто применяют игроки в бридж по отношению к картам на руках, заключается в том, что отдельно анализируется каждый конкретный элемент, который затем помещается в надлежащее место среди других, уже отсортированных элементов, В условиях компьютерной реализации следует позаботиться о том, чтобы освободить место для вставляемого элемента путем смещения больших элементов на одну позицию вправо, после чего на освободившееся место помещается вставляемый элемент. Функция sort из программы 6.1 является программной реализацией этого метода, получившего название сортировки вставками (insertion sort).
Как и в случае сортировки выбором, элементы, находящиеся слева от текущего индекса, отсортированы в соответствующем порядке, тем не менее, они еще не занимают свои окончательные позиции, ибо могут быть передвинуты с целью освобождения места под меньшие элементы, которые обнаруживаются позже. Массив будет полностью отсортирован, когда индекс достигнет правой границы массива. На рис. 6.3 показано, как работает этот метод на примере учебного файла.
Реализация сортировки вставками, представленная в программе 6.1, проста, но не может считаться эффективной. Сейчас мы рассмотрим три способа его совершенствования, иллюстрирующих мотив, который прослеживается во многих разрабатываемых реализациях, а именно: требуется получить компактный, понятный и эффективный программный код, однако эти целевые установки время от времени вступают между собой в противоречие, поэтому часто приходится искать компромисс. Это достигается путем разработки естественной программной реализации с последующим ее улучшением за счет определенной последовательности преобразований с (проверкой эффективности (и правильности) каждого такого преобразования.
A |
S |
O |
R |
T |
I |
N |
G |
E |
X |
A |
M |
P |
L |
E |
РИСУНОК 6.3. ПРИМЕР ВЫПОЛНЕНИЯ СОРТИРОВКИ ВСТАВКАМИ Во время первого прохода сортировки вставками элемент S, занимающий вторую позицию, больше А, так что трогать его не надо. На втором проходе, когда в третьей позиции встречается элемент О, он меняется местами с S, так что последовательность становится A O S отсортированной и т.д. Незаштрихованные элементы – это те, которые были передвинуты на одну позицию вправо |
A |
S |
O |
R |
T |
I |
N |
G |
E |
X |
A |
M |
P |
L |
E |
|
A |
O |
S |
R |
T |
I |
N |
G |
E |
X |
A |
M |
P |
L |
E |
|
A |
O |
R |
S |
T |
I |
N |
G |
E |
X |
A |
M |
P |
L |
E |
|
A |
O |
R |
S |
T |
I |
N |
G |
E |
X |
A |
M |
P |
L |
E |
|
A |
I |
O |
R |
S |
T |
N |
G |
E |
X |
A |
M |
P |
L |
E |
|
A |
I |
N |
O |
R |
S |
T |
G |
E |
X |
A |
M |
P |
L |
E |
|
A |
G |
I |
N |
O |
R |
S |
T |
E |
X |
A |
M |
P |
L |
E |
|
A |
E |
G |
I |
N |
O |
R |
S |
T |
X |
A |
M |
P |
L |
E |
|
A |
E |
G |
I |
N |
O |
R |
S |
T |
X |
A |
M |
P |
L |
E |
|
A |
A |
E |
G |
I |
N |
O |
R |
S |
T |
X |
M |
P |
L |
E |
|
A |
A |
E |
G |
I |
M |
N |
O |
R |
S |
T |
X |
P |
L |
E |
|
A |
A |
E |
G |
I |
M |
N |
O |
P |
R |
S |
T |
X |
L |
E |
|
A |
A |
E |
G |
I |
L |
M |
N |
O |
P |
R |
S |
T |
X |
E |
|
A |
A |
E |
E |
G |
I |
L |
M |
N |
O |
P |
R |
S |
T |
X |
|
A |
A |
E |
E |
G |
I |
L |
M |
N |
O |
P |
R |
S |
T |
X |
Прежде всего, можно отказаться от выполнения операций compexch, если встречается ключ, который не больше ключа вставляемого элемента, поскольку подмассив, находящийся слева, уже отсортирован. А именно, если справедливо условие a[j-l] < a[j], то выполняя команду break, можно выйти из внутреннего цикла for в функции sort программы 6.1. В связи с таким изменением реализация превращается в адаптивную сортировку, благодаря чему быстродействие программы, примененной для сортировки ключей, упорядоченных случайным образом, повышается примерно в два раза (см. лемму 6.2). Внеся усовершенствования, описанные в предыдущем параграфе, получаем два условия прекращения выполнения внутреннего цикла – можно изменить программой: код и представить в виде цикла while, дабы отобразить этот факт наглядно. Менее очевидное улучшение реализации следует из того факта, что проверка условия k>1 обычно оказывается излишней: и в самом деле, она достигает цели только в случае, когда вставляемый элемент является наименьшим из просмотренных к этому моменту, благодаря чему он достигает начала массива. Широко используемая альтернатива этому заключается в том, чтобы сохранять сортируемые ключи в элементах массива от а[1] до a[N], а сигнальный ключ (sentinel key) поместить в а[0], устанавливая его значение, по меньшей мере, не превышающим наименьшего ключа в сортируемом массиве. Теперь проверка того факта, что обнаружен ключ меньше сигнального, одновременно становится проверкой обоих представляющих интерес условий, благодаря чему внутренний цикл становится меньше, а быстродействие программы повышается.
Сигнальные ключи иногда не слишком удобны в применении: по-видимому, не очень-то просто определить значение минимально возможного ключа либо вызывающая программа не располагает местом под дополнительный ключ. В программе 6.3 предлагается один из способов обойти обе упомянутых проблемы в условиях сортировки вставками: сначала выполняется отдельный проход вдоль массива, в результате которого элемент с минимальным ключом помещается в первую позицию. Затем сортируется остальной массив, при этом первый элемент, он же наименьший, служит в качестве сигнального ключа. В общем случае следует избегать употребления в программах сигнальных ключей, поскольку зачастую легче воспринимаются программные коды с явными проверками, однако ситуации, когда сигнальные ключи могут оказаться полезными в плане упрощения программы и повышения ее эффективности, обязательно будут фиксироваться.
Программа 6.3, Сортировка вставками
Этот программа представляет собой усовершенствованный вариант функции sort из программы 6.1, поскольку: (1) он помещает наименьший элемент массива в первую позицию; в этом качестве наименьший элемент может быть использован как сигнальный ключ; (2) во внутреннем цикле он выполняет лишь одну операцию присваивания; операция обмена исключается; и (3) он прекращает выполнение внутреннего цикла, когда вставляемый элемент уже находится в требуемой позиции. Для каждого i он сортирует элементы а[1],..., a[i], перемещая на одну позицию вправо элементы а[1],..., а[i–1] из отсортированного списка, которые по значению больше a[i], после чего a[i] попадает в соответствующее место.
template <class Item>
void insertion (Item a[], int m, int r)
{ int i;
for (i=r; i>m; i––) compexch(a[i-l], a[i]);
for (i=m+2; i<=r; i++)
{
int j=i;
Item v=a[i];
while (v<a[j-l])
{ a[j]=a[j-l]; j––;}
a[j]=v;
}
}
Третье улучшение, которое сейчас будет рассматриваться, также касается удаления лишних команд из внутреннего цикла. Оно следует из того факта, что последовательные обмены значениями с одним и тем же элементом не эффективны. Если производятся два или большее число обменов значениями, то мы имеем
t = a[j]; a[j] = a[j-lj; a[j-l] = t;
за которым следует
t = a[j-l]; a[j-l] = a[j-2]; a[j-2] = t
и т.д. Значение t в этих двух последовательностях не изменяется, но при этом происходит бесполезная трата времени на его запоминание и последующее чтение с целью следующего обмена значениями. Программа 6.3 передвигает большие элементы на одну позицию вправо вместо того, чтобы воспользоваться операцией обмена, тем самым избегая напрасной траты времени.
Программа 6.3 является реализацией сортировки методом вставки, обладающей большей эффективностью, нежели программная реализация этого же метода сортировки, включенная в программу 6.1 (в разделе 6.5 мы убедимся в том, что ее быстродействие примерно в два раза выше). В рамках данной книги нас интересуют не только элегантные и одновременно эффективные алгоритмы, но и элегантные и одновременно эффективные реализации этих алгоритмов. В подобных случаях положенные в их основу алгоритмы несколько отличаются друг от друга – было бы правильно назвать функцию sort из программы 6.1 неадаптивной сортировкой вставками (nonadaptive insertion sort). Правильное понимание свойств алгоритма – лучшее руководство при разработке его программной реализации, которая может эффективно использоваться в различных приложениях.
В отличие от сортировки выбором, время выполнения сортировки вставками зависит главным образом от исходного порядка ключей при вводе. Например, если файл большой, а ключи записей уже упорядочены (или почти упорядочены), то сортировка вставками выполняется быстро, а сортировка выбором протекает медленно. Более полное сравнение различных алгоритмов сортировки приводится в разделе 6.5.