Тема 12 Стек. Организация с помощью внешнего массива

I. На кого рассчитан модуль:

Модуль рассчитан на обучаемого, знакомого со статическим массивом, стандартными конструкциями языка С, реализацией и применением функций.

II. Цели:

  • Понять такую структуру данных как стек, принцип «последним пришел – первым вышел».

  • Научиться использовать данную структуру данных.

III. Важность изучения и использования материала:

Прекрасный пример использования стека - калькулятор с четырьмя действиями. Большинство современных калькуляторов воспринимают стандартную запись выражений, называемых инфиксной записью, общая форма которых выглядит как операнд – оператор – операнд. Например, что бы сложить два числа 100 и 200 необходимо ввести сначала число 100, затем нажать кнопку «+», потом ввести второе число 200 и потом нажать «=» для получения результата. Одно из преимуществ такой записи – это легкость ввода длинных выражений.

Итак, если мы попробуем решить данную задачу, например, при помощи массива, мы столкнемся с рядом трудностей. Представьте, что нам будет необходимо сначала занести первый элемент в массив, потом второй. Получив команду на арифметическое действие, извлекаем из массива один операнд и складываем его со вторым извлеченным операндом. Результат записываем опять же в массив. А если операндов будет не два, а больше? Придется завести отдельную переменную, которая будет указывать на номер элемента, с которым мы работаем. Эти трудности преодолимы, но более легкого решения этой задачи мы можем добиться, если будем хранить данные в реализованной определенным образом структуре, которая называется стек.

IV. Изложение материала модуля

Стек является информационной структурой, реализующий принцип «последним пришел – первым вышел» (last –in, first – out, LIFO). Доступ к элементам возможен только в корневой точке (см. рисунок). Именно в этой точке и происходит добавление или удаление элемента.

Чтобы наглядно представить себе стек, вспомните стопку тарелок. Первая тарелка, стоящая на столе, будет использована последней, а последняя тарелка, положенная наверх – первой.

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

При работе с такой структурой данных, операции занесения в стек и извлечения из стека являются основными. Данные операции традиционно называются «затолкать в стек» (push) и «вытолкать из стека» (pop). Поэтому для минимальной реализации стека необходимо написать 2 процедуры: заталкивания (или сохранения значения) и извлечения обратно.

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

Для реализации стека также необходимо выделить память. Это можно сделать двумя способами: создать статический массив или динамически выделить фрагмент памяти. Мы остановимся на первом варианте – реализации на основе внешнего массива. Ниже приведена общая форма функция push() и pop(). Стеки для данных другого типа можно реализовать, изменив базовый тип данных массива.

#include <stdio.h>

#define MAX 20

int stack[MAX-1];

int tos; // вершина стека

void push(int);

int pop (void);//прототипы функций pop() и push()

main()

{

push(30); // заталкиваем число 30

printf(“%d”, pop(), “\n”); // возвращаем из стека

}

// Затолкать элемент в стек

void push(int i);

{

if (tos>= MAX-1) // проверка полноты стека

{

printf(“Стек полон \n”)

return;

}

stack[tos]=i;

tos++;

}

//получить верхний элемент стека

int pop (void)

{

tos--;

if (tos < 0) // проверка пустоты стека

{

printf(“Стек пуст \n”)

return;

}

return stack[tos];

}

Переменная tos (top of stack – вершина стека) содержит индекс вершины стека. При реализации данных функции должны учитывать заполненность и пустоту стека, которые можно распределить в отдельные функции Full и Empty.

В нашем случае признаком переполнения стека является такое увеличение tos, что его значение будет указывать куда-то за пределы массива (т.е > MAX), а пустоты – равенство -1, т.к нумерация массива в С начинается с 0.

Как бы ни казались функции работы со стеком простыми, каждая из них должна быть реализована в виде отдельной функции. Они должны быть независимы друг от друга и входных данных. Это упрощает создание основной программы. И упрощает управление стеком.

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

В некоторых случаях необходима еще одна, пятая функция – просмотр значения на вершине стека без его удаления – функция view(). Совместим в одной программе функции pop(), push(int), view(), full() и empty().

#include <iostream.h>

#define MAX 20

int stack[MAX];

int tos; // вершина стека

void push(int);

int pop (void);

int view(void);

int full (void);

int empty (void);//прототипы функций

main()

{

push(30); // заталкиваем число 30

push(40); // заталкиваем число 40

printf(“%d”, view(), “\n”); // смотрим из стека

printf(“%d”, pop(), “\n”); // возвращаем из стека

printf(“%d”, pop(), “\n”);

}

int full()

{

if (tos>= MAX) // проверка полноты стека

return 1;

return 0;

}

int empty()

{

if (tos < 0) // проверка пустоты стека

return 1;

return 0;

}

// Затолкать элемент в стек

void push(int i);

{

if (full) // проверка полноты стека

{

printf(“Стек полон \n”)

return;

}

stack[tos]=i;

tos++;

}

//получить верхний элемент стека

int pop (void)

{

tos--;

if (empty) // проверка пустоты стека

{

printf(“Стек пуст \n”)

return;

}

return stack[tos];

}

//посмотреть значение на вершине

int view (void)

{

tos--;

if (empty) // проверка пустоты стека

{

printf(“Стек пуст \n”)

return;

}

tos++;

return stack[tos-1];

}

Таблица действий стека:

Действие

Содержимое стека

push(A)

A

push(B)

BA

push(C)

CBA

pop() извлекает С

BA

push(F)

FBA

pop() извлекает F

BA

pop() извлекает B

A

view() смотрит вершину

A

pop() извлекает A

Пусто

Типичная ошибка – Важна проверка стека на полноту и пустоту. Если этого не делать, тогда обращение к массиву с неопределенным индексом может привести к неожиданным результатам.

Вернемся к исходной задаче про калькулятор. При решении задачи операнды при вводе заталкиваются в стек. При вводе оператора операнды выталкиваются из стека, а результат помещается обратно в стек. Каждая задача, в решении которой применяется стек, должна решаться в 2 этапа:

  1. Реализация самого стека.

  2. Реализация программы работы со стеком.

Мы уже разобрались с реализацией стека, теперь напишем функцию main() для задачи «калькулятор».

#include <stdio.h>

#include <stlib.h>

#define MAX 100

int stack[MAX];

int tos; // вершина стека

void push(int);

int pop (void);

int full (void);

int empty (void);//прототипы функций

main()

{

int a,b;

char s[80];

printf(“Калькулятор с 4 действиями\n”);

printf(“Нажмите q для выхода\n”);

do {

printf(“: ”);

gets(s);

switch (*s) {

case ‘+‘:

a = pop();

b = pop();

printf(“%d\n”, a+b);

push(a+b);

break;

case ‘-‘:

a = pop();

b = pop();

printf(“%d\n”, b-a);

push(b-a);

break;

case ‘*‘:

a = pop();

b = pop();

printf(“%d\n”, a*b);

push(a*b);

break;

case ‘/‘:

a = pop();

b = pop();

if (!a){

printf(“Деление на 0”)

break;

}

printf(“%d\n”, b/a);

push(b/a);

break;

default:

push(atoi(s));

}

}while(*s != ‘q’);

}

int full()

{

if (tos>= MAX) // проверка полноты стека

return 1;

return 0;

}

Стеки часто применяются в системном программном обеспечении. Стеки используются компиляторами в процессе вычисления арифметического выражения. Например, если мы будем вычислять выражение a + (b + (с + d)), тогда сначала в стек заталкивается значение «a», потом вычисляется выражение b + (c + d), теперь в стек заталкивается значение b, потом с + d. Конечный результат вычисляется извлечением данных их стека.