
- •Содержание
- •Введение
- •Указатель
- •1.1. Определение указателя
- •1.2. Понятие динамической структуры данных
- •1.3.Переменные типа "массив". Арифметические операции с указателями
- •Int hours[6];
- •1.4. Динамические массивы
- •Контрольные вопросы
- •Задания для практической работы
- •Абстрактный тип данных
- •Линейный однонаправленный (односвязный ) список
- •1.1. Определение односвязного списка
- •1.2. Операции обработки списка
- •1.3. Создание класса для работы со списком
- •1.4. Реализация копирования списка с помощью класса
- •Контрольные вопросы
- •Задания для практической работы
- •Линейные двунаправленные списки
- •1.1. Определение двунаправленного списка
- •Контрольные вопросы
- •Задания для практической работы
- •Структуры данных стеки и очереди
- •1.1. Определение стека, очереди
- •1.2. Представление стека
- •1.3. Основные операции над стеком
- •1.4. Реализация стека на основе статического массива
- •1.5. Реализация стека с использованием динамической памяти
- •1.6. Представление статической очереди
- •1.7.Представление динамической очереди
- •Контрольные вопросы
- •Задания для практической работы
- •Структуры данных: деревья
- •1.1. Определение структуры: дерево
- •1.2. Обходы деревьев
- •1.2.1. Алгоритмы обхода в глубину
- •1.2.2. Алгоритмы обхода в ширину
- •1.3. Ввод дерева
- •1.4. Разрушение дерева
- •1.5. Представление выражений с помощью деревьев
- •Контрольные вопросы
- •Задания для практической работы
- •Литература
1.4. Реализация стека на основе статического массива
Пример 1:
В качестве задачи поставим разработку стека для хранения вещественных чисел, например, из 20-ти вещественных чисел.
#include <iostream.h>
struct Stack
{ int top;
double v[20];};
void push( Stack& s, double val ) // функция добавления элемента
{
s.v[s.top] = val;
s.top++;
}
double pop( Stack& s ) // функция удаления элемента
{
return s.v[--(s.top)];
}
void init( Stack& s ) //функция инициализации стека
{
s.top = 0;
}
bool full( Stack& s ) //функция проверки переполнения стека
{
return s.top >= 20;
}
void main()
{
Stack s;
init( s ); // Инициализация стека
push( s, 2.31); // Помещение в стек первого элемента
push( s, 1.19 ); // Помещение в стек второго элемента
cout << pop( s ) << '\n'; // Извлечение верхнего элемента
// и вывод его на экран
push( s, 6.7 ); // Помещение в стек элемента
push( s, pop(s)+pop(s) ); // Замена двух верхних элементов
// стека их суммой
}
Рис. 2.Стек на основе массива с фиксацией верхушки стека
Стек приведенный в примере реализован на базе статического массива, один из элементов которого играет роль верхушки стека. Индекс этого элемента хранится в компоненте top. (рис. 2).
Серым цветом обозначены помещенные в стек элементы.
Простейшему варианту реализации стека (в виде структуры "Stack") присущи ряд недостатков, которые можно разбить на две группы:
1) Малая гибкость применения
У стека ограниченный размер (20 элементов).
В стеке могут храниться только значения типа double.
Имена функций наподобие "full()" и "init()" очень распространены и может возникнуть конфликт этих имен с именами функций из других библиотек.
2) Безопасность использования стека
Внутренние переменные стека не защищены от изменений извне. Поэтому стек легко повредить путем изменения значений компонент структуры. Это может привести к сложно обнаружимым ошибкам.
Назначение компонент стека тесно связано с особенностями реализации обслуживающих функций ("init()", "push()", "pop()" и др.).
В обслуживающих функциях не предусмотрена обработка типичных ошибок: переполнение стека, попытка извлечения элемента из пустого стека, повторная инициализация стека, нет способа проверки состояния стека (поврежден/не поврежден).
Присвоение структур (напр., "A=B") приводит к возникновению висячих указателей, т.к. присвоение компонент-массивов означает присвоение указателей, а не копирование отдельных элементов.
1.5. Реализация стека с использованием динамической памяти
Приведен один из вариантов этой структуры:
struct DStack {
double* bottom;
double* top;
int size; // Это максимальный размер стека,
}; // а не количество элементов в нем
Массив для хранения элементов стека должен создаваться динамически на этапе инициализации. Два внутренних указателя стека ссылаются на начало массива ("bottom") и на элемент-верхушку стека ("top"). Устройство стека на базе динамического массива показана на рис. 3.
Рис. 3.Стек с хранением элементов в динамической памяти
Для работы со стеком целесообразно сделать несколько обслуживающих функций:
1) Инициализация стека.
bool DStack_init( DStack& s, int N ); Эта функция динамически создает целочисленный массив размером N элементов и сохраняет его указатель в компонентах структуры "DStack". Если операция инициализации прошла успешно, то функция возвращает "true", а если памяти недостаточно – то "false".
2) Добавление элемента в стек.
bool DStack_push( DStack& s, double val );
Помещает элемент на верх стека. Возвращает "true", если это удалось сделать, или "false", если стек целиком заполнен.
3) Извлечение верхнего элемента.
double DStack_pop( DStack& s );
Возвращает верхний элемент из стека или возвращает 0.0 (или некоторое другое специально оговоренное значение), если стек пуст.
4) Проверка на пустоту.
bool DStack_empty( Dstack& s );
Возврашает "true", если стек пуст, или "false" в противном случае.
5) Удаление стека.
void DStack_free();
Освобождает динамическую память, занятую при создании стека.
По сравнению со стеком из примера 1, в описанном варианте реализации достигается ряд улучшений:
Есть возможность создания стеков разного размера.
В обслуживающих функциях предусмотрена обработка элементарных ошибок переполнения и обращения к пустому стеку.
Имена обслуживающих функций, начинающиеся с "DStack_", менее вероятно обнаружить в других библиотеках.
К сожалению, опасность несанкционированного изменения внутренних переменных стека сохраняется и в этой реализации.