Тема 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 этапа:
-
Реализация самого стека.
-
Реализация программы работы со стеком.
Мы уже разобрались с реализацией стека, теперь напишем функцию 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. Конечный результат вычисляется извлечением данных их стека.
