
- •Технология программирования Конспект лекций
- •1.1. Краткая характеристика языка Си1
- •1.2. Алфавит
- •1.3. Объявление переменных. Типы данных
- •1.4. Простые типы данных
- •1.5. Структурированные типы данных
- •1.6. Константы
- •1.7. Операции Присваивание
- •Арифметические
- •1.9. Функции
- •1.10. Директивы препроцессора
- •1.11. Метки
- •1.12. Указатели Обзор
- •Приведение типов
- •Типизированные и нетипизированные указатели
- •Операции над указателями
- •Массивы
- •Многомерные массивы
- •Указатели в качестве параметров функций
- •Функции работы с указателями и памятью
- •1.13. Строки.
- •1.14. Время жизни и область видимости переменных
- •1.15. Указатель на функцию
- •1.16. Работа с файлами.
- •1.17. Форматированный ввод-вывод
- •1.18. Введение в объектно-ориентированное программирование
- •Часть 2. Введение в технологию программирования.
- •2.1 Понятие технологии программирования
- •2.2. Этапы развития программирования
- •2.3. Проблемы разработки сложных программных систем
- •2.4. Жизненный цикл программного продукта
- •Модели жц.
- •Экстремальное программирование
- •2.5. Оценка качества процессов создания программного обеспечения
- •2.6. Проектирование надёжного программного средства
- •2.7. Процессы проектирования
- •2.8. Требования, цели
- •2.9. Внешнее проектирование
- •Диаграммы потоков данных (Data Flow Diagrams).
- •Диаграммы переходов состояний (State Transition Diagrams). Функциональные диаграммы.
- •Описание структур данных
- •2.10. Проектирование архитектуры
- •2.11. Проектирование модульной структуры. Модуль
- •Внешнее проектирование модулей
- •Модульная декомпозиция
- •2.12. Кодирование.
- •2.13. Тестирование
- •2.14. Документирование
- •2.15. Проектирование пользовательского интерфейса
- •Реализация многомерных массивов
- •Очередь и стек
- •Ассоциативные массивы
- •3.3. Упорядоченные структуры данных.
- •Сортировка методом пузырька
- •Быстрая сортировка
- •3.4. Деревья
- •Бинарное дерево
- •Обход дерева
- •3.5. Множества, графы Множества
- •3.6. Строки Константные строки
- •Списковое представление строк
- •Представление массива строк
- •3.7. Библиотека stl
- •3.8. Реализация некоторых структур данных Реализация двусвязанного списка.
- •Реализация массива переменного размера:
- •Примеры программ
- •Список литературы
Реализация многомерных массивов
Двумерные таблицы могут быть реализованы различным образом:
а) если длина всех строк таблицы одинакова и постоянна, то наилучшим вариантом является использование одномерного вектора, такой вариант уже был рассмотрен ранее;
б) если длина строк различается, то можно использовать вектор векторов или список векторов, в зависимости от требований к количеству строк.
Реализация многомерных таблиц осуществляется аналогично.
Очередь и стек
Очередь - это последовательность элементов одного и того же типа, к которой добавление и удаление элементов происходит только с одного из концов.
Различают очередь, в которой добавление и удаление элементов может осуществляться с любого конца (deque) и очередь, в которой добавление происходит с одного конца, а удаление с другого (queue или очедедь FIFO (первый пришел, первый ушел)).
Очередь, в которой добавление и удаление происходит с одного и того же конца, называют очередь LIFO (последний пришел, первый ушел) или, по-другому, стеком.
Очереди и стеки могут быть ограниченными, в этом случае задана константа, определяющая максимальное количество элементов. В противном случае их называют неограниченными.
Ограниченные очереди и стеки достаточно легко рализуются через вектор, неограниченные - через список.
Стеки широко используются в задачах синтаксического анализа. Также они используются при вызове подпрограмм для сохранения контента вызывающей программы.
Ассоциативные массивы
Ассоциативный массив (словарь, карта) — абстрактный тип данных (интерфейс к хранилищу данных), позволяющий хранить пары (ключ, значение) и поддерживающий операции добавления пары, а также поиска и удаления пары по ключу. Предполагается, что ассоциативный массив не может хранить две пары с одинаковыми ключами. В паре (k,v) значение v называется значением, ассоциированным с ключом k.
Реализация ассоциативного массива рассмотрена в параграфе 3.4.
Другие струткуры данных рассмотрены в параграфах 3.4 - 3.6.
3.3. Упорядоченные структуры данных.
В обычном векторе для поиска элемента требуется просмотреть в среднем половину списка, т.е. время выполнения алгоритма линейно зависит от количества данных. Если объем данных большой и запросы к ним выполняются часто, такая сложность алгоритма неприемлема.
Наиболее простым решением является хранение данных, упорядоченных по значению. В этом случае, используя метод половинного деления (двоичного поиска) сложность алгоритма составляет log2(N).
(Алгоритм половинного деления)
Более эффективным методом является использование деревьев, что будет рассмотрено далее.
Чтобы данные были расположены упорядоченно, их требуется отсортировать.
Сортировка методом пузырька
Это наиболее простой метод сортировки. Его сложность N2. Эффективен, если исходные данные в некоторой мере упорядочены, например, если в отсортированный массив добавляется небольшое количество новых данных.
int t,k=0;
char flag;
do
{
flag = 0;
for(int i=0;i<N-1-k;i++)
if (m[i]>m[i+1]) {t=m[i];m[i]=m[i+1];m[i+1]=t;flag=1;}
k++;
}while(flag);
Быстрая сортировка
"Быстрая сортировка", хоть и была разработана более 40 лет назад, является наиболее широко применяемым и одним их самых эффективных алгоритмов.
Метод основан на подходе "разделяй-и-властвуй". Общая схема такова:
из массива выбирается некоторый опорный элемент a[i],
запускается процедура разделения массива, которая перемещает все ключи, меньшие, либо равные a[i], влево от него, а все ключи, большие, либо равные a[i] - вправо,
теперь массив состоит из двух подмножеств, причем левое меньше, либо равно правого,
для обоих подмассивов: если в подмассиве более двух элементов, рекурсивно запускаем для него ту же процедуру.
В конце получится полностью отсортированная последовательность.
Рассмотрим алгоритм подробнее.
Разделение массива
На входе массив a[0]...a[N] и опорный элемент p, по которому будет производиться разделение.
Введем два указателя: i и j. В начале алгоритма они указывают, соответственно, на левый и правый конец последовательности.
Будем двигать указатель i с шагом в 1 элемент по направлению к концу массива, пока не будет найден элемент a[i] >= p. Затем аналогичным образом начнем двигать указатель j от конца массива к началу, пока не будет найден a[j] <= p.
Далее, если i <= j, меняем a[i] и a[j] местами и продолжаем двигать i,j по тем же правилам...
Повторяем шаг 3, пока i <= j.
Рассмотрим работу процедуры для массива a[0]...a[6] и опорного элемента p = a[3].
Теперь массив разделен на две части: все элементы левой меньше либо равны p, все элементы правой - больше, либо равны p. Разделение завершено.
Реализация на Си.
template<class T>
void quickSortR(T* a, long N) {
// На входе - массив a[], a[N] - его последний элемент.
long i = 0, j = N; // поставить указатели на исходные места
T temp, p;
p = a[ N/2]; // центральный элемент
// процедура разделения
do {
while ( a[i] < p ) i++;
while ( a[j] > p ) j--;
if (i <= j) {
temp = a[i]; a[i] = a[j]; a[j] = temp;
i++; j--;
}
} while ( i<=j );
// рекурсивные вызовы, если есть, что сортировать
if ( j > 0 ) quickSortR(a, j);
if ( N > i ) quickSortR(a+i, N-i);
}
Каждое разделение требует, очевидно, Theta(n) операций. Количество шагов деления(глубина рекурсии) составляет приблизительно log n, если массив делится на более-менее равные части. Таким образом, общее быстродействие: O(n log n), что и имеет место на практике.
Однако, возможен случай таких входных данных, на которых алгоритм будет работать за O(n2) операций. Такое происходит, если каждый раз в качестве центрального элемента выбирается максимум или минимум входной последовательности. Если данные взяты случайно, вероятность этого равна 2/n. И эта вероятность должна реализовываться на каждом шаге... Вообще говоря, малореальная ситуация.
Метод неустойчив. Поведение довольно естественно, если учесть, что при частичной упорядоченности повышаются шансы разделения массива на более равные части.
Сортировка использует дополнительную память, так как приблизительная глубина рекурсии составляет O(log n), а данные о рекурсивных подвызовах каждый раз добавляются в стек.