飞机杯有什么用
Рекурзи?а (лат. recursio, recursion од recurrere: вра?а?е) у математици и информатици означава поступак или функци?у ко?и у сво?о? дефиници?и користе сами себе. Другим речима, уколико неки поступак захтева да делови проблема ко?е ?е раздво?ио од других бива?у независно подвргнути истом том поступку, та? поступак ?е рекурзиван.
Формалне дефиници?е рекурзи?е
[уреди | уреди извор]У математици и рачунарству, рекурзи?а одре?у?е (гради) класу об?еката или метода (или об?екат из одре?ене класе) дефиниса?ем неколико ?едноставних базних случа?ева или метода (често само ?едан), и дефиниса?ем правила како се сложени?и случа?еви своде на ?едноставни?е.
На пример, следи рекурзивна дефиници?а предака дате особе:
- Родите?и особе су му преци (базни случа?);
- Родите?и предака су тако?е преци особе (корак рекурзи?е ).
Згодно ?е сматрати да рекурзивна дефиници?а дефинише об?екте у односу на претходно дефинисане об?екте класе ко?а се дефинише.
Дефиници?е попут ове се често ?ав?а?у у математици. На пример, формална дефиници?а природног бро?а у теори?и скупова ?е: 1 ?е природан бро?, и сваки природан бро? има свог наследника, ко?и ?е тако?е природан бро?.
Следи ?ош ?едан, можда ?едноставни?и начин да се разуме?у рекурзивни процеси:
- Да ли имаш реше?е? Ако имаш, ?ави резултат. (Без оваквог услова за прекида?е, рекурзи?а би се вртела бесконачно).
- Ако не, по?едностави проблем, реши ?едноставни?и проблем (проблеме), и спо? резултате у реше?е почетног проблема. Затим ?ави резултат.
Ша?ива илустраци?а гласи Како би разумео рекурзи?у, човек прво мора да разуме рекурзи?у. Или можда тачни?е, од Ендруа Плоткина: Ако ве? знаш шта ?е рекурзи?а, запамти одговор. Ако не знаш, на?и некога ко сто?и ближе Дагласу Хофштатеру[1], и пита? ?ега шта ?е рекурзи?а.
Примери математичких об?еката ко?и се често дефинишу рекурзивно су функци?е, скупови, и посебно фрактали.
Рекурзивне дефиници?е у математици
[уреди | уреди извор]Рекурзивне дефиници?е су присутне у математици. Пример ?е следе?а дефиници?а природних бро?ева:
- 1 ?е природни бро?
- Ако ?е n природни бро?, онда ?е то и n+1.
Рекурзи?ом се дефинише и Фибоначи?ев низ:
- 1. члан низа ?е 0
- 2. члан низа ?е 1
- сваки n-ти члан низа (n>2) ?е сума претходна два члана ()
Рекурзивни алгоритми у програмира?у
[уреди | уреди извор]Битно ?е напоменути да у савременим програмским ?езицима попут C/C++ и ?аве свако рекурзивно реше?е неког проблема има и сво? итеративни еквивалент, т?. алгоритам ко?и исти проблем решава без рекурзи?е. У практичном програмира?у углавном треба избегавати рекурзи?у ?ер таква реше?а у општем случа?у троше више времена од итеративних.[тражи се извор]
Следи пар примера проблема ко?и су решени рекурзи?ом.
Вредност фактори?ела
[уреди | уреди извор]Фактори?ел ?е математичка функци?а ко?а се у едукативне сврхе често споми?е у контексту рекурзи?е. Фактори?ел природног бро?а n ?е производ ?ега самог и свих природних бро?ева ко?и су ма?и од ?ега:
При овоме важе закони комутативности множе?а над скупом природних бро?ева, те се исто може обав?ати у било ком редоследу. Може се почети од бро?а n па и?и уназад све до бро?а 1. Ево како би то изгледало у програмском ?езику C:
int fakt(int n)
{
if(n < 2) // Уколико ?е доби?ени бро? ма?и од два,
{ return 1; } // вратити 1.
else // У супротном,
{ return n*fakt(n-1); } // вратити тренутни бро? помножен са
// фактори?елом бро?а за ?едан ма?ег
// од ?ега.
}
У конкретном случа?у уколико би функци?а као аргумент добила бро? 5, рачун би се разви?ао на начин показан испод. Притом ?е рекурзивни позиви функци?а бити обележени заградама, да би се дочарао редослед почетака и завршетака ових функци?а.
fakt(5) = 5 · fakt(4)
= 5 · (4 · fakt(3)) // (?ер ?е fakt(4) = 4 · fakt(3))
= 5 · (4 · (3 · fakt(2))) // (?ер ?е fakt(3) = 3 · fakt(2))
= 5 · (4 · (3 · (2 · fakt(1)))) // (?ер ?е fakt(2) = 2 · fakt(1))
= 5 · (4 · (3 · (2 · 1))) // (?ер ?е fakt(1) = 1)
= 5 · (4 · (3 · (2 · 1))) // сада се множе?е ових вредности врши редом
= 5 · (4 · (3 · 2)) // ко?им се ф-?е завршава?у т?. уназад
= 5 · (4 · 6)
= 5 · 24
= 120
Сума низа ко?и се завршава нулом
[уреди | уреди извор]Рецимо да ?е дат низ целих бро?ева чи?у укупну суму треба одредити. ?едно рекурзивно реше?е ?е да се функци?и да?е сам низ и индекс од кога треба почети или наставити сабира?е, све док се не до?е до кра?а низа. До тада се претходно на?ене вредности акумулира?у на сличан начин као што ?е то горе приказано, напреду?у?и за по ?едан елемент приликом сваког рекурзивног позива. Ево како би ова? алгоритам био реализован у ?ави:
public static int sum(int[] niz, int indeks) {
if(indeks >= niz.length) { // Уколико се дошло до кра?а низа,
return 0; // рекурзи?а се прекида и вра?а се
} // нула ко?а представ?а неутрал за
// операци?у сабира?а.
return niz[indeks] + sum(niz, indeks+1); // У супротном, вра?а се сума елемента
// ко?и се налази на датом индексу и
// рекурзивног позива ове функци?е ко?и
// треба да израчуна суму свих елемената
// након ?ега
}
При чему се функци?а увек позива са низом као првим аргументом и нулом као почетним индексом низа.
Узевши да ?е дати низ на пример a = {11,12,13,14,15}, ова функци?а би рачун обавила на следе?и начин:
sum(a,0) = a[0] + sum(a,0+1)
= 11 + (a[1] + sum(a,1+1))
= 11 + (12 + (a[2] + sum(a,2+1)))
= 11 + (12 + (13 + (a[3] + sum(a,3+1))))
= 11 + (12 + (13 + (14 + (a[4] + sum(a,4+1)))))
= 11 + (12 + (13 + (14 + (15 + 0)))) // нема a[5], вра?а се нула
= 11 + (12 + (13 + (14 + 15)))
= 11 + (12 + (13 + 29))
= 11 + (12 + 42)
= 11 + 54
= 65
Ова? проблем има и друго реше?е. Аритметика показивача у ?езику C омогу?ава мало другачи?и приступ. Наиме, функци?а овде не мора да прима и низ, и индекс елемента да би приступила елементу. Дово?но ?о? ?е само дати показивач на тражени елемент. Како се овде увек тражи следе?и елемент, показивач на ?ега ?е лако добити из показивача на тренутни. Да би се имплементаци?а по?едноставила, узе?емо додатни услов да се низ завршава са нулом.
int asum(const int* p)
{
if(*p == 0) // Уколико ?е тренутно обра?ивани бро? нула,
{ return 0; } // рекурзи?а се прекида. Претходном резултату
// ?е бити додата вра?ена нула.
else // У супротном,
{ return *p + asum(p+1); } // се исти бро? сабира са следе?им и као такав
// вра?а претходним сабирцима.
}
Понаша?е ове функци?е ?е идентично понаша?у горенаведене, с том разликом што задати низ треба да буде a = {11,12,13,14,15,0}. Аргуменат при првом позиву функци?е ?е увек име низа, нпр. позив за низ a би био asum(a).
Неопходност нуле на кра?у низа се може избе?и дава?ем дужине низа функци?и.
Верижни разломци
[уреди | уреди извор]?едан од верижних разломака, ко?и се везу?е за вредност бро?а пи.
У овом изразу се разломци нижу ?едан испод другог, при чему се сваки уг?еж?у?е у делиоцу претходног. Притом се могу издво?ити два низа сабирка и де?еника ко?и припада?у сваком од ?их. Они би гласили овако:
- Сабирци: 1, 3, 5, 7, 9, 11, 13, ... = 2n - 1, n = 1, ...
- Де?еници: 1, 4, 9, 16, 25, 36, ... = n2, n = 1, ...
У оба низа се уочава?у правила: први ?е низ непарних бро?ева, а други низ квадратна функци?а природних бро?ева. Први начин представ?а?а овог низа би дакле био формира?е ових разломака по ?иховим редним бро?евима, из ко?их вредности посматраних елемената следе. Оно што ?е битно приметити ?е да се рекурзи?а не може ширити у бесконачност т?. треба ?е негде прекинути. С обзиром да са порастом редног бро?а разломка и бро?еви расту, и то на начин ко?и ума?у?е утица? сваког следе?ег разломка на целокупни резултат, рекурзи?а се може прекинути након одре?еног редног бро?а разломака. Пошто се ?едан разломак, као и сви ?егови следбеници, мора избацити, оста?е да се послед?и зак?учи само са непарним бро?ем, а остатак разломака приближно изрази нулом. Ово би на датом примеру изгледало овако:
Имплементаци?а овог реше?а у програмском ?езику C би изгледала овако:
double f1(int n) // Функци?а доби?а редни бро? разломка
{
if(n > 200) // Ако ?е редни бро? ве?и од 200,
{ return 2*n-1; } // рекурзи?а се прекида вра?а?ем само
// одговара?у?ег непарног бро?а.
else // У супротном
{
return // се вра?а збир
(2*n-1) // одговара?у?ег непарног бро?а,
+ n*n / f1(n+1); // и квадрата редног бро?а разломка,
} // поде?еног са следе?им разломком.
}
Ову функци?у треба увек позивати са аргументом n=1.
Често има више реше?а. Посматра?ем низова се може увидети и следе?а законитост, при исто? расподели на разломке.
Ако су код ?едног разломка сабирак a и де?еник b, код следе?ег ?е то бити a+2 и a+b+2.
Притом редни бро? разломка, ко?и ?е у овом случа?у n=a+1/2, не мора бити ?едини критери?ум за заустав?а?е рекурзи?е. Целобро?ни тип, ко?и ?е и у овом случа?у бити кориштен за обраду вредности a и b има сво?а ограниче?а у опсегу ко?и покрива, тако да би неконтролисани раст ових вредности дао погрешне резултате или изазвао пад програма. С обзиром да вредност b расте много брже од вредности a, дово?но ?е прекинути рекурзи?у када до?е до неке вредности за ко?у ?е сигурно да ?е достижна пре постиза?а недозво?ених вредности. Како и ни?е потребно превише разломака да би се ва?ани резултат сместио у дабл, тип реалних бро?ева, ова граница сме бити прилично ниско.
?една од могу?их имплементаци?а би била:
double f(int a, int b)
{
if(b > 1000) // Уколико промен?ива ''b'' премаши унапред
// задату вредност,
{ return a; } // рекурзи?а се прекида и вра?а се само ''a''.
else // У супротном, рекурзи?а се настав?а
{ return a + b/f(a+2,a+b+2); } // и вра?а се следе?и сабирак бесконачног низа,
// ко?и у себи садржи рекурзивни позив
}
Ова функци?а се увек мора позивати са аргументима a=1 и b=1
Референце
[уреди | уреди извор]- ^ Даглас Хофштатер ?е амерички академик ко?и ?е написао чувену к?игу Гедел, Ешер, Бах: Вечна златна плетеница.