Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Структуры и алгоритмы обработки данных. Часть 1. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
61
# include <stdio.h> int main() { float a,*p1; int b,*p2; char c,*p3; a=2.5; b=3; c='A'; p1=&a; p2=&b; p3=&c; p1++; p2++; p3++; printf("\n p1=%u, p2=%u, p3=%u",p1,p2,p3); return 0; }
Ответ программы: р1=63388, р2=64392, р3=64405.
Операции адресной арифметики подчиняются следующим правилам. По-
сле увеличения значения переменной-указателя на 1 данный указатель будет ссылаться на следующий объект своего базового типа. После уменьшения - на
предыдущий объект. Для всех указателей адрес увеличивается или уменьшает-
ся на величину, равную размеру объекта того типа, на который они указывают.
Поэтому указатель всегда ссылается на объект с типом, тождественным базо-
вому типу указателя.
Применительно к указателям на объект типа char операции адресной
арифметики выполняются как обычные арифметические операции, потому что
длина объекта char всегда равна 1.
Операции адресной арифметики не ограничены увеличением (инкремен-
том) и уменьшением (декрементом). К указателям, например, можно добавлять
или вычитать из них целые числа.
При операции вычитания двух указателей можно определить количество
объектов, расположенных между адресами, на которые указывают эти два ука-
зателя. При этом необходимо, чтобы указатели имели один и тот же тип. Кроме
того, стандартом Cи допускается сравнение двух указателей. Как правило,
сравнение указателей может оказаться полезным только тогда, когда два указа-
теля ссылаются на общий объект, например, на массив.
Все остальные операции над указателями запрещены.
62
В Си функции для осуществления этих действия описаны в библиотеке
stdlib.h. Основными являются функции выделения и освобождения памяти:
1) void * malloc(size_t size); - функция для выделения size байт динамиче-
ской памяти.
Например, выделяется память под массив из 10 чисел типа int:
int *m = (int *)malloc(10*sizeof(int));
2) void free(void *memblock); - функция для освобождения ранее выде-
ленной области динамической памяти.
Например, освобождение блока памяти, выделенного под массив:
free(m);
5.2. Динамические структуры
Динамические структуры характеризуются отсутствием физической
смежности элементов структуры в памяти, непостоянством и непредсказуемо­стью размера (числа элементов) структуры в процессе ее обработки.
Поскольку элементы динамической структуры располагаются по непред-
сказуемым адресам памяти, адрес элемента такой структуры не может быть вы-
числен из адреса начального или предыдущего элемента. Для установления
связи между элементами динамической структуры используются указатели, че-
рез которые устанавливаются явные связи между элементами. Такое представ-
ление данных в памяти называется связным. Элемент динамической структуры
состоит из двух полей:
информационного поля или поля данных, в котором содержатся те дан-
ные, ради которых и создается структура; в общем случае информацион-
ное поле само является интегрированной структурой - вектором, масси­вом, записью и т.п.;
поле связок, в котором содержатся один или несколько указателей, свя-
зывающих данный элемент с другими элементами структуры;
63
Когда связное представление данных используется для решения приклад-
ной задачи, для конечного пользователя "видимым" делается только содержи­мое информационного поля, а поле связок используется только разработчиком.
Достоинства связного представления данных: в возможность обеспечения
значительной изменчивости структур;
размер структуры ограничивается только доступным объемом машинной
памяти;
при изменении логической последовательности элементов структуры тре-
буется не перемещение данных в памяти, а только коррекция указателей.
Вместе с тем связное представление не лишено и недостатков, основные
из которых:
работа с указателями требует высокой квалификации программиста; на поля связок расходуется дополнительная память;
доступ к элементам связной структуры может быть менее эффективным
по времени.
Последний недостаток является наиболее серьезным и именно им ограни-
чивается применимость связного представления данных. Если в смежном пред-
ставлении данных для вычисления адреса любого элемента во всех случаях до-
статочно номера элемента и информации, содержащейся в дескрипторе струк-
туры, то для связного представления адрес элемента не может быть вычислен
из исходных данных. Дескриптор связной структуры содержит один или не-
сколько указателей, позволяющих войти в структуру, далее поиск требуемого
элемента выполняется следованием по цепочке указателей от элемента к эле-
менту. Поэтому связное представление практически никогда не применяется в
задачах, где логическая структура данных имеет вид вектора или массива - с
доступом по номеру элемента, но часто применяется в задачах, где логическая
структура требует другой исходной информации доступа (таблицы, списки, де-
ревья и т.д.).
64
Важным признаком структуры данных является характер упорядоченности
ее элементов. По этому признаку структуры делят на линейные и нелинейные.
Идея организации всех динамических структур одна и та же. Определяет-
ся некоторый структурный тип T, одно или несколько полей которого объявле-
ны указателями на тот же или другой структурный тип. В программе объявля-
ется переменная var1 типа T (или переменная типа указателя на T в случае пол-
ностью динамического создания структуры). Имя этой переменной при выпол-
нении программы используется как имя "корня" динамической структуры. При
выполнении программы по мере построения динамической структуры запра-
шиваются динамические переменные соответствующих типов и связываются
ссылками, начиная с переменной var1 (или первой динамической переменной, указатель на которую содержится в переменной var1). Такой подход позволяет создать динамическую структуру с любой топологией.
Рекурсивным называется любой объект, который частично определяется
через себя. Рекурсивными могут быть как алгоритмы и подпрограммы, так и
типы данных. Такой простейшей структурой является однонаправленный спи-
сок (рис. 11). Для создания списка определяется структурный тип T, у которого имеется одно поле next, объявленное как указатель на T. Другие поля структу-
ры содержат информацию, характеризующую элемент списка. При образовании
первого элемента ("корня") списка в поле next заносится пустой указатель (nil
или NULL). При дальнейшем построении списка это значение будет присут-
ствовать в последнем элементе списка.
Рис. 11. Однонаправленный список
struct T { T *next; // ссылка на следующий элемент того же типа int data; // некоторые данные };
65
Динамические структуры данных в процессе существования в памяти мо-
гут изменять не только число составляющих их элементов, но и характер связей
между элементами. При этом не учитывается изменение содержимого самих
элементов данных. Такая особенность динамических структур, как непостоян-
ство их размера и характера отношений между элементами приводит к тому,
что на этапе создания машинного кода программа-компилятор не может выде-
лить для всей структуры в целом участок памяти фиксированного размера, а
также не может сопоставить с отдельными компонентами структуры конкрет-
ные адреса. Для решения проблемы адресации динамических структур данных
используется метод, называемый динамическим распределением памяти, то
есть память под отдельные элементы выделяется в тот момент, когда они
"начинают существовать" в процессе выполнения программы, а не во время
компиляции. Компилятор в этом случае выделяет фиксированный объем памя-
ти для хранения указателя - адреса динамически размещаемого элемента, а не
самого элемента. Тип указатель является статическим, под него память выделя-
ется во время компиляции, поэтому он требует описания. Значением указателя
является адрес динамического объекта.
Динамическая структура данных характеризуется тем что:
1) структуре выделяется память в процессе выполнения программы;
2) количество элементов структуры может не фиксироваться;
3) отсутствует физическая смежность элементов структуры в памяти;
4) размерность структуры может меняться в ходе выполнения программы;
5) в процессе выполнения программы может меняться характер взаимо-
связи между элементами структуры.
Необходимость в динамических структурах данных обычно возникает в
следующих случаях:
1) используются переменные, имеющие большой размер (например, мас­сивы), необходимые в одних частях программы и не нужные в других.
66
2) в процессе работы программы нужен массив, список или иная структу- ра, размер которой изменяется в широких пределах и трудно предсказуем.
3) когда размер данных, обрабатываемых в программе, превышает объем
сегмента данных.
К динамическим структурам относят:
- однонаправленные (односвязные) списки;
- двунаправленные (двусвязные) списки;
- циклические списки;
- стек;
- дек;
- очередь;
- бинарные деревья.
Они отличаются способом связи отдельных элементов и/или допустимы-
ми операциями.
Поскольку элементами динамической структуры являются динамические
переменные, то единственным средством доступа к динамическим структурам
и их элементам является указатель (адрес) на место их текущего расположения
в памяти. Таким образом, доступ к динамическим данным выполняется специ-
альным образом с помощью указателей. Как правило, это указатель на первый элемент структуры.
67
данные
ссылки
6. Линейные списковые структуры
Каждый элемент (узел) линейной списковой структуры состоит из двух
областей памяти: поля данных и ссылок. Ссылки - это адреса других узлов это­го же типа, с которыми данный элемент логически связан (рис. 12). В языке Си для организации ссылок используются переменные-указатели. При добавлении
нового узла в такую структуру выделяется новый блок памяти и (с помощью
ссылок) устанавливаются связи этого элемента с уже существующими. Для
обозначения конечного элемента в цепи используются нулевые ссылки (NULL).
Рис. 12. Элемент линейной списковой структуры
6.1. Односвязный список
В простейшем случае каждый узел содержит всего одну ссылку. Напри-
мер, пусть решается задача частотного анализа текста - определения всех слов,
встречающихся в тексте и их количества. В этом случае область данных эле-
мента включает строку (длиной не более 40 символов) и целое число.
Рис. 13. Односвязный список
Каждый элемент содержит также ссылку на следующий за ним элемент.
У последнего в списке элемента поле ссылки содержит NULL. Чтобы не поте-
рять список, должен где-то (в переменной) храниться адрес его первого узла ­он называется "головой" списка. В программе надо объявить два новых типа
68
данных - узел списка Node и указатель на него PNode. Узел представляет собой
struct Node {
// область данных
char
word[40]; int
count;
// ссылка на следующий узел
Node
*next; };
// тип данных: указатель на узел
typedef Node *PNode;
PNode CreateNode ( char NewWord[] )
{
PNode NewNode = new Node; // указатель на новый узел
strcpy(NewNode->word, NewWord); // записать слово
NewNode->count = 1; // счетчик слов = 1
NewNode->next = NULL; // следующего узла нет
return NewNode; // результат функции - адрес узла
}
структуру, которая содержит три поля - строку, целое число и указатель на та­кой же узел. Правилами языка Си допускается объявление:
В дальнейшем будем считать, что указатель Head указывает на начало
списка, то есть, объявлен в виде:
PNode Head = NULL;
Первая буква "P" в названии типа PNode происходит от слова pointer ­указатель. В начале работы в списке нет ни одного элемента, поэтому в указа­тель Head записывается нулевой адрес NULL.
6.1.1. Создание элемента списка
Для того, чтобы добавить узел к списку, необходимо создать его, то есть
выделить память под узел и запомнить адрес выделенного блока. Будем счи-
тать, что надо добавить к списку узел, соответствующий новому слову, которое
записано в переменной NewWord. Составим функцию, которая создает новый
узел в памяти и возвращает его адрес. При записи данных в узел используется
обращение к полям структуры через указатель.
После этого узел надо добавить к списку (в начало, в конец или в середину).
69
6.1.2. Добавление узла
void AddAfter (PNode p, PNode NewNode)
{
NewNode->next = p->next;
p->next = NewNode;
}
void AddBefore(PNode &Head, PNode p, PNode NewNode)
{
PNode q = Head;
if (Head == p) {
AddFirst(Head, NewNode); // вставка перед первым узлом
1. Добавление узла в начало списка.
При добавлении нового узла NewNode в начало списка необходимо:
1) установить ссылку узла NewNode на голову существующего списка и
2) установить голову списка на новый узел.
Рис. 14. Добавление узла в начало односвязного списка
п
оменя
Последовательность о
ть
ссылку у узла p, адрес с
п
ера
ций
л
еду
ме
нять нельз
ющег
о у
я,
зл
а будет
пот
ому что если с
пот
еря
н
.
н
ача
л
а
2. Добавление узла перед заданным.
Эта схема добавления самая сложная. Проблема заключается в том, что в
простейшем линейном списке (он называется односвязным, потому что связи
направлены только в одну сторону) для того, чтобы получить адрес предыду-
щего узла, нужно пройти весь список сначала. Задача сведется либо к вставке
узла в начало списка (если заданный узел - первый), либо к вставке после за­данного узла.
70
return;
}
while (q && q->next!=p) // ищем узел, за которым следует p q = q->next; if ( q ) // если нашли такой узел,
AddAfter(q, NewNode); // добавить новый после него
}
void AddLast(PNode &Head, PNode NewNode)
{
PNode q = Head;
if (Head == NULL) { // если список пуст,
AddFirst(Head, NewNode); // вставляем первый элемент
return;
}
while (q->next) q = q->next; // ищем последний элемент
AddAfter(q, NewNode);
}
PNode p = Head; // начали с головы списка
while ( p != NULL ) { // пока не дошли до конца
// делаем что-нибудь с узлом p
p = p->next; // переходим к следующему узлу
}
Такая процедура обеспечивает защиту: если задан узел, не присутствую-
щий в списке, то в конце цикла указатель q равен NULL и ничего не происходит.
3. Добавление узла в конец списка.
Для решения задачи надо сначала найти последний узел, у которого
ссылка равна NULL, а затем воспользоваться процедурой вставки после задан-
ного узла. Отдельно надо обработать случай, когда список пуст.
6.1.3. Проход по списку
Для того, чтобы пройти весь список и сделать что-либо с каждым его
элементом, надо начать с головы и, используя указатель next, продвигаться к
следующему узлу.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]