Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Различные авторы Весь текст 991.22 Kb

Программирование в теоремах и задачах

Предыдущая страница Следующая страница
1 ... 35 36 37 38 39 40 41  42 43 44 45 46 47 48 ... 85
     t:=0; generate;

     7.3.2. Написать программу, которая печатала бы все переста-
новки чисел 1..n по одному разу.

     Решение. Программа оперирует с массивом a[1]..a[n], в кото-
ром  хранится  перестановка  чисел  1..n.  Рекурсивная процедура
generate в такой ситуации печатает все перестановки, которые  на
первых  t позициях совпадают с перестановкой a; по выходе из нее
переменные t и a имеют те же значения, что и до входа.  Основная
программа такова:

    for i:=1 to n do begin a[i]:=i; end;
    t:=0;
    generate;

вот описание процедуры:

     procedure generate;
     | var i,j : integer;
     begin
     | if t = n then begin
     | | for i:=1 to n do begin
     | | | write(a[i]);
     | | end;
     | | writeln;
     | end else begin {t < n}
     | | for j:=t+1 to n do begin
     | | | поменять местами a[t+1] и a[j]
     | | | t:=t+1;
     | | | generate;
     | | | t:=t-1;
     | | | поменять местами a[t+1] и a[j]
     | | end;
     | end;
     end;

     7.3.3. Напечатать все возрастающие последовательности длины
k, элементами которых являются натуральные  числа  от  1  до  n.
(Предполагается, что k не превосходит n - иначе таких последова-
тельностей не существует.)

     Решение. Программа оперирует с массивом a[1]..a[k] и  целой
переменной  t. Предполагая, что a[1]..a[t] - возрастающая после-
довательность чисел натуральных чисел из отрезка 1..n, рекурсив-
но определенная процедура generate печатает все ее  возрастающие
продолжения длины k.

     procedure generate;
     | var i: integer;
     begin
     | if t = k then begin
     | | печатать a[1]..a[k]
     | end else begin
     | | t:=t+1;
     | | for i:=a[t-1]+1 to t-k+n do begin
     | | | a[t]:=i;
     | | | generate;
     | | end;
     | | t:=t-1;
     | end;
     end;

     Замечание. Цикл for мог бы иметь верхней границей n (вместо
t-k+n). Наш вариант экономит часть работы,  учитывая  тот  факт,
что  предпоследний  (k-1-ый)  член  не  может  превосходить n-1,
k-2-ой член не может превосходить n-2 и т.п.
     Основная программа теперь выглядит так:

        t:=1;
        for j:=1 to 1-k+n do begin
        | a[1]:=j;
        | generate;
        end;

Можно было бы добавить к массиву a слева еще и a[0]=0,  положить
t=0 и ограничиться единственным вызовом процедуры generate.

     7.3.4.  Перечислить все представления положительного целого
числа n в виде суммы последовательности невозрастающих целых по-
ложительных слагаемых.

     Решение.  Программа  оперирует  с  массивом a[1..n] (макси-
мальное число слагаемых равно n) и с целой переменной t. Предпо-
лагая, что a[1],...,a[t] - невозрастающая последовательность це-
лых чисел, сумма которых не превосходит  n,  процедура  generate
печатает  все  представления  требуемого  вида, продолжающие эту
последовательность. Для экономии вычислений сумма  a[1]+...+a[t]
хранится в специальной переменной s.

     procedure generate;
     | var i: integer;
     begin
     | if s = n then begin
     | | печатать последовательность a[1]..a[t]
     | end else begin
     | | for i:=1 to min(a[t], n-s) do begin
     | | | t:=t+1;
     | | | a[t]:=i;
     | | | s:=s+i;
     | | | generate;
     | | | s:=s-i;
     | | | t:=t-1;
     | | end;
     | end;
     end;

Основная программа при этом может быть такой:

     t:=1;
     for j:=1 to n do begin
     | a[1]:=j
     | s:=j;
     | generate;
     end;

     Замечание.  Можно немного сэконмить, вынеся операции увели-
чения и уменьшения t из цикла, а также не возвращая s каждый раз
к исходному значению (а увеличивая его на 1 и возвращая к исход-
ному значению в конце). Кроме того,  добавив  фиктивный  элемент
a[0]=n, можно упростить основную программу:

     t:=0; s:=0; a[0]:=n; generate;

     7.3.5.  Написать  рекурсивную  программу обхода дерева (ис-
пользуя те же команды и проверки, что и в главе про обход  дере-
ва).

     Решение.  Процедура  обработать_над обрабатывает все листья
над текущей вершиной и заканчивает работу в той же вершине,  что
и начала. Вот ее рекурсивное описание:

     procedure обработать_над;
     begin
     | if есть_сверху then begin
     | | вверх_налево;
     | | обработать_над;
     | | while есть_справа do begin
     | | | вправо;
     | | | обработать_над;
     | | end;
     | | вниз;
     | end else begin
     | | обработать;
     | end;
     end;

     7.4. Другие применения рекурсии

     Топологическая сортировка. Представим  себе  n  чиновников,
каждый  из  которых  выдает справки определенного вида. Мы хотим
получить все эти справки,  соблюдая  ограничения,  установленные
чиновниками.  Ограничения состоят в том, что у каждого чиновника
есть список справок, которые нужно собрать  перед  обращением  к
нему.  Дело  безнадежно,  если  схема  зависимостей  имеет  цикл
(справку  A  нельзя получить без B, B без C,..., Y без Z и Z без
A). Предполагая, что такого цикла нет, требуется составить план,
указывающий один из возможных порядков получения справок.

     Изображая чиновников точками, а  зависимости  -  стрелками,
приходим  к такой формулировке. Имеется n точек, пронумерованных
от 1 до n. Из каждой точки ведет несколько (возможно, 0) стрелок
в другие точки. (Такая картинка называется ориентированным  гра-
фом.)  Циклов нет. Требуется расположить вершины графа (точки) в
таком порядке, чтобы конец любой стрелки предшествовал ее  нача-
лу. Эта задача называется топологической сортировкой.

     7.4.1. Доказать, что это всегда возможно.

     Решение.  Из  условия  отсутствия циклов вытекает, что есть
вершина, из которой вообще не выходит стрелок (иначе можно  дви-
гаться  по  стрелкам, пока не зациклимся). Ее будем считать пер-
вой. Выкидывая все стрелки, в нее ведущие, мы  сводим  задачу  к
графу с меньшим числом вершин и продолжаем рассуждение по индук-
ции.

     7.4.2.  Предположим,  что  ориентированный  граф без циклов
хранится в такой форме: для каждого i от 1 до n в num[i] хранит-
ся число выходящих из i стрелок, в adr[i][1],..., adr[i][num[i]]
- номера вершин, куда эти стрелки ведут. Составить (рекурсивный)
алгоритм, который производит топологическую сортировку не  более
чем за C*(n+m) действий, где m - число ребер графа (стрелок).

     Замечание.  Непосредственная  реализация  приведенного выше
доказательства существования не дает требуемой оценки; ее прихо-
дится немного подправить.

     Решение. Наша программа будет  печатать  номера  вершин.  В
массиве  printed: array[1..n] of boolean мы будем хранить сведе-
ния о том, какие вершины напечатаны (и корректировать их  однов-
ременно  с  печатью  вершины).  Будем говорить, что напечатанная
последовательность вершин корректна, если никакая вершина не на-
печатана дважды и для любого номера i, входящего в эту последос-
тельность,  все вершины, в которые ведут стрелки из i, напечата-
ны, и притом до i.

     procedure add (i: 1..n);
     | {дано: напечатанное корректно;}
     | {надо: напечатанное корректно и включает вершину i}
     begin
     | if printed [i] then begin {вершина i уже напечатана}
     | | {ничего делать не надо}
     | end else begin
     | | {напечатанное корректно}
     | | for j:=1 to num[i] do begin
     | | | add(adr[i][j]);
     | | end;
     | | {напечатанное корректно, все вершины, в которые из
     | |  i ведут стрелки, уже напечатаны - так что можно
     | |  печатать i, не нарушая корректности}
     | |  if not printed[i] then begin
     | |  | writeln(i); printed [i]:= TRUE;
     | |  end;
     | end;
     end;

Основная программа:

     for i:=1 to n do begin
     | printed[i]:= FALSE;
     end;
     for i:=1 to n do begin
     | add(i)
     end;

     7.4.3.  В  приведенной  программе можно выбросить проверку,
заменив
          if not printed[i] then begin
          | writeln(i); printed [i]:= TRUE;
          end;
на
          writeln(i); printed [i]:= TRUE;
Почему? Как изменится спецификация процедуры?

     Решение.  Спецификацию можно выбрать такой:
       дано: напеватанное корректно
       надо: напечатанное корректно и включает вершину i;
             все вновь напечатанные вершины доступны из i.

     7.4.4. Где использован тот факт, что граф не имеет циклов?

     Решение.  Мы опустили доказательство конечности глубины ре-
курсии. Для каждой вершины  рассмотрим  ее  "глубину"  -  макси-
мальную длину пути по стрелкам, из нее выходящего.  Условие  от-
сутствия циклов гарантирует, что эта величина конечна. Из верши-
ны  нулевой глубины стрелок не выходит. Глубина конца стрелки по
крайней мере на 1 меньше, чем глубина начала. При работе  проце-
дуры  add(i)  все рекурсивные вызовы add(j) относятся к вершинам
меньшей глубины.

     Связная  компонента  графа.  Неориентированный граф - набор
точек (вершин), некоторые из которых соединены  линиями  (ребра-
ми). Неориентированный граф можно считать частным случаем ориен-
тированного графа, в котором для каждой стрелки есть обратная.
     Связной компонентой вершины i называется множество всех тех
вершин, в которые можно попасть из i, идя по ребрам графа. (Пос-
кольку  граф неориентированный, отношение "j принадлежит связной
компоненте i" является отношением эквивалентности.)

     7.4.5. Дан неориентированный граф (для каждой вершины  ука-
зано  число  соседей  и массив номеров соседей, как в предыдущей
задаче). Составить алгоритм, который по заданному i печатает все
вершины связной компоненты i по одному разу (и только их). Число
действий не должно превосходить C*(общее число вершин и ребер  в
связной компоненте).

     Решение.  Программа  в  процессе работы будет "закрашивать"
некоторые вершины графа. Незакрашенной частью графа будем  назы-
вать то, что останется, если выбросить все закрашенные вершины и
ведущие в них ребра. Процедура add(i) закрашивает связную компо-
ненту  i в незакрашенном графе (и не делает ничего, если вершина
i уже закрашена).

     procedure  add (i:1..n);
     begin
     | if вершина i закрашена then begin
     | | ничего делать не надо
     | end else begin
     | | закрасить i (напечатать и пометить как закрашенную)
     | | для всех j, соседних с i
     | | | add(j);
     | | end;
     | end;
     end;

Докажем, что эта процедура действует правильно (в предположении,
что рекурсивные вызовы работают правильно). В самом деле,  ниче-
го, кроме связной компоненты незакрашенного графа, она закрасить
не  может. Проверим, что вся она будет закрашена. Пусть k - вер-
шина, доступная из вершины  i  по  пути  i-j-...-k,  проходящему
только по незакрашенным вершинам. Будем рассматривать только пу-
ти,  не  возвращающиеся  снова  в i. Из всех таких путей выберем
путь с наименьшим j (в порядке просмотра соседей в цикле в  про-
цедуре).  Тогда  при  рассмотрении предыдущих соседей ни одна из
вершин j-...-k не будет закрашена (иначе  j  не  было  бы  мини-
мальным) и потому k окажется в связной компоненте незакрашенного
графа к моменту вызова add(j). Что и требовалось.
     Чтобы установить конечность глубины рекурсии, заметим,  что
на каждом уровне рекурсии число незакрашенных вершин уменьшается
хотя бы на 1.
     Оценим число действий. Каждая вершина закрашивается не  бо-
лее  одного раза - при первым вызове add(i) с данным i. Все пос-
ледующие вызовы происходят при закрашивании соседей - количество
таких  вызовов  не  больше числа соседей - и сводятся к проверке
того, что вершина i уже закрашена. Первый  же  вызов  состоит  в
просмотре  всех  соседей  и  рекурсивных вызовах add(j) для всех
них. Таким образом, общее число действий, связанных  с  вершиной
i, не превосходит константы, умноженной на число ее соседей. От-
сюда и вытекает требуемая оценка.

     7.4.6.  Решить ту же задачу для ориентированного графа (на-
печатать все вершины, доступные из данной по стрелкам; граф  мо-
жет содержать циклы).

     Ответ.  Годится  по  существу  та же программа (строку "для
Предыдущая страница Следующая страница
1 ... 35 36 37 38 39 40 41  42 43 44 45 46 47 48 ... 85
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (1)

Реклама