
- •Структуры и алгоритмы обработки данных
- •Алгоритмы. Основные определения
- •От задачи к программе
- •Написание программы.
- •Типы данных, структуры данных и абстрактные типы данных
- •Указатели и курсоры
- •Время выполнения программ
- •Измерение времени выполнения программы
- •Степень (порядок) роста
- •Вычисление времени выполнения программ
- •Линейные абстрактные типы данных атд «Список»
- •Реализация списков посредством массивов
- •Реализация списков с помощью указателей
- •Атд «Стек»
- •Атд «Очередь»
- •Реализация очередей с помощью циклических массивов
- •Нелинейные абстрактные типы данных Деревья
- •Порядок узлов
- •Прямой, обратный и симметричный обходы дерева
- •Помеченные деревья и деревья выражений
- •Представление деревьев с помощью массивов
- •Специальные виды множеств атд “Дерево двоичного поиска”
- •Атд "Словарь", основанный
- •Представление ориентированных графов
- •Атд для ориентированных графов
- •Задача нахождения кратчайшего пути
- •Int **c, // массив стоимостей
- •Int *d) // массив кратчайших
- •Остовные деревья минимальной стоимости
- •Обход графов
- •Поиск в ширину
- •Поиск в глубину
- •Сортировка
- •Простые схемы сортировки Метод «пузырька»
- •Сортировка вставками
- •Быстрая сортировка
- •Пирамидальная сортировка
- •«Карманная» сортировка
- •Порядковые статистики
- •Методы разработки алгоритмов. Типы алгоритмов
- •Алгоритмы «разделяй и властвуй» (метод декомпозиции)
- •Баланс подзадач
- •Динамическое программирование
- •Перемножение нескольких матриц
- •Шаг 1: строение оптимальной расстановки скобок
- •Шаг 2: рекуррентное соотношение
- •Шаг 3: вычисление оптимальной стоимости
- •Void MatrixChainOrder(int n, // кол-во матриц
- •Int p[], // размеры матриц
- •Int **s) // оптимальное k
- •Шаг 4: построение оптимального решения
- •Int **s, // таблица, полученная
- •Int I, // индексы
- •Когда применимо динамическое программирование?
- •Оптимальность для подзадач
- •Перекрывающиеся подзадачи
- •«Жадные» алгоритмы
- •"Жадные" алгоритмы как эвристики
- •Когда применим жадный алгоритм?
- •Принцип жадного выбора
- •Оптимальность для подзадач
- •Поиск с возвратом
- •Функции выигрыша
- •Метод ветвей и границ
- •Структуры данных и алгоритмы для внешней памяти
- •Внешняя сортировка
- •Хранение данных в файлах
- •Простая организация данных
- •Хешированные файлы
- •Индексированные файлы
- •Содержание
- •Глава I. Линейные абстрактные типы данных 31
- •Глава II. Сортировка 108
Реализация списков посредством массивов
При реализации списков с помощью массивов элементы списка располагаются в смежных ячейках массива.
Это представление позволяет легко просматривать содержимое списка и вставлять новые элементы в его конец.
Но вставка нового элемента в середину списка требует перемещения всех последующих элементов на одну позицию к концу массива, чтобы освободить место для нового элемента.
Удаление элемента также требует перемещения элементов, чтобы закрыть освободившуюся ячейку.
-
первый элемент
второй элемент
список
Last
Последний элемент
свободный
При использовании массива мы определяем тип LIST как запись, имеющую два поля.
Первое поле array – это элементы массива, чей размер считается достаточным для хранения списков любой длины, встречающихся в данной программе.
Второе поле целочисленного типа last, указывает на позицию последнего элемента списка в массиве.
Введем константу MAXLENGTH, определяющую максимальный размер массива.
Структура данных будет иметь вид:
#define MAXLENGTH 100
typedef float element_type;
typedef struct
{
element_type array[MAXLENGTH];
int last;
} LIST;
Реализация списков с помощью указателей
В
этой реализации список состоит из ячеек,
каждая из которых содержит элемент
списка (информационное поле) и указатель
на следующую ячейку списка. Последняя
ячейка содержит последний элемент и
имеет указатель null. Имеется также ячейка
header (заголовок), которая указывает на
первую ячейку. Ячейка header не содержит
элементов списка.
В случае пустого списка заголовок имеет указатель null, не указывающий ни на какую ячейку.
Если движение от элемента к элементу списка возможно только в одном из направлений и список имеет начальную точку такого движения, говорят, что этот список однонаправленный.
О
перации
вставки и удаления в однонаправленном
списке происходят следующим образом:
Эта реализация освобождает нас от использования непрерывной области памяти для хранения списка и, следовательно, от необходимости перемещения элементов списка при вставке или удалении.
Однако ценой за это удобство становится дополнительная память для хранения указателей.
Формально определить структуру элемента однонаправленного списка можно так:
typedef struct
{
char name[20]; /* автор книги */
char title[44]; /* наименование */
int year; /* год издания */
float price; /* цена */
} BOOK;
struct LIST
{
/* информационное поле */
BOOK info;
/* указатель на след. элемент */
struct LIST * next;
}
Во многих приложениях возникает необходимость организовать эффективное перемещение по списку как в прямом, так и в обратном направлении.
Или по заданному элементу нужно быстро найти предшествующий ему и последующий элементы. В этом случае можно включить в каждую ячейку указатели на последующую и предыдущую ячейки списка. А так как список имеет и начало, и конец, описываются два дополнительных указателя – «голова» и «хвост».
Такой список называется двусвязным.
struct LIST
{
/* информационное поле */
BOOK info;
/* указатель на след. элемент */
struct LIST *next;
/* указатель на пред. элемент */
struct LIST *prev;
}
Ценой за возможность перемещения в обе стороны по списку являются дополнительные указатели в каждой ячейке и определенное удлинение некоторых процедур, реализующих основные операторы списка.
Замечание
Разумеется, нас интересует вопрос о том, в каких ситуациях лучше использовать реализацию списков с помощью указателей, а когда с помощью массивов или курсоров. Ответ на этот вопрос зависит от того, какие операторы должны выполняться над списками и как часто они будут использоваться, можно ли изначально определить максимальный размер списка. Но, как правило, списки реализуются с помощью указателей либо в виде односвязного списка, либо в виде двусвязного.
Реализация списков
на основе курсоров
Некоторые языки программирования (например, Фортран и Алгол) не имеют указателей.
Если мы работаем с такими языками, то можно смоделировать указатели с помощью курсоров, т.е. целых чисел, которые указывают на позиции элементов в массивах.
Для всех элементов списка создадим один массив space (область данных), состоящий из записей.
Каждая запись будет состоять из информационного поля element, содержащего элемент списка и поля next для целых чисел, используемых в качестве курсора.
Объявим переменную head в качестве заголовка списка, она будет содержать индекс первого элемента списка.
Значение space[head].element равно первому элементу списка, значение space[head].next является индексом ячейки массива, содержащей второй элемент списка и т.д.
Нуль в поле next означает, что последующих элементов нет.
space
element next
-
5
Head L
1d
7
2
4
3
c
0
1
M
46
5
a
8
6
0
7
e
0
9
свободный
8b
3
9
10
10
2
На рис. показаны два списка L=a.b.c и M=d,e, вложенные в один массив space длиной 10.
Все ячейки массива, не занятые элементами списка, образуют отдельный список, названный свободным.
Этот список используется как источник пустых ячеек при вставке элементов в любой список, а также как место хранения освободившихся (ранее удаленных) ячеек.
Для вставки элемента в список мы берем первую ячейку из свободного списка и помещаем ее в нужную позицию списка.
А вставляемый элемент помещаем в поле element этой ячейки.
Для удаления элемента из списка, мы помещаем его в начало свободного списка.