Dinamik dasturlash 2




![Yuqoridagi diagrammadan biz rekursiya davomida bir xil kichik masalalarni qayta-qayta
yechyapmiz, ya'ni Fib(3) 2 marta, Fib(2) 3 marta va hokazo. Agar n ning qiymatini yana
oshirsak, takrorlanuvchi kichik muammolar ham ortadi. Xuddi shu kichik muammoning
takroriy yechimi tufayli bizning vaqt murakkabligimiz n ning eksponensial tartibida.
Eslatma: Yuqoridagi rekursiyada faqat n+1 xil kichik muammolar mavjud. Dinamik
dasturlash g'oyasi
Xuddi shu kichik muammoni qayta-qayta hisoblash o'rniga, biz ushbu kichik muammoga
duch kelganimizda, ushbu kichik muammolarning echimini ba'zi xotirada saqlashimiz
mumkin. Boshqa kichik muammoni hal qilishda ushbu kichik muammo yana paydo
bo'lganda, biz allaqachon saqlangan yechimni xotiraga qaytaramiz. Bu Time-Memory Trade-
Off ning ajoyib g'oyasi bo'lib, biz vaqt murakkabligini yaxshilash uchun qo'shimcha joydan
foydalanamiz.
Keling, dinamik dasturlash yondashuvidan foydalangan holda samarali yechimni muhokama
qilaylik. Dinamik dasturlash masalalarini hal qilishning ikki xil usuli mavjud:
Yodlash: Yuqoridan pastga
Jadval: Pastdan yuqoriga
Keling, ushbu ikki atamani tushunaylik:
Yuqoridan pastga: Bu yuqoridagi rekursiv yondashuvning o'zgartirilgan versiyasi bo'lib, biz
qayta hisoblashdan qochish uchun qo'shimcha xotira yoki qidirish jadvalida kichik
muammolar yechimini saqlaymiz.
Pastdan yuqoriga: Bu kichik muammolar yechimini yaratish va yechimni qo'shimcha
xotirada saqlash uchun iterativ yondashuv.
Yechim g‘oyasi: Yuqoridan pastga yondashuv .
Yechim bosqichlari
Turli kichik muammolarning yechimini saqlash uchun biz global massiv yoki N+1
o'lchamdagi qidirish jadvalidan foydalanishimiz mumkin. deylik, F[N+1].
Massivdagi barcha qiymatlarni -1 ga boshlang (chunki Fibonachchi qiymati salbiy bo'lishi
mumkin emas). Bu bizga kichik muammoning yechimi allaqachon hisoblanganmi yoki
yo'qligini tekshirishga yordam beradi.
Rekursiv yechimda quyidagi o'zgartirishni amalga oshirishimiz mumkin:
Agar (F[N] < 0), bu N ning Fibonachchi qiymati hali hisoblanmaganligini anglatadi.
Yechimni rekursiv hisoblab, uni F[N] = fib(N-1) + fib(N-2) jadvalining N-indeksida
saqlashimiz kerak.
Agar (F[N] > 0), F[N] da saqlangan qiymatni qaytaring (chunki biz rekursiya davomida F[N]
ni allaqachon hisoblab chiqqanmiz).
N= 0 va N=1 asosiy holatlardir, chunki biz buning qiymatini allaqachon bilamiz va biz
to'g'ridan-to'g'ri echimni F[0] va F[1] da saqlashimiz mumkin.
5](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_5.png)
![Pseudo kod:
Initaize an array F[N+ 1 ] with negative values.
int fib (N)
{
if ( F[N] < 0 )
{
if ( N <= 1 )
F[N] = N
else
F[N] = fib(N- 1 ) + fib(N- 2 )
}
return F[N]
}
Murakkablik tahlili
Rekursiv qo'ng'iroqlarning umumiy soni = 2N-1, chunki biz qayta hisoblashlardan qochamiz
va N+1 kichik muammolarni faqat bir marta hal qilamiz.
Vaqt murakkabligi = O(N).
Kosmik murakkablik: kichik muammolar yechimini saqlash uchun qo'shimcha jadval uchun
O(N) .
Tanqidiy kuzatish
Rekursiya n o‘lchamli kattaroq masaladan 0 va 1 o‘lchamdagi asosiy rekursiyaga yuqoridan
pastga o‘tadi. Lekin u natijalarni pastdan yuqoriga qarab saqlaydi, bunda F[0] va F[1]
dastlabki ikkita qiymatdir. jadvalda saqlanadi.
Rekursiv chaqiruvlar tartibi: fib(n)-> fib(n-1)-> fib(n-2) ...-> fib(i)-> fib(i-1)-> fib(i-2) ...->
fib(2)-> fib(1)-> fib(0)
Natijalarni jadvalda saqlash tartibi: F[0]-> F[1]-> F[2] ...-> F[i]-> F[i-1]-> F[i-2] ...-> F[n-2]-
> F[n-1] ->F[n]
Agar siz n=5 uchun rekursiya daraxtini chizsangiz, u shunday ko'rinadi Murakkablik tahlili
Rekursiv qo'ng'iroqlarning umumiy soni = 2N-1, chunki biz qayta tiklanadigan hallardan
qochamiz va N+1 kichik faqat bir marta bajaramiz.
6](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_6.png)
![Tanqidiy savol: Shunday qilib, murakkab rekursiv yondashuvdan ko'ra, natijalarni jadvalda
saqlash uchun oddiy iterativ yondashuvdan foydalanishimiz mumkinmi? yoki 0 va 1
o'lchamdagi asosiy registrdan n o'lchamdagi kattaroq masalaga yechimni qura olamizmi?
Yechim g‘oyasi: pastdan yuqoriga yondashuv (jadval)Ushbu yondashuvda biz birinchi
navbatda kichikroq muammolarni hal qilamiz, so'ngra n o'lchamdagi kattaroq muammoni
hal qilish uchun kichikroq masalalar natijalarini birlashtiramiz.
Yechim bosqichlari
1. Jadval tuzilishi va hajmini aniqlang: kichikroq kichik muammolar yechimini pastdan
yuqoriga yondashuvda saqlash uchun jadval tuzilishi va uning hajmini aniqlashimiz
kerak.
2. Jadval tuzilishi: Jadval tuzilishi muammoli o'zgaruvchilar soni bilan aniqlanadi. Bu
erda muammoning holati bog'liq bo'lgan faqat bitta o'zgaruvchiga egamiz, ya'ni bu
erda n. (Har bir rekursiv chaqiruvdan keyin n qiymati kamayadi). Shunday qilib, biz
kichik muammolarning echimini saqlash uchun bir o'lchovli massivni qurishimiz
mumkin.
Jadval hajmi: Ushbu jadvalning o'lchami turli xil kichik muammolarning umumiy soni
bilan belgilanadi. Rekursiya daraxtini kuzatadigan bo'lsak, har xil o'lchamdagi jami (n+1)
kichik muammolar bo'lishi mumkin. (O'ylab ko'ring!)
2. Jadvalni asosiy registr bilan boshlang: Biz jadvalni asosiy registrlar yordamida ishga
tushirishimiz mumkin. Bu bizga jadvalni to'ldirishga va kattaroq kichik muammoning
echimini yaratishga yordam beradi. F[0] = 0 va F[1] = 1
3. Jadvalni to'ldirish uchun iterativ tuzilma: Rekursiv yechimning rekursiv tuzilishidan
foydalanib, jadvalni to'ldirish uchun iterativ tuzilmani aniqlashimiz kerak.
Rekursiv tuzilma: fib(n) = fib(n-1) + fib(n-2)
7](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_7.png)
![ Iterativ tuzilma: F[i] = F[i-1] + F[i-2]
4. Tugatish va yakuniy yechimni qaytarish: Yechimni pastdan yuqoriga qarab saqlagandan
so'ng, bizning yakuniy yechimimiz massivning oxirgi indeksida saqlanadi, ya'ni F[N] ni
qaytaradi.
Taqqoslash: Yodlash va Tabulyatsiya.
Kodlash qulayligi - Memoizatsiyani kodlash oson, ya'ni natijalarni saqlash uchun massiv
yoki qidirish jadvalini qo'shsangiz kifoya va hech qachon bir xil muammoning natijasini
qayta hisoblamaydi. Ammo jadvalda siz jadvalni to'ldirish uchun tartib yoki iterativ
tuzilmani o'ylab topishingiz kerak.
Tezlik - Memoizatsiya katta rekursiv qo'ng'iroqlar tufayli jadvalga qaraganda sekinroq
bo'lishi mumkin.
Stack Space - Memoizatsiya bilan, agar rekursiya daraxti juda chuqur bo'lsa, sizda stek
maydoni tugaydi, bu esa dasturingizni buzadi.
Kichik muammolarni hal qilish - Jadvalda barcha kichik muammolar kamida bir marta
echilishi kerak, lekin Yodlashda barcha muammolarni umuman hal qilish shart emas,
yodlangan yechim aniq talab qilinadigan kichik muammolarni hal qilishning afzalligiga ega.
Dinamik dasturlash tili yoki Dinamik muammo bilan adashtirmaslik kerak.
Shakl 1. Optimal quyi tuzilma yordamida grafikdagi eng qisqa yo'lni topish; to'g'ri chiziq
bitta qirrani ko'rsatadi; to'lqinli chiziq u bog'laydigan ikkita cho'qqi orasidagi eng qisqa yo'lni
ko'rsatadi (boshqa yo'llar orasida, ko'rsatilmagan, bir xil ikkita cho'qqilarni baham ko'radi);
qalin chiziq boshidan maqsadgacha bo'lgan umumiy eng qisqa yo'ldir.
Dinamik dasturlash ham matematik optimallashtirish usuli, ham kompyuter dasturlash
usulidir. Usul 1950-yillarda Richard Bellman tomonidan ishlab chiqilgan va aerokosmik
muhandislikdan iqtisodiyotgacha bo'lgan ko'plab sohalarda qo'llanilishini topdi.
Ikkala kontekstda bu murakkab muammoni rekursiv usulda oddiy kichik muammolarga
bo'lish orqali soddalashtirishga ishora qiladi. Ba'zi qarorlar bilan bog'liq muammolarni bu
tarzda ajratib bo'lmasa-da, bir necha vaqt oralig'ida bo'lgan qarorlar ko'pincha rekursiv
ravishda ajralib chiqadi. Xuddi shunday, kompyuter fanida, agar muammoni kichik
muammolarga bo'lish va keyin kichik muammolarning optimal echimlarini rekursiv ravishda
topish yo'li bilan optimal tarzda echilishi mumkin bo'lsa, u optimal quyi tuzilishga ega
deyiladi.
Agar kichik muammolar kattaroq muammolar ichida rekursiv tarzda joylashtirilsa, dinamik
dasturlash usullari qo'llanilishi mumkin bo'lsa, u holda kattaroq muammoning qiymati va
kichik muammolarning qiymatlari o'rtasida bog'liqlik mavjud.[1] Optimallashtirish
adabiyotida bu munosabat Bellman tenglamasi deb ataladi.
Matematik optimallashtirish
8](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_8.png)
![Matematik optimallashtirish nuqtai nazaridan, dinamik dasturlash odatda qarorni vaqt o'tishi
bilan qaror qabul qilish bosqichlari ketma-ketligiga bo'lish orqali soddalashtirishni anglatadi.
Bu V1, V2, ..., Vn qiymat funksiyalari ketma-ketligini aniqlash orqali y ni 1 dan n gacha
bo‘lgan vaqtlarda tizim holatini ifodalovchi argument sifatida amalga oshiriladi. Vn (y) ning
ta'rifi oxirgi n vaqtida y holatida olingan qiymatdir. Oldingi Vi i = n -1, n - 2, ..., 2, 1
qiymatlarini Bellman tenglamasi deb ataladigan rekursiv munosabatdan foydalanib, orqaga
qarab ishlash orqali topish mumkin. i = 2, ..., n, Vi−1 uchun har qanday holatda y Vi dan i −
1 vaqtdagi qarordan olinadigan daromadning oddiy funksiyasini (odatda yig‘indisini) va
yangi vaqtda Vi funksiyasini maksimallashtirish yo‘li bilan hisoblanadi. agar ushbu qaror
qabul qilingan bo'lsa, tizimning holati. Kerakli holatlar uchun Vi allaqachon hisoblanganligi
sababli, yuqoridagi operatsiya ushbu holatlar uchun Vi-1 hosil qiladi. Nihoyat, tizimning
dastlabki holatidagi V1 optimal yechimning qiymati hisoblanadi. Qaror o'zgaruvchilarining
optimal qiymatlari allaqachon bajarilgan hisob-kitoblarni kuzatish orqali birma-bir tiklanishi
mumkin.
1.3 Dinamik dasturlash tarixi .
Dinamik dasturlash atamasi dastlab 1940-yillarda Richard Bellman tomonidan
birin-ketin eng yaxshi qarorlarni topish kerak bo'lgan muammolarni hal qilish
jarayonini tasvirlash uchun ishlatilgan. 1953 yilga kelib, u buni zamonaviy
ma'noga aylantirdi, xususan, kichikroq qarorlar muammolarini kattaroq qarorlar
ichiga joylashtirishga ishora qildi [16] va keyinchalik bu soha IEEE tomonidan
tizim tahlili va muhandislik mavzusi sifatida tan olindi. Bellmanning hissasi
Bellman tenglamasi nomi bilan esga olinadi, bu dinamik dasturlashning markaziy
natijasi bo'lib, optimallashtirish masalasini rekursiv shaklda qayta ko'rsatadi.
Bellman o‘zining “Bo‘ron ko‘zi: avtobiografiya” nomli avtobiografiyasida
dinamik dasturlash atamasining sabablarini tushuntiradi:
Men kuz choragini (1950 yil) RANDda o'tkazdim. Mening birinchi vazifam ko'p
bosqichli qaror qabul qilish jarayonlari uchun nom topish edi. Qiziqarli savol:
"Dinamik dasturlash nomi qaerdan paydo bo'lgan?" 1950-yillar matematik
tadqiqotlar uchun yaxshi yillar emas edi. Vashingtonda Uilson ismli juda qiziq
bir janobimiz bor edi. U Mudofaa vaziri edi va u aslida "tadqiqot" so'zidan
patologik qo'rquv va nafratga ega edi. Men bu atamani oddiy ishlatmayapman;
Men uni aniq ishlataman. Agar odamlar uning huzurida tadqiqot iborasini
ishlatsalar, uning yuzi to'lib-toshgan, qizarib ketgan va zo'ravon bo'lardi. U
matematika atamasi haqida qanday his qilganini tasavvur qilishingiz mumkin.
RAND korporatsiyasi Harbiy havo kuchlarida ishlagan va havo kuchlari Uilsonni
o'zining boshlig'i sifatida egallagan. Shunday qilib, men RAND korporatsiyasida
haqiqatan ham matematika bilan shug'ullanayotganimdan Uilson va havo
kuchlarini himoya qilish uchun nimadir qilishim kerakligini his qildim. Qanday
nom, qanday ismni tanlashim mumkin? Meni birinchi navbatda rejalashtirish,
9](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_9.png)

![2-bob.Rekursiya va dinamik dasturlashni taqqoslash .
2. 1 .Rekursiya va dinamik dasturlashni kiritish.
Rekursiya - bu asosiy holatlarga erishilgunga qadar funktsiya o'zini chaqiradigan jarayon.
Va jarayon davomida murakkab vaziyatlar rekursiv ravishda kuzatiladi va sodda va sodda
bo'ladi. Jarayonning butun tuzilishi daraxtga o'xshaydi. Rekursiya yakuniy bosqichga
etgunga qadar hech qanday qiymatni saqlamaydi (asosiy holat).
Va Dinamik dasturlash asosan oddiy rekursiya bilan solishtirganda optimallashtirishdir.
Asosiy g'oya - asl savolni takrorlanadigan naqshlarga ajratish va keyin natijalarni ko'p sub-
javoblarni saqlash. Shuning uchun, keyin kerak bo'lganda, biz bosqichdan oldingi javoblarni
qayta hisoblashimiz shart emas. Katta O nuqtai nazaridan, ushbu optimallashtirish usuli
odatda vaqt murakkabligini eksponensialdan polinomga qisqartiradi.
Rekursion/dinamik dasturlash skriptini qanday yozish kerak
Dinamik dasturlash va rekursiya juda o'xshash
Rekursiya ham, dinamik dasturlash ham boshlang'ichni ishga tushiradigan asosiy holatdan
boshlanadi.
2. Asosiy holatni yozganimizdan so'ng, muammoning mantiqiy oqimidan keyin har qanday
naqshlarni topishga harakat qilamiz. Uni topganimizdan so'ng, biz asosan tugatdik.
3. Asosiy farq shundaki, rekursiya uchun biz hech qanday oraliq qiymatlarni saqlamaymiz,
dinamik dasturlash esa undan foydalanadi.
Keling, Fibonachchi raqami bilan biroz chuqurroq tanishaylik. Odatda F(n) bilan belgilangan
Fibonachchi raqamlari Fibonachchi ketma-ketligi deb ataladigan ketma-ketlikni hosil qiladi,
shunday qilib har bir raqam 0 va 1 dan boshlab oldingi ikkitasining yig'indisidir. Ya'ni,
F[0] = 0 birinchi raqam sifatida
F[1] = 1 bizning ikkinchi raqamimiz sifatida
Va undan keyingi raqamni hisoblash oson:
F[n] = F[n-1] + F[n-2]
Berilgan n bilan F[n] ni qanday topish mumkin?
2.1 Misollar
Agar bu Fibonachchi raqami deb nomlangan narsani birinchi marta eshitayotgan bo'lsa,
tashvishlanmang, bu savolni tushunish uchun bir nechta oson misollar:
Berilgan n = 2, F[2] = F[1] + F[0] = 0 + 1 = 1
Berilgan n = 3, F[3] = F[2] + F[1] = 1+ 2= 3
11](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_11.png)
![ Ikki yondashuv
Rekursion yondashuv
Rekursiya yondashuvidan boshlaylik.
Yuqoridagi koddan shuni ko'rishimiz mumkinki, biz qiladigan birinchi narsa har doim asosiy
holatni qidirishdir.
Bunday holda, asosiy holat F[0] = 0 va F[1] = 1 bo'ladi, effektga erishish uchun biz bu ikki
shartni if ostida aniq yozdik.
Va asosiy holatdan so'ng, keyingi qadam, F[n] keyinchalik qanday hosil bo'lishining umumiy
modeli haqida o'ylashdir. Yaxshiyamki, muammo so'rovi allaqachon bizga naqsh berdi:
F[n] = F[n-1] + F[n-2]
Shuning uchun biz shunchaki F(n) = F(n-1) + F(n-2) natijasini qaytarishimiz mumkin.
Yuqoridagi koddan shuni ko'rishimiz mumkinki, biz qiladigan birinchi ish yana asosiy
holatni qidirishdir.
Bunday holda, asosiy holat F[0] = 0 va F[1] = 1 bo'ladi, effektga erishish uchun biz bu ikki
shartni if ostida aniq yozdik.
Asosiy holatni tugatganimizdan so'ng, biz tezroq hisoblash uchun barcha oraliq va
vaqtinchalik natijalarni saqlash uchun bo'sh dinamik dasturlash massivini yaratamiz. n 0 dan
boshlangani uchun biz n+1 uzunlikdagi ro'yxat tuzamiz.
Keyingi qadam, keyinchalik F [n] qanday hosil bo'lishining umumiy modeli haqida
o'ylashdir. Yaxshiyamki, muammo so'rovi allaqachon bizga naqsh berdi:
F[n] = F[n-1] + F[n-2]
Shuning uchun biz shunchaki F(n) = F(n-1) + F(n-2) natijasini qaytarishimiz mumkin. Va
aniqroq bo'lish uchun:
dp[i] = dp[i-2] + dp[i-1]
Va bu aslida rekursiya bilan alohida dinamik dasturlashning asosiy farqidir. Rekursiyada biz
dinamik dasturlashda oraliq natijalarni saqlamaymiz, biz barcha oraliq bosqichlarni
saqlaymiz.
F[4] ni hisoblash uchun biz avval F[2], F[3] ni hisoblaymiz va ularning qiymatini oldindan
tuzilgan DP ro'yxatiga joylashtiramiz.
12](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_12.png)


![Dinamik dasturlash yordamida Fibonachchi seriyasi
Faqat yuqoridagi rasmga qarang. Rekursiyada ko'pgina qiymatlar fib (4) kabi qayta-qayta
hisoblab chiqiladi.
Bu 4 uchun Fibonachchi qiymatini hisoblash uchun qo'shimcha ishlov berish xarajatlarini
beradi.
Agar fib(4) uchun hisoblangan qiymatni saqlasak va keyingi safar undan foydalansak nima
bo'ladi?
Bu yerda dinamik dasturlash keladi:
1
2
3
4
5
6
7
8
9
1
0
1
1
1
2 include<stdio.h>
int fib ( int n) {
arrFib[100] = {0};
arrFib[0] = 1;
arrFib[1] = 1;
for ( int i = 2; i<n; i++)
arrFib[i] = arrFib[i-1] + arrFib[i-2];
return arrFib[n-1]
}
void main() {
printf ( "The Fibonacci number for index 6 = %d" ,fib(6));
}
Chiqish:
6 = 8 indeks uchun Fibonachchi raqami
Funktsiyani rekursiv chaqirish o'rniga, biz Fibonachchi seriyasining qiymatini hisoblaymiz
va uni ma'lumotlar bazasi massivida saqlaymiz (memoizatsiya texnikasi).
Bu yerda Dinamik dasturlashda biz xotira maydonini ishlov berish vaqtiga almashtiramiz.
Rekursiya va dinamik dasturlash o'rtasidagi farq
Ushbu ikkita dasturlash atamasi o'rtasidagi farq nima?
Agar siz Fibonachchi dasturining yakuniy natijasiga qarasangiz, rekursiya ham, dinamik
dasturlash ham bir xil ishlarni bajaradi.
Ammo mantiqan ikkalasi ham dasturning amalda bajarilishida farq qiladi.
Rekursiyaga nisbatan dinamik dasturlashning afzalliklari
Bu rekursiv dasturlash texnikasi bo'lgani uchun u chiziq kodini kamaytiradi.
15](/data/documents/98be2372-d781-4fa5-abab-db404a8f81f4/page_15.png)








MUNDARIJA Kirish………………………………………………………………………… 2 1-bob. Dinamik dasturlash g’oyasi…………………………………………. 3 1.1 Yechim g‘oyasi: Rekursiv yondashuv………………………………3 1.2 Taqqoslash: Yodlash va Tabulyatsiya……………………………… 8 1.3 Dinamik dasturlash tarixi.....................................................................9 2-bob. Dinamik dasturlash va rekursiyani taqqoslash ...................................10 2.1 Rekursiya va dinamik dasturlashni kiritish ………………………….. 10 2.2 Dinamik dasturlash haqida…………………………………………..11 2.3 Rekursiya haqida ma’lumot………………………………………….1 2 Xulosa………………………………………………………………………….. 20 Foydalanilgan adabiyotlar…………………………………………………… .21 1
Kirish Ushbu blogning g'oyasi dinamik dasturlash deb ataladigan yangi algoritmni loyihalash texnikasini muhokama qilishdir. Bu kompyuter fanida muammolarni hal qilishda muhim yondashuv bo'lib, biz samaradorlikni oshirish uchun vaqt-xotira almashinuvi g'oyasidan foydalanamiz. Hatto ko'plab texnologiya kompaniyalari o'z intervyularida DP savollarini berishni yaxshi ko'radilar. Ushbu blogda biz DP bilan bog'liq quyidagi tushunchalarni o'rganamiz: n-Fibonachchini topishning rekursiv yechimi Vaqt xotirasi almashinuvidan foydalangan holda samaradorlikni oshirish Yuqoridan pastga amalga oshirish (yodlash) Pastdan yuqoriga amalga oshirish (jadval) Taqqoslash: Yuqoridan pastga va Tabulyatsiya Keyinchalik o'rganish uchun tanqidiy tushunchalar Yechish uchun tavsiya etilgan muammolar Keling, birinchi navbatda n-Fibonachchini topish muammosini muhokama qilaylik. Rekursiya - bu asosiy holatlarga erishilgunga qadar funktsiya o'zini chaqiradigan jarayon. Va jarayon davomida murakkab vaziyatlar rekursiv ravishda kuzatiladi va sodda va sodda bo'ladi. Jarayonning butun tuzilishi daraxtga o'xshaydi. Rekursiya yakuniy bosqichga etgunga qadar hech qanday qiymatni saqlamaydi (asosiy holat). Va Dinamik dasturlash asosan oddiy rekursiya bilan solishtirganda optimallashtirishdir. Asosiy g'oya - asl savolni takrorlanadigan naqshlarga ajratish va keyin natijalarni ko'p sub- javoblarni saqlash. Shuning uchun, keyin kerak bo'lganda, biz bosqichdan oldingi javoblarni qayta hisoblashimiz shart emas. Katta O nuqtai nazaridan, ushbu optimallashtirish usuli odatda vaqt murakkabligini eksponensialdan polinomga qisqartiradi. 2
1-bob. Dinamik dasturlash g’oyasi 1.1 Yechim g‘oyasi: Rekursiv yondashuv Yuqoridagi takrorlanish munosabati yordamida masalani rekursiv hal qilishimiz mumkin. Bu erda biz n o'lchamdagi masalani o'lcham (n-1) va o'lcham (n-2) kichik masala yechimi yordamida hal qilamiz, bu yerda F(0) va F(1) asosiy holatlardir . Pseudo kod: int fib (n) { if (n == 0 ) return 0 else if (n == 1 ) return 1 else return fib(n- 1 ) + fib(n- 2 ) } Murakkablik tahlili: Yuqoridagi kod birinchi qarashda oddiy ko'rinadi, lekin u haqiqatan ham samarasiz. Keling, uni tahlil qilamiz va samarasizligining sababini tushunamiz. Mana rekursiya daraxti diagrammasi: 3
Rekursiya daraxti usulida rekursiv qo'ng'iroqlarning umumiy soni = Rekursiya daraxtidagi tugunlarning umumiy soni. Xuddi shunday, agar biz rekursiya daraxtida k darajaga ega bo'lsak, u holda rekursiya bo'yicha bajariladigan operatsiyalarning umumiy soni = L1 + L2 + L3 ......Lk, bu erda har qanday Li - i-darajadagi operatsiyalarning umumiy soni. rekursiya daraxti. Agar diqqat bilan kuzatadigan bo'lsak, quyidagi narsalarni sanab o'tishimiz mumkin: Biz har bir rekursiv chaqiruvda O (1) operatsiyasini bajaramiz. Rekursiv qo'ng'iroqlarning umumiy soni eksponent ravishda o'sib bormoqda, chunki biz daraxtda pastga qarab harakatlanamiz, ya'ni 2, 4, 8, 16 ... va hokazo. Har bir barg tugunining balandligi (n/2, n) oralig'ida. Shunday qilib, rekursiya daraxtining umumiy balandligi = O(n) = cn, deb aytishimiz mumkin, bu erda c qandaydir doimiydir. (O'ylab ko'ring!) Rekursiv qo'ng'iroqlarning umumiy soni = 1 + 2 + 4 + 8 ...+ 2^cn = (2^cn - 1) / (2 - 1) (Geometrik qatorlarning yig'indisi formulasidan foydalanish) = O(2^n) Vaqt murakkabligi = O(2^n). Bu samarasiz algoritm, chunki vaqt murakkabligi kirish hajmiga (n) nisbatan eksponent ravishda o'sib bormoqda. Umuman olganda, 30 yoki 50 yoki 70 kabi kichik n qiymati uchun mahsulot ishlab chiqarish juda uzoq vaqt talab etadi. Keling, 5-Fibonachchini topish uchun rekursiya daraxti yordamida sababni o'rganamiz. 4
Yuqoridagi diagrammadan biz rekursiya davomida bir xil kichik masalalarni qayta-qayta yechyapmiz, ya'ni Fib(3) 2 marta, Fib(2) 3 marta va hokazo. Agar n ning qiymatini yana oshirsak, takrorlanuvchi kichik muammolar ham ortadi. Xuddi shu kichik muammoning takroriy yechimi tufayli bizning vaqt murakkabligimiz n ning eksponensial tartibida. Eslatma: Yuqoridagi rekursiyada faqat n+1 xil kichik muammolar mavjud. Dinamik dasturlash g'oyasi Xuddi shu kichik muammoni qayta-qayta hisoblash o'rniga, biz ushbu kichik muammoga duch kelganimizda, ushbu kichik muammolarning echimini ba'zi xotirada saqlashimiz mumkin. Boshqa kichik muammoni hal qilishda ushbu kichik muammo yana paydo bo'lganda, biz allaqachon saqlangan yechimni xotiraga qaytaramiz. Bu Time-Memory Trade- Off ning ajoyib g'oyasi bo'lib, biz vaqt murakkabligini yaxshilash uchun qo'shimcha joydan foydalanamiz. Keling, dinamik dasturlash yondashuvidan foydalangan holda samarali yechimni muhokama qilaylik. Dinamik dasturlash masalalarini hal qilishning ikki xil usuli mavjud: Yodlash: Yuqoridan pastga Jadval: Pastdan yuqoriga Keling, ushbu ikki atamani tushunaylik: Yuqoridan pastga: Bu yuqoridagi rekursiv yondashuvning o'zgartirilgan versiyasi bo'lib, biz qayta hisoblashdan qochish uchun qo'shimcha xotira yoki qidirish jadvalida kichik muammolar yechimini saqlaymiz. Pastdan yuqoriga: Bu kichik muammolar yechimini yaratish va yechimni qo'shimcha xotirada saqlash uchun iterativ yondashuv. Yechim g‘oyasi: Yuqoridan pastga yondashuv . Yechim bosqichlari Turli kichik muammolarning yechimini saqlash uchun biz global massiv yoki N+1 o'lchamdagi qidirish jadvalidan foydalanishimiz mumkin. deylik, F[N+1]. Massivdagi barcha qiymatlarni -1 ga boshlang (chunki Fibonachchi qiymati salbiy bo'lishi mumkin emas). Bu bizga kichik muammoning yechimi allaqachon hisoblanganmi yoki yo'qligini tekshirishga yordam beradi. Rekursiv yechimda quyidagi o'zgartirishni amalga oshirishimiz mumkin: Agar (F[N] < 0), bu N ning Fibonachchi qiymati hali hisoblanmaganligini anglatadi. Yechimni rekursiv hisoblab, uni F[N] = fib(N-1) + fib(N-2) jadvalining N-indeksida saqlashimiz kerak. Agar (F[N] > 0), F[N] da saqlangan qiymatni qaytaring (chunki biz rekursiya davomida F[N] ni allaqachon hisoblab chiqqanmiz). N= 0 va N=1 asosiy holatlardir, chunki biz buning qiymatini allaqachon bilamiz va biz to'g'ridan-to'g'ri echimni F[0] va F[1] da saqlashimiz mumkin. 5