
Нелинейные списки
Односвязный список всегда линейный. Двусвязный список может быть и нелинейным, то есть, II указатель двусвязного списка задает произвольный порядок следования. Таким образом, каждый элемент этого списка содержится в двух односвязных списках, при этом переменные S1 и S2 являются указателями начала двух разных односвязных списков, например,
S1 S2
D D A1 0 D A1 A2 D A1 0 D 0 A2
A1
A2
Причем, может быть так, что каждый элемент входит в оба списка, а может быть, и нет. Для каждого списка существует свой дескриптор. При включении и исключении одного элемента придется обрабатывать каждый список.
Если соединить входы и выходы в каждом списке, то получится нелинейная кольцевая структура.
Многосвязные списки
В более общем случае каждый элемент связного списка может содержать произвольное конечное число связок, причем, различное в различных элементах. В этом случае получается многосвязный список, каждый элемент которого входит одновременно в несколько разных односвязных списков.
Такие списки еще называют прошитыми.
Например, есть списки абитуриентов, содержащие общие сведения: фамилию, имя и отчество, год рождения и т.д. Приемную комиссию дополнительно интересует:
Москвич или нет (нуждается ли в общежитии);
Медалисты;
Отличники при сдаче вступительных экзаменов.
Рассмотрим пример такого списка:
Д
Данныеескриптор
о
ОСбщего
списка
S1 Москв.адрес
начала 1
100 52
кол
- во эл - ов 3
1 Мед.№
ук-ля списка 4
Д
Данныеескриптор
с
ОС
S2 35 2 М 5 Мед. Д ОС М 5 Мед. Д ОС М 5 Мед. ОС М 5 Мед.
Дескриптор
списка
медалистов
S3 14 4н
е
л
и
н
Дескриптор е
отличников й
S4
18 3 Д ОС М 5 Мед.ы
й
с
Д
и
с
о
к
Пример различного представления связного списка (различными структурами)
Дерево: ссылки:
А 3
родитель
1 Д 0 С
сыновья
2 В
F 0 0 G
Е 0
братья
Преобразование в бинарные
братья
дети
Наиболее общий вид многосвязной структуры характеризуется следующими свойствами:
Каждый элемент структуры содержит произвольное число направленных связок с другими элементами (или ссылки на другие элементы);
С каждым элементом может связываться произвольное число других элементов;
Каждая связка имеет не только направление, но и вес.
Такую структуру еще называют сетью, логически она эквивалентна взвешенному ориентированному графу общего вида, и поэтому вместо термина «сеть» часто употребляется термин «графовая структура», а вместо элемента – «узел». Сетевые структуры используются в организации банковского дела, СУБД, программном имитационном моделировании, системах искусственного интеллекта, где они отражают логику организации данных и сложные отношения, возникающие между элементами данных.
Сравнение реализаций списков:
Если невозможно заранее узнать количество элементов в списке, то реализовать его лучше посредством указателей;
Если вы хотите произвести операцию включения или исключения элемента по позиции (с номера), то реализуйте список посредством массивов, а не указателей, так как в массивах есть прямой доступ к элементу массива и он легче, а указатели вообще не отслеживают позицию.
Но с другой стороны, операции включения или исключения из массива происходят долго, а с помощью указателей реализовать эти операции и быстрее, и проще.
Массивы – расточительное выделение памяти - сразу под весь массив, Указатели – дополнительная память в каждом элементе на указатель.
То есть, каждый раз при реализации списков надо думать, какой способ реализации выгоднее в данном случае.
Выделение и освобождение динамической памяти
Рассмотрим возможности использования динамической памяти, реализованной в Турбо-Паскале. Вся динамическая память рассматривается как сплошной массив байтов, который называется кучей (HEAP). Физически куча располагается в старших адресах сразу за областью памяти, которую занимает программа.
Системная
область
Старшие адреса памяти
Куча Программа Системная
область HeapEnd HeapOrg
С
HeapPtr
ные пере-
менные Младшие адреса памяти
Обращение к процедурам New и Delete обычно приводит к ячеистой структуре (фрагментации).
Все операции с кучей идут под управлением специальной программы – администратора кучи. Она автоматически подключается к Вашей программе компоновщиком Турбо-Паскаля и ведет учет всех свободных фрагментов в куче. Администратор кучи обрабатывает запросы New, Getmem, Dispose, Freemem и изменяет значения указателей HeapPtr и FreeList.
Системная
область
BIOS, Basic, расш. BIOS)
Куча
Next Size
= 0 TFreeRec
Программа
Системная
область
Младшие адреса
На этом рисунке HeapPtr – адрес нижней границы свободной кучи;
FreeList – адрес описателя первого свободного блока памяти, который указывает на структуру:
Type
PFreeRec = ^TFreeRec;
TFreeRec = Record
Next : Pointer;
Size : Pointer;
End;
Эта структура используется для описания всех свободных областей памяти, расположенных ниже границы HeapPtr, т.е появилась фрагментация. Поле Next содержит адрес описателя следующего по списку свободного блока кучи или адрес, совпадающий с HeapEnd, если этот участок последний в списке. Поле Size содержит либо ненормированную длину свободного блока, либо 0, если ниже HeapPtr нет свободных участков.
Ненормированная длина определяется таким образом:
в старшем слове содержится количество свободных параграфов (§ –16 байт), в младшем – количество свободных байт от 0 до 15.
Функция, преобразующая ненормированную длину свободного блока в байты, выглядит следующим образом:
Function BlockSize (Size : Pointer) : Longint;
Type
PtrRec = Record
Lo : Word; {свободные байты}
Hi : Word; {свободные §-фы }
End;
Var
LenghtBlock : Longint;
Begin
BlockSize := Longint(PtrRec(Size).Hi * 16 + PtrRec(Size). Lo);
End;
Сразу после загрузки получим: HeapPtr = FreeList = HeapOrg. При этом в первых 8 байтах кучи хранится запись типа TFreeRec, а Next = HeapEnd, Size = 0. При освобождении памяти уменьшится значение HeapPtr, FreeList начнет ссылаться на него и в его начале будет запись TFreeRec. Используя FreeList как начало списка, администратор кучи всегда может просмотреть весь список и при необходимости модифицировать его. Так как в любой свободный блок помещается описатель блока, равный 8 байтам, то он (блок) не может быть меньше 8 байт, даже если программа запросит 1 байт. Это надо учитывать для того, чтобы минимизировать возможные потери динамической памяти. Если же администратор кучи не может найти свободный участок памяти, то идет обращение к функции, адрес которой содержится в переменной HeapError.
В других языках программирования возможен другой подход к распределению и освобождению памяти, например, аналогично нуль-терминальным строкам: операторы присваивания реализуются путем изменения указателе и тогда освобождение памяти под переменную не гарантирует не использование ее (памяти). Для возможности ее освобождения используется счетчик обращений (К) к данному адресу, при К=0 память освобождается. Или еще подход – выделение памяти идет до тех пор, пока память есть в наличии, освободившаяся память помечается, но остается недоступной. Когда свободной памяти не остается, то инициализируется программа чистки памяти, которая собирает все ненужные ячейки, создавая список свободного пространства. При работе с динамической памятью самый плохой вариант, когда остаются недоступные участки, так как утерян адрес, ссылающийся на них:
b
a
…
…
Таким образом, это еще раз подтверждает, что работа с динамической памятью требует от программиста внимательности и профессионализма.