3-bo‘lim

Massivlar

Massiv xotirada qanday saqlanadi, indeks nima uchun O(1) ishlaydi, dinamik massivlar va ikki o'lchamli massivlar.

🕑 10 daqiqa o‘qish 📄 630 so‘z 👁 7 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Massiv xotirada qanday joylashadi?
  2. Massiv amallari va ularning narxi
  3. Statik va dinamik massivlar
  4. Ikki o'lchamli massivlar
  5. Amaliy misol: matritsalarni ko'paytirish
  6. Massiv bilan bog'liq klassik masalalar
  7. Ikki ko'rsatkich usuli (two pointers)
  8. Siljitish oynasi (sliding window)
  9. Massivning kuchli va zaif tomonlari
  10. Xulosa

Massiv - eng qadimiy va eng ko'p ishlatiladigan ma'lumotlar tuzilmasi. Boshqa deyarli barcha tuzilmalar uning ustiga quriladi.

Massiv xotirada qanday joylashadi? #

Massivning butun sirri bitta jumlada: elementlar xotirada ketma-ket, yonma-yon turadi.

Xotira (RAM) boshqa 85 92 78 95 88 boshqa [0] [1] [2] [3] [4] 1000 1008 1016 1024 1032 manzil: Element manzili = boshlanish + indeks × element_hajmi massiv[3] → 1000 + 3 × 8 = 1024 (bitta ko'paytirish!)
Ketma-ket joylashuv tufayli istalgan elementga bir amalda yetib boriladi

Mana nima uchun massiv[999999] va massiv[0] bir xil tezlikda ishlaydi - ikkalasi ham bitta ko'paytirish va bitta qo'shish. Bu O(1).

Nima uchun indeks noldan boshlanadi?

Yuqoridagi formuladan ko'rinadi: manzil = boshlanish + indeks × hajm. Agar indeks 1 dan boshlansa, har safar (indeks - 1) ni hisoblash kerak bo'lardi. Noldan boshlash - bu qo'shimcha ayirishdan qutulish.

Massiv amallari va ularning narxi #

Python
baholar = [85, 92, 78, 95, 88]

# O(1) - to'g'ridan-to'g'ri murojaat
print(baholar[2])
baholar[2] = 80

# O(1) - oxiriga qo'shish
baholar.append(91)

# O(n) - boshiga qo'shish: hamma element bir joyga suriladi
baholar.insert(0, 100)

# O(n) - qidirish: har bir elementni tekshirish kerak
print(95 in baholar)

# O(n) - o'rtadan o'chirish: bo'shliqni to'ldirish kerak
baholar.pop(2)
Oxiriga qo'shish - O(1) 85 92 78 91 ← bo'sh joyga yoziladi, 1 ta amal Boshiga qo'shish - O(n) 100 85 92 78 ← barcha n ta element o'ngga suriladi har bir element ko'chiriladi → n ta amal
Oxiriga qo'shish arzon, boshiga qo'shish qimmat

Statik va dinamik massivlar #

C yoki Java'da massiv hajmi oldindan belgilanadi va o'zgarmaydi:

C
int baholar[5];      // aynan 5 ta, ko'p ham emas, kam ham emas

Python'da esa list - bu dinamik massiv. U o'zi kengayadi. Qanday?

Python
import sys

royxat = []
oldingi_hajm = 0

for son in range(17):
    royxat.append(son)
    hajm = sys.getsizeof(royxat)
    if hajm != oldingi_hajm:
        print(f"{len(royxat):>3} ta element -> {hajm:>4} bayt   <- kengaydi")
        oldingi_hajm = hajm
Natija
  1 ta element ->   88 bayt   <- kengaydi
  5 ta element ->  120 bayt   <- kengaydi
  9 ta element ->  184 bayt   <- kengaydi
 17 ta element ->  256 bayt   <- kengaydi

Ro'yxat to'lganda Python ikki barobar kattaroq yangi joy ajratadi va hamma narsani ko'chiradi.

Amortizatsiyalangan O(1)

Ko'chirish amali O(n) - qimmat. Lekin u kamdan-kam, ya'ni har n ta qo'shishda bir marta sodir bo'ladi. Ko'p sonli qo'shishlarga taqsimlaganda o'rtacha narx O(1) bo'lib chiqadi. Bu amortizatsiyalangan murakkablik deb ataladi va append() ni tez deb hisoblashimizga asos beradi.

Ikki o'lchamli massivlar #

Jadval, matritsa, o'yin taxtasi - bularning barchasi ikki o'lchamli massiv.

Python
# 3x4 jadval yaratish
jadval = [[0] * 4 for _ in range(3)]

jadval[0][0] = 1
jadval[1][2] = 5
jadval[2][3] = 9

for qator in jadval:
    print(" ".join(f"{qiymat:>3}" for qiymat in qator))
Natija
  1   0   0   0
  0   0   5   0
  0   0   0   9
Klassik tuzoq
Python
jadval = [[0] * 4] * 3        # XATO!
jadval[0][0] = 1
print(jadval)
Natija
[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]

* 3 uchta bir xil ro'yxatga havola yaratadi, uchta alohida ro'yxat emas. Har doim generator ishlating: [[0] * 4 for _ in range(3)].

Amaliy misol: matritsalarni ko'paytirish #

Python
def matritsalarni_kopaytir(birinchi, ikkinchi):
    """Ikki matritsani ko'paytiradi. Murakkablik: O(n^3)."""
    qatorlar = len(birinchi)
    ichki = len(ikkinchi)
    ustunlar = len(ikkinchi[0])

    if len(birinchi[0]) != ichki:
        raise ValueError("Matritsa o'lchamlari mos kelmaydi")

    natija = [[0] * ustunlar for _ in range(qatorlar)]

    for i in range(qatorlar):
        for j in range(ustunlar):
            for k in range(ichki):
                natija[i][j] += birinchi[i][k] * ikkinchi[k][j]

    return natija


a = [[1, 2],
     [3, 4]]
b = [[5, 6],
     [7, 8]]

for qator in matritsalarni_kopaytir(a, b):
    print(qator)
Natija
[19, 22]
[43, 50]

Massiv bilan bog'liq klassik masalalar #

Ikki ko'rsatkich usuli (two pointers) #

Tartiblangan massivda yig'indisi berilgan songa teng juftlikni topish:

Python
def juftlikni_top(tartiblangan, maqsad):
    """Tartiblangan massivda yig'indisi maqsadga teng juftlikni topadi. O(n)."""
    chap = 0
    ong = len(tartiblangan) - 1

    while chap < ong:
        yigindi = tartiblangan[chap] + tartiblangan[ong]

        if yigindi == maqsad:
            return (tartiblangan[chap], tartiblangan[ong])
        if yigindi < maqsad:
            chap += 1          # kattaroq yig'indi kerak
        else:
            ong -= 1           # kichikroq yig'indi kerak

    return None


sonlar = [2, 7, 11, 15, 19, 24]
print(juftlikni_top(sonlar, 26))
print(juftlikni_top(sonlar, 100))
Natija
(2, 24)
None

Sodda yechim ikkita ichma-ich sikl bilan O(n²) bo'lardi. Ikki ko'rsatkich uni O(n) ga tushiradi.

Siljitish oynasi (sliding window) #

Uzunligi k bo'lgan ketma-ket qism eng katta yig'indisini topish:

Python
def eng_katta_oyna(sonlar, k):
    """k uzunlikdagi eng katta yig'indini topadi. O(n)."""
    if len(sonlar) < k:
        return None

    joriy = sum(sonlar[:k])
    eng_katta = joriy

    for i in range(k, len(sonlar)):
        joriy += sonlar[i] - sonlar[i - k]     # yangisini qo'sh, eskisini ayir
        eng_katta = max(eng_katta, joriy)

    return eng_katta


kunlik_savdo = [120, 340, 90, 500, 210, 430, 180]
print(f"Eng yaxshi 3 kunlik savdo: {eng_katta_oyna(kunlik_savdo, 3)}")
Natija
Eng yaxshi 3 kunlik savdo: 1140
Oynani qayta hisoblamang

Har bir oynaning yig'indisini noldan hisoblasangiz O(n·k) bo'ladi. Oynani siljitish - bitta yangi element qo'shib, bitta eskisini ayirish - uni O(n) qiladi. Bu juda keng tarqalgan usul.

Massivning kuchli va zaif tomonlari #

Kuchli tomonlariZaif tomonlari
Indeks bo'yicha murojaat O(1)Boshiga/o'rtaga qo'shish O(n)
Xotirada ixcham joylashadiHajmni oshirish qimmat
Protsessor keshi bilan yaxshi ishlaydiQidiruv O(n)
Sodda va tushunarliStatik massivda hajm qat'iy
Kesh nima uchun muhim?

Protsessor xotiradan ma'lumotni bittalab emas, bloklab o'qiydi. Massiv elementlari yonma-yon turgani uchun bittasini o'qiganda qo'shnilari ham keshga tushadi. Shu sababli massiv bo'ylab yurish bog'langan ro'yxat bo'ylab yurishdan amalda bir necha barobar tez bo'ladi - Big-O bir xil (O(n)) bo'lsa ham.

Amaliy topshiriq
  1. royxatni_aylantir(royxat, k) funksiyasini yozing - massivni k pozitsiyaga o'ngga suradi. Masalan [1,2,3,4,5] va k=2[4,5,1,2,3]. Avval sodda yechim, keyin O(n) va O(1) xotirali yechim toping.
  2. eng_katta_ikkitasi(royxat) - eng katta ikkita sonni bitta aylanishda toping.
  3. Ikki o'lchamli massivni transponirlash funksiyasini yozing (qator va ustunlarni almashtirish).
  4. Har bir yechimingizning Big-O murakkabligini izohda yozing.

Xulosa #

  • Massiv elementlari xotirada ketma-ket joylashadi - shuning uchun indeks bo'yicha murojaat O(1).
  • Oxiriga qo'shish amortizatsiyalangan O(1), boshiga qo'shish esa O(n).
  • Python list - dinamik massiv: to'lganda hajmini ikki barobar oshiradi.
  • Ikki o'lchamli massivni [[0] * n] * m bilan yaratmang - generator ishlating.
  • Ikki ko'rsatkich va siljitish oynasi - massiv masalalarini O(n²) dan O(n) ga tushiradigan ikki muhim usul.

Keyingi bo'limda massivning asosiy raqibi - bog'langan ro'yxat bilan tanishamiz.

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.