14-bo‘lim
Samarali saralash algoritmlari
Merge sort va quick sort - bo'l va hukmronlik qil yondashuvi, ularning taqqoslanishi va Python Timsort algoritmi.
Ushbu bo‘lim mundarijasi
O(n²) algoritmlar 10 000 ta elementda ham qiynaladi. Endi O(n log n) algoritmlarga o'tamiz - ular millionlab elementni bir soniyada saralaydi.
Bo'l va hukmronlik qil #
Ikkala algoritm ham bitta g'oyaga asoslanadi:
- Bo'l - masalani kichikroq bo'laklarga ajrat
- Hukmronlik qil - har bir bo'lakni rekursiv yech
- Birlashtir - natijalarni yig'
Merge Sort (birlashtirish bilan saralash) #
def birlashtirib_sarala(massiv):
"""Merge sort. Kafolatlangan O(n log n), barqaror, O(n) qo'shimcha xotira."""
if len(massiv) <= 1:
return list(massiv)
orta = len(massiv) // 2
chap = birlashtirib_sarala(massiv[:orta])
ong = birlashtirib_sarala(massiv[orta:])
return _birlashtir(chap, ong)
def _birlashtir(chap, ong):
"""Ikkita tartiblangan ro'yxatni bittaga birlashtiradi. O(n)."""
natija = []
i = j = 0
while i < len(chap) and j < len(ong):
if chap[i] <= ong[j]: # <= barqarorlikni ta'minlaydi
natija.append(chap[i])
i += 1
else:
natija.append(ong[j])
j += 1
natija.extend(chap[i:]) # qolganini qo'shamiz
natija.extend(ong[j:])
return natija
print(birlashtirib_sarala([5, 2, 8, 1, 9, 3, 7]))
[1, 2, 3, 5, 7, 8, 9]
Birlashtirish qanday ishlaydi? #
Ikkita tartiblangan ro'yxatning boshidagi elementlarni solishtirib, kichigini olamiz:
def birlashtirishni_korsat(chap, ong):
natija = []
i = j = 0
while i < len(chap) and j < len(ong):
if chap[i] <= ong[j]:
print(f" {chap[i]} <= {ong[j]} -> {chap[i]} olindi")
natija.append(chap[i])
i += 1
else:
print(f" {chap[i]} > {ong[j]} -> {ong[j]} olindi")
natija.append(ong[j])
j += 1
qolgan = chap[i:] + ong[j:]
if qolgan:
print(f" qolgani qo'shildi: {qolgan}")
return natija + qolgan
print("Natija:", birlashtirishni_korsat([2, 5, 8], [1, 3, 9]))
2 > 1 -> 1 olindi
2 <= 3 -> 2 olindi
5 > 3 -> 3 olindi
5 <= 9 -> 5 olindi
8 <= 9 -> 8 olindi
qolgani qo'shildi: [9]
Natija: [1, 2, 3, 5, 8, 9]
Massivni bitta elementgacha bo'lish uchun log₂(n) daraja kerak (har safar ikkiga bo'linadi).
Har bir darajada barcha n ta elementni birlashtiramiz - bu O(n).
Natijada: log n daraja × n amal = O(n log n).
Quick Sort (tez saralash) #
Merge sort massivni o'rtadan bo'ladi. Quick sort esa tayanch element (pivot) tanlab, undan kichiklarni chapga, kattalarni o'ngga ajratadi.
def tez_sarala(massiv):
"""Quick sort - sodda, o'qish oson variant. O'rtacha O(n log n)."""
if len(massiv) <= 1:
return list(massiv)
tayanch = massiv[len(massiv) // 2]
kichiklar = [x for x in massiv if x < tayanch]
tenglar = [x for x in massiv if x == tayanch]
kattalar = [x for x in massiv if x > tayanch]
return tez_sarala(kichiklar) + tenglar + tez_sarala(kattalar)
print(tez_sarala([5, 2, 8, 1, 9, 3, 7]))
[1, 2, 3, 5, 7, 8, 9]
Bu variant tushunarli, lekin qo'shimcha xotira sarflaydi. Joyida (in-place) ishlaydigan variant:
def tez_sarala_joyida(massiv, chap=None, ong=None):
"""Quick sort - joyida ishlaydi, O(log n) xotira (stek uchun)."""
if chap is None:
massiv = list(massiv)
chap, ong = 0, len(massiv) - 1
if chap < ong:
bolgich = _bol(massiv, chap, ong)
tez_sarala_joyida(massiv, chap, bolgich - 1)
tez_sarala_joyida(massiv, bolgich + 1, ong)
return massiv
def _bol(massiv, chap, ong):
"""Lomuto usuli: oxirgi elementni tayanch qilib, massivni ikkiga ajratadi."""
tayanch = massiv[ong]
i = chap - 1
for j in range(chap, ong):
if massiv[j] <= tayanch:
i += 1
massiv[i], massiv[j] = massiv[j], massiv[i]
massiv[i + 1], massiv[ong] = massiv[ong], massiv[i + 1]
return i + 1
print(tez_sarala_joyida([5, 2, 8, 1, 9, 3, 7]))
[1, 2, 3, 5, 7, 8, 9]
Agar tayanch har doim eng kichik yoki eng katta element bo'lsa, massiv 1 va n-1 ga bo'linadi
va murakkablik O(n²) ga tushadi. Bu tartiblangan massivda birinchi elementni tayanch
qilib olsangiz sodir bo'ladi.
Yechimlar:
- Tayanchni tasodifiy tanlash
- Uchtaning o'rtachasi (birinchi, o'rta, oxirgi elementlardan medianasi)
import random
def tez_sarala_tasodifiy(massiv):
"""Tasodifiy tayanch - eng yomon holat ehtimolini deyarli nolga tushiradi."""
if len(massiv) <= 1:
return list(massiv)
tayanch = random.choice(massiv)
kichiklar = [x for x in massiv if x < tayanch]
tenglar = [x for x in massiv if x == tayanch]
kattalar = [x for x in massiv if x > tayanch]
return tez_sarala_tasodifiy(kichiklar) + tenglar + tez_sarala_tasodifiy(kattalar)
tartiblangan = list(range(1, 11))
print(tez_sarala_tasodifiy(tartiblangan))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Merge sort va Quick sort #
| Xususiyat | Merge Sort | Quick Sort |
|---|---|---|
| Eng yaxshi | O(n log n) | O(n log n) |
| O'rtacha | O(n log n) | O(n log n) |
| Eng yomon | O(n log n) | O(n²) |
| Qo'shimcha xotira | O(n) | O(log n) |
| Barqaror | Ha | Yo'q |
| Amaldagi tezlik | Tezroq emas | Odatda tezroq |
| Tashqi saralash | Mos | Mos emas |
- Kafolat kerakmi (eng yomon holat ham O(n log n))? → Merge sort
- Barqarorlik kerakmi? → Merge sort
- Xotira cheklanganmi va o'rtacha tezlik muhimmi? → Quick sort
- Ma'lumot xotiraga sig'maydimi (fayl, baza)? → Merge sort
Amalda: C++ std::sort - introsort (quick sort + heap sort aralashmasi),
Java obyektlar uchun - Timsort, primitivlar uchun - dual-pivot quicksort.
Timsort - Python algoritmi #
Python sorted() va list.sort() Timsort ishlatadi. Uni 2002-yilda Tim Peters aynan Python uchun yaratgan va keyinchalik Java, Android va Swift ham qabul qilgan.
G'oyasi: real ma'lumot hech qachon butunlay tasodifiy bo'lmaydi. Unda har doim tartiblangan bo'laklar bor.
| Bosqich | Nima qilinadi |
|---|---|
| 1 | Massivda tabiiy tartiblangan bo'laklar ("run") topiladi |
| 2 | Qisqa bo'laklar qo'shish usuli bilan uzaytiriladi |
| 3 | Bo'laklar merge sort kabi birlashtiriladi |
import random
import time
def olcha(nomi, ma_lumot):
boshlanish = time.perf_counter()
sorted(ma_lumot)
print(f"{nomi:<28}{time.perf_counter() - boshlanish:.5f} soniya")
N = 1_000_000
tasodifiy = random.sample(range(N * 2), N)
tartiblangan = sorted(tasodifiy)
teskari = tartiblangan[::-1]
deyarli = tartiblangan[:]
for _ in range(100): # 100 ta elementni aralashtiramiz
i, j = random.randrange(N), random.randrange(N)
deyarli[i], deyarli[j] = deyarli[j], deyarli[i]
olcha("Tasodifiy", tasodifiy)
olcha("Tartiblangan", tartiblangan)
olcha("Teskari tartiblangan", teskari)
olcha("Deyarli tartiblangan", deyarli)
Tasodifiy 0.42718 soniya
Tartiblangan 0.00841 soniya
Teskari tartiblangan 0.01043 soniya
Deyarli tartiblangan 0.05219 soniya
Tartiblangan ma'lumotda 50 barobar tez - Timsort tayyor tartibni sezib, ishni tejaydi.
Saralash chegarasi #
Solishtirishga asoslangan har qanday algoritm uchun O(n log n) - nazariy chegara.
Isbot: n ta elementning n! ta joylashuvi bor. Har bir solishtirish variantlarni ikkiga bo'ladi,
demak kamida log₂(n!) ≈ n log n ta solishtirish kerak.
Solishtirmaydigan algoritmlar (counting sort, radix sort) O(n) ga erisha oladi, lekin ular faqat maxsus ma'lumotda (masalan, chegaralangan butun sonlarda) ishlaydi.
def sanash_bilan_sarala(sonlar, eng_katta):
"""Counting sort. O(n + k) - lekin faqat kichik butun sonlar uchun."""
hisoblagich = [0] * (eng_katta + 1)
for son in sonlar:
hisoblagich[son] += 1
natija = []
for qiymat, soni in enumerate(hisoblagich):
natija.extend([qiymat] * soni)
return natija
baholar = [85, 92, 78, 95, 85, 78, 92, 100, 60]
print(sanash_bilan_sarala(baholar, 100))
[60, 78, 78, 85, 85, 92, 92, 95, 100]
Baholar 0-100 oralig'ida bo'lgani uchun bu O(n) ishlaydi - solishtirishsiz.
- Merge sort ni joyida (in-place) ishlaydigan qilib yozishga urinib ko'ring. Nima qiyin?
- Quick sort ga "uchtaning o'rtachasi" tayanch tanlash usulini qo'shing.
- Merge sort yordamida massivdagi inversiyalar sonini sanang (tartibsiz juftliklar).
radix sortalgoritmini o'rganing va uni butun sonlar uchun yozing.
Xulosa #
- Merge sort: kafolatlangan O(n log n), barqaror, lekin O(n) qo'shimcha xotira.
- Quick sort: o'rtacha O(n log n) va amalda tezroq, lekin eng yomon holatda O(n²).
- Quick sort da tayanchni tasodifiy tanlash eng yomon holatdan himoya qiladi.
- Timsort (Python) real ma'lumotdagi tartiblangan bo'laklardan foydalanadi.
- Solishtirishga asoslangan saralashning nazariy chegarasi - O(n log n).
- Counting sort kabi maxsus algoritmlar chegaralangan ma'lumotda O(n) beradi.
Keyingi bo'limda tartiblangan ma'lumotda qidiruvni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.