Главная страница

А. шень Издание шестое, дополненное Москва


Скачать 1.62 Mb.
НазваниеА. шень Издание шестое, дополненное Москва
Дата17.09.2022
Размер1.62 Mb.
Формат файлаpdf
Имя файлаshen-progbook.pdf
ТипКнига
#681926
страница1 из 19
  1   2   3   4   5   6   7   8   9   ...   19

А. ШЕНЬ
ðòïçòáííéòï÷áîéå
ÔÅÏÒÅÍÙ É ÚÁÄÁÞÉ
Издание шестое, дополненное
Москва
Издательство МЦНМО
2017

УДК 519.671
ББК 22.18
Ш47
Шень А.
Ш47
Программирование: теоремы и задачи. | 6-е изд., дополнен- ное. | М.: МЦНМО, 2017. | 320 с.: ил.
ISBN 978-5-4439-0685-0
Книга содержит задачи по программированию различной трудности.
Большинство задач приводятся с решениями. Цель книги | научить основ- ным методам построения корректных и быстрых алгоритмов.
Для учителей информатики, старшеклассников, студентов младших кур- сов высших учебных заведений. Пособие может быть использовано на круж- ковых и факультативных занятиях в общеобразовательных учреждениях, в школах с углублённым изучением математики и информатики, а также в иных целях, не противоречащих законодательству РФ.
Предыдущее издание книги вышло в 2014 г.
ББК 22.18
ISBN 978-5-4439-0685-0
c

А. Шень, 1995, 2017

Несколько замечаний вместо предисловия
Книга написана по материалам занятий программированием со школьниками математических классов школы Ђ 57 г. Москвы и студентами младших курсов (Московский государственный университет, Независимый московский университет, университет г. Uppsala, Швеция).
Книга написана в убеждении, что программирование имеет свой предмет,
не сводящийся ни к конкретным языкам и системам, ни к методам построения быстрых алгоритмов.
Кто-то однажды сказал, что можно убедить в правильности алгоритма,
но не в правильности программы. Одна из целей книги | попытаться продемонстрировать, что это не так.
В принципе, возможность практического исполнения программ не является непременным условием изучения программирования. Однако она является сильнейшим стимулом | без такого стимула вряд ли у кого хватит интереса и терпения.
Выбранный жанр книги по необходимости ограничивает её
«программированием в малом», оставляя в стороне необходимую часть программистского образования | работу по модификации больших программ. Автор продолжает мечтать о наборе учебных программных систем эталонного качества, доступных для модификации школьниками.
Кажется, Хоар сказал, что эстетическая прелесть программы | это не архитектурное излишество, а то, что отличает в программировании успех от неудачи. Если, решая задачи из этой книги, читатель почувствует прелесть хорошо написанной программы, в которой «ни убавить, ни прибавить», и сомнения в правильности которой кажутся нелепыми, то автор будет считать свою цель достигнутой.
Характер глав различен: в одних предлагается набор мало связанных друг с другом задач с решениями, в других по существу излагается один-единственный алгоритм. Темы глав во многом пересекаются, и мы предпочли кое-какие повторения формальным ссылкам.
Уровень трудности задач и глав весьма различен. Мы старались включить как простые задачи, которые могут быть полезны для начинающих, так и трудные задачи, которые могут посадить в лужу сильного школьника. (Хоть и редко, но это бывает полезно.)

В качестве языка для записи программ был выбран паскаль. Он достаточно прост и естествен, имеет неплохие реализации (например,
старые компиляторы Turbo Pascal фирмы Borland были выложены для бесплатного скачивания) и позволяет записать решения всех рассматриваемых задач. Возможно, Модула-2 или Оберон были бы более изящным выбором, но они менее доступны.
Практически все задачи и алгоритмы, разумеется, не являются новыми.
(В некоторых редких случаях приведены ссылки на конкретную книгу или конкретного человека. См. также список книг для дальнейшего чтения.)
Вместе с тем мы надеемся, что в некоторых случаях алгоритмы
(и особенно доказательства) изложены более коротко и отчётливо.
Это не только и не столько учебник для школьника, сколько справочник и задачник для преподавателя, готовящегося к занятию.
Об «авторских правах»: право формулировать задачу и объяснять её
решение является неотчуждаемым естественным правом всякого, кто на это способен. В соответствии с этим текст является свободно распространяемым. Адреса автора: shen@mccme.ru, sasha.shen@gmail.com,
alexander.shen@lirmm.fr. Сказанное относится к русскому тексту; все права на переводы переданы издательству Springer.
При подготовке текста использовалась (свободно распространяемая)
версия L
A
TEXа, включающая стилевые файлы, составленные
С. М. Львовским (см. ftp://ftp.mccme.ru/pub/tex/).
Я рад случаю поблагодарить всех, с кем имел честь сотрудничать, препо- давая программирование, особенно тех, кто был «по другую сторону барри- кады», а также всех приславших мне замечания и исправления (специаль- ная благодарность | Ю. В. Матиясевичу). Автор благодарит В. Шувалова за хлопоты по вёрстке, а также издательство МЦНМО. Благодарю так- же Институт проблем передачи информации РАН, Американское матема- тическое общество (фонд помощи бывшему СССР), фонд Сороса, универ- ситет г. Бордо, фонд «Культурная инициатива», фонды STINT (Швеция),
CNRS (Франция), Ecole Normale (Лион, Франция), LIF (Марсель, Франция),
LIRMM (Монпелье, Франция), университет г. Уппсала (Швеция), агент- ство ANR (грант RaCAF, ANR-15-CE40-0016-01), Российский фонд фунда- ментальных исследований (гранты 02-01-22001 НЦНИа, 03-01-00475 и дру- гие), а также Совет поддержки научных школ при Президенте РФ (грант
НШ-358.2003.1 ) за поддержку. Вместе с тем содержание книги отражает точку зрения автора, за ошибки которого указанные организации и лица ответственности не несут (и наоборот).

Содержание
1. Переменные, выражения, присваивания
8 1.1. Задачи без массивов
8 1.2. Массивы
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 1.3. Индуктивные функции (по А. Г. Кушниренко)
. . . . . . . 38 2. Порождение комбинаторных объектов
43 2.1. Размещения с повторениями
. . . . . . . . . . . . . . . . . 43 2.2. Перестановки
. . . . . . . . . . . . . . . . . . . . . . . . . . 44 2.3. Подмножества
. . . . . . . . . . . . . . . . . . . . . . . . . 45 2.4. Разбиения
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 2.5. Коды Грея и аналогичные задачи
. . . . . . . . . . . . . . 49 2.6. Несколько замечаний
. . . . . . . . . . . . . . . . . . . . . 55 2.7. Подсчёт количеств
. . . . . . . . . . . . . . . . . . . . . . . 57 3. Обход дерева. Перебор с возвратами
60 3.1. Ферзи, не бьющие друг друга: обход дерева позиций
. . . 60 3.2. Обход дерева в других задачах
. . . . . . . . . . . . . . . 70 4. Сортировка
72 4.1. Квадратичные алгоритмы
. . . . . . . . . . . . . . . . . . 72 4.2. Алгоритмы порядка 𝑛 log 𝑛
. . . . . . . . . . . . . . . . . . 73 4.3. Применения сортировки
. . . . . . . . . . . . . . . . . . . . 80 4.4. Нижние оценки для числа сравнений при сортировке
. . . 82 4.5. Родственные сортировке задачи
. . . . . . . . . . . . . . . 84 5. Конечные автоматы и обработка текстов
90 5.1. Составные символы, комментарии и т. п.
. . . . . . . . . . 90 5.2. Ввод чисел
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

6
Содержание
6. Типы данных
96 6.1. Стеки
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 6.2. Очереди
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 6.3. Множества
. . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.4. Разные задачи
. . . . . . . . . . . . . . . . . . . . . . . . . 115 7. Рекурсия
117 7.1. Примеры рекурсивных программ
. . . . . . . . . . . . . . 117 7.2. Рекурсивная обработка деревьев
. . . . . . . . . . . . . . . 120 7.3. Порождение комбинаторных объектов, перебор
. . . . . . 123 7.4. Другие применения рекурсии
. . . . . . . . . . . . . . . . . 127 8. Как обойтись без рекурсии
135 8.1. Таблица значений (динамическое программирование)
. . 135 8.2. Стек отложенных заданий
. . . . . . . . . . . . . . . . . . 140 8.3. Более сложные случаи рекурсии
. . . . . . . . . . . . . . . 143 9. Разные алгоритмы на графах
146 9.1. Кратчайшие пути
. . . . . . . . . . . . . . . . . . . . . . . 146 9.2. Связные компоненты, поиск в глубину и ширину
. . . . . 152 9.3. Сети, потоки и разрезы
. . . . . . . . . . . . . . . . . . . . 157 10. Сопоставление с образцом
178 10.1. Простейший пример
. . . . . . . . . . . . . . . . . . . . . . 178 10.2. Повторения в образце | источник проблем
. . . . . . . . 181 10.3. Вспомогательные утверждения
. . . . . . . . . . . . . . . . 183 10.4. Алгоритм Кнута { Морриса { Пратта
. . . . . . . . . . . . 183 10.5. Алгоритм Бойера { Мура
. . . . . . . . . . . . . . . . . . . 186 10.6. Алгоритм Рабина
. . . . . . . . . . . . . . . . . . . . . . . . 188 10.7. Более сложные образцы и автоматы
. . . . . . . . . . . . . 190 10.8. Суффиксные деревья
. . . . . . . . . . . . . . . . . . . . . . 197 11. Анализ игр
210 11.1. Примеры игр
. . . . . . . . . . . . . . . . . . . . . . . . . . 210 11.2. Цена игры
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 11.3. Вычисление цены: полный обход
. . . . . . . . . . . . . . . 220 11.4. Альфа-бета-процедура
. . . . . . . . . . . . . . . . . . . . . 223 11.5. Ретроспективный анализ
. . . . . . . . . . . . . . . . . . . 227

Содержание
7 12. Оптимальное кодирование
230 12.1. Коды
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 12.2. Неравенство Крафта { Макмиллана
. . . . . . . . . . . . . 231 12.3. Код Хаффмана
. . . . . . . . . . . . . . . . . . . . . . . . . 235 12.4. Код Шеннона { Фано
. . . . . . . . . . . . . . . . . . . . . . 237 13. Представление множеств. Хеширование
241 13.1. Хеширование с открытой адресацией
. . . . . . . . . . . . 241 13.2. Хеширование со списками
. . . . . . . . . . . . . . . . . . . 244 14. Деревья. Сбалансированные деревья
250 14.1. Представление множеств с помощью деревьев
. . . . . . . 250 14.2. Сбалансированные деревья
. . . . . . . . . . . . . . . . . . 258 15. Контекстно-свободные грамматики
269 15.1. Общий алгоритм разбора
. . . . . . . . . . . . . . . . . . . 269 15.2. Метод рекурсивного спуска
. . . . . . . . . . . . . . . . . 275 15.3. Алгоритм разбора для LL(1)-грамматик
. . . . . . . . . . 286 16. Синтаксический разбор слева направо (LR)
294 16.1. LR-процессы
. . . . . . . . . . . . . . . . . . . . . . . . . . 294 16.2. LR(0)-грамматики
. . . . . . . . . . . . . . . . . . . . . . . 300 16.3. SLR(1)-грамматики
. . . . . . . . . . . . . . . . . . . . . . 306 16.4. LR(1)-грамматики, LALR(1)-грамматики
. . . . . . . . . 307 16.5. Общие замечания о разных методах разбора
. . . . . . . . 310
Книги для чтения
312
Предметный указатель
313
Указатель имён
319

1. ПЕРЕМЕННЫЕ,
ВЫРАЖЕНИЯ,
ПРИСВАИВАНИЯ
1.1. Задачи без массивов
1.1.1. Даны две целые переменные a, b. Составьте фрагмент про- граммы, после исполнения которого значения переменных поменялись бы местами (новое значение a равно старому значению b и наоборот).
Решение. Введём дополнительную целую переменную t.
t := a;
a := b;
b := t;

Попытка обойтись без дополнительной переменной, написав a := b;
b := a;
не приводит к цели (безвозвратно утрачивается начальное значение пе- ременной a).
1.1.2. Решите предыдущую задачу, не используя дополнительных переменных (и предполагая, что значениями целых переменных могут быть произвольные целые числа).
Решение. Начальные значения a и b обозначим a0, b0.
a := a + b; {a = a0 + b0, b = b0}
b := a - b; {a = a0 + b0, b = a0}
a := a - b; {a = b0, b = a0}


1.1. Задачи без массивов
9 1.1.3. Дано целое число а и натуральное (целое неотрицательное)
число n. Вычислите an. Другими словами, необходимо составить про- грамму, при исполнении которой значения переменных а и n не меняют- ся, а значение некоторой другой переменной (например, b) становится равным an. (При этом разрешается использовать и другие переменные.)
Решение. Введём целую переменную k, которая меняется от 0 до n,
причём поддерживается такое свойство: b = ak).
k := 0; b := 1;
{b = a в степени k}
while k <> n do begin k := k + 1;
b := b * a;
end;

Другое решение той же задачи:
k := n; b := 1;
{a в степени n = b * (a в степени k)}
while k <> 0 do begin k := k - 1;
b := b * a;
end;
1.1.4. Решите предыдущую задачу, если требуется, чтобы число действий (выполняемых операторов присваивания) было порядка log n
(то есть не превосходило бы 𝐶 log n для некоторой константы 𝐶; log n |
это степень, в которую нужно возвести 2, чтобы получить n).
Решение. Внесём некоторые изменения во второе из предложенных решений предыдущей задачи:
k := n; b := 1; c:=a;
{a в степени n = b * (c в степени k)}
while k <> 0 do begin if k mod 2 = 0 then begin k:= k div 2;
c:= c*c;
end else begin k := k - 1;
b := b * c;
end;
end;

10 1. Переменные, выражения, присваивания
Каждый второй раз (не реже) будет выполняться первый вариант оператора выбора (если k нечётно, то после вычитания единицы ста- новится чётным), так что за два цикла величина k уменьшается по крайней мере вдвое.

1.1.5. Даны натуральные числа а, b. Вычислите произведение a · b,
используя в программе лишь операции +, -, =, <>.
Решение.
k := 0; c := 0;
{инвариант: c = a * k}
while k <> b do begin k := k + 1;
c := c + a;
end;
{c = a * k и k = b, следовательно, c = a * b}

1.1.6. Даны натуральные числа а и b. Вычислите их сумму а + b.
Используйте операторы присваивания лишь вида

переменная1⟩ := ⟨переменная2⟩,

переменная⟩ := ⟨число⟩,

переменная1⟩ := ⟨переменная2⟩ + 1.
Решение.
{инвариант: c = a + k}

1.1.7. Дано натуральное (целое неотрицательное) число а и целое положительное число d. Вычислите частное q и остаток r при делении а на d, не используя операций div и mod.
Решение. Согласно определению, a = q · d + r, 0 6 r < d.
{a >= 0; d > 0}
r := a; q := 0;
{инвариант: a = q * d + r, 0 <= r}
while not (r < d) do begin
{r >= d}
r := r - d; {r >= 0}
q := q + 1;
end;


1.1. Задачи без массивов
11 1.1.8. Дано натуральное n, вычислите n! (0! = 1, n! = n · (n − 1)!). 
1.1.9. Последовательность Фибоначчи определяется так: a0 = 0,
a1 = 1, ak = ak-1 + ak-2 при k > 2. Дано n, вычислите an.

1.1.10. Та же задача, если требуется, чтобы число операций было пропорционально log n. (Переменные должны быть целочисленными.)
[Указание. Пара соседних чисел Фибоначчи получается из предыду- щей умножением на матрицу




1 1 1 0




| так что задача сводится к возведению матрицы в степень n. Это можно сделать за 𝐶 log n действий тем же способом, что и для чи- сел.]

1.1.11. Дано натуральное n, вычислите
1 0!
+
1 1!
+ . . . +
1
n!

1.1.12. То же, если требуется, чтобы количество операций (выпол- ненных команд присваивания) было бы порядка n (не более 𝐶n для не- которой константы 𝐶).
Решение. Инвариант: sum = 1/1! + . . . + 1/k!, last = 1/k! (важно не вычислять заново каждый раз k!).

1.1.13. Даны два натуральных числа a и b, не равные нулю одно- временно. Вычислите НОД(a,b) | наибольший общий делитель а и b.
Решение. Вариант 1.
if a < b then begin k := a;
end else begin k := b;
end;
{k = min (a,b)}
{инвариант: никакое число, большее k, не является общим делителем (оно больше одного из чисел a,b)}
while not ((a mod k = 0) and (b mod k = 0)) do begin k := k - 1;
end;
{k - общий делитель, большие - нет}

12 1. Переменные, выражения, присваивания
Вариант 2 (алгоритм Евклида). Будем считать, что НОД(0,0)=0.
Тогда НОД(a,b) = НОД(a-b,b) = НОД(a,b-a); НОД(a,0) = НОД(0,a) = a для всех a, b > 0.
m := a; n := b;
{инвариант: НОД (a,b) = НОД (m,n); m,n >= 0 }
while not ((m=0) or (n=0)) do begin if m >= n then begin m := m - n;
end else begin n := n - m;
end;
end;
{m = 0 или n = 0}
if m = 0 then begin k := n;
end else begin {n = 0}
k := m;
end;

1.1.14. Напишите модифицированный вариант алгоритма Евклида,
использующий соотношения НОД(a,b) = НОД(a mod b, b) при a > b,
НОД(a,b) = НОД(a, b mod a) при b > a.

1.1.15. Даны натуральные a и b, не равные 0 одновременно. Найдите d = НОД(a,b) и такие целые x и y, что d = a · x + b · y.
Решение. Добавим в алгоритм Евклида переменные p, q, r, s и впи- шем в инвариант условия m = p*a+q*b; n = r*a+s*b.
m:=a; n:=b; p := 1; q := 0; r := 0; s := 1;
{инвариант: НОД (a,b) = НОД (m,n); m,n >= 0
m = p*a + q*b; n = r*a + s*b.}
while not ((m=0) or (n=0)) do begin if m >= n then begin m := m - n; p := p - r; q := q - s;
end else begin n := n - m; r := r - p; s := s - q;
end;
end;
if m = 0 then begin k :=n; x := r; y := s;
end else begin k := m; x := p; y := q;
end;


1.1. Задачи без массивов
13 1.1.16. Решите предыдущую задачу, используя в алгоритме Евкли- да деление с остатком.

1.1.17. (Э. Дейкстра) Добавим в алгоритм Евклида дополнительные переменные u, v, z:
m := a; n := b; u := b; v := a;
{инвариант: НОД (a,b) = НОД (m,n); m,n >= 0 }
while not ((m=0) or (n=0)) do begin if m >= n then begin m := m - n; v := v + u;
end else begin n := n - m; u := u + v;
end;
end;
if m = 0 then begin z:= v;
end else begin {n=0}
z:= u;
end;
Докажите, что после исполнения алгоритма значение z равно удвоен- ному наименьшему общему кратному чисел a, b: z = 2 · НОК(a,b).
Решение. Заметим, что величина m · u + n · v не меняется в ходе вы- полнения алгоритма. Остаётся воспользоваться тем, что вначале она равна 2ab и что НОД(a, b) · НОК(a, b) = ab.

1.1.18. Напишите вариант алгоритма Евклида, использующий соот- ношения
НОД(2a, 2b) = 2 · НОД(a, b),
НОД(2a, b) = НОД(a, b) при нечётном b,
не включающий деления с остатком, а использующий лишь деление на 2
и проверку чётности. (Число действий должно быть порядка log k для исходных данных, не превосходящих k.)
Решение.
m:= a; n:=b; d:=1;
{НОД(a,b) = d * НОД(m,n)}
while not ((m=0) or (n=0)) do begin if (m mod 2 = 0) and (n mod 2 = 0) then begin d:= d*2; m:= m div 2; n:= n div 2;
end else if (m mod 2 = 0) and (n mod 2 = 1) then begin

14 1. Переменные, выражения, присваивания m:= m div 2;
end else if(m mod 2 = 1) and (n mod 2 = 0) then begin n:= n div 2;
end else if (m mod 2=1) and (n mod 2=1) and (m>=n) then begin m:= m-n;
end else if (m mod 2=1) and (n mod 2=1) and (m<=n) then begin n:= n-m;
end;
end;
{m=0 => ответ=d*n; n=0 => ответ=d*m}
Оценка числа действий: каждое второе действие делит хотя бы одно из чисел m и n пополам.

1.1.19. Дополните алгоритм предыдущей задачи поиском x и y, для которых ax + by = НОД(a, b).
Решение. (Идея сообщена Д. Звонкиным.) Прежде всего заметим,
что одновременное деление a и b пополам не меняет искомых x и y. По- этому можно считать, что с самого начала одно из чисел a и b нечётно.
(Это свойство будет сохраняться и далее.)
Теперь попытаемся, как и раньше, хранить такие числа p, q, r, s, что m = ap + bq,
n = ar + bs.
Проблема в том, что при делении, скажем, m на 2 надо разделить p и q на 2, и они перестанут быть целыми (а станут двоично-рациональ- ными). Двоично-рациональное число естественно хранить в виде пары

числитель, показатель степени двойки в знаменателе⟩. В итоге мы по- лучаем d в виде комбинации a и b с двоично-рациональными коэффи- циентами. Иными словами, мы имеем

  1   2   3   4   5   6   7   8   9   ...   19


написать администратору сайта