1. El documento describe algoritmos divide y vencerás y sus ejemplos de encontrar el elemento mayoritario y ordenar con quicksort. 2. Estos algoritmos dividen recursivamente un problema en subproblemas más pequeños hasta alcanzar casos base triviales. 3. También compara estos algoritmos con enfoques alternativos como fuerza bruta o ordenar y buscar, señalando que divide y vencerás es más eficiente.
1. 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.
2. 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
3. *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
4. 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
5. 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;
6. 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.
7. 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;
}
8. 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í: