Keling, funktsiyalarga qaytamiz va ularni yanada chuqurroq oârganamiz.
Bizning birinchi mavzuimiz rekursiya boâladi.
Agar siz dasturlash bilan yangi tanish boâlmasangiz, ehtimol u sizga tanish va siz ushbu bobni oâtkazib yuborishingiz mumkin.
Rekursiya â bu vazifa tabiiy ravishda bir nechta oâxshash, ammo oddiy vazifalarga boâlinishi mumkin boâlgan hollarda foydali boâlgan dasturiy taâminot usuli. Yoki vazifa oddiy ishlarga, shuningdek, bir xil vazifaning oddiy versiyasiga soddalashtirilishi mumkin. Yoki yaqinda koârib chiqamiz, muayyan maâlumotlar tuzilmalari bilan ishlash.
Funktsiya vazifani bajarganda, u boshqa koâplab funktsiyalarni chaqirishi mumkin. Buning qisman holati shundaki, funktsiya oâzini chaqiradi. Bunga rekursiya deyiladi.
Fikrlashning ikkita usuli
Biron bir oddiy narsadan boshlash uchun â keling, x ni n ning natural darajasiga koâtaradigan pow(x,n) funktsiyasini yozaylik. Boshqacha qilib aytganda, x oâz-oâzini n marta koâpaytiradi.
pow(2, 2) = 4
pow(2, 3) = 8
pow(2, 4) = 16
Uni amalga oshirishning ikki yoâli mavjud.
-
Takroriy fikrlash:
fortsikli:function pow(x, n) { let result = 1; // natijani ko'chadan x n marta ko'paytirish for (let i = 0; i < n; i++) { result *= x; } return result; } alert( pow(2, 3) ); // 8 -
Rekursiv fikrlash: vazifani soddalashtiring va oâzini chaqiring:
function pow(x, n) { if (n == 1) { return x; } else { return x * pow(x, n - 1); } } alert( pow(2, 3) ); // 8
Iltimos, rekursiv variant qanday fundamental farq qilishiga eâtibor bering.
pow(x,n) chaqirilganda, ijro ikkita shoxga boâlinadi:
if n==1 = x
/
pow(x, n) =
\
else = x * pow(x, n - 1)
- Agar
n == 1boâlsa, unda hamma narsa ahamiyatsiz. U rekursiyaning bazasi deb nomlanadi, chunki u darhol aniq natijani beradi:pow (x, 1)tengx. - Aks holda, biz
pow(x, n)nix * pow (x, n - 1)sifatida ifodalashimiz mumkin. Matematikadaxn = x * xn-1yozish mumkin. Bu rekursiv qadam deyiladi: biz vazifani oddiyroq harakatga aylantiramiz (xga koâpaytirish) va xuddi shu vazifani oddiyroq chaqiruviga (powpastroqnbilan). Keyingi qadamlar uni yanada soddalashtiradi van1ga yetguncha.
Shuni ham aytishimiz mumkinki, pow rekursiv ravishda oâzini n == 1 gacha chaqiradi.
Yoki masalan, pow(2, 4) ni hisoblash uchun rekursiv variant quyidagi bosqichlarni bajaradi:
pow(2, 4) = 2 * pow(2, 3)pow(2, 3) = 2 * pow(2, 2)pow(2, 2) = 2 * pow(2, 1)pow(2, 1) = 2
Shunday qilib, rekursiya funktsiya chaqiruvini sodda chaqiruvchiga, soângra yanada soddalashtirishga va hokazo natija aniq boâlguncha kamaytiradi.
Rekursiv yechim odatda takrorlanuvchiga qaraganda qisqaroq boâladi.
Bu yerda biz pow(x, n) funktsiya kodini yanada qisqartirish, ammo oson oâqilishi uchun if oârniga uchlik ? operatoridan foydalangan holda qayta yozishimiz mumkin:
function pow(x, n) {
return (n == 1) ? x : (x * pow(x, n - 1));
}
Ichki chaqiruvlarning maksimal soni (birinchisini ham qoâshganda) rekursiya chuqurligi deb nomlanadi. Bizning holatda, bu aniq n boâladi.
Maksimal rekursiya chuqurligi JavaScript interpretatori bilan cheklangan. 10000 ga yaqin ekanligiga ishonch hosil qilishimiz mumkin, baâzi interpretatorlar koâproq narsalarga imkon beradi, ammo 100000 ularning aksariyati uchun cheklangan boâlishi mumkin. Buni engillashtirishga yordam beradigan avtomatik optimallashtirishlar mavjud (âquyruq chaqiruvlar optimallashtirishâ), ammo ular hali hamma joyda qoâllab-quvvatlanmaydi va faqat oddiy holatlarda ishlaydi.
Endi rekursiv chaqiruvlar qanday ishlashini koârib chiqamiz. Buning uchun biz funktsiyalarning âqopqogâ ostiniâ koârib chiqamiz.
Funksiyaning ishlashi haqida maâlumot uning bajarilish kontekstida saqlanadi.
Ijro etish konteksti â bu funktsiyalarning bajarilishi toâgârisidagi maâlumotlarni oâz ichiga olgan ichki maâlumotlar tuzilishi: boshqaruv oqimi hozir boâlgan joyda, hozirgi oâzgaruvchanlar , this qiymati (biz bu yerda ishlatmaymiz) va boshqa bir nechta ichki tafsilotlar.
Bitta funktsiya chaqiruvi u bilan bogâliq boâlgan bitta ijro kontekstiga ega.
Funktsiya ichki chaqiruvni amalga oshirganda, quyidagilar sodir boâladi:
- Joriy funktsiya toâxtatildi.
- U bilan bogâliq boâlgan ijro konteksti ijro kontekst steki deb nomlangan maxsus maâlumotlar tuzilmasida eslab qolinadi.
- Ichki chaqiruv amalga oshiriladi.
- U tugagandan soâng, stekdan eski ijro konteksti olinadi va tashqi funktsiya toâxtagan joyidan tiklanadi.
Keling, pow(2, 3) chaqiruvi paytida nima sodir boâlishini koârib chiqaylik.
pow(2, 3)
pow(2, 3) chaqiruvining boshida bajarilish konteksti oâzgaruvchanlarni saqlaydi: x = 2, n = 3, ijro oqimi funktsiyaning 1 satrida.
Biz uni quyidagicha chizishimiz mumkin:
- Kontekst: { x: 2, n: 3, 1-satrda} pow(2, 3)
Ana shunda funktsiya bajarila boshlaydi. n == 1 sharti notoâgâri, shuning uchun oqim if ning ikkinchi shoxiga oâtadi:
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) );
Oâzgaruvchanlar bir xil, ammo satr oâzgaradi, shuning uchun kontekst hozir:
- Kontekst: { x: 2, n: 3, 5-satrda } pow(2, 3)
x * pow (x, n - 1) ni hisoblash uchun biz yangi pow(2, 2) argumentlari bilan pow subchaqiruvini qilishimiz kerak.
pow(2, 2)
Ichki chaqiruvni amalga oshirish uchun JavaScript ijro kontekst stekidagi joriy ijro kontekstini eslab qoladi.
Bu yerda biz xuddi shu funktsiyani pow deb ataymiz, ammo bu mutlaqo muhim emas. Jarayon barcha funktsiyalar uchun bir xil:
- Joriy kontekst stekning yuqori qismida âeslab qolinadiâ.
- Subchaqiruv uchun yangi kontekst yaratilinadi.
- Subchaqiruv tugagandan soâng â avvalgi kontekst stekdan chiqadi va uning bajarilishi davom etadi.
pow(2, 2) subchaqiruvga kirganimizda kontekst toâplami:
- Kontekst: { x: 2, n: 2, 1-satrda} pow(2, 2)
- Kontekst: { x: 2, n: 3, 5-satrda } pow(2, 3)
Yangi joriy ijro konteksti tepada (va qalin) va avval eslab olingan kontekstlar quyida keltirilgan.
Subchaqiruv tugatgandan soâng â avvalgi kontekstni davom ettirish oson, chunki u ikkala oâzgaruvchanni va kodning toâxtagan joyini saqlaydi. Bu yerda rasmda biz âsatrâ soâzini ishlatmoqdamiz, ammo bu aniqroq.
pow(2, 1)
Jarayon takrorlanadi: 5 satrda yangi subchaqiruv bajarilindi, endi x = 2, n = 1 argumentlari bilan.
Yangi ijro konteksti yaratildi, oldingisi stekning tepasiga surildi:
- Kontekst: { x: 2, n: 1, 1-satrda } pow(2, 1)
- Kontekst: { x: 2, n: 2, 5-satrda } pow(2, 2)
- Kontekst: { x: 2, n: 3, 5-satrda } pow(2, 3)
Hozirda 2 ta eski kontekst mavjud va joriy pow(2, 1) uchun 1 tasi ishlayapti.
Chiqish
pow(2, 1) ni bajarish paytida, avvalgidan farqli oâlaroq, n == 1 sharti haqiqatdir, shuning uchun if ning birinchi shoxi ishlaydi:
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
Boshqa ichki chaqiruvlar yoâq, shuning uchun funktsiya tugaydi va 2 qaytariladi.
Funktsiya tugagandan soâng, uning bajarilish konteksti endi kerak emas, shuning uchun u xotiradan oâchiriladi. Oldingisi stekning yuqori qismidan tiklanadi:
- Kontekst: { x: 2, n: 2, 5-satrda } pow(2, 2)
- Context: { x: 2, n: 3, 5-satrda } pow(2, 3)
pow(2, 2) ning ijrosi tiklandi. Bu pow(2, 1) subchaqiruvning natijasiga ega, shuning uchun u x * pow(x, n - 1) ni baholashni tugatib, 4 ni qaytaradi.
Soângra oldingi kontekst tiklanadi:
- Kontekst: { x: 2, n: 3, 5-satrda } pow(2, 3)
U tugagach, bizda pow(2, 3) = 8 natijasi boâladi.
Bu holda rekursiya chuqurligi: 3.
Yuqoridagi illyustratsiyalardan koârinib turibdiki, rekursiya chuqurligi stekdagi kontekstning maksimal soniga teng.
Xotira talablariga eâtibor bering. Kontekstlar xotirani oladi. Bizning holatda, n darajasiga koâtarish, aslida n kontekstlari uchun, n ning barcha pastki qiymatlari uchun xotira talab qiladi.
Tsiklga asoslangan algoritm xotirani tejashga koâproq mos keladi:
function pow(x, n) {
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
Qayta takrorlanadigan pow takrorlash jarayonida i va natija ni oâzgartiriladi va bitta kontekst ishlatilinadi. Uning xotiraga boâlgan talablari kichik, qatâiy va n ga bogâliq emas.
Har qanday rekursiyani tsikl sifatida qayta yozish mumkin. Tsikl variantini odatda samaraliroq qilish mumkin.
â¦Ammo baâzida qayta yozish ahamiyatsiz boâladi, ayniqsa funktsiya sharoitga qarab turli xil rekursiv subchaqiruvlardan foydalanganda va ularning natijalarini birlashtirganda yoki tarmoqlanish murakkabroq boâlganda. Va optimallashtirish kerak boâlmasligi va bu umuman harakatlarga loyiq emas boâlishi mumkin.
Rekursiya kodni qisqartirishi mumkin, uni tushunish va qoâllab-quvvatlash osonroq. Har bir joyda optimallashtirish talab qilinmaydi, asosan bizga yaxshi kod kerak, shuning uchun u ishlatiladi.
Rekursiv oâtish
Rekursiyaning yana bir ajoyib qoâllanilishi â bu rekursiv oâtish.
Tasavvur qiling, bizning kompaniyamiz bor. Xodimlar tarkibi obyekt sifatida taqdim etilishi mumkin:
let company = {
sales: [{
name: 'John',
salary: 1000
}, {
name: 'Alice',
salary: 1600
}],
development: {
sites: [{
name: 'Peter',
salary: 2000
}, {
name: 'Alex',
salary: 1800
}],
internals: [{
name: 'Jack',
salary: 1300
}]
}
};
Boshqacha qilib aytganda, kompaniyada boâlimlar mavjud.
-
Boâlimda bir massiv xodimlar boâlishi mumkin. Masalan,
savdoboâlimida ikkita xodim ishlaydi: Aziza va Elbek. -
Yoki kompaniya boâlinmalarga boâlinishi mumkin, masalan,
rivojlanishning ikkita shoxi mavjud:saytlarvaichki dasturals. Ularning har birida oâz shaxsiy xodimlari mavjud. -
Bundan tashqari, agar boâlinma oâsib chiqsa, u pastki boâlimlarga (yoki jamoalarga) boâlinishi mumkin.
Masalan, kelajakda saytlar boâlimi
siteAvasiteBuchun jamoalarga boâlinishi mumkin. Va ular, ehtimol, koâproq boâlinishi mumkin. Faqat bu rasmda emas, balki bu narsani yodda tutish kerak.
Keling, barcha ish haqlarining yigâindisini olish funktsiyasini xohlaymiz deylik. Buni qanday qilishimiz mumkin?
Takroriy yondashish oson emas, chunki tuzilish oddiy emas. Birinchi gâoya, birinchi darajali boâlimlar ustidan ichki subtsikl bilan for tsikl qilishdir. Ammo keyin biz saytlar kabi 2-darajali boâlimlarda ishlaydigan xodimlarni takrorlash uchun koâproq ichki subtsiklarga muhtojmiz. â¦Va keyin kelajakda paydo boâlishi mumkin boâlgan uchinchi darajali boâlimlar uchun yana bitta subtsiklmi? 3 darajasida toâxtashimiz kerakmi yoki 4 darajali tsikl qilishimiz kerakmi? Agar bitta obyektni ustidan takrorlash uchun kodga 3-4 ta ichki ichki tsiklni qoâysak, u juda xunuk boâladi.
Rekursiyani sinab koâraylik.
Koârib turganimizdek, bizning funktsiyamiz boâlimni yigâib olganda, ikkita holat mavjud:
- Yoki bu âoddiyâ odamlar massiviga ega boâlim â biz ish haqini oddiy tsiklda yigâishimiz mumkin.
- Yoki bu
Nboâlinmalariga ega boâlgan obyekt â biz subdepslarning har biri uchun yigâindini olish va natijalarni birlashtirish uchunNrekursiv chaqiruvlarini amalga oshirishimiz mumkin.
(1) â bu rekursiyaning asosi, muhim holat.
(2) â bu rekursiv qadam. Murakkab vazifa kichik boâlimlar uchun subvazifalrga boâlinadi. Ular oâz navbatida yana boâlinishi mumkin, ammo ertami-kechmi boâlinish (1) da tugaydi.
Algoritmni koddan oâqish yanada osonroq boâlishi mumkin:
let company = { // qisqartirish uchun siqilgan bir xil obyekt
sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 600 }],
development: {
sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
internals: [{name: 'Jack', salary: 1300}]
}
};
// Ishni bajarish funktsiyasi
function sumSalaries(department) {
if (Array.isArray(department)) { // hodisa (1)
return department.reduce((prev, current) => prev + current.salary, 0); // massivni yig'ish
} else { // hodisa (2)
let sum = 0;
for (let subdep of Object.values(department)) {
sum += sumSalaries(subdep); // rekursiv ravishda bo'limlarni chaqirish, natijalarni yig'ish
}
return sum;
}
}
alert(sumSalaries(company)); // 7700
Kod qisqa va tushunarli (umid qilaman:)). Rekursiyaning kuchi shunda. Bundan tashqari, har qanday ichki boâlimni joylashtirish uchun ishlaydi.
Chaqiruvlarning diagrammasi:
Biz printsipni osongina koârishimiz mumkin: obyekt uchun {...} subchaqiruvlari amalga oshiriladi, [...] massivlari esa rekursiya daraxtning âbarglariâ dir, ular darhol natija beradi.
Kodda biz ilgari koârib chiqqan aqlli xususiyatlardan foydalanilganligiga eâtibor bering:
- Massiv yigâindisini olish uchun Massiv usullari bobida tushuntirilgan
arr.reduceusuli. - Obyekt qiymatlari boâyicha takrorlash uchun
for(val of Object.values(obj)):Object.valuesularning massivini qaytaradi.
Rekursiv tuzilmalar
Maâlumotlarning rekursiv (rekursiv aniqlangan) tuzilishi â bu qismlarga boâlinib takrorlanadigan tuzilma.
Buni biz yuqoridagi kompaniya tuzilmasi misolida koârdik.
Kompaniya boâlimi bu:
- Yoki bir massiv odamlar.
- Yoki boâlimlarga ega boâlgan obyekt.
Veb-ishlab chiquvchilar uchun juda yaxshi maâlum boâlgan misollar mavjud: HTML va XML hujjatlar.
HTML-hujjatda HTML-teg quyidagilar roâyxatini oâz ichiga olishi mumkin:
- Matn qismlari.
- HTML-sharhlar.
- Boshqa HTML-teglar (bu oâz navbatida matn parchalarini/izohlarni yoki teglarni va boshqalarni oâz ichiga olishi mumkin).
Bu yana bir bor rekursiv taârif.
Yaxshi tushunish uchun biz âbogâlangan roâyxatâ deb nomlangan yana bir rekursiv tuzilmani koârib chiqamiz, bu baâzi hollarda massivlar uchun yaxshi alternativ boâlishi mumkin.
Bogâlangan roâyxat
Tasavvur qiling, biz tartib qilingan obyektlar roâyxatini saqlamoqchimiz.
Tabiiy tanlov massiv boâlishi mumkin:
let arr = [obj1, obj2, obj3];
â¦Ammo massivlarda muammo bor. âElementni oâchirishâ va âelementni kiritishâ operatsiyalari qimmatga tushadi. Masalan, arr.unshift(obj) operatsiyasi yangi obj ga joy ajratish uchun barcha elementlarning raqamlarini oâzgartirishi kerak va agar massiv katta boâlsa, vaqt talab etiladi. arr.shift() bilan bir xil.
Ommaviy raqamlarni oâzgartirishni talab qilmaydigan yagona tizimli oâzgartirishlar massiv oxirida ishlaydiganlardir: arr.push/pop. Shunday qilib, massiv katta navbatlarda juda sekin boâlishi mumkin.
Shu bilan bir qatorda, agar biz haqiqatan ham tezkor kiritish/oâchirishga muhtoj boâlsak, biz bogâlangan roâyxat deb nomlangan boshqa maâlumotlar tuzilishini tanlashimiz mumkin.
Bogâlangan roâyxat elementi quyidagicha obyekt sifatida rekursiv ravishda aniqlanadi:
value.nextkeyingi bogâlangan roâyxat elementiga havola qilingan xususiyat yoki agar bu oxiri boâlsanull.
Masalan:
let list = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null
}
}
}
};
Roâyxatning grafik tasviri:
Yaratish uchun muqobil kod:
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
list.next.next.next.next = null;
Bu erda biz bir nechta obâektlar borligini yanada aniqroq koârishimiz mumkin, ularning har biri qiymati va keyingi qoâshniga ishora qiladi. list oâzgaruvchisi zanjirning birinchi obyekti, shuning uchun undan keyingi koârsatgichlardan soâng istalgan elementga erishishimiz mumkin.
Roâyxat osongina bir nechta qismlarga boâlinishi va keyinchalik birlashtirilishi mumkin:
let secondList = list.next.next;
list.next.next = null;
Qoâshish:
list.next.next = secondList;
Va, albatta, biz har qanday joyga elementlarni qoâshishimiz yoki olib tashlashimiz mumkin.
Masalan, yangi qiymatni oldindan belgilash uchun roâyxatning bosh qismini yangilashimiz kerak:
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
// ro'yxatdagi yangi qiymatni oldindan belgilang
list = { value: "new item", next: list };
Oârtadan qiymatni olib tashlash uchun avvalgisining keyingisini oâzgartiring:
list.next = list.next.next;
list.next 1 dan 2 qiymatiga sakrab chiqdi. 1 qiymati endi zanjirdan chiqarildi. Agar u boshqa joyda saqlanmasa, u xotiradan avtomatik ravishda oâchiriladi.
Massivlardan farqli oâlaroq, ommaviy raqamlarni oâzgartirish mumkin emas, biz elementlarni osongina oâzgartiramiz.
Tabiiyki, roâyxatlar har doim ham massivlardan yaxshiroq emas. Aks holda hamma faqat roâyxatlardan foydalanar edi.
Asosiy kamchilik shundaki, biz elementga uning soniga koâra osonlikcha kira olmaymiz. Bu massivda oson erishiladi: arr[n] â bu toâgâridan-toâgâri maâlumotdir. Ammo roâyxatda biz birinchi elementdan boshlashimiz va N elementni olish uchun keyingi N marta oâtishimiz kerak.
â¦Lekin biz har doim bunday operatsiyalarga muhtoj emasmiz. Misol uchun, biz navbatga yoki hatto ikki tomonlama muhtojmiz â- bu har ikki uchidan ham elementlarni juda tez qoâshish/oâchirish imkonini beruvchi tartibli tuzilishdir, lekin oârtada kirishga iloji yoâq.
Baâzan roâyxatning oxirgi elementini kuzatib borish uchun quyruq nomli boshqa oâzgaruvchanni qoâshishga toâgâri keladi (elementlarni oxiriga qoâshish/olib tashlash va yangilash uchun). Katta elementlar toâplami uchun tezlik farqi massivga nisbatan katta.
Xulosa
Shartlar:
-
Rekursiya â bu âoâzini oâzi chaqirishâ funktsiyasini anglatadigan dasturlash atamasi. Bunday funktsiyalar yordamida baâzi vazifalarni nafis usullar bilan hal qilish mumkin.
Agar funktsiya oâzini oâzi chaqirsa, bu rekursiya qadami deb nomlanadi. Rekursiyaning asosi vazifani shu qadar soddalashtiradigan funktsiya argumentlari boâlib, funktsiya qoâshimcha chaqiruvlarni amalga oshirmaydi.
-
Rekursiv ravishda belgilangan maâlumotlar tuzilishi â bu oâzi yordamida aniqlanishi mumkin boâlgan maâlumotlar tuzilishi.
Masalan, bogâlangan roâyxat (yoki null) ga havola qilingan obyektdan tashkil topgan maâlumotlar tuzilishi sifatida aniqlanishi mumkin.
list = { value, next -> list }Ushbu bobdagi HTML elementlari daraxti yoki boâlim daraxti kabi daraxtlar ham tabiiy ravishda rekursivdir: ular shoxlanadi va har bir shox boshqa shoxlarga ega boâlishi mumkin.
Rekursiv funktsiyalar yordamida ularning ustida yurish uchun foydalanish mumkin, buni biz
sumSalarymisolida koârdik.
Har qanday rekursiv funktsiyani ketma-ket saraluvhanga qayta yozish mumkin. Va bu baâzida narsalarni optimallashtirish uchun talab qilinadi. Ammo koâpgina vazifalar uchun rekursiv yechim juda tez yozish va qoâllab-quvvatlash osonroq.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepenâ¦)