Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.5.1. Стеки.

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

Его еще называют структурой типа LIFO (Last In First Out, первым вошел — последним вышел). Максимальное число элементов, которые можно размес­тить в стеке, не должно ограничиваться программным окружением: по мере вталкивания в стек новых элементов и выталкивания старых, память под него должна динамически запрашиваться и освобождаться. Состояние стека рассматривается только по отношению к его вершине, а не ко всему содержимому. С этой точки зрения состояния (а) и (б) на рис. 5.2 ничем не отли­чаются друг от друга.

Операции, выполняемые над стеком, имеют специальные названия. При до­бавлении элемента в стек мы говорим, что элемент вталкивается в стек (push). Для стека s и элемента sItem определена операция push(s, sItem), по которой в стек s добавляется элемент sItem. Ана­логичным образом определяется опера­ция выталкивания из стека— pop(s), по которой из стека s "верхний" элемент удаляется и возвращается в качестве зна­чения функции. Следовательно, опера­ция присваивания

sItem = pop(s);

удалит элемент из стека и присвоит его значение переменной sItem. На приме­нение операции выталкивания из стека существует единственное ограничение: она не может применяться к пустому стеку, т. е. к такому, который не содер­жит ни одного элемента.

Помимо этих двух основных операций часто бывает необходимо прочитать элемент в вершине стека, не извлекая его оттуда. Для этих целей использу­ют операцию peek(s).

В стек можно помещать элементы любого типа. Никаких ограничений нет.

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

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

/* Интерфейс для работы со стеком */]

#define STACK struct stack

STACK

{

int info;

STACK *next;

};

extern void push(STACK **ppStack, int nltem);

extern int pop(STACK **ppStack, int *nError);

extern int peek(STACK **ppStack, int *nError);

Обратите внимание на второе поле структуры — указатель на структуру stack. Такой способ рекурсивного определения, при котором поле структу­ры содержит ссылку на саму себя, является абсолютно допустимым. Компи­лятор отводит под указатель next требуемое количество памяти независимо от того, на какой объект он указывает.

Еще один момент, на который следует обратить внимание, заключается в том, что в функциях push и pop используются двойные ссылки. Благодаря этому каждая из них может возвращать в качестве результата своей работы указатель на новый элемент stack (используется передача параметров по адресу). Напомню, что указатель в языке С++ является такой же правомерной переменной, что и, например, короткое целое (short). Поэтому, для того чтобы можно было вернуть значение указателя из функции, необходимо ис­пользовать именно двойные ссылки. Входным параметром указанных функ­ций является указатель на стек, с использованием которого вычисляется и возвращается новый адрес элемента, находящегося на вершине. После каж­дой операции вталкивания и выталкивания указатель, связанный с верши­ной, меняется.

На рис. 5.3 показано состояние памяти при работе со стеком.

Функции pop и peek имеют еще один параметр целого типа, передаваемый через указатель, — error. Если в стеке есть хоть один элемент, то функции возвращают error = 0. Если же стек пуст, т. е. не имеет смысла удалять или считывать из него что-либо, error принимает значение 1. Реализация функ­ций работы со стеком представлена в листинге 5.2.

/* Реализация функций работы со стеком */

#include <malloc.h>

#include "stack.h"

void push(STACK **ppStack, int nItem)

{

STACK *pNewItem;

// Запрашиваем память под структуру для элемента стека

pNewItem = (STACK *)malloc(sizeof(STACK));

// Заполняем поля структуры — информационное и

// указателя на следующий элемент

pNewItem->info = nItem;

pNewItem->next = *ppStack;

// Устанавливаем новый указатель на вершину стека

*ppStack = pNewItem;

}

int pop(STACK **ppStack, int *nError)

{

// Запоминаем "старый" адрес вершины стека

STACK *pOldItem = *ppStack;

int nOldInfo = 0;

if(*ppStack)

{

// Если стек не пустой, извлекаем элемент ...

nOldInfo = pOldItem->info;

*ppStack = (*ppStack)->next;

//и освобождаем память

free(pOldItem);

*nError = 0;

} else

// В противном случае ошибка

*nError = 1;

return nOldInfo;

}

int peek(STACK **ppStack, int *nError)

{

if(*ppStack) {

// Если стек не пустой, то читаем информацию об элементе

//не удаляя его из стека

*nError = 0;

return (*ppStack)->info;

}

else

{

//В противном случае ошибка

*nError = 1;

return 0;

} )

Применение стека наглядно иллюстрирует калькулятор с четырьмя основ­ными действиями. Однако пока мы ограничимся более простой програм­мой, оставив калькулятор до знакомства с очередями.

В листинге 5.3 приведена простейшая тестовая программа, иллюстрирующая работу со стеком.

/* Простейшая программа, иллюстрирующая работу со стеком */

#include <stdio.h>

#include "stack.h"

STACK *sl, *s2;

int main()

{

int nError;

// Помещаем в стек число 12

push(&sl, 12);

printf("\npeek(sl) = %d", peek(&sl, &nError)); // проверяем

// Помещаем в стек число 13

push(&sl, 13);

printf("\npeek(si) = %d", peek(&sl, &nError)); // проверяем

// Помещаем в стек число 14

push(&sl, 14);

printf("\npeek(si) = %d", peek(&sl, &nError)); // проверяем

// Помещаем в стек число 15

push(&sl, 15);

printf("\npeek(sl) = %d\n", peek(fisl, snError)); // проверяем

// Извлекаем элементы из одного стека и помещаем в другой

push(&s2, pop(&sl, &nError));

push(&s2, pop(&sl, &nError));

push(&s2, pop(&sl, &nError));

push(&s2, pop(&sl, &nError));

// Распечатываем результаты, одновременно освобождая стек

printf("\npop(s2) = %d", pop(&s2, &nError));

printf("\npop(s2) = %d", pop(&s2, &nError));

printf("\npop(s2) = %d", pop(&s2, &nError));

printf("\npop(s2) = %d\n", pop(&s2, &SnError));

getchar();

return 1;

}