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

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


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

100 6. Типы данных тически приходим к такому утверждению: последовательность круг- лых скобок правильна тогда и только тогда, когда в любом её началь- ном отрезке число закрывающихся скобок не превосходит числа откры- вающихся, а для всей последовательности эти числа равны.

6.1.3. Реализуйте с помощью одного массива два стека, суммар- ное количество элементов в которых ограничено длиной массива; все действия со стеками должны выполняться за время, ограниченное кон- стантой, не зависящей от длины стеков.
Решение. Стеки должны расти с концов массива навстречу друг другу: первый должен занимать места
Содержание[1] . . . Содержание[Длина1],
а второй |
Содержание[n] . . . Содержание[n-Длина2+1]
(вершины обоих стеков записаны последними).

6.1.4. Реализуйте k стеков с элементами типа T, общее количество элементов в которых не превосходит n, с использованием массивов сум- марной длины 𝐶(n + k), затрачивая на каждое действие со стеками
(кроме начальных действий, делающих все стеки пустыми) время не более некоторой константы 𝐶. (Как говорят, общая длина массивов должна быть 𝑂(m + n), a время на каждую операцию | 𝑂(1).)
Решение. Применяемый метод называется «ссылочной реализацией».
Он использует три массива:
Содержание: array [1..n] of T;
Следующий: array [1..n] of 0..n;
Вершина: array [1..k] of 0..n.
Удобно изображать массив Содержание как n ячеек с номера- ми 1 . . . n, каждая из которых содержит элемент типа T. Массив
Следующий изобразим в виде стрелок, проведя стрелку из i в j, ес- ли Следующий[i]=j. (Если Следующий[i]=0, стрелок из i не прово- дим.) Содержимое s-го стека (s ∈ 1 . . . k) хранится так: вершина равна
Содержание[Вершина[s]], остальные элементы s-го стека можно найти,
идя по стрелкам | до тех пор, пока они не кончатся. При этом
(s-й стек пуст) ⇔ Вершина[s]=0.

6.1. Стеки
101
Стрелочные траектории, выходящие из
Вершина[1], . . . , Вершина[k]
(из тех, которые не равны 0) не должны пересекаться. Помимо них,
нам понадобится ещё одна стрелочная траектория, содержащая все не- используемые в данный момент ячейки. Её начало мы будем хранить в переменной Свободная (равенство Свободная = 0 означает, что пусто- го места не осталось). Вот пример:
Вершина
Содержание
Свободная a
p q
d s
t v
w
Содержание a p q d s t v w
Следующий 3 0 6 0 0 2 5 4
Вершина 1 7
Свободная = 8
Стеки: первый содержит p, t, q, a (a | вершина); второй содержит s, v
(v | вершина).
procedure Начать_работу; {Делает все стеки пустыми}
var i: integer;
begin for i := 1 to k do begin
Вершина [i]:=0;
end;
for i := 1 to n-1 do begin
Следующий [i] := i+1;
end;
Следующий [n] := 0;

102 6. Типы данных
Свободная:=1;
end;
function Есть_место: boolean;
begin
Есть_место := (Свободная <> 0);
end;
procedure Добавить (t: T; s: integer);
{Добавить t к s-му стеку}
var i: 1..n;
begin
{Есть_место}
i := Свободная;
Свободная := Следующий [i];
Следующий [i] := Вершина [s];
Вершина [s] :=i;
Содержание [i] := t;
end;
function Пуст (s: integer): boolean;
{s-ый стек пуст}
begin
Пуст := (Вершина [s] = 0);
end;
procedure Взять (var t: T; s: integer);
{взять из s-го стека в t}
var i: 1..n;
begin
{not Пуст (s)}
i := Вершина [s];
t := Содержание [i];
Вершина [s] := Следующий [i];
Следующий [i] := Свободная;
Свободная := i;
end;
function Вершина_стека (s: integer): T;
{вершина s-го стека}
begin
Вершина_стека := Содержание[Вершина[s]];
end;


6.2. Очереди
103 6.2. Очереди
Значениями типа «очередь элементов типа T», как и для стеков, явля- ются последовательности значений типа T. Разница состоит в том, что берутся элементы не с конца, а с начала (а добавляются по-прежнему в конец).
Операции с очередями:

Сделать пустой (var x: очередь элементов типа T);

Добавить (t:T, var x: очередь элементов типа T);

Взять (var t:T, var x: очередь элементов типа T);

Пуста (x: очередь элементов типа T): boolean;

Очередной (x: очередь элементов типа T): T.
При выполнении команды «Добавить» указанный элемент добавля- ется в конец очереди. Команда «Взять» выполнима, лишь если очередь непуста, и забирает из неё первый (положенный туда раньше всех) эле- мент, помещая его в t. Значением функции «Очередной» (определённой для непустой очереди) является первый элемент очереди.
Английские названия стеков | Last In First Out (последним во- шёл | первым вышел), а очередей | First In First Out (первым вошёл |
первым вышел). Сокращения: LIFO, FIFO.
Реализация очередей в массиве
6.2.1. Реализуйте операции с очередью ограниченной длины так,
чтобы количество действий для каждой операции было ограничено кон- стантой, не зависящей от длины очереди.
Решение. Будем хранить элементы очереди в соседних элементах массива. Тогда очередь будет прирастать справа и убывать слева. По- скольку при этом она может дойти до края, свернём массив в окруж- ность.
Введём массив
Содержание: array [0..n-1] of T
и переменные
Первый: 0..n-1,
Длина : 0..n.

104 6. Типы данных
При этом элементами очереди будут
Содержание [Первый], Содержание [Первый+1], . . . ,
Содержание [Первый+Длина-1],
где сложение выполняется по модулю n. (Предупреждение. Если вместо этого ввести переменные Первый и Последний, значения которых | вы- четы по модулю n, то пустая очередь может быть спутана с очередью из n элементов.)
Операции выполняются так.
Сделать пустой:
Длина := 0;
Первый := 0;
Добавить элемент:
{Длина < n}
Содержание [(Первый + Длина) mod n] := элемент;
Длина := Длина + 1;
Взять элемент:
{Длина > 0}
элемент := Содержание [Первый];
Первый := (Первый + 1) mod n;
Длина := Длина - 1;
Пуста:
Длина = 0
Очередной:
Содержание [Первый]

6.2.2. (Сообщил А. Г. Кушниренко) Придумайте способ моделиро- вания очереди с помощью двух стеков (и фиксированного числа пере- менных типа T). При этом отработка n операций с очередью (начатых,
когда очередь была пуста) должна требовать порядка n действий.
Решение. Инвариант: стеки, составленные концами, образуют оче- редь. (Перечисляя элементы одного стека вглубь и затем элементы вто- рого наружу, мы перечисляем все элементы очереди от первого до по- следнего.) Ясно, что добавление сводится к добавлению к одному из

6.2. Очереди
105
стеков, а проверка пустоты | к проверке пустоты обоих стеков. Ес- ли мы хотим взять элемент, есть два случая. Если стек, где находится начало очереди, не пуст, то берём из него элемент. Если он пуст, то предварительно переписываем в него все элементы второго стека, ме- няя порядок (это происходит само собой при перекладывании из стека в стек) и сводим дело к первому случаю. Хотя число действий на этом шаге и не ограничено константой, но требование задачи выполнено,
так как каждый элемент очереди может участвовать в этом процессе не более одного раза.

6.2.3. Деком называют структуру, сочетающую очередь и стек:
класть и забирать элементы можно с обоих концов. Как реализовать дек ограниченного размера на базе массива так, чтобы каждая опера- ция требовала ограниченного числа действий?

6.2.4. (Сообщил А. Г. Кушниренко.) Имеется дек элементов типа T
и конечное число переменных типа T и целого типа. В начальном со- стоянии в деке некоторое число элементов. Составьте программу, после исполнения которой в деке остались бы те же самые элементы, а их чи- сло было бы в одной из целых переменных.
[Указание. (1) Элементы дека можно циклически переставлять, за- бирая с одного конца и помещая в другой. После этого, сделав столько же шагов в обратном направлении, можно вернуть всё на место. (2) Как понять, прошли мы полный круг или не прошли? Если бы какой-то эле- мент заведомо отсутствовал в деке, то можно было бы его подсунуть и ждать вторичного появления. Но таких элементов нет. Вместо этого можно для данного n выполнить циклический сдвиг на n дважды, под- сунув разные элементы, и посмотреть, появятся ли разные элементы через n шагов.]

Применение очередей
6.2.5. Напечатайте в порядке возрастания первые n натуральных чисел, в разложение которых на простые множители входят только чи- сла 2, 3, 5.
Решение. Введём три очереди x2, x3, x5, в которых будем хранить элементы, которые в 2 (3, 5) раз больше напечатанных, но ещё не на- печатаны. Определим процедуру procedure напечатать_и_добавить (t: integer);
begin

106 6. Типы данных writeln (t);
Добавить (2*t, x2);
Добавить (3*t, x3);
Добавить (5*t, x5);
end;
Вот схема программы:
...сделать x2, x3, x5 пустыми напечатать_и_добавить (1);
k := 1; { k - число напечатанных }
{инвариант: напечатано в порядке возрастания k минимальных членов нужного множества; в очередях элементы, вдвое,
втрое и впятеро большие напечатанных, но не напечатанные,
расположенные в возрастающем порядке}
while k <> n do begin x := min (очередной(x2), очередной(x3), очередной(x5));
напечатать_и_добавить (x);
k := k+1;
...взять x из тех очередей, где он был очередным;
end;
Пусть инвариант выполняется. Рассмотрим наименьший из ненапе- чатанных элементов множества; пусть это x. Тогда он делится нацело на одно из чисел 2, 3, 5, и частное также принадлежит множеству. Зна- чит, оно напечатано. Значит, x находится в одной из очередей и, сле- довательно, является в ней первым (меньшие напечатаны, а элементы очередей не напечатаны). Напечатав x, мы должны его изъять и доба- вить его кратные.
Длины очередей не превосходят числа напечатанных элементов. 
Следующая задача связана с графами (к которым мы вернёмся в гла- ве
9
).
Пусть задано конечное множество, элементы которого называют вершинами, а также некоторое множество упорядоченных пар вершин,
называемых рёбрами. В этом случае говорят, что задан ориентирован- ный граф. Пару ⟨𝑝, 𝑞⟩ называют ребром с началом 𝑝 и концом 𝑞; говорят также, что оно выходит из вершины 𝑝 и входит в вершину 𝑞. Обычно вершины графа изображают точками, а рёбра | стрелками, ведущими из начала в конец. (В соответствии с определением из данной верши- ны в данную ведёт не более одного ребра; возможны рёбра, у которых начало совпадает с концом.)
6.2.6. Известно, что ориентированный граф связен, т. е. из любой вершины можно пройти в любую по рёбрам. Кроме того, из каждой

6.2. Очереди
107
вершины выходит столько же рёбер, сколько входит. Докажите, что су- ществует замкнутый цикл, проходящий по каждому ребру ровно один раз. Составьте алгоритм отыскания такого цикла.
Решение. Змеёй будем называть непустую очередь из вершин, в ко- торой любые две вершины соединены ребром графа (началом является та вершина, которая ближе к началу очереди). Стоящая в начале очере- ди вершина будет хвостом змеи, последняя | головой. На рисунке змея изобразится в виде цепи рёбер графа, стрелки ведут от хвоста к голо- ве. Добавление вершины в очередь соответствует росту змеи с головы,
взятие вершины | отрезанию кончика хвоста.
Вначале змея состоит из единственной вершины. Далее мы следуем такому правилу:
while змея включает не все рёбра do begin if из головы выходит не входящее в змею ребро then begin удлинить змею этим ребром end else begin
{голова змеи в той же вершине, что и хвост}
отрезать конец хвоста и добавить его к голове
{"змея откусывает конец хвоста"}
end;
end;
Докажем, что мы достигнем цели.
(1) Идя по змее от хвоста к голове, мы входим в каждую вершину столько же раз, сколько выходим. Так как в любую вершину входит столько же рёбер, сколько выходит, то невозможность выйти означает,
что голова змеи в той же точке, что и хвост.
(2) Змея не укорачивается, поэтому либо она охватит все рёбра,
либо, начиная с некоторого момента, будет иметь постоянную длину.
Во втором случае змея будет бесконечно «скользить по себе». Это воз- можно, только если из всех вершин змеи не выходит неиспользованных рёбер. В этом случае из связности следует, что змея проходит по всем рёбрам.
Замечание по реализации на паскале. Вершинами графа будем счи- тать числа 1 . . . n. Для каждой вершины i будем хранить число Out[i]
выходящих из неё рёбер, а также номера Num[i][1],. . . ,Num[i][Out[i]]
тех вершин, куда эти рёбра ведут. В процессе построения змеи будем выбирать первое свободное ребро. Тогда достаточно хранить для ка- ждой вершины число выходящих из неё использованных рёбер | это будут рёбра, идущие в начале списка.


108 6. Типы данных
6.2.7. Докажите, что для всякого 𝑛 существует последовательность нулей и единиц длины 2
𝑛
со следующим свойством: если «свернуть её
в кольцо» и рассмотреть все фрагменты длины 𝑛 (их число равно 2
𝑛
),
то мы получим все возможные последовательности нулей и единиц дли- ны 𝑛. Постройте алгоритм отыскания такой последовательности, тре- бующий не более 𝐶
𝑛
действий для некоторой константы 𝐶.
[Указание. Рассмотрим граф, вершинами которого являются после- довательности нулей и единиц длины 𝑛 − 1. Будем считать, что из вер- шины 𝑥 ведёт ребро в вершину 𝑦, если 𝑥 может быть началом, а 𝑦 |
концом некоторой последовательности длины 𝑛. Тогда из каждой вер- шины входит и выходит два ребра. Цикл, проходящий по всем рёбрам,
и даст требуемую последовательность.]

6.2.8. Реализуйте 𝑘 очередей с ограниченной суммарной длиной 𝑛,
используя память 𝑂(𝑛 + 𝑘) [= не более 𝐶(𝑛 + 𝑘) для некоторой кон- станты 𝐶], причём каждая операция (кроме начальной, делающей все очереди пустыми) должна требовать ограниченного константой числа действий.
Решение. Действуем аналогично ссылочной реализации стеков: мы помним (для каждой очереди) первого, каждый участник очереди по- мнит следующего за ним (для последнего считается, что за ним стоит фиктивный элемент с номером 0). Кроме того, мы должны для каждой очереди знать последнего (если он есть) | иначе не удастся добавлять.
Как и для стеков, отдельно есть цепь свободных ячеек. Заметим, что для пустой очереди информация о последнем элементе теряет смысл |
но она и не используется при добавлении.
Содержание: array [1..n] of T;
Следующий: array [1..n] of 0..n;
Первый: array [1..k] of 0..n;
Последний: array [1..k] of 0..n;
Свободная : 0..n;
procedure Сделать_пустым;
var i: integer;
begin for i := 1 to n-1 do begin
Следующий [i] := i + 1;
end;
Следующий [n] := 0;
Свободная := 1;
for i := 1 to k do begin

6.2. Очереди
109
Первый [i]:=0;
end;
end;
function Есть_место : boolean;
begin
Есть_место := Свободная <> 0;
end;
function Пуста (номер_очереди: integer): boolean;
begin
Пуста := Первый [номер_очереди] = 0;
end;
procedure Взять (var t: T; номер_очереди: integer);
var перв: integer;
begin
{not Пуста (номер_очереди)}
перв := Первый [номер_очереди];
t := Содержание [перв]
Первый [номер_очереди] := Следующий [перв];
Следующий [перв] := Свободная;
Свободная := перв;
end;
procedure Добавить (t: T; номер_очереди: integer);
var нов, посл: 1..n;
begin
{Есть_место }
нов := Свободная; Свободная := Следующий [Свободная];
{из списка свободного места изъят номер нов}
if Пуста (номер_очереди) then begin
Первый [номер_очереди] := нов;
Последний [номер_очереди] := нов;
Следующий [нов] := 0;
Содержание [нов] := t;
end else begin посл := Последний [номер_очереди];
{Следующий [посл] = 0 }
Следующий [посл] := нов;
Следующий [нов] := 0;
Содержание [нов] := t
Последний [номер_очереди] := нов;
end;
end;

110 6. Типы данных function Очередной (номер_очереди: integer): T;
begin
Очередной := Содержание [Первый [номер_очереди]];
end;

6.2.9. Та же задача для деков вместо очередей.
[Указание. Дек | структура симметричная, поэтому надо хранить ссылки в обе стороны (вперёд и назад). При этом удобно к каждому деку добавить фиктивный элемент, замкнув его в кольцо, и точно такое же кольцо образовать из свободных позиций.]

В следующей задаче дек используется для хранения вершин вы- пуклого многоугольника.
6.2.10. На плоскости задано 𝑛 точек, пронумерованных слева на- право (а при равных абсциссах | снизу вверх). Составьте программу,
которая строит многоугольник, являющийся их выпуклой оболочкой,
за 𝑂(𝑛) [= не более чем 𝐶𝑛] действий.
Решение. Будем присоединять точки к выпуклой оболочке одна за другой. Легко показать, что последняя присоединённая точка будет одной из вершин выпуклой оболочки. Эту вершину мы будем назы- вать выделенной. Очередная присоединяемая точка видна из выделен- ной (почему?). Дополним наш многоугольник, выпустив из выделенной вершины «иглу», ведущую в присоединяемую точку. Получится выро- жденный многоугольник, и остаётся ликвидировать в нём «впуклости».
r r
r r
r r
r r
P
P
P
P
P
P
A
A
A
A




B
B
B
B
B
B
H
H
H
H
H
H
H











Будем хранить вершины многоугольника в деке в порядке обхода его периметра по часовой стрелке. При этом выделенная вершина явля- ется началом и концом (головой и хвостом) дека. Присоединение «иглы»
теперь состоит в добавлении присоединяемой вершины в голову и в

6.3. Множества
111
хвост дека. Устранение впуклостей несколько более сложно. Назовём подхвостом и подподхвостом элементы дека, стоящие за его хвостом.
Устранение впуклости у хвоста делается так:
while по дороге из хвоста в подподхвост мы поворачиваем у подхвоста влево ("впуклость") do begin выкинуть подхвост из дека end
Таким же способом устраняется впуклость у головы дека.
Замечание. Действия с подхвостом и подподхвостом не входят в определение дека, однако сводятся к небольшому числу манипуляций с деком (надо забрать три элемента с хвоста, сделать что надо и вер- нуть).
Ещё одно замечание. Есть два вырожденных случая: если мы во- обще не поворачиваем у подхвоста (т. е. три соседние вершины лежат на одной прямой) и если мы поворачиваем на 180

(так бывает, если наш многоугольник есть двуугольник). В первом случае подхвост сто- ит удалить (чтобы в выпуклой оболочке не было лишних вершин), а во втором случае | обязательно оставить.

6.3. Множества
Пусть T | некоторый тип. Существует много способов хранить (ко- нечные) множества элементов типа T; выбор между ними определяется типом T и набором требуемых операций.
Подмножества множества {1 . . . 𝑛}
6.3.1. Как, используя память размера 𝑂(𝑛) [пропорциональную 𝑛],

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


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