
- •Структуры и алгоритмы обработки данных: план курса
- •Лабораторные работы
- •Литература
- •Ста: Лекция №1 - Введение. Данные в памяти программ
- •Введение
- •Модель памяти в прикладных программах
- •Int main ()
- •Int main ()
- •Int main ()
- •Int main ()
- •Char buf[ 2000000 ];
- •Return f(); // бесконечно долго вызываем сами себя // вызываем переполнение стека через время
- •Int main ()
- •Return f();
- •Int main ()
- •Delete[] p; // Освобождаем память
- •Int main ()
- •Int a[ n ]; // ошибка, размер нельзя вычислить во время компиляции
- •Delete[] p;
- •Int main ()
- •Проблема фиксированного размера массивов
- •Int main ()
- •Int main ()
- •Int main ()
- •Динамически растущие массивы (векторы)
- •Int main ()
- •Struct IntegerVector
- •Int * m_pData;
- •Int m_nUsed;
- •Int m_nAllocated;
- •Void IntegerVectorDestroy ( IntegerVector & _vector )
- •Int main ()
- •#Ifndef _integer_vector_hpp_
- •#Include "integer_vector.Hpp"
- •Void IntegerVectorRead ( IntegerVector & _vector, std::istream & _stream );
- •Ста: Лекция №2 - Связные списки.
- •Всегда ли хорош вектор?
- •Связные списки
- •Ста: Лекция №3 - Реализация и использование простейших атд
- •Абстрактные типы данных (атд)
- •Атд “Список” ( “Последовательность” )
- •Атд “Стек”
- •Атд “Очередь”
- •Ста: Лекция №7 - Деревья
- •Основные сведения о деревьях
- •Обход деревьев
- •Атд “Дерево”
- •Типичные структуры данных для n-арных деревьев
- •1. Массив меток и массив родительских индексов.
- •2. Массив меток и заголовок со списками дочерних узлов.
- •3. Динамическая структура с указателями
- •В результате ее выполнения в динамической памяти формируется структура объектов, в существенной степени напоминающая оригинальный пример из описания понятия деревьев:
- •Бинарные деревья
- •Глава 3 “Элементарные структуры данных”
- •Глава 4 “Абстрактные типы данных”
- •Глава 10 “Элементарные структуры данных” (подразделы 10.1-10.3)
- •Глава 2 “Основные абстрактные типы данных” (подразделы 2.1-2.4)
- •Глава 6 “Элементарные методы сортировки”.
- •Глава 5 “Рекурсия и деревья”.
Проблема фиксированного размера массивов
Предположим имеется тривиальная задача вывести поступающую от пользователя последовательность целых чисел в обратном порядке. Такая задача предполагает хранение всей входной последовательности в памяти программы. Достаточно знать длину последовательности, выделить массив подходящего размера, считать данные в массив и затем вывести их на экран в обратном порядке. Предположим пользователь будет вводить последовательность из 10 чисел с содержимым “1 2 3 4 5 6 7 8 9 10”, ожидаемый результат в таком случае - “10 9 8 7 6 5 4 3 2 1”. Ниже приведен первый простейший вариант реализации и вывод программы:
#include <iostream>
Int main ()
{
// Длина входной последовательности
const int SEQUENCE_LENGTH = 10;
// Приглашение пользователя ко вводу
std::cout << "Input " << SEQUENCE_LENGTH << " integers: ";
// Массив целых чисел на стеке для хранения данных
int data[ SEQUENCE_LENGTH ];
// Ввод данных с консоли в массив
for ( int i = 0; i < SEQUENCE_LENGTH; i++ )
std::cin >> data[ i ];
// Вывод данных на консоль в обратном порядке
for ( int i = SEQUENCE_LENGTH - 1; i >=0; i-- )
std::cout << data[ i ] << ' ';
std::cout << std::endl;
}
Серьезная проблема этой программы состоит в том, что размер последовательности фиксирован и равен 10. На практике довольно редко встречаются задачи, в которых количество данных фиксировано и строго прописывается в явном виде в коде программы. Обычно объем входных данных варьируется в зависимости от потребностей пользователя.
Предположим, пользователь будет вводить длину последовательности в начале выполнения программы. Это все еще не похоже на реальную ситуацию, но все же заметно повышает гибкость рассматриваемой программы. Теперь программа сможет обрабатывать последовательности с длиной отличной от 10. Получив от пользователя желаемую длину последовательности, выделяем массив для хранения данных в динамической памяти, ведь заранее предсказать размер этих данных мы не можем. В дальнейшем решение отличается от предыдущего лишь явной инструкцией по освобождению выделенной динамической памяти:
#include <iostream>
Int main ()
{
// Вводим длину последовательности
std::cout << "Input length of the sequence: ";
int sequenceLength;
std::cin >> sequenceLength;
// Приглашение пользователя ко вводу
std::cout << "Input " << sequenceLength << " integers: ";
// Выделяем массив нужной длины в динамической памяти
int * pData = new int[ sequenceLength ];
// Ввод данных с консоли в массив
for ( int i = 0; i < sequenceLength; i++ )
std::cin >> pData[ i ];
// Вывод данных на консоль в обратном порядке
for ( int i = sequenceLength - 1; i >=0; i-- )
std::cout << pData[ i ] << ' ';
std::cout << std::endl;
// Освобождение динамической памяти
delete[] pData;
}
Все же, в реальной программе для пользователя сообщать программе сколько данных будет введено слишком утомительно. Поэтому программы должны обрабатывать весь имеющийся ввод и адаптироваться к его размеру не запрашивая количество данных. Но это создает сложности с точки зрения организации хранения входных данных в памяти программы, поскольку их размер не известен ни заранее, ни во время выполнения в момент выделения памяти.
Простейшее грубое решение - выделить некоторый достаточно большой объем памяти (допустим, для 50 чисел) и надеяться, что пользователь столько данных не введет. Решение могло бы выглядеть следующим образом:
#include <iostream>