Подставляя (1.7) в (1.4) проверяем, что T (N) = CN log N, для некоторой константы C:
CN log N = O(N) +
2C
N
(
1
2
N
2
log N −
1
8
N
2
)
= CN log N + (O(N) −
C
4
N)
К сожалению, допущенное нами предположение о равномерности распределения «осей» относительно интерва-
лов, может не встречаться на практике. Например, есть вариации детерминированного алгоритма 11 «Quicksort», для
которых «плохими» будут уже отсортированные (или почти отсортированные) последовательности, а таковые часто
встречаются в реальных задачах. В таких случаях, когда нельзя «сделать случайными» входные данные, можно вне-
сти «элемент случайности» непосредственно в сам алгоритм. В частности, чтобы сохранилась оценка матожидания
(1.6), достаточно сделать вероятностным выбор осевого элемента из разделяемого интервала. Алгоритм 12 «Quicksort-
random» отличается от алгоритма 11 «Quicksort», только строчкой, где задается выбор вероятностный выбор осевого
элемента, однако, как мы увидели, это дает ему возможность «избегать потерь» на входных данных, которые были
«наихудшими» для детерминированного алгоритма, и достигнуть оценки матожидания времени работы (1.6), вне зави-
симости от входных данных.
1.2 Формально об алгоритмах
1.2.1 Машины с произвольным доступом (RAM)
В предыдущих лекциях мы довольствовались качественным, интуитивным понятием «эффективного» алгоритма. Для
построения же математической теории сложности алгоритмов, разумеется, необходимо строгое количественное опре-
деление меры эффективности.
Опыт, накопленный в теории сложности вычислений, свидетельствует, что наиболее удобным и адекватным спосо-
бом сравнения эффективности разнородных алгоритмов является понятие асимптотической сложности, рассмот-
рению которого и посвящен настоящий параграф.
Первое, о чем следует договориться — это выбор вычислительной модели, в которой конструируются наши ал-
горитмы. Оказывается, что как раз этот вопрос не имеет слишком принципиального значения для теории сложности
вычислений, и тот уровень строгости, на котором мы работали в предыдущем параграфе (число выполнений операто-
ров на языке Python) оказывается почти приемлемым. Главная причина такого легкомысленного отношения к выбору
модели состоит в том, что существуют весьма эффективные способы моделирования (или трансляции программ в
более привычных терминах) одних естественных вычислительных моделей с помощью других. При этих моделирова-
ниях сохраняется класс эффективных алгоритмов и, более того, как правило, алгоритмы «более эффективные» в одних
моделях оказываются более эффективными и в других.
Сначала рассмотрим модель, наиболее напоминающую современный компьютер, программируемый непосредствен-
но в терминах инструкций процессора (или на языке Assembler): random access machines, RAM
11
, т.е. «машины со
случайным доступом».
RAM-машина состоит из (См. Рис. 1.2.1):
• Конечная входная read-only лента, куда записываются входные данные.
• Полубесконечная выходная write-only лента, куда записываются результат работы машины.
• Бесконечное число регистров r
0
, r
1
, r
2
, . . . ,, каждый из которых может хранить целое число, причем регистр r
0
является выделенным, и называется «сумматором» — этот регистр используется при арифметических операциях
как накопитель, т.е. как второй операнд и место хранения результата.
• Программа, состоящая из конечного числа инструкций, каждая из которых содержит адрес и команду с операн-
дом. Список команд приведен в Таблице 1.2. Важный, характеристический момент — в качестве операнда можно
использовать как произвольный регистр, так и регистр, номер которого хранится в другом регистре — так назы-
ваемая косвенная адресация.
• Регистр-счетчик «PC», указывающий на текущую команду.
На самом деле, легко видеть, что практически все высокоуровневые конструкции языков программирования, типа
циклов, легко моделируются на ассемблере RAM — машин:
11
в теории сложности вычислений под машинами традиционно понимают single-purpose machines, т.е. машины, созданные для решения какой-
либо одной фиксированной задачи. В привычных терминах это скорее программы.
21