Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
TP_1-51_53.docx
Скачиваний:
36
Добавлен:
04.11.2018
Размер:
192.97 Кб
Скачать

35) Структурное программирование. Контроль программного модуля.

При программировании модуля следует иметь ввиду, что программа должна быть понятной не только компьютеру, но и человеку: и разработчик модуля, и лица, проверяющие модуль, и тестовики, готовящие тесты для отладки модуля, и сопроводители ПС, осуществляющие требуемые изменения модуля, вынуждены будут многократно разбирать логику работы модуля. В современных языках программирования достаточно средств, чтобы запутать эту логику сколь угодно сильно, тем самым, сделать модуль трудно понимаемым для человека и, как следствие этого, сделать его ненадежным или трудно сопровождаемым. Поэтому необходимо принимать меры для выбора подходящих языковых средств и следовать определенной дисциплине программирования. Впервые на это обратил внимание Дейкстра [8.2] и предложил строить программу как композицию из нескольких типов управляющих конструкций (структур), которые позволяют сильно повысить понимаемость логики работы программы. Программирование с использованием только таких конструкций назвали структурным.

Рис. 8.1. Основные управляющие конструкции структурного программирования.

Основными конструкциями структурного программирования являются: следование, разветвление и повторение (см. Рис. 8.1). Компонентами этих конструкций являются обобщенные операторы (узлы обработки [8.5]) S, S1, S2 и условие (предикат) P. В качестве обобщенного оператора может быть либо простой оператор используемого языка программирования (операторы присваивания, ввода, вывода, обращения к процедуре), либо фрагмент программы, являющийся композицией основных управляющих конструкций структурного программирования. Существенно, что каждая из этих конструкций имеет по управлению только один вход и один выход. Тем самым, и обобщенный оператор имеет только один вход и один выход.

36. Пошаговая детализация и понятие о псевдокоде. Контроль программного модуля.

Структурное программирование дает рекомендации о том, каким должен быть текст модуля. Возникает вопрос, как должен действовать программист, чтобы построить такой текст. Иногда программирование модуля начинают с построения его блок-схемы, описывающей в общих чертах логику его работы. Однако современная технология программирования не рекомендует этого делать. Хотя блок-схемы позволяют весьма наглядно представить логику работы модуля, при их кодировании на языке программирования возникает весьма специфический источник ошибок: отображение существенно двумерных структур, какими являются блок-схемы, на линейный текст, представляющий модуль, содержит опасность искажения логики работы модуля, тем более, что психологически довольно трудно сохранить высокий уровень внимания при повторном ее рассмотрении. Исключением может быть случай, когда для построения блок-схем используется графический редактор и они формализованы настолько, что по ним автоматически генерируется текст на языке программирования (как например, это может делаться в Р-технологии [8.6]).

В качестве основного метода построения текста модуля современная технология программирования рекомендует пошаговую детализацию [8.1, 8.3, 8.5]. Сущность этого метода заключается в раз-

биении процесса разработки текста модуля на ряд шагов. На первом

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

Пошаговая детализация связана с использованием частично формализованного языка для представления указанных описаний, который получил название псевдокода [8.5, 8.8]. Этот язык позволяет использовать все конструкции структурного программирования, которые оформляются формализованно, вместе с неформальными фрагментами на естественном языке для представления обобщенных операторов и условий. В качестве обобщенных операторов и условий могут задаваться и соответствующие фрагменты на базовом языке программирования.

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

должно содержать:

                    начало модуля на базовом языке, т.е. первое предложение или заголовок (спецификацию) этого модуля [8.1];

                    раздел (совокупность) описаний на базовом языке, причем вместо описаний процедур и функций - только их внешнее оформление;

                    неформальное обозначение последовательности операторов тела модуля как одного обобщенного оператора (см. ниже), а также неформальное обозначение последовательности операторов тела каждого описания процедуры или функции как одного обобщенного оператора;

                    последнее предложение (конец) модуля на базовом языке [8.1].

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

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

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

 

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

ИСКЛЮЧЕНИЕ имя_исключения

обобщенный_оператор

ВСЕ ИСКЛЮЧЕНИЕ

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

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

 Идею пошаговой детализации приписывают иногда Дейкстре [8.1]. Однако Дейкстра предлагал принципиально отличающийся метод построения текста модуля [8.2], который нам представляется более глубоким и перспективным. Во-первых, вместе с уточнением операторов он предлагал постепенно (по шагам) уточнять (детализировать) и используемые структуры данных. Во-вторых, на каждом шаге он предлагал создавать некоторую виртуальную машину для детализации и в ее терминах производить детализацию всех уточняемых понятий, для которых эта машина позволяет это сделать. Таким образом, Дейкстра предлагал, по-существу, детализировать по горизонтальным слоям, что является перенесением его идеи о слоистых системах (см. лекцию 6) на уровень разработки модуля. Такой метод разработки модуля поддерживается в настоящее время пакетами языка АДА [8.7] и средствами объектно -ориентированного программирования [8.9].

Контроль программного модуля.

Применяются следующие методы контроля программного модуля:

                    статическая проверка текста модуля;

                    сквозное прослеживание;

                    доказательство свойств программного модуля.

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

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

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

37 - Алгоритм и оценка эффективности сортировки прямого выбора.

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

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

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

3. И так далее до предпоследнего элемента.

38 - Алгоритм и оценка эффективности сортировки прямого включения.

Одним из наиболее простых и естественных методов внутренней сортировки является сортировка с простыми включениями. Идея алгоритма очень проста. Пусть имеется массив ключей a[1], a[2], ..., a[n]. Для каждого элемента массива, начиная со второго, производится сравнение с элементами с меньшим индексом (элемент a[i] последовательно сравнивается с элементами a[i-1], a[i-2] ...) и до тех пор, пока для очередного элемента a[j] выполняется соотношение a[j] > a[i], a[i] и a[j] меняются местами. Если удается встретить такой элемент a[j], что a[j] <= a[i], или если достигнута нижняя граница массива, производится переход к обработке элемента a[i+1] (пока не будет достигнута верхняя граница массива). Легко видеть, что в лучшем случае (когда массив уже упорядочен) для выполнения алгоритма с массивом из n элементов потребуется n-1 сравнение и 0 пересылок. В худшем случай (когда массив упорядочен в обратном порядке) потребуется n?(n-1)/2 сравнений и столько же пересылок. Таким образом, можно оценивать сложность метода простых включений как O(n2).

Можно сократить число сравнений, применяемых в методе простых включений, если воспользоваться тем фактом, что при обработке элемента a[i] массива элемента a[1], a[2], ..., a[i-1] уже упорядочены, и воспользоваться для поиска элемента, с которым должна быть произведена перестановка, методом двоичного деления. В этом случае оценка числа требуемых сравнений становится O(n?log n).

Заметим, что поскольку при выполнении перестановки требуется сдвижка на один элемент нескольких элементов, то оценка числа пересылок остается O(n2).

Пример сортировки методом простого включения

Начальное состояние массива 8 23 5 65 44 33 1 6

Шаг 1 8 23 5 65 44 33 1 6

Шаг 2 8 5 23 65 44 33 1 6

5 8 23 65 44 33 1 6

Шаг 3 5 8 23 65 44 33 1 6

Шаг 4 5 8 23 44 65 33 1 6

Шаг 5 5 8 23 44 33 65 1 6

5 8 23 33 44 65 1 6

Шаг 6 5 8 23 33 44 1 65 6

5 8 23 33 1 44 65 6

5 8 23 1 33 44 65 6

5 8 1 23 33 44 65 6

5 1 8 23 33 44 65 6

1 5 8 23 33 44 65 6

Шаг 7 1 5 8 23 33 44 6 65

1 5 8 23 33 6 44 65

1 5 8 23 6 33 44 65

1 5 8 6 23 33 44 65

1 5 6 8 23 33 44 65

39 - Алгоритм и оценка эффективности бинарной сортировки.

Для ускорения числа сравнений при поиске места, в которое нужно вставить элемент X, можно использовать логарифмический поиск. Это означает, что сначала Х сравнивается с элементом k[j/2], затем, в зависимости от результата сравнения, с элементом, лежащим посередине между 1 и j/2 или посередине между j/2+1 и j и т.д. . При этом число сравнений для каждого j равно примерно log(j). Число пересылок эле ментов при этом не изменяется и остается примерно равным N¤/4.

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

Время работы алгоритма t примерно оценивается формулой:

t=a*N¤ + b*N + c*N*logN

где a,b,c - неизвестные константы, зависящие от программной реализации алгоритма.

40 - Алгоритм и оценка эффективности сортировки прямого обмена.

Простая обменная сортировка (в просторечии называемая "методом пузырька") для массива a[1], a[2], ..., a[n] работает следующим образом. Начиная с конца массива сравниваются два соседних элемента (a[n] и a[n-1]). Если выполняется условие a[n-1] > a[n], то значения элементов меняются местами. Процесс продолжается для a[n-1] и a[n-2] и т.д., пока не будет произведено сравнение a[2] и a[1]. Понятно, что после этого на месте a[1] окажется элемент массива с наименьшим значением. На втором шаге процесс повторяется, но последними сравниваются a[3] и a[2]. И так далее. На последнем шаге будут сравниваться только текущие значения a[n] и a[n-1]. Понятна аналогия с пузырьком, поскольку наименьшие элементы (самые "легкие") постепенно "всплывают" к верхней границе массива.

Пример сортировки методом пузырька

Начальное состояние массива 8 23 5 65 44 33 1 6

Шаг 1 8 23 5 65 44 33 1 6

8 23 5 65 44 1 33 6

8 23 5 65 1 44 33 6

8 23 5 1 65 44 33 6

8 23 1 5 65 44 33 6

8 1 23 5 65 44 33 6

1 8 23 5 65 44 33 6

Шаг 2 1 8 23 5 65 44 6 33

1 8 23 5 65 6 44 33

1 8 23 5 6 65 44 33

1 8 23 5 6 65 44 33

1 8 5 23 6 65 44 33

1 5 8 23 6 65 44 33

Шаг 3 1 5 8 23 6 65 33 44

1 5 8 23 6 33 65 44

1 5 8 23 6 33 65 44

1 5 8 6 23 33 65 44

1 5 6 8 23 33 65 44

Шаг 4 1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

Шаг 5 1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

Шаг 6 1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

Шаг 7 1 5 6 8 23 33 44 65

Для метода простой обменной сортировки требуется число сравнений n?(n-1)/2, минимальное число пересылок 0, а среднее и максимальное число пересылок - O(n2).

41 - Алгоритм и оценка эффективности шейкерной сортировки.

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

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

Пример шейкерной сортировки

Начальное состояние массива 8 23 5 65 44 33 1 6

Шаг 1 8 23 5 65 44 33 1 6

8 23 5 65 44 1 33 6

8 23 5 65 1 44 33 6

8 23 5 1 65 44 33 6

8 23 1 5 65 44 33 6

8 1 23 5 65 44 33 6

1 8 23 5 65 44 33 6

Шаг 2 1 8 23 5 65 44 33 6

1 8 5 23 65 44 33 6

1 8 5 23 65 44 33 6

1 8 5 23 44 65 33 6

1 8 5 23 44 33 65 6

1 8 5 23 44 33 6 65

Шаг 3 1 8 5 23 44 6 33 65

1 8 5 23 6 44 33 65

1 8 5 6 23 44 33 65

1 8 5 6 23 44 33 65

1 5 8 6 23 44 33 65

Шаг 4 1 5 6 8 23 44 33 65

1 5 6 8 23 44 33 65

1 5 6 8 23 44 33 65

1 5 6 8 23 33 44 65

Шаг 5 1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

1 5 6 8 23 33 44 65

Шейкерная сортировка позволяет сократить число сравнений (по оценке Кнута средним числом сравнений является (n2 - n?(const + ln n)), хотя порядком оценки по-прежнему остается n2. Число же пересылок, вообще говоря, не меняется. Шейкерную сортировку рекомендуется использовать в тех случаях, когда известно, что массив "почти упорядочен".

42 - Алгоритм и оценка эффективности сортировки Шелла.

Общий метод, который использует сортировку вставкой, применяет принцип уменьшения расстояния между сравниваемыми элементами. На рис.2 показана схема выполнения сортировки Шелла для мас сива "оасве".     Сначала сортируются все элементы, которые смещены  друг от друга на три позиции. Затем сортируются все элементы, которые смещены на две позиции. И, наконец, упорядочиваются все соседние элементы.

проход 1 f d a c b e

проход 2 c b a f d e

проход 3 a b c e d f

результат a b c d e f

{ сортировка Шелла }

procedure Shell(var item: DataArray; count:integer);

const t = 5; var

i, j, k, s, m: integer;

h: array[1..t] of integer;

x: DataItem;

begin

h[1]:=9; h[2]:=5; h[3]:=3; h[4]:=2; h[5]:=1;

for m := 1 to t do begin

k:=h[m];

s:=-k;

for i := k+1 to count do begin

x := item[i];

j := i-k;

if s=0 then begin

s := -k;

s := s+1;

item[s] := x;

end;

while (x<item[j]) and (j<count) do begin

item[j+k] := item[j];

j := j-k;

end;

item[j+k] := x;

end;

end;

end;

{ конец сортировки Шелла }

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

Расстояния между     сравниваемыми элементами могут изменяться  по-разному. Обязательным является лишь то, что последний шаг должен равняться единице. Например, хорошие результаты дает последовательность шагов 9, 5, 3, 2, 1, которая использована в показанном выше примере. Следует избегать последовательностей степени  двойки, которые, как показывают сложные математические выкладки, снижают эффективность алгоритма сортировки. /Однако, при исполь зовании таких последовательностей шагов между сравниваемыми элементами эта сортировка будет по-прежнему работать правильно/.

Внутренний цикл имеет два условия проверки. Условие  "х<item[j]" необходимо для упорядочения элементов. Условия "j>0" и "j<= count" необходимы для того, чтобы предотвратить выход    за  пределы массива "item". Эта дополнительная проверка в некоторой  степени ухудшает сортировку Шелла. Слегка измененные версии сор тировки Шелла используют специальные управляющие элементы, которые не являются в действительности частью той информации, которая  должна сортироваться. Управляющие элементы имеют граничные для   массива данных значения, т.е. наименьшее и наибольшее значения. В  этом случае не обязательно выполнять проверку на граничные значения. Однако, применение таких управляющих элементов требует специальных знаний о той информации, которая сортируется, и это сни жает универсальность процедуры сортировки.

Анализ сортировки Шелла требует решения некоторых сложных   математических задач,    которые выходят за рамки этой книги. Время  выполнения сортировки    Шелла пропорционально n**1.2. Эта зависимость значительно лучше квадратичной зависимости, которой подчиняются рассмотренные ранее алгоритмы сортировки. Однако, прежде   чем вы решите использовать сортировку Шелла, следует иметь в виду, что быстрая сортировка дает даже еще лучшие результаты.

43 - Алгоритм и оценка эффективности сортировки Фон Неймана.

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

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

Один из популярных алгоритмов внутренней сортировки со слияниями основан на следующих идеях (для простоты будем считать, что число элементов в массиве, как и в нашем примере, является степенью числа 2). Сначала поясним, что такое слияние. Пусть имеются два отсортированных в порядке возрастания массива p[1], p[2], ..., p[n] и q[1], q[2], ..., q[n] и имеется пустой массив r[1], r[2], ..., r[2?n], который мы хотим заполнить значениями массивов p и q в порядке возрастания. Для слияния выполняются следующие действия: сравниваются p[1] и q[1], и меньшее из значений записывается в r[1]. Предположим, что это значение p[1]. Тогда p[2] сравнивается с q[1] и меньшее из значений заносится в r[2]. Предположим, что это значение q[1]. Тогда на следующем шаге сравниваются значения p[2] и q[2] и т.д., пока мы не достигнем границ одного из массивов. Тогда остаток другого массива просто дописывается в "хвост" массива r.

Для сортировки со слиянием массива a[1], a[2], ..., a[n] заводится парный массив b[1], b[2], ..., b[n]. На первом шаге производится слияние a[1] и a[n] с размещением результата в b[1], b[2], слияние a[2] и a[n-1] с размещением результата в b[3], b[4], ..., слияние a[n/2] и a[n/2+1] с помещением результата в b[n-1], b[n]. На втором шаге производится слияние пар b[1], b[2] и b[n-1], b[n] с помещением результата в a[1], a[2], a[3], a[4], слияние пар b[3], b[4] и b[n-3], b[n-2] с помещением результата в a[5], a[6], a[7], a[8], ..., слияние пар b[n/2-1], b[n/2] и b[n/2+1], b[n/2+2] с помещением результата в a[n-3], a[n-2], a[n-1], a[n]. И т.д. На последнем шаге, например (в зависимости от значения n), производится слияние последовательностей элементов массива длиной n/2 a[1], a[2], ..., a[n/2] и a[n/2+1], a[n/2+2], ..., a[n] с помещением результата в b[1], b[2], ..., b[n].

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

Начальное состояние массива 8 23 5 65 44 33 1 6

Шаг 1 6 8 1 23 5 33 44 65

Шаг 2 6 8 44 65 1 5 23 33

Шаг 3 1 5 6 8 23 33 44 65

При применении сортировки со слиянием число сравнений ключей и число пересылок оценивается как O(n?log n). Но следует учитывать, что для выполнения алгоритма для сортировки массива размера n требуется 2?n элементов памяти.

44 - Алгоритм и оценка эффективности быстрой сортировки.

Метод сортировки разделением был предложен Чарльзом Хоаром (он любит называть себя Тони) в 1962 г. Этот метод является развитием метода простого обмена и настолько эффективен, что его стали называть "методом быстрой сортировки - Quicksort".

Основная идея алгоритма состоит в том, что случайным образом выбирается некоторый элемент массива x, после чего массив просматривается слева, пока не встретится элемент a[i] такой, что a[i] > x, а затем массив просматривается справа, пока не встретится элемент a[j] такой, что a[j] < x. Эти два элемента меняются местами, и процесс просмотра, сравнения и обмена продолжается, пока мы не дойдем до элемента x. В результате массив окажется разбитым на две части - левую, в которой значения ключей будут меньше x, и правую со значениями ключей, большими x. Далее процесс рекурсивно продолжается для левой и правой частей массива до тех пор, пока каждая часть не будет содержать в точности один элемент.

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

Алгоритм недаром называется быстрой сортировкой, поскольку для него оценкой числа сравнений и обменов является O(n?log n). На самом деле, в большинстве утилит, выполняющих сортировку массивов, используется именно этот алгоритм.

45 - Алгоритм и оценка эффективности пирамидальной сортировки.

Пирамидальная сортировка.

Пусть исходный файл, взятый для примера:

503 87 512 61 908 170 897 275 653 426 154 509 612 677 765 703

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

908

703 897

653 426 612 765

275 503 87 154 509 170 677 512

61

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

Структуру данных для представления пирамиды удобно выбрать в виде последовательного списка ключей, так что для каждого ключа K[i] его правый и левый сыновья находятся в позициях 2*i и 2*i+1 (ключи K[2*i] и K[2*i+1]). Например, вышеприведенная пирамида из 16 ключей отображается таким последовательным списком:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

908 703 897 653 426 612 765 275 509 87 154 509 170 677 512 61

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

897

703 765

653 426 612 677

275 503 87 154 509 170 61 512

Выходной массив можно формировать в конце исходного массива дан-ных.

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

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

Время работы алгоритма t примерно оценивается формулой:

t=a*N*logN + b*N

где a,b - неизвестные константы, зависящие от программной реализации алгоритма.

Достоинства и недостатки:

  • Достоинства

  • Имеет доказанную оценку худшего случая O(nlogn).

  • Требует всего O(1) дополнительной памяти

Недостатки:

  • Сложен в реализации.

  • Неустойчив — для обеспечения устойчивости нужно расширять ключ.

  • На почти отсортированных массивах работает столь же долго, как и на хаотических данных.

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

  • Сортировка слиянием при расходе памяти O(n) быстрее (O(n·logn) с меньшей константой) и не подвержена деградации на неудачных данных.

  • Из-за сложности алгоритма выигрыш получается только на больших n. На небольших n (до нескольких тысяч) быстрее сортировка Шелла.

Пирамидальная сортировка — алгоритм сортировки, работающий в худшем, в среднем и в лучшем случае (то есть гарантированно) за Θ(n log n) операций при сортировке n элементов. Количество применяемой служебной памяти не зависит от размера массива (то есть, O(1)).

46 - Алгоритм N-путевого слияния и алгоритм получения начальных отрезков с помощью выбора с замещением.

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

Наиболее часто внешняя сортировка используется в СУБД. Основным понятием при использовании внешней сортировки является понятие отрезка. Отрезком длины K является последовательность записей Ai, Ai + 1,…,Ai + k, что в ней все записи упорядочены по некоторому ключу. Максимальное количество отрезков в файле N (все элементы не упорядочены). Минимальное количество отрезков 1 (все элементы являются упорядоченными).

Например, в некотором файле А есть одномерный массив:

12 35 65 0 24 26 3 5 84 90 6 2 30

Поделим массив на отрезки:

12 35 65 | 0 24 36 | 3 5 84 90 | 6 | 2 30

Можно сказать, что массив в файле А состоит из 5 отрезков. Например, в некотором файле B есть одномерный массив:

1 2 3 4 5 6 7 8 9 10

Поделим массив на отрезки:

| 1 2 3 4 5 6 7 8 9 10 |

Можно сказать, что массив в файле B состоит из 1 отрезка. Например, в некотором файле А есть одномерный массив:

20 17 16 14 13 10 9 8 6 4 3 2 0

Поделим массив на отрезки:

| 20 | 17 | 16 | 14 | 13 | 10 | 9 | 8 | 6 | 4 | 3 | 2 | 0 |

Можно сказать, что массив в файле А состоит из 13 отрезков.

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

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

Основные методы сортировок:

Естественная сортировка (метод естественного слияния)

Сортировка методом двух путевого сбалансированного слияния

Сортировка методом n-путевого слияния.

Многофазная сортировка (Фибоначчиевая)

Получение начальных серий посредством выбора с замещением. Технология выбора с замещением может использоваться на первой фазе внешней сортировки, если фактически вьшолнить Р-путевое слияние входных данных с самими собой. В этом случае Р выбирается достаточно большим, чтобы заполнить, по существу, всю внутреннюю память. Каждая запись при вьшоде замещается очередной записью из исходных данных. Если ключ новой записи меньше ключа выведенной записи, то мы не добавляем ее в текущую серию; в противном случае мы обычным образом включаем ее в дерево выбора, так что она образует часть серии, порождаемой в данный момент. Таким образом, каждая серия может содержать больше Р записей, хотя в любой момент в дереве выбора находится не более Р записей. В табл. 1 показан этот процесс для Р - i; числа, заключенные в скобки, ожидают включения в следующую серию.

47 - Алгоритм линейного поиска и прямого поиска строки.

ЛИНЕЙНЫЙ ПОИСК

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

Элемент найден, т. е. ai = х.

Весь массив просмотрен и совпадения не обнаружено.

Это дает нам линейный алгоритм:

Следует обратить внимание, что если элемент найден, то он найден вместе с минимально возможным индексом, т. е. это первый из таких элементов. Равенство i=N свидетельствует, что совпадения не существует.

Очевидно, что окончание цикла гарантировано, поскольку на каждом шаге значение i увеличивается, и, следовательно, оно достигнет за конечное число шагов предела N; фактически же, если совпадения не было, это произойдет после N шагов.

ПРЯМОЙ ПОИСК

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

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