Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Book-advanced-algorithms.pdf
Скачиваний:
310
Добавлен:
27.03.2016
Размер:
5.11 Mб
Скачать

54

Глава 1. АЛГОРИТМЫ И ИХ СЛОЖНОСТЬ

1.2Формально об алгоритмах. Несложно о сложности

1.2.1«RAM»: машины с произвольным доступом

При написании данного раздела использован обзор [КР96].

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

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

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

Сначала рассмотрим модель, наиболее напоминающую современный компьютер, программируемый непосредственно в терминах инструкций процессора (или на языке Assembler): random access machines (RAM)¹ , т. е. «машины с произвольным доступом к памяти».

¹ В теории сложности вычислений под машинами традиционно понимают single-purpose machines, т. е. машины, каждая из

1.2. ФОРМАЛЬНО ОБ АЛГОРИТМАХ. НЕСЛОЖНО О СЛОЖНОСТИ

55

RAM-машину составляют следующие компоненты (рис. 1.7).

Конечная входная read-only лента, на которую записываются входные данные.

Полубесконечная¹¹ выходная write-only лента, куда записывается результат работы машины.

Бесконечное число регистров r0; r1; r2; : : :, каждый из которых может хранить произвольное целое число (изначально везде записаны нули). Регистр r0 является выделенным и называется «сумматором» — этот регистр используется при арифметических операциях как накопитель, т. е. как второй операнд и место хранения результата.

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

Регистр-счетчик «PC» — указатель текущей команды.

Машина последовательно, такт за тактом, выполняет команды, на которые указывает регистр «PC», читает входную ленту, изменяет значения регистров и записывает результат на выходную ленту.

Легко видеть, что высокоуровневые конструкции языков программирования, типа циклов, легко моделируются на ассемблере RAM-машин (см. рис. 1.8).

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

которых создана для решения какой-либо одной фиксированной задачи. В привычных терминах это скорее программы. ¹¹Ограниченная с одной стороны и неограниченная с другой.

56

Глава 1. АЛГОРИТМЫ И ИХ СЛОЖНОСТЬ

входная read-only лента

 

 

 

 

 

 

 

 

 

 

x1

x2

x3

 

 

 

 

 

 

xn

 

 

 

 

 

 

 

 

 

 

O

Program Counter

RAM–Program

 

 

0: LOAD r1

 

1: ADD 3

 

z 2: STORE r7

 

3: LOAD 77

 

4: STORE r7

 

5: JUMP 100

 

: : :

y1

y2

y3

y4

y5

y6

y7

y8

выходная write-only лента

z r0

r1

r2

r3

r4

r5

1.2. ФОРМАЛЬНО ОБ АЛГОРИТМАХ. НЕСЛОЖНО О СЛОЖНОСТИ

57

Таблица 1.2: RAM-машина: Список команд

LOAD OP

 

r0

OP

Загрузить операнд

в сумматор.

 

STORE OP

 

rOP

r0

Сохранить сумматор в регистре.

 

 

ADD OP

 

r0

r0 + OP

Прибавить операнд к сумматору.

 

 

SUB OP

 

r0

r0 OP

Вычесть операнд из сумматора.

 

 

READ OP

 

rOP

input

Загрузить ячейку из входной ленты в rOP и перейти к сле-

 

 

 

 

 

 

дующей.

 

 

WRITE OP

 

OP ! output

Записать OP в текущую ячейку выходной ленты и сдвиг

 

 

 

 

 

 

к следующей.

 

 

JUMP OP

 

P C

OP

Установить счетчик команд в OP .

 

 

JGTZ OP

 

P C

OP : r0 > 0

Установить счетчик команд в OP , если r0 > 0.

 

JZERO OP

 

P C

OP : r0 = 0

Установить счетчик команд в OP , если r0 = 0.

 

HALT

 

 

 

Остановить работу.

 

 

 

 

 

 

 

Операнд OP может быть:

 

 

 

целым числом, например, 7, 1917;

 

 

регистром, например, r12, r34;

значением регистра, указанного в другом регистре. Например, операнд r14 означает значение регистра, номер которого указан в регистре r14.

58

 

 

 

 

 

Глава 1. АЛГОРИТМЫ И ИХ СЛОЖНОСТЬ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

...

 

 

 

 

 

 

 

0777:

LOAD

1

 

 

 

 

 

 

0778:

STORE

r17

 

 

 

 

 

0779:

начало тела цикла

 

 

 

 

 

 

...

 

 

 

 

 

...

 

 

 

 

 

 

 

1035:

LOAD

1

 

 

 

 

for i in range( 1::78 ):

 

 

 

 

 

 

1036:

ADD

r17

 

 

 

 

тело цикла

)

 

 

 

1037:

STORE

r17

 

 

...

 

1038:

конец тела цикла

 

 

 

 

 

 

 

 

 

 

 

1039:

LOAD

78

 

 

 

 

 

 

1040:

SUB

r17

 

 

 

 

 

1041:

JGTZ

0779

 

 

 

 

 

 

 

...

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 1.8: Моделирование циклов для RAM

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

Если мы будем принимать в расчет только число выполненных команд, то определим так называемые

1.2. ФОРМАЛЬНО ОБ АЛГОРИТМАХ. НЕСЛОЖНО О СЛОЖНОСТИ

59

однородные меры сложности. В англоязычной литературе RAM с однородной мерой сложности называ-

ются unit-cost RAM.

Более реалистично было бы учитывать битовый размер операндов при выполнении каждой команды. Например, можно считать, что время выполнения каждой команды есть единица плюс величина пропорциональная сумме логарифмов значений операндов¹², если они есть (т. е. их суммарной битовой длине), а общее время работы программы на рассматриваемых входных данных — суммарное время выполнения всех индивидуальных команд. Таким образом, мы можем определить различные логарифмические меры сложности.

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

иделения при работе на входных данных x1; : : : ; xm, битовая длина каждого из которых не больше n, равно t. Так как при выполнении любого индивидуального оператора максимальная битовая длина может возрасти не более чем на 1, в ходе выполнения всей программы встречаются лишь числа битовой длины не более (t + n), и, стало быть, логарифмическая сложность превышает однородную не более чем в 2(t + n) раз. В частности (см. обсуждение в разделе 1.1), с точки зрения эффективности однородная

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

Если же добавить умножение и деление к списку элементарных операций, то это исключит равносильность однородной и логарифмической сложности. Следующий известный способ быстро переполнить память карманного калькулятора (алгоритм 13) имеет однородную сложность порядка (t + 1) и логарифмическую порядка экспоненты — 2t.

¹²Более корректно логарифм брать от модуля операнда плюс 2, чтобы логарифм был положительным.

60

 

 

 

Глава 1. АЛГОРИТМЫ И ИХ СЛОЖНОСТЬ

 

 

 

 

 

 

Алгоритм 13 Переполнение памяти умножением

 

 

 

 

 

def full_memory(loop):

 

loop:

2

result: 16

 

result = 2

 

loop:

3

result: 256

 

 

for i in range(loop):

 

loop:

4

result: 65536

 

loop:

5

result: 4294967296

result = result * result

 

 

loop:

6

result: 18446744073709551616

return result

 

loop:

7

result: 340282366920938463463374607431768211456

 

 

 

 

loop: 8 result: 115792089237316195423570985008687907853269984665640564039457

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Алгоритм 14 Тривиальное вычисление a b на RAM

 

 

 

 

 

def mult(a, b):

 

= 6

 

 

2 × 3

 

mul = 0

 

6 × 2

= 12

 

for i in range(b):

 

183 ×

34 = 6222

 

mul = mul + a

 

 

 

 

 

return mul

 

 

 

 

 

 

 

 

 

 

 

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

В тех же случаях, когда умножение используется «в мирных целях», его в большинстве случаев можно промоделировать с помощью сложения очевидным образом (см. алгоритм 14 «Умножение»).

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