Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная_3.docx
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
39.16 Кб
Скачать

Операція Union

Нам треба реалізувати операцію Union, що об’єднує два списки в один за час O(1). Спочатку виберемо тип списків, якими будемо користуватися.

Так як списки, що будуть об’єднуватись, мають однаковий тип ми можемо ніби «вставити» один список в інший. При цьому всі зв’язки між елементами вставленого списку зберігаються, тож алгоритми роботи зі списками будуть працювати як і раніше.

Вся задача при цьому зводиться до зміни адрес першого та останнього елементів вставленого списку та двох сусідніх елементів списку, в який іде вставка. Найбільше переваг тут нам дають списки, звернуті в кільце. Більш того, у кільцевому списку ми можемо взяти за перший та останній елемент вставленого списку будь-які два сусідніх його елементи.

Запишемо нашу процедуру для однозв’язних і двозв’язних закільцьованих списків.

Union(L1, L2)

t ← next[head[L1]]

next[head[L1]] ← next[head[L2]]

next[head[L2]] ← t

Union(L1, L2)

t ← prev[head[L2]]

prev[head[L2]] ← prev[head[L1]]

next[prev[head[L1]]] ← head[L2]

next[t] ← head[L1]

prev[head[L1]] ← t

XOR – звязаний список

Завдяки властивостям операції XOR (сума за модулем два, виключне або) ми можемо скоротити кількість використаної пам’яті в реалізації двозв’язного списку. Стиснемо поля next і prev у поле np = next XOR prev. Тепер, знаючи одну адресу, ми завжди зможемо взнати іншу.

Наприклад:

next = np XOR prev = next XOR prev XOR prev = next XOR 0 = next

При цьому можна побачити, що np[head] = nil XOR next[head]. При реалізації програми за нульовий показчик найчастіше беруть значення 0, тож np[head] = 0 XOR next[head] = next[head],

np[tail] = prev[tail] XOR 0 = prev[tail].

Запишемо процедури Search, Insert, Delete.

Search(L, key)

x ← head[L]

p ← 0

while x ≠ 0 and key[x] ≠ key

do t ← x

x ← np[x] XOR p

p ← t

return x

Insert(L, x)

If head[L] = 0

Then np[x] ← 0

Else np[head[L]] ← np[head[L]] XOR x

np[x] ← head[L]

head[L] ← x

Delete(L, x, prev)

If prev = 0

Then head[L] ← np[x]

If np[x] ≠ 0

Then np[np[x]] ← np[np[x]] XOR x

Else np[prev] ← np[prev] XOR x XOR np[x] XOR prev

If np[x] XOR prev ≠ 0

Then np[np[x] XOR prev] ← np[np[x] XOR prev] XOR x XOR prev

Також цікавим може виявитись те, що, через комутативність операції XOR, наш список можна проходити з обох сторін тими самими алгоритмами. Для цього лице потрібно виконати присвоєння head[L] := tail[L]. Щоправда для цього нам доведеться знайти tail[L], що вимагає O(n) часу.

Скоротити час до O(1) можна у звернутому в кільце списку. В такому типі списку нам, все-одно, доведеться завжди зберігати у пам’яті адресу tail[L], для знаходження next[head[L]]. В такому разі процедура розвертання списку може мати такий вигляд:

Reverse(L)

If head[L] ≠ 0

Than swap(head[L], tail[L])

Використання хеш-значень для пошуку

Нехай у нас є список, де кожен елемент має поля:

  • key (значення ключа)

  • hash (значення деякої функції )

  • next (показчик на наступний елемент)

Якщо при цьому ключ представляє із себе доволі складний тип порівняно зі своїм хеш-значенням (наприклад ключ – рядок символів, а його хеш-значення – ціле число), то пошук у списку можна спростити:

Search(L, key)

x ← head[L]

while x ≠ nil

do If hash[x] = hash(key)

Then If key[x] = key

Then return x

x ← next[x]

return x