22 β Brute force va to'liq qidiruv¶
β¬ οΈ Oldingi: 21 β Graflar: tasvirlash va Union-Find Β· π README Β· Keyingi: 23 β Divide and Conquer (bo'lib hal qil) β‘οΈ
Bu bobda: Bu bob β kitobning IV qismi (algoritm paradigmalari)ning kirishi. Avval umumiy dizayn strategiyalari (brute force, divide & conquer, greedy, dinamik dasturlash, backtracking) bilan tanishamiz, so'ng eng sodda va eng umumiy paradigma β brute force (to'liq sanab chiqish)ni chuqur o'rganamiz: barcha mumkin yechimlarni hosil qilib, har birini tekshirib, to'g'risini yoki eng yaxshisini tanlash. Klassik misollar (chiziqli qidiruv, two-sum, eng yaqin juftlik, qism-to'plamlar va permutatsiyalar), kombinatorik portlash hodisasi va brute force qachon yetarli, qachon yetarli emasligini ko'rib chiqamiz.
Halollik / Eslatma: Bu yerdagi murakkablik chegaralari (chiziqli qidiruv O(n), two-sum brute O(nΒ²), eng yaqin juftlik O(nΒ²), qism-to'plamlar 2βΏ, permutatsiyalar n!) matematik aniq. Brute force "yomon algoritm" emas β u to'g'ri vositada to'g'ri javob. Barcha Python namunalari haqiqatan ishga tushirib tekshirilgan; ko'rsatilgan chiqishlar kod bergan haqiqiy natijalar.
IV qismga kirish: algoritm paradigmalari¶
Shu paytgacha biz ma'lumotlar strukturalarini (massiv, ro'yxat, daraxt, graf) va tahlil vositalarini (Big-O, rekurrent munosabatlar, amortizatsiya) o'rgandik. Endi savol o'zgaradi: berilgan masalani yechish uchun algoritmni qanday o'ylab topamiz?
Tajriba shuni ko'rsatadiki, juda ko'p turli masalalar bir nechta umumiy dizayn strategiyasi (paradigma) ostida yechiladi. Paradigma β bu retsept emas, balki fikrlash qolipi: "bu masalaga qaysi qolip mos keladi?" deb so'rab, yechimga yo'l ochiladi.
IV qismda beshta asosiy paradigmani ko'rib chiqamiz:
| Paradigma | G'oya (bir jumlada) | Bob |
|---|---|---|
| Brute force | Barcha variantni hosil qil va tekshir | shu bob |
| Divide & Conquer | Masalani kichik bo'laklarga bo'l, hal qil, birlashtir | 23-bob |
| Greedy | Har qadamda hozir eng yaxshi ko'rinadigan tanlovni qil | 24-bob |
| Dinamik dasturlash | Qism-masalalar javobini eslab qol, qayta hisoblama | 25-bob |
| Backtracking | Variantlarni daraxt bo'ylab sina, umidsiz shoxlarni kes | 26-bob |
Bu paradigmalar bir-biriga zid emas β ko'pincha brute forcedan boshlanadi va asta-sekin aqlliroq strategiyaga aylantiriladi. Aynan shuning uchun biz brute force'dan boshlaymiz: u barcha qolganlarining etaloni (boshlang'ich nuqtasi).
Brute force nima¶
Intuitsiya: Kalitlar bog'lami bor, eshikni ochish kerak. Qaysi kalit ekanini bilmaysiz. Eng sodda usul β har bir kalitni navbat bilan sinab ko'rish, ishlaganini topguncha. Bu β brute force.
Brute force (yoki to'liq sanab chiqish, ingliz tilida exhaustive search) β masalaning barcha mumkin yechimlarini (yoki barcha nomzodlarni) sistematik ravishda hosil qilib, har birini tekshirib chiqadigan strategiya. To'g'ri yechim qidirilsa β birinchi mos kelganini qaytaramiz; eng yaxshi yechim qidirilsa β hammasini ko'rib, eng yaxshisini eslab qolamiz.
Yuqoridagi diagrammada brute force'ning umumiy "shakli" ko'rsatilgan: kirishdan yechim fazosi (barcha nomzodlar) hosil qilinadi, har bir nomzod tekshiruvdan o'tkaziladi, mos kelganlari orasidan natija tanlanadi. Umumiy narx oddiy ko'paytma:
Psevdokod ko'rinishida brute force shabloni quyidagicha:
brute_force(masala):
eng_yaxshi = yo'q
har bir nomzod β barcha_nomzodlar(masala): # hosil qilish
agar yaroqli(nomzod) va nomzod > eng_yaxshi: # tekshirish
eng_yaxshi = nomzod
qaytar eng_yaxshi
E'tibor bering: bu yerda hech qanday "aqllilik" yo'q. Biz strukturadan, matematik xossadan yoki nayrangdan foydalanmaymiz β shunchaki hammasini ko'rib chiqamiz. Aynan shuning uchun u har doim eng tushunarli yechim.
Nega brute force muhim¶
Brute force ko'pincha "sodda, demak ahamiyatsiz" deb qabul qilinadi. Bu xato. U to'rt sababga ko'ra g'oyatda muhim:
- Har doim to'g'ri javob beradi (agar yechim fazosi cheklangan bo'lsa). Biz barcha nomzodni ko'rib chiqamiz β demak to'g'ri javob ular orasida bo'lsa, biz uni topamiz. Bu β eng kuchli to'g'rilik kafolati.
- Etalon (baseline) yechim. Optimallashtirishdan oldin avval ishlaydigan yechim kerak. Brute force'ni yozish odatda eng oson, va u keyinroq tezroq algoritm to'g'ri ishlayotganini tekshirish uchun reference bo'lib xizmat qiladi (kichik kirishlarda ikkalasi bir xil javob berishi kerak).
- Kichik kirishda mukammal. Agar
nkichik bo'lsa (masalann β€ 20), brute force tez ishlaydi va boshqa hech narsa kerak emas. Murakkab algoritm yozish β vaqt isrofi. - Ba'zan eng yaxshi ma'lum yechim. Ayrim masalalar uchun (NP-qiyin masalalar β 32-bob) ma'lum bo'lgan eng yaxshi aniq algoritm baribir eksponensial. Bunday holatda brute force (yoki uning aqlliroq varianti β backtracking) amalda ishlatiladigan yechim bo'lib qoladi.
Eslatma: "Premature optimization is the root of all evil" (erta optimallashtirish β barcha yomonlikning ildizi) degan mashhur ogohlantirish aynan shu haqida. Avval ishlaydigan brute force yoz, ishonch hosil qil, keyin agar haqiqatan kerak bo'lsa tezlashtir.
Klassik misollar¶
Endi brute force'ni beshta klassik masalada ko'ramiz. Har birining murakkabligini aniq aytamiz.
1-misol. Chiziqli qidiruv β O(n)¶
Eng sodda brute force: massivda elementni topish uchun boshidan oxirigacha har bir elementni nishon bilan solishtiramiz.
def chiziqli_qidiruv(massiv, nishon):
for i, x in enumerate(massiv):
if x == nishon:
return i # topildi: indeksni qaytar
return -1 # topilmadi
print(chiziqli_qidiruv([4, 2, 7, 1, 9], 7)) # -> 2
print(chiziqli_qidiruv([4, 2, 7, 1, 9], 5)) # -> -1
Bu yerda "yechim fazosi" β massivning n ta pozitsiyasi; har birini tekshirish O(1). Demak jami O(n). Eng yomon holatda (element yo'q yoki oxirida) hamma n ta elementni ko'ramiz.
Eslatma: Agar massiv saralangan bo'lsa, biz brute force o'rniga binar qidiruvni (28-bob) ishlatib O(log n) ga tushira olamiz. Bu β "strukturadan foydalanish" optimallashtirishning birinchi misoli: saralanganlik xossasi yarmini tashlab yuborishga imkon beradi.
2-misol. Two-sum (ikki sonli yig'indi) β O(nΒ²)¶
Masala: massivda yig'indisi target ga teng bo'lgan ikkita elementni toping. Brute force: barcha juftliklarni sinab ko'ramiz.
def two_sum_brute(nums, target):
n = len(nums)
for i in range(n):
for j in range(i + 1, n): # har juftlik bir marta
if nums[i] + nums[j] == target:
return (i, j)
return None
print(two_sum_brute([2, 7, 11, 15], 9)) # -> (0, 1)
print(two_sum_brute([3, 2, 4], 6)) # -> (1, 2)
Juftliklar soni C(n, 2) = n(nβ1)/2 = O(nΒ²), har birini tekshirish O(1). Demak O(nΒ²).
Buni hash jadval (15-bob) bilan O(n) ga tushirish mumkin: har bir x uchun target β x ni daftarchada qidiramiz.
def two_sum_hash(nums, target):
korilgan = {} # qiymat -> indeks
for i, x in enumerate(nums):
if target - x in korilgan: # juftini O(1) da topdik
return (korilgan[target - x], i)
korilgan[x] = i
return None
print(two_sum_hash([2, 7, 11, 15], 9)) # -> (0, 1)
print(two_sum_hash([3, 2, 4], 6)) # -> (1, 2)
Mana bu β brute force'dan aqlliroq paradigmaga o'tishning ajoyib namunasi: ichki sikl (juftni qidirish) hash jadval orqali O(1) ga aylanadi, shu sababli jami O(nΒ²) β O(n) ga tushadi. Lekin e'tibor bering: brute force versiyasi avval bizga to'g'ri javob nima ekanini ko'rsatdi, va u hash versiyasini tekshirish uchun etalon bo'lib xizmat qildi.
3-misol. Eng yaqin ikki nuqta β O(nΒ²)¶
n ta nuqta berilgan; bir-biriga eng yaqin ikkitasini toping. Brute force: har bir juftlik orasidagi masofani hisoblab, eng kichigini eslaymiz.
import math
def eng_yaqin_juftlik(nuqtalar):
n = len(nuqtalar)
eng_kichik = math.inf
juftlik = None
for i in range(n):
for j in range(i + 1, n):
(x1, y1), (x2, y2) = nuqtalar[i], nuqtalar[j]
masofa = math.hypot(x1 - x2, y1 - y2)
if masofa < eng_kichik:
eng_kichik = masofa
juftlik = (i, j)
return juftlik, round(eng_kichik, 3)
nuqtalar = [(0, 0), (3, 4), (1, 1), (5, 5)]
print(eng_yaqin_juftlik(nuqtalar)) # -> ((0, 2), 1.414)
Yana C(n, 2) = O(nΒ²) ta juftlik. Qiziq tomoni: bu masalani divide & conquer (23-bob) bilan O(n log n) ga tushirish mumkin β bu paradigmalarning kuchini ko'rsatadigan mashhur natija. Lekin brute force versiyasi atigi besh qatorda yoziladi va kichik n uchun mukammal.
4-misol. Barcha qism-to'plamlar (2βΏ) va permutatsiyalar (n!)¶
Ko'p brute force masalalarda biz barcha qism-to'plamlarni yoki barcha tartiblarni hosil qilib, har birini baholaymiz. Bu β kombinatorik qidiruvning yuragi.
n elementli to'plamning qism-to'plamlari soni β 2βΏ. Sababi: har bir element uchun mustaqil "olamiz yoki olmaymiz" degan ikki tanlov bor.
def barcha_qism_toplamlar(elementlar):
natija = [[]] # bo'sh to'plamdan boshlaymiz
for x in elementlar:
natija += [oldingi + [x] for oldingi in natija]
return natija
qts = barcha_qism_toplamlar([1, 2, 3])
print(len(qts)) # -> 8
print(qts) # -> [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
Bu g'oya daraxt sifatida juda yaxshi ko'rinadi: har bir element bo'yicha shox ikkiga bo'linadi.
Diagrammada {a, b, c} to'plami uchun sanab chiqish daraxti ko'rsatilgan: har bir daraja bir elementga "olamiz (1) / olmaymiz (0)" tanlovini qo'shadi, daraxt har darajada barglar sonini ikkilantiradi β 2Β³ = 8 ta barg, har biri bitta qism-to'plam.
Permutatsiyalar (tartiblar) soni esa β n!. Bu yerda har darajada tanlov soni bittaga kamayadi: birinchi pozitsiyaga n ta variant, keyingisiga nβ1, va hokazo.
from itertools import permutations
perms = list(permutations([1, 2, 3]))
print(len(perms)) # -> 6
print(perms[0]) # -> (1, 2, 3)
print(perms[-1]) # -> (3, 2, 1)
Eslatma: Ko'p mashhur masalalar shu ikki shaklga keladi. Masalan, knapsack (sumka) β qaysi buyumlarni olish (qism-to'plam, 2βΏ); kommivoyajyor (TSP) β shaharlarni qaysi tartibda aylanib chiqish (permutatsiya, n!). Brute force ularni to'g'ri yechadi, lekin faqat kichik
nda.
5-misol. Parol / kombinatsiya sinash (g'oya)¶
Brute force'ning eng tanish nomi β "parolni brute force qilish". G'oya: ruxsat etilgan alifboning barcha mumkin ketma-ketliklarini hosil qilib, har birini sinab ko'rish.
from itertools import product
def kodni_top(haqiqiy_kod, alifbo="0123456789", uzunlik=3):
sinaldi = 0
for harflar in product(alifbo, repeat=uzunlik): # 000, 001, ..., 999
nomzod = "".join(harflar)
sinaldi += 1
if nomzod == haqiqiy_kod:
return nomzod, sinaldi
return None, sinaldi
print(kodni_top("019")) # -> ('019', 20)
3 raqamli kod uchun fazoning hajmi 10Β³ = 1000; "019" 20-chi bo'lib sinaladi. Lekin uzunlik oshsa, fazo alifbo ^ uzunlik bo'lib eksponensial o'sadi: 8 belgili harf-raqamli parol uchun 62βΈ β 2.18 Γ 10ΒΉβ΄ variant. Mana shu yerda biz eng muhim cheklovga β kombinatorik portlashga duch kelamiz.
Kombinatorik portlash¶
Intuitsiya: Shaxmat taxtasining birinchi katagiga 1 dona bug'doy, keyingisiga 2, keyingisiga 4 ... deb ikkilantirib qo'ysangiz, 64-katakka dunyodagi bug'doydan ko'p don kerak bo'ladi. Eksponensial o'sish β inson sezgisini aldaydi: "biroz kattaroq" kirish "vaqt qadar ko'p" ish degani bo'lib chiqadi.
Kombinatorik portlash (combinatorial explosion) β yechim fazosining hajmi kirish n bilan eksponensial yoki faktorial o'sishi. Brute force barcha nomzodni ko'rgani uchun, uning narxi to'g'ridan-to'g'ri fazo hajmiga teng. Quyidagi jadval n ozgina o'sganda nima bo'lishini ko'rsatadi:
| n | nΒ² | 2βΏ (qism-to'plamlar) | n! (tartiblar) |
|---|---|---|---|
| 10 | 100 | 1 024 | 3 628 800 |
| 20 | 400 | ~1.05 million | ~2.4 Γ 10ΒΉβΈ |
| 50 | 2 500 | ~1.13 Γ 10ΒΉβ΅ | ~3 Γ 10βΆβ΄ |
E'tibor bering: nΒ² n=50 da ham atigi 2 500 β bemalol hisoblanadi. Lekin 2βΏ 50 da kvadrillionga, n! esa tasavvurdan tashqari songa yetadi.
Buni Python bilan to'g'ridan-to'g'ri ko'rsatamiz:
import math
for n in [10, 20, 50]:
print(f"n={n}: n^2={n**2}, 2^n={2**n}, n!={math.factorial(n)}")
# Chiqish:
# n=10: n^2=100, 2^n=1024, n!=3628800
# n=20: n^2=400, 2^n=1048576, n!=2432902008176640000
# n=50: n^2=2500, 2^n=1125899906842624, n!=30414093201713378043612608166064768844377641568960512000000000000
Amaliy chegara. Zamonaviy kompyuter taxminan 10βΈ (yuz million) sodda amalni bir soniyada bajaradi. Demak:
2βΏ:n β 26da sekundlar;n β 40da soatlar;n β 50da haftalar β chegara taxminan n β€ 25β30.n!:n = 12da ~0.5 milliard (sekundlar);n = 15da soatlar;n = 20da kosmik β chegara taxminan n β€ 11β12.
Mana shu sabab brute force katta kirishda ishlamaydi: fazoning o'zi shunchalik kattaki, hatto har bir nomzodga bir nanosoniya sarflasak ham, koinotning yoshi yetmaydi. Brute force "sekin kod" emas β u bajarib bo'lmaydigan hajmda ish qiladi.
Diqqat: Kompyuterni 1000 marta tezlatish eksponensial muammoni yechmaydi.
2βΏda 1000x tezlik β bu atiginni+10ga oshiradi (chunki2ΒΉβ° β 1000). Eksponensial o'sishni faqat boshqa algoritm (boshqa paradigma) yengadi, ko'proq apparat emas.
Brute force'dan tezroqqa: optimallashtirish yo'llari¶
Agar brute force juda sekin bo'lsa, uni qanday tezlashtiramiz? Keyingi boblar shu haqida, lekin asosiy yo'nalishlarni hozir sanab o'tamiz:
- Erta to'xtash (early exit). Javob topilishi bilan to'xtash. Two-sum'da birinchi mos juftlikni topib qaytdik β qolganini ko'rmadik. Bu eng yomon holatni o'zgartirmaydi, lekin amalda ko'p vaqt tejaydi.
- Kesib tashlash (pruning). Yechim fazosini daraxt sifatida ko'rib, umidsiz shoxlarni ulardan oldin kesish. Bu β backtracking (26-bob)ning yuragi: nomzodni to'liq qurmasdan turib, "bu yo'ldan yaxshi yechim chiqmaydi" deb butun pastki daraxtni tashlab yuborish.
- Strukturadan foydalanish. Saralash (binar qidiruv imkonini beradi), hash jadval (O(1) qidiruv), oldindan hisoblangan jadval. Two-sum'da hash bizni O(nΒ²) β O(n) ga olib chiqdi.
- Aqlliroq paradigma. Masalada takrorlanuvchi qism-masalalar bo'lsa β dinamik dasturlash (25-bob); bo'lib hal qilish mumkin bo'lsa β divide & conquer (23-bob); ochko'z tanlov to'g'ri bo'lsa β greedy (24-bob).
Quyidagi jadval shu bobdagi misollarning brute force va yaxshilangan murakkabliklarini umumlashtiradi:
| Masala | Brute force | Yaxshilangan | Vosita |
|---|---|---|---|
| Saralangan massivda qidiruv | O(n) | O(log n) | binar qidiruv (struktura) |
| Two-sum | O(nΒ²) | O(n) | hash jadval |
| Eng yaqin juftlik | O(nΒ²) | O(n log n) | divide & conquer |
| Qism-to'plam masalalari | O(2βΏ) | gohida O(nΒ·W) | dinamik dasturlash / pruning |
Halollik: brute force "yomon" emas¶
Brute force haqida ikki keng tarqalgan noto'g'ri tushuncha bor, ularni to'g'rilaymiz.
Anti-pattern: "Brute force β havaskorlar uchun, professional har doim aqlli algoritm yozadi." Bu xato. Professional avval brute force yozadi (chunki u tez yoziladi va to'g'riligi aniq), keyin profillab (o'lchab) ko'radi: agar tez bo'lsa β ish tamom. Murakkab algoritmni faqat haqiqatan kerak bo'lganda yozadi, chunki har bir qo'shimcha murakkablik β yangi xato manbai.
Trade-off: Brute force soddalik va to'g'rilik kafolatini beradi, evaziga tezlikni oladi. Aqlli algoritm tezlikni beradi, evaziga murakkablik va xato xavfini oladi. To'g'ri tanlov
nning kattaligiga bog'liq: kichiknβ brute force; kattanβ aqlliroq paradigma.
Eng to'g'ri yondashuv β bosqichma-bosqich:
1. Brute force yoz -> ishlaydi, to'g'ri (etalon)
2. n kichik bo'lsa -> TO'XTA. Yetarli.
3. n katta bo'lsa -> profillab, optimallashtir
4. Tezlatilgan kodni brute force bilan (kichik n da) solishtir -> teng bo'lsa, to'g'ri
Bu jarayonda brute force ikki marta yutuq beradi: birinchi ishlaydigan yechim sifatida, va keyin tezroq kodning to'g'riligini tasdiqlovchi reference sifatida. Aynan shuning uchun u β barcha paradigmalarning poydevori, va shu sababli IV qism aynan u bilan boshlanadi.
Asosiy g'oyalar (bobni qisqacha)¶
- Algoritm paradigmasi β masala yechishning umumiy fikrlash qolipi. Beshta asosiysi: brute force, divide & conquer, greedy, dinamik dasturlash, backtracking.
- Brute force (to'liq sanab chiqish) β barcha mumkin nomzodlarni hosil qilib, har birini tekshirib, to'g'ri yoki eng yaxshi yechimni tanlash. Eng sodda va eng umumiy strategiya.
- Narx β (nomzodlar soni) Γ (bitta tekshiruv narxi).
- Brute force muhim, chunki: har doim to'g'ri javob beradi (cheklangan fazoda); etalon (baseline) yechim; kichik kirishda mukammal; ba'zan eng yaxshi ma'lum yechim.
- Klassik murakkabliklar: chiziqli qidiruv O(n); two-sum brute O(nΒ²); eng yaqin juftlik O(nΒ²); qism-to'plamlar 2βΏ; permutatsiyalar n!.
- Kombinatorik portlash:
2βΏvan!shunchalik tez o'sadiki, brute force taxminann > 25β30(2βΏ) yokin > 12(n!) da amaliy emas. Ko'proq apparat eksponensial muammoni yechmaydi. - Optimallashtirish: erta to'xtash, pruning (backtracking), strukturadan foydalanish (saralash, hash), aqlliroq paradigma.
- Halollik: brute force "yomon" emas. Avval ishlaydigan brute force, keyin kerak bo'lsa tezlatish β bu "premature optimization"dan saqlaydi.
Mashqlar¶
Oson¶
1-mashq. Massivdagi eng katta elementni topadigan brute force funksiyani yozing (so'zda yoki kodda) va uning vaqt murakkabligini Big-O da ayting. Nega bundan tezroq qilish mumkin emas (saralanmagan massivda)?
2-mashq. "Brute force" so'zini o'z so'zlaringiz bilan ta'riflang. Nega u har doim to'g'ri javob beradi (yechim fazosi cheklangan bo'lsa)? Bir jumlada ayting.
3-mashq. 4 elementli to'plamning nechta qism-to'plami bor? 5 ta shaharni nechta xil tartibda aylanib chiqish mumkin? Formulalarni ishlatib hisoblang.
O'rta¶
4-mashq. Massivdagi barcha juftliklarni ro'yxat qilib qaytaradigan funksiya yozing (har juftlik bir marta, tartibsiz). [10, 20, 30, 40] uchun natija nechta juftlikdan iborat? Funksiyaning murakkabligini ayting.
5-mashq. Two-sum'ni ikki usulda β brute force O(nΒ²) va hash jadval O(n) bilan β yeching. Har biri uchun: (a) ichki ish nima qiladi, (b) nima uchun murakkablik shunday, (c) qaysi resurs evaziga tezlik olinadi (hash versiyada)?
6-mashq. [1, 2, 3] to'plamining barcha qism-to'plamlarini generatsiya qiluvchi funksiyani rekursiv usulda yozing (har element uchun "olamiz / olmaymiz"). Natijada nechta qism-to'plam bo'lishi kerak?
Qiyin¶
7-mashq. Quyidagi funksiyaning vaqt murakkabligini aniq hisoblang (Big-O):
maksimal_qism_yigindi_brute(arr):
eng_yaxshi = arr[0]
har i β 0..n-1:
har j β i..n-1:
joriy = sum(arr[i..j]) # bu ichki amal nechta qadam?
eng_yaxshi = max(eng_yaxshi, joriy)
qaytar eng_yaxshi
Diqqat: sum(arr[i..j]) o'zi bir sikl. Qancha bo'ladi va nega?
8-mashq. Bir masalada 2βΏ brute force kerak. Kompyuter sekundiga 10βΈ amal bajaradi. (a) n = 40 uchun taxminan qancha vaqt ketadi? (b) Kompyuteringizni 1000 baravar tezlatsangiz, qaysi n gacha yecha olasiz (ya'ni 1000x tezlik n ni qanchaga oshiradi)? (c) Xulosa: ko'proq apparat eksponensial muammoni yechadimi?
9-mashq. Sizga masala berildi va n ning kattaligi noma'lum. Qaror daraxti (yoki qadamlar ro'yxati) tuzing: qachon brute force yetarli, qachon optimallashtirish kerak, va optimallashtirilgan kodning to'g'riligini qanday tekshirasiz? Brute force'ning ikki rolini (birinchi yechim + reference) eslang.
Yechimlar
1-mashq yechimi¶
Brute force: birinchi elementni "eng katta" deb olamiz, keyin qolganlarini ketma-ket ko'rib, kattaroqini uchratsak β yangilaymiz.
Murakkablik O(n) β har bir elementni bir marta ko'ramiz. Bundan tezroq qilish mumkin emas: saralanmagan massivda eng kattani topish uchun har elementni hech bo'lmasa bir marta ko'rish shart β agar birortasini o'tkazib yuborsak, aynan o'sha eng katta bo'lib chiqishi mumkin. Demak O(n) β bu masala uchun optimal (pastki chegara ham O(n)).
2-mashq yechimi¶
Brute force β masalaning barcha mumkin nomzodlarini sistematik hosil qilib, har birini tekshirib, mosini (yoki eng yaxshisini) tanlaydigan strategiya. U har doim to'g'ri javob beradi, chunki biz barcha nomzodni ko'rib chiqamiz β to'g'ri javob fazoda mavjud bo'lsa, biz uni albatta uchratamiz (hech narsa o'tkazib yuborilmaydi).
3-mashq yechimi¶
- 4 elementli to'plamning qism-to'plamlari:
2β΄ = 16ta (bo'sh to'plam va to'liq to'plam ham kiradi). - 5 ta shaharni aylanib chiqish tartiblari:
5! = 5 Γ 4 Γ 3 Γ 2 Γ 1 = 120ta.
(Eslatma: agar TSP'da boshlang'ich shahar qat'iy bo'lsa, aylanma yo'llar soni (5β1)! = 24, lekin sof permutatsiyalar soni 120.)
4-mashq yechimi¶
def barcha_juftliklar(arr):
juftliklar = []
n = len(arr)
for i in range(n):
for j in range(i + 1, n): # j > i: har juftlik bir marta
juftliklar.append((arr[i], arr[j]))
return juftliklar
print(barcha_juftliklar([10, 20, 30, 40]))
# -> [(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)]
print(len(barcha_juftliklar([10, 20, 30, 40]))) # -> 6
Juftliklar soni C(4, 2) = 4Β·3/2 = 6. Umuman C(n, 2) = n(nβ1)/2 = O(nΒ²). Funksiya har juftlikni bir marta hosil qiladi, demak O(nΒ²).
5-mashq yechimi¶
Brute force O(nΒ²):
def two_sum_brute(nums, target):
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return (i, j)
return None
(a) Ichki ish: har bir (i, j) juftligi uchun yig'indini hisoblab, target bilan solishtiradi. (b) Juftliklar soni C(n, 2) = O(nΒ²), har biri O(1) β O(nΒ²). (c) Qo'shimcha resurs yo'q (O(1) xotira), lekin sekin.
Hash O(n):
def two_sum_hash(nums, target):
korilgan = {}
for i, x in enumerate(nums):
if target - x in korilgan:
return (korilgan[target - x], i)
korilgan[x] = i
return None
(a) Ichki ish: har bir x uchun "juftim" target β x daftarchada (hash jadvalda) bormi deb O(1) da qaraydi. (b) Massivni bir marta aylanamiz, har qadamda O(1) hash amali β O(n). (c) Tezlik O(n) qo'shimcha xotira evaziga olinadi (hash jadval) β klassik vaqt-xotira trade-offi.
6-mashq yechimi¶
def qism_toplamlar_rek(elementlar):
if not elementlar: # bazaviy holat: bo'sh to'plam
return [[]]
bosh, qoldiq = elementlar[0], elementlar[1:]
kichik = qism_toplamlar_rek(qoldiq) # qoldiqning qism-to'plamlari
# har biriga "bosh"ni qo'shgan / qo'shmagan variant:
return kichik + [[bosh] + s for s in kichik]
print(qism_toplamlar_rek([1, 2, 3]))
# -> [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
print(len(qism_toplamlar_rek([1, 2, 3]))) # -> 8
Natijada 2Β³ = 8 ta qism-to'plam. Rekursiya har elementda fazoni ikkiga bo'ladi ("olamiz / olmaymiz") β bu aynan diagrammadagi sanab chiqish daraxti.
7-mashq yechimi¶
Uchta ichki-ichiga sikl bor:
- Tashqi
i:nmarta. - O'rta
j: hariuchunn β imarta, o'rtacha~n/2. - Eng ichki
sum(arr[i..j]): bu yashirin sikl βj β i + 1ta elementni qo'shadi, buO(n)qadargacha.
Demak jami: barcha (i, j) juftliklar uchun O(nΒ²) ta, va har biri uchun ichki yig'indi O(n). Jami O(nΒ³).
Aniqrog'i: Ξ£_i Ξ£_j (j β i + 1) = Ξ(nΒ³). Bu β klassik "max subarray sum" masalasining sodda brute force'i. (Uni ichki yig'indini takrorlamasdan O(nΒ²) ga, Kadane algoritmi bilan esa O(n) ga tushirish mumkin β yana brute force etalon, optimallashtirish keyin.) Tekshiruv:
def maksimal_qism_yigindi_brute(arr):
n = len(arr)
eng_yaxshi = arr[0]
for i in range(n):
for j in range(i, n):
joriy = sum(arr[i:j+1])
if joriy > eng_yaxshi:
eng_yaxshi = joriy
return eng_yaxshi
print(maksimal_qism_yigindi_brute([-2, 1, -3, 4, -1, 2, 1, -5, 4])) # -> 6
8-mashq yechimi¶
(a) n = 40: 2β΄β° = 1 099 511 627 776 β 1.1 Γ 10ΒΉΒ² amal. 10βΈ amal/sek tezlikda: ~1.1 Γ 10ΒΉΒ² / 10βΈ = ~11 000 sekund β ~3 soat.
(b) 1000 baravar tezlik β bu 2ΒΉβ° β 1000, demak biz qo'shimcha +10 ta n ni qoplay olamiz (2^(n+10) = 1024 Β· 2βΏ β 1000 Β· 2βΏ). Ya'ni n = 40 o'rniga n = 50 gacha (o'sha vaqtda). Atigi 10 ga ko'paydi!
(c) Yo'q. Ko'proq apparat eksponensial muammoni yechmaydi: tezlikni 1000x oshirsangiz ham n chegarasi faqat o'nlab birlik siljiydi. Eksponensial o'sishni faqat boshqa algoritm/paradigma (pruning, dinamik dasturlash, ...) yengadi.
9-mashq yechimi¶
Qaror qadamlari:
1. BRUTE FORCE YOZ.
- Tez yoziladi, to'g'riligi aniq. Bu birinchi ishlaydigan yechim VA reference.
2. n NING KATTALIGINI BAHOLA.
- n kichik (mas. brute O(2^n) da n <= 25, O(n!) da n <= 11,
O(n^2) da n <= ~10000) -> TO'XTA, brute force yetarli.
- n katta -> 3-qadamga o't.
3. OPTIMALLASHTIR (kerak bo'lsa).
- Strukturadan foydalan (hash, saralash), pruning (backtracking),
yoki aqlliroq paradigma (DP, divide&conquer, greedy).
4. TO'G'RILIKNI TEKSHIR.
- Tezlatilgan kodni KICHIK n larda brute force bilan solishtir.
- Ko'p tasodifiy kirishda ikkalasi bir xil javob bersa -> ishonch.
Brute force'ning ikki roli: (1) birinchi yechim β to'g'riligi kafolatlangan ishlaydigan kod; (2) reference (etalon) β keyinroq yozilgan tezroq kodning to'g'riligini kichik kirishlarda tasdiqlash uchun "haqiqat manbai". Aynan shuning uchun, hatto oxir-oqibat tashlab yuboriladigan bo'lsa ham, brute force yozishga arziydi.
β¬ οΈ Oldingi: 21 β Graflar: tasvirlash va Union-Find Β· π README Β· Keyingi: 23 β Divide and Conquer (bo'lib hal qil) β‘οΈ