
- •Структуры и алгоритмы обработки данных: план курса
- •Лабораторные работы
- •Литература
- •Ста: Лекция №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 “Рекурсия и деревья”.
Int main ()
{
// Приглашение пользователя ко вводу
std::cout << "Input integers, stop with Ctrl+Z: ";
// Выделяем достаточно большой блок памяти
int * pData = new int[ 50 ];
// Пытаемся вводить числа с консоли одно за другим
int numbersCount = 0;
while ( true )
{
// Попытка ввода числа
int temp;
std::cin >> temp;
// В случае успеха записываем число в массив, увеличивая счетчик
if ( std::cin )
pData[ numbersCount++ ] = temp;
else
// Иначе прерываем ввод
break;
}
// Количество введенных данных сохранено в переменной numbersCount
// Выводим данные в обратном порядке
for ( int i = numbersCount - 1; i >= 0; i-- )
std::cout << pData[ i ] << ' ';
std::cout << std::endl;
// Освобождение динамической памяти
delete[] pData;
}
Программа будет прекрасно справляться с поставленной задачей до тех пор, пока количество вводимых чисел не превысит 50. Если же пользователь введет больше чисел, например, 51, программа будет завершиться с крахом, поскольку произойдет запись за дозволенные границы выделенного массива (скорее всего, крах будет обнаружен в момент выполнения освобождения памяти инструкцией delete[]):
Очевидно, такое поведение является неприемлемым. Наивное решение состоит в увеличении размера выделяемой памяти. Но на любое подобное допущение всегда найдется набор входных данных, который разрушит программу. Еще одна наивная мысль - выделить огромное количество ячеек, скажем миллион, что скорее всего хватит для практически любых случаев, которые реально могут встретиться. Огромный недостаток этой очередной идеи состоит в крайне нерациональном расходовании ресурсов памяти. Несколько подобных приемов - и никакого объема оперативной памяти на компьютере не хватит.
В результате данного рассуждения остается лишь прийти к следующим выводам:
Программа должна обрабатывать количество данных, которое в нее вводит пользователь.
Программа не должна завершаться фатально ни при каком вводе.
Программа должна рационально расходовать память, выделяя необходимый ей минимум.
Каким же образом можно удовлетворить вышеуказанным требованиям к реализации?
Динамически растущие массивы (векторы)
Применим простейшую классическую структуру данных - динамически растущий массив. Во многих языках программирования эту структуру также называют вектором.
Основная идея такого массива состоит в резервировании некоторого небольшого объема памяти для данных и последующем автоматическом расширении как только текущий объем полностью заполняется. Для организации такой структуры необходимо ввести 3 переменные:
адрес начала текущего блока памяти для хранения (pData);
число выделенных ячеек в текущем блоке памяти (nAllocated);
число фактически занятых полезными данными ячеек в данный момент (nUsed).
Начальный выделяемый объем может быть произволен, разумной начальной настройкой является 8-10 ячеек. В начале часть выделенных ячеек использоваться не будет. Ячейки будут заполняться данными по мере ввода:
Затем, как только число занятых ячеек сравняется с числом выделенных, массив следует расширить вдвое. Прямого способа расширить выделенный массив язык С++ не предоставляет. Поэтому под расширением понимают выделение нового большего блока данных, перенос ранее введенных данных и освобождение старого блока. После расширения новый блок будет заполнен на 50%, а значит сможет принять еще столько же входных данных:
Продемонстрируем данную идею модифицировав часть предыдущей версии программы:
#include <iostream>
#include <cstring>