Вы находитесь на странице: 1из 1001

Р.

СIЩЖ ВИК АлГОРIml<Ы на С ++

Рис. 5.9. Двоич ный подсчет и функция рисования линейки

Вычисление функции рисования линейки эквивалентно под счету


количества оконечных нулей в четных N -разрядны х числах.

Программа 5.9 - альтернативный способ рисования линейки , на


который натолкнуло соответствие с двоичными числами (см. рис . 5.10).
Эту версию алгоритма называют восходящей (bottom -up) реализацией.
Она не является рекурсивной, но определенно навеяна рекурсивным
алгоритмом. Эта связь между ал горитмами "разделяй и властвуй " и
двоичными пр едставлениями чисел часто помогает найти решение при
анализе и р азработке усовершенствованны х версий , таких как
восходящие подходы. Мы будем рассматривать данную возможность ,
чтобы понять и, возможно , усоверше нс твовать каждый
рассматриваемый алгоритм вида ' 'разделяй и властвуй ".

Восходящий подход предполагает изм ене ни е порядка выполнения


вычислений при рисовании л ин ейки. На рис. 5.11 показан еще один
при мер , в котором изменен порядок следования трех вызовов функций
в рекурсивной реализации. Этот прим ер соответствует рекурсивному
рисованию первоначально описанным способом: нанесение средней
метки, затем левой половины, а затем правой. Последовательность
нан есения меток выглядит сложной , но является результатом простой
п еремены мест двух операторов в прогр амме 5.8. Как будет показано в
разд еле 5.6, взаимосвязь междУ ри с. 5.8 и рис . 5.11 срадни различию
между постфиксными и пр ефиксными арифметическими выражениями.

Программа 5.9. Нерекурсивная программа для рисования л ин ейки

В отличие от про граммы 5.8, линейку можно нарисовать, вначале


и зобраз ив все метки длиной 1, затем все метки длиной 2 и т.д.
Переменная t пр едставляет длину меток, а переменная j - количество
меток между д вумя последовательными метками дли ной t. Внешний
циЮl for увеличивает значение t при сохранении соотноше ния j = 2
-1 . Внугренний циЮl for рисует все метки длиной t .

vою rule(int L iпt г, int h)


{
fo r ( iпt t :::: 1, j == 1; t <== h; j += j, t++)
fo r ( iпt i = О; l+j+i <= г; i + = j+j)
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

mark(l+j+\ ');
}

Возможно, нанесение меток в порядке, показанном на ри с. 5.8, более


удобно по сравнению с вычислениями в изм ен е нн ом порядке , которые
содержатся в программе 5.9 и прив едены на ри с. 5.11, по скольку в этом
случае можно нарисовать л ин ейку прои звольной длины . Достаточно
представить себе графическое устройство, которое просто непрерывно
перемещается от одной метки к следУЮщей. Анало гично , при решении
задачи о ханойских башнях мы ограничены по следовательностью
перемещений, которая должна быть выполнена. Вообще говоря , мно ги е
рекурсивные программы основываются на решениях под задач, которые

должны быт ь выполнены в конкретном порядке. Для других


выч ислений (см . , например , программу 5.6) порядок решения подзадач
роли не играет. Для таких вычислений единственным ограниче ни ем
служит н еобходимость решения подзадач п еред тем , как можно будет
решить главную задачу. Понимание то го , когда можно изменять порядок
вычисления , не только служит Юlючом К успешной разработке
ал горитма , но и во многих случаях оказывает непосредственное

практическое влияние. Например, этот вопрос ИСЮlючительно важен


при реализации алгоритмов для работы на параллельных пр оцессорах.

Восходящий подход соответствует общему методу разработки


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

элемен тарных подзадач с по следующим объеди н ени ем этих решений


дл я получения решения несколько больших подзадач и т.д., пока вся
задача н е БУдет решена. Этот под ход МОЖНО было бы назвать "объединяй
и властвуй ".

Л ишь н еболь шой шаг отделяет рисование линеек от рисования


д вумерных узоров , похожих на показанный на рис . 5. 12. Этот рисунок
показывает, как просто е рекурсивное описание может приводи ть к

сложным на ви д вычислениям (см. упражнение 5.30).

'"
р СIЩЖВИК АлГОРIml<Ы на С ++

" """"""

,1,1,1,1,1,1,1, ,1,1,1,1,1,1,1,

1111111111111111111111111111111

Рис. 5.10. Рисование линейки в восходящем порядке

Для рисования линейки нерекурсивным методом в н ачале рисуются все


метки длиной 1и пр опускаются п озиции , затем рисуются метки дли н ой
2 и пропускаются остающиеся п озиции , зате м рисуются метки длиной 3
с п ро п уском остающихся позиций и т. д.
Р. СIЩЖВИК АлГОРIml<Ы на С ++

гulв (O I 3:, З)
шагJt(4 , З)
nйe(O , 41 2)
!!II.aU. (2:, 2)
rule(O~ 2. 1)
::nark( l. 1)
:rule CO . 1 ~ О)
:rule ( l. 2:~ О)
rule(2:~ 40, 1)
mark:(3. 1 )
:rul8(2 . 3, О)
n1e(3. 4. О)
ru1e<4. В. 2)
"""'(6. 2)
nЙO(4. 6. 1)
mark(Б . 1)
:rule (4 . 1:>, О)
:ru1e (Б , 6, О)
rule (б, 8 , 1)
mark(7 . 1)
:rulе (б . 7~ О)
:rule(7 . 8, о)

Рис . 5. 11 . Вы зов ы функций для р и сова ни я л ин ейки (верс и я с


использован ием пр ямого обхода)

Эта п оследовательн ость отображает результат нан есе ни я меток перед


р екурс ивными вы зовами, а не между н ими.
, СIЩЖ ВИК АлГОРIml<Ы на С ++

•••••••••••••••
•••• •••••••••••
•••• •••••••••••
••••
•••• •••••••••••
•••••••••••
••• ••
••
••
• ••
•• ••
•••• • ••
•••••••••••••
•••• •••••••••••
•••• •••••••••••
•••• •••••••••••
ICICICICICICICIC
ICICICICICICICIC
ICICICICICICICIC
ICICICICICICICIC
ICICICICICICICIC
хххххххх
ХХХХХХХХ
хххххххх

ММИМ
ММИХ
ММИХ
ммих

Рис. 5. 12. Д вум ер на я фрактальная з везда

Этот фрактал - двуме рн ая версия рис. 5. 10. Очерченные квад р аты на


ни жнем рисунке демо нс тр ируют р екурс ивную структуру вычисления.

Р екурс ивно определен ные геаметричеСЮ1е узо ры , наподобие


п оказанно го на рис . 5.1 2, ин огда называют фракталами. При
и(пол ьз ов ании более сложных при ми тивов рисования и более
сложн ых рекурс ивных функций (особен н о рекурсив н о определенных
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

функций на вещественной оси и комплексной плос кости ) можно


получи ть п о р аз и тел ьно р аз нообра з ны е и слож ны е узоры. На ри с. 5.13
прив еде н еще один прим е р - з в езда Коха, котор ая определяетс я
рекурсивно следующим образом: з в езда Коха п орядка О - пр остой
вы ступ, по казанный на рис. 4. 3, а звезда Коха n - го порядка - это звезда
Коха порядка п - 1, в которой каждый отрезок заменен звездой порядка
О соответствую ще го размера.

подобно р еше ния м задач рисования л инейки и ханойски х ба шен , эти


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

соответствующей системе счисле ния (см. упражнение 5.34).


Р. СIЩЖВИК АлГОРIml<Ы на С ++

2 CDpy gв { dllp О r1in.8tCJ }


{
Зidi:v
:.:: DiJП loochR
00 :rvl:ate
:2 D:IJIY loocbR
- 120 mt:a.te
2 D:IJIY kocbR
f1:) IVtIl.W
2 0'JFl ko cЪR.
}ifelse
р;>р р>р

Н"'
D О !tOOVe'tQ
2781 kOChR
О 'Е шrJЧetао
91 81 100cJlR
О 54I!ЮЧ8tCJ
381 kocbR
D 81 !IЮVet()
i 81 loocJlR
,troloo

Рис. 5.13. Рекурсивная PostScript -прогр амма для рисования фрактала


Коха

ЭТО изменение про граммы PostScript, приведенной на рис. 4.3,


пр еоб р азует результат ее р аботы в фрактал (см. текст).

3адача о ханойских башнях, р и сова ни е линейки и фракталы вес ь ма


Р. СIЩЖВИК АлГОРIml<Ы на С ++

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

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


понимание одного из основных методов разработЮ1 алгоритмов -
деление задачи пополам и независимое решение одной или обеих
половин задачи. Возможно, это наиболее важная и з всех технолоrnй ,
рассматриваемых в книге. В таблица 5.1 подробно описаны бинарный
поиск и сортировка слиянием, которые не только являются важными и

широко используемыми на практике алгоритмами , но и служат

типичными примерами разработЮ1 алгоритмов вида "разделяй и


властвуй ".

Бинарный поиск (см. ' Принципы анализа алгоритмов" и 'Табл ицы


символов и деревья бинарного поиска ' ) и сортировка слиянием (см.
"Слия ние и сортировка слиянием') - типичные алгоритмы "разделяй и
властвуй ", которые обеспечивают гарантированную оптимальную
прои зводи тельность , соответственно, поиска и сортировЮ1.

Рекуррентные соотношения демонстрируют сущность вычислений


методом "разделяй и властвуй " для каждого алгоритма. (Вывод
решений , прив еде нны х в правом столбце, см. в разделах 2.5 ' Принципы
анализа алгоритмов" и 2.6.) При бинарном поиске задача делится
пополам , выполняется одно сравнение, а затем рекурсивный вызов для
одной И З половин. При сортировке слиянием задача делится пополам ,
за тем выполняется рекурсивная обработка обеих половин, после чего
прогр амма выполняет N сравнений. В книге БУдет рассмотрено
множество других алгоритмов, разработанных с применением этих
рекурсивных схем.

Таблица 5.1. Основные алгоритмы типа ' 'р аздел яй и властвуй "
рекуррентное приближенное
соотношение решение

Бинарный поиск

количество сравнений 19 N
Сортировка слиянием

количество рекурсивных
N
вызовов

количество сравнений NIgN

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Быстрая сортировка (см. ' 'Быстрая сортировка ' ) и поиск в би нарном


дереве (см. ' Та бл ицы символ ов и де ревья бинаDНОГО п о иска ' )
представляют важную разновидность базового подхода ' 'р азделяй и
властвуй ", в которой задача р азбивается на под за.цачи размеров k- 1и N
- k для некоторого k, определенного во входных данных. При случайных
входных данны х эти алгоритмы разбивают задачи на подзадач и ,
размеры которых в среднем вдвое меньше исходного (как в сорти р овке
слиянием или бинарном поиске). Влияние этого различия будет
рассмотрено при изучении этих алгоритмов.

Заслуживают внимания и следУЮщие разновидности основной темы:


разбиение на части различных размеров, разбиение более чем на две
части , разбиение на перекрывающиеся части и выполнение различного
объема вычислений в н ерекурсивной части алгоритма. В общем случае
ал горитмы "разделяй и властвуй" требуют выполнения вычислений для
разбиения входного массива на части , либо для объединения
результатов обработки двух независимо решенных частей исходной
задачи , либо для упрощения задачи после того как полови на входного
массива обработана. То есть, код может находиться перед, после и
между двумя рекурсивными вызовами. Естественно, подобные
вариации приводят к созданию более сложных ал горитмов , чем
бинарный поиск или сортировка сл ияни ем , и эти алгоритмы труднее
анализировать. В дан ной книге будУТ рассмотрены многочисленные
примеры; к более сложным при ложениям и способам анализа мы
вернемся в части У IIl.

Упражнения

5. 16. Напишите рекурсивную программу, которая на ходи т максимальный


элемен т в массиве, выполняя сравнение первого элемента с

максимальным элементом остальной части массива (найденным


рекурсивно).

5. 17. Напишите рекурсивную программу, которая на ходи т максимальный


элемен т в связном списке.

5. 18 Измените программ у ' 'разделяй и властвуй " для отыскания


максимального элемента в массиве (программа 5.6), чтобы она делила
массив р азмера N на две части , одна из которых имеет размер k=2risN
1, а вторая -N- к (чтобы размер хотя бы одной части был степенью 2).

'"
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

5.19. Нарисуйте дерево, которое соответствует рекурсив н ым вызовам ,


выполняемым п ро граммой из упражнения 5.18 при размере массива 11.

5.20. Методом индукции докажите, что количество вызовов функции ,


выполняемых любым ал горитмом ''разделяй и властвуй ", который
делит задачу н а части , в сумме составляющие задачу в целом, а затем

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

5.21 . Докажите, что рекурсив н ое решение задачи о ха Н ОЙСЮ1х башнях


(программа 5.7) является оптимальным. То есть п окажите , что любое
решение требует п о меньшей мере 2N - 1 переЮIaдываниЙ.

5.22 . Напишите рекурсивную п рограмму, которая вычисляет дли н у i -ой

метки на линейке с 2 П
- 1 метками.

5.23. Проанализируйте таблицы " -разрядных чисел наподобие


приведенной на ри с . 5.9 и определите свойство i -го числа ,
определ яющего направле н ие i - го перемещения (указанно го знаковым
битом на рис. 5.7) при решении задачи о ханойсЮ1Х башнях.

5.24. Напиши те про грамм у, которая выдает решение задачи о хаНОЙСЮ1х


башнях п угем заполнения массива, содержащего все п еремеще н ия , как
сделано в про грамме 5.9.

5.25. Напи шите рекурсивную прorрамму, которая заполняет массив


размером n х 2 n нулями и единицами таким образом, чтобы массив
представлял все n -р аз р ядн ые числа , как п оказано на рис. 5.9.

5.26. Приведите результаты использования рекурсивной прorраммы


рисования линейЮ1 (программа 5.8) для следующих значений
ap ryмeHToB: ru1e(0 , 1 1, 4) r u1e(4 , 20, 4) и r u1e( 7 ,
ЗА , 5).

5.27. Докажите следующее свойство п рограммы рисования линейЮ1


(программа 5.8): если разность междУ ее п ервыми двумя ар гументами
является сте п е н ью 2, то оба ее рекурсивных вызова также обладают
этим свойством.

5.28. Напиши те функцию , которая эqxpeктив н о вычисляет количество


завершающих нулей в двоичном представлении целого числа .
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

5.29. Сколько квадратов изображено на рис. 5.12 (включая и т е, которые


CKpbIТЫ БОЛЫlIИ.МИ квадратами)?

5.30. Напишите рекурсивную пр о грамм у на С ++ , результатом которой


будет PostScript - пр о грамма в qюрме списка вызовов функций х у r
Ьо х, которая вычерчивает нижнюю диагр амму на рис . 5. 12; функция
Ьох рисует квадрат r xr в точке с коорд инатами ( х, у) . Реализуйте
функцию Ьох в виде команд PostScript (см. "Абст рактные т ипы да нны х' ').

5.31 . Напишите восходящую н ерекурсивную пр о грамм у (аналогичную


программе 5.9), которая вычерчивает верхнюю часть ри с. 5.1 2
способом , описанным в упражнении 5.30.

5.32 . Напишите PostScript - пр о грамм у, вычерчивающую нижнюю часть


ри с. 5. 12.

5.33. Сколько прямолинейны х отрезков содержит звезда Коха " -го


порядка ?

5.34. Вычерчивание звезды Коха n - го порядка сводится к выполнению


последо вательности команд ви да ' ,nов ернугь на а гр адУСОВ, затем
прочертить отрезок длиной 1/3" ". Най дите связь с системами
счисления , которая по зволяет вычертить звезду пугем увеличения

значения счетчика и последУЮщего вычисления угла а из этого

знач ения .

5.35. И зме ни те программу рисования звезды Коха , прив еде нную на рис.
5.13, для создания д ругого фрактала , на основе фигуры , со стоящей из 5
линий нулевого порядка, вычерчиваемых смещениями на одну
условную единицу в во сточно м, се верном , во сточн ом, южном и

во сточном направления х (см. рис. 4.3).

5.36. Напишите рекурсивную функцию ' 'разделяй и властвуй " дл я


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

Динамическое программирование
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

Основная характеристика алгоритмов вида "разделяй и властвуй ",


рассмотренных в разделе 5.2 - разбиение и ми задачи на независимые
п о.цзадачи. Если по.цзадачи не являются н езависимыми, ситуация
усложн яется , в первую очередь потому, что н епо средственная

рекурсивная реализация даже пр остейших алгоритмов этого тип а может


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

Например , программа 5.10 - н еп осредственная рекурсивная реализация


рекуррентного соотношения , определяюще го числа Фибоначчи (см.
"Э л е м е нта рные структуры данных' '). Не используйте эту программу -
она весьма неэфрективна. Действи тельн о, количество рекурсивных
вызовов для вычисления FN равно FN + 1 . НО F N при ближенно равно фN ,
где Ф " 1,618 - пропорция золотого сечения. Как это ни Удивительно, н о
для программы 5. 10 время этого элементарного вычисления
определяется экспоненциальной зависимостью . На ри с. 5.14, на
котором прив едены рекурсивные вызовы для небольшого при мера ,
наглядно демонстрируется тр ебуемый объем повторных вычислений.

1 О

Рис. 5.14. Структура рекурсивного алгоритма для вычисления чисел


Фибоначчи

Из схемы рекурсивных вызовов для вычисления F8 с помощью

стандартного рекурсивного ал гори тма видно, как рекурсия с

п ерекрывающимися подзадачами может прив ести к экспо н е нциальному

возрастанию затрат. В данном случае второй рекурсивный вызов


игнорир ует вычисление, выполненное во время п ервого вызова, что

при водит к значительным повторным вычислениям, поскольку эфрект


нараста ет в геометрической прогрессии. Рекурсивные вызовы для
вычисления F 6 '" 8 (показаны в пр авом подцереве корня и в левом

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

П ОДДереве лево го подцерева корня) приведены ниже.

BF (6)
5 У(5)
э У(4)
2 «3)
1 F(2)
1 F(1)
О «О)
1 У(1)
1 У ( 2)
1 F(1)
О F(O)
2 П3)
1 У ( 2)
1 F(1)
О F(O)
lFШ
3 у(4)
2 F (3)
1 У(2)
1 <(1)
О У(О)
lFШ
1 F (2)
lFШ
О «О)

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


п ервые N чисел Фибоначчи за время, пропорциональное N:

F[O] = О ; F[1 ] = 1;
for (i == 2; i <= N; i++ )
F[i] = F[i -1 ] + F [i -2];

Числа возрастают экспоненциально , поэтому массив не должен бы ть


большим; наприм ер, F 45 1836311903 - наи большее число

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


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

Этот под ход дает н епосредственный способ получения численных

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

решений для любых рекуррентных соотн о шений. В случае с числами


Фибоначчи мож но обойтись даже без массива и ограничиться тол ько
по след ни м и двумя з н ач е ния ми (см. ynр а жн е ни е 5.37); однако во мно ги х
APyrn:x случаях часто встречающиXLЯ рекуррентных соотноше ний (см. ,
наприм ер, ynражнение 5.40) н еобходим массив для хра н ен ия всех
и звестны х знач е ний .

Программа 5.10. Числ а Фи бо нач ч и (рекурсивная реализация)

Эта программа выглядит компактно и и зя щно , однако не прим енима на


пр акти ке, поскольку время вычисления FN экс пон ен циально зависит от

N . Время вычисления F N + 1 в ф " 1 .б раз больше времени вычисления

F N' Например, поскольку ~ > БО, если для вычисления Fп компьютеру


требуется около секунды , то для вычисления F N +9 по требуется более

ми н угы , а для вычисления F N + 1B - более часа.

iпt F(iпt i)
{
иО < l)геturпО ;
if (i == 1) гешт 1;
геturп F(i -1) + F(i -2);
}

Р екур р е нтн ое соотн о ше ни е ЭТО рекурсивная функц ия с

ц ело чи сленными значениями. Рассуждения , прив еде нны е в


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

текущего значения. Эта техн и ка называ ется восходящим динами ч ески м


прогр амм иров а ни ем (bottom -ир dynamic ргоgrаmmiпg). Она при ме нима к
любому рекурсивному вычислению при условии, что есть возможность
хра нить все ранее вычисленные значения . Такая те хника разработки
алгоритмов ус п е uпю используется для решения широкого круга за.цач.

Так чт о обратите внимание на эту простую т ехноло гию , кото р ая может


и змен и ть время выполнения ал гори тма с экспоненциального на

линейное!

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Нисходящее динам и ческое пр о граммирование (top -down dynamic


ргоgrаП1IТllng) еще более простая техника, которая позволяет
автоматичесЮ1 выполнять рекурсивные функции при том же (или
меньшем) количестве итераций, что и в восходящем динамическом
прогр амм ировании. При этом рекурсивная прогр амма должна
(заЮlючительным действием) сохранять каждое вычисленное ей
значени е и (первым действием) проверять эти значе ния во избежание
пов торно го вычисления любого из них. Программа 5.11 - результат
механического преобразования прorраммы 5.10, в которой нисходящее
динамическое программирование позволило уменьшить время

выполнения до линейного.

На рис. 5.15 демонстрируется радикальное уменьшение количества


рекурсивных вызовов, дости гн угое этим простым автоматическим

и зменением . Иногда нисходящее динамическое программирование


называю т также мемоизацией (meпюizatiо п).

в качестве более сложно го прим ера рассмотрим задачу о ранце: вор ,


гр абящий сейф, находит в нем N видов предметов различных размеров
и ценности , но имеет только н ебольшой ранец емкостью М , в котором
может унести н а гра блен но е . Задача заЮlючается в том , чтобы
определить комбинацию предметов, которые вор долже н уложить в
ранец , чтобы общая стоимость похищенноro оказалась наи боль шей .

Программа 5.11. Числа Фибоначчи (динамическое программирование)

Сохра н ение вычисляемых знач ений в стат ическом массиве (элементы


которого в С++ инициализируются О) позволяет явно ИСЮlючить любые
пов торные вычисления. Эта программа вычисляет Fn за время ,

пропорциональное N, что существенно отличается от времени O(~),


которое требуется для вычислений программе 5. 10.

int F(int i)
{ static int krюwпF [maxN ];
if (knownF[i] != О) геtшп knownF[i] ;
intt =~
if (i < О) ге turп О ;
if (; > 1) t = F(i -1) + F(i - 2);
геtшп knownF[i] = t;

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Рис. 5.15. Применение ни сходящего д ин ам ич еского пр о грамми р ования


для вычисления чи сел Фибоначчи

И з этой схем ы рекурсивных вызовов, выполненных для вычисления F8


методом нисходящего динами ч еского программирования , видно, как

сохранение вычисленных значений снижает затраты с

экспо н ен циально го (см. ри с. 5.14) до линейного.

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

О 1 2 3 •
• ,
itM А В С D Е

~i%4!

.о'
, ,•
3 7
10 11 13

00000
Д в с D Е

ш D Е

ШД с с
d

dJ
шш
ддд в е

Рис . 5. 16. Прим ер задачи о ра нц е

В ход ными да нными задачи о ранце (вв ерху) являются емкость р а нц а и


набор п редметов р азл ичны х размеров (ко торые пр едставл е ны
з н ач ен иями на горизонтальной оси) и сто и мости (значения на
вертикальн ой оси) . На этом рисунке nока за ны ч етыре различных
способа за полн е ния р анца, размер которого равен 17; два и з эти х
способов дают максимальную суммарную стоимость, равную 24.

Наприм ер , пр и н аличии тип ов пр едметов, пр едстав ленны х на рис. 5.16,


'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

вор, располагающий ранцем, размер которого равен 17, может взять


только пять (но н е шесть) пр едметов А общей стоимостью 20, и ли
предметы D и Е суммар ной стоимостью 24, или одно из множества
дрyrn:х сочетаний. Наша ц ель - найти Э$lJeктивный алгоритм для
определения оп тимально го решения при любом заданном наборе
предметов и вместимости ранца.

Решения задач и о ранце важны во м ногих приложениях. Например ,


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

в рекурсивном решении задачи о ранце при каждом выборе предмета


мы пр едполагаем, что можем (рекурсивно) определ ить оптимальный
способ заполнения оставшегося места в ранце. Есл и объем ранца равен
сар , то для каждого доступ ного элемента i определяется общая
стоимость элементов, которые можно было бы унести , УЮIaдывая i -й
элемен т в ранец при оптимальной упаковке остальных элемен тов. Э та
оптимальная упаковка - просто упаковка, которая определена (или будет
определ ена) для меньшего ранца объемом сар -items[i].size. 3десь
используется следующий принцип: оптимальные принятые решения в
дальнейшем н е требуют пересмотра. Когда установлено, как оптимально
упаковать ранцы меньших размеров, эти задачи н е требуют повторного
исследования независимо от следующих элементов .

Программа 5.12 - пр ямое рекурсивное решение , которое основано на

приведенных рассуждениях. Эта программа также неприменима дл я


решения реальных задач, п оскольку из -за болы.lюоo объема повторных
вычислений (см . рис . 5. 17) время решения связано с количеством
элемен тов экспонен циально. Но для решения задачи можно
автоматически задействовать нисходящее ди на мическое
программирование - и получить программу 5.13. Как и ранее, эта
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

техника ИСЮlючает все повторные вычисления (см. рис. 5. 18).

Программа 5.12. 3адача о ранце (рекурсивная реализация)

Как и в случае рекурсивного вычисления чисел Фибоначчи, н е следует


использовать эту программу, поскольку для ее выполнения потребуется
экспоненциальное время и поэтому, возможно, не Удастся получить

решение даже небольшой задачи. Тем не менее, программа представляет


компактное решение, которое легко можно усовершенствовать (см.
программу 5.13). В ней предполагается , что элементы являются
структурами с размером и стоимостью, которые определены как

typ edef struct { iпt size; iпt val; } Item;

и имеется массив N элементов типа 1 tem . Для каждо го возможного


элемента вычисляется (рекурсивно) максимальная стоимость, которую
можно было бы получить, ВЮlючив этот элемент в выборку, а затем
выбирается максимальная и з всех стоимостей .

iпt knaр(iпt сар )


{ iпt ~ space, та х, t;
fo r (i = О , шах = О ; i < N; i++)
if «space := с а р -items[i]. size) >:= О )
if«t := knap(space) + items[i].val) > та х)
ша х:= t;

return ша х;
}

Программа 5.13. 3адача о ранце (динамическое программирование)

Эта механическая модификация прогр аммы 5.12 снижает время


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

выч исления : i temKnown [ М] находится в ранце , остальное

'"
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

содержимое сов п адает с оптимальной упаковкой р а нц а р азмера М


i te mKnown [М] . size, следовательно, в ранце н аходится

i te mKnown[M - i tе m s[М] . s iz e ] ит.д .

iпt knaр(iпt М)
{ int ~ space, тах, таю = О, t;
if (ma xК no wn[M ] != unknown) return тa xК no wn[M] ;
for (i = О , тах = О ; i < N; i++)
if «space = М -items[i] .s ize) >= О)
if«t = knap(space) + items[i].val) > тах)
{ тax= t;maxi = ~}
тaxК nown[M ] = тах; ite mКno wn[ M] = [ешs[maю];
return та х;

Рис. 5.17. Рекурсивная структура алгоритма решения задачи о ра нц е

ЭТО дерево пр едставляет структуру рекурсив н ых вызовов про стого


рекурсив н ого алгоритма решения задачи о ранце , реализованно го в

программе 5.1 2. Числ о в каждом узле означает оставшееся свобод н ое


место в р анце. Н едостатком алгоритма является то же
экспоненциальное время выполнения из - за бол ьшого объема
повторных вычислений, тре буемых для реше ни я п ерекрываю щи хся
подзадач, что и п ри вычислении чисел Фибоначчи (см. ри с. 5.14).

95 6 7 1 С1 11

2 З. 4 1 В

Рис. 5. 18. Применени е метода нис ходящего динамич еского


программирования для реализации алгоритма решения задачи о р анце
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Как и в случае вычисления чисел Фибоначчи, техника сохранения


известных значений уменьшает затраты алгоритма с экспоненциального
(см. рис. 5.1 7) до линейного.

Д инамическое программирование принципиально ИСЮJючает все


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

Лемма 5.3. Динамическое программирование снижает время

выполнения рекурсивной функции до не более чем суммарного

времени , необходимого на вычисление функции для всех аргументов ,

меньших или равных данному аргумен ту, при условии, что затраты на

рекурсивный вызов по стоянны.

См. упражнение 5.50 . •

Применительно к задаче о ранце из леммы следУет, что время


выполнения пропорционально произведению NM. Таким образом ,
задача о ранце легко подцается решению, когда емко сть ранца не очень

велика; для очень больших емкостей время и требуем ый объем памяти


MOryг оказаться н едо п устимо большими .

Восходящее динамическое программирование также применимо к задаче


о ранце. Вообще -то метод восходящего программирования можно
применять во всех случаях, когда применим метод нис ходящего

программирования , только при этом необходимо обесп е чить


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

увеличения аргумента (см. упражн е ни е 5.53), однако для более сложных


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

Например , совсем не обязательно ограничиваться рекурсивными


функциями только с одним целочисленным аргументом. При наличии
функции с несколькими целочисленными аргум е нтами решения

меньши х подзадач можно сохранять в многомерных массивах, по

од ном у измерению для каждого ар гумента. В д ругих ситуациях можно

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

обойтись вообще без целочисленных аргументов и использовать


абст р актную дискрет ную формулировку задачи, позволяющую разбить
задачу на менее сложные. Примеры таких задач рассмотрены в частях V
- УIII .

При использовании нисходящего ди намич еского программирования


известные значения сохраняются; при использовании восходящего

динам и ческо го программирования он и вычисляются заранее. В общем


случае нисходящее динам иче ское програ мм ир ование предпочтительней
восходящего , по скольку

• оно представляет собой механическую трансформац ию


естественно го решения задачи;

• порядок решения подзадач определяется сам собой;


• может не потребоваться решение всех подзадач.

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


различаются по сущности подзадач и объему сохраняемой для них
информации.

Однако необходимо учитывать следУЮЩИЙ важный моме н т:

динам и ческое пр о гра ммирование становится неэ$lJeктивным , когда


количество возможных значений функции, которые MOryr
потребоваться, столь велико, что мы не можем себе позволить их
сохранять (при нисходящем пр о граммировании) или вычислять
предварительно (при восходящем программировании). Например, если
в задаче о ранце объем ранца и размеры элементов - 64 -разрядные
величины или числа с плавающей точкой, значения уже невозможно
сохранять nyreM их индексирования в массиве. Это не пр осто
небольшое неУдобство, это принципиальная ТР Удн ость . Для подобны х
задач пока не известно ни одного при емлемо го решения; как будет
показано в части 8, имеются веские причины считать, что эфрективного
решения нет вообще.

Д инамическое прогр аммирование - это техника разработки алгоритмов ,


которая рассчитана в первую очередь на решение сложных задач того

вида, который БУдет рассмотрен в частях V - VIII. Болыlшствоo


ал горитмов , рассмотренных в частях 11 - IV, пр едставляют собой
реализацию методов "разделяй и властвуй" с н е перекрывающимися

ш
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

подзадачами , и основное внимание было уделено скорее


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

Ynражнения

5.37. Напишите функцию, которая вычисляет F N mod М , используя для

промежyrоч ных вычислений постоянный объем памяти.

5.38. Каково наибольшее значение N, для которого FN может быть

представле но в виде 64 · разря.цного ц ело го числа ?

5.39. Нарисуйте дерево, которое соответствует рис. 5.15 для случая ,


когда рекурс ивные вызовы в программ е 5.11 поменяны местами.

5.40. Напишите функцию , которая использует восходящее ди намич еское


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

Рп == LN/ 2 j + Р Ln/ 2 j + Р п 2 i ,ДЛЯ N > 1 , при РО = о.

Нарисуйте график зависимости PN - N 1 gN / 2 от N для О < N <


10 2 4 .

5.41. Напишите функцию, в которой восходящее ди намич еское


прогр амм ирование используется для решения упражнения 5.40.

5.42 . Нарисуйте дерево , которое соответствует рис. 5.15 для функции из


упражнения 5.41 при вызове с ар гум ентом N := 23.

5.43. Нарисуйте график завис имо сти от N количества рекурсивных


вызовов, выполняемых функцией и з упражнения 5.41 для вычисления
PN при О < N < 1 О 2 4. (При этом для каждого значения N
прогр амма должна за п ускаться заново.)

5.44. Напишите функцию, в которой восходящее ди намич еское


Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

п рограммирование используется для вычисления значения CN,


определяемого рекурре н тным соотношением

CN = N + N ? (K j + CN k) дл я N > 1, при СО 1.

N1 < K< N

5.45. Нап и шите функцию, в которой нисходящее ди н амическое


п рограммирование применяется для решения упражнения 5.44.

5.46. Нарисуйте дерево , которое соответствует рис. 5.15 для функции из


упражнения 5.45 при вызове с ар гумен том N = 2 3.

5.47. Нарисуйте график зависимости от N количества рекурс и вных


вызовов, вы п ол н яемых функцией из упражн е н ия 5.45 для вычисления
CN при О < N < 10 2 4 . (При этом для каждого значения N
п рограмма должна за п ускаться заново.)

5.48. Приведите содержимое масс и вов maxКnown и itemКnown,


вычисленное программой 5.13 для вызова knap(1 7) с элементами ,
п риведенными на рис. 5. 16.

5.49. Приведите дерево, соответствующее рис. 5.18, если элементы


рассматриваются в п орядке умен ьшения их размеров .

5.50. Докажи те лемму 5.3.

5.51 . Напишите функцию , решающую задачу о ранце , с помощью


вариан та п ро граммы 5. 12, в котором применяется восходящее
динам и ческое программирова н ие.

5.52 . На пи шите функцию , которая ре шает задачу о р анце методом


н исходящего ди н амического программирова н ия , н о используя п ри этом

рекурсив н ое решение , осн ован ное на вычислении оптимального

количества конкретно го элемен та , который должен бы ть помещен в


ранец , когда (рекурс и вно) известе н о п ти мальн ый способ упаковки
ран ц а без это го элеме н та.

5.53. Напиши те функцию , которая решает задачу о ра нц е с помощью


вариан та рекурси вно го решения , описанно го в упражнении 5.52, в

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

котором п рименяется восходящее динамическое програ мм ировани е.

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


ynражнения 5.4. На блюдай те за общим количеством сохран яемых
вы зово в функций.

5.55. Н а пиши те про грамму для вычисления бином и ального


коэфрициента 1 1 с п омо щью ни сходящего динамич еского

п рограммирования , ис ходя из рекуррентного соотношения

Деревья

Дерев ья· это математическая абстракц ия , играющая гл авн ую роль пр и


разработке и анализе алгоритмов, п оскол ьку

• дере вья и с пользуются для описания динамических свойств


алгоритмов;

• ч асто создаются и использую тся структуры да нн ых, которые

являются конкретны м и ре ал и за циями дере в ьев.

Мы уже встречались с при мер ами обоих примен е ний деревьев . В


' 'Введение '' были разработаны ал гори тмы для р еше ния задачи
связности , которые основан ы н а д ревови дн ы х структурах, а в ра зделах

5.2 и 5.3 структура вызовов рекурсив ны х ал горитмов была описана с


п омощью древов идных структур.

Мы ч асто встречаемся с деревьями в пов седневной жиз ни • это


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

за и мств ов ана именно и з это й области. Еще один при ме р - организация


спортивных тур ни ров; в частности , исследованием этого при менения

за ни мался Льюис Кэрролл. В качестве третьего примера можно


п ривести орга низ ацио нн ую диагр амму больuюй кор п орации ; это
п риме н е ни е напомина ет иерархическое разделение, ха р актер н ое дл я

ал горитмов ' 'р азделяй и властвуй ". Ч етв е ртым пример ом служит де рево
синтаксического разбора пр едложе ния английского (или любого другого
языка) на составляющие его части; такие дерев ья те с но связаны с

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

обработкой компьютерных языков, как описано в части У. Типичный


при мер дерева - в данном случае описывающего структуру глав этой
книП1 - показан на рис. 5. 19. Далее в книге нам встретится и множество

дрyrnх примеров применения деревьев.

Одно из наиболее известных прим енений древовидны х структур в


компьютерных приложениях - организация файловых систем. Файлы
хранятся в катало гах (иногда называемых также папками), которые
рекурсивно определяются как последовательности каталогов и файлов.
ЭТО рекурсивное определение снова отражает естественное
рекурсивное разбиение на составляющие и идентично определению
определенного типа дерева.

Существует множество р азлич ны х типов деревьев , и важно понимать


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

Рассмотрение начн ется с определения деревьев как абстрактных


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

• Деревья
• Деревья с корнем
• Упорядоченные деревья
• М -арные и бинар ны е деревья

После это го н еqюрмального р ассмотре ния мы перейдем к qюрмальным


определениям и рассмотрим различные представления и пр именения .

На рис. 5.20 показаны мно гие из этих базовых концепций , которые


будуг сначала рассмотрены, а затем и определены.

Дерево (tree) это н е п устая коллекция вершин и ребер ,


удовлетворяющих определенным требованиям. Вершина (уепех) - это
простой объект (называемый также узлом (node)), который может иметь
и мя и содержать другую связа нн ую с ним инqюрмацию; ребро (edge) -
это связ ь междУ двумя вершинами. Пугь (path) в дереве - это список
отдельных вершин, в котором последовательные вершины соединены

ребрами дерева . Определяющее свойство дерева - существование

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

только одного п уги, соединяющего любые два узла . Есл и между какой -
либо парой узлов существует более одного пуги, или если междУ какой -
либо парой узлов пугь отсугствует, то это граф, а не дерево .
Несвязанное м ножество деревьев называется лесом (forest).

Дерево с корнем (rooted) - это дерево , в котором один узел назначен


корнем (root) дерева . В компьютерных науках термин дерево обычно
при ме н яется к деревьям с корнем, а термин свободно е дерево (free tree)-
к более общим структурам, о пи са нны м в предыдущем абзаце. В дереве с
корнем любой узел является корнем подцерева , состоящего из него и
расположенных под ни м узлов.

Рис. 5.19. Дерево

Это дерево о пи сывает части, главы и соответствующие и м разделы


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

КО p.;lж"

ЛJ1Сl ~

ИЖlWflИЙ'(ЗQЛ

ДD'КIРI-IИЙ ---о­
y~'

Рис. 5.20. Типы деревьев


Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

На этих схемах приведе ны примеры бинарного дерева (вверху слева) ,


тернарно го дерева (вверху справа), дерева с корнем (внизу слева) и
свободного дерева (внизу справа).

Существует только один п угь между корнем и каждым из других узлов


дерева. Данное определение никак не определяет направление ребер; и
в завис и мости от конкретной ситуации можно считать, что все ребра
указывают или от корня , или к корню. Обычно деревья рисуются с
корнем вверху (хотя поначал у это со глашение кажется н еестеств е нны м)
и говорят, что узел у располагается под узлом х (а х располагается над у) ,
если х находится на пуги от у к корню (т.е. , у находится под х, как
нари сова но на ст р анице, и соединяется с х пугем, который не проходи т
через корень). Каждый узел (за исключением корня) имеет только один
узел над ним , который на з ыва ется его родительским узлом (parent); узлы ,
расположенные непосредственно под да нным узлом, называются его

дочерними узлам и (child). Иногда аналогия с генеало гич ес кими


деревьями распространяется дальше, и тогда говорят о " бабyuжaх "
(grand parent) или " сестрах " (sibling) данно го узла.

Узлы, не и меющие дочерних узлов , на з ывают ся листьями (leaf) и ли


терминальными (оконечными , tепninа l) узлам и. И , соответственно, узлы ,
имеющие хотя бы один д очерний узел , иногда называются
н етер миналь ными (rюпtеmllпаl) узлами. В этой главе мы уже встречались
с различным прим е н е ни ем этих ти пов узлов. В деревьях, которые
использовались дл я представления структуры вызовов рекурсивных

алгоритмов (например, на рис . 5. 14), н етерм инальные узлы (кружки)

представляю т вызовы функций с рекурсивными вызовами , а


терми нальные узлы (квадратики) пред ставляют вызовы функций без
рекурсивных вызовов.

в н екоторых прилож ениях способ упорядочения дочер ни х узлов


каждого узла имеет значение; в других это не важно. Упо р ядо ч е нн ое
(ordered) дерево - это дерево с корнем , в котором определе н порядок
следования до ч ер них узлов каждого узла. Упорядоченные деревья -
есте ствен ное представление: ведь при рисовании дерева дочерние узлы

размещаются в определенном порядке. Действ и тельно, многие другие


конкретные пред ставления имеют аналогично предполагаемый
пор ядок; например , об ычн о это различие имеет значение при работе с
компьютерными пр едставле ния ми деревьев.

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Если каждый узел должен иметь конкретное количество (М) до ч ерних


узлов, расположенных в конкретном пор ядке, мы имеем М -ар ное
дерево. В таком дереве часто можно определить специальные внешние
узлы, которые не имеют дочерних узлов. Тогда внешние узлы можно
задействовать в качестве фиктивных, чтобы на них ссылались узлы, не
им еющие должного количества дочерних узлов. В частности ,
простейшим ти пом М -арного дерева является бинарное дерево .
Бинарное дерево (Ьinагу tree) - это упорядоченное дерево, состоящее из
узлов двух типов : внешних узлов без дочерних узлов и внугренних
узлов, каждый из которых имеет ровно два дочерних узла. Поскольку
два дочерних узла каждого внугреннего узла упорядочены , можно

говорить о левом дочер н ем узле (left сlШd) и правом дочернем узле (right
сlШd) внугренних узлов. Каждый внугренний узел должен иметь и
левый, и правый дочерние узлы, хотя один И З ни х или оба мо гуг бы ть
внешними узлами. Лист в М -ар ном дереве - это внугренний узел, все
дочерние узлы которого являются внешними.

Все это общая терминолоrnя. Далее рассматриваются qюрмальные


определения, представления и приложения в порядке расширения

понятий:

• бинарные и М -ар ны е деревья


• упорядоченные деревья

• деревья с корнем

• свободные деревья

Начнем с наиболее специфической абстрактной структуры - как мы


увидим , это по зволит подробно рассмотреть конкретные
пр едставления .

Определение 5.1. Бинарное дерево - это либо внешний узел, либо


внугренний узел , связанный с (упорядоченной - прим. перев.) парой
бинар ны х деревьев, которые называются левым и правым
п аддеревьями этого узла .

Из этого определения понятно, что само бинарное дерево - абстрактное


математич еское понятие. При работе с компьютерным представлением
мы работаем лишь с одной конкретной реализацией этой абстракции .
Эта ситуация не отличается от пр едставления действительных чисел
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

значениями тип а float, ц ел ых ч и сел значе ниями т ипа int и т.д . Когда мы
ри суем дере во с узлом в корне, связа нным ребрами с левым
по.цдеревом, р асп оложен ным слева, и с правым подцеревом,

располож ен н ым справа, мы выбира ем удоб н ое конкретное


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

При реализации программ, и с поль зую щи х и обрабатывающих


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

разделе 3.3 'Эл ементарные структуры данных" :

struct node { Item item; rюdе *1, *г; }


typedef node *liпk;

Это просто код С ++ для определения 5.1. Узл ы состоят и з элеме нтов и
пар указа телей на узлы; указател и на узлы называются также ссылками .
Так, например , абстрактная операция п ереход к левому подцереву
реализуется с помощью обращения ч ерез указатель наподобие х = х -
>1 .

Это стандартное п редставление позволяет п остро и ть эqxpeкт ивную


реализацию о п ера ций , в которых нужн ы перемещения по дере ву вниз
от корня , но не пер еме ще ния по дереву вв ерх от до черн его узла к его

роди тельскому узлу Для ал гори тмов, где тр ебуются таки е о п ера ции ,
можно добави ть в каждый узел тр етью ссылку, направ лен н ую к его
роди тельскому узлу Эт а альтернатива анал оги чн а д вухсвязным с пи скам .
Как и в случае со связными с пи скам и (см. рис. 3.б), в некоторых
си туациях удобнее хра ни т ь узл ы де ре ва в массиве и использовать в
качестве ссылок ин декс ы , а не указател и. Конкр ет ны й п ример такой
реализации бу,цет рассмотрен в разделе 12.7 'Таблицы символов и
деревья бинарного пои ска". Для о п ределенных сп ециальны х
Р. СIЩЖ В ИК АлГОРIml< Ы н а С ++

алгоритмов используются и дрyrnе представления бинарных де р евьев -


мы будем обращаться к ним в основном в "Очереди с приоритетами и
пирамидальная сортировка ".

Рис. 5.2 1. Представле ние бинарного дерева

в ста н дар тном пр едстав ле нии бинарного дерева используются узлы с


двум я ссылками: левая указывает на левое подцерево , а правая - на

правое подцерево. Пустые ссылки соответствуют внешним узлам.

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


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

от реализации этих операций. В дан ной книге такой подход не


используется, п оскол ьку

• ч а ще всего БУдет использ оваться пр едставление с двумя ссылками;


• деревья будуг прим е няться для реализации АТД более высокого
уровня, и мы хоти м сосредоточить внимание на эт и х АТД;
• э$lJeктивность ал гори тмов может зависеть от конкретного
представ ле ния - это обстоятельство может быть упущено в АТД .

ЭТО те же причины , по которым мы используем знакомые конкретные


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

И зучение связных списков мы начали с рассмотрения элементарных


операций вставки и Удаления узлов (см . ри с . з. з и ри с . З.4). При
использовании стандартного пр едставления бинарных дерев ьев такие
операции совсем н е обязательно БУдУГ элемен тарны ми из -за наличия
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

второй ссылки. Если нужно Удали ть узел из би нарного дерева, то


возникает принципиальная проблема наличия двух дочерних узлов, с
которыми нужно что -то делать после удаления узла , и только одного

родительского. Существуют три естественных опера ции , для которы х


подобное осложнение не возникает: вставка нового узла в нижнюю
часть дерева (заме на п устой ссылки ссылкой на новый узел), Удаление
листа (заме на ссылки на н е го п устой ссылкой) и объединение двух
деревьев посредством создания нового корня, левая ссылка которого

указывает на одно дерево , а правая - на другое . Э ти операции


интенсивно используются при работе с бинарными деревьями.

Определение 5.2. М -ар но е дерево - это либо внешний узел, либо


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

Обычно узлы в М -ар ны х деревьях представляются либо в виде


структур с М именованными ссылками (как в бинарных деревьях), либо
в виде массивов М ссылок. Например , в главе 15 "Пора зряд ный поиск "
рассмотрены 3 -арные (или терн арны е) деревья, в которы х
используются структуры с тремя именованными ссылками (левой ,
средней и правой), каждая из которых имеет специальное значение для
связанных с этими деревьями алгоритмов. В остальных случаях вполне
годится хра н е ни е ссылок в массивах, поскольку значение М
фиксировано хотя , как мы уви д им, при использовании такого
представления нужно внимательно следить за объемом использованной
памяти.

Определение 5.3. Дерево (на зываемое также упорядоченным деревом) -


это узел (называемый корнем), связанный с по следовательностью
несвязанных деревьев. Такая п оследовательность называется лесом .

Различие между упорядоченными деревьями и М - арными де ревьями


состоит в том , что узлы в упорядоченных деревьях MOryr иметь любое
количество дочерних узлов, а узлы в М -арных деревьях должны иметь
точно М дочер ни х узлов. Иногда , если нужно различать упорядоченные
и М - арные деревья, используется тер мин обобщенное де рево (genera!
иее).

Поскольку каждый узел в упорядоченном дереве может иметь любое


количество ссылок, для хранения ссылок на дочерние узлы естественно

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

использовать связный спи сок, а не масс и в. При мер такого


представления п риведен н а рис . 5.22. Из это го примера видно, что
каждый узел содержи т две ссылки: одн у для связно го списка ,
соединяющего е го с сестринскими узлами, и в торую для связного

списка его дочерн и х узлов.

Рис. 5.22. Представление дерева

Представление упорядоченно го дерева с П ОМОJ.Цbю связного списка


дочерних узлов каждо го узла эквивалентно его представле н ию в виде

двоич н ого дерева. На схеме справа вверху показано представлен и е в


виде связного сп и ска дочер н их узлов для дерева , показа нн ого слева

вверху Э тот сп исок реализован в правых ссылках узлов, а левая ссылка


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

Лемма 5.4. С уществует взаимно однозначное соответствие между


бинар н ыми деревьями и упорядоченными лесами.

ЭТО соответствие п оказано на рис . 5.22. Любой лес можно представи ть


в в и де б ин арно го де рева , в котором левая ссылка каждо го узла
указывает н а его левый дочер н ий узел , а п равая ссылка каждого узла -
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

на сестринский узел, расположенный справа . •

Определение 5.4. Дерево с корнем (или неynорядоченное дерево) - это


узел (называемый корнем) , связанный с мультимножеством де ревьев с
корнем. (Такое мультимножество называется неynорядоченным лесом.)

Деревья, с которыми мы встречались в "Введ ени е", посвященной задаче


связности, являются неynорядоченными деревьями. Такие деревья
MOryг быть определены как упорядоченные деревья, в которых порядок
расположения до черних узлов узла неважен. Неynорядоченные деревья
можно определить и в виде множества отношений ''роди -тельский -
до черний " между узлами. Может показаться , что этот вариант имеет
слабое отношение к рассматриваемым рекурсивным структурам , но ,
возможно, такое конкретное представление наиболее соответствует
абстрактному понятию дерева .

Неynорядоченное де рево можно представить в компьютере

ynорядоченным деревом; нужно лишь осознавать , что одно и то же

неynорядоченное де рево может быть представлено несколькими


различными ynорядоченными деревьями. Хотя обратная задача -
определение того, представляют ли два различные ynорядоченные

де рева одно и то же неynорядоченное дерево (задача изоморфизма


де ревьев) - подается решению с трудом.

Наиболее общим видом деревьев является де рево , в котором не выделен


корневой узел. Например , этим свойством обладают остовные деревья ,
полученные в результате работы алгоритмов связности из "Введение ".
Для правильного определения неупорядоченных деревьев без корня, т.е.
свободных де ревьев , потребуется начать с определения граqюв (graph).
Определение 5.5. Граф - это множество узлов вместе с множеством
ребер, которые соединяют пары отдельных узлов (причем любая пара
узлов соединяется не более чем одним ребром).

Из любого узла можно перейти вдоль ребра до друго го узла, от этого - к


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

пугем (simple path). Граф является связным (connected), если для любой
пары узлов существует связывающий их простой пугь. Простой пугь, у
которого первый и последний узел совпадают, называется циклом
(cycle).
ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Каждое дерево является графом, а какие же графы являются деревьями?


Граф счи тается деревом, если он удовлетворяет любому из следующих
четырех условий:

• Граф имеет N - 1 ребер и ни одного ЦИЮJa.


• Граф имеет N - 1 ребер и является связным.
• Каждую пару вершин в графе соединяет только один простой
пугь.

• Граф является связным , но п ерестает быть таковым при удалении


любого ребра.

Любое из этих услов ий - необходимое и достаточное условие для


выполнения остальны х тр ех. Формально для определения свободного
дерева следует выбрать одно из них; НО мы, отбросив формальности ,
считаем определением все условия вместе .

Свободно е дерево представляется прос то как коллекция ребер. Если


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

Абстракция дерева используется часто, и рассмотренные в этом разделе


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

Прежде чем вернугься к ал гори тмам и реализациям, мы рассмотрим ряд


основных математических свойств деревьев; эти свойства будуг
использоваться при разработке и анализе алгоритмов на деревьях.

Упр ажнения

5.56. Приведите представления свободного дерева, показанного на рис.

'"
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

5.20, в ф:Jрме дерева с корнем и бинарного дерева.

5.57. Определите количество ра зл ичных способов представления


свободного дерева, показа нного на рис . 5.20, в ф:Jрме упорядоченного
дерева.

5.58. Нарисуйте три упорядоченных дерева, которые изоморфны


упорядоченному дереву, показанному на рис. 5.20. ЭТО значит, должна
существовать возможность пр еобразования всех четырех деревьев
одного в д ругое пугем обмена дочерних узлов.

5.59. Допустим , деревья содержат элементы, для которых определена


операция == . Н апи ши те рекурсивную пр о грамму, которая Удаляет в

бинарном дереве все листья , содержащие элементы, равные да нн ому


(см. программу 5.5).

5.60. И зме ни те функцию вида "разделяй и властвуй " для поиска


максимального элемента в массиве (программа 5.6), чтобы она делила
массив на к ч астей , р азмер которых различался бы не более чем на 1,
рекурсивно на~ила максимум в каждой ч асти и возвра~ла
наибольший из этих максимумов.

5.61 . Нарисуйте 3 -ар ные и 4 -ар ны е дерев ья для случаев к == 3 и к


4 в рекурсивной конструкции, предложенной в упражнении 5.60,
для массива, состояще го из 11 элеме н тов (см. рис. 5.6).

5.62 . Бинарные деревья эквивалентны дво и ч ным строкам , в которых


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

1110010110001011000

5.6З . Упорядоченные деревья эквивале нтны согласова нны м строкам из


пар кр углых скобок: упорядоченное дерево - это л и бо п устая строка ,
либо п оследовательность упорядоченных деревьев, заЮlюченных в
круглые скобки. Нарисуйте упорядоченное дерево, соответствующее

ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

строке

5.64. Напишите программу для определения того , пр едставляют ли два


массива N целых чисел от О до N - 1 изоморфные неупоря.цоченные
деревья, если интерпретировать их (как в ' 'Вв едение ' ) как ссылки из

родительских узлов на дочерние в дереве с узлам и , пронумерованными

от О до N - 1. То есть программа должна определять, существует ли


способ изменения нумерации узлов в одном дереве, чтобы
представление в виде массива одного дерева было идентичным
представлению в виде массива другого дерева.

5.65. Напишите программ у для определения того , являются ли два


бинар ных дерева изоморфными неупоря.цоченными деревьями.

5.66. Нарисуйте все упоря.цоченные деревья, которые могли бы


представлять дерево , определенное набором ребер О -1, 1 -2, 1
-3, 1 -4, 4 -5 .

5.67. Докаж ит е, что если в связном графе , состоящем из N узлов ,


удалени е любого ребра влечет за собой разъединение гр афа, то в н ем N
- 1 ребер и ни одного цикла.

Математические свойства бинарных деревьев

Прежде чем приступить к рассмотрению алгоритмов обработки


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

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


явных структур данных, но и рекурсивных алгоритмов ' 'р азделяй и
властвуй ", и других аналогичных прим е н е ниЙ .

Лемма 5.5. Бинарное дерево с N внугренними узлам и имеет N + 1


внешних узлов. Э та лемма доказывается методом индукции: бинарное
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

дерево без внугренних узлов имеет один внешний узел, следовательно ,


для N = О лемма справедлива. Для N > О любое бинарное дерево с N
внугренними узлами имеет к внугренних узлов в левом по.цдереве и N-
1 - к внугренних узлов в правом по.цдереве для н еюпоро го к в
диапазоне междУ О и N - 1, п оскольку корень является внугренним
узлом. В соответствии с индуктивным пр едположением левое
П QЦДерево и меет к: + 1 внешних узлов, а право е по.цдерево - N - к:

внешних узлов , что в сумме составляет N + 1. :

Лемма 5.6. Бинарное дерево с N внугренними узлами имеет 2N ссылок:


N - 1 ссылок на внугренние узлы и N + 1 ссылок на внешние узлы .

В любом дереве с корнем каждый узел , за ИСЮlюч ени ем корня , имеет


единственный родительский узел, и каждое ребро соединяет узел с его
родительским узлом; следовательно , 1 ссылок,
существует N
соединяющих внугренние узлы. Анало гично , каждый из N + 1 внешних
узлов им еет одну ссылку на свой единств ен ный родительский узел . •

Характеристики прои зводительности м ногих алгоритмов зависят не


только от количества узлов в связанных с ними деревьях, но и от

различных структурных свойств.

Определение 5.6. Уровень ОеУе!) узла в дереве - число, на единицу


большее уровня его родительского узла (корень размещается на уровне
О). Высота (height) дерева - максимальный из уровней узлов дерева .
Длина пуги (path !ength) дерева - сумма уровней всех узлов дерева .
Длина внугреннего пуги (intema! path !ength) би нарного дерева - сумма
уровней всех внугренних узлов дерева. Длина внешнего пуги (externa!
path !ength) бинарного дерева - сумма уровней всех внешних узлов
дерева.

Удобный способ вычисления длины пуги дерева заЮlючается в


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

Для этих величин существуют также простые рекурсивные


определения, вытекающие н е по средственно из рекурсивных

определений деревьев и б инарных деревьев. Например, высота дерева


на 1 больше макс и мальной высоты подцеревьев е го корня , а длина пуги
дерева с N узлами равна сумме длин пугей по.цдеревьев его корня плюс
ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

N - 1. П р ив еден ны е величины также непосредстве нн о связаны с


анализом рекурсивных алгор и тмов . Например , для мноrn.х рекурсивных
вычислений высота соответствующего дерева в точности равна
максимальной глубине рекурсии, то есть размеру стека, необходимого
для п одцержЮ1 выч и сле ния.

Лемма 5.7. Длина в н еlllilе го пуги любого бинар н ого дерева , имеющего
N в н уг р енних узлов, на 2N боль ше длины внугреннего п уги .

Эту лемму можно было бы доказать методом ИНдУКЦи и , но существует


другое , более нагл ядное , доказательство (которое п рименимо и для
доказательства леммы 5.6). Обратите в ни мание, что любое бинар н ое
дерево может быть создано пр и помощи следующе го пр оцесса .
Начинаем с бинар но го дерева, состоящего из одн ого внеlШlего узла .
Затем повторяем N раз следую щее: выбираем в н е lШlИ Й узел и заме н яем
его новым внугренним узлом с двумя дочер ни ми в н е lШlИМИ узлами .

Если выбранный внешний узел находится на уровне к, длина


внугреннего п уги увеличивается на к, но длина внеlШlего пуги

увели ч ивается на к + 2 (Удаляется один внешний узел н а уров н е к, но


добавляются два на уровне к + 1). Этот п роцесс начина ется с дерева ,
дли на внугреннего и внеlШlего п угей которого равны О , и на каждом и з
N шагов длина внешнего пуги увеличивается н а 2 больше, ч ем дл ин а
внугреннего пуги .•

Лемма 5.8. Высота би нарн о го дерева с N внугренними узлами не


меньше 19N и не боль ше N - 1.

Худший случай - вырожденное д ерево , имеющее только оди н лист и N-


1 ссылок ОТ корня ДО этого листа (см . рис. 5.23). В лучшем случае мы
и меем уравновешенное дерево с 2i внугренними узлами на каждом
уровне ~ за исключением самого нижнего (см. ри с. 5.23 ). Если его
высота равна h, то должно быть с п раведливо соотноше ни е

по скольку существует N + 1 внешних узлов. И з этого не р авенства и


следует данная лемма: в луч шем случае высота равна 19N, округленному
до бл и жай шего ц елого числа . •
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Лемма 5.9. Длина внугреннего пуги бинарного дерева с N внугренними


узлами не меньше чем N 19 (N / 4) и не превышает N (N - 1) / 2.

Худший и лучший случай соответствуют тем же деревьям, которые


упоминались при доказательств е леммы 5.8 и показаны на ри с . 5.23. В
>gIДшем случае длина внугреннего пуги дерева р авна О + 1 + 2 +
+ (N - 1) = N (N - 1) / 2 . В лучшем случае дерево имеет N

+ 1 внеuпшх узлов при высоте , не пр евы шающей LlgN J . Перемножив


эти значения и применив лемму 5.7, получим предельное значение

(N + l)Ll gN J - 2N < N/g(N /4) .•


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

когда бинарные деревья полностью (или почти) сбалансированы.


Например , деревья , которые использовались для описания алгоритмов
''разделяй и властвуй ", подобных бинарному пои ску и сортировке
слиянием, полностью сбалансированы (см. упражнение 5.74).

в ' 'Оч е реди с при о ритета м и и пирамидал ьн а я сортировка " и

"Сбала н с ир о ванные дерев ья " БУдУГ р ассмотр ены структуры данных,


основанные на уравновешенных деревьях .

Эти основные свойства деревьев предоставляют информацию ,


н еобходи мую для разработЮ1 эqxpeктив ны х алгоритмов решения мно ги х
пр актичесЮ1Х задач. Более подробный анализ нескольЮ1Х особых
алгоритмов, с которыми придется встретиться , требует сложных
математич еских выкладок, хотя часто полезные приближенные оценЮ1
можно получить с помощью простых индуктивных рассуждений ,
п одобных использованным в этом разделе. В последующих главах мы
продолжи м рассмотрение математических свойств деревьев по мере
возникновения необходимости. А пока можно вернугься к теме
алгоритмов.
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

~ -

"Ю~
'О _
ураООflь1 --

УРОИr:>IН. 4 --

Рис. 5.23. Три бинарных дерева с 10 внугренними узлами

Бинарное дерево, показанное вверху, имеет высоту 7, длину


внугреннего пуги 31 и длину bhe llD-lегО пуги 51. Полностью
сбалансированное бинарное дерево (в центре) с 10 внугренними
узлами имеет высоту 4, длину внугреннего п уги 19 и длину bhellD-lегО
пуги 39 (ни одно двои чное дерево с 10узлами не может и меть меньшее
знач ени е любого из этих парам етров). Вырожденное дерево (внизу) с 10
внугренними узлами имеет высоту 10, дли н у внугреннего п уги 45 и
длину bhellD-lегО пуги 65 (ни одно бинарное дерево с 10 узлам и не
может иметь большее з нач ение любого из этих параметров).

Ynражнения

5.68. Сколько внешних узлов существует в М -ар ном дереве с N


внугренними узлами? Используйте свой отв ет для определения объема
памяти, н еобходимого для представления такого дерева, если считать ,
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

что каждая ссылка и каждый элемент за ни мает од но слово па мяти .

5.69. Приведите верхнее и нижнее граничны е значен и я высоты М -


арного дерева с N в н утренними узлами.

5.70. Приведите верхнее и ни жнее граничны е значения длины


внутреннего пути М -ар нorо дерева с N внутренними узлами .

5.71 . Приведите верхнее и нижнее гр аничные значе ни я количества


листьев в бинарном дереве с N узлами.

5.72 . Покажите, что если уровни внешних узлов в бинар н ом дереве


различаются на константу, то высота дерева составляет O(logN).

5.73. Дерево Фибоначчи высотой n > 2 - это бинарное дерево с


деревом Фибоначчи высотой n 1 в одном подцереве и дерево
Фибоначчи высотой n - 2 - в другом . Дерево Фибоначчи высотой О -
это единственный внешний узел , а дерево Фибоначчи высотой 1 -
единственный внутренний узел с двумя в н ешними до черними узлами
(см. рис. 5.14). Выразите высоту и длину внешнего пути для дерева
Фибоначчи высотой nв в и де функции от N (количество узлов в дереве).

5.74. Дерево ви да ''разделяй и властвуй ", состоящее из N узлов - это


бинар н ое дерево с корнем, обозначенным N, деревом ' 'разделяй и

властвуй l /2 J
" из N узлов в одном п одцереве и де ревом ''раздел яй и

властвуй " из rN/21 узлов в другом . (Дерево ''разделяй и властвуй "


п оказано на ри с . 5.6.) Нарисуйте дерево ''разделяй и властвуй " с 11, 15,
16 и 23 узлами.

5.75. Докажи те методом ин дукции , что длина внутреннего пути дерева


вида "р азделяй и властвуй" находится в пределах между N 19N и N
19N + N.

5.76. Дерево вида "об ъединяй и властвуй ", состоящее из N узлов - это
бинар н ое дерево с корнем, обозначенным N, деревом "объединяй и

властвуй " из l N /2 J узлов в одн ом подцереве и деревом "объеди няй и

властвуй " из _М r /21 узлов в другом (см . упражн ен и е 5. 18). Нарисуйте


дерево "объединяй и властвуй " с 11, 15, 16 и 23 узлами.

'"
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

5.77. Докажите методом и н дукции, что длина BHyrpeHHero пyrи дерева


вида "объединяй и властвуй" находится в пределах между N 1 gN и N
19N + N.

5.78. Полное (complete) би н арное дерево - это дерево, в котором


заполнены все уров ни , кроме, возможно, последнего , который
заполняется слева направо, как показано н а рис . 5.24. Докажи те, что
дли н а BHyrpeHH ero п уги полного дерева с N узлами лежит в пределах
между N 19N и N 19N + N.

Р и с. 5.24. Полные би н арные деревья с семью и десятью внугрен ни ми


узлами

Если количество в н ешних узлов является сте п е н ью 2 (верхний рисунок) ,


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

слева от в н ешних узлов предпоследнего уровня.

Обход дерева

Прежде чем пристyn и ть к изучению ал горитмов, в которых создаются


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

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

существовать несколько сс ы лок для перехода.

Начнем рассмотрение с обхода бинар ны х деревьев.

в случае со связ ны ми списками имелись две основные возможности


(см. программу 5.5): обработать узел, а затем перейти по ссылке
(посещение узлов в прямом порядке) , или перейти по ссылке связи, а
затем обработать узел (В этом случае узлы п осещаются в обратном
порядке). Узлы бинарных деревьев содержат две сс ы лки и,
следовательно , возможны три основных порядка посещения узлов:

• Прямой обход (све р ху вниз), при котором посещается узел, а затем


левое и правое по.цдеревья

• Поперечный обход (слева направо) , при котором посещается левое


подцерево , пото м узел, а затем правое подцерево

• Обратный обход (снизу вверх), при котором посещаются левое и


пр авое подцеревья , а затем узел.

Эти методы можно легко реализовать с помощью рекурсивной


программы (программа 5.14), которая является непосредственным
обобщением программы 5.5 обхода связного списка. Для реализации
обходов в другом порядке достаточно соответствующим образом
переставить вызовы функций в программе 5.14. При м еры п осещения
узлов дерева при использовании каждо го из порядков обхода показаны
на рис. 5.26. На рис . 5.25 прив едена последовательность вызовов
функций, которые выполняются при вызове прогр аммы 5.14 для дерева
из рис. 5.26.
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

tr.а.vегзс Е
viэit ~
И.аУег.s:e D
Vi5it D
tra.verse В
visi't Б
trаvoerэ е А
vi.sit А
tr:a.verse 11:
tr:a.verse 11:
trа~э е С
vi..sit с
tr:a.verse 11;
tr:a.verse 11;
tra.verse ..
t.ravers:e Н
Vi9it Н
tra.verse F
visi't F
'trаvаэ е +:
'tra~g e G
visi't G
t raversll'J ..
t raversll'J 4;
tra.verэе "

Рис. 5.25. Вызовы функций при прямом обходе

Эта последовательность вызовов функций о пр еделяет пря мой обход для


при мера дерева , показанного на ри с . 5.26.

с этими базовыми рекурсивными процессами, на которых


основываются различные методы обхода дерева , мы уже встречались в
рекурсивных программах вида ''разделяй и властвуй " (см . рис . 5.8 и
5.11) и в арифметических выражениях. Например , выполнение прямого
обхода соответствует рисованию вначале метки на линейке, а затем
выполнению рекурсивных вызовов (см. ри с. 5. 11); выполнение
поперечного обхода соответствует переЮJaдыванию само го болы.lюоo
диска в решении задачи о ханойских баlllilЯХ между рекурсивными
вызовами , которые перемещают все остальные диски; выполнение

обратного обхода соответствует вычислению постфиксных выражений


и т. д .

'"
Р. СIЩЖВИК АлГОРIml<Ы на С ++

Программа 5.14. Рекурсивный обход дерева

Эта рекурс ивная функция п ринимает в качестве аргумента ссылку на


де рев о и вызывает функцию visit для каждого из узлов дерева. В
nриведенном виде функция ре ал изует прямой обход; если поместить
вызов visit междУ рекурсивными вызовами, получ и тся п оперечный
обход; а если п оместить обращение к visit после рекурсив н ых вызовов -
то обратный обход .

уою traverse(link h, уою visit(link))


{
if (Ь == О) return;
vi;it(h);
traverse(h - > ~ visit);
traverse(h - > г, visit);
}
Р. СIЩЖВИК АлГО РIml<Ы на С ++

;~"<.)d7f; )'~
?~
~
)(
~ ~r;: )'(~
'f< ~ 'f~щ, Cf{~
i"---..
о .
~
}i. G С ~

"'c/~ ~~
, ~ , С
.
Г

6~ ~ r.Y'"
t'fгrя

.
~ ~
~
, ,
-f
, о

~ ~~ f f'

Рис. 5.26. Порядки обхода дерева

Эти п оследовательност и nоказывают порядок посещения узлов дл я


nрямого (слева) , п оперечно го (в ц ентре) и обратно го (cnрава) обхода
де рева.

Эти соответствия позволяют гораздо лучше понять сугь механизмов ,


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

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

второе п еремеще н ие является п ереЮIaдыван и ем маленького диска.

Полезно также рассмотреть нерекурсив н ые реализации, в которых


используется явный стек Для п р остоты мы н ачнем с рассмотрения
абст р актно го стека, который может содержать как узлы , так и деревья, и
внач але содержит дерево , которое нужно обойти . Затем вы п ол н яется
циЮl , в кото р ом выталкивается и обрабатывается ве р хний элемен т
стека , и который пр одолжается, п ока стек не о п устеет. Если
вытолкнугое содержимое является элементом, мы посещаем е го; а если

это дерево, мы выполняем последовательность опе р аций в талкивания ,


кото р ая зависит от требуемого порядка:

• Пр и прямом обходе занос и тся правое подцерево , затем левое


померево , а затем узел.

• При поперечном обходе занос и тся п р авое ПОДДерево, затем узел, а


затем левое п омерево.

• При обрат н ом обходе заноси тся узел, затем правое померево, а


затем левое п омерево.

Пустые деревья в стек не за н осятся. На рис . 5.27 п оказано содержимое


стека при использовани и каждо го из этих трех методов для обхода
дерева, п риведенно го на рис. 5.26. НетрУдНО убедиться (методом
и н дукции), что для любо го бинарного дерева этот метод при водит к
такому же результату, как и рекурсивный метод.

в предыдущем абзаце о пи са н а концеп туальн ая схема, охватывающая


три метода обхода дерева, однако реализа ции , и с п ользуемые на
практике, несколько п роще.
Р. СIЩЖ ВИК АлГОРIml<Ы на С + +

i ~
, (]о ~@
11 D ~ ® " "о@, ~
liI

• D$D ~$ . CD(}~~
... ~ ... ~ () ~
, о r;;
, о;

," , B@Oto@
@о ~ @
@е O~E
, CIiDH
g о@о;
1r:

• , , С D IC
(]о ~®
~

• o®~
,@
, ® ,
о
" ~' ,
"0 ~~ ,, ", " ,
" ®,@ , F@H
@" , , " ," ,
G ,

, @ , ",
G "
,
G
G G
" ",
"
Рис. 5.27. Соде р жимое стека для алгоритмов обхода дерева

Эти последовательности от р ажают соде р жи мое стека при прямом


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

На п ример , при вы п ол н е ни и прямо го обхода узлы заносит ь в стек не


обязательно (поскольку в н ачале п осещается корень каждого

выталкиваемо го дерева). Поэтому можно воспользоваться п ростым


стеком , который содержит элементы только одно го типа (ссылки на
деревья), как это сделано в нерекурси вной реализации в программе
5. 15. С и стемный стек, поддерживающий работу рекурсив н ой
про граммы , содержит адреса возврата и значения аргумен тов, а не

элемен ты или узлы , но факти ческая п оследовательность выполнения


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

Четвертая естественная стратегия обхода - просто посещение узлов


де рева в порядке, в котором о н и нарисованы на стра ниц е - свер>о' вниз

и слева н апр аво . Э тот метод называется обходом по уровням (leve! -


order), поскольку все узлы каждого уровня посещаются вместе , по
порядку. Посещение узлов дерева , показанного н а ри с. 5.26, пр и обходе
по уров н ям п оказано на рис. 5.28.
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Рис. 5.28. Обход по уровням

Эта посл едовательность п оказывает результат пос ещени я узлов дерева в


порядке сверху вниз и слева на пр аво.

Ин терес н о, что обход по уров н ям мож но получить, заменив в


программе 5.15 ст е к н а очередь, что дем онстрирует пр о грамма 5. 16. Для
реализации пря мorо обхода используется структура данн ы х тип а

'"
Р. СIЩЖВИК АлГОРIml<Ы на С ++

''последним вошел , первым вышел " (LIFO); для реализации обхода по


уровням используется структура данных типа ' 'первым вошел , первым
вышел " (FIFO). Э ти программы заслуживают внимательного изучения ,
поскольку они представляют существенно различающиеся подходы к

организации оставшейся невыполненной работы. В частности , обход по


уровням не соответствует р е курсивной реализации , связанной с
р е кур с ивной структурой дер ева.

Программа 5. 15. Прямой обход (нер е курсивная реализация)

Эта нер е курсивная функция с использованием стека функционально


эквивал ентна е е рекурсивном у аналогу - программ е 5.14.

vою traverse(link h, vою visit(link))


( SТACK <link > s(тax);
s.push(h);
while (!s.empty())
(
visit(h = s. pop());
if(h - >r! = О) s. push(h ->r);
if(h - >I != О) s. push(h - >1);
)
)

Программа 5. 16. Обход по уровням

Замена структуры данных, лежащей в основе прямого обхода (см.


программу 5.15), со стека на очередь дает обход по уровням.

vою traverse(link h, vою visit(link))


( QUEUE<link> q(max);
q. put(h);
while (!q.empty())
(
visit(h = q.get());
if(h - >I != О) q. put(h ->1);
if(h - >, != О) q.put(h ->r);
)
)
Р. СIЩЖВИК АлГОРIml<Ы на С ++

Прямой обход, обратн ый обход и обход по уровням можно о п ределить и


для лесов. Чтобы о п ределения были единообразными , представьте себе
лес в виде дерева с воображаемым корнем. Тогда правило для прямого
обхода формулируется следующим об р азом: ''посетить корень, а затем
каждое из подцеревь ев "; а правило для обратн ого обхода - ''посети ть
каждое из подцеревь ев, а затем коре нь ". Правило для обхода по уров н ям
то же , что и для бинар н ых де р евьев. Непосредстве нны е реализа ции
этих методов - примитивные обобщения программ прямого обхода с
и спользован ием стека (программы 5.14 и 5.15) и программы обхода п о
уровням с использованием очереди (программа 5.16) дл я би н арных
деревьев , которые мы только что рассмотрели. Конкретные реализации
н е п риводятся, поскольку в р азделе 5.8 будет ра ссмотре н а более общая
п ро ц едУРа.

Ynражнения

5.79. Приведите порядок п осещения узлов для п рямого , поп е речного ,


обратного и обхода по уровням для следующих бинарных деревьев:

~'!
8
С
F
G

5.80. Приведите содержимое очереди во время обхода по уров н ям


(программа 5.16) дерева с ри с. 5.28 в стиле рис . 5.27.

5.81 . Покажите , что прямой обход леса экв ив алентен пр ямому обходу
соответствующего бинарно го дерева (см. лемму 5.4), а обратн ый обход
леса эквивален тен поперечному обходу бинар н ого дерева.

5.82 . Приведи те нерекурсив н ую реализацию п оперечно го обхода .

5.83. Приведи те нерекурсив н ую реализацию обратн о го обхода .

5.84. Напишите программ у; которая пр ин и мает н а входе прямой и


п оперечный обходы бинар н ого де рева и генерирует обход дерева по
уровням.
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Рекурсивные алгоритмы для бинарных деревьев

Алгоритмы обхода дерева, рассмотренные в разделе 5.6, наглядно


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

рекурсив н ых вызовов (или же использовать все три метода).

Часто требуется определить различ н ые структурные п араметры дерева ,


имея только ссылку на не го. Например , программа 5.17 содержит
рекурсивные функции для вычислен и я количества узлов и высоты
заданно го дерева. Эти функции написаны непосредстве нн о исходя из
определения 5.6. Ни одна из этих функций не зависит от порядка
обработки рекурсивных вызовов: о н и обрабатывают все узлы дерева и
возвращают одинаковый результат для любого п орядка рекурсивных
вызовов. Не все па р аметры дерева вычисляются так легко: например ,
п рограмма для эфрективного вычисле ни я длины в н угренне го пуги
бинарного дерева более сложна (см. упражнения 5.88 - 5.90).

Еще одн а функция, которая бывает нужна при создании программ ,


обрабатывающих деревья - функция, которая выводит структуру дерева
или вычерчивает его. Например , про грамма 5.18 п редставляет собой
рекурсив н ую п роцедуру, выводящую дерево в формате , п риведенном на
ри с. 5.29. Э ту же базовую рекурсивную схему можно ис п ользовать для
вычерчивания более сложных п редставлений деревьев , п одобных
п риведенным на рисунках в этой книге (см . упражнение 5.85).
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Н
• Е
D
• В
G А

• •
F •
• с
Е •
• •
D •
• н
С F

в
• •G
• •
А

• •
Рис. 5.29. Вывод дерева (п ри п оперечном и п рямом обходе)

Левая часть получе н а в результате работы п рограммы 5.18 с деревом ,


п риведенным на рис. 5.26. В ней п риведена структура дерева, подобная
графическому представле ни ю, которое используется в данной книге, но
п овернугая на 90 градУСОВ. Правая часть получена в результате
выполнения этой же программы, где оператор вывода перемещен в
н ачало программы; здесь п оказана структура дерева в привычном

схематическом формате.

Программа 5. 18 вы п ол н яет по п еречный обход , а если выводи ть элемен т


п еред рекурсив н ыми вызовами, получ и тся прямой обход; этот вариан т
также п риведен на рис. 5.29.

Программа 5.17. Вычислен ие па р аметров дерева

Для выяснения базовых структурных свойств дерева можно


и спользовать такие рекурс и вные процедУРЫ.

int count(link h)
{
if (h == О) геШт О;
геturп СО Шlt(h ->1) + СО Шlt(h -> г) + 1;
}
Р. СIЩЖВИК АлГОРIml<Ы на С ++

iпt hеight(1iпk h)
(
if(h == О) return -1;
iпt II = height(h - >1), v = height(h ->г) ;
if(u > у) геturп u+1; else геturпv+ 1 ;
}

Программа 5.18. Функция быстрого вывода дерева

Эта рекурсивная програ мма отслеживает высоту дерева и использует


эту инфор мацию для подсчета отступов при выводе представления ,
которое можно использовать для отла.цЮ1 прогр амм об р аботЮ1 деревьев
(см. рис . 5.29). Здесь пред полагается , что элементы в узлах имеют тип
Item, для которого определена п ерегруженная операция «~ о

уою ргintпоdе(Itеm х, in! h)


{faг (int i = О ; i < h; i++) cout « "";
cout « х « endt
}
уою show(link t, in! h)
{
if (! == О) { printnode(,*', h); гешт; }
show(t - > г, h+1);
ргintпоdе(t ->item, h);
show(t ->\ h+ l);
}

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


файлов в д ревовидной файловой структуре или при создании структуры
п е чатного докуме н та. Например , прямой обход дерева, прив еде нного на
р ис. 5. 19, выводит оглавление этой книги.

Первым при мером пр о граммы , которая строит древов идную структуру;


будет при ложение определе ния максимума , которое было рассмотрено в
разделе 5.2. Наша цель - по ст р ое ни е турнира , т.е . бинарного дерева, в
котором элеме нт ом каждого внугреннего узла является копия большего
из элементов его двух д очерних элементов. Тогда элемент в корне
является копией наибольшего элемента в турнире. Э лементы в листья

'"
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

(узлах без дочер ни х узлов) содержат исследуемые данные, а остальная


часть дерева - структура да нны х, которая позволяет эфрективно
находи ть наибольший из элементов.

Программа 5. 19 - рекурсивная прогр амма, которая строит турнир из


элементов массива. БУдУЧИ расширенной версией прогр аммы 5.6, она
использует стратегию ''разделяй и властвуй ": чтобы по строить тур нир
для единств енного элемента, программа создает лист, содержащий этот
элемент, и возвращает этот элемент. Чтобы п остроить тур нир для N >
1 элементов, программа делит все множество элементов пополам ,

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


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

На рис . 5.30 приведен прим ер древовидной структуры, построенной


прогр аммой 5. 19. Иногда по строение таких рекурсивных структур
бывает предпочтительнее отыскания максимума простым перебором
данных, как это было сделано в пр о грамме 5.6, поскольку древовидная
структура обеспечивает возможность выполнения других операций.
Важным прим ером служи т и сама операция , использованная для
по строения турнира. При наличии двух тур ниров их можно объединить
в один турнир , создав новый узел, левая ссылка которого указывает на
QЦин турнир , а правая на другой , и приняв больший из двух элементов
(в корнях двух дан ны х турниров) в качестве наибольшего элемента
объединенного турнира. Можно также рассмотреть алгоритмы
добавления и у,цале ния элементов и выполнения других операций.
3десь мы не станем рассматривать такие операции, по скольку
аналогичные структуры данных с подобными функциями будyr
рассмотрены в "Очеред и с приоритет ами и пира м и д ал ьн а я сортиров ка ".

Вообще -то реализации с использованием дерев ьев для нескольких из


АТД обобщенных очередей (рассмотренных в разделе 4.6 "Абстр а ктные
типы данны х' ) являются основной темой обсуждения в значи тельной
части этой книги. В частности , многие из алгоритмов, приведенных в
главах 12 - 15, основываются на деревьях бинарного поиска (blnary
search tree) это деревья , соответствующие б инарном у поиску,
аналогично то му, как структура на рис . 5.30 соответствует рекурсивному
алгоритму отыскания максимума (см. ри с. 5.6). Сложность реализации и
использования таких структур заЮlючается в обесп ечении их
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

эqxpeктивности после выполнения большого числа операций вставить ,


удалит ь и т.п.

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


и змененная версия программы вычисления префиксного выражения из
раздела 5.1 (программа 5.4), которая не пр осто вычисляет префиксное
выражение, а создает представляющее е го дерево (см. РDИс. 5.3 1). В
прогр амме 5.20 используется та же рекурсивная схема, что и в
прогр амме 5.4, но рекурсивная функция возвращает н е значение, а
ссылку на дерево .

Программа создает новый узел дерева для каждого символа в


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

на свои операнды , а листовые узлы содержат переменные (и ли


константы) , которые являются входны ми данными выраже ния .

Программа 5.19. Построение турнира

Да нна я рекурсивная функция дели т массив а [1] , . . . , а [ r ] на две


части a[l J , ... , а[т] и a[m+ 1 J , . , а [ r J , строит
(рекурсивно) турниры для этих двух частей и создает турнир для всего
массива, установив ссылки в новом узле на рекурсивно построенные

турниры и пом естив в н е го копию больше го элемен та из корней двух


рекурсивно по строенных турниров.

struct node
{ Item ке т; node * ~ * г;
node(Item х)
{ ке т = х; 1= О ; r = О ; }
};
typedef node* liпk ;
link max(Item а [ ], iпt 1, iпt г)
{ int m = (I+ г)/2;
link х = new node(a[m]);
if (1 == г) retum х;
х · >1= та х(а , 1, т) ;
х · >г = та х(а , т+ 1 , г);
Item u = х · > I-> itе щ v = х - > г -> item;
if ( и > у) х · > Ке т = и; else х · >item = у;
Р. СIЩЖ ВИК АлГО РIml<Ы на С ++

return х;
}

Рис . 5.30. Дерево для отыскания максимума (турнир)

На этом рисун ке пока зана структура дерева, созданная пр о гр аммой 5.19


из входных да нны х А М Р L Е. Элеме нты да нных находятся в
листьях. Каждый внугренний узел содержи т копию больше го из
элементов в д вух доче рни х узлах, та к что по ин дукц ии наи бо льший
элемент на ходится в корне.

в пр о граммах трансляции н а подоби е компиляторов часто испол ьзую тся


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

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


пр едставленного в ви де де р е в а с обратным обходом. А можн о
и спользовать де рево с поп е р е чным обходом для вывода выражения в
инфиксной qюрме и ли дерево с обратным обходом - для вывода
выраж ени я в п остфиксной qюр ме.

в этом разделе было рассмотрено н ес колько примеров ,


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

вари ан ты и р яд д руги х н юансов. Однако мы отложим более подробн ое


и зуче ние прorра мм об р аботки де р е вьев до ' Та блицы сим вол ов и
д е ревья б инарного поиска ", п оскольку в ле кциях 7 - 11 де ревья
испол ьзуются в ос н ов н ом в описательных ц ел ях . К явным реализациям
деревьев мы вернемся в ' Табл ицы си м волов и де рев ья б ин а рного
пои с ка ", поскольку они служат ос н овой для м н о гих алгори тмо в ,
ш
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

рассматриваемых в лекциях 12 - 15 .

Программа 5.20. Создан и е дерева синтаксического анализа

Используя ту же стратегию, которая была задействована для


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

char *а; int ~


struct node
{ Item кет; node * ~ * г;
node(Item х)
{ ке т == х ,· 1== О ,· r == О·, }
};
typ edef node* liпk ;
link parseO
{ crnr t :::: a[i++ ];
link х:::: new node(t);
if «! == '+') 11 (! == '*'))
{ х ->1:::: parseO; х - > г :::: parseO; }
return х;
}

+C\...----~' f

Рис. 5.31. Дерево синтакс ического анализа


Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Это дерево создано программой 5.20 для пр ефиксного выражения * +


а * * ь с + d е f . Оно представляет собой естественный способ
пр едставления выражения: каждый операнд размещается в листе
(который показан здесь в качестве внешнего узла), а каж.цая операция
должна выполняться с выражениями, которые пр едставлены левым и

пр авым паддеревьями узла, содержащего операцию.

Ynражнения

5.85. Измените программу 5.18, чтобы она выводила PostScript -


прогр амму, которая вычерчивает дерево в формате как на рис. 5.23, но
без квадратиков , пр едставляющих внешние узлы. Для вычерчивания
линий используйте функции moveto и lineto, а для отри совки узлов -
пользовательскую операц ию

/node {newpath moveto cur rentpoint 4 о 360 arc


f i l l } def

После инициализации этого определения вызов node при водит к


пом ещению черной точки с координатами, которые находятся в стеке
(см. "Абстрактные типы данных' ').

5.86. Напишите программу, которая подсчитывает листья в би нарно м


дереве.

5.87. Напишите программу, которая подсчитывает количество узлов в


бинар ном дереве с одним в н ешним и одни м внугренним дочер ни ми
узлами.

5.88. Напишите рекурсивную программу, котор ая вычисляет дл ину


внугреннего пуги бинар ного дерева, используя определ ение 5.6.

5.89. Определи те количество вызовов функций, выполненных


прогр аммой при вычислении дли ны внугреннего пуги бинарного
дерева. Докажите ответ методом ИНдУКЦии.

5.90. Напишите рекурсивную программу, котор ая вычисляет дл ину


внугреннего пуги б инарного дерева за время , пропорциональное
количеству узлов в дереве.
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

5.91. Напишит е рекурсивную программу, которая удаляет из турнира все


листья с заданным ютючом (см. упражнение 5.59).

Обход графа

в качестве заютючительного прим е р а в это й глав е рассмотрим одну из


наиболее важных рекурсивных пр огр амм: рекурсивный обход графа, и ли
поиск в глуб ин у (depth -first search). Э тот метод систематического
по се ще ния всех узлов графа представляет собой н е поср едств е нн ое
обобщение методов обхода дерев ьев , рассмотренных в разделе 5.6, и
служит основой для м ноги х базовых ал гори тмов обработки графов (см.
часть 7). Э ТО пр остой рекурсивный алгоритм . Н ач ин ая с любого узла У,
мы

• п осе щаем у;

• ( р екурсивно) пос е ща ем кажды й (не пос е ще нный) узел, связанный


с v.

Если граф является связным , со в рем е н ем будуг пос е ще ны все узлы .


Программа 5.2 1 является реализацией этой рекурсивной пр о ц едуры.

Программа 5.2 1. Поиск в гл убину

Для посещения в графе всех узлов , связанных с узлом к, мы по ме ча ем


его как посещенный , а затем (рекурсивно) п осещаем все не по се ще нные
узл ы в списке смежности узла к.

vою traverse(int k, УОЮ visit(int»


(visit(k); vis ited[k] = 1;
for (liпk t = adj[k]; t ,= О ; t = t - >пеxt)
if (!yjs ited[t - > у]) traverse(t ->v, visit);
}

Наприм ер , пр едположим , что используется предста вление в виде


списков связности, прив еден но е для графа на рис. 3. 15. На рис . 5.32
приведена последовательность вызовов , выполненных при п оиске в

глубину в этом графе , а последовательность прохождения ребер гр афа


Р. СIЩЖВИК АлГОРIml<Ы на С ++

п оказана в левой части рис. 5.33. При прохождени и каждого из ребер


гр афа возможны два вар и ан та: если ребро привадит к уже п осещенному
узлу, мы игнорируем его; если оно приводи т к еще н е посеще нн ому узлу,

мы проходи м п о н ему с помощью рекурсивно го вызова . Множество


всех пройде нн ых таким образом ребер образует остовное дерево графа .

7
® 4
б 5
3
ло~::це ,,::ие 1}

r:ю~:::re:J-a~ ~ "} ( n~p 5!:::1 3 CDlCК~ О)


ло~:::re:Ю1е ! ( n~p3 ~!I CГ:)!C ~ 1]
лрл"еРJl:iI. 7 в СПИС ке 1
ЛРО~ ~~Jl:а О 3 спис к~ 1
ЛО~::!.~Ю1е .2 ( ~'rО!;Ю;:З !l CГ'){C ~ 1]
ЛРОЕеРJl:iI. 7 в СПИСке 2
r:po~~pt:a О ~ cm<:c Y.~ 2:
!lpOверк" О в спис~ 7
посещение ~ ( ~ет:!ЗеP'l'EIIЙ в спис!te 1)
г.~е Ю1е б ( r.:~P :SIOl 3 сг.ис~~ ~ ]
r:po5e pIl:a 4. :5 С!IY.СЮ= б
г.РО5ер!\:а () :5 С!JY.СЮ= б
г.о::еil:еЮ1 е 50 ( з ::::-о~й 3 cr.;[C~ ~ )
пptJ f.е pIl:а () :5 с:v.сю= :-
про~ер:к ... -4 ~ C!IY.C кec :;

г. О~J-a1 ~ 3 ( ~Т'"АЙ 3 = 1 = 5 )
r:pc:<вepKa .5 :5 СЛИСЮ;: 3
~ epKa 4 ~ C~~ 3
r:poE epI<:iI. 7 з сПУ.ске 4
r:poE epI<:iI. 3 з CnY.CKe 4.
про~ер:ка .5 I'! C;y'CK~ D
npoвер!':д. .2 Е C1Y.CK€ D

i!pOвер!':д. 1 в СПZСК€ D
ПРJf.е РЖd Е в с;у.сКЕ D

Р и с. 5.32. Вызовы функции поиска в глубину

Эта п оследовательность вызовов функций реализует поиск в глубину


для графа , п риведенно го на рис. 3.15. Дерево, которое о п исывает
структуру рекурс и вных вызовов (вве р ху), н азывается деревом п оиска в
глуб ину

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Различие междУ п оиском в гл убину и об щим обходом дерева (см .


программу 5. 14) состо и т в том , что н еобходимо явно исключить
посещение уже посещенных узлов. В дере ве таЮ1е узл ы не встречаются .
Де йс тви тельно, есл и граф является де ревом , рекурсивный п оиск в
глубину, начи н ающийся с корня , эквивалентен прямому обходу.

Лемма 5.10. Время, необходимое для п оиска в глубину в графе с V


ве РlШlна м и и Е ребрами, пропорционально v + Е , если используется
представле ние гр афа в виде списков смежн ости .

В пр едставлении в в и де сп исков смежности каждому ребру гр афа


соответствует один узел в с пи ске, а каждой веРlШlне гр афа
соответствует один указатель на начало с пи ска. Пои ск в гл убину
использует каждый из н и х не более од ного раза . •

По скольку время, н еобходи мое для построе ни я п редставления в виде


списков смежности из п оследо вательности ребер (см. програ мму 3.19),
также пропорционально V + Е, поиск в глубину обес п е чивает
решение задачи связности из "Введ ение " с линейным времен ем

в ыполн е ния. Однако для очень болы шl x граqюв решения вида


объединение - поиск MOгyr оказаться п редп очтительнее, поскольку дл я
представле ния в сего графа н уже н объем п амяти , пр о порциональный Е,
а для решений объеди н е ни е -поис к - п ро п орциональн ый только v.
Как и в случае обхода дере в а, мож но определи ть метод обхода графа , пр и
котором и с п ользуется явный стек (см. рис. 5.34). Можн о пр едста вить
себе абстрактный стек, содержащий двойны е элемен ты : узел и указатель
на с пи сок смежности для этого узла . Если вначале стек содержи т
начальный узел, а указа тел ь указывает на п ер вый элемен т списка
смежности для этого узла, ал горитм поиска в гл убину экви вален тен
входУ в ци кл, в котором с начал а посещается узел и з верхушки сте ка ( если
он еще н е был посещен); по том сохраняется узел, указанн ый текущим
указа телем с пи ска смежности; затем сс ы лка из с пи ска смеж н ости

сдвигается на следУЮЩИЙ узел (с вытаЛЮ1ванием элеме н та, если


достигнут ко н ец списка смежн ости); и, након е ц , в стек за носи тся
элемен т для сохр аненно го узла со ссылкой на п ервый узел е го списка
смежности.

'"
Р. СIЩЖВИК АлГОРIml<Ы на С ++

Рис.5.З З. Поиск в глубину и поиск в ширину

При поиске в глубину (слева) п ереби р аются все узлы, с возвратом к


п редшествующему узлу, когда все связи данн ого узла проверены. При
п оиске в ширин у (справа) сначала п е ребираются все связи дан н ого узла ,
а затем выполняется переход к следУЮщему узлу.

Или же , как это было сделано для обхода дерева, можно создать стек,

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

содержащий только ссылЮ1 на узлы. Если вначале стек содержит


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

Алгоритм по се щения верхнего узла и зан есе ни я всех его соседей


простая qюрмулировка п оиска в глубину, НО из ри с. 5.34 понятно , что
этот метод может оставлять в стеке нескольЮ1 Х копий каждого узла. ЭТО
происходит даже в случае nроверЮ1 , nо се щался ли каждый узел ,
который должен быть п омещен в стек, и если да, то отказа от занесения
в стек тако го узла. Во избежание этой проблемы можн о воспользоваться
реализацией стека , которая устраняет дублирование с помощью правила
"забыть старый элемент ": по скол ьку ближайшая к верхушке стека копия
все гда посещается п е рвой , в се остальные копии пр осто вытаЛЮ1ваются
из стека.

Динамика состояния стека дл я поиска в глубину, nоказанная на ри с .


5.34, основана на том , что узлы каждого списка смежности появляются
в стеке в том же порядке , что и в списке. Для получения этого порядка
для да нного списка смежн ости при зан есе нии узлов по одному

необходимо втолкнугь в стек сначала по след ний узел , затем


предпоследний и т.д.

'"
Р. СIЩЖВИК АлГОРIml<Ы на С ++

о о 7 о 7 5 '

~~~~20 ®
7 7 1 2 0 4 5126
I 1 7 О 2 045 I 2 6
о о® 7 2 О 2 О 4 5 2 6
7 2 О 5 20 4 5 26
2 ' ф7@0@ 2 7 О О 4 5 2 6
, О 7 О о@ 00 4 5 26
7 О О 5 0 4 5 2 6
7 4 О 5 4 5 1 2 6
4 4 6 О 5 4 6 5 7 3 5 2 6
6 6 4 4 5 о@ б 4 О 5 7 3 5 2 6
6 О 4 5 о@ 05735 , 6
4 5 О 5 5 7 3 5 2 б
5 5 О 4 7 0<]) 5 о 4 3 73 5 2 Б
5 4 4 7 0<]) 4 3 7 3 5 2 6
5 3 4 7 O~ 3 7 3 5 2 б
3 3 .5 4 7 О 5 3 5 4 735126
3 4 4 7 О 5 4 7351 2 6
4 7 О 5 7 3 5 2 6
4 3 О 5 3 5 1 2 6
О 5 5 , 6
О 2 2 б
О 2 б
О 6 6

Рис. 5.34. Ди на мика стека при поиске в глубину

Стек, обес п ечивающий п оиск в глубину, можно пред ставить себе как
соде р жащий элементы , каждый из которых состоит и з узла и ссылки на
список смежности для этого узла (показан узлом в кр ужке) (слева) .
Таким образом , обработка начинается с находящегося в стеке узла О со
ссылкой на первый узел в его с пи ске смежности - узел 7. Каждая строка
отражает результат выталкивания из стека , занесения ссылки на

следующий узел в списке п осещенных узлов и за н есения в стек


элемен та для не посещенных узлов. Этот пр о ц есс можно также
пр едставить себе в виде про сто го занесе ния в стек всех узлов , смежных
с любым не п осещенным узлом (справа).

Более того , чтобы о гра ни чить размер стека числом вершин и все - таки
п осе щать узлы в том же порядке, как и пр и поиске в глубину,
н еобходи мо использовать стек с забыва ни ем старого элемента. Если
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

посещение узлов в том же порядке, что и при поиске в глубину, не


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

узел на верJ\YШКе стека, затем обрабатываем его список смежно сти ,


помещая в стек каждый узел (если он еще не был посещен); здесь
должна использоваться реализация стека, которая запрещает

дублирование по правилу ''игнорировать новый элемент ". Э тот


ал горитм обеспечивает посещение всех узлов графа аналогично поиску
в глуБИНУ; но не является рекурсивным.

Алгоритм , описанный в предыдущем абзаце , заслуживает внимания ,


поскольку вместо стека можно использовать любой АТД обобщенной
очереди и все же посетить каждый и з узлов графа (плюс сгенерировать
развернугое дерево). Например, если вместо стека задействовать
очередь, то получится поиск в ШИРИН У; который аналогичен обходу
де рева по уровням. Программа 5.22 - реализация этого метода (при
условии, что используется реализация очереди наподобие программы
4.12); пример этого алгоритма в действии показан на рис. 5.35. В части
6 БУдет рассмотрено множество алгоритмов обработки графов ,
основанных на более сложных АТД обобщенных очереде й.

и при поиске в ШИРИНУ; и при поиске в глубину посещаются все узлы


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

областях компьютерных наук, а н е только в пои ске в графах.

Программа 5.22. Поиск в ширину

Чтобы посетить в графе все узлы, связанные с узлом К, узел к


помещается в очередь FIFO, затем выполняется ЦИЮJ, в котором из
очереди выбирается следУЮЩИЙ узел и , если он еще не был посещен , он
посе щается , а в стек заталкиваются все не посе щенные узлы из его

списка смежности. Э тот пр оцесс продолжается до тех пор , пока очередь


Р. СIЩЖВИК АлГОРIml<Ы на С ++

н е опустеет.

vою traverse(int k, уою visit(int))


(
QUEUE<int> q(V*V);
q.put(k);
while ('q.empty())
if (vistted[k = q.get01 == О)
(
vistt(k); vistted[k] = 1;
for (liпk t = adj[k]; t != О; t := t - >next)
if(vistted[t ->v] == О) q.put(t ->v);
}
}

о о
о 7 5 2 6 О 7 5 2 6
7 5 2 1 6 2 4 7 5 2 6 4
5 2 6 2 4 4 3 5 2 6 4 3
2 1 б 1 2 4 4 3 2 1 б 4 3
I б t 2 4 4 3 I б 4 3
6 2 4 4 3 4 6 4 3
2 4 4 3 4 4 3
4 4 3 4 3
4 4 3 4 3
3 4 3
3 4 3
3

Рис.S.ЗS. Ди на мика очереди п ри поиске в шири н у

Обработка начинается с узла О в очереди, затем мы извлекаем узел О ,


п осещаем его и помещаем в очередь узлы 7 5 2 1 6 из его списка

смежности, причем именно в этом порядке. Затем мы извлекаем узел 7,


п осещаем е го и помещаем в очередь узлы из его списка смежности, и

т.д. В случае за п рета дублирования по правилу ''игнорировать новый


элемен т " (с п рава) мы по лучаем такой же результат без лишних
элемен тов в очереди .

'"
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

Рис. 5.36. Деревья обхода графов

На этой схеме показаны п оиск в глубину (в центре) и поиск в ширину


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

'"
Р. СIЩЖВИК АлГОРIml<Ы на С ++

А при поиске в ширину п осещаются все узлы , связан н ые с данным ,


п режде чем двигаться дальше; поэтому некоторые узлы связаны с

множеством д ругих.

Ynражнения

5.92 . Построив диа гр аммы, соответствующие рис. 5.33 (слева) и 5.34


(справа), п окажите , как пр оисходи т по се щение узлов при рекурсивном
п оиске в глубину в графе, п остроенном для последовательно ст и ребер О
-2 , 1
-4, 2 -5 , 3 -6, О -4 , 6 -о и 1 -3 (см .

упражнение 3.70).

5.93. Построив диаграммы, соответствующие рис. 5.33 (слева) и 5.34


(справа), покажите , как п ро и сходи т посещение узлов п ри пои ске в
ширину (с исп ользованием стека) в графе , построенном дл я
п оследовательности ребер О -2, 1 -4 , 2 -5 , 3 -6, О -4, 6
-о и 1 -3 (см. упражн е ни е 3.70).

5.94. Построив диаграммы, соответствующие рис. 5.33 (слева) и 5.35


(справа), покажите , как п ро и сходи т посещение узлов п ри пои ске в
ширину (с использованием очереди) в графе , построенном дл я
п оследовательности ребер О -2 , 1 -4 , 2 -5, 3 -6 , О -4 ,
6 -о и 1 -3 (см. упражнение 3.70).

5.95. Почему время вы п олнен и я, упоминаемое в лемме 5. 10,


п ро п ор ци ональн о v + Е , а не про сто Е?

5.96. Построив диа гр аммы, соответствующие рис. 5.33 (слева) и 5.35


(справа), покажите , как п ро и сходи т посеще ни е узлов п ри пои ске в
глубину (с использованием стека и п равила "забы ть старый элемент ') в
гр афе, пр иведенном на ри с. 3.15.

5.97. Построив диа гр аммы, соответствующие рис. 5.33 (слева) и 5.35


(справа), покажите , как п ро и сходи т посеще ни е узлов п ри пои ске в
глубину (с использованием стека и правила ''и гнорировать новый
элемен т ') в графе, прив еде нном на рис. 3.15.

5.98. Реализуйте п оиск в глуби н у с использованием стека для граqюв ,


которые п редставлены с пи сками смежности.
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

5.99. Реализуйте рекур с ивный поиск в глубину для графов , которые


представлены списками смежности.

Перспективы

Рекурсия лежит в основе ранни х теоретических иссл едова ний природы


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

разделе ния задач на подцающи еся р е шению на компьютере и на

непригодные для этого.

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


осветить столь обширные темы, как деревья и рекурсия. Многие самые
удачные примеры р екурс ивных программ БУдУГ постоянно встречаться
нам на протяжении всей книги - к ним относятся ал горитмы ''разделяй
и в л аствуй " и рекурсивные структуры данных, которые ус п е шн о
применяются для решения широкого спектра задач. Для многих
приложений нет смысла выходить за р амки пр остой непоср едств е нной
рекурсивной ре ал изации ; для д руги х БУдет рассмотрен вывод
нерекурсивных и восходящих реализаций.

в этой книге основное внимани е уделено практич еским аспектам


построения рекурсивных программ и структур данных. Н аша цель -
применение рекурсии для создани я изящных и эqxpeктивных
реализаций. Для достижения этой ц ел и необходимо особо учитывать
опасности использования прос тых прогр амм, которые ведyr к

экспоненциальному увел ич ен ию количества вызовов функций или


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

привлекательны, п оскол ьку часто они наводят на ИН дУКТивные

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


эфрективности разработанных програ мм.

в данной книге деревья используются как для упрощения пони мания


ди нами ческих свойств пр о гра мм, так и в качестве динам ич еских
структур данн ых. В главах 12 - 15 особенно большое внимание
уделяется работе с древовид ными структурами. Свойств а, описанные в
этой гл аве, предоставляют базовую инфор мацию , которая требуется для

'"
Р. СIЩЖ В ИК АлГОРIml< Ы н а С ++

эqxpeктивного применения древовидных структур.

Несмотря на центральную роль в разработке алгоритмов, рекурсия -


вовсе не панацея на все случаи жизни. Как было показано при
изучении алгоритмов обхода деревьев и графов, алгоритмы с
использованием стека (ю:порые рекурсивны по своей природе) - не
единственная возможность при необходимости управлять сразу
несколькими вычислительными задачами. Эqxpeкт ивная техника
разработки алгоритмов дл я решения многих задач заключается в
использовании реализаций обобщенных очередей, отличающиxrя от
стеков; такие очереди позволяют выбирать следующую за.цачу в
соответствии с каким -либо более субъективным критерием, нежели
простой выбор самой послед ней. Структуры данных и алгоритмы,
которые эqxpeктивно подцерживают такие операции - основная т ема
"Очереди с приоритетами и пирамидальная сортировка ", а со многими

при мерами их применения мы встретимся во время изучения

алгоритмов обработки графов в части 7.

Ссылки ДЛЯ части II

Существует множество учебников для начинающих, посвященных


структурам данных. Например , в книге Стендиша (Standish) темы
связных структур, абстракций данных, стеков и очередей ,
распределения памяти и создания программ освещаются более
подробно, чем здесь. Конечно , кrJaссические книги Кернигана и Ритчи
(Кегnighaп -Ritcrue) иСтрауструпа (Stroustrup) - бесценные источники
подробной информации по реализациям на С и С ++ . Книги Мейерса
(Meyers) также содержат полезную информацию о реализациях на С++.

Разработчики PostScript, вероятно , не могли даже и предполагать , что


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

Парадигма "ЮlИ е нт -интерфейс -реализация" подробно и с множеством


примеров описывается в книге Хэнсона (Наnsоп). Эта книга -
замечательный справочник для тех программистов, которые намерены
Р. СIЩЖ ВИК АлГОРIml<Ы на С ++

писать надежный и п е рен ос и м ый код для больших систем.

Книги Кн уга (Knuth), в особенности 1 -й и 3 -й том а , остаются


авторит ет ны м источ ником информации по свойствам э лементарных
структур данных. Книги Баеса - Яте са (Baeza -Yates) и Ганне (Gonnet)
содержат более свежую июlюрмацию, п одкр е п ле нную внyuштельным
библ иогр афи ч ес ки м п е речн ем. Книга Седжви ка и Флаж оле (Sedgewk:k
and Flajo let) подробно освещает математические свойства де р е вьев.

1. Adobe Systems lncorporated, PostScript Language Reference Manual, seco nd


edition, Addison -Wesley, Reading, МА , 1990.

2. R. Baeza -Yates and G. Н. Go nnet, Harкlbook of Algorithms and Data


S truсtшеs, second edition, Addison -Wesley, Reading, МА , 1984.

з.D. R. Н аnsоп, С lnterfaces and lmplementations: Techniques for C reating


ReusabIe Software, Addison -Wesley, 1997.

4. Брайан У Керниган , Деннис М . Ри т чи , Язык програм м ирования С


(Си), 2 -е издание , ид ' 'Вильяме '', 2008 г.

5. д.э . Кнуг, Искусств о програм ми рования , то м 1: Основные


алгоритмы , 3 -е издание, ид ''В и л ьям е'', 2008 г. ; д.э. Кнуг, Искусств о
прогр амм ирования , том 2: Получ исл е нны е алгоритмы , 3 -е и зда ни е, ИД
''Вильяме '', 2008 г. ; д.э. Кнуг, Искусство прorра мм ирования , том з .
Сортировка и поиск, 2 -е издани е, ИД ''В и л ьям е'', 2008 г.

6. S. Meyers, Effective С ++ , second edition, Addison -Wesley, Reading, МА,


1996.

7. S. Meyers, More Effective С ++ , Addison -Wesley, Reading, МА, 1996.

8. R. Sedgewk: k and Р Flajolet, Ап lntroduction to the Analysis of Algo rit:hms,


Addison -Wesley, Reading, МА , 1996.

9. Т. А. Standish, Data S truсtшеs , Algorit:hms, and Software Principles in С,


Addison -Wesley, 1995.

10. В. Stroustrup, The С ++ Рro grашшing Language, third editio n, Addison -


Wesley, Reading МА, 1997.
Р. СIЩЖ ВИК

Элементарные методы сортировки

Рассмотрены элемен тарные методы сортировки небольших файлов


либо файлов со с п ециальной структурой.

В качестве п ерво го экскурса в область алгоритмов сортировки мы


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

и зучения более сложных алгоритмов. Во-вторых, эти простые методы


во многих приложениях сортировки показали себя более
эqxpeктивными, чем мощные универсальн ые методы. В-третьих,
н екоторые из простых методов можн о расширить в более эффективные
универсальные методы или же п риме н ить для повышения

эqxpeктивности более сложных методов сортировки .

Цель настоящей главы - не только в оз н акомлении читателя с


элементарными методами сортировки, но и в создании среды ,

обле гчающей изучение сортировки в последующих главах. Мы


рассмотрим различные важн ые ситуации, которые MOryr возникнугь

п ри п рименении ал горитмов сортировки, различные виды входных

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


изучения их свойств .

Мы н ачнем с рассмотре н ия п ростой программы - драйвера для


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

алгоритмов для конкретных п риложений. Затем мы подробно


рассмотрим реализацию трех элементарных методов: сортировки

выбором , сортировЮ1 вставками и пузырьковой сортировЮ1 . После


этого будуг исследованы характеристиЮ1 производительности этих
алгоритмов. Далее мы рассмотрим сортировку Шелла , которой не
очень-то подходит эпитет " элементарная " , однако она достаточно
п росто реализуется и имеет много общего с сортировкой вставками .

ш
Р. СIЩЖ ВИК

После изучения математических свойств сортировки Шелла мы


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

Во многих применения х сортировки часто бывают удобнее простые


алгоритмы. Во-первых, очень часто программа сортировки

используется лишь один или небольшое количество раз. После "


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

искать более быстрые методы. Если число сортируемых элементов не


очень большое (скажем , не превыша ет н ескольких сотен элемен тов) ,
мож но воспользоваться пр остым методом и не морочиться с

ин терфейсом для системной сортировки или с реализацией и отладкой


сложного метода. Во-вторых, элементарные методы всегда Удобны для
файлов небольших размеров (скажем, из н ескольких десятюв элементов)
- сложным алгоритмам в общем случае присущи дополнительные
затраты , что делает их работу для маленьких файлов более медленной ,
чем элементарных методов. Эта про блема становится существенной ,
толью если возникает необходимость сортировки большого числа
маленьких файлов , однако при ложения с подобными требованиями
встречаются н е так уж редю. Сорти р овка выполняется легко и для
файлов , которые уже почти (или пол но стью) отсортированы и ли
содержат Болы.lюe число одинаювых ключей. Мы увидим , чт о
н еюторые про стые методы особенно эqxpeктивны при сортировке таких
весьма структурированных файлов.

Как правило, на сортировку случай но ynорядоченных N элементов


элементарные методы, рассматриваемые в данной главе , зат р ачивают

время, пропорциональное N 2. Если N н евелико, то время выполнения


сортировки может оказаться вполне приемлемым. Как только что было
отмечено, при сортировке файлов н еБОЛ ЫllИ Х размеров и в ряде других
специальных случаев эти методы часто р аботают быстрее более

'"
Р. СIЩЖ ВИК

сложных методов. Однако методы , описанные в нас тоящей гл аве, не


годятся для сортировки бол ьши х случайно упо р ядо ч енны х файлов ,
по скольку время и х сор тир овки БУдет н едо п усти мо боль ши м даже на
самых б ыс тр ы х комп ьютерах. Заметн ы м и сключ е нием является
сортировка Шелла (см . р аздел 6.6), которой при больших N тр ебуется
гор аздо меньше, ч ем N 2 шагов. По хоже, этот метод является одним и з
лучших для сортировки файлов средних р азмеров и для ряда других
специальных случаев.

Правила игры

Пр ежде чем перейти к изучению конкретных алгоритмов, полезно


рассмотреть общую терм инологи ю и основные положения алгоритмов
сортировки. Мы БУдем р ассматр ива ть методы сортировки файлов (Ше) ,
которые состоят из элеме н тов (item), обладаю щи х ключами (key). Э ти
понят ия являются естественными абстракциям и в сов рем е нны х средах
прогр амм иров а ния. Ключи , которые являются ли шь част ью (зачастую
очень не бол ьшо й частью) элементов , используются для управления
сортировкой. Ц ель метода сортировки заключается в пер емеще нии
элементов таким образом, что бы и х ключи были упо р ядо ч ены по
н екоторо му заданному критерию (обы чно это чи словой или
алфавитный п орядок). Конкретные ха р актер ис тики Юlюч е й и элемен тов
в разных при ложе ниях MOryг существенно отличаться друг от друга ,

од нако абстра ктно е понятие размещения ключей и связанной с ни ми


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

Есл и сортируемый фай л пол н остью по мещается в оперативной па мяти ,


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

по следователь ны й перебор элемен тов или , по крайней мере, больши ми


блоками. Н екоторые методы вн е шн ей сортировки рассматриваются в
"С пециальн ые методы сортировки", однако большая часть
рассматриваемых ал гори т мов относится к внугренней сортировке.
Р. СIЩЖ В ИК

МЫ будем рассматривать и масс ивы , и связные списки, посколь ку при


разработке ал гори тмов для некоторых базовых задач будет удобнее
последовательное размещение элементов, а для других задач - связные

структуры. Некоторые из класси ч еских методов настолько абстрактны ,


чт о их можно э$lJeктивно реализовать с помощью как массивов, так и
связных списков; но есть и такие, для которых гора здо удобнее один из
методов. Иногда MOгyr появи ться и другие виды ограничения доступа.

Начнем мы с сортировки массивов. Программа 6.1 демо нс трирует


многие соглаше ния , которым мы бу,цем следовать в наlШfХ реализациях.
По суги это программа-драйвер, т. е. управляющая прorрамма. Она
заполняет массив, считывая целые числа из стандартного ввода либо
генерируя случайные целые знач ения (режим задается ц елочисленным
ар гум е нтом) , затем вызывает функцию сорт ировки , чтобы упорядочить
элемен ты масс ива , и в заве ршен и е выводит результат сортировки.

Как было описано в ' Элементарные структуры данных " и "Абстрактные


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

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


рассмотрены в р азделе 6.7. Функция sort из программы 6.1 представляет
собой шаблонную реализацию , которая обращается к сортируемым
элемен там только ч ерез первый аргуме н т и нескольких простых
операций с да нными. Как об ычно , такой подход по зволяет исп ользовать
один и тот же програм мный код для сортировки элементов разных
типов. Например, есл и код функции таin в программ е 6.1 изменить так,
чт об ы генерация, хранение и вывод случай ны х ключей выполнялись не
для целых чисел, а для чисел с плавающей точкой, то функцию sort
можно оставить без каких-либо и зменений . Для дости жения такой
гибкости (и в то же время явной идентификации п ереме нны х для
хранения сортируемых элеме н тов) наши реализации долж ны быть
параметризованы для работы с типом данных Item. Пока тип данных
Item можно считать тип ом int или t1oat, а в разделе 6.7 БУдУГ подроб н о
рассмотрены реализации тип ов данных, которые позволят исп ользовать

наши реализации сортировки для произвольных элементов с ключами в

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


описанные в 'Э лементарны е структуры д анны х" и "Аб страктны е типы
д анны х".
Р. СIЩЖ ВИК

Функцию 50rt можно замени ть любой реализацией сортир овки масс ив а


из данно й главы или глав 7- 10. В каждой и з них выполняется
сортир овка элементов ти п а Item, и каждая использует три аргумента :
масс ив и левую и п равую гр ан ицы подмассива , подлежащего

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

Эти соглашения позволят нам изучить естестве нны е и компактные


реализации мноrnх алгоритмов сортир овки массивов. В разделах 6.7 и
6.8 рассматривается драйвер , на пр имере которого БУдет п оказано
при ме н е ни е реализа ци й сортир овок в более общих ко нт екстах, а также
различные реализации ти п ов данных. Мы всегда будем об р ащать
внимание на кон кретные детал и , однако основные ус или я будyr
напр авле ны на ал горитм и ческие вопросы , к р ассмотре нию которых мы

сей ча с и переходим.

Функция сортировки в пр о грамме 6.1 является одним из вариан тов


сортировки вставкам и , которая БУдет подробно рассмотрена в разделе
6. 3. Так как в н ей и с пользуются только операции с р ав н е ни я и обмена ,
она является пр имером неадаптивной (nonadaptive) сортировки:
по след овательность выполняемых о п ера ций не зав иси т от
упорядо ч е нн ости данных . И наоборот, ада птивн ая (adaptive) сортировка
выполняет различные п оследовательн ост и опера ци й в зависимости от
результатов сравнения (вызовов операци и <). Неадаптивные методы
сортир овки интересны тем , что они достаточ но пр осто реализуются

аппаратными с р едствам и (см . "С п е uиальны е м етод ы с орт ир ов ки ' ) ,


од н ако больши н ство универсальных ал горитмов сортировки , которые
мы рассмотрим , являются адап тивными.

Программа 6.1. Пример сортировки массива с п омощью пр о граммы­


д р айве р а

Да нна я пр о гр амма служит иллюстрацией наших со глашен ий ,


Р. СIЩЖВИК

касающиXLЯ реализации базовых алгоритмов сортировки массивов.


Функция main - драйвер, который инициализирует массив целыми
з н ач ениями (случайными либо из стандартного ввода), вызывает
функцию sort для сортировки заполненно го массива , п осле чего
выводит упорядоченный результат.

Ша блоны по зволяют использовать эту реализацию для сортировки


элементов любого типа данных, для которого определены операции
сравнения и при сваивания . В да нном случае функция sort представляет
собой вариант сортировки вставками (см . раздел 6.3, в котором
приводи тся подробное описание, прим ер и улучше нный вариант
реализации). Она исп ользует ша блон н ую функцию , которая с р авнивает
два элемента и при необходимости производит обмен их местами ,
чтобы второй элемен т был не меньше первого.

Мы можем изм енять программу-драйвер для сортировки любых типов


данных, для которых определена операция <, совершенно не меняя

функцию sort (см. раздел 6.7).

#include <iostream.h>
#include <stdlib .h>
template <class Item>
уоИl exch(Item &А, Item &В)

( "еm' = А; А = В; В = '; )
template <class Item>
уоИl compexch(Item &А, Item &В)
( ~ (В < А) exch(A, В); }
template <class Item>
уоИl sort(ltem а[] , int 1, int г)
{ for (int i = l+1; i <= г; i++ )
for (in' j = \ j > \ j-- )
compexch(aO-l], аО1);
}
int main(int argc, char *argv[])
( in, \ N = ato~argv[l]), sw = ato~argv[2]);
int *а = пеw int[N];
~(sw)
for(i= О; i < N; i++)
a[i] = IOOO*(I.O*rand()IRAND_MAX);
Р. СIЩЖВИК

e~e
{N = О; while (с iп » a[N]) N ++ ; }
50 rt(a, О, N- l);
for (i = О; i < N; i++ ) cout « а[П«" ";
cout« endl;
}

Как обычно , и з все х характеристик прои зв оди тельности алгоритмов


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

Как будет показано в ра зделе 6.5 , для выпол нения сортировки N


элементов методом выбора , методом вставок и пузырьковым методом ,
которые БУдУГ р ассматр иваться в разд елах
6.2- 6.4, требуется вр емя,
2
пропорциональное N . Более совершенные методы , о которых речь
пойдет в главах 7- 10, MOryr упорядочить N элементов за вр емя,
пропорциональное N logN, однако эти методы н е всегда столь же
эqxpeктивны , как рассматриваемые здесь методы, для н еболь ших
знач ений N, а такж е в н екоторых особых случаях. В разд еле 6.6 будет
рассмотрен более со верше нный метод (сортировка Шелла), который

может потр ебовать время, пропорциональное N Зl2 или даже меньше, а в


разделе 6.10 при водится с пециализ ированный метод (распределяющая
сортировка), которая дл я некоторых типов Юlючей выполняет ся за
время , пропорциональное N.

Аналитические результаты , изложенные в пр едьщущем абза це ,


получены на основе подсчета базовых операций (сравнений и
обменов) , которые выполняет ал гори тм. Как было сказано в разделе 2.2
''П ринципы анализа алгоритмов", следует также учитывать за тр аты на

выполнение этих операций. Однако в общем случае мы считаем , чт о


основное внимание следует Удел ить наиболее часто используем ым
операциям (внугренний ЦИ Юl ал горитма). Наша цел ь заЮlючается в том,
чтоб ы разработать эфрективные и н есложны е реализации эффективных
ал горитмов. Поэто му мы БУдем не только изб егать ИЗЛИI.ШIИ Х В Юlюче ний
во внугренние ЦИ ЮlЫ алгоритмов , но и ПО возможности пы таться

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


снижения затрат в прилож е нии - это пер еключе ни е на более
эqxpeктивный алгоритм , а второй лучший способ - поджатие
внугреннего ЦИЮlа. Для алгоритмов сортировки мы будем н еод н окра тно
Р. СIЩЖ ВИК

испол ьз овать оба эти с посо ба.

Вторым по важн ости фактором , который мы БУдем рассматривать ,


явл яется объем до полнител ьной п амяти , и с по льзуемо й алгоритмом
сортировки. По это му критерию в се методы мож но р азб ить на три
категори и : т е, которые вы п ол н яют сортировку на месте и не тр ебуют
допол ни тель ной па м яти , за ИСЮlючением , во зможно, небольuюго стека
или таблиц ы ; те, кото ры е используют пр едстав лени е в ви де связного
списка или каким- то другим способом обращаются к данным с
п омощью N указателе й или ин дексо в массивов, для которых н ужна
допол ни тель ная память ; и те, которые требуют дополн и тельной па м яти
для размещения еще одной коп ии сортируемого масс ива.

Ча ст о п рименяются методы сорти ровки элементов с несколькими


Юlючами - иногда даж е требуется упорядочение одно го и то го же
на бора элементов в разные моме н ты по разным Юl юч ам. В таких
случаях очень важно з нат ь , обладает л и вы б р анны й метод сортировки
следующим свойств ом:

Определе ни е 6.1. Гово рят, что метод сортировки устой чив , если он
сохр ан я ет относи тел ьный порядок размещения в файле элеме н тов с
оди на ко выми Юlюч ам и.

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


и году выпуска, ТО усто й ч ивый метод сортировки выдаст список
учеников, р аспределе нн ый по Юlассам , в том же алфавит н ом порядке, а
н еустойчивый метод , скорее в се го, выдаст с пи сок без следов
п ер вон ачальной упорядоченности . Когда люди, не знаком ы е с поняти ем
устойчив ости , впервые сталкиваю тся с подоб ного рода си туа ци ей , они
часто Удивляютс я , до какой сте п е ни н еустой чивый алгоритм может
п ереме ша ть данные.

Н екоторые (но отнюдь не все) п ростые методы сортировки , которые


рассматриваются в данной гл ав е , яв л яются устой ч ивы м и. А мно гие
сложны е алгор итмы со ртировки (тоже не все) , которые будyr
рассмотрены в н есколь ки х п оследУЮЩИХ глав ах, н еустоЙч ивы . Если
устойчив ость в ажна , ее можно обесп еч и ть , доб авив пер ед сортир о вкой
к каждому Юlючу н ебол ьшой ин декс и л и как- то по -другому расширив
ЮlЮЧ сортировки . Вы п ол н е ни е этой до п ол ни тель ной р аботы
равносильно использова нию при сортировке обоих Юlючей (см. I!!:!.!;

'"'
Р. СIЩЖВИК

б.....l) , поэтому л уч ше задействоват ь устойчивый ал гори тм. Однако очень


н емногие сложные алгоритмы , которые БУдУГ рассматриваться в
п оследУЮЩИХ главах, обес п е чивают устойчивость без существенных
допол н ительны х затрат памяти или времени .

Матэ
Black 2
В гowп 4
Jackson 2
Jones 4
Smith
Тhompson 4
Wаshi пфо п 2
White 3
Wllson 3

Adarrn;
Smith
WаShi ПQtо п 2
Jackson 2
Вlaск 2
White 3
Wllson 3
Тhompson 4
В гowп 4
Jones 4

Adarrn;
Smith
Вlaск 2
Jackson 2
Washington 2
White 3
Wllson 3
В г<7IЧ П 4
Jones 4
Тhотрsoп 4

Р и с. 6.1 . При мер устойчивой сортировки

Сорти ровку представленны х здесь за пи сей можно вы п ол н ить ПО


любому из д вух ключей . Пред положи м , что в н ачале за пи си были

'"
Р. СIЩЖ ВИК

отсортированы по первому Юlючу (вверху). Неустойчивая сортировка по


второму Юlючу не сохраняет этот порядок для записей с
повторяющимися Юlючами (в центре), а устойчивая сортировка
сохраняет этот порядок (внизу).

Как уже было сказано, программы сортировки обычно осуществляют


доступ к элементам одним из двух способов: либо доступ к Юlючам для
их сравнения, либо доступ полностью к элементам для их перемещения.
Если сортируемые элементы имеют большой размер , лучше не
перемещать и х в памяти , а выполнять косвенную (indirect) сортировку:
переупорядочиваются не сами элементы, а массив указателей (или
индексов) так, что первый указатель указывает на наименьший элемент,
следующий - на наименьший из оставшихся и т. д. Ключи можно
хранить либо вместе с самими элементами (если ЮlЮЧИ большие) , либо
с указателями (если ЮlЮЧИ малы). После сортировки можно
переупорядочить и сами элементы , но часто в этом нет необходимости ,
т.к. имеется возможность (косвенного) обращения к ним в
отсортированном порядке. Косвенные методы сортировки

рассматриваются в разделе 6.8.

Упражнения

6.1. Детская игрушка состоит из i карт, отверстие в которых подходит к


колышку в i-ой позиции, причем i принимает значения от 1 до 5.
разработайте метод для помещения карт на колышки , считая, что по
виду карты невозможно сказать , подходит ли она к тому или иному

колышку (обязательно нужно попробовать).

6.2. Для выполнения карточного трюка нужно , чтобы колода карт была
упорядочена по мастям (пики, потом червы , трефы и бубны), а внугри
каждой масти - по старшинству. Попро си те нескольких своих друзей
выполнить эту задачу (перед каждой попыткой перета суй те карты!) и
запишите методы, которыми они пользовались.

6.3. Объясните, как вы БУдете сортировать колодУ карт при условии , ЧТО
карты необходимо уюыДывать в ряд лицевой стороной вниз, и
допускается только проверка значений двух карт и (при необходимости)
обмен этих карт местами.

6.4. Объясните, как вы БУдете сортировать колодУ карт при условии , что

'"
Р. СIЩЖ ВИК

карты должны находиться в колоде, и допускаются лишь проверка

знач ений двух верхних ка рт в колоде, обмен эт и х карт местами и


п ереме ще ни е верхней ка рты вниз.

6.5. Приведите все по следоват ельнос ти и з трех операций сравнения­


обмена для упорядочения трех элементов.

о 6.6. Приведите последовательность и з пяти операций сравне ния ­


обмена, которая упорядочивает четыре элемен та.

6.7. Напиши те клиен тскую программ у, которая пров еряет устойчивость


используемой подпрогр аммы сортировки.

6.8. Про верка упорядоченности массива после выполнения функции


sort н е доказывает, что сортировка работает. Почему?

6.9. Напишите Юlиентскую прогр амму-драйвер ДJlЯ заме р а

прои зводительности сорт ировки , которая многократно вызывает

функцию sort для файлов различных размеров, замеряет время каждого


выполнения и выводит (в виде текста или графика) среднее время
выполнения.

6.10. Напишите учебную Юlие нтскую программу-драйвер, которая


вызывает функцию 50rt для сложных или па тологических случаев ,
которые MOryг встретиться в реальных ситуациях. Примерами MOryг
служить уже упорядоченные файлы, файлы, пр едставле нные в
обратном порядке, файлы, все записи которых имеют одни и те же
ЮJЮЧИ, файлы, содержащие только два отличных друг от друга значения ,
файлы размерами О или 1.

Сортировка выбором

Один из самых простых алгоритмов сортировки работает следУЮ ЩИМ


образом. Сначала находи тся наименьший элемент массива и меняется
местами с элементом, стоящим п ервым в сортируемом массиве. Потом
находи тся второй наименьший элемент и меняется местами с
элементом, стоящим вторым в исходном масс иве . Э тот процесс
продолжается до те х пор , пока весь массив н е БУдет отсортирован.
Да нный метод на зывается сортировкой выбором (selection sort),
'"
Р. СIЩЖ ВИК

поскольку он все время выбирает наименьший элеме н т и з числ а не


отсортированных. Н а рис 6.2 представлен пример работы этого метода.

(Ns
, о R Т N аЕ.Х"'М р L Е

,, S о R Т N G Е. Х @М
®Х 5 М
Р L Е

,, ,, о
Е
R
R
Т
Т
N G
N G О Х S
Р
Р
L

Е

,, ,, Е Е Т I Н @О Х S " Р L R
Е Е G (i) N Т О Х S " Р
L R

,, Е Е G I N r о х "
5 М P(L)R
5@Р N А
,, ,, Е

Е
Е

Е
G L Т О Х

Р(м)А
,, Е Е
G
G
L
L "" О Х
N Х
5 1
S 1 Р@А

,, ,, Е Е '® )[
Е Е
G L
" N О S R
Р 1з )[00

,, Е Е
G L
" N О

А®Х 1

,, ,, Е Е
G
G
L
L "" N
N
О
О
Р
Р R S кф
Е Е
Е Е
G
G
L
L "" N
N
О
О
Р
Р
R S
R S
Т
r
Х
х

Рис. 6.2 . При мер сортировки вы бором

В этом при мере п е рвый п роход н ичего н е ме н яет, поскольку в массиве

нет элемента, меньшего самого лево го элемента А . На втором п роходе


наименьшим среди оставшихся оказался другой элеме н т А, п оэтому он
меняется местами с элементом 5, занимающим вторую позицию . Н а
тр етьем пр оходе элеме н т Е из середины массива обменивается с О в
тр етье й п озиции, н а четвертом п роходе еще оди н элеме нт Е ме н яется
местами с Rв ч етвертой позиции и т.д .

Программа 6.2 реализация сорти р овки выбором , в которой


выдержаны все приняты е н ам и соглашения. Внугренний ЦИЮl содержит
только сравнение текущего элеме н та с н аиме ньшим выявленным на

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

необходимо N- 1 таких опера ций (для п оследнего элемента обмен не


нуже н ). По этом у вр емя вы п ол н е ни я опр еделяется в основном
количеством срав н е ний . В разделе 6.5 мы покажем , чт о это количество
про п орционально N 2, н аучимся пр едсказ ыв ать общее время
выполнения и узнаем, как с р ав н ивать сор тир овку выбором с д руги ми
элемен тар ными методами.

'"
Р. СIЩЖВИК

Программа 6.2 . Сортировка выбором

Для каждого i от 1до г-1 элемент аП] меняется местами с минимальным


элементом из а [ i ] , ... , а [ r ] . По мере пр одвижения индекса i
слева направо элементы слева от него занимают свои окончательные

позиции в массиве (и больше не перемещаются), поэтому, когда


достигнет правого конца, массив будет полно стью отсортирован.

template <class Item>


vою se)ection(Item а[], int ~ int г)
{ for (int i := 1; i < г; i++ )
{ int min= ~
for(intj:= i+ l ;j <= r;j ++)
if(a[j] < а[min]) min = j ;
exch(a[i], а [min]) ;
)
)

Н едостаток сортировки выбором заЮlючается в том , что время ее


выполнения почти не зависит от упорядоченности исходного файла .
Процесс поиска минимального элемента за один проход файла дает
очень мало сведе ний о том, где может находиться минимальный
элемент на следующем проходе этого файла . Неискушенный
пользователь БУдет немало удивл ен , когда увидит, что н а сортировку уже
отсортированного файла или файла со всеми одинаковыми Юlючами
тр ебуется столько же времени, сколько и на сортировку случайно
упорядоченного файла! Как мы убедимся позже, д ругие методы лучше
используют упорядоченность исходного файла.

Несмотря на простоту и очевидный примитивизм , сортировка выбором


превосходит более совершенны е методы в одном важном случае: когда
элементы очень велики , а ЮlЮЧИ очень малы. В подобных п риложениях
стоимость п е ремещения дан н ых намного превосходят стоимость

сравнения, а никакой алгоритм н е способен упорядочить файл с заметно


меньшим числом перем е щений да нн ых, чем сортировка выбором (см .
лемму 6.5 в разделе 6.5).

Уп ражнения
Р. СIЩЖ ВИК

6.11. Покажи те в сти ле рис. 6. 2 п роцесс ynорядочения фай ла Е А S У


Q u Е S Т 1 О N методом выбора .

6.12 . Каково максимальное количество обме н ов любого ко н кретного


элемента в процессе сорт ировки выбором? Ч ему равно среднее
количество обменов, приходящееся на оди н элемент?

6.13. Прив ед и те при мер файла из N элементов , в котором во вр ем я


выполн е ния сортировки выборо м условие а [ j] < а [mi n] н е верн о
макс и мальное количество раз (когда min и зме н яет значение).

6.14. Явля ется ли сортировка вы бором устойчивой ?

Сортировка вставками

Картеж ники часто ynорядочивают карты в руках следУЮЩИМ образом:


вы б ирают по очереди каждУЮ ка рту и вставляют ее в нужн ое место
среди уже отсортированных элеме н то в. В компьютерной ре ализа ции
п отребуется освободить место для вставляемого элемента, сдвинув
боль ши е элементы на од н у позицию вправо и п ереместив на
освободившееся место вставляемый элеме н т. Функция so rt из
п рограммы 6.1 яв ляется пр о граммной реализацией это го метода ,
п олучившего на зван и е сортировка вставками (insепiоп sort).

Как и в случае сорт ир о вки вы бором , элеме н т ы сле ва от текущего


инд екса уже ynорядоче н ы , однако они е ще не на ходятся в

окончательн ых по зиц иях, Т.К MOryг быть сдвинугы для освобождения


места под меньшие элемен ты , которые будуг обна р ужен ы по зже .
Массив будет п ол н остью отсортирова н , когда ин декс достигнет правого
конца. При мер р аботы это го метода пока за н на рис. 6.3.

Р еализа ция сортировки вставками, представ ле нна я в п рограмме 6.1,


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

компромисс. Для этого мы сначала разработаем естестве нную


реализацию , а п отом ус овершенствуем ее серией пр еобразований ,
Р. СIЩЖВИК

п роверяя э~ктив н ость (и прави л ьность) каждо го такого

п реобразова ни я.

Прежде всего , можно отказаться от вы п олнения операций compexch,


когда встречается ключ, не больший ключа вставляемого элемента ,
п оскольку п одмассив , находящийся слева , уже отсортирован. А имен н о ,
п ри выпол н ен ии услов и я а [j -1 ] < а [j ] можно выполни ть

команду break, чтоб ы вый ти из в н угренне го цикла for в функци и sort


п рограммы 6. 1. ЭТО и зменен и е пр евра ща ет реализацию в адаптив н ую
сортировку и примерно вдвое ускоряет р аботу программы для случайно
упорядоченных ключей (см. лемму 6.2).

После усовершен ствования , о пи са нн ого в предьщущем абза це ,


п олучилось два условия прекращения выполнения внугре нн е го ци кла

- для н аглядности можн о заме нить е го на оператор while. Менее


очевидное улучше ни е реализац ии следУет из то го факта, что условие j
> 1 обычно оказывается и зл и шним: ведь о н о выполняется только когда
вставляемый элемен т является наиме ньши м из пр осмотренных к этому
моменту и п оэтому доходит до начала массива.

, s о R Т
,,
,, ,, ,,
О
, р L
~ iEO R Т
,, ,, • ,
,, О " р L Е

" О S А Т О д р L
~ т
o(R)s
" ОR S фl
I
, , , •"" ,
, О д р L Е

Т ,,, ,, , • ,
О р L
" фо А S О д р L
" 1000
~~ ~
I
R S о
О R S Т
r
д , , •" р

р
L
L
Е
Е
,
,
Е G

, G
•• , r , •" ,
О R S
О R 1 ®д
д р

р
L
L
Е

,," ®, Е rr (i;i) ,
G
Е G
N о R S Т ,
о R S Т Х
• р

Р
L
L
Е
Е

, Д Е G 1 М N О @А S r
,, х L Е

,, Д

д
EG I Q)MNO P R S
Е (Е) G L М N , О Р
Т
R S Т
д Е Е G I L М N О Р R S Т Х

Рис . 6.3. При мер вы п ол н е ни я сортировки вставкам и

Во время перво го пр охода сорти ровки вставкам и элемент S во второй


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

'"
Р. СIЩЖ ВИК

одну по з ицию вправо.

Часто применя ется альтернативный способ: сортируемые ключи


хранятся в элементах массива от а [1] до а [N] , а в а[О] заноситс я
сигнальный ключ (sentine! key), з нач е ни е которого по крайней мере не
больше наи меньшего ключа в сортируемом масс иве . Тогда пр оверка ,
найден л и меныlшй ключ, пр о веряет сразу оба услов ия , и в результате
внугренний ци кл становится меньше , а быстродействие програ ммы
повышается.

С игнал ьны е ключи н е в се гда Удоб ны: иногда бывает трудно определить
з н ач ени е минимально возможного ключа , а иногда в вызыв аю щей
программе нет места для до полни тельно го ключа. В пр ограмме б.З
предлагается один из способов обойти ср азу обе эт и проблемы: с начала
выполняется отдельный проход по масс иву, который п оме ща ет в первую
позицию элеме нт с минимальным ключом. Затем сортируется
остальной массив , а первый, он же наи мен ыlш, ' элемен т служи т в
качестве сигнального ключа. Обычно мы БУдем избегать употребления в
коде сиrnальных ключей, п оскол ьку часто легче понять код с явными
проверками. Но мы БУдем обязательно отмечать ситуации, когда
сигнальные ЮIючи MOryг оказаться поле зными дл я упрощения

программы и повышения ее э<fxlJeктивности.

Третье улуч ше ни е , которое мы сейчас рассмотрим, также связано с


удалени ем ЛИ lШlи х команд и з вн уг ренн его цикл а. Оно следует и з того
факта, что последовательные обмены с одним и тем же элеме н том
неэqxpeктивны. При двух и л и более обменах вначале выполняется

t = а Ш ; аШ = a[j- l]; a[j- l] = ' ;

затем

t = a[j - l ]; a[j - l] = a[j-2]; a[j-2 ] = t

и т.д. Значе ни е t между эт ими д вум я посл едовател ьно стям и не


изменяется , но пр о ис ход и т бес полезн ая тр ата вр еме ни на его
запоминание и туг же на чте ни е для следУЮщего обмена. Прогр амма б.З
сдвигает Болы lшe элеме н ты вправо без выполнения обменов, тем

'"
Р. СIЩЖ ВИК

самым избегая напрасной траты времени.

Программа 6.3. Сортировка вставками

ЭТО усовершенствованный вариант функции sort из прorраммы 6.1,


по скольку он (1) помещает в п ервую позицию наименьший элемен т
массива, чтобы использовать е го как си гнальный ключ; (2) во
внугреннем цикле выполняет лишь одну операцию присваивания , а не

обмена; и (3) прекращает выполнение внугреннего ци кла , когда


вставляемый элемент уже находится в требуемой позиции. Для каждого
i упорядочиваются элементы а [1 ] , . .. , а [i ] с помощью сдвига
на одну позицию вправо элементов а [ 1] , . , а [ i -1 ] из

отсортированного списка, которые больше аН] , и занесения аП] в


освободившееся место.

template <class Item>


vою insе rtiоп( Itе m а[ ] , iпt ~ iпt r)
{ iпt ~
for (i = r; i > 1; i-- ) соmр ехс Ь(а П- 1] , аШ);
for (i = 1+2; i <= r; i++ )
{ iпt j := i; Item у:= а П];
while (у < a O- 1])
( а Ш = a O- 1]; j --; )
а Ш = у;
}
}

Реализация сортировЮ1 вставками в программе 6.3 з начительно


эфрективнее реализации в программе 6.1 (в разделе 6.5 мы увидим , что
она работает почти вдвое быстр ее). В данной книге нас интересуют как
элегантные и эqxpeктив ны е алгоритмы, так и элегантны е и эффективные
реализации этих алгоритмов. В данном случае алгоритмы несколько
отличаются друг от д руга - правильнее назвать функцию sort из
прогр аммы 6.1 неадаптивной (nonadaptive) сортировкой вставками.
Глубокое понимани е свойств алгоритма - лучшее руководство для
разработЮ1 е го реализации, которая может эqxpeктивно использоваться в
различных прило жениях.
Р. СIЩЖ ВИК

В отличие от сортировки выбором, время выполнения сортировки


вставками зависит главным образом от исходного порядка Юlючей во
входных да нны х. Например , если файл большой , а ЮlЮЧИ уже
упорядочены (или почти упорядочены), то сортировка вставками
выполняется быстро , а сортировка выбором - медленно . Более полное
сравнение алгоритмов сортировки приведено в разделе 6.5.

Ynражнения

6. 15. Покажите в стиле рис . 6.3 процесс упорядочения файла Е А S У


Q u Е S Т 1 О N методом вставок.

6. 16. Приведите реализацию сортировки вставками, в которой


внугренний ЦИЮl оqюрмлен с пом ощью оператора while с завершением
по одном у из двух услов ий , описанных в тексте.

6. 17. Для каждого из условий ЦИЮlа while в упражнении 6.16 приведите


прим ер файла из N элементов , для которого в момент выхода из ЦИЮlа
это условие всегда ложно.

о 6.18. Является ли сортировка вставками устойчивой?

6. 19. Приведите неадаптивную реализацию сортировки выбором ,


основанную на поиске минимально го элемента с помощью кода

нап одобие первого ЦИЮlа for из программы 6.3.

Пузырьковая сортировка

Многие нач и нают знакомство с сортировкой с ИСЮlючительно простого


метода пузырьковой сортировки (bubble sort). В проц ессе своей работы
она выполняет проходы по файлу с обме ном местами соседних
элементов, нарушающих порядок, до тех пор , пока файл не станет
отсортирован. Основным достоинством пузырьковой сортировки
является легкость ее реализации , хотя в этом она сравнима с методом

вставок и методом выбора. В общем случае пузырьковый метод работает


медленнее , однако мы рассмотрим его для полноты картины.

Предположим , что мы всегда п ередвигаемся по файлу справа нал ево .


Когда на первом проходе попадается минимальный элемент, он

'"'
Р. СIЩЖ ВИК

меняется местами с каждым э л ем е нтом сл ева от него, пока н е займет


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

Программа 6.4. Пузырьковая сортировка

Для каждого iOT I до r-l внугренний циЮl Ш по элементам аП], ... , a[r]
помещает минимальный элемент в аШ, перебирая элементы справа
налево и выполняя сравнение с обменом соседних элементов .
Наименьший элемент перемеща ется при всех таких сравнениях и "
вс п лывает" в начало файла. Как и в сортировке выбором , индекс i
перемещается по файлу слева направо , а элемен ты слева от него
находятся в окончательных позициях.

template <class Item>


vою bubbIe(Item а[], int ~ int r)
{ for (iпt i := 1; i < r; i++ )
for (intj := r; j > i; j-- )
compexch(aU-l], а Ш);
}

Быстродей ствие программы 6.4 можно повысить , тщательно


оптимизировав внугренний цикл примерно так же, как это было
сдела но в разделе 6.З для сортировки вставками (см. упражн е ние 6.25).
В самом дел е , сравните коды: пр о гра мма 6.4 пр актически и де нтична
неада птивной сортировке вставками из программы 6.1. Они
отличаются только тем , что в сортировке вставками внугренний цикл
for пер ебирает левую (отсортированную) част ь массива , а в
пузырьковой сортировке - правую (не обязательно упорядоченную)
часть массива.

Программа 6.4 использует только инструкции comp exch и поэтому не


является адаптивной , однако можно повысить ее э$lJeктивность дл я

'"
Р. СIЩЖ ВИК

почти ynоря.цоченных файлов , п рове р яя п осле каждо го прохода, были


ли выполнены переста н овки (т. е. файл п ол н остью отсортирова н , и
можно вый ти из внешнего ЦИЮJa). Э ТО усоверше н ствование ускоряет
пузырьковую сорти ровку на некоторых типах файлов , однако, как будет
показано в разделе 6.5, в общем случае оно все равно не так
эфрективно, как выход и з внугреннего цикла сортировки вставками.

ASOR T N GE X A M P L E
A ® SORT N GE X@M P L
AA @ SORT N GE X(b)M P
Д Д E@ S О R Т N G(b) x@XV
Д Д Е Е @S О R Т I N CDiJ)x ®
AAEEG (D SORTCD N @ Р Х
AAEEGI (b) SORT @ N Р Х
AAEEGIL @SOR T N Р Х
AAEEGILM ® SOR т
P~
AAEEG LM N@S® R Т Х
AAE:EG LM N O ®S R Т Х
AAEEG LM N OP @ S т х
А А Е Е G L М N О Р R s т х
А А Е Е G L М N О Р R s Т >&
А А Е Е G L М N О Р R s т х
А А Е Е G L М N О Р R S Т Х

Рис. 6.4. При мер вы п ол н е н ия пузырьковой сортировки

в п узырьковой сортировке ключи с малыми значениями постеп ен но


сдвигаются влево . Поскольку проходы выполняются сп рава н алево ,
каждый ключ меняется местами с ключом слева до тех п ор, пока не
будет обнаружен ключ с ме н ьшим значением. На п ервом проходе Е
меняется местами с L, Р и М и оста н авливается справа от А; затем А
продви гается к началу файла , п ока не оста н овится п еред другим А,
который уже н аходи тся на свом месте . Как и в случае сортировки
выбором, п осле i-ro п рохода i-й п о величине ключ устанавливается в
окончательн ое положение , но п ри этом другие ключи также

приближаются к своим окончательным позициям.

Уп ражнения

6.20. Покажите в стиле рис. 6.4 процесс ynоря.цочения файла Е А S У


Q u Е S Т 1 О N методом пузырьковой сортировки.

'"
Р. СIЩЖВИК

6.21. Приведите пример файла , для которого пузырьковая сортировка


выполняет максимально возможное количество перестановок

элементов.

6.22. Является ли пузырьковая сортировка устойчивой ?

6.23. Объясните, почему пузырьковая сортировка лучше неа.цаптивноЙ


версии сортировки выбором, описанной в упражнении б.19.

6.24. Э кспериментально опред елите количество сэкономленных


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

6.25. Разработайте эффективную реализацию пузырьковой сортировки с


минимально возможным числом операторов во внугреннем ЦИЮlе.

Проверьте , что ваши " усовершенствования " не снижают


быстродействие программы!

Характеристики производительности элементарных

методов СОРТИРОВКИ

Сортировка выбором, сортировка вставками и пузырьковая сортировка


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

среднем случае; все они не требуют дополнительной памяти. Поэтому


время их выполнения отличается лишь на постоянный коэфрициент,
хотя принципы работы существенно различаются (см. рис . 6. 5, рис . б . б
и рис . б . 7 ).
Р. СIЩЖ ВИК

Рис. 6.5. ДинамичеСЮ1е ха р актер ис тиЮ1 сортировок выбором и


вставками

Эти снимЮ1 процесса сортировЮ1 вставками (слева) и выбором (с права )


случайной последовательности иллюстрируют выполнение сортировЮ1
обоими методами. Упорядо ч енность массива по каза на в виде графика
зависимости аН] от индекса i Перед началом сортировЮ1 график
пр едставляет равномерно распределенную случайную величину, а по
окончании сортировЮ1 он выглядит диагональю из левого нижнего угла

в пр авый верхний угол. Сортировка вставками никогда н е забегает


вперед за текущую позицию в массиве , а сортировка выбором никогда
н е возвращается назад.

'"
Р. СIЩЖ ВИК

A/'\\\~H l d A/'\\\~h~ l d A/'\\\~h~ld


л /'\\\~h~ d A/'\\\~ ~ I d "A/XI/'MMJ
tX /,\\\ Ii<- h~ d ,, ;!, /,\\\ ~ ы 1d " А /,\i\ /1\1 111 J
I И'\\\~h~ d ""/'\\\I!h;~ld ,,\ А /J\\ /J\IIIIJ
\I/!.:\i\~h~ d ,,\11,\\\ /Ih;~ 1d '\\ А /WlIXIIIIJ
\ ' I/Ji:I ~h~ d \\\\ /xMIh;~ l..,t ,,\\\ 4/)\\ /A IIIJ
\III/..К\ ~h~ d ,,\\\ Jbllh;IH ,,,\\\ 4 IJ-\I /А 11""
\\lI/#. ~h~ d \\\\\ШIШ~d ""\\\\ J.\ml/A 1""
\\\IIШ~~~ d "\\\\\ h/IШН \,,\\\\\ AII Ш 1 //1""
'I. \\II!!~ ~~ d ,,\\\\\\ II!ШН ""\\\\\ \;t, 1:r1I//""
\\\\I'//д h~ d ,,\\\\\\\ /AI'.ш..,t ",,\\\\\1,1),IIШ/J
\\\\1////11 h~ d ,,\\\\\\\, А/Л~А' ""\\\\\\1 1),1 и//""
\\\\\III/I!I. ~~ d "\\\\\\\II IШI/d "'\\\\\\\1 1)'IШ""
\\\\\\IIIIIIA ~ d "\\\\\\\IIIIA'JIU "'\\\\\\\1 I I,//Ш""
,,\\\\\II//Ш \ d "\\\\\\\IIIU1ll4 "'\\\\\\\1 11141///.1/
",,\\\" \III/llЛ d "\\\\\\\IIIII ШI4 "\\\\\\\/1 11/;1///.1/
",,\\\\\1111//1,( d \\\\\\\\\IIII/ШI4 ""\\\\\\1 Illi////.I/
",,\\\\\\ ' III/Ш d \\\\\\\\\1111111/44 ""\\\\\\1 IIII/Ш""
"\\\\\\\\ I III//Шd \,,\\\\\\1111111111 4 ""\\\\\\1 1111//111""
",,\\\\\\11 11/I//I/ d \\\\\\\\lllIlil/IM ""\\\\\\11111111/1""
",,\\\\\\II'III//IЙ ",\\\\\\IIIII/I/IМf ",,\\\\\\11111111//.1/
",,\\\\\\1 1'III//I/ d ,,\\\\\\\111111/11/"" "\\\\\\\1 1/11//1/""
",,\\\\\\II IIIIII//'w ,,\\\\\\\ IIIIIIIIШ/ ""\\\\\\1 11111/1//.1/
"",\\\\\\IIIIIIII//'w ,,\\\\\\\ II IIIIII;/,W ,,"\\\\\\l IIIIII/I/'w

Рис. 6.6 . О п ерации сравнения и обмена в элементар н ых методах


сортировки

На это й диаграмме показаны различия в способах ynорядочения файла


сортировкой вставками, сортировкой выбором и пузырьковой
сортировкой. Сорт ируемый файл представлен отрезками , которые
Р. СIЩЖ ВИК

сортируются П О углу наюю на. Ч ерные отрезки означают элементы , к


которым выполнялся доступ на каждом прОXDде сортировки; серые

отрезки означают не задействованные элемен ты . В проц ессе


сортировки вставками (слева) вставляемый элемент перемещается на
каждом про ходе примерно на полпуги назад по упорядоче нной части .
Сортировка выбором (в центре) и п узырьковая сортировка (с пр ава)
перебирают на каждом прОXDде всю неотсортированную част ь масс ив а ,
чтобы найти в ней следующий наименьший элемент. Различие между
этими методами заЮlючается в том, что п узырьковая сортировка меняет

местами каждую попавшуюся неупорядоченную пару соседних

элементов, а сортировка выбором с р азу помещает минимальный


элемен т в окончательную позицию. ЭТО различие проявляется в том ,
что по мере выполнения пузырьковой сортировки н еотсортированная
часть массива становится все более упорядоченной .
Р. СIЩЖ ВИК

Рис. б.7. ДинамичеСЮ1е характеристиЮ1 двух п узырьковых сортировок

Ста ндартн ая пузырьковая сортировка (слева) похожа на сорти р овку


выбором тем, что на каждом проходе один элемент занимает свою
окончательную позицию, но одноврем енно она асимметрично

привносит ynоря.цоченность в остальную час ть массива. Пооч ередная


смена направления просмотра массива (т.е. просмотр от начала до
конца массива меняется на просмотр от конца до н ачала и наоборот)
дает новую разновидность пузырьковой сортировЮ1, получившей
название шейкерной сортировЮ1 (справа), которая заканчивается
быстрее (см. ynражнение б.30).
Р. СIЩЖ ВИК

в общем случае время выполнения алгоритма сортировки

пропорционально количеству операций сравнения, выполняемых этим


алгоритмом, количеству п еремещений или обменов элементов, а,
возможно, и ТОМУ; И другому сразу. Для случай но ynорядоченных
данных сравнение элементарных методов сортировки сводится к

и зучению постоянных коэфрициентов, на которые отличаются


количества выполняемых операций сравнений и обменов, а также
по стоянных коэфрициентов, на которые отличаются длины внугренних
ЦИЮlов . В случае входных данных с особыми характеристика ми времена
выполнения различных видов сортировок MOryr отличаться и более чем
на постоянный коэффициент. В дан ном разделе бу,цуг подробно
рассмотрены аналитические результаты , на которых основано это

заЮlючение.

Лемма 6.1 . Сортировка выбором выполняет порядка N 2/ 2 сравнений и


N обменов элементов.

Да нно е свойство легко проверить на примере данных, приведенных на


рис. 6.2. ЭТ О табли ца размером N х N, в которой незаllПрихованные
буквы соответствуют сравнениям. Примерно половина элемен тов этой
таблицы н е заllПрихована, эти элемен ты расположены над диагональю .
Каждый из N 1 элементов на д иагонали (кроме последнего)
соответствует операции обмена. Точнее , исследование кода показывает,
что для каждого i от 1 до N - 1 выполняется один обмен и N 1

сравнений, так что всего выполняется N - 1 операций обмена и (N -


1) + (N - 2) + + 2 + 1 = N (N - 1) ; 2 операций

сравнения. Эти соображения н е зависят от при роды входных да нных;


единственная часть сортировки выбором , которая зависит от характера
входных данных - это количество присваиваний переменной min
новых значений. В худшем случае эта величина может оказаться
квадратичной , однако в среднем она и меет порядок О (N logN) (см.
раздел ссылок), поэтому можно сказать, что время выполнения
сортировки выбором н е чувствительно к природе входных данных. •

Лемма 6.2 . Сортир овка вставками выполняет в среднем порядка N2 ; 4


сравнений и N2 ; 4 полуобменов (перемещений), а в худшем случае в
два раза больше .

Сортировка , реализованная в программе 6.3, выполняет одинаковое

'"
Р. СIЩЖ ВИК

количество с р авнений и п еремещениЙ. Как и в случае леммы 6.1, эту


величину легко наглядно увидеть на диагр амме размером N х N,
кото р ая демонстрирует подробности работы алгоритма (см . ри с. 6.3).
3десь ведется подсчет элеме н тов п од глав н ой диа го н алью - в худшем
случае всех . Для случайно упорядоченных входных да нн ых можно
ожидать, что каждый элемен т проходит в средн ем п римерно половину
п yrи назад , поэтому н еобходимо учи тывать только половину элементов ,
лежащих ниже диагонал и .•

Лемма 6.3. Пузырьковая сортировка вы п олн яет п орядка N2 ; 2


сравнений и N2 ; 2 обменов - как в средн ем, так и в худшем случае.

На i-OM проходе пузырьковой сортировки н ужно выполнить N 1

опе р аций с р авнения-обмена, поэтому лемма доказывается так же, как и


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

обратной упорядоченности i-й проход требует вы п ол н е н ия N- 1

сравнений и обменов. Как отмечал ось ранее , производи тельность


сортировки для обычных да нн ых н е н амно го выше, чем для худшего
случая , однако доказательство этого факта достаточно сложно (см .
раздел ссылок) . •

Хотя понятие частич н о отсортированного файла п о своей природе


довольно неточно, сортировка вставкам и и пузырьковая сортировка

хорошо работают с файлам и , не обладающими произвольн ой


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

Определе ни е 6.2. Инверсией называется п ара Юlючей , которые


н арушают порядок в файле .
Р. СIЩЖ ВИК

ДЛЯ подсчета количества инверсий в файле н еобходимо для каждого


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

сортировки вставками. В частично отсортированном файле меньше


инв ерсий, чем в произвольно упорядоченном файле.

Существуют такие типы частично отсортированных файлов, в которы х


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

Лемма 6.4. Сортир овка вставками и пузырьковая сортировка


выполняют линейное количество сравнен ии и обме нов в файлах с не
более чем постоянным числом инверсий, приходящихся на каждый
элемен т.

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


пр ямо пр о по рционально количеству инв ерс ий в сортируемом файле.
Для пузырьковой сортировки (здес ь имеется в виду пр ограмма 6.4, где
выполнение п рекращается, как только файл становится упорядоче нны м)
доказательство тр ебует более тонких рассужде ний (см. упражнение
6.29). Для любого элеме н та каждый проход пузырьковой сортировки
умень шает количество элемен тов справа , меньших е го , в точности на 1
(если оно не было равным О). Следовательно , для р ассматриваемых
типов файлов п узырьковая сортировка выполняет не более чем
по стоянное количество пр оходов, а значит, количество срав н ен ий и
обменов не более чем линейно . •

Наиболее часто встречаются дрyrnе виды частично упорядоченных

'"

Вам также может понравиться