Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Programmirovanie_i_osnovyi_algo

.pdf
Скачиваний:
10
Добавлен:
05.03.2016
Размер:
9.5 Mб
Скачать

left

п

median Если i <=

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;

 

 

 

 

261

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

left right

arr[j] key 06 12

median

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 - размер сортируемого массива.

262

Пример 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. При рекурсивных

вызовах функции создаются поколения

263

вызовов-функций.

Это означает, что имеются вложенные друг в дру­

га активные экземпляры рекурсивной функции.

2. Каждый

рекурсивный вызов помещает в системный стек:

копии параметров рекурсивной функции, передаваемых по значению; адреса аргументов из вызова рекурсивной функции, соответст­

вующих параметрам, передаваемым по ссылке; ее автоматические переменные; адрес возврата из функции;

возвращаемое значение, если оно имеется.

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

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

С учетом этих особенностей и была спроектирована рекурсив­ ная сортировка Хоора. Прототип функции QuickSortl для рекурсив­ ной сортировки Хоора, ее определение и пример вызова даны в примере программы из подразд. 15.2. Эта функция является интер­ фейсной и предназначена для сортировки массивов. Для разделения исходного сегмента на подсегменты функция Quicksort 1 использует служебную рекурсивную функцию Split. Ее прототип, определение и пример вызова даны также в примере программы из подразд. 15.2. Функция Split является рекурсивной и именно при ее разработке бы­ ли учтены перечисленные выше важные особенности рекурсивных функций.

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

Приводимые ниже в табл. 29 данные получены для программы, написанной на языке Паскаль (ЭВМ SDS6400) для неупорядоченных массивов.

Из приведенных в таблице данных следует, в частности, что даже для массива относительно небольшого размера из 512 элемен­ тов:

1. Худшая по производительности из простых сортировок (простая сортировка обменом) работает в 35 раз медленнее быстрой сортировки Хоора.

2. Самая быстрая из простых сортировок (простая сортировка вставками) работает медленнее в 4,2 раза, чем самая худшая по про-

264

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

При увеличении размеров массива, указанные в пп. 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 б), т.е.

266

{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).

267

100

10.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/

//

Вес

 

ребра

 

 

 

 

};

268

/ /

Адрес первого

элемента массива структур с информацией

о

//

ребрах

графа

 

А

 

 

*рАгс;

 

Задание графа на основе списка ребер удобйо свести в одну структуру:

/ / Структурный

тип для

графа

 

 

 

 

struct GRAPH

 

 

 

 

 

 

{

NumTop;

//

Число

вершин

 

 

±nt

 

 

Izit

NumArc;

//

Число

ребер

 

ребер

А

*рАгс;

//

Указатель на начало массива

 

 

//

в динамической

памяти

 

} ;

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

1. Использование матрицы соединений требует хранения в па­ мяти NumTop*NumTop элементов.

2.Использование списка ребер требует хранения в памяти Ъ"^NumArc элементов.

3.При Ъ'^NumArc < NumTop"^NumTop эффективнее использо­ вать задание графа с помощью списка ребер.

4.Использование списка ребер алгоритмичнее. Это означает, что алгоритмы решения задач с использованием графов, заданных списком ребер, проще и эффективнее.

Для примера, приведенного на рис. 81, информация для списка ребер имеет следующий вид:

2, 80. .0

i ,

10. .0

2,

3 ,

20.

,0

5, 10. .0

3,

4,

20.

О

4,

5,

10.

0

Здесь в первой строке 1 и 2 - номера вершин, а 80.0 - вес соединяю­ щего их ребра.

16.3. Почему для решения задачи подходит рекурсивный алгоритм?

в общем случае путей из вершины start до вершины finish мо­ жет быть несколько, но только один путь будет наилучшим (в част­ ном случае, путь может вообще не существовать, например, в несвя­ занном графе, или может быть несколько наилучших, эквивалент-

269

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]