Мегаобучалка Главная | О нас | Обратная связь


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



2019-07-03 416 Обсуждений (0)
Циклические связные списки 0.00 из 5.00 0 оценок




Вместо того, чтобы устанавливать указатель 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, все элементы освобождаются автоматически, так же как и в односвязном списке.

 

Dim ptr As DoubleListCell

' Очистить указатели PrevCell, чтобы разорвать циклические ссылки.

Set ptr = TopSentinel.NextCell

Do While Not (ptr Is BottomSentinel)

   Set ptr.PrevCell = Nothing

   Set ptr = ptr.NextCell

Loop

Set TopSentinel.NextCell = Nothing

Set BottomSentinel.PrevCell = Nothing

 

Если создать класс, инкапсулирующий двусвязный список, то его обработчик события Terminate сможет уничтожать список. Когда основная программа установит значение ссылки на список равным Nothing, список автоматически освободит занимаемую память.

Программа DblLink работает с двусвязным списком. Она позволяет добавлять элементы до или после выбранного элемента, а также удалять выбранный элемент.

 

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

 

Потоки

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

Обычный связный список позволяет просматривать элементы только в одном порядке. Используя указатель PrevCell, можно создать двусвязный список, который позволит перемещаться по списку вперед и назад. Этот подход можно развить и дальше, добавив больше указателей на структуру данных, позволяя выводить список в другом порядке.

Набор ссылок, который задает какой‑либо порядок просмотра, называется потоком (thread), а сам полученный список — многопоточным списком (threaded list). Не путайте эти потоки с потоками, которые предоставляет система Windows NT.

Список может содержать любое количество потоков, хотя, начиная с какого‑то момента, игра не стоит свеч. Применение потока, упорядочивающего список сотрудников по фамилии, будет обосновано, если ваше приложение часто использует этот порядок, в отличие от расположения по отчеству, которое вряд ли когда будет использоваться.

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

Сравните этот случай с тем, когда вы хотите упорядочить список сотрудников по фамилии. Если список не включает поток фамилий, вам придется найти фамилию, которая будет первой в списке, затем следующую и т.д. Это процесс со сложностью порядка O(N2), который намного менее эффективен, чем сортировка по полу со сложностью порядка O(N).

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

Программа Treads демонстрирует простой многопоточный список сотрудников. Заполните поля фамилии, специальности, пола и номера социального страхования для нового сотрудника. Затем нажмите на кнопку Add (Добавить), чтобы добавить сотрудника к списку.

Программа содержит потоки, которые упорядочивают список по фамилии по алфавиту и в обратном порядке, по номеру социального страхования и специальности в прямом и обратном порядке. Вы можете использовать дополнительные кнопки для выбора потока, в порядке которого программа выводит список. На рис. 2.10 показано окно программы Threads со списком сотрудников, упорядоченным по фамилии.

Класс ThreadedCell, используемый программой Threads, определяет следующие переменные:

 

Public LastName As String

Public FirstName As String

Public SSN As String

Public Sex As String

Public JobClass As Integer

Public NextName As TreadedCell ‘ По фамилии в прямом порядке.

Public PrevName As TreadedCell ‘ По фамилии в обратном порядке.

Public NextSSN As TreadedCell ‘ По номеру в прямом порядке.

Public NextJobClass As TreadedCell ‘ По специальности в прямом порядке.

Public PrevJobClass As TreadedCell ‘ По специальности в обратном порядке.

 

Класс ThreadedList инкапсулирует многопоточный список. Когда программа вызывает метод AddItem, список обновляет свои потоки. Для каждого потока программа должна вставить элемент в правильном порядке. Например, для того, чтобы вставить запись с фамилией «Смит», программа обходит список, используя поток NextName, до тех пор, пока не найдет элемент с фамилией, которая должна следовать за «Смит». Затем она вставляет в поток NextName новую запись перед этим элементом.

При определении местоположения новых записей в потоке важную роль играют сигнальные метки. Обработчик событий Class_Initialize класса ThreadedList создает сигнальные метки на вершине и в конце списка и инициализирует их указатели так, чтобы они указывали друг на друга. Затем значение метки в начале списка устанавливается таким образом, чтобы оно всегда находилось до любого значения реальных данных для всех потоков.

Например, переменная LastName может содержать строковые значения. Пустая строка "" идет по алфавиту перед любыми действительными значениями строк, поэтому программа устанавливает значение сигнальной метки LastName в начале списка равным пустой строке.

Таким же образом Class_Initialize устанавливает значение данных для метки в конце списка, превосходящее любые реальные значения во всех потоках. Поскольку "~" идет по алфавиту после всех видимых символов ASCII, программа устанавливает значение поля LastName для метки в конце списка равным "~".

Присваивая полю LastName сигнальных меток значения "" и "~", программа избавляется от необходимости проверять особые случаи, когда нужно вставить новый элемент в начало или конец списка. Любые новые действительные значения будут находиться между значениями LastValue сигнальных меток, поэтому программа всегда сможет определить правильное положение для нового элемента, не заботясь о том, чтобы не зайти за концевую метку и не выйти за границы списка.

 

@Рис. 2.10. Программа Threads

 

=====41

 

Следующий код показывает, как класс ThreadedList вставляет новый элемент в потоки NextName и PrevName. Так как эти потоки используют один и тот же ключ — фамилии, программа может обновлять их одновременно.

 

Dim ptr As ThreadedCell

Dim nxt As ThreadedCell

Dim new_cell As New ThreadedCell

Dim new_name As String

Dim next_name As String

 

' Записать значения новой ячейки.

With new_cell

   .LastName = LastName

   .FirstName = FirstName

   .SSN = SSN

   •Sex = Sex

   .JobClass = JobClass

End With

 

' Определить место новой ячейки в потоке NextThread.

new_name = LastName & ", " & FirstName

Set ptr = m_TopSentinel

Do

   Set nxt = ptr.NextName

   next_name = nxt.LastName & ", " & nxt.FirstName

   If next_name >= new_name Then Exit Do

   Set ptr = nxt

Loop

 

' Вставить новую ячейку в потоки NextName и prevName.

Set new_cell.NextName = nxt

Set new_cell.PrevName = ptr

Set ptr.NextName = new_cell

Set nxt.PrevName = new_cell

 

Чтобы такой подход работал, программа должна гарантировать, что значения новой ячейки лежат между значениями меток. Например, если пользователь введет в качестве фамилии "~~", цикл выйдет за метку конца списка, т.к. "~~" идет после "~". Затем программа аварийно завершит работу при попытке доступа к значению nxt.LastName, если nxt было установлено равным Nothing.

 

========42

 



2019-07-03 416 Обсуждений (0)
Циклические связные списки 0.00 из 5.00 0 оценок









Обсуждение в статье: Циклические связные списки

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Как построить свою речь (словесное оформление): При подготовке публичного выступления перед оратором возникает вопрос, как лучше словесно оформить свою...
Почему люди поддаются рекламе?: Только не надо искать ответы в качестве или количестве рекламы...
Организация как механизм и форма жизни коллектива: Организация не сможет достичь поставленных целей без соответствующей внутренней...
Генезис конфликтологии как науки в древней Греции: Для уяснения предыстории конфликтологии существенное значение имеет обращение к античной...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (416)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.007 сек.)