Типы данных
Использование стека
Будем рассматривать последовательности открывающихся и закрывающихся круглых и квадратных скобок ( ) [ ]. Среди всех таких последовательностей выделим правильные - те, которые могут быть получены по таким правилам:
- пустая последовательность правильна.
- если A и B правильны, то и AB правильна.
- если A правильна, то [ A ] и ( A ) правильны.
Пример. Последовательности (), ,
правильны,
а последовательности ], )(, (], ([)] - нет.
6.1.1. Проверить правильность последовательности за время, не превосходящее константы, умноженной на ее длину. Предполагается, что члены последовательности закодированы числами:
![\begin{tabular}{rr}
{(} & 1 \\
{[} & 2 \\
{)} & -1 \\
{]} & -2
\end{tabular}](/sites/default/files/tex_cache/3fd76d48cb1649d7ad9ba332c53309e4.png)
Решение. Пусть - проверяемая
последовательность.
Разрешим хранить в стеке открывающиеся круглые и квадратные
скобки (т. е. 1 и 2).
Вначале стек делаем пустым. Далее просматриваем члены последовательности слева направо. Встретив открывающуюся скобку (круглую или квадратную), помещаем ее в стек. Встретив закрывающуюся, проверяем, что вершина стека - парная ей скобка; если это не так, то можно утверждать, что последовательность неправильна, если скобка парная, то заберем ее (вершину) из стека. Последовательность правильна, если в конце стек оказывается пуст.
Сделать_пустым (s); i := 0; Обнаружена_ошибка := false; {прочитано i символов последовательности} while (i < n) and not Обнаружена_ошибка do begin | i := i + 1; | if (a[i] = 1) or (a[i] = 2) then begin | | Добавить (a[i], s); | end else begin {a[i] равно -1 или -2} | | if Пуст (s) then begin | | | Обнаружена_ошибка := true; | | end else begin | | | Взять (t, s); | | | Обнаружена_ошибка := (t <> - a[i]); | | end; | end; end; Правильно := (not Обнаружена_ошибка) and Пуст (s);
Убедимся в правильности программы.
(1) Если последовательность построена по правилам, то программа даст ответ "да". Это легко доказать индукцией по построению правильной последовательности. Надо проверить для пустой, для последовательности AB в предположении, что для A и B уже проверено, и, наконец, для последовательностей [A] и (A) - в предположении, что для A уже проверено. Для пустой очевидно. Для AB действия программы происходят как для A и кончаются с пустым стеком; затем все происходит как для B. Для [A] сначала помещается в стек открывающая квадратная скобка и затем все идет как для A - с той разницей, что в глубине стека лежит лишняя скобка. По окончании A стек становится пустым - если не считать этой скобки - а затем и совсем пустым. Аналогично для (A).
(2) Покажем, что если программа завершает работу с ответом "да", то последовательность правильна. Рассуждаем индукцией по длине последовательности. Проследим за состоянием стека в процессе работы программы. Если он в некоторый промежуточный момент пуст, то последовательность разбивается на две части, для каждой из которых программа дает ответ "да"; остается воспользоваться предположением индукции и определением правильности. Пусть стек все время непуст. Это значит, что положенная в него на первом шаге скобка будет вынута лишь на последнем шаге. Тем самым, первый и последний символы последовательности - это парные скобки, и последовательность имеет вид (A) или [A], а работа программы (кроме первого и последнего шагов) отличается от ее работы на A лишь наличием лишней скобки на дне стека (раз ее не вынимают, она никак не влияет на работу программы). Снова ссылаемся на предположение индукции и определение правильности.
6.1.2. Как упростится программа, если известно, что в последовательности могут быть только круглые скобки?
Решение. В этом случае от стека остается лишь его длина, и мы фактически приходим к такому утверждению: последовательность круглых скобок правильна тогда и только тогда, когда в любом ее начальном отрезке число закрывающихся скобок не превосходит числа открывающихся, а для всей последовательности эти числа равны.
6.1.3. Реализовать с помощью одного массива два стека, суммарное количество элементов в которых ограничено длиной массива; все действия со стеками должны выполняться за время, ограниченное константой, не зависящей от длины стеков.
Решение. Стеки должны расти с концов массива навстречу друг другу: первый должен занимать места
![{Содержание[1]}\ldots{Содержание[Длина1]},](/sites/default/files/tex_cache/ab4d7ecd5566a1d594e8990aa7a67fa5.png)
![{Содержание[n]}\ldots{Содержание[n-Длина2+1]}](/sites/default/files/tex_cache/08d2eba81c37b28763868504a6cc00bf.png)
6.1.4.
Реализовать k стеков с элементами типа T, общее
количество элементов в которых не превосходит n,
с использованием массивов суммарной длины , затрачивая на каждое действие со стеками
(кроме начальных действий, делающих все стеки пустыми)
время не более некоторой константы
. (Как говорят, общая
длина массивов должна быть
, a время на
каждую операцию -
.)
Решение. Применяемый метод называется "ссылочной реализацией". Он использует три массива:
Содержание: array [1..n] of T; Следующий: array [1..n] of 0..n; Вершина: array [1..k] of 0..n.
Удобно изображать массив Содержание как n ячеек
с номерами , каждая из которых содержит
элемент типа T. Массив Следующий изобразим в виде
стрелок, проведя стрелку из i в j, если Следующий[i]=j. (Если Следующий[i]=0, стрелок
из i не проводим.) Содержимое s -го стека
(
) хранится так: вершина равна Содержание[Вершина[s]], остальные элементы s -го
стека можно найти, идя по стрелкам - до тех пор, пока они
не кончатся. При этом
![\text{({s}-ый стек пуст)} \Leftrightarrow {Вершина[s]=0}.](/sites/default/files/tex_cache/0a8c7582c2f7ed27fc7cc212904588a4.png)
Стрелочные траектории, выходящие из
![{Вершина[1]},\ldots, {Вершина[k]}](/sites/default/files/tex_cache/32bccc01392928a9903890e66c1a023b.png)
![{Свободная} = {0}](/sites/default/files/tex_cache/f61f649c40ff93bf5708a0449fcb5409.png)
Стеки: 1-ый содержит p, t, q, a ( a - вершина); 2-ой содержит 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; | Свободная:=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;