Реалізація пов'язаних списків на базі масивів h2>
Чуриков Костянтин, Донбаський державний
технічний університет p>
Визначення лінійних списків h2>
Списком називається впорядкована множина, що складається
із змінного числа елементів, до яких застосовні операції включення,
винятку. Список, що відображає відносини сусідства між елементами, називається
лінійним. p>
З реалізаціями лінійних списків в імперативних мовах
програмування можуть виконуватися такі операції: p>
отримання доступу до деякого елементу списку для
перевірки та/або зміни вмісту його полів; p>
вставка нового елемента відразу перед або після
довільного елемента; p>
видалення довільного елемента; p>
об'єднання в одному списку двох (або більше) лінійних
списків; p>
розбиття лінійного списку на два (або більше) списку; p>
створення копії лінійного списку; p>
визначення кількості елементів в списку; p>
сортування елементів списку; p>
пошук елементів із заданим значенням. p>
В одній програмі вкрай рідко виникає необхідність
використовувати всі дев'ять типів операцій. При цьому досить важко створити
єдину реалізацію лінійних списків, при якій ефективно виконувалися б усі
ці операції. Тому лінійні списки можуть бути реалізовані по-різному в
залежно від класу операцій, які найбільш часто повинні з ними
виконуватися в даній програмі, або найбільш критичних до часу виконання. p>
Внутрішнє подання лінійних списків h2>
Основні способи зберігання лінійних списків в пам'яті
комп'ютера можна розділити на способи послідовного і пов'язаного зберігання.
При послідовному зберіганні елементи списку розташовуються в пам'яті в
послідовних клітинках, при цьому один елемент слід відразу ж за іншим.
Пов'язане зберігання являє собою більш гнучку схему, при якій кожен
елемент списку містить зв'язок з наступним елементом, а їх взаємне
розташування в пам'яті може бути довільним. Кожен спосіб має свої
переваги і недоліки. При виборі способу зберігання в конкретній програмі
слід враховувати, які операції і з якою інтенсивністю будуть виконуватися
над лінійними списками, вартість їхнього виконання і обсяг необхідної пам'яті для
зберігання списку. p>
Послідовне зберігання лінійних списків розглянуто
в безлічі джерел і не становить предмету даної статті [1]. Також
широко розглянуто пов'язане зберігання лінійних списків, але в переважній
більшості випадків така реалізація заснована на посилальної реалізації [2]. У той
Водночас існує витончена, але мало відома широкому колу молодих
програмістів реалізація зв'язкового зберігання лінійних списків на базі масивів.
Досить докладний, хоч і вельми специфічне опис такої реалізації
дано в [3]. Цьому способу і присвячена ця стаття. P>
ПРИМІТКА p>
Така реалізація зустрічається не так вже
рідко. Просто вона часто ховається за різними назвами - індексні масиви,
масиви відповідності і т.п. Незважаючи на різницю в назвах, використовується
один і той же алгоритм. - Прим.ред. P>
Реалізація пов'язаного списку на базі масивів h2>
Розглянемо цей спосіб на прикладі реалізації лінійного
односвязного списку. Елементи такого списку лінійно впорядковані. Крім
елементів у списку є навігатор, який може перебувати в наступних
позиціях: p>
на початку списку (тобто перед першим елементом
списку); p>
в кінці списку (тобто за останнім елементом
списку); p>
між елементами списку. p>
Навігатор можна пересувати тільки від початку до кінця
списку на один елемент за раз. Крім того, додавати елементи списку можна
тільки в позиції, на яку посилається навігатор. А змінювати/видаляти/читати
можна тільки елементи, які знаходяться в позиції, що передує тій, на
яку посилається навігатор, тобто попереду по ходу його руху. p>
Основна ідея цієї реалізації полягає у відділенні
інформації про вміст елементів списку від інформації про порядок їх проходження.
Графічно ця ідея представлена на малюнку 1. P>
p>
Малюнок 1. p>
Далі посилання будемо зображати стрілками, як показано
на малюнку 2. p>
p>
Малюнок 2. p>
Природно, що для роботи зі списком одних тільки
посилань між елементами недостатньо - треба знати, наприклад, де розташований
перший елемент списку, і відрізняти останній елемент від всіх інших (адже за
ним вже ніяких елементів немає). Для цих цілей можна було б ввести два
змінні, які містили б індекси початку і кінця списку. Ми, однак,
цього робити не будемо, а скористаємося прийомом «згортання в кільце» --
«Звернемо» список, ввівши спеціальний уявний елемент NULL, який завжди
будемо мати у своєму розпорядженні в елементі з індексом NullElem (який має значення 0) масиву,
використовуваного для зберігання посилань на елементи списку (назвемо його Refs). На
малюнку 3 показано графічне представлення цієї моделі. p>
Зауважте, що тепер посилання між елементами списку
утворюють кільце, що починається з NullElem. Таким чином, посилання з індексом
NullElem показує на перший елемент списку, а якщо чергова посилання дорівнює
NullElem, то відповідний елемент є у списку останнім. P>
p>
Малюнок 3. p>
пустому списку відповідає ситуація, коли у кільці
ніяких елементів, крім NullElem, немає, тобто, осередок масиву посилань з індексом
NullElem посилається сама на себе. P>
Навігатор розташовується між елементами, тому
реалізуємо його у вигляді двох змінних BEFORE і AFTER, де AFTER містить індекс
елемента за пройденим, а BEFORE - індекс елемента ще не пройденого елементу.
Положення навігатора на початку списку, таким чином, відповідає випадок,
коли AFTER дорівнює NullElem, а положенню в кінці - коли це значення міститься
в BEFORE. p>
Тепер пов'язаний список зімітував повністю.
Незрозумілим залишається тільки наступні питання: p>
Як визначати наявність або відсутність вільного місця
в масиві елементів (назвемо його Elems)? p>
Як знаходити вільний елемент в масиві елементів при
імітації додавання елемента до списку? p>
Щоб не гадати, які елементи Elems вільні, а
які зайняті, приймемо рішення, зберігати крім списку зайнятих елементів так само
список вільних. Елементи цього списку також будуть з'єднані в кільце,
що починається з службового елемента зберігається в елементі з індексом
NullFreeSpace (рівним одиниці) масиву посилань (Refs). Графічне представлення
що вийшла моделі зображено на малюнку 4. p>
Таким чином, щоб визначити, чи є в списку
вільне місце, треба подивитися значення елемента Refs (NullFreeSpace). Якщо
це значення дорівнює NullFreeSpace (тобто показує на себе), то вільного місця
немає. В іншому випадку це значення вказує на перший вільний елемент
масиву елементів. При додаванні елемента до списку необхідно виключити індекс
цього елемента зі списку вільних і включити його до основного списку. При
видаленні елемента необхідно зробити зворотну операцію. p>
На малюнку 4 чорним кольором позначений Автоматичне виведення
пов'язаний список, а червоним - список вільних елементів. p>
p>
Малюнок 4. p>
У програмній реалізації списку присвоювання посиланню з
індексом virtualIndex значення realIndex виділимо в окрему підпрограму.
Крім того, в окремі підпрограми виділимо всі дії, пов'язані з роботою
з вільним місцем. p>
ПРИМІТКА p>
У прикладі описується створення списку
для зберігання дійсних чисел. Щоб створити список елементів іншого типу
потрібно відповідним чином змінити оголошення типу елементів масиву
Elems (), типу параметра element у процедурі AddItem () і типу що повертається
значення у функції ReadItem (). Мається на увазі, що наведений код оформлений у
вигляді окремого модуля або класу, що краще. Крім того, хоча в
статті розглянуто обмежений список, у реалізації передбачена можливість
динамічного збільшення довжини списку в разі потреби. p>
З урахуванням усіх зауважень вищесказаних реалізація
односвязного лінійного списку на Visual Basic 6.0 може мати вигляд, наведений
нижче. p>
Клас
MyLinkedList: p>
'номер комірки Refs,
що вказує на перший елемент списку p>
Const NullElem = 0 p>
'номер комірки Refs,
що вказує на перший елемент з "вільного місця" p>
Const NullFreeSpace =
1 p>
Dim Elems () As Double 'Масив для зберігання елементів списку p>
Dim Refs () As Integer 'Масив для зберігання посилань p>
Dim AFTER As Integer 'Покажчик попереднього
елемента p>
Dim BEFORE As Integer 'Покажчик наступного елемента p>
Dim Count As Integer 'Кількість елементів у списку p>
'Створення списку для
зберігання capacity елементів p>
'1-а осередок Refs вказує
на перший елемент списку p>
'2-я осередок Refs вказує
на 1-й елемент Х з "вільного місця" p>
'capacity - максимально
допустиму кількість елементів у списку. p>
Sub CreateLinkedList (capacity As Integer) p>
ReDim Elems (capacity + 1) p>
ReDim Refs (capacity + 1) p>
ClearList p>
End Sub p>
'Очищення списку p>
Sub ClearList () p>
Refs (NullElem) = NullElem 'кінець списку вказує сам на себе p>
Dim i As Integer p>
'Оскільки список порожній, то всі комірки
масиву позначаються p>
'як "вільне місце". p>
For i = NullFreeSpace To
UBound (Refs) - 1 p>
Refs (i) = i + 1 p>
Next i p>
Refs (UBound (Refs)) =
NullFreeSpace 'закільцьовують "вільне місце". P>
AFTER = 0 p>
BEFORE = 0 p>
Count = 0 p>
End Sub p>
'Присвоєння віртуального індексом virtualIndex значення realIndex p>
Private Sub Link (virtualIndex As Integer, realIndex As Integer) p>
Refs (virtualIndex) = realIndex p>
End Sub p>
'Виділення місця для нових
елементів p>
Private Function GetSpace () As Integer p>
Dim i As Integer, OldLength As
Integer p>
If IsListFull Then p>
OldLength = UBound (Elems) p>
ReDim Preserve
Elems (OldLength * 2) 'динамічне збільшення довжини p>
ReDim Preserve
Refs (OldLength * 2) 'списку, якщо він вже повністю заповнений p>
For i = OldLength + 1 To OldLength * 2 - 1
'додаються елементи позначаються p>
Refs (i) = i + 1 'як вільне місце p>
Next i p>
Refs (NullFreeSpace) =
OldLength + 1 p>
Refs (OldLength * 2) =
NullFreeSpace p>
End If p>
i = Refs (NullFreeSpace) p>
Link NullFreeSpace, Refs (i) p>
GetSpace = i p>
End Function p>
'звільнення місця при
видалення елемента зі списку p>
Private Sub PutSpace (i As Integer) p>
Link i, Refs (NullFreeSpace) p>
Link NullFreeSpace, i p>
End Sub p>
'додати element до списку p>
Sub AddItem (element As Double) p>
Dim i As Integer p>
i = GetSpace () 'отримуємо вільне місце p>
Link AFTER, i '
встановлюємо його в p>
Link i, BEFORE 'потрібному місці списку p>
BEFORE = i 'встановлюємо покажчик p>
Elems (i) = element 'додаємо елемент
element до списку p>
MoveNext p>
Count = Count + 1 'збільшуємо лічильник кількості елементів p>
End Sub p>
'видалити елемент зі списку p>
Sub RemoveItem () p>
Dim i As Integer p>
If Count <> 0 Then p>
i = BEFORE p>
BEFORE = Refs (i) p>
Link AFTER, BEFORE p>
PutSpace i p>
Count = Count - 1 p>
Else p>
Err.Raise vbObjectError +
513,, "List is empty" p>
End If p>
End Sub p>
'прочитати значення
елемента зі списку p>
'якщо параметр Index
відсутній або p>
'не є порядковим
номером еелемента списку p>
'то повертається значення
елемента перед покажчиком p>
'у противному випадку
повертається значення елемента p>
'з порядковим номером Index p>
Function ReadItem (Optional Index As Integer = -1) As Double p>
If Not (Index> = 0 And
Index
If IsEndOfList Then p>
ReadItem = Elems (AFTER) p>
Else p>
ReadItem = Elems (BEFORE) p>
End If p>
Else p>
Dim i As Integer, k As
Integer p>
k = NullElem p>
For i = 0 To Index p>
k = Refs (k) p>
Next i p>
ReadItem = Elems (k) p>
End If p>
End Function p>
'повернути кількість
елементів у списку p>
Function GetCount () As Integer p>
GetCount = Count p>
End Function p>
'визначити, чи є
вільне місце в списку p>
Private Function IsListFull () As Boolean p>
IsListFull = False p>
If Refs (NullFreeSpace) =
NullFreeSpace Then IsListFull = True p>
End Function p>
'пересунути вказівник на
один елемент списку p>
Sub MoveNext () p>
AFTER = BEFORE p>
BEFORE = Refs (BEFORE) p>
End Sub p>
'пересунути покажчик в
початок списку p>
Sub MoveFront () p>
AFTER = 0 p>
BEFORE = Refs (NullElem) p>
End Sub p>
'визначити, чи знаходиться
покажчик в кінці списку p>
Function IsEndOfList () As Boolean p>
IsEndOfList = False p>
If Refs (AFTER) = NullElem Then
IsEndOfList = True p>
End Function p>
Приклад
використання даної реалізації списку: p>
Sub Test () p>
Dim i As Integer p>
Dim list1 As MyLinkedList p>
Set list1 = New MyLinkedList p>
list1.CreateLinkedList 3 p>
'висновок кількості елементів p>
Debug.Print "count of elems =",
list1.GetCount - 1 p>
'заповнення списку випадковими числами p>
Randomize p>
For i = 1 To 5 p>
list1.AddItem Rnd () * 100 p>
Next i p>
'висновок вмісту списку p>
list1.MoveFront p>
Do Until list1.IsEndOfList p>
Debug.Print
list1.ReadItem p>
list1.MoveNext p>
Loop p>
'висновок кількості елементів p>
Debug.Print "count of
elems = ", list1.GetCount - 1 p>
'видалення першого елемента p>
list1.MoveFront p>
list1.RemoveItem p>
'висновок вмісту списку p>
list1.MoveFront p>
Do Until list1.IsEndOfList p>
Debug.Print
list1.ReadItem p>
list1.MoveNext p>
Loop p>
'висновок вмісту 2-го елемента p>
Debug.Print list1.ReadItem (2) p>
End Sub p>
Список літератури h2>
Д. Батіг. Мистецтво програмування. (3-е издание)
Т.1. p>
А.Г. Кушніренко, Г.В. Лебедєв. Програмування для
математиків. М: Наука. 1988, стор 202-210. p>
Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
p>