3.3. Язык логического программирования Пролог Основные синтаксические конструкции: 3.4. Модальная логика 3.5. Нечеткая логика 3.6. Темпоральная логика Задав вход массовой проблемы, мы образуем индивидуальную задачу. Таким образом массовую проблему можно рассматривать как множество индивидуальных задач. Массовая проблема характеризуется: 1) Точным описанием допустимых значений параметров (множество допустимых входов). 2) Заданием условий, которым должен удовлетворять ответ. Пример задачи. 1. Определить наибольший общий делитель чисел X и Y. Вход: натуральные числа X и Y. Ответ: максимальное натуральное число Z, такое, что X%Z=0 и Y%Z=0. (Под операцией % понимается остаток от деления). Для решения каждой задачи требуются определенные вычислительные ресурсы. Величина, которая численно характеризует объем вычислительных ресурсов, минимально необходимых для решения задачи, называется сложностью задачи. Важно, что сложность задачи может зависеть от входа и в общем случае является функцией некоторых характеристик входа. Неформальное определение алгоритма: алгоритм решения массовой проблемы П - это пошаговая процедура, которая за конечное время решает любую из индивидуальных задач I принадлежащих П. Алгоритм должен обладать следующими свойствами: Описание алгоритма конечно, при этом предполагается, что существует объект, понимающий и выполняющий это описание. Такой объект назовем вычислителем. 1) Должны быть четко указаны условия остановки процесса и что следует считать результатом процесса. (Свойство результативности) 2) Алгоритм решения массовой проблемы П должен быть способен решить любую из индивидуальных задач I принадлежащих П, потратив на решение индивидуальной задачи конечное время. (Свойство называется массовостью.) 4. Алгоритмы и вычислимость 3) Процесс решения одной и той же индивидуальной задачи 4.1. Задачи и алгоритмы Задача (массовая проблема) - некоторый общий вопрос, на который протекает одинаково. (Свойство называется детерминированностью.) Не для каждого алгоритма очевидно, что он приводит в должен быть дан ответ. Как правило задача имеет 1 или несколько параметров. правильному решению поставленной задачи. Если правильность Фиксированное значение параметров образует исходные данные задачи, алгоритма не очевидна, то алгоритм нуждается в доказательстве. Данное выше определение алгоритма достаточно для большинства которые будем называть входом. применений, но не для всех, ибо существуют такие задачи, для которых построение алгоритма невозможно. Если задача поставлена, то удовлетворительными можно считать два результата ее решения: Если в программе решение проставленной задачи или доказательство того факта, что останавливается. решение задачи невозможно. Для такого доказательства не годится неформальное определение алгоритма. Не является удовлетворительным и сведение понятия алгоритма до какого-нибудь из известных языков программирования по причине их сложности. Понятие алгоритма связано: во-первых с массовой проблемой, которую он призван решать, во-вторых с вычислителем, который будет этот алгоритм выполнять. Различные подходы к формализации понятия алгоритма формализуют как понятие задачи, так и понятие вычислителя. 4.2. Машина Тьюринга Первый подход к формализации алгоритма связан с формализацией понятия вычислителя, т. е. к построению очень простого, но функционально полного вычислителя. Таким вычислителем является машина Тьюринга. Неформальное описание. Машина Тьюринга состоит из: - бесконечной в обе стороны ленты; - вычислительной головки. В каждый момент времени (такт) головка находится в некотором состоянии и обозревает одну позицию на ленте, в которой находится символ (обозреваемый символ). Головка может выполнить одно из четырех действий: 1) Записать в обозреваемую позицию на ленте новый символ. 2) Переместиться на одну позицию влево по ленте. 3) Переместиться на одну позицию вправо по ленте. 4) Больше ничего не делать. Действие головки однозначно определяется парой: (<текущее состояние головки> <обозреваемый символ>) и задается программой. текущая пара не существует, то головка Функция называется вычислимой по Тьюрингу, если существует такая машина Тьюринга, которая вычисляет ее значения для тех наборов значений аргументов, для которых функция определена, и работающая вечно, если для данного набора значений аргументов функция не определена. Условимся значения x1, x2 ,…,xn аргументов располагать на ленте в виде слова: Переработка слова начинается из стандартного положения q1, когда обозревается крайняя правая единица слова. Если функция f определена на данном наборе аргументов, то на ленте в результате останется f(x1,…,xn) записанных подряд единиц, иначе – машина работает бесконечно. При эти условиях будем говорить, что машина Тьюринга вычисляет данную функцию. Композиция машин Тьюринга. Пусть заданы Для каких функций возможно их вычисление с помощью машины Тьюринга? Многочисленные исследования показали, что каждая функция, для вычисления значения которой существует какой-нибудь алгоритм, оказалась вычислимой посредством некоторой машины Тьюринга, что дало повод выдвинуть следующую гипотезу. Тезис Тьюринга (основная гипотеза теории алгоритмов): оператора примитивной рекурсии, если для любых x1, …, xn, y справедливы равенства: f(x1, …, xn, 0)= g(x1, …, xn), f(x1, …, xn, y+1)= g(x1, …, xn,y, f(x1, …, xn, y)). Функция называется примитивно рекурсивной, если она может быть получена из простейших функций (определенных выше) при 4.3. Рекурсивные функции помощи конечного числа применений операторов суперпозиции и Рекурсивные функции – другой способ уточнения понятия примитивной рекурсии. алгоритма. 3) Оператор минимизации. Говорят, что С каждым алгоритмом однозначно связана функция, которую он вычисляет (т.е. сопоставляет допустимым исходным данным результат). Для всякой ли функции существует вычисляющий ее алгоритм? Как описать такие алгоритмически вычислимые функции? Исследование этих и других вопросов привело к созданию теории рекурсивных функций. Дадим описание класса вычислимых (рекурсивных) функций. Ограничимся рассмотрением функций, которые вместе со своими аргументами принимают значения в множестве всех целых неотрицательных чисел. Функции возьмем частичные, т.е., определенные не для всех значений аргументов. Сначала объявим простейшие (базисные) функции: 1) O(x) = 0 (одноместная нуль-функция, которая при любом значении x принимает значение 0); Очевидно, что каждый из трех введенных операторов сохраняет 2) S(x) = x+1 (одноместная функция следования, принимающая на свойство вычислимости функций. числе x значение x+1); Функция называется частично рекурсивной, если она может быть 3) (n-местная функция-проектор, получена из простейших функций при помощи конечного числа принимающая на наборе чисел (x1, …, xn) значение xm, 1 ≤ m ≤ n; n = 1, применений операторов суперпозиции, примитивной рекурсии и 2,…). минимизации. Очевидно, что базисные функции вычислимы (правильно Если функция всюду определена и частично рекурсивна, то она вычислимы по Тьюрингу). называется общерекурсивной. Далее на множестве всех рассматриваемых функций введем Из данного определения и приведенных выше замечаний о следующие операторы. вычислимости базисных функций и о сохранении вычислимости 1) Оператор суперпозиции. Говорят, что частичная функция f(x1, операторами суперпозиции, примитивной рекурсии и минимизации …, xn) получена из частичных функций g(y1, …, ym), h1(x1, …, xn), …, следует, что всякая частично рекурсивная функция является hm(x1, …, xn) с помощью оператора суперпозиции, ели для всех x1, …, xn вычислимой. справедливо равенство: f(x1, …, xn) = g(h1(x1, …, xn), …, hm(x1, …, xn)). Вместе с обратным предложение (гипотеза) называется как 2) Оператор примитивной рекурсии. Говорят, что функция f(x1, …, Тезис Черча. Любая функция тогда и только тогда вычислима, xn, xn+1) получена из функций g(x1, …, xn) и h(y1, …, yn+2) с помощью когда она частично рекурсивна. Иными словами, любой алгоритм над данными в цифровой форме может быть построен как последовательность операций суперпозиции, примитивной рекурсии и минимизации над базисными функциями от исходных данных. Исторически именно это предложение было первым точным математическим определением понятия алгоритма. Как и тезис Тьюринга, эта гипотеза не может быть доказана строго математически, она подтверждается практикой, опытом. После введения определений алгоритма в виде машины Тьюринга и нормального алгоритма Маркова (см. далее) была доказана равносильность всех трех этих определений. 4.4. Нормальные алгоритмы Маркова Мы будем называть алфавитом всякое конечное множество символов (знаков, букв, цифр и т.п.). Все символы в алфавите будем называть его буквами. Словом в алфавите называется всякий набор букв этого алфавита. Алгоритм, для которого слова в алфавите A служат исходными данными и результатами работы, называется алгоритмом в алфавите A. Конкатенацией (соединением) слов P=a1a2…an и Q=b1b2…bm называется слово PQ = a1a2…anb1b2…bm. Эта операция ассоциативна: слова P(QS) и (PQ)S совпадают и записываются как PQS. К словам в любом алфавите мы относим и так называемое пустое слово, обозначаемое Λ и обладающее следующим свойством: ΛP = PΛ = P для любого слова P. Если R=PQS, то говорят, что слово Q входит в слово R и данное вхождение Q в R называют левым, если не существует слов P´ и S´ таких, что R = P´QS´ и длина P´ меньше длины P . Другими словами, это первое вхождение слова Q в слово R. Если P и Q - слова в алфавите A и символы → и · не входят в A, то слова P→Q и P→·Q называются формулами Марковской подстановки в алфавите A. Заметим, что здесь каждое из слов P и Q может быть пустым словом. Формула подстановки P→Q называется простой, формула P→·Q заключительной. Пусть P→(·)Q обозначает любую из формул подстановки P→Q или P→·Q. Конечная последовательность формул подстановки в алфавите A P1→(·)Q1, P2→(·)Q2, …, Pr→(·)Qr называется схемой в алфавите A. Она определяет следующий алгоритм A переработки любого слова R в алфавите A: Пусть R0 = R. Шаг i алгоритма A (i = 0, 1, 2, …). Если ни одно из слов P1, P2, …, Pr не входит в Ri, то работа алгоритма заканчивается и Ri будет его результатом A(R); в противном случае находим в схеме первую (с наименьшим номером m) формулу подстановки Pm→(·)Qm такую, что Pm входит в Ri, и подставляем Qm вместо левого вхождения Pm в Ri; результат такой подстановки обозначаем Ri+1 (в этом случае пишем A: Ri→(·)Ri+1 и читаем «A переводит Ri в Ri+1». Если формула подстановки Pm→(·)Qm заключительная, то работа алгоритма заканчивается и его результатом A(R) является Ri+1; в противном случае переходим к шагу i+1. Возможно, что описанный процесс никогда не закончится. В таком случае мы говорим, что алгоритм A неприменим к слову R и его результат A(R) неопределен. Алгоритм A, определенный таким образом, называется нормальным алгоритмом Маркова в алфавите A, а определяющая его схема - схемой алгоритма A. Пример1. Пусть А={a, b} – алфавит, а схема нормального алгоритма следующая: Применим алгоритм к слову aabab: Пример2. Пусть A = {+, 1} и алгоритм A имеет схему: 1+1→ 11, + → +, 1→·1. Тогда, например, A: 111+1+11→1111+11→ 111111→·111111. Данный алгоритм неприменим к словам в алфавите А, которые или начинаются с буквы +, или заканчиваются этой буквой, или содержат две рядом стоящие такие буквы, а в остальных словах в алфавите А стирает все буквы +, т.е. осуществляет сложение натуральных чисел в представлении числа n словом из n единиц. Функция f, заданная на некотором множестве слов алфавита А, называется нормально вычислимой, если найдется расширение В алфавита А и такой нормальный алгоритм в В, что каждое слово Q в алфавите А из области определения функции f этот алгоритм перерабатывает в слово f(Q). Гипотеза А.А. Маркова (принцип нормализации Маркова): для вычисления функции, заданной в некотором алфавите, существует алгоритм тогда и только тогда, когда функция нормально вычислима. Другими словами: всякий алгоритм эквивалентен некоторому нормальному алгоритму Маркова. Это утверждение нельзя доказать, и оно не является теоремой. Скорее, это есть точное определение (одно из) понятия алгоритма, а именно: алгоритмом называется то и только то, что является нормальным алгоритмом Маркова, т.е. может быть определено схемой в некотором алфавите. Понятия нормально вычислимой функции, вычислимой по Тьюрингу и частично рекурсивной равносильны: 4.5. Алгоритмически неразрешимые проблемы Алгоритмическая проблема (массовая проблема) – проблема, в которой требуется найти единый алгоритм решения серии однотипных единичных задач. Проблема называется алгоритмически не разрешимой, если не существует алгоритма ее решения. Под алгоритмом при этом, естественно, подразумевается алгоритм в его точном определении нормальный алгоритм Маркова, машина Тьюринга и т.п. Нумерация алгоритмов. Множество всех алгоритмов счетно (т.к. алгоритм можно задать конечным описанием – словом, а множество всех конечных слов в конечном алфавите счетно), как и множество натуральных чисел, т.е. существует взаимно-однозначное соответствие между этими двумя множествами. Нумерацией алгоритмов назовем функцию, которая каждому алгоритму А ставит в соответствие число n – номер алгоритма А. Нумерация всех алгоритмов есть и нумерация всех вычислимых функций, если считать ее номером номер некоторого алгоритма, который ее вычисляет. Нумерация машин Тьюринга. Проблема распознавания самоприменимых машин Тьюринга алгоритмически неразрешима. На основании этой теоремы устанавливается и другая алгоритмически не разрешимая проблема: Проблема распознавания применимых машины Тьюринга алгоритмически неразрешима. Другие алгоритмически неразрешимые проблемы: Проблема остановки: не существует алгоритма, который по номеру x любого алгоритма и исходным данным y определял бы, остановится алгоритм при этих данных, или нет. Проблема определения общерекурсивности алгоритма, т.е. проблема определения того, ко всяким ли допустимым начальным данным применим алгоритм, неразрешима. Десятая проблема Гильберта – нахождения алгоритма, определяющего, имеет ли уравнение вида F(x,y,z)=0, где F(x,y,z)многочлен с целыми показателями степеней и целыми коэффициентами, целочисленное решение, алгоритмически неразрешима. Проблема эквивалентности алгоритмов – определить по двум произвольным заданным алгоритмам (например, по двум машинам Тьюринга), будут ли они выдавать одинаковые выходные результаты на любых исходных данных, неразрешима. И др. 5. Анализ алгоритмов 5.1. Сравнительные оценки алгоритмов Очевидно, что для решения некоторой массовой проблемы можно придумать не один алгоритм. Поэтому важно иметь критерии, позволяющие оценивать разработанные алгоритмы. Время обычно интересует в наибольшей степени, поэтому именно на оценке временной сложности алгоритмов мы остановимся подробнее. Временная сложность. Время решения индивидуальной задачи зависит от скорости вычислителя, языка программирования и многого другого. Определяя качество алгоритма имеет смысл не учитывать эти факторы. Вместо времени мы будем оценивать количество характерных операций, которое необходимо выполнить, решая индивидуальную задачу данным алгоритмом. Рассмотрим конкретную проблему (индивидуальную задачу), заданную N словами памяти. Под трудоемкостью алгоритма для данного конкретного входа – Fa(N), будем понимать количество «элементарных» операций совершаемых алгоритмом для решения конкретной проблемы в данной формальной системе. При более детальном анализе трудоемкости алгоритма оказывается, что не всегда количество элементарных операций, выполняемых алгоритмом на одном входе длины N, совпадает с количеством операций на другом входе такой же длины. Это приводит к необходимости введения специальных обозначений, отражающих поведение функции трудоемкости данного алгоритма на входных данных фиксированной длины. Пусть DА – множество индивидуальных задач (конкретных проблем) данной массовой проблемы. Пусть DDА – задание индивидуальной задач и |D| = N. Пусть DNDА – множество всех индивидуальных задач, имеющих мощность N, т.е. DN = {D DN : |D| = N}. Тогда данный алгоритм, решая различные задачи размерности N, будет выполнять в каком-то случае наибольшее количество операций, а в каком-то случае наименьшее количество операций. Можно ввести следующие обозначения (функции оценки трудоемкости): 1. Fa(N) – худший случай – наибольшее количество операций, совершаемых алгоритмом А для решения индивидуальных задач размерностью N: Fa(N) = max {Fa (D)} – худший случай на DN DDN Временной сложностью в наихудшем случае (верхней оценкой сложности алгоритма) называют функцию T(N), равную максимальному времени выполнения алгоритма для всех индивидуальных задач размером N. Часто бывает полезно определить не верхнюю оценку сложности, а среднюю, хотя это, как правило, сложнее. Точную верхнюю оценку сложности получить тоже не всегда просто. В таком случае ограничимся определением порядка роста верхней оценки временной сложности. Функция f(n) имеет порядок роста функции g(n), если найдется такая константа C, что для любых n выполняется неравенство: f(n) < C*g(n) 2. Fa(N) – лучший случай – наименьшее количество операций, совершаемых алгоритмом А для решения индивидуальных задач размерностью N: Fa(N) = min {Fa (D)} – лучший случай на DN DDN 3. Fa(N) – средний случай – среднее количество операций, совершаемых алгоритмом А для решения индивидуальных задач размерностью N: Fa(N) = (1 / |DN|)* {Fa (D)} – средний случай на DN DDN Емкостная сложность. Примерами алгоритмов с параметрически-зависимой трудоемкостью являются алгоритмы вычисления стандартных функций с заданной точностью путем вычисления соответствующих степенных рядов. Очевидно, что такие алгоритмы, имея на входе два числовых значения – аргумент функции и точность выполняют существенно зависящее от значений количество операций. - Количественно-параметрические по трудоемкости алгоритмы Однако в большинстве практических случаев функция трудоемкости зависит как от количества данных на входе, так и от значений входных данных, в этом случае: Fa (D) = Fa (|D|, P1,…,Pm) = Fa (N, P1,…,Pm) Тогда: В качестве примера можно привести алгоритмы численных методов, в которых параметрически-зависимый внешний цикл по точности включает в себя количественно-зависимый фрагмент по размерности. - Порядково-зависимые по трудоемкости алгоритмы Среди разнообразия параметрически-зависимых алгоритмов выделим еще одну группу, для которой количество операций зависит от 5.2. Классификация алгоритмов по виду функции порядка расположения исходных объектов. трудоёмкости Пусть множество D состоит из элементов (d1,…,dn), и |D|=N. В зависимости от влияния исходных данных на функцию Определим Dp = {(d1,…,dn)}-множество всех упорядоченных N-ок трудоемкости алгоритма может быть предложена следующая классификация, имеющая практическое значение для анализа из d1,…,dn, отметим, что |Dp|=n!. Если Fa (iDp) Fa (jDp), где jDp Dp, то алгоритм будем называть алгоритмов: порядково-зависимым по трудоемкости. - Количественно-зависимые по трудоемкости алгоритмы Примерами таких алгоритмов могут служить ряд алгоритмов сортировки, Это алгоритмы, функция трудоемкости которых зависит только от алгоритмы поиска минимума и максимума в массиве. размерности конкретного входа, и не зависит от конкретных значений: Fa (D) = Fa (|D|) = Fa (N) 5.3. Трудоемкость основных алгоритмических Примерами алгоритмов с количественно-зависимой функцией трудоемкости могут служить алгоритмы для стандартных операций с конструкций Для получения функции трудоемкости алгоритма, уточним понятия массивами и матрицами – умножение матриц, умножение матрицы на «элементарных» операций, соотнесенных с языком высокого уровня. вектор и т.д. В качестве таких «элементарных» операций предлагается - Параметрически-зависимые по трудоемкости алгоритмы Это алгоритмы, трудоемкость которых определяется не использовать следующие: 1) Простое присваивание: а:= b; размерностью входа (как правило, для этой группы размерность входа 2) Одномерная индексация a[i] : (адрес (a)+i*длина элемента); обычно фиксирована), а конкретными значениями обрабатываемых 3) Арифметические операции: (*, /, -, +); слов памяти: 4) Операции сравнения: a < b; Fa (D) = Fa (d1,…,dn) = Fa (P1,…,Pm), m n 5) Логические операции: (l1)or (l2), (l1)and (l2), not(l1). После введения элементарных операций анализ трудоемкости основных алгоритмических конструкций в общем виде сводится к следующим положениям: 1) Конструкция «Следование» f1 Трудоемкость конструкции есть сумма трудоемкостей блоков, следующих друг за другом. f2 F «следование» = f1+ … +fk, где k – количество блоков. B) Конструкция «Ветвление» if (l1 ) then fthen с вероятностью p else felse с вероятностью (1-p) Общая трудоемкость конструкции «Ветвление» требует анализа вероятности выполнения переходов на блоки «Then» и «Else» и определяется как: F «ветвление» = fthen *p + felse * (1-p). C) Конструкция «Цикл» i:=1 for i := 1 to N f i := i+1 if i N end После сведения конструкции к элементарным операциям ее трудоемкость определяется как: F «цикл» = 1+N*(3+f«тела цикла») Примеры анализа простых алгоритмов Пример 1 Задача суммирования элементов квадратной матрицы SumM (A, n; Sum) Sum := 0 For i := 1 to n For j := 1 to n Sum := Sum + A[i,j] end for Return (Sum) End Алгоритм выполняет одинаковое количество операций при фиксированном значении n, и следовательно является количественно-зависимым. Применение методики анализа конструкции «Цикл » дает: FA(n)= 1+1+ n *(3+1+ n *(3+4))=7 n2+4* n +2 = (n2), заметим, что под n понимается линейная размерность матрицы, в то время как на вход алгоритма подается n 2 значений. Пример 2 Задача поиска максимума в массиве MaxS (S,n; Max) Max := S[1] For i := 2 to n if Max < S[i] then Max := S[i] end for return Max End 1+(1+3n) Данный алгоритм является количественно-параметрическим, поэтому для фиксированной размерности исходных данных необходимо проводить анализ для худшего, лучшего и среднего случая. 1). Худший случай Максимальное количество переприсваиваний максимума (на каждом проходе цикла) будет в том случае, если элементы массива отсортированы по возрастанию. Трудоемкость алгоритма в этом случае равна: FA^(n)=1+1+1+ (n-1) (3+2+2)=7n - 4 = (n). 2) Лучший случай Минимальное количество переприсваивания максимума (ни одного на каждом проходе цикла) будет в том случае, если максимальный элемент расположен на первом месте в массиве. Трудоемкость алгоритма в этом случае равна: FA(n)=1+1+1+ (n-1) (3+2)=5n - 2 = (n). В) Средний случай Алгоритм поиска максимума последовательно перебирает элементы массива, сравнивая текущий элемент массива с текущим значением максимума. На очередном шаге, когда просматривается к-ый элемент массива, переприсваивание максимума произойдет, если в подмассиве из первых к элементов максимальным элементом является последний. Очевидно, что в случае равномерного распределения исходных данных, вероятность того, что максимальный из к элементов расположен в определенной (последней) позиции равна 1/к. Тогда в массиве из n элементов общее количество операций переприсваивания максимума определяется как: N 1 / i Hn Ln( N ) , γ 0,57 i 1 Величина Hn называется n-ым гармоническим числом. Таким образом, точное значение (математическое ожидание) среднего количества операций присваивания в алгоритме поиска максимума в массиве из n элементов определяется величиной Hn (на бесконечности количества испытаний), тогда: FA(n)=1 + (n-1) (3+2) + 2 (Ln(n) + )=5 n +2 Ln(n) - 4 +2 = (n). 5.4. Переход к временным оценкам Сравнение двух алгоритмов по их функции трудоемкости вносит некоторую ошибку в получаемые результаты. Основной причиной этой ошибки является различная частотная встречаемость элементарных операций, порождаемая разными алгоритмами и различие во временах выполнения элементарных операций на реальном процессоре. Таким образом, возникает задача перехода от функции трудоемкости к оценке времени работы алгоритма на конкретном процессоре: Дано: FA(DA) - трудоёмкость алгоритма требуется определить время работы программной реализации алгоритма – TA(DA). На пути построения временных оценок мы сталкиваемся с целым набором различных проблем. Укажем основные: неадекватность формальной системы записи алгоритма и реальной системы команд процессора; наличие архитектурных особенностей существенно влияющих на наблюдаемое время выполнения программы, таких как конвейер, кеширование памяти, предвыборка команд и данных, и т.д.; различные времена выполнения реальных машинных команд; различие во времени выполнения одной команды, в зависимости от значений операндов различные времена реального выполнения однородных команд в зависимости от типов данных; неоднозначности компиляции исходного текста, обусловленные как самим компилятором, так и его настройками. Попытки различного подхода к учету этих факторов привели к появлению разнообразных методик перехода к временным оценкам. 1) Пооперационный анализ Идея пооперационного анализа состоит в получении пооперационной функции трудоемкости для каждой из используемых алгоритмом элементарных операций с учетом типов данных. Следующим шагом является экспериментальное определение среднего времени выполнения данной элементарной операции на конкретной вычислительной машине. Ожидаемое время выполнения рассчитывается как сумма произведений пооперационной трудоемкости на средние времена операций: TA(N) = Faопi(N) * t опi 2) Метод Гиббсона Метод предполагает проведение совокупного анализа по трудоемкости и переход к временным оценкам на основе принадлежности решаемой задачи к одному из следующих типов: задачи научно-технического характера с преобладанием операций с операндами действительного типа; задачи дискретной математики с преобладанием операций с операндами целого типа задачи баз данных с преобладанием операций с операндами строкового типа Далее на основе анализа множества реальных программ для решения соответствующих типов задач определяется частотная встречаемость операций, создаются соответствующие тестовые программы, и определяется среднее время на операцию в данном типе задач –t тип задачи. На основе полученной информации оценивается общее время работы алгоритма в виде: TA(N) = FA(N) *t тип задачи 3) Метод прямого определения среднего времени В этом методе так же проводится совокупный анализ по трудоемкости – определяется FA(N), после чего на основе прямого эксперимента для различных значений Nэ определяется среднее время работы данной программы Tэ и на основе известной функции трудоемкости рассчитывается среднее время на обобщенную элементарную операцию, порождаемое данным алгоритмом, компилятором и компьютером – tа. Эти данные могут быть (в предположении об устойчивости среднего времени по N) интерполированы или экстраполированы на другие значения размерности задачи следующим образом: tа= Tэ(Nэ) / FA(Nэ), T(N) = tа * FA(N). 5.5. Сложностные классы задач Теоретический предел трудоемкости задачи Рассматривая некоторую алгоритмически разрешимую задачу, и анализируя один из алгоритмов ее решения, мы можем получить оценку трудоемкости этого алгоритма в худшем случае – ˆA(DA)=O(g(DA)). Такие же оценки мы можем получить и для других известных алгоритмов решения данной задачи. Рассматривая задачу с этой точки зрения, возникает резонный вопрос: какова оценка сложности самого «быстрого» алгоритма решения данной задачи в худшем случае? Очевидно, что это оценка самой задачи, а не какого либо алгоритма ее решения. Таким образом, мы приходим к определению понятия функционального теоретического нижнего предела трудоемкости задачи в худшем случае: Fthlim= min { (Fa^ (D)) } Если мы можем на основе теоретических рассуждений доказать существование и получить оценивающую функцию, то мы можем утверждать, что любой алгоритм, решающий данную задачу работает не быстрее, чем с оценкой Fthlim в худшем случае: Fa^ (D) = (Fthlim) Приведем ряд примеров: 1) Задача поиска максимума в массиве A=(a1,…,an) – для этой задачи, очевидно должны быть просмотрены все элементы, и Fthlim= (n). 2) Задача умножения матриц - для этой задачи можно сделать предположение, что необходимо выполнить некоторые арифметические операции со всеми исходными данными, что приводит нас к оценке Fthlim= (n2). С началом широкого использования вычислительной техники для решения практических задач, возник вопрос о границах практической применимости данного алгоритма решения задачи в смысле ограничений на ее размерность. Какие задачи могут быть решены на ЭВМ за реальное время? Ответ на этот вопрос был дан в работах Кобмена (Alan Cobham, 1964), и Эдмнодса (Jack Edmonds, 1965), где были введены сложностные классы задач. Класс P (задачи с полиномиальной сложностью) Задача называется полиномиальной, т.е. относится к классу P, если существует константа k и алгоритм, решающий задачу с Fa(n)=O(nk), где n - длина входа алгоритма в битах n = |D| . Задачи класса P – это интуитивно, задачи, решаемые за реальное время. Отметим следующие преимущества алгоритмов из этого класса: для большинства задач из класса P константа k меньше 6; класс P инвариантен по модели вычислений (для широкого класса моделей); класс P обладает свойством естественной замкнутости (сумма или произведение полиномов есть полином). Таким образом, задачи класса P есть уточнение определения «практически разрешимой» задачи. Класс NP (полиномиально проверяемые задачи) Представим себе, что некоторый алгоритм получает решение некоторой задачи – соответствует ли полученный ответ поставленной задаче, и насколько быстро мы можем проверить его правильность? Содержательно задача относится к классу NP, если ее решение некоторым алгоритмом может быть быстро (полиномиально) проверено. Проблема P = NP После введения в теорию алгоритмов понятий сложностных классов Эдмондсом (Edmonds, 1965) была поставлена основная проблема теории сложности – P = NP? Словесная формулировка проблемы имеет вид: можно ли все задачи, решение которых проверяется с полиномиальной сложностью, решить за полиномиальное время? Очевидно, что любая задача, принадлежащая классу P, принадлежит и классу NP, т.к. она может быть полиномиально проверена – задача проверки решения может состоять просто в повторном решении задачи. На сегодня отсутствуют теоретические доказательства как совпадения этих классов (P=NP), так и их несовпадения. Предположение состоит в том, что класс P является собственным подмножеством класса NP, т.е. NP \ P не пусто. Класс NPC (NP – полные задачи) Понятие NP – полноты основывается на понятии сводимости одной задачи к другой. Сводимость может быть представлена следующим образом: если мы имеем задачу 1 и решающий эту задачу алгоритм, выдающий правильный ответ для всех индивидуальных задач, составляющих задачу, а для задачи 2 алгоритм решения неизвестен, то если мы можем переформулировать (свести) задачу 2 в терминах задачи 1, то мы решаем задачу 2. Таким образом, если задача 1 задана множеством индивидуальных задач DA1, а задача 2 – множеством DA2 , и существует функция fs (алгоритм), сводящая конкретную постановку задачи 2 (dА2) к конкретной постановке задачи 1(dА1): fs(d(2) DA2) = d(1) DA1, то задача 2 сводима к задаче 1. Если при этом FA (fs) = O(nk), т.е. алгоритм сведения принадлежит классу P, то говорят, что задача 1 полиномиально сводится к задаче 2. Принято говорить, что задача задается некоторым языком, тогда если задача 1 задана языком L1, а задача 2 – языком L2, то полиномиальная сводимость обозначается следующим образом: L2 p L1. Определение класса NPC (NP-complete) или класса NP-полных задач требует выполнения следующих двух условий: во-первых, задача должна принадлежать классу NP (L NP), и, во-вторых, к ней полиномиально должны сводиться все задачи из класса NP (Lx P L, для каждого Lx NP). Для класса NPC доказана следующая теорема: Если существует задача, принадлежащая классу NPC, для которой существует полиномиальный алгоритм решения (F = O(nk)), то класс P совпадает с классом NP, т.е. P=NP. Схема доказательства состоит в сведении любой задачи из NP к данной задаче из класса NPC с полиномиальной трудоемкостью и решении этой задачи за полиномиальное время (по условию теоремы). В настоящее время доказано существование сотен NP– полных задач, но ни для одной из них пока не удалось найти полиномиального алгоритма решения. В настоящее время исследователи предполагают следующее соотношение классов, показанное на рис. – P NP, то есть NP \ P , и задачи из класса NPC не могут быть решены (сегодня) с полиномиальной трудоемкостью. NPC NP P 5.6. Построение эффективных алгоритмов. Метод декомпозиции Рассмотрим один из методов, применяемых для снижения сложности алгоритмов. Пусть необходимо решить индивидуальную задачу размером N. Попробуем разбить эту задачу на подзадачи меньшего размера. Если разбивать некуда, то решаем задачу минимального размера очевидным способом. Разработаем процедуру, позволяющую решить исходную задачу размером N, используя решения подзадач меньшего размера. Подзадачи будем решать аналогичным способом. Описанный подход называется методом декомпозиции. С его помощью можно получить эффективные алгоритмы решения многих массовых проблем. Пример. Поиск минимального и максимального элемента массива. Вход: Натуральное число N, массив X[1..N]. Выход: Xmax, Xmin - соответственно максимальный и минимальный элемент массива X. Очевидно, что за размер задачи можно принять число N - количество элементов массива X. Будем считать характерной операцией операцию сравнения двух чисел. Первый вариант решения. Будем просматривать массив слева направо, сравнивая каждый элемент массива с наименьшим и наибольшим значением, при необходимости обновляя наименьший и наибольший элемент. Запишем алгоритм строго. (int, int) minmax (int N, array [1...N] of int X) { int MIN, MAX; MIN = X(1); MAX = X(1); for I = 2 to N { if (MIN < X(I)) MIN = X(I); if (MAX > X(I)) MAX = X(I); } return(MIN,MAX); } Сложность этого алгоритма: T(N)=2*N-2 Второй вариант решения. Применим метод декомпозиции. Если массив состоит из двух элементов, то находим максимальный и минимальный элемент этого массива простым сравнением этих двух чисел. Если в массиве больше двух элементов, то: разделим массив пополам, найдем максимальный и минимальный элементы в каждой половине. Сравнив два найденных максимальных элемента, найдем максимальный элемент массива. Сравнив два найденных минимальных элемента, найдем минимальный элемент массива. Искать максимальный и минимальный элементы в половинах будем тем же способом. Запишем алгоритм строго. (int, int) MINMAX (int N, array [1...N] of int X) { int MIN1, MIN2, MAX1, MAX2, MIN, MAX; array [1...N/2+1] of int A, B; if (N == 1) return(X[1],X[2]); else if (N == 2) { if (X[1] < X[2]) return(X[1],X[2]); else return(X[2],X[1]); } else { A[1...N/2]=X[1...N/2]; B[1...N-N/2]=X[N/2+1...N]; (MIN1,MAX1)=MINMAX(N/2, A); (MIN2,MAX2)=MINMAX(N-N/2, B); if (MIN1 < MIN2) MIN = MIN1; else MIN = MIN2; if (MAX1 < MAX2) MAX = MAX2; else MAX = MAX1; return(MIN,MAX); } } Вычислим сложность приведенного алгоритма. Пусть n=2**k k=1 (n=2): T(2)=1; k=2 (n=4): T(4)=(1*2)+2=4; k=3 (n=8): T(8)=((1*2)+2)*2+2=10; .................................. k=r (n=2r): T(2r)=(…((1*2)+2)*2+2…)*2+2=2r-1+2r-1+2r-2+2r-3+…+2 Перед нами сумма геометрической прогрессии. Сумма членов геометрической прогрессии: S=a0-an/1-q=(2r-2)/(2-1)=2r-2 T(2r)=2r+2r-1-2 Но r=log2n, следовательно T(n)=n+n/2-2=3n/2-2. Очевидно, что сложность второго алгоритма меньше, чем сложность первого алгоритма. При помощи метода декомпозиции получен более эффективный алгоритм.