a)
i = left, while( arr[ i ].key < median.key ) i++;
Всегда ли закончится цикл? Да, всегда, поскольку, как минимум, справа есть median.
пПосле выхода из цикла получим агг[ I ] key >= median.key
median Q)
j = right; while( arr[ j ] key > median.key ) j — ; right
По тем же причинам, цикл будет заканчиваться всегда и после его завершения получим агг[ j ].кеу <= median key
в)
j , то divrl i ] и arr[ j ] меняем местами: copy = arr[ i ]; arr[ i ] = arr[ j ]; arr[ j ] = copy; i++; j - ;
г)
Перейти к a), если i <= j
Рис. 76. Разделение исходного сегмента на подсегменты
J. Примеры, Пример 1 для общего случая представлен на рис.
77.
Примеры 2, 3 и 4 (частные случаи) приведены на рис. 78. Их анализ предлагается выполнить самостоятельно.
|
|
left |
|
|
|
|
|
|
|
right |
|
Исходные значения ключей |
44 |
55 |
12 |
|
42 |
94 |
18 |
06 |
67 |
|
элементов массива arr[i] key |
|
|
|
|
|
4 |
5 |
6 |
7 |
|
(1 = 0 , 1 , .., size-1), size = 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
median=arr[(left+right)/2] |
|
|
a) Разбиение исходного массива |
|
|
|
|
1=0, |
j=7,6; |
arr[0] меняем местами с arr[6], |
i++; j - - , |
i<=j (продолжаем |
обмен) |
1=1, |
j=5; |
arr[1] меняем местами с arr[5], |
i++, j - - , |
i<=j (продолжаем |
обмен) |
1=2,3, j=4,3; |
arr[3] меняем местами с arr[3]; |
i++, j - - ; |
i>j |
(разбиение на |
|
сегменты закончено) В результате получаем: |
|
|
|
|
|
|
|
|
left |
|
|
|
|
|
|
|
right |
|
|
|
' |
" |
|
|
|
|
|
|
|
|
|
|
|
06 |
18 |
12 |
|
42 |
94 |
55 |
44 |
67 |
|
|
|
|
|
|
|
|
|
4 |
5 |
6 |
7 |
|
|
|
|
|
|
|
|
|
median=arr[(left+right)/2] |
Сегменты. |
|
левый left J J |
и |
правый » nght |
|
|
|
|
|
|
|
|
(большего размера) |
|
б) Каждый из полученных сегментов также разбиваем на сегменты, причем
это выгоднее начать с меньшего сегмента (см. обоснование ниже) Поэтому
больший из полученных сегментов запоминаем в стеке для последующего разбиения
|
left |
|
j i g h t |
агг[|].кеу |
06 |
18 |
12 |
|
0 |
1 |
2 |
|
|
Н Н |
nnedlan |
|
left |
|
rightt |
агг[|].кеу |
06 |
12 |
18 |
|
0 |
1 |
2 |
|
|
|
med lan |
\=o,^^, j=2; arr[1] меняем местами с агг[2],
•++; J--; i>J (разбиение на подсегменты
закончено) В результате получаем
|
1 |
|
|
Левый сегмент left |
j и правый i .right (меньшего |
размера) |
|
Рис. 77. Пример сортировки Хоора для общего случая |
4. В каком |
порядке сортировать |
полученные |
сегменты? |
Сразу оба полученных сегмента сортировать нельзя - какой-то из них нужно отложить на более позднее время, например, запомнить его в стеке.
|
struct |
STACK |
// |
Тип элемента |
стека |
|
( |
|
// |
Левая |
граница |
сегмента |
|
±пЬ |
1; |
|
// |
Правая |
граница |
сегмента |
|
±пЬ |
г; |
|
} s[ М |
]; |
// |
Указатель вершины стека |
|
int |
sp; |
|
|
|
|
|
в) Больший из полученных сегментов (левый) запоминаем в стеке для последующего разбиения. Работа с правым (меньшим) сегментом закончена, так как в нем один элемент. Извлекаем из стека и разбиваем очередной сегмент
left right
i=0; j=1,0; arr[0] меняем местами с arr[0]; i++, j - i>j (разбиение на сегменты закончено). Оба полученных сегмента единичной или нулевой А^'^чь! и работа с ними закончена
г) Извлекаем из стека и разбиваем очередной сегмент
|
left |
55 |
44 |
right |
i=4; j=7,6; arr[4] меняем местами с агг[6]; |
агг[1].кеу |
94 |
67 |
i++; j — ; |
i<=j (продолжаем обмен) |
|
|
|
6 |
7 |
i=5; j=5; |
агг[5] меняем местами с arr[5]; |
|
|
|
median |
i++; j — ; |
i>j (разбиение на сегменты |
|
|
|
закончено). |
|
left |
|
|
|
|
|
94 |
right |
Получаем левый left..j и правый 1..right |
arr[i].key |
44 |
55 |
67 |
(большего размера) сегменты. Больший |
сегмент запоминаем в стеке для |
|
|
|
6 |
7 |
последующего разбиения, а работа с |
|
|
|
median |
|
|
|
меньшим сегментом закончена (в нем один |
элемент).
д) Извлекаем из стека и разбиваем очередной сегмент left right
arr[i].key 94 67
7 median
i=6; j=7; агг[6] меняем местами с агг[7]; i++; j — ; i>j (разбиение на сегменты закончено). Оба полученных сегмента имеют единичную длину и работа с ними закончена.
Так как в стеке нет больше сегментов для разбиения,то и сортировка закончена:
06 |
12 |
|
18 |
42 |
44 |
55 |
67 |
94 |
0 |
1 |
2 |
|
3 |
4 |
5 |
6 |
7 |
Прод. рис. 77 •
Какой должна быть глубина стека М? Величина М зависит от порядка разбиения полученных сегментов. Н. Вирт показал, что ес ли в стек помещать более длинный из получившихся сегментов, а с коротким сегментом сразу "расправляться", то
М = \0g2isize),
где size - размер сортируемого массива.
Пример 2 |
left |
|
|
nght |
Исходные значения ключей элементов |
|
|
3 |
2 |
- 7 |
5 4 |
массива агг[ i ].кеу (i = О, 1, .. , size-1), size = |
О |
|
|
|
Пример 3 |
median=arr[(left+right)/2] |
left |
|
|
right |
|
|
|
Исходные значения ключей элементов |
3 |
2 |
5 - 7 |
4 |
массива агг[ i ].кеу (i = О, 1, ..., size-1), size = |
|
1 |
|
|
|
median=arr[(left+right)/2] |
Пример 4 |
left |
|
|
right |
|
|
|
Исходные значения ключей элементов |
3 |
2 |
4 - 7 |
5 |
массива агг[ 1 ].кеу (1 = О, 1, ..., slze-1), size = |
0 |
1 |
|
|
, median=arr[(left+right)/2] Рис. 78. Частные случаи для сортировки Хоора
Если, в целях унификации, границы исходного массива также заносить в стек, то
Л/ = 1 + \og2(size).
При занесении в стек сделаем так, что операция занесения не будет выполняться, если длина заносимого сегмента меньше или равна единице.
Прототипы функций для занесения границ сегментов в стек (Push) и извлечения границ сегментов из стека (Pop), определения функций и примеры их вызова даны в примере программы из подразд. 15.2. Эти функции являются служебными для нерекурсивной функции Quicksort. Подобные функции, как указывалось выше, на зывают неинтерфейсными функциями.
5.Нерекурсивная сортировка Хоора, Прототип функции Quicksort, ее определение и пример вызова даны в примере про граммы из подразд. 15.2. Эта функция является интерфейсной функ цией и может использоваться для сортировки массива.
6.Рекурсивная сортировка Хоора.
Напомним ваэюнейшие |
особенности рекурсии: |
1. При рекурсивных |
вызовах функции создаются поколения |
вызовов-функций. |
Это означает, что имеются вложенные друг в дру |
га активные экземпляры рекурсивной функции. |
2. Каждый |
рекурсивный вызов помещает в системный стек: |
копии параметров рекурсивной функции, передаваемых по значению; адреса аргументов из вызова рекурсивной функции, соответст
вующих параметрам, передаваемым по ссылке; ее автоматические переменные; адрес возврата из функции;
возвращаемое значение, если оно имеется.
По этой причине в рекурсивной функции лучше иметь меньше па раметров и автоматических переменных (помните, что поколений активных экземпляров рекурсивной функции может быть много).
3. На рекурсивный вызов тратится больше времени, но зато программа получается нагляднее и проще.
С учетом этих особенностей и была спроектирована рекурсив ная сортировка Хоора. Прототип функции QuickSortl для рекурсив ной сортировки Хоора, ее определение и пример вызова даны в примере программы из подразд. 15.2. Эта функция является интер фейсной и предназначена для сортировки массивов. Для разделения исходного сегмента на подсегменты функция Quicksort 1 использует служебную рекурсивную функцию Split. Ее прототип, определение и пример вызова даны также в примере программы из подразд. 15.2. Функция Split является рекурсивной и именно при ее разработке бы ли учтены перечисленные выше важные особенности рекурсивных функций.
15.9. Сравнительные показатели производительности различных методов сортировки массивов
Приводимые ниже в табл. 29 данные получены для программы, написанной на языке Паскаль (ЭВМ SDS6400) для неупорядоченных массивов.
Из приведенных в таблице данных следует, в частности, что даже для массива относительно небольшого размера из 512 элемен тов:
1. Худшая по производительности из простых сортировок (простая сортировка обменом) работает в 35 раз медленнее быстрой сортировки Хоора.
2. Самая быстрая из простых сортировок (простая сортировка вставками) работает медленнее в 4,2 раза, чем самая худшая по про-
изводительности из сложных сортировок (сортировка Шелла).
При увеличении размеров массива, указанные в пп. 1 и 2 эф- фекты проявляются еще в большей степени.
Табл. 29. Сравнительные показатели производительности различных методов сортировки массивов
|
Простые методы сортировки |
|
|
|
Время |
Время сорти |
Соотношение |
методов |
Метод |
сортировки |
ровки |
по производительности |
сортировки |
для |
для |
(относительное |
время |
|
size==256, |
size=512, ми |
сортировки) |
|
милисекунд |
лисекунд |
|
|
Вставками |
356 |
1444 |
1 |
|
Выбором |
509 |
1956 |
1.3 |
|
Обменом |
1026 |
4054 |
3 |
|
|
Сложные методы сортировки |
|
|
Обменом (Хоора) |
60 |
116 |
1 |
1 |
Выбором (с помощью |
Н О |
241 |
1.7 |
|
двоичного дерева) |
|
|
|
|
Вставками (Шелла) |
127 |
349 |
2.1 |
|
16. ГРАФЫТРАНСПОРТНАЯ ЗАДАЧА (ЗАДАЧА КОММИВОЯЖЕРА)
16,1. Терминология
Граф - это пара (К, /?), где V - конечное непустое множество вершин, а R -множество неупорядоченных пар <а, Ь> вершин из множества К, называемых ребрами. Говорят, что ребро г — <а, Ь> соединяет вершины "а" и "6". Ребро 'V" и вершина "<з", ребро 'V" и вершина "Z?" называются инцидентными. Вершины "а" и "6" являют ся смеэюными. Ребра, инцидентные одной и той же вершине, также называют смеэюными. Степень вершины равна числу ребер, инци дентных ей.
Для простоты будем ограничиваться классом графов без пе тель, т.е. без таких ребер <а, 6>, что а = Ь.
Пример графа._ Города и связывающие их дороги можно пред ставить с помощью графа, показанного на рис. 79.
Рис. 79. Пример графа, представляющего города и дороги между ними
В данном примере граф задает объекты (города) и отношения между объектами (дороги). В приведенном графе содержатся пять вершин и семь ребер. Степень вершин 1, 2, 3, 4 равна трем, а вершины 5 - двум.
В ориентированном или направленном графе каждое ребро имеет направление (например, дорога с односторонним движением, рис. 80 а). В направленном графе отношения не симметричны.
В неориентированном графе отсутствует ориентация ребер (рис. 80 б), т.е.
{a,b) G R тогда и только тогда, когда {Ь,а) е R
а |
b |
а |
b |
О |
Ю |
о |
о |
а - предшественник вершины "Ь" |
|
|
b - преемник вершины "а" |
|
|
|
а) |
|
б) |
|
Рис. 80. Граф: |
|
|
а) ориентированный (направленный); |
|
б) неориентированный |
|
Путь |
в неориентированном графе, соединяющий вершины "а" |
и "Z?", - это последовательность |
вершин Vo,v,,...,i/„(«>0) такая, что |
v/Q=a,v„=6, |
а для любого /(0</<«-1) вершины |
v, и v,^,, соединены |
ptGpoM. Длина пути Vo,v,,...,v„ равна количеству его ребер, т.е. п. Для примера, показанного на рис. 76, путь 1-4-3-2 между вершинами 1 и 2 имеет длину 3.
Путь замкнут, если v^ =v„. Путь называется простым, если все его вершины различны. Замкнутый путь, в котором все ребра раз личны, называется циклом. Простой цикл - это замкнутый путь, все вершины которого, кроме вершин VQ И V„, попарно различны.
Расстояние между двумя вершинами - это длина кратчайшего пути, соединяющего эти вершины. Например, на рис. 79 расстояние
между вершинами |
1 и 2 равно единице, а между вершинами 3 и 5 - |
двум. |
|
|
|
Обод - это граф, вершины которого |
Vo,v,,...,v/„ при п>2 можно |
занумеровать так, что для всех |
i{\<i<n-\) |
вершина v, соединена |
ребрами с v,_, и v,^,, вершина VQ С V„, а других ребер нет. |
Граф называется связным, |
если лля любой пары вершин суще |
ствует соединяющий их путь. |
|
|
Во взвешенном |
графе, в дополнение |
к графу, задана функция |
W = f{R), определяющая вес или длину ребра в графе. Обычно взве шенные графы являются неориентированными, а веса ребер - поло жительными. Пример взвешенного неориентированного графа при веден на рис. 81.
В этом примере в качестве веса ребра можно выбрать расстоя ние между городами. Из примера со всей очевидностью следует, что при поиске пути с суммарным минимальным весом не обязательно, что минимальный вес имеет путь с минимальным числом ребер (в примере лучшим путем от start ло finish является путь по трем доро гам 1-4-5-2 с суммарным весом 30.0).
10.0 Рис. 81. Пример взвешенного неориентированного графа
16.2. Формы задания графа
Используются две основные формы:
1. С помощью матрицы инциденций (соединений):
а) для неориентированного взвешенного графа (рис. 82 а) мггт- риц|^ инциденций имеет число строк и столбцов, равное числу вер шин, и симметрична;
б) для ориентированного взвешенного графа матрица соедине ний не симметрична (рис. 82 б).
а |
20 |
|
|
b |
|
|
а |
|
|
20 |
b |
о |
О |
20 |
|
-О |
|
|
о |
а |
0 |
20 |
-ю |
а |
|
|
|
|
|
|
ь | |
2 0 |
I О I |
|
|
|
|
b |
0 |
0 |
|
|
а |
b |
|
|
|
|
|
|
а |
|
|
|
а) |
Рис. 82. Способы задания графа |
|
б) |
|
|
|
|
|
|
2. С помощью |
списка ребер: |
|
|
|
|
|
|
±nt |
|
Num Top , |
// |
Число |
вершин |
|
|
|
|
|
|
NumArc; |
// |
Число |
ребер |
|
|
|
|
\сЬ А |
|
|
|
// |
Ребро |
графа |
|
|
|
|
±nt |
|
first; |
// |
1-я |
вершина |
ребра |
|
|
|
±nt |
|
last |
/ |
// |
2-я |
вершина |
ребра |
|
|
|
float |
|
weight/ |
// |
Вес |
|
ребра |
|
|
|
|
};
/ / |
Адрес первого |
элемента массива структур с информацией |
о |
// |
ребрах |
графа |
|
А |
|
|
*рАгс; |
|
Задание графа на основе списка ребер удобйо свести в одну структуру:
/ / Структурный |
тип для |
графа |
|
|
|
|
struct GRAPH |
|
|
|
|
|
|
{ |
NumTop; |
// |
Число |
вершин |
|
|
±nt |
|
|
Izit |
NumArc; |
// |
Число |
ребер |
|
ребер |
А |
*рАгс; |
// |
Указатель на начало массива |
|
|
// |
в динамической |
памяти |
|
} ;
Сопоставление указанных форм задания графа позволяет заключить следующее.
1. Использование матрицы соединений требует хранения в па мяти NumTop*NumTop элементов.
2.Использование списка ребер требует хранения в памяти Ъ"^NumArc элементов.
3.При Ъ'^NumArc < NumTop"^NumTop эффективнее использо вать задание графа с помощью списка ребер.
4.Использование списка ребер алгоритмичнее. Это означает, что алгоритмы решения задач с использованием графов, заданных списком ребер, проще и эффективнее.
Для примера, приведенного на рис. 81, информация для списка ребер имеет следующий вид:
1г |
2, 80. .0 |
i , |
4г |
10. .0 |
2, |
3 , |
20. |
,0 |
2г |
5, 10. .0 |
3, |
4, |
20. |
О |
4, |
5, |
10. |
0 |
Здесь в первой строке 1 и 2 - номера вершин, а 80.0 - вес соединяю щего их ребра.
16.3. Почему для решения задачи подходит рекурсивный алгоритм?
в общем случае путей из вершины start до вершины finish мо жет быть несколько, но только один путь будет наилучшим (в част ном случае, путь может вообще не существовать, например, в несвя занном графе, или может быть несколько наилучших, эквивалент-