Связанный список является структурой, которая легко расширяется с помощью
одной функции, и бывает полезна, когда вам небходим массив, но вы изначально
понятия не имеете, какого размера. Концепция связанного списка такова, что
каждый узел структуры имеет указатель на следующий и предыдущий узел
структуры. Это называется двойным связанным списком , так как узел связан с
двумя различными узлами. Используя указатель на структуру, можно указать
нулевой указатель, если нет следующего или предыдущего узла. И так как
указатель хранит адрес памяти, количество узлов, которые можно хранить,
ограничивается только объемом памяти.
Единственным недостатком связанного списка является то, что для того, чтобы
хранить скажем
Integer, вы должны выделить место
не только для этого
Integer, но и структуру,
содержащую указатель на
Integer , а так же
указатель на окружающие узлы. Это не делает особой разницы на современных
компьютерах, если вы не храните миллионы узлов.
Базовой структурой связанного списка является узел. Декларация заключается в
следующем:
Type listnode
As Any Ptr pData
As listnode Ptr pNext
As listnode Ptr pPrev
End Type
Эта структура содержит три указателя. Первый является указателем на
что-нибудь (
Any Ptr), это означает, что вы можете
хранить строки, целые числа, символы, даже пользовательские типы и
union. Но это также означает, что вы должны
передать указатель. Вы можете выделить память для указателя с помощью
функций Allocate (или CAllocate).
Следующие два указатели, указатели на listnodes, то есть, технически это
можно сделать:
Print node->pNext->pNext->pNext->pNext->pNext...
поскольку каждый узел содержит указатель на другой узел. Проблема с
предыдущим вариантом синтаксиса в том, что вы ограничиваете себя кол-вом
узлов и кроме того код становится трудно понять. Вы можете использовать
функцию ListGetNext и цикл
While в ней для этой
цели.
Прежде чем идти дальше, давайте посмотрим, все декларации функций для
использования связанных списков. Обратите внимание, что каждая функция имеет
префикс "List".
Declare Function ListCreate() As listnode Ptr
Declare Function ListAdd(list As listnode Ptr, item As Any Ptr) As Any Ptr
Declare Function ListAddHead(list As listnode Ptr, item As Any Ptr) As Any Ptr
Declare Function ListGetFirst(list As listnode Ptr) As listnode Ptr
Declare Function ListGetLast(list As listnode Ptr) As listnode Ptr
Declare Function ListGetNext(list As listnode Ptr) As listnode Ptr
Declare Function ListGetPrev(list As listnode Ptr) As listnode Ptr
Declare Function ListGetData(list As listnode Ptr) As Any Ptr
Declare Function ListRemove(list As listnode Ptr, bDelete As Integer = 0) As listnode Ptr
Declare Sub ListRemoveAll(list As listnode Ptr, bDelete As Integer = 0)
Вы можете видеть, что есть функции для создания связанного списка,
добавления элемента, получения различных узлов, получения данных, и для
удаления узлов. В настоящее время мы сосредоточимся на функции ListCreate.
Она не принимает никаких параметров и возвращает указатель listnode.
Структура, которую она создает, не имеет данных для заполнения. Вся
структура является действительной, но это по-прежнему структура. Если
добавить узел, член pNext изменится и будет указывать на новый пункт,
поэтому он не будет оставаться как узел со значением null, так как не было
бы никакого смысла в этом. То есть по сути значение, возвращенное с помощью
ListCreate является нулевым узлом без данных о
предыдущем узле
pPrev и без данных для
pData, но в этом узле будет ссылка на первый узел
в
pNext.
Функция ListCreate выглядит так:
' СОЗДАНИЕ
Function ListCreate() As listnode Ptr
Dim As listnode Ptr pTemp
pTemp = CAllocate(Len(listnode))
' CAllocate автоматически
забивает нулями память.
Return pTemp
End Function
Я предпочитаю использовать R
eturn для возврата
значения из функции, но FUNCTION = pTemp и ListCreate = pTemp также
допускаются, хотя они не дают сразу выход из функции.
Суть этой функции легко увидеть, для узла выделяется память и происходит
возврат из функции. Комментарий говорит, что функция CAllocate автоматически
забивает нулями память. Если вы будете использовать функцию
Allocate, то память не будет автоматически
обнуляться и вам придется это делать самим.
Следующие функции, ListAdd и ListAddHead, добавление узла к списку. ListAdd
добавляет узел в конец списка (в хвост), а ListAddHead вставляет узел на
самом верху (в голову).
' ДОБАВЛЕНИЕ В КОНЕЦ СПИСКА, ДОБАВЛЕНИЕ В НАЧАЛО СПИСКА
Function ListAdd(list As listnode Ptr, item As Any Ptr) As Any Ptr
Dim As listnode Ptr pTemp
If (list = 0) Then Return item
pTemp = ListGetLast(list)
pTemp->pNext = CAllocate(Len(listnode))
pTemp->pNext->pPrev = pTemp
pTemp->pNext->pData = item
Return item
End Function
Function ListAddHead(list As listnode Ptr, item As Any Ptr) As Any Ptr
Dim As listnode Ptr pTemp
If (list = 0) Then Return item
pTemp = list->pNext
list->pNext = CAllocate(Len(listnode))
list->pNext->pPrev = list
list->pNext->pData = item
list->pNext->pNext = pTemp
If (pTemp <> 0) Then
pTemp->pPrev = list->pNext
End If
Return item
End Function
Вы можете увидеть, что ListAdd делает ссылку на функцию ListGetLast. Пока
все, что вам нужно знать, это то, что она возвращает указатель на последний
узел в списке. Позже она будет рассмотрена.
ListAdd извлекает последний узел и задает его в указатель pNext новой
структуры listnode. Это не вызовет потерю памяти, поскольку последний узел
имеет значение null. После того, как наш узел добавился, мы можем получить
доступ к нему с помощью оператора ->. Линия
pTemp->pNext->pPrev = pTemp
является всей основой связанных списков, за счет связующих звеньев. Это
говорит о том, что у нас есть ссылка на узел. Это узел знает, где ссылка на
следующий узел, и теперь мы определяем для узла где будет предыдущий узел.
Это может выглядеть немного излишним сначала, но компилятор не знает, где
узлы, пока вы не установите их. После того как вы сделали это, вы можете
пройти по связанному списку.
Функция ListAddHead немного сложнее, так как мы на самом деле вставляем узел
между текущим первым узлом и нулевым узлом от ListCreate. Что она делает?
В основном выделяет место для хранения текущего первого узла, создает там
новый узел, и связывает их все вместе. Если вы немного изучите пример, то
это станет казаться намного яснее. Заявление
IF в
конце просто гарантирует, что мы не пытаемся получить доступ к памяти,
которая не существует (NULL-> pPrev). Если pTemp, фактически не равен нулю,
то его член pPrev будет назначен. В противном случае, нет никаких оснований
беспокоиться об этом.
Следующие функции ListGetFirst и ListGetLast. Я реализовал их рядом, потому
что ListGetLast была ссылкой в вышеупомянутой функции.
' ПОЛУЧИТЬ ПЕРВЫЙ УЗЕЛ, ПОЛУЧИТЬ ПОСЛЕДНИЙ УЗЕЛ
Function ListGetFirst(list As listnode Ptr) As listnode Ptr
If (list = 0) Then Return 0
Return list->pNext
End Function
Function ListGetLast(list As listnode Ptr) As listnode Ptr
Dim As listnode Ptr pTemp
If (list = 0) Then Return 0
pTemp = list
While (pTemp->pNext <> 0)
pTemp = pTemp->pNext
Wend
Return pTemp
End Function
Первая функция, вероятно самая простая для понимания. Ее работа опирается на
тот факт, что вы имеете указатель на узел, возвращенный ListCreate и с
помощью этого указателя вы получаете указатель на первый узел или узел,
который приходит сразу после нулевого узла.
Вторая функция ListGetLast, перебирает список до тех пор, пока не будет
найден узел со значением null. Причина, по которой я проверяю if pTemp->pNext
= 0 вместо pTemp = 0 это то, что я не хочу возвращать нуль. Я хочу вернуть
последний узел , который имеет в pNext значение, равное нулю. После того,
как узел найден, ListGetLast возвращает его.
Следующие 3 функции - просто вспомогательные функции и могли быть легко
достигнуты одной линией кода, но все таки удобнее данную функциональность
завернуть в функции.
' ПОЛУЧЕНИЕ СЛЕДУЮЩЕГО УЗЛА, ПОЛУЧЕНИЕ ПРЕДЫДУЩЕГО УЗЛА
Function ListGetNext(list As listnode Ptr) As listnode Ptr
If (list = 0) Then Return 0
Return list->pNext
End Function
Function ListGetPrev(list As listnode Ptr) As listnode Ptr
' не делать ничего, если список
равен нулю
If (list = 0) Then Return 0
' Это необходимо для нижней
записи
If (list->pPrev = 0) Then Return 0
' Поскольку список начинается с
нулевого узла(pPrev = 0 и pData = 0),
' мы проверяем: а не окажется ли
предыдущий узел нулевым.
If (list->pPrev->pPrev = 0) Then Return 0
Return list->pPrev
End Function
' ПОЛУЧЕНИЕ ДАННЫХ УЗЛА
Function ListGetData(list As listnode Ptr) As Any Ptr
If (list = 0) Then Return 0
Return list->pData
End Function
Первая функция, ListGetNext, точно такая же, как ListGetFirst, но разница в
том как вы будете ее использовать. И хотя вы можете использовать
ListGetFirst в этой реализации, это не очень умная идея, потому что в
некоторых других реализациях могут быть циклы к началу списка, чтобы найти
первый узел, и в этом случае вы бы застряли в бесконечном цикле.
Функция ListGetPrev немного сложнее, так как я не хочу, возвращать нулевой
узел. Первая и третья строка кода (не учитывая комментарии) действительно
необходимы, но вторая гарантирует, что мы не обращается к нулевой
памяти. Третья строка говорит, что если два узла до нулевого узла, мы должны
вернуть ноль. Это делается для того, чтобы мы не могли возвратить ссылку на
нулевой узел, в котором по сути нет никаких данных, кроме ссылки на реально
используемый первый узел. Последняя строка обрабатывает условие по
умолчанию, где на самом деле является предыдущий узел, и он должен быть
возвращен.
Функция ListGetData так же проста, как и функции ListGetFirst и ListGetNext.
Она просто возвращает указатель на данные узла.
Окончательные две функции для удаления узлов из списка.
' УДАЛЕНИЕ УЗЛА, УДАЛЕНИЕ ВСЕХ УЗЛОВ
Function ListRemove(list As listnode Ptr, bDelete As Integer = 0) As listnode Ptr
Dim As listnode Ptr pPrev
Dim As listnode Ptr pNext
If (list = 0) Then Return 0
pPrev = list->pPrev
pNext = list->pNext
If ((list->pData <> 0) And (bDelete <> 0)) Then Deallocate list->pData
Deallocate list
If (pPrev <> 0) Then
pPrev->pNext = pNext
End If
If (pNext <> 0) Then
pNext->pPrev = pPrev
End If
Return pNext
End Function
Sub ListRemoveAll(list As listnode Ptr, bDelete As Integer = 0)
Dim As listnode Ptr node
node = list
If (list = 0) Then Return
While (node <> 0)
If ((node->pData <> 0) And (bDelete <> 0)) Then Deallocate node->pData
node = ListRemove(node)
Wend
End Sub
Функция ListRemove выполняет две работы: удаляет указанный узел и
связывает два окружающих его узла вместе. Вы можете видеть, что он хранит
предыдущий и следующий указатель, чтобы осуществить это. Необязательный
параметр, bDelete нужен для указания удалять или нет элемент данных. Если вы
просто храните целые числа, или даже структуры без инициализированных
указателей в них, можно указать 1 для этого параметра и узел будет удален.
Но если у вас сложные структуры, для которых вы сами выделяли память, то во
избежание утечек памяти, лучше проделать удаление данных отдельным кодом.
Указатель listnode освобождается независимо от параметра
bDelete.
ListRemoveAll опирается на функцию ListRemove для удаления узлов. Она просто
перебирает список, используя цикл
WHILE и удаляет
все узлы. Оригинальный код, использует цикл
FOR,
но FB кажется, не нравится такое
For node = list To 0 Step ListRemove(node)
таким образом, это было изменено.
Вот и все, ниже готовый пример всего вышесказанного. Это мой первый учебник,
так что не стесняйтесь оставлять комментарии о тех вещах, которые можно
улучшить. Кроме того, если вы найдете ошибку в коде (я нашел пару ошибок во
время написания учебника), пожалуйста, дайте мне знать. Не стесняйтесь
редактировать ошибки, но я хотел бы знать о них.
Type listnode
As Any Ptr pData
As listnode Ptr pNext
As listnode Ptr pPrev
End Type
Declare Function ListCreate() As listnode Ptr
Declare Function ListAdd(list As listnode Ptr, item As Any Ptr) As Any Ptr
Declare Function ListAddHead(list As listnode Ptr, item As Any Ptr) As Any Ptr
Declare Function ListGetFirst(list As listnode Ptr) As listnode Ptr
Declare Function ListGetLast(list As listnode Ptr) As listnode Ptr
Declare Function ListGetNext(list As listnode Ptr) As listnode Ptr
Declare Function ListGetPrev(list As listnode Ptr) As listnode Ptr
Declare Function ListGetData(list As listnode Ptr) As Any Ptr
Declare Function ListRemove(list As listnode Ptr, bDelete As Integer = 0) As listnode Ptr
Declare Sub ListRemoveAll(list As listnode Ptr, bDelete As Integer = 0)
Dim As listnode Ptr list, node
Dim As Integer Ptr item
list = ListCreate()
item = ListAdd(list, CAllocate(Len(Integer)))
*item = 4
item = ListAdd(list, CAllocate(Len(Integer)))
*item = 44
item = 0 ' просто
чтобы показать, что это работает
node = ListGetFirst(list)
While node <> 0
Print "found item"
item = ListGetData(node)
Print *item
node = ListRemove(node,1)
Wend
While Inkey$ = "" : Wend
' СОЗДАНИЕ
Function ListCreate() As listnode Ptr
Dim As listnode Ptr pTemp
pTemp = CAllocate(Len(listnode))
' CAllocate автоматически
забивает нулями память.
Return pTemp
End Function
' ДОБАВЛЕНИЕ В КОНЕЦ СПИСКА,
ДОБАВЛЕНИЕ В НАЧАЛО СПИСКА
Function ListAdd(list As listnode Ptr, item As Any Ptr) As Any Ptr
Dim As listnode Ptr pTemp
If (list = 0) Then Return item
pTemp = ListGetLast(list)
pTemp->pNext = CAllocate(Len(listnode))
pTemp->pNext->pPrev = pTemp
pTemp->pNext->pData = item
Return item
End Function
Function ListAddHead(list As listnode Ptr, item As Any Ptr) As Any Ptr
Dim As listnode Ptr pTemp
If (list = 0) Then Return item
pTemp = list->pNext
list->pNext = CAllocate(Len(listnode))
list->pNext->pPrev = list
list->pNext->pData = item
list->pNext->pNext = pTemp
If (pTemp <> 0) Then
pTemp->pPrev = list->pNext
End If
Return item
End Function
' ПОЛУЧИТЬ ПЕРВЫЙ
УЗЕЛ, ПОЛУЧИТЬ ПОСЛЕДНИЙ УЗЕЛ
Function ListGetFirst(list As listnode Ptr) As listnode Ptr
If (list = 0) Then Return 0
Return list->pNext
End Function
Function ListGetLast(list As listnode Ptr) As listnode Ptr
Dim As listnode Ptr pTemp
If (list = 0) Then Return 0
pTemp = list
While (pTemp->pNext <> 0)
pTemp = pTemp->pNext
Wend
Return pTemp
End Function
' ПОЛУЧЕНИЕ СЛЕДУЮЩЕГО
УЗЛА, ПОЛУЧЕНИЕ ПРЕДЫДУЩЕГО УЗЛА
Function ListGetNext(list As listnode Ptr) As listnode Ptr
If (list = 0) Then Return 0
Return list->pNext
End Function
Function ListGetPrev(list As listnode Ptr) As listnode Ptr
' не делать ничего, если список
равен нулю
If (list = 0) Then Return 0
' Это необходимо для нижней
записи
If (list->pPrev = 0) Then Return 0
' Поскольку список начинается с
нулевого узла(pPrev = 0 и pData = 0),
' мы проверяем: а не окажется ли
предыдущий узел нулевым.
If (list->pPrev->pPrev = 0) Then Return 0
Return list->pPrev
End Function
' ПОЛУЧЕНИЕ ДАННЫХ УЗЛА
Function ListGetData(list As listnode Ptr) As Any Ptr
If (list = 0) Then Return 0
Return list->pData
End Function
' УДАЛЕНИЕ УЗЛА, УДАЛЕНИЕ
ВСЕХ УЗЛОВ
Function ListRemove(list As listnode Ptr, bDelete As Integer = 0) As listnode Ptr
Dim As listnode Ptr pPrev
Dim As listnode Ptr pNext
If (list = 0) Then Return 0
pPrev = list->pPrev
pNext = list->pNext
If ((list->pData <> 0) And (bDelete <> 0)) Then Deallocate list->pData
Deallocate list
If (pPrev <> 0) Then
pPrev->pNext = pNext
End If
If (pNext <> 0) Then
pNext->pPrev = pPrev
End If
Return pNext
End Function
Sub ListRemoveAll(list As listnode Ptr, bDelete As Integer = 0)
Dim As listnode Ptr node
node = list
If (list = 0) Then Return
While (node <> 0)
If ((node->pData <> 0) And (bDelete <> 0)) Then Deallocate node->pData
node = ListRemove(node)
Wend
End Sub
Если вы еще не заметили , ListAdd и ListAddHead возвращает указатель на
данные, которые вы вводили. Пример кода (см. выше) показывает, как
использовать эту функцию. ListRemove возвращает указатель на следующий узел.
ListRemoveAll удаляет узлы. ListRemoveAll является единственной функцией,
которая ничего не возвращает. Там нет необходимости, так как весь список
будет пуст после того как вы ее назвали.