Ay DA Semana 5 Divide y Venceras

15
ANALISIS Y DISEÑO DE ALGORITMOS Cesar Liza Avila Divide y Vencerás

Transcript of Ay DA Semana 5 Divide y Venceras

ANALISIS Y DISEÑO DE ALGORITMOS

Cesar Liza Avila

Divide y Vencerás

Divide y VencerásEsquema general

1) Descomponer un problema en subproblemas más pequeños del mismo tipo con lo cual la recursión es una técnica adecuada.

2) Resolver cada subproblema.3) Combinar los resultados para construir la solución al

problema original.InicioSi el problema es pequeño resolverloSino descomponer en subproblemas para cada subproblema resolverlo combinarlosFin

Cuando el subproblemas es solo de un tamaño una unidad menor se llama simplificación o reducción.

Análisis de la complejidad de funciones DyV

Ciertamente el análisis de la complejidad de funciones recursivas es más complejo, debiendo utilizar las llamadas funciones de recurrencia, las cuales deben ser resueltas.

Sin embargo, las funciones DyV (divide y vencerás) que parte el problema en varios problemas mas pequeños y del mismo tipo se pueden resolver fácilmente aplicando el

llamado Teorema Maestro.

Teorema Maestro Θ(1), si n<=noT(n) aT(n/b) + Θ(nk), si n > no

Donde:a>=1b>1k>=0no>=0

Si el tamaño del problema es pequeño (n<=no), entonces se puede resolver en tiempo constante.

Si el tamaño del problema es grande (n>no), entonces dividimos el problema en a subproblemas de tamaño n/b.

El valor de a es el número de veces en que se llaman recursivamente, y n/b es el tamaño de la entrada en cada llamada.

El término Θ(nk), especifica la cantidad de tiempo requerida para realizar la descomposición, así como el tiempo requerido para combinarlas.Θ(nk)

si a<bk

Θ(nk log n ) si a=bk

Θ(nlog ba) si a>bk

Ejemplo: Potencia entera de un númeroEscriba un programa para elevar un número a una potencia entera, esto es:

Xn

# include <iostream.h>int pot(int, int);void main(void){ int b, e; cout<<"Base: "; cin>>b; cout<<“Exponente: "; cin>>e;

cout<<pot(b, e)<<endl;}int pot (int b, int e){ if (e==0) return 1; if (e==1) return b; if (e==2) return b*b;

int p=pot(b, e/2); if (e % 2 == 0) return p*p; else return p*p*b;}

¿usa “divide y vencerás” la siguiente función?

int pot2(int b, int e){ if (e==0) return 1; if (e==1) return b; return b*pot2(b,e-1);}

Código

Cálculo de la complejidadLa función pot( ), divide el problema en un subproblema de la mitad del tamaño (T(n/2) ), y en el peor de los casos la condicional efectuará 2 multiplicaciones (esto ocurre cuando e es impar).

1, si n=1T(n) T(n/2) + 2, si n>1

Por el teorema maestroΘ(nk) si a>bk

Θ(nk log n ) si a=bk Θ(nlog ba) si a<bk

Θ(1), si n<=noT(n) aT(n/b) + Θ(nk), si n > no

Comparando con

Tenemos:a=1b=2k=0 (ya que 2 es constante)

De donde la complejidad de pot( ) es Θ(log n)

Ejemplo: Subsecuencia de suma máxima

El problema del subsecuencia de suma máxima consiste en encontrar un secuencia cuya suma sea máxima dentro de un vector original.

-1 6 -2 5 -1 4 3 -4 3 1

La suma máxima se consigue desde los subíndices 1 al 6 y es 15

Solución mediante divide y vencerás (DyV)

Esto significa que hemos dividido el problema original en 3 subproblemas más

pequeños.

Dividimos el arreglo en dos partes.

La subsecuencia máxima puede estar en la primera mitad(lo resolvemos por recursión)

La subsecuencia máxima puede estar en la segunda mitad(lo resolvemos por recursión)

La subsecuencia máxima puede empezar en la primera mitad y terminar en la segunda(lo resolvemos calculando la subsecuencia máxima de la primera mitad pero que termine en el ultimo elemento de esa mitad y…… calculando la subsecuencia máxima de la segunda mitad pero que empiece en el ultimo elemento de esa mitad……las dos se unen para construir la subsecuencia de suma máxima).

# include <iostream.h>int sumaMax(int [], int, int);

void main(void){ int a[]={-1, 6, -2, 5, -1, 4, 3, -4, 3, 1}; cout<<"la suma es:" << sumaMax(a, 0, 9)<<endl;}

int sumaMax(int a[ ], int izq, int der){ if ( izq==der ) return a[izq]>0? a[izq]:0;

int centro=(izq+der)/2; // maxima suma de la 1ra mitad int maxSumIzq = sumaMax(a, izq, centro); // maxima suma de la 2da mitad int maxSumDer = sumaMax(a, centro+1, der); // maxima suma que empieza en una mitad y termina en la otra int sumIzqBorde = 0; int maxSumIzqBorde= 0; for(int i=centro; i>=izq; i--) { sumIzqBorde += a[i]; if ( sumIzqBorde > maxSumIzqBorde ) maxSumIzqBorde = sumIzqBorde; }

Código int sumDerBorde = 0; int maxSumDerBorde = 0; for ( int j=centro+1; j<=der; j++ ) { sumDerBorde += a[j]; if ( sumDerBorde > maxSumDerBorde ) maxSumDerBorde = sumDerBorde; }

// calcula la mayor suma entre maxSumIzq, // maxSumDer y la maxima suma que empieza // en una mitad y termina en otra int max =maxSumIzq; if( maxSumDer > max) max=maxSumDer;if( maxSumIzqBorde + maxSumDerBorde > max) max= maxSumIzqBorde+ maxSumDerBorde;

return max;} desde los subíndices 1 al 6

(modifique el prog para mostrarlo)

Cálculo de la complejidadLa función sumaMax( ), se hace 2 llamadas recursivas de la mitad de tamaño, y dos bucles que depende de n. Por lo que su recurrencia seria:

Θ(1), si n=1T(n) 2.T(n/2) + Θ(n), si n>1

Por el teorema maestroΘ(nk) si a<bk

Θ(nk log n ) si a=bk Θ(nlog ba) si a>bk

Θ(1), si n<=noT(n) aT(n/b) + Θ(nk), si n > no

Comparando con

Tenemos:a=2b=2k=1

De donde la complejidad de sumaMax( ) es Θ(nlog n)

Ejemplo: Calendario de un campeonatoSe tiene que programar los encuentros de un campeonato que tiene n participantes con la condición de que cada participante se enfrente a cada uno de los otros solo una vez.

Para simplificar el problema suponga que n es una potencia de 2.1 2 3 4 5 6 71 2 3 4 5 6 7 82 1 4 3 6 7 8 53 4 1 2 7 8 5 64 3 2 1 8 5 6 75 6 7 8 1 4 3 26 5 8 7 2 1 4 37 8 5 6 3 2 1 48 7 6 5 4 3 2 1

# include <iostream.h># include <iomanip.h># define COL 64

void torneo(int n, int tabla[][COL]);

void main(void){ int n, tabla[COL][COL];

cout<<"Nro de participantes (potencia de 2):"; cin>>n;

torneo(n, tabla);

for(int i=0; i<n; i++) { for(int j=0; j<n-1; j++)

cout<<setw(3)<<tabla[i][j]; cout<<endl; }}

Código void torneo(int n, int tabla[][COL]){ int i, j; if(n==2) { tabla[0][0]=2; tabla[1][0]=1; } else { // llena el cuadrante superior izquierdo torneo(n/2, tabla); // llena el cuadrante inferior izquierdo for (i=n/2; i<n; i++) for(j=0; j<n/2-1; j++) tabla[i][j]=tabla[i-n/2][j] + n/2; // llena el cuadrante superior derecho for(i=0; i<n/2; i++) for(j=n/2-1; j<n-1; j++)

if(i+j<n-1) tabla[i][j]= i + j + 2;

else tabla[i][j]= i + j - n/2 +

2; // llena el cuadrante inferior derecho for(i=n/2; i<n; i++) for(j=n/2-1; j<n-1; j++)

if(i>j) tabla[i][j] = i - j;else tabla[i][j] = i + n/2 - j;

}}

Cálculo de la complejidadLa función torneo( ), divide el problema en un problema de la mitad de tamaño y 3 partes más para completar la matriz.

Como ocurre una llamada recursiva, y como cada una de las 3 partes tiene una complejidad cuadrática, tendremos:

2, si n=2T(n) 1.T(n/2) + 3n2, si n>=2

Por el teorema maestroΘ(nk) si a<bk Θ(nk log n ) si a=bk

Θ(nlog ba) si a>bk

Θ(1), si n<=noT(n) aT(n/b) + Θ(nk), si n > no

Comparando con

Tenemos:a=1b=2k=2

De donde la complejidad de torneo( ) es Θ(n2)