Sparround

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)`-dirprev məlum olduğu üçün node.prev.next = node.next; node.next.prev = node.prev kifayətdir. Singly-də əvvəlki node-u tapmaq O(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ırnull yoxlamaları demək olar aradan qalxır, çünki hər node-un həmişə nextprev-i var
  • Bir tək current pointer 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əliyyatSinglyDoublyŞə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əkO(n)O(1)LRU cache-in bütün əsası budur
Geriyə gəzintiMümkün deyil (və ya O(n) əlavə iş)O(n)Browser history üçün vacibdir
Yaddaş (node başına)1 pointer2 pointerDoubly ~təxminən 1.5-2x metadata
Səhv etmə riskiAşağıYüksək — iki istinad sinxron qalmalıdırSilmə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ə. HashMap aç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ə node O(1)-də çıxarılıb önə köçürülür, tutum dolanda sonuncu node atılır. Hər əməliyyat O(1)-dir və bunu massivlə etmək mümkün deyil.
  • Browser history / undo-redo — geri və irəli hərəkət prev/next ilə birbaşa alınır. Yeni səhifə açılanda current-dən sonrakı zəncir kəsilir.
  • Round-robin planlaşdırma — circular siyahı üzərində current = current.next; növbəti prosesə keçid O(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 — getput 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.