Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Билеты на зачет.docx
Скачиваний:
51
Добавлен:
01.06.2015
Размер:
554.66 Кб
Скачать

21. Стеки, очереди, деревья

      1. Стеки

До сих пор мы фокусировали свое внимание на том, как структурировать данные так, чтобы была возможность вставлять, удалять и работать с произвольно взятым элементом. Однако часто бывает выгодно ограничить способ доступа к данным. Такие ограничения выгодны по двум причинам: во-первых, это может избавить программу от необходимости знать во всех деталях о структуре данных (учет индексов массива или указателей списка); во-вторых, это позволяет писать более простые более гибкие программы так, как требуется поддерживать лишь небольшое число операций.

Очень показательный, к тому же часто используемый на практике пример структуры подобного рода – стек. В стеке используется идеология LIFO (LastInFirstOut): последний пришел, – первый вышел. Над стеком определены только две операции:push (задвинуть)иpop (вытолкнуть).Для того чтобы положить что-либо на стек, мы делаемpush, а для того, чтобы взять элемент со стека, мы делаемpop.

Аналогом стека может быть стопка тарелок. Помытые тарелки кладутся сверху (на вершину стека). Берутся тарелки так же сверху, таким образом, тарелка, помытая последней, будет использоваться первой.

В дальнейшем мы встретимся с громадным количеством задач, использующих стеки. Для начала рассмотрим использование стека на примере вычисления арифметических выражений. Предположим, что нам нужно узнать значение арифметического выражения включающего умножение и суммирование целых:

Стек является идеальным механизмом для сохранения промежуточных результатов в подобных вычислениях. Например, наше выражение можно вычислить посредством следующих операций:

Порядок, в котором выполняются операции, продиктован скобками в выражении и соглашением о том, что мы движемся слева направо. Возможны и другие соглашения; например 4*6 можно вычислить раньше, чем 9+8. Некоторые калькуляторы работают именно по этому принципу: Каждая операция извлекает свои аргументы со стека и возвращает результат на стек.

push(5) push(9) push(8) push(pop+pop) push(4) push(6) push(pop*pop) push(pop*pop) push(7) push(pop+pop) push(pop*pop) writeln(pop)

Основные операции со стеком легко реализуемы с помощью связного списка. Приведенная ниже реализация стека включает в себя также код инициализации стека и проверку на то, что он пуст. В задачах, в которых используется только один стек, мы можем предполагать, что существует некая глобальная переменная head, указывающая на начало стека. В противном случае программу необходимо изменить так, чтобы во все функции передавался указатель на стек.

typelink = ^node; node =recordkey : integer; next : linkend;varhead, z : link;

procedurestackinit;beginnew(head); new(z); nead^.next := z; z^.next := z;end;

procedurepush(v : integer);vart:link;beginnew(t); t^. key := v; t^.next := head^.next; head^.next := t;end;

function pop : integer;vart : link;begint :=head^.next; pop := t^.key; head^.next := t^.next; dispose(t);end;

functionstackempty : boolean;beginstackempty := (head^.next=z)end;

Порядок вычислений, производимых в нашем примере, требует, чтобы операнды встречались до операторов так, чтобы они уже были на стеке к тому моменту, когда нам встречается оператор. Любое арифметическое выражение можно переписать подобным образом, в частности, наш пример может быть записан так:

5 9 8 + 4 6 ** 7 + *

Такая форма записи выражений называется обратной польской нотацией (потому, что впервые была использована польским логиком), илипостфиксом. Наш обычный способ записи называетсяинфиксом. Одна из замечательных особенностей постфикса – это то, что скобки вообще не требуются, хотя в инфиксе они просто необходимы.

Следующая программа преобразует правильное инфиксное выражение, с порядком вычислений указанным скобками, в постфикс:

Результат выводится на экран. Операнды передаются сразу же после того как встречаются так, как они следуют в том же порядке, что и постфиксном выражении. Операторы кладутся на стек. Правая скобка говорит нам о том, что оба операнда уже написаны, и что теперь надо поднять со стека оператор и тоже написать его. Программа совершенно не контролирует ошибки в выражении. В частности игнорируются левые скобки и все символы не являющиеся цифрами или операторами. Заметьте, что многозначные числа остаются написанными правильно, так как расположение цифр не меняется.

stack_init; repeatrepeat read(c)until c<>' '; if c = ')'then Write( chr(pop) ); if c = '+'then Push( ord(c) ); if c = '*'then Push( ord(c) ); while ( c>='0' )and( c<='9' )do begin Write(c); Read(c);end; if c<>'('then Write(' ');until eoln;

Как мы видели раньше, использование постфикса позволяет проводить вычисления прямо на стеке. Следующая программа читает любое постфиксное выражение, включающее в себя сложение и умножение целых, и затем печатает результат. Пробелы игнорируются, и цикл whileконвертирует строковые в числа. Затем целочисленные операнды помещаются в стек, а умножение и сложение заменяют два верхних элемента стека на их произведение или сумму соответственно.

Stack_Init; repeatx := 0; repeat Read(c)until c<>' '; if c ='*'then x:=Pop*Pop; if c='+'then x:=Pop+Pop; while (c>='0')and(c<='9')do begin x:=x*10+ord(c)-ord('0'); Read(c); end; Push(x);until EoLN; Writeln(Pop);

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

constmaxP=100;varstack :array[0..maxP]ofinteger; p : integer;

procedurePush(v:integer);beginstack[p]:=v; p:=p+1end;

functionPop : integer;beginp:=p-1; Pop := stack[p]end;

procedureStackInit;beginp:=0end;

functionStackEmpty : boolean;beginStackEmpty := (p<=0)end;

Переменная p – глобальная переменная, играющая роль указателя на стек. Эта простая реализация стека экономит место на хранении дополнительных указателей и время за счет отказа от динамической памяти. Единственный ее недостаток – потери памяти при резервировании места для максимально возможного размера стека.

На рис. 1. показано как изменяется стек при серии операций pushиpop:

A * S A * M * P * L * E S * T * * * A * C K * *.

Буква означает операцию push, а звездочка –pop.

  1. Пример изменения стека

17.10 Обычно, даже большое количество операций не требуют слишком большого стека. Размер стека зависит только от глубины вложенности. В таких случаях можно использовать массивы. В тех случаях, когда размер стека заранее предсказать невозможно, нужно использовать списки.

Как мы увидим далее, стеки часто возникают изнутри, даже если мы и не используем их прямо.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]