- •Предисловие
- •Введение
- •Глава 1. Множества
- •§ 1. Множества н их спецификация
- •§ 2. Простейшие операции над множествами
- •X ∉ ø при любом х.
- •§ 3. Диаграммы Венна
- •§ 4. Подмножества и доказательства
- •Глава 2. Отношения
- •§ 5. Произведения множеств
- •§ 1. Основные понятия
- •§ 2. Графические представления
- •§ 3. Свойства отношений
- •§ 4. Разбиения и отношения эквивалентности
- •§ 5. Отношения порядка
- •§ 6. Отношения на базах данных и структурах данных
- •§ 7. Составные отношения
- •§ 8. Замыкание отношений
- •Глава 3. Функции
- •§ 1. Функции и отображения
- •§ 2. Обратные функции и отображения
- •§ 3. Мощность множеств и счетность
- •§ 4. Некоторые специальные классы функций
- •§ 5. Аналитические свойства вещественных функций
- •§ 6. Операции
- •Глава 4. Основные понятия арифметики
- •§ 1. «Малая» конечная арифметика
- •§ 2. «Большая» конечная арифметика
- •§ 3. Двоичная арифметика
- •§ 4. Логическая арифметика
- •Глава 5. Алгебраические структуры
- •§ 1. Алгебраические структуры и подструктуры
- •§ 2. Простейшие операционные структуры
- •§ 3. Кольца и поля
- •§ 4. Линейная алгебра
- •4.1. Векторные пространства о линейные преобразования.
- •§ 5. Решетка и булевы алгебры
- •§ 6. Замкнутые полукольца
- •Глава 6. Матрицы
- •§ 1. Матрицы и бинарные отношения на конечных множествах
- •§ 2. Матрицы над другими алгебраическими структурами
- •§ 3. Матрицы и векторные пространства
- •Глава 7. Теория графов
- •§ 1. Вводные понятия
- •§ 2. Маршруты, циклы и связанность.
- •§ 3. Планарные графы
- •3.1. Теоремы Эйлера и Куратовского.
- •3.2. Раскраска карт и графов.
- •§ 4. Структуры данных для представления графа
- •§ 5. Обход графа
- •5.2. Обход графа по глубине.
- •5.4. Остовные леса обходов по глубине и ширине.
- •§ 6. Ориентированные графы
- •6.2. Маршруты и связность в орграфах.
- •Глава 8. Языки и грамматики
- •§ 1. Основные понятия
- •§ 2. Грамматики с фразовой структурой
- •2.1. Основные определения.
- •§ 3. Контекстно-свободные языки
- •§ 4. Понятия грамматического разбора и грамматических модификаций
- •§ 5. Грамматики операторного предшествования
- •Глава 9. Конечные автоматы
- •§ 1. Общие понятия
- •§ 2. Конечные автоматы
- •§ 3. Регулярная алгебра
- •Глава 10.Компьютерная геометрия
- •§ 1. Системы координат для подмножеств r3
- •§ 2. Преобразования
- •§ 3. Кривые и поверхности
Глава 9. Конечные автоматы
Автоматом является устройство, управляющее и контролирующее само себя. Обычный компьютер с программой способен при достаточном запасе энергии контролировать сам себя и, следовательно, является автоматом. Как таковые, компьютеры изучались много лет, однако более естественно рассматривать программу и машину, в которой она содержится, как отдельные компоненты.
Конечно, для выполнения вычислений нам нужна не только программа, но и машина, на которой эти вычисления могут выполняться. Однако здесь мы не стремимся приступать к детальному изучению теории вычислений, поэтому, за исключением тех случаев, где это необходимо для полноты, мы ограничим наше внимание математическим описанием некоторых конечных машин.
Несмотря на эти замечания, мы начнем (в § 1) с общего введения, которое показывает границы того, что машины могут выполнять. Далее (§ 2) мы изучим математические модели устройств (обычно их малые фрагменты), а затем (в § 3) связанную с этим алгебру.
§ 1. Общие понятия
Все используемые па практике компьютерные устройства ограничены (некоторым образом) количеством информации, которую они могут хранить,— они конечны. Цель данного параграфа — показать, как можно делать утверждения о программах без утомительного рассмотрения синтаксических деталей; затем мы продемонстрируем, что даже при отсутствии ограничения па размеры памяти существуют задачи, которые нельзя решить.
1.1. Универсальная машина. Несмотря на использование многочисленных различных типов данных и множеств символов внутри реальных программ, для теоретического изучения достаточно ограничиться рассмотрением программ, которые действуют на множестве V = N {0}, Это равносильно изучению программ, вычисляющих теоретико-числовые функции, которые будут введены в § 2; наша непосредственная задача — описать идеализированный компьютер, позволяющий запоминать элементы V, с которыми можно осуществлять преобразования, и дать детальное описание того, как могут быть представлены программы для машины.
П
редположим,
что машина имеет память, состоящую из
неограниченного числа регистров:
R1,
R2,
R3
и т. д., и что содержание каждого регистра
R1
есть
r1
V.
Это можно изобразить так, как это сделано
на рис. 9.1.
Для более ясного изложения удобно разрешить использование многих операций, действующих лад регистрами, однако па самом деле необходимы лишь две операции:
Rn Rn+1, Rn Rn–1.
Обозначая содержимое регистра n до операции и после через rn и r’n соответственно, можно описать результаты выполнения этих операций следующим образом:
Rn
Rn+1
n=
rn
+ 1,
Rn
Rn–1
n
=
Кроме
операций, изменяющих значения регистров
памяти, необходимо, чтобы машина
имела связь с программой для того,
чтобы влиять на ее работу. Необходимой
в этом случае является только одна
операция, а именно та, которая
осуществляется сравнение
Rn
=
0. Однако, как и прежде, мы будем разрешать
более общие формы операций такого
рода. Результат Rn
= 0 справедлив тогда и только тогда,
когда
= 0,
и это условие используется для управления
программами. Множество регистров вместе
с описанными выше определениями
называется машиной
с неограниченной памятью
(МНГ).
Программы состоят из конечной совокупности операций, занумерованных от 1 до некоторого n N. Мы не будем вдаваться в детали, а выведем-более общие заключения об этих программах. Поэтому не будем давать формального определения такой структуры. Рисунков 9.2 и 9.3 достаточно, чтобы понять, какого рода конструкции допустимы в ней.
Программа на рис. 9.2 не предназначена для выполнения особо разумных вычислений, однако она указывает
вид блок-схемы программы для нашей машины. Заметим, что все это может быть записано не обязательно в виде рисунка. Например, можно написать:
1: если R2 = 0 то перейти к 4 иначе перейти к 2
2: R2 R2 – 1 (затем перейти к 3)
3: R1 R1 + 1 (затем перейти к 4)
4: если R2 = 0 то перейти к 6 иначе перейти к 5
5: R3 R3 – 1 (затем перейти к 2)
6: STOP
На
рис. 9.3 представлена более общая форма
программы, в которую включены
макрокоманды
,
и проверки
.
Это могут быть стандартные команды
того типа, который уже определен,
или же они могут быть представлены
последовательностью основных команд,
которым
для
удобства чтения даны имена (подобно
подпрограмме), детали которых уже
где-то определены. Такие последовательности
будем называть макропоследовательностями.
Сейчас мы можем описать некоторые из этих макропоследовательностей, которые обеспечат связь с последующими темами, а также помогут убедить читателя,
Рис.
9.3 Рис. 9.4
что наше небольшое число простых команд на самом деле является достаточно мощным средством.
Пример
1.1.
может быть реализовано следующим
фрагментом программы, где метки
х, у и
z
выбраны так, чтобы не пересекаться
с другими командами в
программе:
х: если Rt = 0 то перейти к z иначе перейти к у
у:
– 1 (затем перейти к
х)
z: ?
В терминах блок-схем этот фрагмент программы изображен на рис. 9.4.
Пример 1.2. Сейчас, определяя макрокоманду, удовлетворяющую предыдущему примеру, и включая в явном виде выражения «go to» («перейти к») только тогда, когда мы уклоняемся от выполнения команды «go to next instruction» («перейти к следующей команде»), мы можем дать раскрытие формулы m (для некоторого ):
Очередное расширение множества команд требует использования «рабочей памяти». Мы будем предполагать, что по крайней мере в простейших случаях читатель в состоянии придумать подходящую стратегию работы с памятью, и, следовательно, не будем специально упоминать о том, как выбираются эти дополнительные регистры. Случаи, когда количество требуемой памяти неизвестно (такие, как стеки и т. п.), будут рассмотрены ниже *).
Пример 1.3. Используя Rk в качестве рабочего регистра, мы можем скопировать содержимое Rj в Ri (Ri Rj). Делая это, мы уничтожаем содержимое Ri, которое в дальнейшем должно быть восстановлено. Следующая программа осуществляет требуемые вычисления:
Rk
х: if = 0 then go to у
Rk Rk + 1
Rj Rj – 1 then go to x
y: Ri
w: if Rk = 0 then go to z
Ri Ri+1
Rj Rj+1
Rk Rk –1 then go to w
z: //
Сейчас мы вернемся к «собственно» арифметическим вычислениям. Сложение и вычитание строятся непосредственно, однако, поскольку в регистрах существуют ограничения на значения величин, операция вычитания должна быть несколько модифицирована. Подобным же образом можно выполнить умножение и усеченное деление.
Пример 1.4. Сложение Ri Ri + Rk, имеющее результатом ri = rj + rk, может быть выполнено следующим образом:
Rj Rk
х: if Rj = 0 then go to у
Ri Ri + 1
Rj Rj – 1 then go to x
y:
Чтобы получить полную программу в терминах основных команд, мы должны расшифровать макрокоманду «Rj Rk»как это было сделано в примере 1.3. С этого времени мы не будем требовать доказательства таких расшифровок.
Подобным образом «ограниченное вычитание» Ri Rj Rk может быть выполнено как
Rj Rk
х: if Rj = 0 then go to у
Ri Ri – 1
Rj Rj – 1 then go to x
y:
Заметим, что если первоначальные значения Ri и Rk были такие, что ri < rk то после ri итераций операция Rj ← Rj – 1 не будет иметь эффекта.
Аналогично Ri ← Ri * Rk может быть представлено как
Rj 0
Ri Rk
х: if Rj = 0 then go to у
Ri Ri – 1
Rj Rj + Ri then go to x
y: Ri Rj //
В большинстве случаев таким же способом, каким может быть расширено множество операций над регистрами путем определения макрокоманд, можно также ввести команды сравнения, которые на первый взгляд оказываются более сложными, однако в действительности строятся из последовательностей стандартных операций и операций сравнения.
Пример 1.5. Мы можем записать операцию «if Ri > Rk then go to x else go to у» («если Ri > Rk, то перейти к х; иначе перейти к у») следующим образом:
Rj Ri
Ri Rj Ri
if Rj = 0 then go to у else go to x
Этими
операциями условного перехода мы можем
дополнить наше множество первоначальных
арифметических операций вместе с
операцией деления «Ri
Rj
Rk,
где
ri
= 0, если rk
= 0». В нашем случае этой операции
соответствует следующий алгоритм:
Rj 0
if Rk = 0 then go to x
y: if Rj < Rk then go to x
Ri Ri + 1
if Rj = Rk then go to x
Rj Rj – Rk then go to y
x: Ri Ri
Из менее общего, но необходимого приложения, которое кратко приведено ниже, следуют две операции, связанные с точностью усечения при делении целых чисел.
Пример 1.6. Если ri 0, операция «если Rk — делитель Ri, то перейти к х; иначе перейти к у» действует следующим образом:
if Rk = 0 then go to у
Rj Ri
Ri Rj Rk
Ri Rj Rk
if Rj = Ri then go to x else go to у
При помощи этой (возможно, несколько странной) операции и операции сравнения вида Ri = m (оставленной в качестве упражнения) мы можем проверить, является ли г( простым числом.
Ясно, что можно операцию «если Ri простое, то перейти к х; иначе перейти к у» смоделировать следующим образом:
if Ri = 0 then go to у
if Ri = 1 then go to у
Rj Ri –1
z: if Rj = 1 then go to x
if Ri делитель Rj then go to у
Rj Ri –1 then go to z //
Перед последним примером этого параграфа мы должны упомянуть, как в машине вводятся данные и выводится результат. Предположим, что мы хотим вычислить значение теоретико-числовой функции f: Vn Vm. Значения m и n известны перед началом выполнения соответствующей программы; поэтому мы можем вначале выделить n регистров (в которые должны быть загружены начальные значения перед началом выполнения программы) для входных данных и m регистров (которые не должны быть обязательно отличными от выбранных вначале) для выходных данных. Когда программа останавливается, мы предполагаем, что некоторый «внешний представитель» может выдать «ответы» из соответствующих мест. Это, конечно, разумный путь моделирования потоков данных в программе, поскольку точно показывает взаимодействие операций ввода-вывода. С этими соглашениями пример 1.7 может рассматриваться или как полная программа (в которой Ri и Rj выбраны как входные и выходные регистры), или как схема для подпрограммы.
П р и м е р 1.7. Последовательность команд приведенных ниже, помещает n-е простое число в Rj где п является содержимым Ri, предполагается, что п не равно нулю:
(Start)
Rk Ri –1
Ri 2
х: if Rk = 0 then go to у
Rj Rj + 1
z: if Rj простое then go to w
Ri Rj + 1 then go to z
w: Rk Rk — 1 then go to x
у: (stop) //
Сейчас мы объясним наш очевидный интерес к простым числам.
1.2. Кодирование программ. Программы п. 1.1 могли работать с элементами из = N {0}. Мы должны объяснить, как в принципе можно любую программу рассматривать в качестве программы такого типа. Это можно сделать, описав способы, при помощи которых различные типы данных могут быть закодированы в элементы из V. Для этого рассматриваем данные предложения над подходящими алфавитами и, следовательно, почти как постороннее следствие этого процесса получаем также метод для кодирования предложений на языках программирования, а именно сами программы.
Основное математическое средство, используемое для этой цели, это теорема о единственности разложения, известная также как основная теорема арифметики. Эта теорема устанавливает, что произвольный элемент из V является или 0, или 1 либо может быть выражен единственным образом как произведение упорядоченных простых чисел. Ясно, что если n ∈ V\{0, 1} и
где q1, ..., qn, s1, ..., sn — все простые числа такие, что
то i = j и sk = qk для всех k, 1 k i. Напомним, что простые числа — это элементы из N, которые делятся только на 1 и на самих себя.
Полное доказательство этой теоремы несложно, но его запись существенно отвлекла бы нас от основной задачи. Поэтому вместо доказательства мы предлагаем рассмотреть конструкцию алгоритма (процедуры/программы) для выделения упорядоченных простых множителей q1,..., qt для любого данного n. Таким образом, избегая деталей ввода и вывода, мы можем использовать схему, представленную на рис. 9.5.
Предположим теперь, что некоторая программа читает данные, которые состоят из последовательности символов алфавита А = {x, у, z}. Произвольно выбирая порядок φ для элементов из А, можно получить φ(1) = x, φ(2) = у, φ(3) = z. Если вводимая последовательность i имеет длину n и выражается как α1 α2 … αn, где αi ∈ A,тогда существует последовательность
φ -1(α1) φ -1(α2)…φ -1(αn)
над {1, 2, 3}. Взяв первые п простых чисел р1 ..., рn, мы можем составить число
назовем его Ф(а). Пусть по соглашению Ф(А) = 1.
Чтобы проиллюстрировать использование этой общей формулы, закодируем строку «хухz» посредством 1, 2, 3
Рис.
9.5
следующим образом: 21325173«=30870. Используя теорему о единственности разложения на простые множители и тот факт, что Ф-1 является биекцией, имеем, что «хухz» является единственной строкой над А, которая дает это значение. Следовательно, применяя Ф, мы можем «обратить» вычисления, чтобы восстановить строку «хухz».
При помощи этой процедуры все строки над А можно закодировать таким образом, чтобы они имели разные значения в множестве V. Это в силу упорядоченности, индуцируемой Z (поскольку V Z), влечет упорядоченность строк из А*. Например, «ху» > «ух».
Необходимо отметить два фактора, связанных с этим методом. Во-первых, поскольку А конечно, в N существуют значения, которые не являются кодами какой-либо строки в А* (например, не существует α ∈ А* такого, что Ф(α) = 25, так как иначе a ∈ А: φ-1(5) = а, а такого а не существует). Во-вторых, поскольку элементы А* являются неограниченными, то таковыми являются и коды. (Для любого n ∈ N возьмем простое рт>п и рассмотрим строку
α = α1... αm...αq
такую, что |α| ≥ m; тогда отсюда следует, что
Используя методику, описанную в § 3 гл. 3, преобразуем кодирование Ф так, чтобы получить новое кодирование, которое сохраняет порядок, полученный Ф, но использует все V. Это один из тех случаев, когда трудно дать арифметическую формулу для вычисления измененных кодов, однако все еще достаточно легко описать способ их получения.
Очевидно, что новое кодирование Ψ с областью значений V задается формулой α |{β: Ф(β) < Ф(α)}|. В частном случае для рассмотренного в примере множества А и его упорядочения φ первые десять значений Ф и Ψ приведены в табл. 9.1.
Таблица 9.1
Чтобы получить эти коды и выполнить процедуры кодирования, требуется машина, способная понимать и создавать символы вне алфавита D = {0, 1, 2, ..., 8, 9}, на котором определены элементы множества V. Для этого необходимо только устройство, позволяющее имитировать действие φ (и φ-1), некоторым простым способом воздействуя на таблицу ввода-вывода; остаток процедуры кодирования, включающий Ф u можно потом учесть при помощи машины с неограниченной памятью.
Следовательно, любой ввод в данную компьютерную систему может рассматриваться как конечная строка (взятая из потенциально бесконечного множества строк над некоторым конечным алфавитом), и если применить описанный выше процесс к соответствующему алфавиту А, то эту строку можно преобразовать в единственный элемент V. Обратная процедура, примененная к полученному значению из V, дает значение над другим алфавитом В.
Следовательно, программа Р: А* → В* похожа на соответствующую программу Р': V → V, где
Р: а → Ф-1 (Р'(Ф(α))), Р': n → Ф (Р(Ф-1 (α))),
В
еличина
используемых значений даже в простых
ситуациях делает примеры непригодными,
однако связанные
Рис. 9.6
диаграммы па рис. 9.6 восстанавливают сущность пропущенных этапов.
Переходы, включающие детальную спецификацию Р', являются сложными и не обязательно соответствуют Р «очевидным» образом. Однако если Р вычислимо, то и Р' вычислимо.
Как отмечалось выше, мы можем также применять такие же кодирующие процедуры к программам, и, следовательно, расширив Ψ таким образом, чтобы можно было игнорировать семантически неверные программы так же, как и программы, включающие синтаксические ограничения, можно перечислить все программы для данной системы, использующей числа из V. Мы предполагаем, что для того, чтобы программа превосходила любое заданное в V число, к пей можно добавить неограниченное множество кодов, которые содержат произвольно много нулевых утверждений, таких, как Ri ← Ri. Однако в большинстве языков программирования существует много избыточности в синтаксисе; поэтому в лучшем случае мы будем обращать внимание только на лексические знаки. Прежде чем пытаться сделать замечания общего характера, рассмотрим, что можно сделать с простым языком, который использовался ранее в машине с неограниченной памятью. Существует четыре типа команд:
х: Ri ← Ri + 1 then go to у
x: Ri ← Ri – 1 then go to у
x: if Ri = 0 then go to у else go to z
x: stop
(Без потери общности мы можем предполагать, что все программы начинаются с команды, помеченной индексом 1.) Все, что мы должны знать из команды х,— это, какой тип операции должен выполняться, на каком регистре и какая команда выполняется следующей. Для того чтобы можно было использовать ту же самую схему для всех команд, введем формат
(тип, регистр, правильный выход, неправильный выход). Обозначая команды «останов» («stop»), «больше», «меньше» и «равно нулю» через 0, 1, 2 и 3 соответственно, получаем Ф-уровневое кодирование типичных утверждений, как показано ниже:
Ф (Ri – Ri + 1 then go to у) = 213i5y7y
Ф (Ri – Ri – 1 then go to y) = 223i5y7y
Ф (if Ri = 0 then go to у else go to z) = 233i5y7y
Ф (stop) = 20305070 = 1.
Поэтому команда Ф содержит всю информацию, необходимую для ее выполнения или, если необходимо, для перехода к следующему шагу выполнения программы.
Поскольку все строки в А* конечны, а блок-схема программы для машины с бесконечной памятью сверху не ограничена, то каждая отдельная программа имеет n утверждений, n ∈ N. Тогда можно распространить Ф на программы следующим образом:
где «prog» состоит из n помеченных утверждений, из которых 1-м является si.
Из Ф (prog) можно получить Ф(si) выделением компоненты pf, и, следовательно, разлагая это- значение на простые сомножители, мы можем узнать детали утверждения, помеченного номером i.
Используя подходящую «урезающую» функцию Ψ, мы получаем кодирование, которое является биекцией между ρ, множеством всех программ для машины с бесконечной памятью и V.
1.3. Проблема останова. Теперь мы можем доказать, что конструкции некоторых общих программ невозможны: не только потому, что мы не находим решения, но и потому, что решение может не существовать. Формальные доказательства ограничим рассмотрением двух основных случаев. Первое из доказательств получим, исходя из начальных принципов, а затем докажем, как второе выводится из первого, иллюстрируя, таким образом, общее использование техники сведения одних задач к другим. После этого сформулируем перечень проблем, относительно которых будет показано, что они неразрешимы подобным образом.
Строго говоря, все утверждения, приведенные ниже, относятся к программам па машине с бесконечной памятью со специальным кодированием функций Ψ1 и Ψ2. Однако, задав произвольную «универсальную» машину и подходящий язык программирования, мы в состоянии построить адекватные кодирующие функции, и, следовательно, полученные результаты применимы п в общем случае.
Первой задачей, относительно которой мы докажем ее неразрешимость, является задача самоприменимости.
Теорема. Для машины с бесконечной памятью не существует программы, которая для любой заданной программы А при ее кодировании а = Ψ1 (A) будет останавливаться с выходным значением 0, если А (а) останавливается (т. е. программа А останавливается, если входное значение равно а), и с выходным значением 1, если А (а) не останавливается.
Доказательство. Будем строить доказательство от противного. Предположим, что такая программа существует; назовем ее В. Итак, В(а) останавливается с результатом, равным 0, если а приводит программу А к останову, и с результатом, равным 1, если а не приводит к останову А.
И
зменим
программу
В
следующим образом. Заменим команду
останова условной петлей, так что,
если выходной регистр имеет значение
0, мы обходим эту команду; в противном
случае — останов. Назовем эту программу
С
(рис. 9.7). Имеем следующее:
С
(х) останавливается (и имеет то же самое
выходное значение, что и
В(х))
тогда и только тогда, когда выходное
значение равно 1.
Рассматривая С при входном значении с = Ψ(С), получаем противоречие: так С(с) останавливается тогда и только тогда, когда С(с) = В(с)= 1, то В(с)= 1 тогда и только тогда, когда С(с) не останавливается. Отсюда следует, что С не может существовать, а поэтому и В не существует. //
Предыдущее доказательство по стилю подобно тому, которое использовалось, чтобы опровергнуть существование множества Рассела в примере 1.1 гл. 1. Кажется, что оно носит тривиальный характер, однако, подобно многим другим кратким математическим рассуждениям, является достаточно тонким. Следует на это обратить внимание перед тем, как использовать этот факт при доказательстве более общего результата — неразрешимости проблемы останова.
Теорема. Для машины с бесконечной памятью не существует программы, которая по произвольным входным данным, представляющим программу и ее данные, будет определять, останавливается программа на этих данных или нет.
Доказательство. Покажем, что если бы такая программа существовала, то мы могли бы путем выбора правильных входных данных решить проблему самоприменимости. Поскольку последнее невозможно, то отсюда будет следовать, что решение проблемы останова также невозможно.
Очевидно, что программа требует ввода двух сортов данных: a(=Ψ1(A))—кода программы А и x(=Ψ2(X)), где X—входные данные для А. Это может быть закодировано обычным путем — с использованием двух различных простых чисел р и q. Пусть входные данные будут paqx, и предположим, что существует программа для решения нашей задачи; назовем ее В. Тогда B(paqx) останавливается с результатом 1, если А(Х) останавливается, и останавливается с результатом 0, если А(Х) не останавливается. В этом случае из В можно создать новую программу С, добавляя к началу В последовательность команд, которые превращают содержимое i-го входного элемента в (pq)t. Тогда
Следовательно, С решает проблему самоприменимости, о которой мы знаем, что она неразрешима. Следовательно, С не существует, а поэтому не существует и В. //
Проблема останова является классическим результатом о неразрешимости в теории компьютеров. Приведенное здесь доказательство использует общую технику сведения одной задачи к другой. Далее показывается, что первая задача неразрешима, поскольку если бы это было не так, то была бы разрешима и другая задача, о которой известно, что она неразрешима.
Используя этот принцип (и соответствующие конструкции, детали которых здесь опускаются), можно показать, что многие другие проблемы также неразрешимы. В частности, неразрешимы следующие проблемы:
а) останавливается или нет произвольная программа, если входное значение равно 0;
б) останавливается или нет произвольная программа при любых входных данных;
в) выполнено ли равенство L(G)= для произвольной контекстно-зависимой грамматики G;
г) выполнено ли равенство L(G1) L(G2) = , где G1 и G2 — произвольные контекстно-свободные грамматики;
д) выполнено ли равенство L(G)=T*, где G = (N, Т, Р, S)-— произвольная контекстно-свободная грамматика;
е) выполнено ли равенство L(G1) = L(G2), где G1 и G2 — произвольные контекстно-свободные грамматики;
ж) является ли произвольная контекстно-свободная грамматика неоднозначной.
1.4. «Расширенная» машина. Насколько реалистичной является машина с бесконечной памятью в качестве модели компьютера? Память такой машины, очевидно, превосходит память любой существующей машины, которая подвергается ограничениям как на число регистров, так и на значения, которые могут содержаться в каждом регистре. Однако эта идеализация в пределах машины с неограниченной памятью расширяет, а не ограничивает возможности машины. Существуют ли аспекты в компьютерных системах, которые не могут быть смоделированы на машине с неограниченной памятью?
Мы утверждаем, что нет. Хотя формально мы не можем доказать это высказывание, остановимся на основных моментах такой «расширенной» машины, которая может моделировать все нужные свойства, хотя в качестве абстрактной модели она применяется редко.
Нас будут интересовать следующие свойства:
а) большой набор операций;
б) более широкий набор внешних типов данных;
в) массивы;
г) стеки;
д) более сильные команды управления;
е) общий механизм управления;
ж) рекурсия.
В пп. 1.1 и 1.2 мы показали, как в случае а) можно добавить арифметические операции к множеству команд, а в случае б), добавляя простые периферийные устройства (для того чтобы иметь возможность работать с символами, которых пет в машине с неограниченной памятью), можно расширить диапазон представлений входных и выходных данных над произвольным алфавитом.
Остановимся па работе с массивами. Предположим, что нам необходим массив с десятью компонентами А [1], ... ,А [10] и что содержимое этих регистров суть a1, ..., a10. С помощью методики, которая уже использовалась, мы можем сохранить эти значения, вводя величину
Рис.
9.8
прямого кодирования для программ Ф (а не Ψ), мы можем закодировать всю программу одним значением, которое запоминается в регистре. Разложение на простые множители затем может использоваться для выделения кода «следующего утверждения». Это моделирует свойство ж), а вместе с использованием стеков
Р
ис.
9.8
дает возможность рекурсии з). Следовательно, используя машину с неограниченной памятью, можно произвести «расширение» путем добавления этих свойств. Однако в действительности такие расширения носят лишь внешний характер, а результирующая система может быть смоделирована с использованием другой машины с неограниченной памятью, у которой блок-схема программы состоит из простых команд увеличения, уменьшения и «равенства пулю».
Упражнение 9.1.
1. Построить на машине с неограниченной памятью блок-схему программы, осуществляющей проверку «R1 = m».
2. Спроектировать машину с неограниченной памятью, которая, если дана программа, закодированная в До, будет выполнять эту программу. (За основу взять кодирующую схему из п. 1.2.)
