Algoritmos divide y vencerás

8
Algoritmos divide y vencerás Ejemplo 1: elemento mayoritario Ejemplo 2: algoritmo de ordenación quickSort Este tipo de algoritmos se usa en la resolución de problemas que pueden ser simplificados en problemas más sencillos, cuando hablo de simplificación del problema me refiero a reducir el volumen de datos manejados. Se pueden distinguir tres partes fundamentales: 1. Los casos base: son las sentencias que se ejecutarán cuando no sea posible reducir más el problema 2. La simplificación del problema: es un bloque de instrucciones cuya finalidad es dividir el problema en subproblemas menores del mismo tipo. 3. La parte recursiva: consiste en hacer una llamada a la propia función para que trabaje sobre los subproblemas obtenidos anteriormente. Ejemplo 1: Se quiere encontrar un algoritmo para obtener, si lo hay, el elemento mayoritario de una colección de números enteros. Se considera elemento mayoritario a aquel que aparece más de n/2 veces, donde n es la cantidad de elementos de la colección de datos. En la imagen se muestra el diagrama de flujo del algoritmo implementado para la resolución del problema.

Transcript of Algoritmos divide y vencerás

Page 1: Algoritmos divide y vencerás

Algoritmos divide y vencerás

Ejemplo 1: elemento mayoritario

Ejemplo 2: algoritmo de ordenación quickSort

Este tipo de algoritmos se usa en la resolución de problemas que pueden ser simplificados

en problemas más sencillos, cuando hablo de simplificación del problema me refiero a

reducir el volumen de datos manejados.

Se pueden distinguir tres partes fundamentales:

1. Los casos base: son las sentencias que se ejecutarán cuando no sea posible reducir

más el problema

2. La simplificación del problema: es un bloque de instrucciones cuya finalidad es dividir

el problema en subproblemas menores del mismo tipo.

3. La parte recursiva: consiste en hacer una llamada a la propia función para que

trabaje sobre los subproblemas obtenidos anteriormente.

Ejemplo 1: Se quiere encontrar un algoritmo para obtener, si lo hay, el elemento mayoritario de una

colección de números enteros.

Se considera elemento mayoritario a aquel que aparece más de n/2 veces, donde n es la

cantidad de elementos de la colección de datos.

En la imagen se muestra el diagrama de flujo del algoritmo implementado para la resolución

del problema.

Page 2: Algoritmos divide y vencerás

A continuación se muestra el código en Java correspondiente a la implementación del

algoritmo.

public class Mayoritario {

/**

* Excepción personalizada que se lanza cuando no hay elemento mayoritario

**/

public static class NoMayoritarioException extends Exception{

public NoMayoritarioException(){

super("No hay elementos mayoritarios");

}

}

/**

* Devuelve un entero correspondiente al elemento mayoritario de un

Page 3: Algoritmos divide y vencerás

*ArrayList de enteros.

*¡Ojo porque el contenido del ArrayList se pierde!

* @param cjto Si quiere conservar los datos pase una copia del original

* @return

* @throws mayoritario.Mayoritario.NoMayoritarioException

*/

public int calcMayoritario(ArrayList<Integer> cjto)throws NoMayoritarioException{

//Inicio de los casos base

if(cjto.size()<2){

throw new NoMayoritarioException();

}

switch(cjto.size()){

case 2:

if(cjto.get(0) ==cjto.get(1)) {

return cjto.get(0);

}

throw new NoMayoritarioException();

case 3:

if(cjto.get(0) ==cjto.get(1)||cjto.get(0) ==cjto.get(2)) {

return cjto.get(0);

}else if(cjto.get(2) ==cjto.get(1)){

return cjto.get(1);

}

throw new NoMayoritarioException();

}

if(cjto.size()==2){

}

//Selección de candidatos

ArrayList<Integer> candidatos;

candidatos = new ArrayList<>();

for(int i=0;i<cjto.size()-2;i+=3){

if(cjto.get(i) ==cjto.get(i+1) || cjto.get(i) ==cjto.get(i+2)){

candidatos.add(cjto.get(i));

}if(cjto.get(i+1) ==cjto.get(i+2)){

candidatos.add(cjto.get(i+1));

}

}

int len=cjto.size();

if(len%2==0 && cjto.get(len-2) ==cjto.get(len-1)){

candidatos.add(cjto.get(len-1));

}

//Aplico la recursión

if(candidatos.size()>=cjto.size()/2){

//Alivio un poco la carga espacial, con lo que se pierden los datos de partida

cjto=null;

return this.calcMayoritario(candidatos);

}else{

throw new NoMayoritarioException();

}

}

}

Comparación con otras alternativas

Page 4: Algoritmos divide y vencerás

Al aplicar este algoritmo en cada llamada a la función se recorre una sola vez la colección de

datos y de una a otra llamada el número de elementos es como mucho la mitad del

anterior.

Las alternativas más directas para resolver este problema sin usar un algoritmo de este tipo

pasarían por recorrer todos los elementos hasta encontrar uno que se repita más de n/2

veces. El caso más favorable es cuando el primer elemento es el mayoritario por lo que

tendrá complejidad lineal, pero en el resto de casos será cuadrática

Otra opción pasa por ordenar primero la colección, pero habría que tener en cuenta la

complejidad del algoritmo de ordenación.

Otra posibilidad sería utilizar un algoritmo dinámico que se apoyase en una colección de

posibles soluciones, donde cada elemento está compuesto por el valor y el número de

repeticiones. En ese caso se recorrería una sola vez la colección inicial y un máximo de n

veces, en el peor de los casos, la colección auxiliar para comprobar si el valor i-ésimo ya se

encuentra en la colección auxiliar e insertarlo o incrementar el valor de sus repeticiones. Por

lo que tanto la complejidad espacial como temporal serán mayores.

Ejemplo 2: algoritmo quickSort El algoritmo quickSort es uno de los casos más típicos de aplicación de algoritmos del tipo

divide y vencerás. Consiste en la ordenación de una colección indexada de objetos

dividiendo de forma recursiva la colección en subconjuntos que se caracterizan porque los

elementos del subconjunto anterior aun elemento dado, pivote, son menores que este y los

del subconjunto posterior son mayores. Al hacer subconjuntos de los subconjuntos de forma

recursiva se logra dividir el problema en muchos subproblemas triviales.

Cabe destacar que con este algoritmo no es necesario un paso posterior de mezcla para

obtener la colección ordenada.

La complejidad del algoritmo es cuadrática en el peor de los casos y nLogn en los casos

mejor y medio. El caso será mejor o peor en función de la distancia entre el pivote elegido y

la mediana del conjunto de datos, cuanto más alejado peor. En la implementación que se

muestra a continuación se escoge como pivote al primer elemento del subconjunto, por lo

que este algoritmo es susceptible de ser optimizado sin mucho esfuerzo, aunque hay que

tener en cuenta la complejidad del algoritmo de elección del pivote para estimar la eficiencia

Page 5: Algoritmos divide y vencerás

final del nuevo algoritmo.

public static void quickSort(int[] a,int prim, int ult){

if(prim<ult){

//Genero subconjuntos

int l=pivote(a,prim,ult);

//Aplico recursión sobre los subconjuntos

if(l>prim){quickSort(a,prim,l-1);}

if(l<ult){quickSort(a,l+1,ult);}

}//Caso base prim=ult

}

/**

* Devuelve la posición del pivote, elemento que por la izquierda solo

* tiene elementos de valor inferior y por la derecha de valor superior.

* Sobra decir que lo que hace es colocar los elementos a derecha o

* del pivote según su valor.

*/

private static int pivote(int[] a,int prim, int ult){

int i=prim+1;

int l=ult;

Page 6: Algoritmos divide y vencerás

while(a[i]<=a[prim] && i<ult){i++;}

while(a[l]>a[prim]){l--;}

while(i<l){

intercambia(a,i,l);

while(a[i]<=a[prim]){i++;}

while(a[l]>a[prim]){l--;}

}

intercambia(a,prim,l);

return l;

}

ALGORITMO POR FUERZA BRUTA

El algoritmo para una búsqueda binaria es método muy útil dentro de un arreglo unidimencional.

Generalmente se le da este nombre búsqueda binaria porque el algoritmo divide en dos el arregelo.

La única condición para que funcióne el algoritmo es que los datos estén ordenados de menor a

mayor.

El metodo mas facil para hacer esto es por la “Fuerza Bruta” Pero este método puede resultar

ineficiente cuando se tiene una gran cantidad de datos, ya que busca posición por posición hasta que

encuentra el dato que se requiere.

El código por fuerza bruta es muy sencillo.

[crayon lang="java"]

for(int i=0; i<Arreglo.length; i++)

if(Arreglo[i] == elemento)

System.out.println(“Elemento encontrado en la posicion: ” + i);

[/crayon]

Se recorre todo el arreglo y se verifica si la posición i es parecida o igual al dato que se busca, el

código anterior se puede mejorar simplemente agregandole una bandera, pero aun asi no es lo

suficientemente bueno.

1. Se declaran los índices superior e inferior. El inferior igual a 0 y el superior con el

tamaño del arreglo menor a 1.

2. Se calcula el centro del arreglo con la siguiente formula:

centro = (superior + inferior) / 2

3. Verificamos si el arreglo en la posición centro es igual al dato que se busca. Si es

igual significa que encontramos el dato y retornamos al centro.

4. Si son diferentes verificamos si el arreglo en la posición centro es mayor al dato que

que queremos buscar. Si es mayor actualizamos superior: superior = centro – 1, si

no actualizamos inferior: inferior = centro + 1.

5. Volvemos al paso 2.

Cuando ya no se cumpla la condicón del ciclo y no se encontro el dato retornamos -1 indicando que

el dato no se encuentra en el arreglo.

Page 7: Algoritmos divide y vencerás

Se tiene un arreglo {2, 3, 5, 7, 9, 11, 14, 18, 22, 25} y se quiere buscar el dato 18, entonces el

inferior toma el valor de 0 y superior el valor de 9 (ya que es tamaño del arreglo menos 1).

Calculamos el centro, centro = (superior + inferior) / 2

centro = (9 + 0) / 2

centro = 4

división entera(ignorando el decimal).

Arreglo en la posición centro es 9 (Arreglo[centro] = 9) ya que empieza de 0.

Comprobamos si arreglo en la posición centro es igual al dato que queremos buscar Arreglo[centro] =

dato, como Arreglo[centro] = 9 y dato = 18. no son iguales por lo tanto ahora verificamos si es

mayor, en este caso no lo es entonces actualizamos inferior = centro + 1, esto hace que podamos

descartar todos los datos del centro hacia atras, esto reduce nuestro arreglo a {11, 14, 18, 22, 25}. Y

así seguiremos hasta que Arreglo[centro] = dato.

Como se puede notar este método es bastante eficiente en arreglos grandes, por ejemplo supongamos

que tenemos un arreglo de 10000 datos, y estamos buscando un dato que se encuentra en la posición

6000, solo en la primera vuelta del ciclo ya se pueden descartar los primeros 5000 valores, en cambio

por fuerza bruta se tendrian que realizar esos 6000 ciclos para poder localizar el dato.

[crayon lang="Java"]

public class BusquedaBinaria

{

public static int busquedaBinaria(int[] Arreglo, int elemento)

{

int i = 0, centro = 0, posicion = 0, inferior = 0, superior = Arreglo.length-1;

while(inferior <= superior)

{

centro = (superior + inferior) / 2;

if (Arreglo[centro] == elemento)

return centro;

else

if (Arreglo[centro] > elemento)

superior = centro – 1;

else

inferior = centro + 1;

}

return -1;

}

Page 8: Algoritmos divide y vencerás

public static void main (String[] args)

{

java.util.Scanner Leer = new java.util.Scanner(System.in);

System.out.print(“Tamanio del arreglo: “);

int tamanioArreglo = Leer.nextInt();

int[] Arreglo = new int[tamanioArreglo];

for(int i=0; i<Arreglo.length; i++)

Arreglo[i] = Leer.nextInt();

System.out.print(“Elemento a buscar: “);

int elemento = Leer.nextInt();

int posicion = busquedaBinaria(Arreglo, elemento);

if(posicion == -1)

System.out.println(“\nElemento no encontrado”);

else

System.out.println(“\nElemento ” + elemento + ” encontrado en la posición ” + posicion);

}

}

[/crayon]

Si lo quieren probar directamente descarguen de aquí: