Doubly və Circular Linked List
Doubly linked list — hər node-un next-dən əlavə bir də prev istinadı saxladığı siyahıdır. Bir əlavə pointer bahasına iki mühüm imkan alırsan:
- İki istiqamətli gəzinti — həm irəli, həm geri; siyahını tərsinə oxumaq
O(n)-dir və əlavə struktur tələb etmir - Node əlindədirsə silmə `O(1)`-dir —
prevməlum olduğu üçünnode.prev.next = node.next; node.next.prev = node.prevkifayətdir. Singly-də əvvəlki node-u tapmaqO(n)idi.
Qiyməti: node başına əlavə pointer (yaddaş) və hər əməliyyatda iki istinadın sinxron saxlanılması (səhv etmək daha asandır).
Doubly siyahılarda demək olar həmişə həm head, həm tail saxlanılır — bu, hər iki ucda O(1) əlavə/silmə deməkdir, yəni struktur təbii olaraq deque kimi işləyir.
Circular linked list — sonuncu node-un null əvəzinə yenidən head-ə istinad etdiyi variantdır. Doubly circular-da isə head.prev da tail-ə baxır — nəticədə "başlanğıc" və "son" anlayışı şərti olur.
Üstünlükləri:
- Sonsuz dövrə təbii şəkildə alınır — round-robin, karusel, playlist "repeat" rejimi
- Sərhəd halları azalır —
nullyoxlamaları demək olar aradan qalxır, çünki hər node-un həmişənextvəprev-i var - Bir tək
currentpointer ilə hər iki ucda işləmək olur
Əsas təhlükə: gəzinti dayanmır. Sadə while (cur != null) sonsuz dövrə çevrilir. Dayanma şərti həmişə "başlanğıca qayıtdıqmı?" olmalıdır: do { ... } while (cur !== start).
Çox işlənən əlavə texnika — sentinel (dummy) node: real data saxlamayan bir node həm head, həm tail rolunu oynayır. Boş siyahı belə sentinel.next === sentinel olur, deməli bütün null xüsusi halları itir. LRU cache implementasiyalarının əksəriyyəti məhz belə qurulur.
| Əməliyyat | Singly | Doubly | Şərh |
|---|---|---|---|
| Başa əlavə/silmə | O(1) | O(1) | Hər ikisində ucuz |
| Sona əlavə (tail pointer ilə) | O(1) | O(1) | — |
| Sondan silmə | O(n) | O(1) | `prev` olduğu üçün doubly qazanır |
| Verilmiş node-u silmək | O(n) | O(1) | LRU cache-in bütün əsası budur |
| Geriyə gəzinti | Mümkün deyil (və ya O(n) əlavə iş) | O(n) | Browser history üçün vacibdir |
| Yaddaş (node başına) | 1 pointer | 2 pointer | Doubly ~təxminən 1.5-2x metadata |
| Səhv etmə riski | Aşağı | Yüksək — iki istinad sinxron qalmalıdır | Silmədə `prev`-i unutmaq klassik buqdur |
Real istifadə sahələri — bu strukturların harada işlədiyini bilmək müsahibədə nəzəriyyədən daha çox dəyərləndirilir.
- LRU cache — kanonik nümunə.
HashMapaçardan node-a birbaşa istinad verir (O(1)tapmaq), doubly linked list isə istifadə sırasını saxlayır: hər müraciətdə nodeO(1)-də çıxarılıb önə köçürülür, tutum dolanda sonuncu node atılır. Hər əməliyyatO(1)-dir və bunu massivlə etmək mümkün deyil. - Browser history / undo-redo — geri və irəli hərəkət
prev/nextilə birbaşa alınır. Yeni səhifə açılandacurrent-dən sonrakı zəncir kəsilir. - Round-robin planlaşdırma — circular siyahı üzərində
current = current.next; növbəti prosesə keçidO(1), siyahının sonu anlayışı yoxdur. - Musiqi/media playlist — "növbəti/əvvəlki" düymələri və repeat rejimi.
- Mətn redaktorunda kursor, oyunlarda növbə sırası, Josephus tipli məsələlər.
Qeyd: bu strukturların əksəriyyəti kitabxanalarda hazır gəlir (Java LinkedList/LinkedHashMap, Kotlin ArrayDeque, Dart Queue/LinkedList), amma müsahibədə səndən əl ilə yazmağı gözləyirlər.
İnterview məsləhəti. "LRU cache implementasiya et" orta səviyyə müsahibələrin ən çox verilən dizayn sualıdır və doğru cavab hər dəfə eynidir: hash map + doubly linked list. Cavabı belə qur: (1) tələb — get və put O(1) olmalıdır; (2) niyə tək struktur bəs etmir — map sıra saxlamır, siyahı isə axtarış vermir; (3) birləşmə — map açardan node-a istinad verir, siyahı sıranı saxlayır; (4) sentinel node-larla sərhəd hallarının aradan qaldırılması.
Digər klassik sual: "Doubly linked list-in singly-yə nisbətən üstünlüyü nədir və qiyməti nədir?" — cavabda mütləq hər ikisini de: O(1) silmə və geriyə gəzinti qazanırsan, node başına əlavə pointer və iki istinadı sinxron saxlamaq məsuliyyəti ödəyirsən.
Ən çox rast gəlinən səhvlər: silmə zamanı yalnız next-i yeniləyib prev-i unutmaq (siyahı bir istiqamətdə düzgün, digərində pozuq qalır — bu, tapılması ən çətin buqlardandır); circular siyahıda dayanma şərtini null üzərində qurub sonsuz dövrə düşmək; sentinel node-u element kimi saymaq və size-ı bir artıq göstərmək.
📚 Mənbələr və sənədlər
- java.util.LinkedListrəsmidocs.oracle.com
Standart kitabxananın doubly-linked list implementasiyası və əməliyyat mürəkkəblikləri.