Correctesa de Programes Iteratius i Programació Recursiva › pro2 › data › uploads ›...

35
Correctesa de Programes Iteratius i Programació Recursiva Assignatura PRO2 Març 2012

Transcript of Correctesa de Programes Iteratius i Programació Recursiva › pro2 › data › uploads ›...

Corr ectesadeProgramesIteratiusi ProgramacióRecursiva

AssignaturaPRO2

Març2012

2

Índex

5 Corr ectesade ProgramesIteratius 55.1 El principi d’inducció . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55.2 Correctesadeprogramesiteratius. . . . . . . . . . . . . . . . . . . . . . . . . . 65.3 Disseny inductiud’algoritmesiteratius . . . . . . . . . . . . . . . . . . . . . . . 75.4 Exemplesdejustificaciódeprogramesiteratius . . . . . . . . . . . . . . . . . . 8

6 Programaciórecursiva 216.1 Disseny deprogramesrecursius:justificaciódela sevacorrectesa. . . . . . . . . 216.2 Exemplesdedisseny i justificaciódeprogramesrecursius. . . . . . . . . . . . . 236.3 Immersióo generalitzaciód’unafunció . . . . . . . . . . . . . . . . . . . . . . 25

6.3.1 Immersionsd’especificacionsperafeblimentdela postcondició . . . . . 256.3.2 Immersionsd’especificacióperreforçamentdela precondició . . . . . . 296.3.3 Exemplesd’immersionsambaccions . . . . . . . . . . . . . . . . . . . 306.3.4 Relacióentrealgoritmesrecursiuslinealsfinalsi algoritmesiteratius. . . 33

3

4 ÍNDEX

Capítol 5

Corr ectesadeProgramesIteratius

5.1 El principi d’inducció

Perdemostrarla correctesadeprogramesiteratiusi recursiusutilitzaremel principi d’inducció.Aquestserveix engeneralperdemostrarpropietatsdelsnombresnaturals,i ésunaeinamatemà-tica bàsica.

La primeraversiódelprincipi d’inducció,onP�x� significaquex compleixla propietatP, és

�P�0����� x � N

�P�x��� P

�x � 1� � ����� y � N P

�y� �

Aquestprincipi diu quesi volemdemostrarquetotselsnombresnaturalscompleixenunacertapropietatP, ésadir � y � N P

�y� , cal verificarprimer:

1. El nombrezerocompleixla propietatP, ésadir P�0� .

2. Perqualsevol natural,si aquestcompleixla propietatP, llavorsel seusuccessortambélacomplirà,ésadir � x � N

�P�x���� P

�x � 1� � .

La demostraciódequeel nombrezerocompleixla propietatP s’anomenabasede la inducció.D’altra banda,demostrarquesi unnombrenaturalcompleixla propietat,llavorsel seusuccessortambé,s’anomenapasd’inducció.

El principi d’inducció és bastantnatural. Donat que hi ha un nombreinfinit de nombresnaturals,nopodemcomprovarunperunquetotsells tenenla propietatquevolem.El quefemésdemostrarqueel zerola té, i quesi un númerola té,el següenttambé.D’aquestamanera,donatqueel zerotélapropietat,el 1 tambéla té,aixòimplicaqueel2 tambéla té,i així successivament.

Existeixunaaltraformadeprincipi d’induccióquetambéutilitzarem.Ésaquesta:

�P�0����� x � N

� � y � x P�y��� P

�x� � ����� z � N P

�z�

En aquestcas,si volem demostrarque tots els naturalscompleixen una propietatP, això és� z � N P

�z� , cal demostrar:

1. El nombrezerocompleixla propietat,aixòésP�0� .

5

6 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

2. Perqualsevol naturalx, si totselsseusantecessorscompleixenlapropietatP, llavorsaquesttambéla compleix,ésadir, � y � x P � y����� P � x� .

Ambdósprincipissónmolt utilitzatspera demostrarpropietatsbàsiquesdelsnaturals,comperexemple � n � 1 � 2 ��� � � ��� n 1��� n � n � n � 1� ! 2, etc. El queno estàclar, demoment,éscomutilitzar aquestsprincipispera ajudar-nosa demostrarla correcciód’algoritmesiteratiusirecursius,i enaquestcas,dequinespropietatsP denombresnaturalsestemparlant.

5.2 Corr ectesadeprogramesiteratius

El formathabitualdelsprogramesiteratiusésel següent:

/* Pre */inicialitzacionswhile (B){

S}/* Post */

Perdemostrarla correcciód’un bucle,hemdegarantirquesi a l’inici del’execuciólesvaria-blescompleixenla precondició,al finalitzarl’execuciódelbuclecompleixenla postcondició.Peraixò,ésessencialtrobarl’ invariant. Aquestpermettransmetrela informaciódela precondicióala postcondició.Pertant l’invariantésunapropietatques’hademantenirencadaiteració.Ésadir, quementreescompleixla condicióB, l’invariantseràcerta l’inici i al finalitzarcadaiteraciódelbucle.A méss’handegarantirunparelldecosesmés.Perunabandalesinicialitzacionshandefer certl’invariant,i d’altre,enla darreraiteraciódelbucle,quanB ja nosiguicert,l’invariant(queescompliràcomal final decadaiteració)had’implicar la postcondició.

Els elementsademostrarquehemmencionatsónelssegüents:

1. Demostrarque " Pre# Inicialitzacions" Invariant # . És a dir, si les variablescompleixenla precondició,desprésde realitzarles instruccionsd’inicialització abansdel bucle, lesvariablescompliranl’invariant.

2. Demostrarque " Invariant $ B # S" Invariant # . Ésadir, si lesvariablescompleixenl’invari-anti la condiciód’entradaal bucle,desprésd’executarlesinstruccionsd’aquest,estornaràacomplir l’invariant.

3. " Invariant $&% B#'���(" Post # . Ésadir, si lesvariablescompleixenl’invariantperòno lacondiciód’entradaal bucle,llavorshandecomplir la postcondició.

Veiemla relaciód’aquestspassosambel principi deinduccióquehemintroduïtenla seccióanterior. Suposemquela propietatquevolem demostrarésqueles variablesdel programaenqualsevol iteraciódel buclecompleixen l’invariant,on el nombrenaturalquecompleixaquestapropietatésel nombred’iteració. En la iteraciózeroescompleixl’invariantperquèdemostrar

5.3. DISSENYINDUCTIU D’ALGORITMES ITERATIUS 7

el punt1 ésequivalenta això. El punt2 ésel pasd’inducció. En ell diemquesi al final d’unaiteracióescompleix l’invariant,al final de la següenttambéescomplirà. El resultatd’aquestraonamentseriaconclourequeentota iteraciódel bucleescompliràl’invariant.Malgrat tot, enaquestcasnovolemconclourequesempreescompliràl’invariant,sinóúnicamentsi escompleixla condiciód’entradaal bucle.Finalmentla condició3 ensdiu quesi noescompleixla condiciód’entradaal bucle, esa dir ) B, llavors s’ha de complir la postcondicióqueésel quevolíemdemostrar.

Unadarreracosaésimportantpera la correcciódel’algoritme. Hemd’assegurar-nosdequeaquestacaba.Ésa dir, queno estemdinsd’un bucleinfinit. Peraixò hemdetrobarunavariabledel bucleo unafunció d’aquestes(funció defita), quetingui un valor naturali quedecreixiencadaiteraciódel bucle.Aquestparàmetrehad’apropar-noscadacopmésa la condiciód’acaba-mentdel bucle,ésa dir ) B. Així demostraremqueel bucleacabadesprésd’un nombrefinit depassos.

Per tant, si la nostrafunció de fita es diu t, hauremde demostrarduescoses. Una, queInvariant * t + N, per assegurar-nosquesi estemen la situaciódeterminadaper l’invariant,llavors t té un valor natural. L’altre, , Invariant - B - t . T / S, t 0 T / , per assegurar-nosquetdecreixdesprèsdecadaiteració.

5.3 Dissenyinductiu d’algoritmes iteratius

Volemdonarunaideadecomgenerarun programaiteratiua partir de la seva especificació.Laideaésobtenirprimerl’invariant,i despréslesinstruccionsdel bucle.Aquestsserienelspassos:

1 A partir fonamentalmentde la postcondició,queésl’assercióqueensparladel quehadeassolirel bucle,hemde caracteritzarl’invariant. Peraixò hemde pensarquel’invariantha de descriureun punt intermig de l’execuciódel bucle. Una altra manerade veure-hoéspensantque l’invariantésun afeblimentde la postcondició,en el sentit de queés lapostcondiciórespectea la part tractadaen l’execució. Perpoderafeblir la postcondiciónecessitaremparlar de noves variables(seranvariableslocals en el codi), amb les queexpressarl’invariant.

1 A partir de l’invarianthemdededuirl’estatenel quel’execucióhaacabat.Aquestaseràla condiciód’acabamentdelbucle,i negantaquestaobtindremla condicióB delbucle.

1 Veurequinesinstruccionsnecessitael cosdel bucleper tal quesi a l’entradad’aquestescompleix l’invariant,al final de l’execuciódel cosdel bucle tambéescomplirà. I donatquecadaiteraciódel bucleenshad’aproparal final del’execuciód’aquest,hemdetrobarunafuncióquedecreixiencadaiteració.

1 A méshemdeveurequinesinstruccionso inicialitzacionsfaranqueescompleixil’invari-antabansd’entraral bucle.

Tingueuencomptequequandiemqueunainstruccióo conjuntd’instruccionssimples(senseiteracióni recursivitat) fanqueescompleixialgunacosa,aixòcomportaunasèriecomprovacions

8 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

addicionals:si estractad’accesosa vectors,quelesposicionsvisitadesestiguindefinides,si estractadecridesaoperacions,queescompleixila sevaprecondició,si el quetenimésun if , quecadabrancaarribi a l’objectiu perseparat,etc.

5.4 Exemplesde justificació deprogramesiteratius

A l’horadetreballarambelsexemples,posaremunaseriedepetitesrestriccionsalsnostrescodis,pertal depoderfer lesjustificacions.

1. Noméspodremfer iteracionsambla instrucciówhile.

2. Lesexpressionsbooleanesno s’avaluenambprioritat.

3. Enel casdefuncions,solamentpodemutilitzar unreturn,i aquestestrobaràal final detot.

Aquestesrestriccionsno sónimportants,i no suposenrestriccionsrealsen el codi. La pri-meraimplicanomésqueenlloc d’utilitzar la instrucció,perexemple,del “for”, utilitzaremla del“while”. Respectea la segonarestricció,el quevol dir ésqueenunaexpressióbooleanaconjun-ció o disjunció,s’avaluenles2 subexpressions.Perexemple,si tenim“p andq”, si p esfals,qtambés’avalua.Si tenim"p or q", si p escert,q tambés’avalua.Enquanta la tercera,el fet deferel returnnomésal final introdueixun certordreenel nostrecodi,el fa méssimpledeverificar, ipotevitar errorsdeprogramació.Clarament,qualsevol altrecodiquenocompleixialgunadelesrestriccions,pot sertransformatfàcilmentenund’equivalentquesíquelescompleixi.

Els primersexemplesqueveuremsónduesvariantsde la cercalineal en un vector. Donatun vectord’entersi un enter, s’ha de veuresi l’enter apareixal vector. En els dosalgorismestindremun índex i quemarcala posiciódel següentelementa comprovar. La diferènciaentreelsdosalgorismesresideixensi incrementemo no l’índex i enel casdetrobarl’elementcercat.Tanmateix,aquestapetitadiferènciafaràqueelsrespectiusinvariantssuguindiferents,així comles justificacionscorresponents.Es important,doncs,perpoderfer la justificaciócorrectament,quel’invariantdonatsigui completamentcoherentambel codidel’algorismeiteratiu.

A continuació,el primeralgorismei la seva justificació:

bool cerca_iter1_vec_int (co ns t vector<int> &v, int x)/* Pre: cert *//* Post: El resultat ens diu si x és un element de v o no */{

int i = 0;bool ret = false;

/* Inv: 0<=i<=v.size(), ret="l’enter x és un element de v[0..i-1]" */

while(i<v.size() and not ret){ret = (v[i] == x);++i;

5.4. EXEMPLESDE JUSTIFICACIÓ DE PROGRAMESITERATIUS 9

}return ret;

}

2 Inicialitzacions. Inicialmentnohemcomprovatcapelementdev , pertantinicialitzem i azero,la posiciódel primerelementdel vector, satisfentla primerapartde l’invariant.Persatisferla segona,cal posarel valor false a ret , donatqueno pot existir l’enter x enunsubvectorbuit v[0..-1] .

2 Condiciódesortida. Espotsortir del bucleperduesraons:

– Si i arribaa serv.size() vol dir, per l’invariant,quehemexplorat tot el vectorv iqueret ensdiu si l’elementx estàa v , comespreténa la postcondició.

– Encascontrari,comqueperl’invarianttenimi<=v.size() , escompleixquei<v.si-ze() i persortir delbucles’hadecumplir queret éscert.

Peròsi abansd’arribaral final del vectorv tenim que ret passaa ser true , voldràdir (denouperl’invariant)quehemtrobatx al subvectorv[0..i-1] i queret ja ensdiu quex estàa v i tambéescompleixla postcondició.

2 Cos del bucle. Abans d’avançarl’índex i , hem de satisferl’invariant canviant i peri+1 . Necessitemque ret contingui la informacióde si l’elementx estrobaal subvec-tor v[0..i] . Com que,si hementratal bucle vol dir que ret==false , i per l’invarianttenimqueret contéla informaciósobresi x estàal subvectorv[0..i-1] , quedaclarquex no estàal subvectorv[0..i-1] i nomésenscal veuresi x ésigual a v[i] , i actualit-zar el valor de ret depenentde si sónigualso no. Un cop quetenim ret modificat, japodemincrementari assegurantquesatisfeml’invariant. Noteuque,perentraral bucle,i<v.size() , per tant, marcaunaposicióvàlida del vector i desprésd’incrementari escompleixquei<=v.size() (tal comdemanal’invariant).

2 Acabament. A cadavolta decreixla distànciaentrev.size() i l’índex i , perquèincre-mentemi a cadascunad’elles.

Ara, la segonavariant (noteules diferènciesen l’invariant i en la manerade justificar lacorrecciódel’algorisme):

bool cerca_iter2_vec_int (co ns t vector<int> &v, int x)/* Pre: cert *//* Post: El resultat ens diu si x és un element de v o no */{

int i = 0;bool ret = false;

/* Inv: 0<=i<=v.size(), el subvector v[0..i-1] no conté l’element x,si ret és true llavors v[i]==x */

10 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

while(i<v.size() and not ret){if (v[i] == x) ret = true;else ++i;

}return ret;

}

3 Inicialitzacions. Inicialmentno hemcomprovat capelementdev , per tant inicialitzem ia zero, la posiciódel primer elementdel vector, satisfentla primerai la segonapart del’invariant,donatquenopotexistir l’enterx enunsubvectorbuit v[0..-1] . Persatisferlatercerapart,noméscal posarel valor false a ret , ja quequalsevol implicacióquetinguicomapremisafalse sempreescompleix(perdefinició).

3 Condiciódesortida. Espotsortir del bucleperduesraons:

– Si i arribaa serv.size() vol dir, per l’invariant,quehemexplorat tot el vectorvi queret==false (altrement,voldria dir quev[i]==x , el queésimpossibleperquèv[v.size()] no existeix),comespreténa la postcondició.

– Encascontrari,comqueperl’invarianttenimi<=v.size() , escompleixquei<v.si-ze() i persortir delbucles’hadecumplir queret éscert.

Peròsi abansd’arribaral final delvectorv tenimqueret passaasertrue , voldràdir(denouperl’invariant)quehemtrobatx a la posicióv[i] i queret ja ensdiu quexestàa v i tambéescompleixla postcondició.

3 Cos del bucle. Per l’invariant sabemque x no es troba a v[0..i-1] i per la condiciód’entradasabemquei<v.size() marcaunaposicióvàlidadel vectori queret==false .Necessitemcomprovardoncssi v[i]==x ono. Si nohoés,llavorsx noestrobaav[0..i] ,i noméscal incrementari perquèes torni a complir l’invariant. Si l’element x==v[i]llavors les duesprimerespartsde l’invariantescontinuencomplint i noméscal assignarret=true perpodersortir delbuclesatisfental mateixtempsla tercerapartdel’invariant.Noteuqueno podemincrementari si trobeml’elementx , perquèllavorsno escompliriala segonapartdel’invariant.

3 Acabament. A cadavolta, o bé decreixla distànciaentrev.size() i l’índex i , perquèincrementemi , o béposemel booleàret a true i sortimdel bucle.

El següentexempleconsisteixen,donatun vectord’estudiants,retornarel percentatged’es-tudiantspresentatsdelvector.

double presentats(const vector<Estudiant> &vest)/* Pre: vest conté al menys un element *//* Post: el resultat és el percentatge de presentats de vest */

5.4. EXEMPLESDE JUSTIFICACIÓ DE PROGRAMESITERATIUS 11

Implementació:Peraconseguir el percentatgede presentatsnecessitemsaberel nombrede presentats,ésa

dir, el nombred’estudiantsambnota.L’estratègiaperresoldreaquestproblemaésanarcomptantel nombred’estudiantsambnota

a mida querecorremel vector. Pertant, consideremquehemcomptatelsestudiantsambnotafins a un punt “i” (senseincloure’l) i avancemconsiderantel següent(l’i-èssim). Així doncs,l’invariantés

I: 0<=i<=vest.size(), n="nombre d’estudiants amb nota en vest[0..i-1]"

Raonemarasobrelesinstruccionsdelbucle.

4 Inicialitzacions. Inicialmentconsideremquenohi hacapestudiantdel vectortractat,aixòho representemposantun 0 a la i , la qualcosaenspermetsatisferl’invariantassignantun0 a la n, ja quenohi hacapestudiantfinsa i-1 ambnota.

4 Condicióde sortida. Com quela postcondiciódiu quen ésel nombred’estudiantsambnotade tot el vector, sortiremdel bucle quan i=vest.size() , ja queaquestacondiciójuntamentambl’invariantensdónael valordesitjatden. Pertant,ensquedemmentrei <vest.size() (noteuquehemdemanata l’invariantquei <= vest.size() ).

4 Cosdel bucle. Comquehemd’avançar, el mésnaturalésincrementarla i demaneraques’acostial final delvector. Peròabansd’incrementarla i , hemdesatisferl’invariantcanvi-anti peri+1 . Necessitem,pertant,quen continguifinalmentel “nombred’estudiantsambnotaenvest[0..i] ”. Comqueactualmentn contéel “nombred’estudiantsambnotaenvest[0..i-1] ”, nomésenscal preguntarsi vest[i] té nota(l’accéséscorrecteja que0<= i < vest.size() ). En casafirmatiuel nombred’estudiantsambnotaésn+1 , queésel quehemd’assignaran. Encanvi, si no ténotael nombred’estudiantsambnotasegueixsentn, i pertantno cal fer res.

Un copquen contéel “nombred’estudiantsambnotaen vest[0..i] ”, ja podemincre-mentarla i assegurantquesatisfeml’invariant.A mésla condiciódel bucleensasseguraquei+1 <= vest.size() .

4 Acabament. A cadavoltadecreixla distànciaentrevest.size() i la i .

4 Instruccionsfinals. Quansortimdel bucletenima n el “nombred’estudiantsambnotaenvest ”, pertantnoméscalmultiplicar-lo per100 i dividir-lo pervest.size() , i obtindremel percentatge.

El programaanotatquedadela següentmanera:

double presentats(const vector<Estudiant> &vest)/* Pre: vest conté almenys un element *//* Post: el resultat és el percentatge de presentats de vest */{

12 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

int numEst = vest.size();int n = 0;int i=0;

/* Inv: 0<=i<=numEst, n=nombre d’estudiants amb nota en vest[0..i-1]*/

while(i < numEst){if (vest[i].te_nota()) ++n;++i;

}/* n és el nombre d’estudiants amb nota de vest */double pres = n*100./numEst;return pres;

}

El properexempleésun programaque,donatunvectord’estudiants,el modificaarrodonint-nelesnotesa la dècimaméspropera(espot fer comaaccióo coma funció).Especificació:

void arrodonir_notes(vec tor <Estu dia nt> &vest);/* Pre: cert *//* Post: vest té les notes dels estudiants arrodonides respecteal seu valor inicial*/

Implementació:Persimplificar el disseny hemsuposat,per abstracciófuncional,quetenim unafunció que

ensarrodoneixun real. Aquestafunció la dissenyaremal final, i d’aquestamanerahemseparatel problematècnic de com fer l’arrodonimentd’un real, del nostreproblemageneralque ésarrodonirlesnotesd’un vectord’estudiants.

Lesjustificacionssónsimilarsal programaanterior. Consideremcoma invariant

vest[0..i-1] té les notes dels estudiants arrodonides respecteal seu valor inicial, 0 =< i <= vest.size()

L’invariantésla postcondicióaplicadaala parttractadadelvector. Justifiqueminformalmentel programa.

5 Inicialitzacions. Inicialmentconsideremquenohi hacapestudiantdel vectortractat,aixòhorepresentemposantun0 a la i . Persatisferl’invariantnocal fer resmésja quenohi hacapestudiantfins a i-1 .

5 Condiciódesortida. Comquela postcondiciódiu quevest[0..vest.size()-1] hadetenirlesnotesarrodonidesrespecteal seuvalorinicial, sortiremdelbuclequani=vest.size() ,ja queaquestacondició juntamentambl’invariantensassegura l’anterior. Per tant, ensquedemmentrei < vest.size() .

5.4. EXEMPLESDE JUSTIFICACIÓ DE PROGRAMESITERATIUS 13

6 Cosdel bucle. Com quehemd’avançar, el mésnaturalés incrementarla i de maneraques’acostial final del vector. Peròabansd’incrementarla i , hemdesatisferl’invariantcanviant i per i+1 . Necessitem,pertant,quevest estiguimodificaten [0..i] . Comqueactualmentvest estàmodificaten [0..i-1] , nomésenscal preguntarsi vest[i] té notai encasafirmatiumodificar-li la nota,uncoparrodonida.Si no ténotanocal fer res.Coma l’exempleanterior, l’accéséscorrecteja que0 <= i < vest.size() .

Un cop quetenim vest modificatambla notaarrodonidadelsestudiantsen [0..i] , japodemincrementarla i assegurantquesatisfeml’invariant(a mésla condiciódel bucleensasseguraquei+1 <= vest.size() ).

6 Acabament. A cadavoltadecreixla distànciaentrevest.size() i la i .

El programadescritquedacom:

void arrodonir_notes(vec tor <Estu dia nt> &vest)/* Pre: cert *//* Post: vest té les notes dels estudiants arrodonides respecteal seu valor inicial */{

int numEst = vest.size();int i=0;

/* Inv: vest[0..i-1] té les notes dels estudiants arrodonidesrespecte al seu valor inicial, 0 =< i <= numEst */

while (i < numEst) {/* mirem si vest[i] té nota */if (vest[i].te_nota()) {

/* obtenim la nota de vest[i] arrodonida */double aux = arrodonir(vest[i].c onsul tar _no ta ()) ;/* modifiquem la nota de vest[i] amb la nota arrodonida */vest[i].modificar_ not a( aux );

}++i;

}}

Hem utilitzat la variableaux per augmentarla llegibilitat. Noteuquehemd’utilitzar mo-dificar_nota perquèl’estudiantja té nota. Finalment,implementemla funció arrodonir ,considerantqueenel llenguatgehi haunafunció int(...) queagafa la partenterad’un real.

double arrodonir(double r)/* Pre: cert *//* Post: el resultat és el valor original de r arrodonit ala dècima més propera (i més gran si hi ha empat) */

14 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

{return int(10.*(r + 0.05)) / 10.0;

}

A continuaciómostremun exempledejustificaciósobrepiles. Estractade,donadaunapilad’enters,calcularla sevaalçadao nombred’elements.La següentseriaunaprimeraespecificaciódel problema.

int alcada_pila_int(s tac k<i nt > p)/* Pre: cert *//* Post: El resultat és el nombre d’elements de p */

El problemaésqueper saberel nombred’elementsd’una pila, hemd’anardesempilanticomptantelselements.Això suposaqueel paràmetrepila esmodificaenl’execució,i per tant,al final de la execucióel paràmetrep seràunapila buida, i no seràcertqueel resultatésel seunombred’elements.Convé per tant canviar l’especificació.D’aquestamanerapodremfer unabonajustificació. Per fer unabonaespecificacióconvé distingir entreel paràmetrepila p queesva modificant,i la pila inicial (o valor inicial de p) queanomenaremP. Així doncs,la novaespecificació(queja esdescriuaixí enelsapuntsdela tercerasessió)és:

int alcada_pila_int(s tac k<i nt > p)/* Pre: p=P *//* Post: El resultat és el nombre d’elements de P */

Desprésdepresentarl’algoritme el justificarem.

int alcada_pila_int(s tac k<i nt > p)/* Pre: p=P *//* Post: El resultat és el nombre d’elements de P */{

int n=0;

/* Inv: n="nombre d’elements de la part tractada de P" ip="elements de la pila inicial P que queda per tractar". */

while(not p.empty()){++n;p.pop();

}return n;

}

Pera la funció alçada la justificacióinformalhaestatla següent:

7 Inicialitzacions. Inicialmentconsideremquenohi hacapelementdeP tractat(p contétotselselements),pertantl’invariantrequereixquen vagui0.

5.4. EXEMPLESDE JUSTIFICACIÓ DE PROGRAMESITERATIUS 15

8 Condiciódesortida. Comquela postcondiciódiu quen hadeserel nombred’elementsdeP, cal quetotala pila P hagiestattractadao, equivalentment,quep no tingui capelement,ésadir sortiremquanp siguiunapila buida.Pertant,ensquedemmentrenot p.empty() .

8 Cosdel bucle. Comquehemd’avançar, el mésnaturaléseliminarel cim dela pila p (perla condiciósabemqueno ésbuida). Peròabansd’eliminar-lo, hemdesatisferl’invariantcanviant p per la pila resultatde fer p.pop() (novament,això no dónaerrorperquèp noésbuida). Necessitem,per tant,quen continguiel nombred’elementsde la part tractadadesprésdedesempilar. Comque,actualment,n contéel nombred’elementsqueno sónap abansdedesempilar, nomésenscal sumar-li 1 a n, per tenir el nombred’elementsquenosónap desprésdedesempilar. Un copquetenimn modificat,ja podemeliminarel cimdep assegurantquesatisfeml’invariant.

8 Acabament. A cadavoltadecreixla midadep.

Donaremaraunexempledejustificaciód’unacercaambcues.Donadaunacuad’entersi unenter, s’hadeveuresi l’enterapareixa la cua.Aquí comenl’exempleanterior, donatquela cuaesmodificadurantl’execució,hemd’inclourea l’especificacióun símbolquerepresentiel valorinicial del’estructura,peraqueespuguifer la justificació.

bool cercar_iter_cua_int (qu eue<i nt> c, int x)/* Pre: c=C *//* Post: El resultat ens diu si x és un element de C o no */{

bool ret=false;

/* Inv: ret="l’element x està a la part tractada de C" ic és la part de C que queda per tractar. */

while(not c.empty() and not ret){ret = (c.front() == x);c.pop();

}return ret;

}

8 Inicialitzacions. Inicialmentno s’ha tractatcapelementde C, ésdir, c contétotselsele-ments,pertantl’invariantrequereixposarel valor false a ret .

8 Condiciódesortida. Si c arribaaserunacuabuidavol dir, perl’invariant,quehemtractattotala cuaC i queret ensdiu si l’elementx estàaC, comespreténa la postcondició.Peròsi abansdebuidarc tenimqueret passaasertrue , voldràdir (denouperl’invariant)quehemtrobatx a la parttractadadeC i queret ja ensdiu quex estàa C i tambéescompleixla postcondició.Així quesortiremo béquanc notingui capelement,ésadir siguiunacuabuida,o béquanret sigui certperquèja hemtrobeml’elementx . Pertant,ensquedemmentrenot c.empty() and not ret .

16 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

9 Cosdelbucle. Comquehemd’avançar, el mésnaturaléseliminarel primerdela cuac (perla condiciósabemqueno ésbuida). Peròabansd’eliminar-lo, hemdesatisferl’invariantcanviant c per la cuaresultatde fer c.pop() (novament,això espot fer perquèc no ésbuida). Necessitemque ret contingui la informacióde si l’elementx estrobaa la parttractadade C desprésd’avançar(ésa dir, els queno sóna c desprésde c.pop() ). Comque,actualment,ret contéla informaciósobresi x estàentreelselementsqueno sóna cabansd’avançar, enscalveuresi x ésigualal primerelementdec , i actualitzarel valorderet depenentdesi sónigualso no. Un copquetenimret modificat,ja podemeliminarelprimerdec assegurantquesatisfeml’invariant.

9 Acabament. A cadavoltadecreixla midadec .

Seguidamentfaremun exempleambllistes. Donadaunallista i un enterk, transformaremla llista sumantk a cadaelementde la llista original. A continuaciódoneml’especificació,l’algorismei l’invariant.

void suma_llista_k(list< int > &l, int k)/* Pre: l=L *//* Post: Cada element de l és la suma de k i l’element de La la seva mateixa posició */{

list<int>::iterator it;it = l.begin();

/* Inv: Cada element de l des de l’inici fins a la posició anterior a la quemarca it són la suma de k més l’element corresponent d’L. A partir d’it finsal final de l, els elements són els mateixos que els seus corresponents a L. */

while(it != l.end())){*it+=k;++it;

}}

9 Inicialitzacions. Desprèsd’executarit = l.begin() s’hadecomplir l’invariant.Això esaixí donatqueenel casquel’iteradorestiguial principi dela llista, l’invariantensdiu queelselementsde l sónelsmateixosqueelsdeL, la llista inicial. En aquestcasl’invariantcoincideixambla precondició,i comqueno hemtractatcapelement,éscert.

9 Condiciódesortida. Comquela postcondiciódiu quetotselselementsdel siguinla sumadel seuvalor a L mésk , tindremaquestresultat(segonsl’invariant)quanl’últim elementdela llista sigui l’anterior al quemarcait , i això passaràquanit sigui igual a l.end() ,l’elementfictici definal dellista. Pertant,ensquedemmentreit != l.end() .

5.4. EXEMPLESDE JUSTIFICACIÓ DE PROGRAMESITERATIUS 17

: Cosdel bucle. Suposaremquea l’inici d’una iteracióqualsevol del bucleescompleixenl’invarianti la condiciód’entradaenel bucle. Veuremquedesprésd’executarles instruc-cionsdel bucleestornaa fer cert l’invariant. Quanexecutem*it+=k; lesposicionsde ldel’inici fins a la apuntadaper it tenenla k sumadaalsvalorsdela llista inicial. Desprésdeit, elsvalorsde l continuensentelsdela llista original. Pertant,quanexecutem++it;estornaa complir l’invariant.

: Acabament. A cadavoltadecreixla midadela subllistaentreit i l.end() .

Finalment,veuremun exemplede justificacióon tenim dosbucles,un dins de l’altre. Enaquestscasos,cal donarun invariantper cadascund’ells i fer justificacionsseparades,unaperbucle.

typedef vector<vector<int> > Matriu;

Matriu sumar_Matriu_int(con st Matriu& m1, const Matriu& m2)/* Pre: m1 i m2 tenen les mateixes dimensions *//* Post: la matriu resultat té les mateixes dimensions que m1 i m2, i

cada element de la matriu resultat és la suma dels elementsde m1 i m2 a la seva mateixa posició */

{int fil = m1.size();int col = m1[0].size();Matriu suma (fil, vector<int>(col));int i = 0;

/* Inv. bucle extern:fil és el nombre de files de m1, de m2 i de suma,col és el nombre de columnes de m1, de m2 i de suma,0<=i<=fil,cada element de la matriu suma des de la fila 0 a la fila (i-1)

és la suma dels elements de m1 i m2 a la seva mateixa posició*/

while (i<fil) {int j = 0;

/* Inv. bucle intern:col és el nombre de columnes de m1, de m2 i de suma,0<=j<=col,cada element de la fila i de la matriu suma des de la columna 0 a

la columna (j-1) és la suma dels elements de m1 i m2 a la sevamateixa posició

*/

while (j<col) {

18 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

suma[i][j] = m1[i][j] + m2[i][j];++j;

}/* cada element de la fila i de la matriu suma és la suma dels

elements de m1 i m2 a la seva mateixa posició*/

++i;}

return suma;}

La justificaciódel bucleexternés:

; Inicialitzacions. Persatisferl’invariantcreemla matriusuma delesmateixesdimensionsquem1(igualstambéquelesdem2perla Pre),assignemalesvariablesfil i col el nombredefiles i el nombredecolumnes,respectivament,delestresmatrius,i donemel valor 0 al’índex defiles i , ja quecompleixla restricciódevalorsi satisfàl’última partdel’invariantal ser0..i-1 un interval buit defiles.

; Condiciódesortida. Comquela matriuresultatqueretornemal final dela funcióéssuma,percomplir la postcondiciócalquetotselselementsdesuma siguinla sumadelselementsdem1 i m2a la sevamateixaposició.Satisfaremaquestacondiciói sortiremdelbuclequans’hagincalculattoteslesfiles desuma, ésadir, quanescompleixi l’invarianti la condiciói==fil . Per tant, ensquedemmentrei < fil (ja que la negaciód’aquestacondició il’invariantensgaranteixen i==fil ).

; Cosdel bucle. Comquehemd’avançar, el mésnaturalésincrementarla i demaneraques’acostial final de la matriu. Peròabansd’incrementarla i , hemde satisferl’invariantcanviant i per i+1 . Comquel’invariantensasseguraquelesfiles anteriorsa la i ja hanestatcalculades,nomésnecessitem,per tant,quela fila i de la matriu suma continguilasumadelesfiles i delesmatriusm1 i m2. El bucleinterns’ocupadesatisferaixò i desprésja espot incrementari .

; Acabament. A cadavolta decreixla distànciaentrefil i la i , perquèincrementemi acadaiteració.

A continuació,la justificaciódelbucleintern:

; Inicialitzacions. La variablecol ja contéelnombredecolumnesdelestresmatrius,perquèhaestatinicialitzadaaixí enel bucleexterni noesmodificaposteriorment.Donemel valor0 a l’índex decolumnesj , ja quecompleixla restricciódevalorsi satisfàl’última partdel’invariantal ser0..j-1 un interval buit decolumnes.

5.4. EXEMPLESDE JUSTIFICACIÓ DE PROGRAMESITERATIUS 19

< Condicióde sortida. La postcondiciódel bucle intern ésquetots els elementsde la filai de suma siguin la sumadelselementsde m1 i m2 a la seva mateixaposició. Satisfaremaquestacondiciói sortiremdelbuclequans’hagincalculatelselementsdela fila i desumaper totesles columnes,ésa dir, quanescompleixi l’invarianti la condició j==col . Pertant,ensquedemmentrej < col (ja quela negaciód’aquestacondiciói l’invariantensgaranteixen j==col ).

< Cosdel bucle. Com quehemd’avançar, el mésnaturalés incrementarla j de maneraques’acostial final de la fila. Peròabansd’incrementarla j , hemde satisferl’invariantcanviant j perj+1 . Comquel’invariantensasseguraqueelselementsdela fila i anteriorsa la columnaj ja han estatcalculats,nomésnecessitem,per tant, assignara l’elementsuma[i][j] la sumadem1[i][j] i m2[i][j] . Desprésja podemincrementarj .

< Acabament. A cadavolta decreixla distànciaentrecol i la j , perquèincrementemj acadaiteració.

20 CAPÍTOL 5. CORRECTESADE PROGRAMESITERATIUS

Capítol 6

Programaciórecursiva

La formaméssimpledeprogramarecursiuésun condicional(instruccióif ) on, si escompleixla propietatbooleanadel’ if (c = x> al següentexemple),executemunconjuntd’instruccions,querepresentael casdirectei queanomenemd. Si noescompleixla propietatbooleanadel’ if , lla-vorsexecutemaltresinstruccionsonunad’ellesésunacridaa la mateixafunció f , peròambelsparàmetresdela crida“méspetits”,ésadir elsapliquemprimerunafunció queaquíanomenemg. Finalmentcalculemel valor desortidaaplicantunaaltrafunció,queaquíanomenemh, sobreelsvalors.El següentalgoritmerepresentaaquestaversiósimplificadad’algoritmerecursiu.

Tipus2 f(Tipus1 x)/* Pre: Q(x) *//* Post: R(x,s) */{

Tipus2 r,s;

if (c(x)) s= d(x);else{

r= f(g(x));s= h(x,r);

}return s;

}

En general,podemtenir mésd’un casdirectei mésd’un casrecursiu. Cadacasdirecteconstaràdela sevacondiciód’entradaci i lesinstruccionsdirectesdi . Cadacasrecursiuconstaràdela sevacondiciód’entradac j i lessevesfuncionsg j i h j .

6.1 Dissenyde programesrecursius: justificació de la sevacorrectesa

Comal casdelsalgoritmesiteratius,perdemostrarla correcciód’un algoritmerecursiuhemdedemostrarquesi a l’inici lesvariablesimplicades(enaquestcas,elsparàmetres)compleixenla

21

22 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

precondició,al final del’execuciócompleixenla postcondició.El mateixcriteri enshadeguiara l’hora dedissenyaraquestsalgoritmes.

L’estructuradel disseny d’un algoritmerecursiuésla següent.

? Detectarelscasossenzillsi resoldre’ls sensecridesrecursives. En definitiva, mirant l’e-xempleabstractedela seccióanterior, consistiriaendetectarla condicióbooleanadel’ if(c @ xA ) i el conjuntd’instruccionsques’hand’executarsi escompleixd @ xA . En aquestscasossenzills,cal assegurar-sedequesi lesvariablescompleixenla precondiciói la con-dició booleanadel if , llavorsdesprésd’executarles instruccions(d @ xA enel exemple)escompleixla postcondicióde l’especificacióde la funció. Pertant,s’hadedemostrarqueQ @ xA�B c @ xADC R@ x E d @ xA A .

? Considerarel caso casosrecursiusi resoldre’ls. Perpodergenerarlesinstruccionsneces-sàriesquannoescompleixla condiciódel’ if , hemdefer unacridarecursivaa la mateixafunció ambun paràmetred’entradamenorquel’actual. Pera queaquestacridapuguifer-se,si x ésel paràmetred’entrada,hauremdedemostrarquesi Q @ xAFB�G c @ xA , llavorsg @ xA noéserronii g @ xA compleixla precondiciódela funciórecursiva,esadir Q @ g @ xA A . A partirdeaquí,suposemla hipòtesid’inducció queésl’afirmació: si g @ xA compleixla precondicióQ @ g @ xA A , desprésd’executarla cridarecursiva f @ g @ xA A , el resultatr compleixla postcondi-ció R@ g @ xA E r A . A partird’aquíhemd’afegir leslíniesdecodiaddicionals(h @ x E r A enaquestcas)quesiguinnecessàriespera queel codi compleixi la postcondicióR@ x E sA . En altresparaules,hemdedemostrarquesi x compleixla condiciód’entradai la precondició,i sidesprésdela cridarecursivaescompleixla postcondicióR@ g @ xA E r A llavorstambéescom-pleix R@ x E h @ x E r A A . PertanthemdedemostrarqueQ @ xAFB�G c @ xAFB R@ g @ xA E r AHC R@ x E h @ x E r A A .

? Finalment,hemde raonarsobreles condicionesd’acabamentdel programarecursiu. Ésa dir, indicarquin paràmetre,paràmetres,o funció d’aquests,esva fent méspetit encadacridarecursiva. En el nostrealgoritmerecursiusimplificat,si x ésel paràmetred’entrada,la funció del paràmetrex quedecreixl’anomenemt @ xA . D’aquestamaneraverificaremquelescridesrecursivesdintredecridesrecursivessónun nombrefinit. Pertant,hauremdedemostrarduescoses.Una,queQ @ xAIC t @ xAIJ N, perassegurar-nosdequela funcióquegaranteixl’acabamentde la recursivitat retornaun natural. L’altre, Q @ xAHB�G c @ xAICt @ g @ xA ALK t @ xA , per assegurar-nosde queen les cridesrecursivesutilitzem un paràmetremenorqueel paràmetred’entrada.

Al procésdescritsel’anomenainductiu. Un cop generatel codi, podemdemostrarla sevacorreccióutilitzant el principi d’inducció. El quevolemdemostrarésquetotacridaa la funciórecursivacompleixla postcondició.Veiemelspassos:

? Demostrarque el codi que se executaen els casosbàsicsfa que es compleixi la post-condició. Això ésequivalenta demostrarP @ 0A , ja quehemfet zerocridesrecursives, idemostremquecompliml’especificació.Pertant,P @ 0A corresponaR@ x E d @ xA A .

? Demostrarla correcciódel casrecursiu. És a dir hemde demostrarM n J N @ P @ nA�N�CP @ n O 1A A o bé M y K x P @ yAN�C P @ xA . Hemdesuposarquela cridarecursivaéscorrecta,

6.2. EXEMPLESDE DISSENYI JUSTIFICACIÓ DE PROGRAMESRECURSIUS 23

ésa dir quesi g P xQ ésméspetit quex i compleixla precondició,llavors f P g P xQ Q compleixla postcondició.A partir d’aquí,nomésquedademostrarqueles instruccionsposteriorsala cridaarribenala postcondicióoriginal. L’enunciatqueconsisteixenafirmarquela cridarecursiva retornael resultatdesitjat(perquès’ha cridat correctamenti acaba)s’anomenahipòtesid’inducció, i ésunelementessencialdela demostració.

Ara podemdeduirqueel codi éscorrecteutilitzant el principi d’inducció. L’únic queensmancaésdemostrarquel’algoritme termina. Pera això cal trobarunafunció defita, depenentdelsparàmetres, queretorni un naturali quedecreixien cadacrida recursiva. En el casmésgeneralla funciódefita potdependredetotselsparàmetresconjuntament.

6.2 Exemplesde dissenyi justificació deprogramesrecursius

Dissenyemarala funció piles_iguals , queresoldremrecursivament.

bool piles_iguals(stack< int > &p1, stack<int> &p2 )/* Pre: p1=P1 i p2=P2 *//* Post: El resultat ens indica si les dues piles inicials P1 i P2 són iguals */{

bool ret;if(p1.empty() or p2.empty()) ret=p1.empty() and p2.empty();else if (p1.top()!=p2.top() ) ret=false;else {

p1.pop();p2.pop();ret=piles_iguals( p1, p2) ;

/* HI: ret="P1 sense l’últim element afegit i P2 sense l’últimelement afegit són dues piles iguals" */

}return ret;

}

La justificacióinformal d’aquestafunció recursivaésla següent.R Casossenzills.

1. Si algunadelesduespilesésbuida,llavorslespilessónigualssi lesduessónbuides.Pertantassignema ret l’expressió(p1.empty() and p2.empty()) .

2. Si les duespiles no sónbuides,en podemconsultarels cims,peròsi sóndiferents,llavorslespilesno podenseriguals.Pertantposemret a fals.

R Casrecursiu. Si lespiles no sónbuidesi elscimssóniguals,llavors lespilessónigualssi coincideixen en la restad’elements.Això ho podemobtenir, per hipòtesid’inducció,cridant recursivamenta la funció amb les piles sensel’element del cim (podemfer-hoperquèlespilesnosónbuides).Peraixò assignema ret el resultatdela cridarecursiva.

24 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

S Acabament. A cadacridarecursiva fem méspetitala primerapila (i la segona).

Dissenyem araunafunció que,donatun arbrebinari, enscalculi la seva mida, ésa dir, elnombred’elementsqueconté.

int mida(Arbre<int> &a)/* Pre: a=A *//* Post: El resultat és el nombre de nodes de l’arbre inicial A */{

int x;if (a.es_buit()) x=0;else{

Arbre<int> a1;Arbre<int> a2;a.fills(a1,a2);int y=mida(a1);int z=mida(a2);

/* HI: y="nombre de nodes del fill esquerre d’A" iz="nombre de nodes del fill dret d’A" */

x=y+z+1;}return x;

}

La justificacióinformal d’aquestafunció recursivaésla següent.

S Cassenzill.

Si l’arbreésbuit té zeroelementsi aquestésel valorques’had’assignara x .

S Casrecursiu. Si l’arbre no ésbuit, llavorsexisteixenelsseussubarbresesquerrei dret, iespodenobtenirsenseerrorambl’operaciónfills . La midade l’arbre serà,per tant, lasumadelesmidesdelsseussubarbresmés1 (la contribuciódel’arrel).

Lesmidesdelssubarbress’obtenenperhipòtesid’inducció,amblescridesrecursives.Perúltim, fem la sumai el programaquedacomplet.

S Acabament. A cadacridarecursiva fem méspetit l’arbre.

FemaraunafuncióquecerquiunEstudiantenunacuadeEstudiants,donatundni.

bool cerca_rec_cua_Estud ian t( queue<Est udian t> &c, int i)/* Pre: i és un dni vàlid i c=C *//* Post: El resultat ens diu si hi ha algun estudiant amb dni i a C */{

6.3. IMMERSIÓ O GENERALITZACIÓ D’UNA FUNCIÓ 25

bool ret;if(c.empty()) ret=false;else if (c.front().consulta r_D NI( ) == i) ret=true;else {

c.pop();ret=cerca_rec_cua _Estud ia nt( c,i );

/* HI: ret ens diu si hi ha algun estudiant amb dni i a Csense el primer element */

}return ret;

}

La justificacióinformal d’aquestafunció recursivahaestatla següent.

T Casossenzills.

1. Si la cuaésbuidanohi hacapelementi el resultathadeserfals .

2. Si la cuano ésbuida,esdescomposaenel seuprimerelementi la resta(i espodenobtenirsenseerrorambfront i pop ). Si el seuprimerelementtéel dni quebusquem,el resultatseràcert .

T Cas recursiu. Si la cua no ésbuida i el seuprimer elementno ésel quebusquem,totdependràde si aquestestrobaa la restade la cua. Això s’obtéper hipòtesid’inducció,cridantrecursivamenta la funciódesprèsd’avançarla cua.

T Acabament. A cadacridarecursiva fem méspetitala cua.

6.3 Immersió o generalitzaciód’una funció

La tècnicad’immersióesdesenvolupaperla necessitatdegeneralitzarunafuncióambl’objectiude, o bé poderfer un disseny recursiu,o bé transformarun disseny recursiuja fet per obtenirsolucionsrecursivesalternativesméssenzilleso eficients.

Unaimmersióésunageneralitzaciód’unafuncióon,o béintroduïmparàmetresaddicionals,o bé resultatsaddicionals,o les duescoses.Donatqueels paràmetrescanvien, l’especificaciótambécanviarà, i parlaremde reforçarla precondicióo afeblir la postcondició. Al final, perobtenirla funció quevolíemobtenir, o béesfixenelsparàmetresaddicionals,o béesrebutgenelsresultatsaddicionals.

La millor manerad’explicar la tècnicad’immersióésmitjançantexemples.A continuacióexplicaremun primertipusd’immersions,la immersióper dades, i enmostraremexemples.Alcapítolsegüentveuremexemplesd’immersionsper resultats.

26 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

6.3.1 Immersionsd’especificacionsper afebliment de la postcondició

Els exemplesqueveurema continuacióutilitzen immersionsambla finalitat depoderfer un al-goritmerecursiu(immersionsd’especificació).En cadascund’ells obtenimunafunció recursivaauxiliar ambparàmetresaddicionals,i la seva postcondicióésmésfeblequela dela funció quevolemresoldreoriginalment.

A continuaciódissenyaremunafunció quedonatun vectorno buit d’enters,ensretornalasumadetotselsseuselements.La sevaespecificacióésla següent.

int suma_vect_int(con st vector<int> &v)/* Pre: v.size()>0 *//* Post: el valor retornat és la suma de tots els elements del vector */

Si volemfer un disseny recursiud’aquestafunció,hauremdetenir unavariablequeretorna-remcoma resultaton anemsumantelsvalors,i encadaexecuciódela funció, sumarun valor icridarrecursivamenta la mateixafunció. El problemaésquequanfemla cridarecursivahauremdetenirunparàmetrequeensafiti la partdelvectorquehemdesumar. D’aquestaformaapareixunparàmetreextraambla finalitatderesoldreel problema,i la novaespecificacióseràla següent.

int i_suma_vect_int(c ons t vector<int> &v, int i)/* Pre: v.size()>0, 0<=i<v.size() *//* Post: el valor retornat és la suma de v[0..i] */

Hem afegit un paràmetreenteraddicionali , ambla finalitat d’afitar els valorsdel vectorasumar. En aquestcasensserveix per indicar queel valor retornatés la sumade tots els ele-mentsde v fins la posició i . Noteu que la nova postcondicióés mésfeble que la original,perquèhi hav.size() parellsd’entersquela compleixen(cadavaloren [0.. v.size()-1 ] ila corresponentsumaparcial),mentrequeaquellanomésescompleixambun d’aquestsparells(v.size()-1 i la sumatotal). La implementaciódela funcióésla següent.

int i_suma_vect_int(c ons t vector<int> &v, int i)/* Pre: v.size()>0, 0<=i<v.size() *//* Post: el valor retornat és la suma de v[0..i] */{

int suma;if(i==0) suma=v[0];else {

suma=i_suma_vect_ int (v, i- 1);/* HI: "suma" és la suma de v[0..i-1] */suma+=v[i];

}return suma;

}

Justificaremaquestcodi recursiude la mateixamaneraqueels exemplesanteriors. Noteuquetotafunció obtingudafentunaimmersióperafeblimentdela postcondiciótindràla mateixaestructuraqueaquesta,pertantla seva justificaciós’assemblaràforça.

6.3. IMMERSIÓ O GENERALITZACIÓ D’UNA FUNCIÓ 27

U Cassenzill. Si i=0 , la postcondiciórequereixobtenirla sumadev[0..0] , queòbviamentésv[0] i seràel queretornarem.

U Casrecursiu. Si i!=0 , la sumade v[0..i] queensdemanena la postcondicióequivala la sumade v[0..i-1] mésv[i] . Per hipòtesid’inducció, la primerapart ja l’hemobtingutambla crida recursiva i val suma. En conseqüència,el resultata retornarseràsuma+v[i] . La crida ésvàlida perquèsi i!=0 i apliquemla precondicióensquedaque0<i o equivalentment0<=i-1 . L’accésa v[i] éscorrecteperla precondició.

U Acabament. A cadacridarecursivael valord’i esfaméspetit.

Finalmenthemderesoldreel problemaoriginalambunacridaa la funció V W X�Y�Z [H\�] ^ V _�^ .Ho femambel valor v.size()-1 al lloc d’i , quecompleixla precondicióperquèv.size()>0 .

int suma_vect_int(con st vector<int> &v)/* Pre: v.size()>0 *//* Post: el valor retornat és la suma de tots els elements del vector */{

return i_suma_vect_int(v, v. siz e() -1) ;}

A continuaciópresentemunexempleonesrealitzaunacercadicotòmicaenunvectornobuiti ordenatd’enters.L’especificacióésla següent:

int cerca_vect_int(co nst vector<int> &v, int n)/* Pre: v.size()>0, v està ordenat de menor a major *//* Post: Si n es troba a v, llavors el valor retornat és una posició ones troba l’element n dins del vector v. Si n no es troba a v, llavors elvalor retornat és -1. */

Si volem fer una implementaciórecursiva d’aquestalgoritme,necessitaremintroduir dosparàmetresaddicionalsamblafinalitatd’indicarl’inici i el final dela zonaoncercareml’element.A continuaciópresenteml’especificaciói implementaciódela funciód’immersió.

int i_cerca_vect_int( con st vector<int> &v, int n, int esq, int dre)/* Pre: v.size()>0, v està ordenat de menor a major,

0 <= esq <= v.size(), -1 <= dre < v.size(), esq <= dre+1 *//* Post: Si n es troba a v[esq...dre], llavors el valor retornat ésuna posició de v entre els valors esq i dre on es troba el valor n.Si n no es troba a v[esq...dre], el valor retornat és -1 */{

int posicio;if(dre < esq) posicio= -1;else{

int mig = (dre+esq)/2;

28 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

if (v[mig]==n) posicio=mig;else{

if (v[mig]< n) posicio= i_cerca_vect_int(v, n, mig+1, dre);/* HI: Si n es troba a v[mig+1...dre], llavors el valorretornat és una posició de v[mig+1...dre] on es troba n.Si n no es troba a v[mig+1...dre], el valor retornat és -1. */

else posicio=i_cerca_ve ct_ int (v, n, esq, mig-1);/* HI: Si n es troba a v[esq...mig-1], llavors el valorretornat és una posició de v[esq...mig-1] on es troba n.Si n no es troba a v[esq...mig-1], el valor retornat és -1. */

}}return posicio;

}

A continuaciómostremla justificacióinformaldela funció i_cerca_vect_int .` Casossenzills.

1. Si dre < esq llavors l’interval ésbuit, n no es trobaa v[esq..dre] i per tant lapostcondicióensdiu queel valora retornarhadeser a 1.

2. Si v[mig]=n , llavorsla postcondicióensdiu queel valora retornarhadesermig .

` Casosrecursius. Si l’interval del vector no és buit i n no es troba a mig , llavors nestàal subvectorv[esq..mig-1] o al subvectorv[mig+1..dre] o no hi ésal subvec-tor v[esq..dre] . Donatquev estàordenatde menora major, si v[mig] < n hemdecercaral subvectorv[mig+1..dre] , i sinó al subvectorv[esq..mig-1] . En qualsevoldelsdoscasos,la hipòtesid’induccióensdiu quela cridarecursivacorresponentretornaràel valor demanaten la postcondició,queseràunaposicióon estroban al subvectoro -1si no hi és.

Hemdedemostrarquel’accèsav[mig] éscorrectei queescompleixenlesprecondicionsdelescridesrecursives,enparticular, lesdesigualtatsqueafectenalsparàmetresenters.Hihaun parelldepropietatsfonamentals,quesón:si Prei esq b�c dre llavorsesq b�c mig imig b�c dre. A partir d’elles,lesdeméssurtenfàcilment.

La demostraciódetotesduesesbasaconcretamentenquesi 0 b�c esq b�c dre, llavors

a) esqc 2 d esqe 2 fhg esqi drej e 2 c mig

b) mig ckg esqi drej e 2 f 2 d dree 2 c dre

` Acabament.A cadacridarecursiva el subvectora tractarésméspetit, ésa dir, decreixladistància g dre i 1 a esqj . Aquestaexpressióésnaturalperquèesq f dre i 1 forma partde la precondició. Noteu que g dre a esqj no és unabonafunció de fita en aquestcas,perquèno garanteixun valor naturalen totselscasosquecompleixen la precondició.Eldecreixements´obtéapartir delesduespropietatsanteriors.

6.3. IMMERSIÓ O GENERALITZACIÓ D’UNA FUNCIÓ 29

– dre l 1 mon mig l 1pDq dre l 1 m esqr dre m mig q dre l 1 m esqr esq q mig l 1 resqs mig, queéscertcomhemvist abans,pera)

– n mig m 1p�l 1 m esq q dre l 1 m esqr mig m esq q dre l 1 m esqr mig q dre l 1 rmig s dre, queéscertcomhemvist abans,perb)

Finalmentpresentemel codi de la funció quevolíemresoldre,queconsisteixenunacridaala funció d’immersiófixant els paràmetresaddicionalsa la primerai última posiciódel vector.Noteucomlestresdesigualtatsdela precondiciódela cridaescompleixentrivialment.

int cerca_vect_int(co nst vector<int> &v, int n)/* Pre: v.size()>0, v està ordenat de menor a major *//* Post: Si n es troba a v, llavors el valor retornat és una posició ones troba l’element n dins del vector v. Si n no es troba a v, llavors elvalor retornat és -1. */{

return i_cerca_vect_int(v , n, 0, v.size()-1);}

6.3.2 Immersionsd’especificacióper reforçamentde la precondició

Aquestmètodeconsisteixen obtenirunafunció auxiliar ambla precondicióoriginal reforçadaambunaversiófebledela postcondicióoriginal, demaneraques’escurciel camícapa la con-secuciódel’objectiu quemarcala postcondició.Aquestaversióafeblidadela postcondicióques’afegeixala precondicióhadepodercomplir-sefàcilment,ja quela primeracridarecursivaestàobligadaacomplir-la.

Comexempleenpresentaremundela seccióanterior, i donaremunasoluciódiferent.Utilit-zaremel problemadesumarelselementsd’un vector.

int suma_vect_int(con st vector<int> &v)/* Pre: v.size()>0 *//* Post: el valor retornat és la suma de tots els elements del vector */

Unamaneraderesoldreel problemaconsisteixenafegir unparàmetrea la funcióquecontin-gui ja unasumaparcialdelselementsdel vector, deformaquecadaexecuciódela nova funcióafegeixi unaltreelementa la sumaparcial.D’aquestamanera,a la precondicióseli afegeixpartde la informacióde la postcondició,i esfa mésfàcil arribara aquesta.Perúltim, necessitaremun paràmetreextra queensindiqui fins a quinaposiciódel vectorhemcalculatla sumaparcial.La funciód’immersióésla següent:

int ii_suma_vect_int( con st vector<int> &v, int i, int suma_parcial)/* Pre: v.size()>0, 0<=i<=v.size(), suma_parcial = suma de v[0..i-1] *//* Post: el valor retornat és la suma de tots els elements del vector v */{

int suma;

30 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

if(i==v.size()) suma=suma_parcial;else suma= ii_suma_vect_int(v, i+1 ,su ma_parci al+ v[ i]) ;return suma;

}

Aquestcodi recursiutambées justifica de la mateixamaneraque els exemplesanteriors.Noteuquetota funció obtingudafent unaimmersióper reforçamentde la precondiciótindrà lamateixaestructuraqueaquesta,pertantla seva justificaciós’assemblaràforça.

t Cassenzill. Si i=v.size() , per la precondiciótenima suma_parcial la sumade tot elvector, queésel queensdemanena la postcondiciói seràel queretornarem.

t Casrecursiu. Si i!=v.size() , sabemper la precondicióde la funció que i<v.size() ,pertanti+1<=v.size() . La restadelaprecondiciódelacridarecursivaquedasuma_parcial= suma dels elements de v[0..i] o equivalentmentsuma_parcial = suma delselements de v[0..i-1] + v[i] . Per la precondicióde la funció, la primerapart latenimala suma_parcial original,demaneraqueel nouvalordesuma_parcial hauràdesersuma_parcial+v[i] . L’accésav[i] éscorrecteja quela precondiciódiu que0<=i i,comja hemvist, i<v.size() .

t Acabament. A cadacridarecursivael valordev.size()-i esfaméspetit.

Finalmentresolemel problemaqueenshavíemplantejatdela següentmanera:

int suma_vect_int(con st vector<int> &v)/* Pre: v.size()>0 *//* Post: el valor retornat és la suma de tots els elements del vector */{

return ii_suma_vect_int(v ,0 ,0) ;}

Comveiem,enla cridaa la funció uFu v wFxHy z�{�| } u ~F} desde v w�x�y zH{�| } u ~�} , escompleixtrivialmentla precondició,donatquela sumaparcialqueconsideremésla d’un subvectorbuit.

6.3.3 Exemplesd’immersions amb accions

Consideremel següentexercici: donatun entermésgranquezero,s’hand’obtenir totselsseusdivisors naturalsmenorsque ell en una pila ordenadaen ordre decreixent desdel cim. Perexemple,si n=24 , la pila hadeser[Cim] 12 8 6 4 3 2 1 [Últim] .

Especifiquemel problema(a partir d’ara,quandiem“divisor” volemdir “divisor natural”).Recordeuque les funcionsrecursivesqueretornenobjectescomplexos tenenuna ineficiènciaocultaperculpade lesassignacionsi lesdestruccions,demaneraquefaremservir accionsperaquestcas.

6.3. IMMERSIÓ O GENERALITZACIÓ D’UNA FUNCIÓ 31

void divisors(int n, stack<int> &p)/* Pre: n>0, p és buida *//* Post: p conté els divisors d’n menors que ell, en ordre decreixent des del cim */

Noteuquehemde recòrreral disseny per immersió,ja quesi intentemun disseny recursiudirecteveuremquenohi haunamanerasenzilladetrobarunacridarecursiva(ambunparàmetremenorquen) queensajudi a obtenirunapartdelsdivisorsquepoguemcompletarambd’altresinstruccions.

En canvi, podemplantejarla següentimmersióperafeblimentdela postcondició,demaneraanàlogaalsexemplesanteriors

void i_divisors(int n, stack<int> &p, int m)/* Pre: n>=m>0, p és buida *//* Post: p conté els divisors d’n menors que m, en ordre decreixent des del cim */

Comprovemquel’operacióoriginal espotprogramarmitjantçantunacridaa l’auxiliar

void divisors(int n, stack<int> &p)/* Pre: n>0, p és buida *//* Post: p conté els divisors d’n menors que ell, en ordre decreixent des del cim */{

i_divisors(n,p,n);}

La cridaa i_divisors éscorrectaperquèescompleixla precondició:comquen>0 , llavorsn>=n>0 ; tambéescompleixquep ésbuida. A més,la postcondicióde la cridagaranteixquep“contéelsdivisorsd’n menorsquen (ésa dir, ell mateix),enordredecreixentdesdel cim”, queesel mateixquela postoriginal.

Ara faltadissenyar el codi de i_divisors . Podemaplicarel mateixesquemaquelesante-riors immersionsper afeblimentde la postcondició:un casdirecteon estractala situacióméssenzilla(enaquestcas,m=1) i uncasrecursiuquefaciunacridaambdecreixementdelvalord’m:

void i_divisors(int n, stack<int> &p, int m)/* Pre: n>=m>0, p és buida *//* Post: p conté els divisors d’n menors que m, en ordre decreixent des del cim */{

if (m > 1) {i_divisors(n,p,m- 1);/* HI: p conté els divisors d’n menors que m-1, en ordre decreixent des del cim */if (n%(m-1) == 0) p.push(m-1);

}}

La justificacióessimilar a la delsexemplesanteriorsdel mateixtipus (com ja havíem dit),ambla particularitatdetractar-sed’unaacció.

32 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

� Cassenzill. Si m=1, no hi hacapdivisord’n menorquem, pertantno s’had’afegir resa p.

� Casrecursiu. Si m>1, la col.lecciódedivisorsd’n menorsquemestàformadapelsdivisorsd’n menorsquem-1, mésm-1 si aquestésdivisord’n. Obtenimla primeraparta p amblacrida,perhipòtesid’inducció,i li empilemm-1 encasnecessari.

La cridaésvàlidaperquèsi m>1 i apliquemla precondicióensquedaquen>=m-1>0 . Ladivisió perm-1 ésvàlidaperla mateixaraó.

� Acabament. A cadacridarecursivael valord’mesfaméspetit.

Passemaraa obtenirunaimmersióperenfortimentdela precondicióperaquestmateixpro-blema.La ideaésquel’auxiliar rebi unapila ambunapartdelsdivisorsja calculats.

void ii_divisors(int n, stack<int>& p, int m)/* Pre: n>=m>0, p conté els divisors d’n menors que m, en ordre decreixent des del cim *//* Post: p conté els divisors d’n en ordre decreixent des del cim */

Comprovemquerealmentéstractad’unaimmersiódel’original

void divisors(int n, stack<int> &p)/* Pre: n>0, p és buida *//* Post: p conté els divisors d’n menors que ell, en ordre decreixent des del cim */{

ii_divisors(n,p,1);}

La cridaa ii_divisors éscorrectaperquèescompleixla precondició:comquen>0 , llavorsn>=1>0 . A més,no hi hacapdivisord’n méspetit que1, pertantp hadeserbuida,peròaixò escompleixtambéperla preoriginal.

La postcondicióde la crida és la mateixaque la post original (com a tota immersióperenfortimentdela precondicióbendefinida).

Ara faltadissenyar el codi de ii_divisors . Podemaplicarel mateixesquemaquelesan-teriorsimmersionsperenfortimentde la precondició:un casdirecte(enaquestcas,m=n) on eselsparàmetresja portencalculatsels resultatsdefinitiusi un casrecursiuqueensapropaal casdirecte:

void ii_divisors(int n, stack<int>& p, int m)/* Pre: n>=m>0, p conté els divisors d’n menors que m, en ordre decreixent des del cim *//* Post: p conté els divisors d’n en ordre decreixent des del cim */{

if (m<n){if (n%m == 0) p.push(m);ii_divisors(n,p,m +1) ;

}}

6.3. IMMERSIÓ O GENERALITZACIÓ D’UNA FUNCIÓ 33

Aquestcodi recursiutambéesjustificadela mateixamaneraqueelsexemplesanteriorsdelmateixtipus.

� Cassenzill. Si m=n, perla precondiciótenima p totselsdivisorsden menorsqueell, queésel queensdemanena la postcondiciói, pertant,nocal fer res.

� Casrecursiu. Si m<n, sabemperlaprecondiciódel’operacióquen>m>0, pertantn>=m+1>0.La restade la precondicióde la cridarecursiva requereixquep continguila col.lecciódedivisorsd’n menorsquem+1o, equivalentment,quecontinguila col.lecciódedivisorsd’nmenorsquem, mésmadaltdetot si ésdivisorden. Perla precondiciódel’operació,la pri-merapartla tenima la p original,demaneraquenomésfaltaempilar-li mencasnecessari.Comquem>0, la divisió ésvàlida.

� Acabament. A cadacridarecursivael valorden-m ésun naturalqueesfaméspetit.

6.3.4 Relacióentrealgoritmesrecursiuslinealsfinals i algoritmes iteratius

Diem queun algoritmeésrecursiulineal, si cadacridarecursiva generademaneradirecta,ésadir dinsdela mateixaactivació,solamentuna(o cap)cridarecursiva. Unafunciórecursivalinealésfinal si l’última instruccióques’executaésla crida recursiva i el resultatde la funció éselresultatques’haobtingutdela cridarecursiva,sensecapmodificació.

Perexemple,si miremlesduesmaneresderesoldreel problemadesumarelselementsd’unvectord’entersde les duesseccionsanteriors,les duesfuncionsrecursivesambimmersiósónlineals,peròunad’elles té recursivitat final i l’altre no. En la solucióde � � �F�H� ����� � � �F� perafeblimentdela postcondicióel valor retornatnoésdirectamentel resultatdela cridarecursiva,perquèhem de sumar ��� � � ; per tant, no tenim recursivitat final. En canvi, en la solució perreforçamentdela precondició,�F� � �F�H� ����� � � �F� , el valor queesretornaésdirectamentel queretornala cridarecursiva;enaquestcassí quetenimrecursivitat final.

Unapropietatquecompleixenmoltesvegadeselsalgoritmesambrecursivitat final ésquelapostcondiciódela cridarecursiva ésla mateixaquela postcondiciódela funció. En aquestcasdiemquela funció te postcondicióconstant.

Existeix unamaneradirectai molt simplede transformarun algoritmerecursiulineal finalambpostcondicióen un algoritmeiteratiu. Agafemel següentesquemageneralde recursivitatlineal final:

Tipus2 f(Tipus1 x)/* Pre: Q(x), x=X *//* Post: R(X,s) */{

Tipus2 s;

if (c(x)) s= d(x);else{

s= f(g(x));

34 CAPÍTOL 6. PROGRAMACIÓ RECURSIVA

}return s;

}

La condiciódel else(la del if negada)ésla condiciód’entradadel while . Lesinstruccionsinternesdel while sónlesdel elsesensela cridaa la funció recursiva; ésa dir, g � x� . Finalmentel valora retornardela funció iterativaescalculautilitzant lesinstruccionsdel if , ésa dir d � x� .Pertantl’esquemaiteratiuqueda:

Tipus2 f_iter(Tipus1 x)/* Pre: Q(x) *//* Post: R(x,s) */{

Tipus2 s;

while(not c(x)) x=g(x);

s= d(x);

return s;}

Tornemara l’exemplede sumarels elementsd’un vectord’enters. El codi recursiulinealfinal era:

int ii_suma_vect_int( con st vector<int> &v, int i, int suma_parcial)/* Pre: v.size()>0, 0<=i<=v.size(), suma_parcial = suma de v[0..i-1] *//* Post: el valor retornat és la suma de tots els elements del vector v */{

int suma;if(i==v.size()) suma=suma_parcial;else suma= ii_suma_vect_int(v, i+1 ,su ma_parci al+ v[ i]) ;return suma;

}

Aquestexemple té algunesdiferènciesrespecteal esquemarecursiude partida. A l’es-quema,suposàvem que teníemnomésun paràmetred’entrada,la � . En el nostrecastenimtres � , � i � �F�H� ���F��������� . Pertant,el conjuntd’instruccions��� ��� ésaraun conjuntd’instruc-cionsequivalentsperalstresparàmetres.Si miremla cridarecursiva ��� � ����� �H��� � � �F��� ��  �I¡¢   � ����� �H���H�F�F�F�L¡£��¤ � ¥ � veuremquela ��� ��� escorrespona sumar-li ��¤ � ¥ a � ����� ���F��������� , i asumar-li

¢a la � . El paràmetrev restaconstant.Amb la transformaciódescritaobtenim:

int ii_suma_vect_int_ ite r(c onst vector<int> &v, int i, int suma_parcial)/* Pre: v.size()>0, 0<=i<=v.size(), suma_parcial = suma de v[0..i-1] *//* Post: el valor retornat és la suma de tots els elements del vector v */

6.3. IMMERSIÓ O GENERALITZACIÓ D’UNA FUNCIÓ 35

{int suma;

while(i != v.size()){suma_parcial += v[i];++i;

}suma= suma_parcial;

return suma;}

Noteuquela precondiciónonomésescompleixal principi, sinótambéal entrari al sortir decadavoltadelbucle.Això vol dir queval comainvariant.Tambépodemaprofitarla demostraciódel decreiximent.

Si, a més,la funció recursiva vé d’una immersióper reforçamentde la precondiciód’unaaltra,comenaquestcas,on ii_suma_vect_int ésunaimmersióperreforçamentdela precon-dició desuma_vect_int , podemobtenirunaversióiterativadel’original, tansolstransformantelsparàmetresd’immersióenvariableslocals,inicialitzadesambelsvalorsde la cridaa la im-mersió.Coma simplificaciófinal, la variablequeguardaelscàlculsintermitjosespot substituirperla del resultatfinal a tot arreu,donatqueal final sel’assignemdetotesmaneres.

int suma_vect_int_ite r(c ons t vector<int> &v)/* Pre: v.size()>0 *//* Post: el valor retornat és la suma de tots els elements del vector v */{

int suma=0;int i=0;/* Inv: 0<=i<=v.size(), suma = suma de v[0..i-1] */while(i != v.size()){

suma += v[i];++i;

}

return suma;}