O‘rtasini topish
Navbatda turgan odamlar qatorini tasavvur qiling. Qator boshi va oxiri ko‘rinib turibdi, lekin qatorda nechta odam borligini bilmaysiz. “Qatorning aynan o‘rtasidagi odamni top” deyilsa, eng oddiy yo‘l — avval boshidan oxirigacha sanab chiqish, keyin sonni ikkiga bo‘lib, yana boshidan o‘sha o‘ringacha yurish. Bu ikki marta yurishni talab qiladi. Xuddi shu masala linked list uchun ham tug‘iladi, faqat bu yerda “necha kishi borligini” array kabi darhol bilib bo‘lmaydi — buni bilish uchun ham ro‘yxat bo‘ylab yurish kerak.
Masala sharti
Bosh (head) reference’i berilgan singly linked list mavjud. Ro‘yxatning o‘rta node’ini topish kerak.
Element soni juft bo‘lsa, odatda ikkinchi yarmning birinchi node’i o‘rta hisoblanadi — bu shart, albatta, masala
qo‘yilishiga qarab o‘zgarishi mumkin, shuning uchun bu maqolada shu qoidaga amal qilamiz.
Kirish: 1 → 2 → 3 → 4 → 5 → NULL
Chiqish: 3 (o'rtadagi node)
Kirish: 1 → 2 → 3 → 4 → NULL
Chiqish: 3 (juft sonli ro'yxatda ikkinchi yarmning boshi)
Ro‘yxat bo‘sh bo‘lsa, o‘rta node ham mavjud emas. Bitta elementli ro‘yxatda o‘rta — shu yagona node’ning o‘zi.
Sodda yechim: uzunlikni avval hisoblash
Eng to‘g‘ridan-to‘g‘ri yo‘l — avval butun ro‘yxatni bir marta aylanib, uzunlikni hisoblash, keyin boshidan
length / 2 marta qadam bosib, o‘rtaga yetib borish:
FUNCTION FIND_MIDDLE_TWO_PASS(head)
length = 0
current = head
WHILE current != NULL
length = length + 1
current = current.next
current = head
steps = length / 2 // butun songa bo'lish
FOR i FROM 0 TO steps - 1
current = current.next
RETURN current
Bu yechim to‘g‘ri ishlaydi, ammo ro‘yxat bo‘ylab ikki marta yuradi: birinchi marta uzunlikni bilish uchun,
ikkinchi marta o‘rtaga yetib borish uchun. Vaqt murakkabligi baribir O(n) bo‘lsa-da, amalda ikki barobar ko‘proq
qadam bosiladi. Bundan tashqari, ba’zi vaziyatlarda ro‘yxat bo‘ylab faqat bir marta yurish imkoni bo‘ladi —
masalan, ma’lumot oqim (stream) tarzida kelayotgan bo‘lsa. Shu sababli bitta o‘tishda ishlaydigan yechim qulayroq.
Optimal yechim: sekin va tez ko‘rsatkich
G‘oya oddiy: ikkita reference bir vaqtning o‘zida headdan yurishni boshlaydi. Biri — slow — har qadamda bitta
node oldinga siljiydi. Ikkinchisi — fast — har qadamda ikkita node oldinga siljiydi. fast ro‘yxat oxiriga
yetganda, slow aynan o‘rtada bo‘ladi — chunki u fastdan ikki marta sekin yurgan.
FUNCTION FIND_MIDDLE(head)
IF head = NULL
RETURN NULL
slow = head
fast = head
WHILE fast.next != NULL AND fast.next.next != NULL
slow = slow.next
fast = fast.next.next
RETURN slow
Nega bu ishlaydi?
fast har qadamda slowdan ikki barobar tez yuradi. Demak, fast ro‘yxat oxiriga yetganda, u slowga nisbatan
ikki barobar ko‘proq masofa bosib o‘tgan bo‘ladi. Boshqacha aytganda, slow aynan fast bosib o‘tgan masofaning
yarmida turadi — bu esa ro‘yxatning o‘rtasi degani.
Sikl sharti (fast.next != NULL AND fast.next.next != NULL) ikkita holatni tekshiradi: fastning o‘zi emas,
fast.next va fast.next.nextning mavjudligi. Bu shart tanlovi juft va toq uzunlikdagi ro‘yxatlar uchun qaysi
node o‘rta hisoblanishini belgilaydi.
Bosqichma-bosqich misol
Toq uzunlikdagi ro‘yxat: 1 → 2 → 3 → 4 → 5 → NULL.
Boshlanish: slow = 1, fast = 1
1-qadam: slow = 2, fast = 3
2-qadam: slow = 3, fast = 5
fast.next = NULL → sikl to'xtaydi
Natija: slow = 3 (to'g'ri, chunki 3 - o'rtadagi element)
Juft uzunlikdagi ro‘yxat: 1 → 2 → 3 → 4 → NULL.
Boshlanish: slow = 1, fast = 1
1-qadam: slow = 2, fast = 3
tekshiruv: fast.next = 4 (mavjud), fast.next.next = NULL → sikl to'xtaydi
Natija: slow = 2 emas — tekshiramiz qaytadan
Diqqat bilan qarasak: birinchi qadamdan keyin fast = 3. Sikl sharti yana tekshiriladi: fast.next (ya’ni 4)
mavjud, lekin fast.next.next (4dan keyingisi) NULL. Demak sikl davom etadi, chunki shart AND bilan
bog‘langan va ikkalasi ham NULL bo‘lmasligi kerak edi — bu yerda ikkinchisi NULL, shuning uchun sikl to‘xtaydi
va natija slow = 2 bo‘lib qoladi.
Kirish: 1 → 2 → 3 → 4 → NULL
Natija: slow = 2
Izoh: bu qoida bo'yicha juft ro'yxatda "birinchi yarmning oxirgi elementi" o'rta hisoblanadi.
Note
Agar juft uzunlikda ikkinchi yarmning birinchi elementi o‘rta hisoblanishi kerak bo‘lsa, sikl sharti
fast != NULL AND fast.next != NULL ko‘rinishida yoziladi. Ikkala variant ham to‘g‘ri; farq shunda — masala
qaysi node’ni “o‘rta” deb belgilashiga bog‘liq. Shu sababli masalani yechishdan oldin bu shartni aniqlashtirib
olish kerak.
Vaqt va xotira murakkabligi
Ro‘yxat bo‘ylab faqat bitta o‘tish bajariladi, ammo bu o‘tishda ikkita reference harakatlanadi. fast har
safar ikki qadam bosgani uchun, u oxiriga yetganda slow taxminan n / 2 marta siljigan bo‘ladi. Umumiy bajarilgan
qadamlar soni nga mutanosib, shuning uchun vaqt murakkabligi O(n) — xuddi ikki o‘tishli yechim kabi, lekin
amaliy jihatdan ikki barobar kamroq harakat bilan.
Qo‘shimcha xotira — faqat ikkita reference, slow va fast. Ro‘yxat uzunligidan qat’i nazar bu son o‘zgarmaydi,
shuning uchun xotira murakkabligi O(1).
Nozik holatlar
- Bo‘sh ro‘yxat.
head = NULLbo‘lsa, funksiya darholNULLqaytarishi kerak. Aks holdafast.nextga murojaat qilishga urinish xatoga olib keladi. - Bitta elementli ro‘yxat.
fast.nextallaqachonNULL, shuning uchun sikl birinchi tekshiruvdayoq to‘xtaydi vaslow = headqaytadi — bu to‘g‘ri natija. - Ikkita elementli ro‘yxat.
fast.nextmavjud, lekinfast.next.nextNULL. Sikl bir marta ham bajarilmaydi, natija tanlangan qoidaga qarab birinchi yoki ikkinchi node bo‘ladi. - Tsikl bor ro‘yxat. Agar ro‘yxatda halqa mavjud bo‘lsa (ya’ni oxirgi node yana avvalgi node’ga ulangan
bo‘lsa),
fast.nextyokifast.next.nexthech qachonNULLbo‘lmaydi va sikl to‘xtamaydi. Bu funksiya faqat tsiklsiz ro‘yxat uchun mo‘ljallangan; tsiklni aniqlash alohida masaladir (qarang: Tsikl bormi?).
Keng tarqalgan xatolar
Faqat fast != NULL tekshirish. Agar sikl sharti fast.next.nextni tekshirmasdan faqat fast != NULL
bo‘lsa, fast.nextga murojaat qilinganda fast allaqachon NULL bo‘lishi mumkin — bu NULL.nextga murojaat
qilish xatosiga olib keladi. Shart har doim keyingi murojaat qilinadigan node’larning mavjudligini oldindan
tekshirishi kerak.
Uzunlikni noto‘g‘ri hisoblash. Ikki o‘tishli yechimda length / 2ni butun songa bo‘lishda ortiqcha yoki
kam qadam bosish oson xato. Fast-slow usuli bu hisob-kitobni butunlay chetlab o‘tadi, chunki u uzunlikni bilishga
ehtiyoj sezmaydi.
Juft/toq holatni aniqlashtirmaslik. Yuqorida ko‘rsatilganidek, juft uzunlikdagi ro‘yxatda qaysi node “o‘rta” hisoblanishi shartga bog‘liq. Bu aniqlanmasa, to‘g‘ri algoritm ham noto‘g‘ri natija berayotgandek ko‘rinishi mumkin.
Xulosa
Sekin va tez ko‘rsatkich (fast-slow pointer yoki “toshbaqa va quyon”) usuli linked listning o‘ziga xos xususiyatidan — index bo‘yicha bevosita kirish yo‘qligidan — kelib chiqadigan cheklovni chetlab o‘tadi: uzunlikni oldindan bilish shart emas, chunki ikkita ko‘rsatkichning nisbiy tezligi o‘rtani o‘zi topib beradi. Bu g‘oya linked list bilan bog‘liq boshqa ko‘plab masalalarda, jumladan ro‘yxatda tsikl bor-yo‘qligini aniqlashda ham qayta ishlatiladi.