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

Учебное пособие 1295

.pdf
Скачиваний:
4
Добавлен:
30.04.2022
Размер:
949.61 Кб
Скачать

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

Третий, основной тип алгоритмов, предназначен не для поиска ответа на поставленную задачу, а для моделирования физических систем с использованием ЭВМ.

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

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

61

жать повторного рассмотрения вариантов. Кроме применения ограничения сверху на генерируемые числа наименее эффективным является метод линейного поиска. По этому методу поиск начинается с одного конца таблицы и последовательно производится по всем строкам таблицы. Последовательность пробных вариантов получается с помощью увеличения или уменьшения на единицу номера текущей строки. Если поиск осуществляется не случайным, а регулярным способом, для таблицы из n строк в худшем случае требуется n проб. В среднем число проб при регулярном способе перебора равно (n+1)/2. Если строки в таблице упорядочены, в худшем случае понадобится число проб, равное log2n+l.

3.2. Сложность алгоритмов

Зависимость времени работы программы от объема обрабатываемых данных определяется оценкой сложности алгоритма.

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

Время работы алгоритма обработки каких-либо массивов данных зависит от размеров этих массивов. Время работы алгоритма, выполняющего только операции чтения и занесения данных в оперативную память, определяется формулой an+b, где a — время, необходимое для чтения и занесения в память одной информационной единицы, и b время, затрачиваемое на выполнение вспомогательных функций. Поскольку эта формула выражает линейную зависимость от n, сложность соответствующего алгоритма называют линейной.

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

62

меньший элемент оставшегося списка и производится его обмен со вторым элементом и т. д. При выполнении такого алгоритма производится n1 сравнений при определении первого наименьшего, n—2 сравнений при определении второго и т. д. Общий объем сравнений для такого алгоритма вычисляется по формуле

(n-1)+(n-2)+(n-3)+…+4+3+2+1=(n2-n)/2.

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

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

for k:=1 to (n–1) do min:=A{k); loc:= k;

for i:=(k+1) to n do if min>A(i) then min:= A(i); loc:=i;

A(loc):=A(k);

A(k):=min.

Внешний цикл выполняется n1 раз. Внутренний цикл срабатывает в среднем n/2 раз для каждого вхождения во внешний цикл. Общее число обращений к внутреннему циклу равно (n2-n)/2. При больших значениях n за оценку этого выражения принимается n2.

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

63

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

Формально сложность алгоритма определяется как порядок функции, выражающей время его работы. Функции f(n) и g(n) одного порядка, если для больших n существует константа k, такая, что f(n)/g(n) k. Это записывается следующим образом: f(n)=O(g(n)). Алгоритм чтения и занесения в память набора данных имеет оценку О (n). Алгоритм двоичного поиска в таблице с упорядоченными элементами оценивается как О (log2n). Простая обменная сортировка имеет оценку О(n2), а умножение матриц - О (n3).

Когда говорят, что алгоритм поиска имеет сложность О(log2n) или что сложность сортировки равна О(n2), под этим подразумевается, что элемент данных будет найден за время, в худшем случае пропорциональное log2n, или что сортировка информации будет выполнена в самом лучшем случае за время, пропорциональное n2. В описанной процедуре сортировки оценка О(n2) является как минимальной, так и максимальной. Сложность решения задачи можно уменьшить, если найти более выгодный алгоритм.

3.3. Способы реализации алгоритмов

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

64

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

ний [2].

Итерация и рекурсия

Повторение является основной управляющей конструкцией обработки данных, за исключением случаев, когда входные данные состоят из одного элемента, который преобразуется в один выходной элемент (например, в случае системы обработки запросов или при обновлении записей в режиме on line). Существуют две основные формы повторений: итерация и рекурсия. Итерация в основном используется для тех видов обработки, которые лучше всего определяются выражением типа «выполнить для всех х», а рекурсия - для получения результирующих данных, которые легче всего описать рекурсивно, т. е. задать выражением вида «выполнить то же, что и в последний раз». Текущее действие определяется с помощью предыдущего ответа или предыдущих стадий вычислений. В действительности итерация и рекурсия взаимозаменяемы. Рекурсивные процедуры могут быть переписаны в итерационном ви-

65

де в языках, в которых рекурсия невозможна (например, в Фортране) и наоборот (например, в Лиспе).

Классическим примером рекурсии может служить определение факториала в следующем виде:

1 n!

n(n 1)!

для

n 0,

 

(1)

для

n 0.

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

n!=l*2*3*...*(n—l)*n. (2)

Рекурсивная процедура, построенная на математической зависимости (1):

procedure факториал (n); if n = 0 then return(l)

else return (n* факториал (n - 1)) end.

Следующая процедура реализует соотношение (2) и основана на итерационном алгоритме:

procedure факториал (n); f:=1; k:=n;

while k > 0 do f :=f * k; k :=k- 1;

return(f)

end.

66

В итерационной процедуре используется переменная k для подсчета числа возвратов от n до 0. В рекурсивных процедурах для той же цели предназначен аргумент функции. Выполнение операции умножения начинается со старших чисел в итерационной процедуре и с младших - в рекурcивной. Условия окончания выполнения обеих процедур аналогичны друг другу.

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

procedure список;

while not конец файла do читать (число); печатать (число) end.

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

procedure список; if not конец файла читать (число)

печатать (число); список

end.

Условие окончания обеих процедур одно и то же.

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

67

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

Параллельная обработка

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

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

procedure инициализация (А); for i:=n downto 1 do

A(i):=1

68

end.

procedure инициализация (А); for i:=1 to n do

A(i):=1

end.

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

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

Сопрограммы

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

В многопроцессорных системах сопрограммы могут в действительности выполняться параллельно. Это возможно в ограниченных пределах в большинстве ЭВМ. Центральный

69

процессор инициализирует функционирование процессоров, предназначенных для обмена, после чего продолжает работать одновременно с ними. Сигналы, передаваемые между центральным процессором и другими процессорами, служат для координации работы системы. Это характерно для задачи типа «поставщик— потребитель», которая является классическим примером взаимодействия сопрограмм. Один процесс (Р) поставляет некоторый продукт, другой (С) этот продукт использует. Р может выпустить столько единиц продукта, сколько может разместиться в общей области хранения. Если же область хранения полна, то Р должен ждать С. С может использовать единицы продукта только в том случае, если они есть в области хранения. Если же она пуста, то С должен ждать Р. Процесс начинается с того, что Р производит первую единицу продукта, а заканчивается, когда или С получил достаточное для него количество продукта, или у Р кончился материал, нужный для производства продукта, или, наконец, когда Р извещает С о прекращении работы.

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

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

70