
Операція 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