Смекни!
smekni.com

VB, MS Access, VC++, Delphi, Builder C++ принципы(технология), алгоритмы программирования (стр. 11 из 72)

Это намного упрощает основную программу. Например, следующий код показывает, как программа LnkList2 удаляет элемент из списка. Только одна строка в программе в действительности отвечает за удаление элемента. Остальные отображают новый список. Сравните этот код с предыдущей процедурой:

Private sub CmdRemoveAfter_Click()

Llist.RemoveAfter SelectedIndex

SelectedItem SelectedList ‘ Снова выбрать элемент.

DisplayList

NewItem.SetFocus

CmdClearList.Enabled

End Sub

=====33

Доступ к ячейкам

Класс LinkedList, используемый программой LnkLst2, позволяет основной программе использовать список почти как массив. Например, подпрограмма Item, приведенная в следующем коде, возвращает значение элемента по его положению:

Function Item(ByVal position As Long) As Variant

Dim ptr As ListCell

If position < 1 Or position > m_NumItems Then

‘ Выход за границы. Вернуть NULL.

Item = Null

Exit Function

End If

‘ Найти элемент.

Set ptr = m_Sentinel

Do While position > 0

position = position - 1

Set ptr = ptr.NextCell

Loop

Item = ptr.Value

End Function

Эта процедура достаточно проста, но она не использует преимущества связной структуры списка. Например, предположим, что программе требуется последовательно перебрать все объекты в списке. Она могла бы использовать подпрограмму Item для поочередного доступа к ним, как показано в следующем коде:

Dim i As Integer

For i = 1 To LList.NumItems

‘ Выполнить какие‑либо действия с LList.Item(i).

:

Next i

При каждом вызове процедуры Item, она просматривает список в поиске следующего элемента. Чтобы найти элемент I, программа должна пропустить I‑1 элементов. Чтобы проверить все элементы в списке из N элементов, процедура пропустит 0+1+2+3+…+N-1 =N*(N-1)/2 элемента. При больших N программа потеряет много времени на пропуск элементов.

Класс LinkedList может ускорить эту операцию, используя другой метод доступа. Можно использовать частную переменную m_CurrentCell для отслеживания текущей позиции в списке. Для возвращения значения текущего положения используется подпрограмма CurrentItem. Процедуры MoveFirst, MoveNext и EndOfList позволяют основной программе управлять текущей позицией в списке.

=======34

Например, следующий код содержит подпрограмму MoveNext:

Public Sub MoveNext()

‘ Если текущая ячейка не выбрана, ничего не делать.

If Not (m_CurrentCell Is Nothing) Then _

Set m_CurrentCell = m_CurrentCell.NextCell

End Sub

При помощи этих процедур, основная программа может обратиться ко всем элементам списка, используя следующий код. Эта версия несколько сложнее, чем предыдущая, но она намного эффективнее. Вместо того чтобы пропускать N*(N-1)/2 элементов и опрашивать по очереди все N элементов списка, она не пропускает ни одного. Если список состоит из 1000 элементов, это экономит почти полмиллиона шагов.

LList.MoveFirst

Do While Not LList.EndOfList

‘ Выполнить какие‑либо действия над элементом LList.Item(i).

:

LList.MoveNext

Loop

Программа LnkList3 использует эти новые методы для управления связным списком. Она аналогична программе LnkList2, но более эффективно обращается к элементам. Для небольших списков, используемых в программе, эта разница незаметна. Для программы, которая обращается ко всем элементам большого списка, эта версия класса LinkedList более эффективна.

Разновидности связных списков

Связные списки играют важную роль во многих алгоритмах, и вы будете встречаться с ними на протяжении всего материала. В следующих разделах обсуждаются несколько специальных разновидностей связных списков.

Циклические связные списки

Вместо того, чтобы устанавливать указатель NextCell равным Nothing, можно установить его на первый элемент списка, образуя циклический список (circular list), как показано на рис. 2.7.

Циклические списки полезны, если нужно обходить ряд элементов в бесконечном цикле. При каждом шаге цикла, программа просто перемещает указатель на следующую ячейку в списке. Допустим, имеется циклический список элементов, содержащий названия дней недели. Тогда программа могла бы перечислять дни месяца, используя следующий код:

===========35

@Рис. 2.7. Циклический связный список

‘ Здесь находится код для создания и настройки списка и т.д.

:

‘ Напечатать календарь на месяц.

‘ first_day — это индекс структуры, содержащей день недели для

‘ первого дня месяца. Например, месяц может начинаться

‘ в понедельник.

‘ num_days — число дней в месяце.

Private Sub ListMonth(first_day As Integer, num_days As Integer)

Dim ptr As ListCell

Dim i As Integer

Set ptr = top_cell

For i = 1 to num_days

Print Format$(i) & ": " & ptr.Value

Set ptr = ptr.NextCell

Next I

End Sub

Циклические списки также позволяют достичь любой точки в списке, начав с любого положения в нем. Это вносит в список привлекательную симметрию. Программа может обращаться со всеми элементами списка почти одинаковым образом:

Private Sub PrintList(start_cell As Integer)

Dim ptr As Integer

Set ptr = start_cell

Do

Print ptr.Value

Set ptr = ptr.NextCell

Loop While Not (ptr Is start_cell)

End Sub

========36

Проблема циклических ссылок

Уничтожение циклического списка требует немного больше внимания, чем удаление обычного списка. Если вы просто установите значение переменной top_cell равным Nothing, то программа не сможет больше обратиться к списку. Тем не менее, поскольку счетчик ссылок первой ячейки не равен нулю, она не будет уничтожена. На каждый элемент списка указывает какой‑либо другой элемент, поэтому ни один из них не будет уничтожен.

Это проблема циклических ссылок (circular referencing problem). Так как ячейки указывают на другие ячейки, ни одна из них не будет уничтожена. Программа не может получить доступ ни к одной из них, поэтому занимаемая ими память будет расходоваться напрасно до завершения работы программы.

Проблема циклических ссылок может встретиться не только в этом случае. Многие сети содержат циклические ссылки — даже одиночная ячейка, поле NextCell которой указывает на саму эту ячейку, может вызвать эту проблему.

Решение ее состоит в том, чтобы разбить цепь ссылок. Например, вы можете использовать в своей программе следующий код для уничтожения циклического связного списка:

Set top_cell.NextCell = Nothing

Set top_cell = Nothing

Первая строка разбивает цикл ссылок. В этот момент на вторую ячейку списка не указывает ни одна переменная, поэтому система уменьшает счетчик ссылок ячейки до нуля и уничтожает ее. Это уменьшает счетчик ссылок на третий элемент до нуля, и соответственно, он также уничтожается. Этот процесс продолжается до тех пор, пока не будут уничтожены все элементы списка, кроме первого. Установка значения top_cell элемента в Nothing уменьшает его счетчик ссылок до нуля, и последняя ячейка также уничтожается.

Двусвязные списки

Во время обсуждения связных списков вы могли заметить, что большинство операций определялось в терминах выполнения чего‑либо после определенной ячейки в списке. Если задана определенная ячейка, легко добавить или удалить ячейку после нее или перечислить идущие за ней ячейки. Удалить саму ячейку, вставить новую ячейку перед ней или перечислить идущие перед ней ячейки уже не так легко. Тем не менее, небольшое изменение позволит облегчить и эти операции.

Добавим новое поле указателя к каждой ячейке, которое указывает на предыдущую ячейку в списке. Используя это новое поле, можно легко создать двусвязный список (doubly linked list), который позволяет перемещаться вперед и назад по списку. Теперь можно легко удалить ячейку, вставить ее перед другой ячейкой и перечислить ячейки в любом направлении.

@Рис. 2.8. Двусвязный список

============37

Класс DoubleListCell, который используется для таких типов списков, может объявлять переменные так:

Public Value As Variant

Public NextCell As DoubleListCell

Public PrevCell As DoubleListCell

Часто бывает полезно сохранять указатели и на начало, и на конец двусвязного списка. Тогда вы сможете легко добавлять элементы к любому из концов списка. Иногда также бывает полезно размещать сигнальные метки и в начале, и в конце списка. Тогда по мере работы со списком вам не нужно будет заботиться о том, работаете ли вы с началом, с серединой или с концом списка.

На рис. 2.9 показан двусвязный список с сигнальными метками. На этом рисунке неиспользуемые указатели меток NextCell и PrevCell установлены в Nothing. Поскольку программа опознает концы списка, сравнивая значения указателей ячеек с сигнальными метками, и не проверяет, равны ли значения Nothing, установка этих значений равными Nothing не является абсолютно необходимой. Тем не менее, это признак хорошего стиля.

Код для вставки и удаления элементов из двусвязного списка подобен приведенному ранее коду для односвязного списка. Процедуры нуждаются лишь в незначительных изменениях для работы с указателями PrevCell.

@Рис. 2.9. Двусвязный список с сигнальными метками

Теперь вы можете написать новые процедуры для вставки нового элемента до или после данного элемента, и процедуру удаления заданного элемента. Например, следующие подпрограммы добавляют и удаляют ячейки из двусвязного списка. Заметьте, что эти процедуры не нуждаются в доступе ни к одной из сигнальных меток списка. Им нужны только указатели на узел, который должен быть удален или добавлен и узел, соседний с точкой вставки.

Public Sub RemoveItem(ByVal target As DoubleListCell)

Dim after_target As DoubleListCell

Dim before_target As DoubleListCell

Set after_target = target.NextCell

Set before_target = target.PrevCell

Set after_target.NextCell = after_target

Set after_target.PrevCell = before_target

End Sub

Sub AddAfter (new_Cell As DoubleListCell, after_me As DoubleListCell)

Dim before_me As DoubleListCell

Set before_me = after_me.NextCell

Set after_me.NextCell = new_cell

Set new_cell.NextCell = before_me

Set before_me.PrevCell = new_cell

Set new_cell.PrevCell = after_me

End Sub

Sub AddBefore(new_cell As DoubleListCell, before_me As DoubleListCell)

Dim after_me As DoubleListCell

Set after_me = before_me.PrevCell

Set after_me.NextCell = new_cell

Set new_cell.NextCell = before_me

Set before_me.PrevCell = new_cell

Set new_cell.PrevCell = after_me

End Sub

===========39

Если снова взглянуть на рис. 2.9, вы увидите, что каждая пара соседних ячеек образует циклическую ссылку. Это делает уничтожение двусвязного списка немного более сложной задачей, чем уничтожение односвязных или циклических списков. Следующий код приводит один из способов очистки двусвязного списка. Вначале указатели PrevCell всех ячеек устанавливаются равными Nothing, чтобы разорвать циклические ссылки. Это, по существу, превращает список в односвязный. Когда ссылки сигнальных меток устанавливаются в Nothing, все элементы освобождаются автоматически, так же как и в односвязном списке.