Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


Recursividad y algoritmos de búsqueda - Prof. López García, Apuntes de Economía

El concepto de recursividad en programación y cómo se aplica a algoritmos de búsqueda, como la búsqueda binaria. Se incluyen ejemplos en c y se comparan con algoritmos de repetición. El documento también incluye un ejemplo de la función recursiva factorial.

Tipo: Apuntes

2012/2013

Subido el 07/05/2013

msalvo-1
msalvo-1 🇪🇸

1 documento

1 / 10

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
UNITAT FORMATIVA 2: DISSENY MODULAR. Recursivitat
1
Activitat 5: Disseny modular . Recursivitat. .a
1. Disseny de funcions recursives.
1. Simplicació d’algorismes complexos usant recursivitat. .
A l’hora de crear programes complexos, un dels aspectes que diferencia el
bon programador de l’aficionat és la seva capacitat de fer algorismes eficients.
O sigui, que siguin capaços de resoldre el problema plantejat en el mínim de
passes.
En el cas d’un programa, això significa la necessitat d’executar el mínim
nombre d’instruccions possible. Certament, si el resultat ha de ser exactament
el mateix, sempre semillor fer una tasca en 10 passes que no pas en 20,
intentant evitar passes que en realitat són innecessàries. Per tant, l’etapa de
disseny d’un algorisme és força important i cal pensar una estratègia
eficient. Ara bé, normalment, els algorismes més eficients també són més
difícils de pensar i codificar, ja que no sempre són evidents.
Un exemple molt senzill d’això és la resolució del problema següent:
Suposeu que una amiga apunta un número entre el 0 i el 99 en un full de
paper i vosaltres l’heu d’endevinar. Cada cop que contesteu, us dirà si el valor
que heu dit és més gran o més petit que el que heu d’endevinar.
Quina estratègia seguiríeu per assolir-ho?
Cal pensar un algorisme a seguir per resoldre aquest problema. Una
aproximació molt ingènua podria ser anar dient tots els valors un per un,
començant pel 0. està clar que quan arribeu al 99 l’haureu endevinat. En el
millor cas, si havia escrit el 0, encertareu a la primera, mentre que en el pitjor
cas, si havia escrit el 99, necessitareu 100 intents. Si estava pel mig, potser
amb 40-70 n’hi ha prou. Aquest seria un algorisme que fa el fet i és molt senzill,
però no gaire eficient. Anar provant valors a l’atzar en lloc de fer això tampoc
millora gran cosa el procés, i ve a ser el mateix. De ben segur, si mai heu jugat
a aquest joc, el que heu fet és ser una mica més astuts i començar per algun
valor del mig. En aquest cas, per exemple, podria ser el 50. Llavors, en cas de
fallar, un cop sabeu si el valor secret és més gran o més petit que la vostra
resposta, en l’intent següent provar un valor més alt o més baix, i anar fent això
repetides vegades.
Generalment, la millor estratègia per endevinar un número secret entre 0 i N
seria:
Primer provar N/2. Si no s’ha encertat, llavors si el número secret és
més alt s’intenta endevinar entre (N/2 + 1) i N. Si era més baix, s’intenta
endevinar el valor entre 0 i N-1.
Per a cada cas, es torna a provar el valor que hi ha al mig del nou
interval. I així successivament, fent cada cop més petit l’interval de
cerca, fins a endevinar-lo.
pf3
pf4
pf5
pf8
pf9
pfa

Vista previa parcial del texto

¡Descarga Recursividad y algoritmos de búsqueda - Prof. López García y más Apuntes en PDF de Economía solo en Docsity!

Activitat 5: Disseny modular. Recursivitat. .a

**1. Disseny de funcions recursives.

  1. Simplicació d’algorismes complexos usant recursivitat..**

A l’hora de crear programes complexos, un dels aspectes que diferencia el bon programador de l’aficionat és la seva capacitat de fer algorismes eficients. O sigui, que siguin capaços de resoldre el problema plantejat en el mínim de passes.

En el cas d’un programa, això significa la necessitat d’executar el mínim nombre d’instruccions possible. Certament, si el resultat ha de ser exactament el mateix, sempre serà millor fer una tasca en 10 passes que no pas en 20, intentant evitar passes que en realitat són innecessàries. Per tant, l’etapa de disseny d’un algorisme és força important i cal pensar bé una estratègia eficient. Ara bé, normalment, els algorismes més eficients també són més difícils de pensar i codificar, ja que no sempre són evidents.

Un exemple molt senzill d’això és la resolució del problema següent:

Suposeu que una amiga apunta un número entre el 0 i el 99 en un full de paper i vosaltres l’heu d’endevinar. Cada cop que contesteu, us dirà si el valor que heu dit és més gran o més petit que el que heu d’endevinar.

Quina estratègia seguiríeu per assolir-ho?

Cal pensar un algorisme a seguir per resoldre aquest problema. Una aproximació molt ingènua podria ser anar dient tots els valors un per un, començant pel 0. està clar que quan arribeu al 99 l’haureu endevinat. En el millor cas, si havia escrit el 0, encertareu a la primera, mentre que en el pitjor cas, si havia escrit el 99, necessitareu 100 intents. Si estava pel mig, potser amb 40-70 n’hi ha prou. Aquest seria un algorisme que fa el fet i és molt senzill, però no gaire eficient. Anar provant valors a l’atzar en lloc de fer això tampoc millora gran cosa el procés, i ve a ser el mateix. De ben segur, si mai heu jugat a aquest joc, el que heu fet és ser una mica més astuts i començar per algun valor del mig. En aquest cas, per exemple, podria ser el 50. Llavors, en cas de fallar, un cop sabeu si el valor secret és més gran o més petit que la vostra resposta, en l’intent següent provar un valor més alt o més baix, i anar fent això repetides vegades.

Generalment, la millor estratègia per endevinar un número secret entre 0 i N seria:

  • Primer provar N/2. Si no s’ha encertat, llavors si el número secret és més alt s’intenta endevinar entre (N/2 + 1) i N. Si era més baix, s’intenta endevinar el valor entre 0 i N-1.
  • Per a cada cas, es torna a provar el valor que hi ha al mig del nou interval. I així successivament, fent cada cop més petit l’interval de cerca, fins a endevinar-lo.
  • En el cas de 100 valors, això garanteix que, en el pitjor dels casos, en 7 intents segur que s’endevina. Això és una millora molt gran respecte al primer algorisme, on calien 100 intents, i per tant, aquest seria un algorisme més eficient. Concretament, sempre s’endevinarà en log 2 (N) intents com a màxim.

Si us hi fixeu, l’exemple que tot just s’acaba d’explicar, en realitat, no és més que un esquema de cerca dins una seqüència de valors, com pot ser dins d’un array, partint de la condició que tots els elements estiguin ordenats de més petit a més gran. Aquest algorisme ja el coneixem com cerca binària, també es pot anomenar cerca dicotòmica.

1.1 Aplicació de la recursivitat

Malauradament, sovint us trobareu que explicar de paraula la idea general d’una estratègia pot ser senzill, però traduir-la a instruccions de C ja no ho és tant.

Atès que cal anar repetint unes passes en successives iteracions, està més o menys clar que el problema plantejat per fer cerques eficients es basa en una estructura de repetició. Però no es recorren tots els elements i l’índex no s’incrementa un a un, sinó que es va canviant a valors molt diferents per cada iteració. No és un cas evident. Precisament, aquest exemple no s’ha triat a l’atzar, ja que és un cas en què us pot anar bé aplicar un nou concepte que permet facilitar la definició d’algorismes complexos on hi ha repeticions.

La recursivitat és una forma de descriure un procés per resoldre un problema de manera que, al llarg d’aquesta descripció, s’usa el procés mateix que s’està descrivint, però aplicat a un cas més simple.

La millor estratègia per endevinar un número secret entre 0 i N seria primer provar N/2.

  • Si no s’ha encertat, llavors si el número secret és més alt s’intenta endevinar entre (N/2 + 1) i N.
  • Si era més baix, s’intenta endevinar el valor entre 0 i N-1.
  • Per a cada cas, es torna a provar el valor que hi ha al mig del nou interval. I així successivament, fins a endevinar-lo.

O sigui, el procés d’ endevinar un número es basa en el procés d’intentar endevinar un número!

Això sembla fer trampes, ja és com usar la mateixa paraula que es vol definir a la seva pròpia definició. Però fixeu-vos en un detall molt important. Els nous usos del procés d’ “endevinar” són casos més simples , ja que primer s’endevina entre N valors possibles, després entre N/2 valors, després entre N/4, etc. Aquest fet no és casual i d’ell depèn poder definir un procés recursiu de manera correcta.

Si ens fixem el factorial de un numero n mes gran que 2 es pot expressar com ell mateix per el factorial de n-1:

2! = 2 * 1! 3! = 3 * 2! 4! = 4 * 3! 5! = 5 * 4! 6! = 6 * 5! 7! = 7 * 6! .......

Normalment, la definició matemàtica d’aquesta operació es fa de manera recursiva:

  • cas base : 1! = 1
  • cas recursiu : n! = n*(n - 1)!

Així, doncs, fixeu-vos que el cas recursiu realitza un càlcul que depèn d’usar la pròpia definició de l’operació, però quan ho fa és amb un nou valor inferior a l’original, de manera que es garanteix que, en algun moment, es farà una crida recursiva que desembocarà en el cas base. Quan això passi, la cadena de crides recursives acaba. Una manera de veure això és crear el arbre de crides a la funció:

La seva implementació en C seria la següent. Ara bé, en aquest codi s’han afegit algunes sentències per escriure informació per pantalla, de manera que es vegi amb més detall com funciona un mètode recursiu. Veureu que, inicialment, es porten a terme un seguit d’invocacions del cas recursiu, un rere l’altre, fins que s’arriba a una crida que executa el cas base. És a partir de llavors quan, a mesura que es van executant les sentències return del cas recursiu, realment es va acumulant el càlcul. Una altra manera de veure-ho és depurant el programa.

int llegirNumeroPositiu ( char* , char*); long int factorial (int);

int llegirNumeroPositiu (char* missatge, char* missatgeError){ int n;

do{ puts(missatge); scanf(“%i”,&n); if (n<0) puts(missatgeError); }while (n<0);

return n; }

long int factorial (int n){ long int res;

if (n == 1) { //Cas base: Se sap el resultat directament

printf("Cas base: S’avalua a 1"); return 1;

}else { // Cas recursiu: invoca a la pròpia funció // El valor del nou paràmetre d’entrada ha de variar de // manera que es vagi aproximant al cas base

printf("Cas recursiu %i S’invoca el factorial”, n - 1); return = n * factorial(n 1);

} }

void main (void){ int n; long int fact;

n = llegirNumeroPositiu(“Factorial de: ”, “El numero ha de ser positiu.”); fact = factorial(n); printf(“El factorial de %li es: “,fact);

}

entre instruccions força més complexes que l’opció recursiva (un cop s’entén aquest concepte, és clar).

1.3 Recursivitat al joc de les torres de Hanoi:

Aquest exemple es una solució recursiva a l’antic joc de les torres de Hanoi, un dels exemples clàssics d’algorisme recursiu.

El joc parteix de tres bases A, B i C. En aquestes bases es poden posar discos, tots de mida diferent, de forma que mai pot estar un disc a sobre d'un altre disc més petit. Si tots els discos estan inicialment a la base A, el joc consisteix en traslladar-los d'un en un fins posar-los a la base C, mantenint sempre la regla que un disc mai estigui a sobre d'un altre més petit.

El següent programa implementa aquest procés amb la funció recursiva h anoi() :

#include <stdio.h> #include <stdlib.h>

void hanoi(int,char,char,char);

int main(){ int n;

system("clear"); printf("introduïu el nombre n de discos :"); scanf("%d",&n); fflush(stdin); hanoi(n,'A','C','B'); return 0; }

void hanoi(int n, char a, char c, char b){

if (n==1){ printf("%c->%c\t",a,c); return; } hanoi(n-1,A,B,C); hanoi(1,A,C,B); hanoi(n-1,B,C,A); }

Captura de l'execució del programa.

Cas base :

Si hi ha només un disc , el problema és trivial: és suficient moure l'únic disc d'A fins a C. Anomenarem a aquest algorisme hanoi(1,A,C,B), B no s’utilitza. Moviment: A -> C

A B C A B C

Cas recursiu:

En el cas que hi hagi dos discos , es pot fer amb tres moviments simples (cas base):

A->B, A-> C, B-> C, és a dir, tenim un procediment per passar dos discos d'A a C utilitzant B com ajuda. Anomenarem a aquest algoritme hanoi(2,A,C,B ) (passar dos discos d'A a C fent servir B).

hanoi(2,A,B,C) hanoi(1,A,B,C)

hanoi(1,A,C,B) hanoi(1,B,C,A)

En general, si hi ha n, es pot fer:

hanoi(n–1,A,B,C); hanoi(1,A,C,B); hanoi(n–1,B,C,A);

Això és justament el que fa la funció recursiva hanoi():

void hanoi ( int n, char a, char c, char b){

if (n==1){ printf("%c->%c\t",A,C); return; } hanoi(n-1,A,B,C); hanoi(1,A,C,B); hanoi(n-1,B,C,A); }

En aquesta funció, el cas base o d’escapament és el cas trivial n = 1 en el qual s'imprimeix el moviment.

L’arbre de crides a la funció hanoi:

hanoi(4,A,C,B)

hanoi(3,A,B,C,) hanoi(1,A,C,B) hanoi(3,B,C,A)

h(2,A,C,B) h(1,A,B,C) h(2,C,B,A) h(2,A,C,B) h(1,B,C,A) h(2,C,B,A)

h(1,A,B,C) h(1,C,A,B) h(1,B,C,A) h(1,A,B,C) h(1,A,C,B) h(1,C,B,A) h(1,B,A,C) h(1,A,C,B) h(1,B,C,A) h(1,A,B,C) h(1,C,A,B) h(1,B,C,A)

Si executem el programa amb el valor 4 ens mostra 15 moviments:

A->B A->C B->C A->B C->A C->B A->B A->C B->C B->A

C->A B->C A->B A->C B->C

Que es corresponent a les següents crides de la funció:

A->B A->C B->C A->B C->A C->B A->B A->C B->C B->A C->A B->C A->B A->C B->C