Documente Academic
Documente Profesional
Documente Cultură
Generari combinatoriale
RECURSIVITATE
Definire şi exemple
Să ne imaginăm un copac. O ramura a copacului este formată din mai multe ramuri,
fiecare la rândul ei fiind formată din ramuri mai mici, acestea sunt formate din ramuri
şi mai mici ... şi aşa mai departe pană când ajungem la cele mai mici ramuri, fiecare
fiind formată dintr-o tulpină şi mai multe frunze. Am descris ramura unui copac
folosind-ne de aceeaşi noţiune, ramura, până când am ajuns la cele mai mici elemente
care o descriu, tulpina si frunzele.
Matematica modelează şi exprimă prin relaţii şi formule natura, aşa că regăsim şi
aici astfel de definiţii, numite „relaţii de recurenţa”. De exemplu, pentru a calcula
factorialul unui număr, procedăm astfel:
n! = n * (n-1) * (n-2) * ... 2 * 1
Putem scrie deci că
n!= 1 , dacă n=1 şi
n!=n*(n-1)! , dacă n>1 .
Să observăm din exemplul de mai sus următoarele:
Ultimul nivel al definiţiei, să-l numim nivel elementar (n=1), trebuie să fie
explicitat.
In cadrul definiţiei unei noţiuni apare însăşi noţiunea care se defineşte.
Se citesc pe rând literele unui cuvânt, urmate de caracterul spatiu, care nu face parte
din cuvânt. Să se afişeze caracterele în ordinea inversă citirii.
#include <iostream>//FACTORIAL
using namespace std;
int fact(int x)
{if (x<=1) //condiţia de oprire
return 1;
else
return (x*fact(x-1)); //autoapelul functiei
}
int main()
{ int n;
cin>>n;
cout<<"n!="<<fact(n); //apelul functiei
}
STIVA
x=1 nivelul 5
x=2 nivelul 4
x=3 nivelul 3
x=4 nivelul 2
x=5 nivelul 1
Priviţi codul programului FACTORIAL, de mai sus. Presupunem că dorim să
calculăm valoarea lui 5!, prin urmare n=5. Să observăm că funcţia are parametrul
transmis prin valoare, x. Prin urmare x se va memora pe fiecare nivel al stivei,
întocmai ca variabila locală lit din programul precedent. Conform programului de mai
sus, valoarea funcţiei se calculează astfel:
pe nivelul 1: x=5 se reapelează fact(4)
pe nivelul 2: x=4 se reapelează fact(3)
pe nivelul 3: x=3 se reapelează fact(2)
Stabilim ce se execută la o anumită etapă, apoi reluăm procedeul pentru toate celelalte
elemente rămase, prin reapelarea funcţiei, având grijă să impunem condiţii de oprire
ale reapelării funcţiei.
#include<iostream> //ŞIRURI
using namespace std;
double an(int n);
double bn(int n);
double a,b;
int main()
{int n;
cin>>a;
cin>>b;
cin>>n;
cout<<"an("<<n<<")="<<an(n)<<" bn("<<n<<")="<<bn(n);
//apelul funcţiilor an() şi bn();
}
double an(int n)
{if(n>0)
return (an(n-1)+bn(n-1))/2; //funcţia an() se autoapelează
//direct, dar se autoapelează şi
//indirect prin intermediul
//funcţiei bn()
else
return a;
}
double bn(int n)
{if(n>0)
return sqrt(an(n-1)*bn(n-1)); //funcţia bn() se autoapelează
//direct, dar se autoapelează şi
//indirect prin intermediul funcţiei
//an();
else
return a;
}
În principiu, orice algoritm poate fi elaborat atât recursiv cât şi iterativ. O funcţie
recursivă se reapelează de mai multe ori. Aceasta înseamnă un salt în program, de
fiecare dată când se face un reapel, prin urmare consum de timp. La fiecare reapel al
funţiei pe segmentul de stivă alocat funcţiei, se crează un nou nivel, unde se
memorează parametri şi variabilele locale. Acest lucru înseamnă consum de memorie,
dar segmentul de stivă este oricum rezervat. Observăm deci că o funcţie recursivă
consumă mai mult timp, prin reapelări succesive şi mai multă memorie decât o funcţie
iterativă. Acest lucru afectează programul doar în momentul în care se efectuează un
număr foarte mare de reapelări, ceea ce ar putea duce şi la ocuparea totală a
segmentului de stivă, caz în care programul se întrerupe cu eroare( un exemplu in
acest sens este calculul recursiv al unui termen de rang n din sirul lui Fibonacci)
Pe de altă parte o funcţie recursivă se scrie cu mai multă uşurinţă, codul sursă
poate fi mult restrâns, este mai naturală şi mai uşor de urmărit.
Anagrame
Soluţie
Pentru a verifica dacă cele două şiruri sunt anagrame vom proceda asfel:
a. ) Dacă cele două şiruri au lungimi diferite, nu pot fi anagrame.
b. ) Dacă au aceeaşi lungime, vom căuta primul caracter din şirul x în al şirul y.
Dacă îl găsim, vom şterge acest caracter atât din şirul x cât şi din şirul y. Reluăm
procedeul până când cele două şiruri devin vide.
c. ) În cazul în care caracterul din x nu se găseşte în y, înseamnă că şirurile nu
sunt anagrame.
#include <iostream>///ANAGRAME
#include <cstring>
using namespace std;
int anagrama(char x[50],char y[50]);
int main()
{
char x[50],y[50];
cin>>x;
cin>>y;
if (strlen(x)!=strlen(y))
cout<<"sirurile nu au ac lungime";
else if (anagrama(x,y)) ///apelul funcţiei
cout<<"sirurile sunt anagrame";
else cout<<"sirurile nu sunt anagrame";
}
int anagrama(char x[50],char y[50])
{
char *p;
if ((strcmp(x,"")==0)&&(strcmp(y,"")==0))
return 1;
p=strchr(y,x[0]); ///se cauta prima litera din x in y
if (p==0) return 0;
strcpy(x,x+1); ///daca se gaseste, se sterge din x si
///din y
strcpy(p,p+1);
return anagrama(x,y); ///autoapelarea funcţiei
}
MORSE
Soluţie
{
if(i>n) ///dacă i a ajuns la n, înseamnă ca am completat
///toate cele n semne
afisare();
else
{
a[i]='.'; ///punem un '.' pe poziţia i a vectorului
generare(i+1); ///prin apeluri recursive,
GENERARI COMBINATORIALE
Permutări
Soluţie
#include <iostream>
using namespace std;
int p[20],n, use[20];
void afisare()
{
int i;
for(i=1;i<=n;i++)
cout<<p[i]<<' ';
cout<<'\n';
}
void perm(int k)
{
int i;
if(k<=n)
{
for(i=1;i<=n;i++)
if(!use[i])
{
p[k]=i;
use[i]=1;
perm(k+1);
use[i]=0;
}
}
else
afisare();
}
int main()
{
cin>>n;
perm(1);
return 0;}
Aranjamente
Soluţie
#include <iostream>
using namespace std;
int x[20],n,m, use[20];
void afisare()
{
int i;
for(i=1;i<=m;i++)
cout<<x[i]<<' ';
cout<<'\n';
}
void aranj(int k)
{
int i;
if(k<=m)
{
for(i=1;i<=n;i++)
if(!use[i])
{
x[k]=i;
use[i]=1;
aranj(k+1);
use[i]=0;
}
}
else
afisare();
}
int main()
{
cin>>n>>m;
aranj(1);
return 0;
Combinări
Soluţie
#include <iostream>
using namespace std;
int c[20],n,m;
void afisare()
{
int i;
for(i=1;i<=m;i++)
cout<<c[i]<<' ';
cout<<'\n';
}
void comb(int k)
{
int i;
if(k<=m)
for(i=c[k-1]+1;i<=n-m+k;i++)
{c[k]=i;
comb(k+1);
}
else
afisare(m);
}
int main()
{
cin>>n>>m;
comb(1);
return 0;
}
Submulţimi
Soluţie
#include <iostream>
using namespace std;
int s[20],n;
void afisare(int k)
{
int i;
for(i=1; i<=k; i++)
cout<<s[i]<<' ';
cout<<'\n';
}
void subm(int k)
{
int i;
for(i=s[k-1]+1; i<=n; i++)
{
s[k]=i;
if(k<=n)
afisare(k);
subm(k+1);
}
}
int main()
{
cin>>n;
subm(1);
return 0;
}
Produs cartezian
Soluţie
#include <iostream>
using namespace std;
int x[20],n,c[20];
void afisare()
{
int i;
for(i=1; i<=n; i++)
cout<<x[i]<<' ';
cout<<'\n';
}
void cart(int k)
{
int i;
if(k<=n)
for(int i=1;i<=c[k];i++)
{x[k]=i;
cart(k+1);
}
else
afisare();
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>c[i];
cart(1);
return 0;
}
P1: {1 2 3}
P2:{1 2 } {3}
P3:{1 3} {2}
P4:{1} {2 3}
P5:{1} {2} {3}
Soluţie
#include <iostream>
using namespace std;
int p[20],n,ns;// ns= numarul curent de clase care formeaza partitia
void afisare(int k)
{
int i,j;
for(i=1; i<=ns; i++)///fixez cate o clasa a partitiei
{
cout<<'{'; /// afisez toate valorile din clasa fixata
for(j=1; j<=n; j++)
if(p[j]==i)
cout<<j<<' ';
cout<<"} ";
}
cout<<'\n';
}
void partitii(int k)/// pana la nivelul k, exista deja ns clase
{
int i;
if(k<=n)
{
for(i=1; i<=ns; i++)/// plasez pe rand in solutie fiecare
/// clasa deja existenta
{
p[k]=i;
partitii(k+1);
}
p[k]=++ns; /// generez alte solutii plasand intr-o clasa
/// noua elementul k
partitii(k+1);
ns--; /// la revenirea din apel restaurez numarul
/// de clase
}
else
afisare(n);
}
int main()
{
cin>>n;
partitii(1);
return 0;
}
n=1+1+1+1+1
n=1+1+1+2
n=1+1+3
n=1+2+2
n=1+4
n=2+3
n=5
Soluţie
Vom reprezenta o soluţie ca pe un vector în care pe fiecare nivel i vom plasa o valoare
mai mare sau egală cu elementul anterior p[k]>=p[k-1]( pentru aceasta, plasăm pe
nivelul 0 al soluţiei valoarea 1, ca valoare de referinţă) şi mai mică sau egală cu n-
suma valorilor deja plasate în solutie(sp). Vom considera aceasta sumă parţială ca pe
o variabilă globală la care adaugăm fiecare element al soluţiei înainte de urcarea în
recursie şi pe care o vom restaura la valoarea anterioară la coborârea din recursie.
Avem o partiţionare a lui n când suma parţială este egală cu n.
#include <iostream>
using namespace std;
int p[20],n,sp;
void afisare(int k)
{
int i,j;
cout<<"n=";
for(i=1; i<=k; i++)
cout<<p[i]<<' ';
cout<<'\n';
}
void part_num(int k)
{
int i;
if(sp<n)
{
for(i=p[k-1]; i<=n-sp; i++)
{
p[k]=i;
sp=sp+p[k];
part_num(k+1);
sp=sp-p[k];
}
}
else
afisare(k-1);
}
int main()
{
cin>>n;
p[0]=1;
part_num(1);
return 0;
}
#1809, #3095,#830
Pbinfo.ro
#1842-https://www.pbinfo.ro/probleme/1842/crearenumarrec
#826-https://www.pbinfo.ro/probleme/826/cifminparrec
#831-https://www.pbinfo.ro/probleme/831/generare3
#839-https://www.pbinfo.ro/probleme/839/vraja2
#3605-https://www.pbinfo.ro/probleme/3605/descprime
#841-https://www.pbinfo.ro/probleme/841/bomber
Tema suplimentara
Windows-http://campion.edu.ro/arhiva/index.php?page=pro
blem&action=view&id=520
Imagine-http://campion.edu.ro/arhiva/index.php?page=pro
blem&action=view&id=771