Рефераты

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

операций определялось в терминах выполнения чего-либо после определенной

ячейки в списке. Если задана определенная ячейка, легко добавить или

удалить ячейку после нее или перечислить идущие за ней ячейки. Удалить саму

ячейку, вставить новую ячейку перед ней или перечислить идущие перед ней

ячейки уже не так легко. Тем не менее, небольшое изменение позволит

облегчить и эти операции.

Добавим новое поле указателя к каждой ячейке, которое указывает на

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

двусвязный список (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

Другие связные структуры

Используя указатели, можно построить множество других полезных

разновидностей связных структур, таких как деревья, нерегулярные массивы,

разреженные массивы, графы и сети. Ячейка может содержать любое число

указателей на другие ячейки. Например, для создания двоичного дерева можно

использовать ячейку, содержащую два указателя, один на левого потомка, и

второй – на правого. Класс BinaryCell может состоять из следующих

определений:

Public LeftChild As BinaryCell

Public RightChild As BinaryCell

На рис. 2.11 показано дерево, построенное из ячеек такого типа. В 6 главе

деревья обсуждаются более подробно.

Ячейка может даже содержать коллекцию или связный список с указателями на

другие ячейки. Это позволяет программе связать ячейку с любым числом других

объектов. На рис. 2.12 приведены примеры других связных структур данных. Вы

также встретите похожие структуры далее, в особенности в 12 главе.

Псевдоуказатели

При помощи ссылок в Visual Basic можно легко создавать связные структуры,

такие как списки, деревья и сети, но ссылки требуют дополнительных

ресурсов. Счетчики ссылок и проблемы с распределением памяти замедляют

работу структур данных, построенных с использованием ссылок.

Другой стратегией, которая часто обеспечивает лучшую производительность,

является применение псевдоуказателей (fake pointers). При этом программа

создает массив структур данных. Вместо использования ссылок для связывания

структур, программа использует индексы массива. Нахождение элемента в

массиве осуществляется в Visual Basic быстрее, чем выборка его по ссылке на

объект. Это дает лучшую производительность при применении псевдоуказателей

по сравнению с соответствующими методами ссылок на объекты.

С другой стороны, применение псевдоуказателей не столь интуитивно, как

применение ссылок. Это может усложнить разработку и отладку сложных

алгоритмов, таких как алгоритмы сетей или сбалансированных деревьев.

@Рис. 2.11. Двоичное дерево

========43

@Рис. 2.12. Связные структуры

Программа FakeList управляет связным списком, используя псевдоуказатели.

Она создает массив простых структур данных для хранения ячеек списка.

Программа аналогична программе LnkList1, но использует псевдоуказатели.

Следующий код демонстрирует, как программа FakeList создает массив

клеточных структур:

' Структура данных ячейки.

Type FakeCell

Value As String

NextCell As Integer

End Type

' Массив ячеек связного списка.

Global Cells(0 To 100) As FakeCell

' Сигнальная метка списка.

Global Sentinel As Integer

Поскольку псевдоуказатели — это не ссылки, а просто целые числа, программа

не может использовать значение Nothing для маркировки конца списка.

Программа FakeList использует постоянную END_OF_LIST, значение которой

равно -32.767 для обозначения пустого указателя.

Для облегчения обнаружения неиспользуемых ячеек, программа FakeList также

использует специальный «мусорный» список, содержащий неиспользуемые ячейки.

Следующий код демонстрирует инициализацию пустого связного списка. В нем

сигнальная метка NextCell принимает значение END_OF_LIST. Затем она

помещает неиспользуемые ячейки в «мусорный» список.

========44

' Связный список неиспользуемых ячеек.

Global TopGarbage As Integer

Public Sub InitializeList()

Dim i As Integer

Sentinel = 0

Cells(Sentinel).NextCell = END_OF_LIST

' Поместить все остальные ячейки в «мусорный» список.

For i = 1 To UBound (Cells) - 1

Cells(i).NextCell = i + 1

Next i

Cells(UBound(Cells)).NextCell = END_OF_LIST

TopGarbage = 1

End Sub

При добавлении элемента к связному списку, программа использует первую

доступную ячейку из «мусорного» списка, инициализирует поле ячейки Value и

вставляет ячейку в список. Следующий код показывает, как программа

добавляет элемент после выбранного:

Private Sub CmdAddAfter_Click()

Dim ptr As Integer

Dim position As Integer

Dim new_cell As Integer

' Найти место вставки.

ptr = Sentinel

position = Selectedlndex

Do While position > 0

position = position - 1

ptr = Cells(ptr).NextCell

Loop

' Выбрать новую ячейку из «мусорного» списка.

new_cell = TopGarbage

TopGarbage = Cells(TopGarbage).NextCell

' Вставить элемент.

Cells (new_cell).Value = NewItem.Text

Cells(new_cell).NextCell = Cells(ptr).NextCell

Cells(ptr).NextCell = new_cell

NumItems = NumItems + 1

DisplayList

SelectItem SelectedIndex + 1 ' Выбрать новый элемент.

NewItem.Text = ""

NewItem.SetFocus

CmdClearList.Enabled = True

End Sub

После удаления ячейки из списка, программа FakeList помещает удаленную

ячейку в «мусорный» список, чтобы ее затем можно было легко использовать:

Private Sub CmdRemoveAfter_Click()

Dim ptr As Integer

Dim target As Integer

Dim position As Integer

If SelectedIndex < 0 Then Exit Sub

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

ptr = Sentinel

position = SelectedIndex

Do While position > 0

position = position - 1

ptr = Cells(ptr).NextCell

Loop

' Пропустить следующий элемент.

target = Cells(ptr).NextCell

Cells(ptr).NextCell = Cells(target).NextCell

NumItems = NumItems - 1

' Добавить удаленную ячейку в «мусорный» список.

Cells(target).NextCell = TopGarbage

TopGarbage = target

SelectItem Selectedlndex ' Снова выбрать элемент.

DisplayList

CmdClearList.Enabled = NumItems > 0

NewItem.SetFocus

End Sub

Применение псевдоуказателей обычно обеспечивает лучшую производительность,

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

используя ссылки на объекты. Затем, если вы обнаружите, что программа

значительную часть времени тратит на манипулирование ссылками, вы можете,

если необходимо, преобразовать ее с использованием псевдоуказателей.

=======45-46

Резюме

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

такие как связные списки, циклические связные списки и двусвязные списки.

Эти списки позволяют легко добавлять и удалять элементы из любого места

списка.

Добавляя дополнительные ссылки к классу ячеек, можно превратить двусвязный

список в многопоточный. Развивая и дальше эти идеи, можно создавать

экзотические структуры данных, включая разреженные массивы, деревья, хэш-

таблицы и сети. Они подробно описываются в следующих главах.

========47

Глава 3. Стеки и очереди

В этой главе продолжается обсуждение списков, начатое во 2 главе, и

описываются две особых разновидности списков: стеки и очереди. Стек — это

список, в котором добавление и удаление элементов осуществляется с одного и

того же конца списка. Очередь — это список, в котором элементы добавляются

в один конец списка, а удаляются с противоположного конца. Многие

алгоритмы, включая некоторые из представленных в следующих главах,

используют стеки и очереди.

Стеки

Стек (stack) — это упорядоченный список, в котором добавление и удаление

элементов всегда происходит на одном конце списка. Можно представить стек

как стопку предметов на полу. Вы можете добавлять элементы на вершину и

удалять их оттуда, но не можете добавлять или удалять элементы из середины

стопки.

Стеки часто называют списками типа первый вошел — последний вышел (Last-In-

First-Out list). По историческим причинам, добавление элемента в стек

называется проталкиванием (pushing) элемента в стек, а удаление элемента из

стека — выталкиванием (popping) элемента из стека.

Первая реализация простого списка на основе массива, описанная в начале 2

главы, является стеком. Для отслеживания вершины списка используется

счетчик. Затем этот счетчик используется для вставки или удаления элемента

из вершины списка. Небольшое изменение — это новая процедура Pop, которая

удаляет элемент из списка, одновременно возвращая его значение. При этом

другие процедуры могут извлекать элемент и удалять его из списка за один

шаг. Кроме этого изменения, следующий код совпадает с кодом, приведенным во

2 главе.

Dim Stack() As Variant

Dim StackSize As Variant

Sub Push(value As Variant)

StackSize = StackSize + 1

ReDim Preserve Stack(1 To StackSize)

Stack(StackSize) = value

End Sub

Sub Pop(value As Variant)

value = Stack(StackSize)

StackSize = StackSize - 1

ReDim Preserve Stack(1 To StackSize)

End Sub

=====49

Все предыдущие рассуждения о списках также относятся к этому виду

реализации стеков. В частности, можно сэкономить время, если не изменять

размер при каждом добавлении или выталкивании элемента. Программа SimList

на описанная во 2 главе, демонстрирует этот вид простой реализации списков.

Программы часто используют стеки для хранения последовательности элементов,

с которыми программа будет работать до тех пор, пока стек не опустеет.

Действия с одним из элементов может приводить к тому, что другие будут

проталкиваться в стек, но, в конце концов, они все будут удалены из стека.

В качестве простого примера можно привести алгоритм обращения порядка

элементов массива. При этом все элементы последовательно проталкиваются в

стек. Затем все элементы выталкиваются из стека в обратном порядке и

записываются обратно в массив.

Dim List() As Variant

Dim NumItems As Integer

' Инициализация массива.

:

' Протолкнуть элементы в стек.

For I = 1 To NumItems

Push List(I)

Next I

' Вытолкнуть элементы из стека обратно в массив.

For I = 1 To NumItems

Pop List(I)

Next I

В этом примере, длина стека может многократно изменяться до того, как, в

конце концов, он опустеет. Если известно заранее, насколько большим должен

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

размера стека по мере того, как он растет и уменьшается, можно отвести под

него память в начале работы и уничтожить его после ее завершения.

Следующий код позволяет создать стек, если заранее известен его

максимальный размер. Процедура Pop не изменяет размер массива. Когда

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

EmptyStack для освобождения занятой под стек памяти.

======50

Const WANT_FREE_PERCENT = .1 ' 10% свободного пространства.

Const MIN_FREE = 10 ' Минимальный размер.

Global Stack() As Integer ' Стековый массив.

Global StackSize As Integer ' Размер стекового массива.

Global Lastltem As Integer ' Индекс последнего элемента.

Sub PreallocateStack(entries As Integer)

StackSize = entries

ReDim Stack(1 To StackSize)

End Sub

Sub EmptyStack()

StackSize = 0

LastItem = 0

Erase Stack ' Освободить память, занятую массивом.

End Sub

Sub Push(value As Integer)

LastItem = LastItem + 1

If LastItem > StackSize Then ResizeStack

Stack(LastItem) = value

End Sub

Sub Pop(value As Integer)

value = Stack(LastItem)

LastItem = LastItem - 1

End Sub

Sub ResizeStack()

Dim want_free As Integer

want_free = WANT_FREE_PERCENT * LastItem

If want_free < MIN_FREE Then want_free = MIN_FREE

StackSize = LastItem + want_free

ReDim Preserve Stack(1 To StackSize)

End Sub

Этот вид реализации стеков достаточно эффективен в Visual Basic. Стек не

расходует понапрасну память, и не слишком часто изменяет свой размер,

особенно если сразу известно, насколько большим он должен быть.

=======51

Множественные стеки

В одном массиве можно создать два стека, поместив один в начале массива, а

другой — в конце. Для двух стеков используются отдельные счетчики длины

стека Top, и стеки растут навстречу друг другу, как показано на рис. 3.1.

Этот метод позволяет двум стекам расти, занимая одну и ту же область

памяти, до тех пор, пока они не столкнутся, когда массив заполнится.

К сожалению, менять размер этих стеков непросто. При увеличении массива

необходимо сдвигать все элементы в верхнем стеке, чтобы выделять память под

новые элементы в середине. При уменьшении массива, необходимо вначале

сдвинуть элементы верхнего стека, перед тем, как менять размер массива.

Этот метод также сложно масштабировать для оперирования более чем двумя

стеками.

Связные списки предоставляют более гибкий метод построения множественных

стеков. Для проталкивания элемента в стек, он помещается в начало связного

списка. Для выталкивания элемента из стека, удаляется первый элемент из

связного списка. Так как элементы добавляются и удаляются только в начале

списка, для реализации стеков такого типа не требуется применение

сигнальных меток или двусвязных списков.

Основной недостаток применения стеков на основе связных списков состоит в

том, что они требуют дополнительной памяти для хранения указателей

NextCell. Для стека на основе массива, содержащего N элементов, требуется

всего 2*N байт памяти (по 2 байта на целое число). Тот же стек,

реализованный на основе связного списка, потребует дополнительно 4*N байт

памяти для указателей NextCell, увеличивая размер необходимой памяти втрое.

Программа Stack использует несколько стеков, реализованных в виде связных

списков. Используя программу, можно вставлять и выталкивать элементы из

каждого из этих списков. Программа Stack2 аналогична этой программе, но она

использует класс LinkedListStack для работы со стеками.

Очереди

Упорядоченный список, в котором элементы добавляются к одному концу списка,

а удаляются с другой стороны, называется очередью (queue). Группа людей,

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

подходят сзади. Когда покупатель доходит до начала очереди, кассир его

обслуживает. Из-за их природы, очереди иногда называют списками типа первый

вошел — первый вышел (First-In-First-Out list).

@Рис. 3.1. Два стека в одном массиве

=======52

Можно реализовать очереди в Visual Basic, используя методы типа

использованных для организации простых стеков. Создадим массив, и при

помощи счетчиков будем определять положение начала и конца очереди.

Значение переменной QueueFront дает индекс элемента в начале очереди.

Переменная QueueBack определяет, куда должен быть добавлен очередной

элемент очереди. По мере того как новые элементы добавляются в очередь и

покидают ее, размер массива, содержащего очередь, изменяется так, что он

растет на одном конце и уменьшается на другом.

Global Queue() As String ' Массив очереди.

Global QueuePront As Integer ' Начало очереди.

Global QueueBack As Integer ' Конец очереди.

Sub EnterQueue(value As String)

ReDim Preserve Queue(QueueFront To QueueBack)

Queue(QueueBack) = value

QueueBack = QueueBack + 1

End Sub

Sub LeaveQueue(value As String)

value = Queue(QueueFront)

QueueFront = QueueFront + 1

ReDim Preserve Queue (QueueFront To QueueBack - 1)

End Sub

К сожалению, Visual Basic не позволяет использовать ключевое слово Preserve

в операторе ReDim, если изменяется нижняя граница массива. Даже если бы

Visual Basic позволял выполнение такой операции, очередь при этом

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

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

большого количества элементов через очередь, ее границы могли бы в конечном

итоге стать слишком велики.

Поэтому, когда требуется увеличить размер массива, вначале необходимо

переместить данные в начало массива. При этом может образоваться

достаточное количество свободных ячеек в конце массива, так что увеличение

размера массива может уже не понадобиться. В противном случае, можно

воспользоваться оператором ReDim для увеличения или уменьшения размера

массива.

Как и в случае со списками, можно повысить производительность, добавляя

сразу несколько элементов при увеличении размера массива. Также можно

сэкономить время, уменьшая размер массива, только когда он содержит слишком

много неиспользуемых ячеек.

В случае простого списка или стека, элементы добавляются и удаляются на

одном его конце. Если размер списка остается почти постоянным, его не

придется изменять слишком часто. С другой стороны, так как элементы

добавляются на одном конце очереди, а удаляются с другого конца, может

потребоваться время от времени переупорядочивать очередь, даже если ее

размер остается неизменным.

=====53

Const WANT_FREE_PERCENT = .1 ' 10% свободного пространства.

Const MIN_FREE = 10 ' Минимум свободных ячеек.

Global Queue() As String ' Массив очереди.

Global QueueMax As Integer ' Наибольший индекс массива.

Global QueueFront As Integer ' Начало очереди.

Global QueueBack As Integer ' Конец очереди.

Global ResizeWhen As Integer ' Когда увеличить размер массива.

' При инициализации программа должна установить QueueMax = -1

' показывая, что под массив еще не выделена память.

Sub EnterQueue(value As String)

If QueueBack > QueueMax Then ResizeQueue

Queue(QueueBack) = value

QueueBack = QueueBack + 1

End Sub

Sub LeaveQueue(value As String)

value = Queue(QueueFront)

QueueFront = QueueFront + 1

If QueueFront > ResizeWhen Then ResizeOueue

End Sub

Sub ResizeQueue()

Dim want_free As Integer

Dim i As Integer

' Переместить записи в начало массива.

For i = QueueFront To QueueBack - 1

Queue(i - QueueFront) = Queue(i)

Next i

QueueBack = QueueBack - QueuePront

QueueFront = 0

' Изменить размер массива.

want_free = WANT_FREE_PERCENT * (QueueBack - QueueFront)

If want_free < MIN_FREE Then want_free = MIN_FREE

Max = QueueBack + want_free - 1

ReDim Preserve Queue(0 To Max)

' Если QueueFront > ResizeWhen, изменить размер массива.

ResizeWhen = want_free

End Sub

При работе с программой, заметьте, что когда вы добавляете и удаляете

элементы, требуется изменение размера очереди, даже если размер очереди

почти не меняется. Фактически, даже при неоднократном добавлении и удалении

одного элемента размер очереди будет изменяться.

Имейте в виду, что при каждом изменении размера очереди, вначале все

используемые элементы перемещаются в начало массива. При этом на изменение

размера очередей на основе массива уходит больше времени, чем на изменение

размера описанных выше связных списков и стеков.

=======54

Программа ArrayQ2 аналогична программе ArrayQ, но она использует для

управления очередью класс ArrayQueue.

Циклические очереди

Очереди, описанные в предыдущем разделе, требуется переупорядочивать время

от времени, даже если размер очереди почти не меняется. Даже при

неоднократном добавлении и удалении одного элемента будет необходимо

переупорядочивать очередь.

Если заранее известно, насколько большой может быть очередь, этого можно

избежать, создав циклическую очередь (circular queue). Идея заключается в

том, чтобы рассматривать массив очереди как будто он заворачивается,

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

На рис. 3.2 изображена циклическая очередь.

Программа может хранить в переменной QueueFront индекс элемента, который

дольше всего находится в очереди. Переменная QueueBack может содержать

конец очереди, в который добавляется новый элемент.

В отличие от предыдущей реализации, при обновлении значений переменных

QueueFront и QueueBack, необходимо использовать оператор Mod для того,

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

добавляет элемент к очереди:

Queue(QueueBack) = value

QueueBack = (QueueBack + 1) Mod QueueSize

На рис. 3.3 показан процесс добавления нового элемента к циклической

очереди, которая может содержать четыре записи. Элемент C добавляется в

конец очереди. Затем конец очереди сдвигается, указывая на следующую запись

в массиве.

Таким же образом, когда программа удаляет элемент из очереди, необходимо

обновлять указатель на начало очереди при помощи следующего кода:

value = Queue(QueueFront)

QueueFront = (QueueFront + 1) Mod QueueSize

@Рис. 3.2. Циклическая очередь

=======55

@Рис. 3.3. Добавление элемента к циклической очереди

На рис. 3.4 показан процесс удаления элемента из циклической очереди.

Первый элемент, в данном случае элемент A, удаляется из начала очереди, и

указатель на начало очереди обновляется, указывая на следующий элемент

массива.

Для циклических очередей иногда бывает сложно отличить пустую очередь от

полной. В обоих случаях значения переменных QueueBottom и QueueTop будут

равны. На рис. 3.5 показаны две циклические очереди, пустая и полная.

Простой вариант решения этой проблемы — сохранять число элементов в очереди

в отдельной переменной NumInQueue. При помощи этого счетчика можно узнать,

остались ли в очереди еще элементы, и осталось ли в очереди место для новых

элементов.

@Рис. 3.4. Удаление элемента из циклической очереди

@Рис. 3.5 Полная и пустая циклическая очереди

=========56

Следующий код использует все эти методы для управления циклической

очередью:

Queue() As String ' Массив очереди.

QueueSize As Integer ' Наибольший индекс в очереди.

QueueFront As Integer ' Начало очереди.

QueueBack As Integer ' Конец очереди.

NumInQueue As Integer ' Число элементов в очереди.

Sub NewCircularQueue(num_items As Integer)

QueueSize = num_items

ReDim Queue(0 To QueueSize - 1)

End Sub

Sub EnterQueue(value As String)

' Если очередь заполнена, выйти из процедуры.

' В настоящем приложении потребуется более сложный код.

If NumInQueue >= QueueSize Then Exit Sub

Queue(QueueBack) = value

QueueBack = (QueueBack + 1) Mod QueueSize

NumInQueue = NumInQueue + 1

End Sub

Sub LeaveQueue (value As String)

' Если очередь пуста, выйти из процедуры.

' В настоящем приложении потребуется более сложный код.

If NumInQueue = QueueSize Then ResizeQueue

Queue(QueueBack) = value

QueueBack = (QueueBack + 1) Mod QueueSize

NumInQueue = NumInQueue + 1

End Sub

Private Sub LeaveQueue(value As String)

If NumInQueue new_priority

cell = nxt

nxt = cell.NextCell

Loop

' Вставить элемент после ячейки в списке.

:

Для удаления из списка элемента с наивысшим приоритетом, просто удаляется

элемент после сигнальной метки начала. Так как список отсортирован в

порядке приоритетов, первый элемент всегда имеет наивысший приоритет.

Добавление нового элемента в эту очередь занимает в среднем N/2 шагов.

Иногда новый элемент будет оказываться в начале списка, иногда ближе к

концу, но в среднем он будет оказываться где-то в середине. Простая очередь

на основе списка требовала O(1) шагов для добавления нового элемента и O(N)

шагов для удаления элементов с наивысшим приоритетом из очереди. Версия на

основе упорядоченного связного списка требует O(N) шагов для добавления

элемента и O(1) шагов для удаления верхнего элемента. Обеим версиям требует

O(N) шагов для одной из этих операций, но в случае упорядоченного связного

списка в среднем требуется только (N/2) шагов.

Программа PriList использует упорядоченный связный список для работы с

приоритетной очередью. Вы можете задать приоритет и значение элемента

данных и нажать кнопку Enter для добавления его в приоритетную очередь.

Нажмите на кнопку Leave для удаления из очереди элемента с наивысшим

приоритетом.

Программа PriList2 аналогична программе PriList, но она использует для

управления очередью класс LinkedPriorityQueue.

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15


© 2010 БИБЛИОТЕКА РЕФЕРАТЫ