
- •Дополнение
- •Наивная теория алгоритмов
- •4.4. Наивная теория алгоритмов
- •4.4.1. Вычислимые функции
- •4.4.2. Перечислимые множества
- •4.4.3. Разрешимые множества
- •4.4.4. Протокол выполнения алгоритма
- •4.4.5. Алгоритмы и программы
- •4.4.6. Невычислимые функции и неразрешимые множества
- •4.4.7. Истинность и доказуемость
- •4.4.8. Множество истин арифметики
4.4.2. Перечислимые множества
Рассмотрим теперь случай, когда алгоритм A не принимает на вход никаких данных, а будучи запущенным, вырабатывает некоторую последовательность результатов (см. п. 1.1.2). Тем самым алгоритм A определяет некоторое множество A, являющееся множеством слов в алфавите U, которое называется перечислимым:
A = {aU* | a:=A()}.
Совершенно аналогично вычислимым функциям, перечислимые множества могут быть реализованы бесконечным множеством алгоритмов, которые все эквивалентны. Условимся для каждого перечислимого множества выбирать представление в классе эквивалентных алгоритмов и не различать перечислимое множество и его представлении без нужды. Алгоритм, представляющий перечислимое множество, для краткости речи называется просто перечисляющим.
В используемом псевдокоде в теле алгоритма, перечисляющего множество, как правило, присутствует оператор yield a.
Замечание 1. Иногда оператор yield a отсутствует в явном виде, но тогда он всегда моделируется оператором A:=A+a добавления элемента a в изначально пустое множество A.
Замечание 2. Всякое конечное множество может быть задано перечислением элементов {a1, …, an} (п. 1.1.2) и тем самым является перечислимым. Действительно, алгоритм begin yield a1; …; yield an end очевидно, перечисляет это множество. В частности, алфавит U перечислим, перечисляющий его алгоритм обозначим U.
Замечание 3. Всякое перечислимое множество A линейно упорядочено (п. 1.8.1). Действительно, порядок выполнения операторов yield (или оператора добавления элемента) очевидно, линеен.
Замечание 4. Перечислимые множества (и только они!) могут появляться в заголовке цикла по множеству for aA do B(a) end for, где B — некоторый алгоритм. Действительно, чтобы реализовать этот цикл, достаточно в теле перечисляющего алгоритма A заменить оператор yield a вызовом алгоритма B(a).
Замечание 5. Конкретные перечисляющие алгоритмы, приводимые в примерах, генерируют каждый элемент ровно один раз, как это и требуется в обычных множествах. Однако явно накладывать такое ограничение на перечисляющие алгоритмы необязательно. Действительно, если имеется перечисляющий алгоритм, который перечисляет какие-то элементы несколько раз, то в его тело можно вставить проверку, исключающую повторные добавления элементов. Перечисляющие алгоритмы, генерирующие элементы по несколько раз, можно использовать для работы с мультимножествами, которые в этом разделе не понадобились.
Примеры.
1. Множество натуральных чисел перечислимо:
= {n | n:= 0; while true do n:=n+1; yield n end while}.
2. Алгоритмы 1.1, 1.8–1.9 задают перечислимые множества.
Пустое множество и множество всех слов U* перечислимы. Пустое множество перечислимо пустым алгоритмом skip, а множество всех слов перечислимо очевидным алгоритмом, который вначале перечисляет все слова длины 1, потом все слова длины 2 и так далее.
Теорема 1. Существуют невычислимые функции и неперечислимые множества.
Доказательство. Число различных слов в непустом конечном алфавите счётно. Всякий алгоритм — это слово в конечном алфавите, следовательно, число различных алгоритмов не более чем счётно. Фактор-множество (п. 1.7.2) не может превосходить исходное множество по мощности, поэтому множество классов функционально эквивалентных алгоритмов не более чем счётно. С другой стороны, множество всех подмножеств счетного множества несчётно. Значит число различных множеств слов и число различных функций из множеств слов в множества слов несчётно. Следовательно, невозможно взаимно-однозначное соответствие из множества алгоритмов в множество множеств и в множество функций. чтд
Следствие. Существуют перечислимые множества и вычислимые функции; их количества не более чем счётны.
Замечание. Наряду с вычислимыми функциями и перечислимыми множествами бывает полезно рассматривать и другие типы алгоритмов, например: итераторы, трансформации и др. В этом разделе другие типы не понадобились.
Пример. Множество перечислимо, множество 2= также перечислимо. Например, следующий алгоритм N2 перечисляет пары натуральных чисел <a, b>:
s:=1; while true do s := s+1; for a from 1 to s–1 do yield <a, s–a> end for end while.
Перечислимость множеств тесно связана с вычислимыми функциями натурального ряда. Действительно, перечислимое множество (и его отрезки, п. 1.2.5) — это удобное и естественное средство нумерации, то есть перечисления множеств слов. Пусть A — перечислимое множество, заданное алгоритмом A. Рассмотрим вычислимую функцию F : A, заданную следующим алгоритмом:
func F (n: ) : A
k:=0;
for aA do
k := k+1;
if k = n then return a end if
end for
Построенная функция возвращает элемент перечислимого множества по его номеру. Такую перечисляющую функцию всегда можно и иногда удобно использовать в качестве представления перечислимого множества наряду с генерирующим алгоритмом.
Нетрудно сделать и обратное — узнать номер заданного элемента перечислимого множества:
proc F –1 (x: A) :
k:=0;
for aA do
k := k+1;
if x = a then return k end if
end for
Пример. Формула 2a (2b + 1) – 1 задает номер пары в некотором перечислении натуральных пар <a, b>.
Теорема 2. Объединение и пересечение перечислимых множеств перечислимы.
Доказательство. Пусть X и Y — перечислимые множества с перечисляющими функциями X: X и Y: Y.
[] Если хотя бы одно из множеств пусто, то утверждение тривиально. Иначе вычислимая функция Z: XY перечисляет объединение:
Z(n) := if n четно then return X(n/2) else return Y((n+1)/2) end if.
[] Если пересечение пусто, то оно перечислимо по определению. Иначе рассмотрим алгоритм N2, перечисляющий пары натуральных чисел (см. пример выше), и построим алгоритм, перечисляющий пересечение:
for <a, b>N2 do if X(a)=Y(b) then yield X(a) end if end for.
Замечание. Другие примеры перечисляющих алгоритмов в форме итераторов приведены в п. 1.3.8.