You are on page 1of 33

Mdulo 4

Unidad 4
Lectura 4

Materia: Taller de Algoritmos y Estructura de Datos I


Profesor: Marcelo Bianchi
4.1 Concepto de Recursin
Frases introductorias

Un mtodo parcialmente definido en trminos de s mismo recibe el


nombre de recursivo. (M.A. Weiss, Estructuras de Datos en Java-, 2000,
pag. 167)

La recursin es un concepto que trata de explicar el funcionamiento interno


de memoria de las computadoras que se da cuando se usa un mtodo
recursivo, es por ello que Weiss dice simplemente que la recursin consiste
en el uso de mtodos recursivos. Es una herramienta potente para producir
algoritmos cortos y eficientes.

Recuerde que para que un mtodo sea recursivo debe cumplir cuatro
condiciones bsicas y que se da la recursin cuando de forma directa o
indirecta hace una llamada a s mismo, es por ello que requiere de un corte
de control para que no quede en un bucle infinito.
Recursin
Por cada llamada a s mismo crea un nuevo hilo de ejecucin utilizando un
Un mtodo es recursivo nuevo lugar en memoria.
cuando directa o
indirectamente hace una La recursin es una herramienta muy potente de resolucin de problemas,
pero hay que saber cundo utilizarlos. Muchos algoritmos se pueden
llamada a s mismo
expresar de forma sencilla usando una formulacin recursiva, esto sucede
por ejemplo con las estructuras de datos avanzadas dinmicas rbol y grafo,
donde es necesario aplicar algoritmos recursivos en las operaciones.

Recursin Bsica
Una de las caractersticas importantes de las
estructuras de datos es la reutilizacin.

Recursin

No se debe crear una Abstraccin


lgica circular que lleve a
bucles infinitos.
VENTAJA
Independizar el diseo del programa de la
implementacin especifica de los datos

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 2


Fundamentos de la Recursin

INDUCCIN
Tcnica usada para demostrar teoremas que
cumplen los enteros positivos

Repasemos las cuatro reglas bsicas de la recursividad

Caso Base: se puede demostrar directamente. Es necesario que se


establezca cul es el caso base que se utilizar como corte de control de
la recursin.

Caso General: se demuestra que si el teorema es cierto para


determinados casos, se puede extender para incluir el siguiente caso. El
caso general es siempre una frmula que representa cualquier trmino
de una serie o sucesin numrica, la que utilizaremos en la condicin de
llamada a s mismo.

Ejemplo: Si es cierto para N = k, debe ser cierto para N = k + 1.

Puede Creerlo: Siempre tiene que asumir que la recursividad


funciona correctamente en memoria y resolver todos los algoritmos
que quedaron pendientes para salir por el caso base.

Regla de inters compuesto: nunca duplique trabajo


resolviendo la misma instancia de un problema en llamadas recursivas
separadas. El algoritmo siempre debe ser sencilla.

Debe recordar estas cuatro reglas bsicas cuando disee algoritmos


recursivos.

Algunas funciones matemticas se definen recursivamente.


S(N) //denota la suma de los primeros N enteros.

S(1) = 1 //caso base

S(N) = S (N-1) + N //caso general

El cdigo que lo resuelve es el siguiente:

public class recursionBasica {

public static long s( int n ) {

if( n==1 ) // caso base

return 1;

else // caso general

return s( n-1 ) + n;

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 3


La recursin siempre se puede implementar usando una pila, recuerde
que cada instancia de programa se ubica en un lugar distinto. El valor
de N inicial es siempre mayor o igual al del caso base y el nuevo valor
de cada llamada se acerca al del caso base.

La ejecucin del programa comienza con el primer valor de


N = 4, con cada llamada N disminuye en 1, hasta llegar a
N = 1 completando todos los clculos pendientes.

Problemas de la Recursin
Cada llamada recursiva genera una nueva instancia en la pila
del sistema.

Si se llama a s(0) el programa se ejecutar hasta quedarse sin


memoria ya que no habr progeso hacia el caso base.

If ( n == 0 ) return 0;

Lo mismo sucede si se llama a s(N) con N < 0

If ( n <= 0 ) return 0;

Si en la llamada S(N) el nmero N es demasiado grande, la


cantidad de instancias de la llamada recursiva puede hacer que
la pila del sistema agote la memoria.

El problema anterior no suceder si la funcin se implementa


con un bucle.

Ejemplo: Clculo de Factorial

En el siguiente ejemplo se utiliza recursin para calcular N! , siendo N! el


producto de los primeros N primeros enteros.
Caso base : 1!= 1

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 4


public class Factorial{

public static long factorial( int n ) {


if ( n<=1 ) //caso base
return 1;
else
return n * factorial( n-1 ); //caso general
}
}

Ejemplo: Rutina recursiva para imprimir un nmero N


en cualquier base entre 2 y 16.

Weiss plantea el siguiente ejemplo en el que se utiliza un vector de caracteres


para hacer ms sencilla la impresin de las cifras entre la a y la f, de tal forma
que la salida de cada dgito se realiza indexando el vector tablaDigitos.

final static String tablaDigitos = "0123456789abcdef";

public static void imprimeEntero (int n , int base) {


if ( n >= base )
imprimeEntero ( n / base, base);
System.out.print(tablaDigitos.charAt( n % base ));
}

Observaciones: ste cdigo no es robusto, puesto que:


si la base es mayor a 16 entonces el ndice de tablaDigitos estar fuera
del rango.
si la base es 0, se producir un error aritmtico al intentar dividir por
0.
Si la base es 1, la llamada recursiva dentro del if no progresa hacia el
caso base, puesto que los dos parmetros de la llamada recursiva son
idnticos a los de la llamada original, lanzando una excepcin.

Ejemplo: Rutina robusta para imprimir un nmero N


en cualquier base entre 2 y 16.
Solucin a los problemas anteriormente planteados

Podra pensarse como una posible solucin realizar una comprobacin


explcita del valor de base, sin embargo el problema de esta estrategia es que
dicha validacin se realizara en todas las llamadas recursivas y no solamente
en la primera. Para evitar esta ineficiencia, se utiliza una rutina gua que
comprueba el valor de base y luego llama a la rutina recursiva:

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 5


public class RutinaRobustaImpresionNumeros {
//Imprime n en cualquier base
static final String tablaDigitos = "0123456789abcdef";
private static final int maxBase = tablaDigitos.length();

//Rutina recursiva
public static void imprimeEnteroRec ( int n, int base) {
if ( n >= base )
imprimeEnteroRec ( n / base, base );
System.out.print( tablaDigitos.charAt(n % base));
}

//Rutina gua
public static void imprimeEntero ( int n , int base) {

if ( base <= 1 || base > maxBase )


System.err.println( "Base ilegal: " + base);
else {
if ( n < 0 ) {
n = -n;
System.out.print("-");
}
imprimeEnteroRec ( n, base );
}
}
}

Ejemplo: Sucesin de Fibonacci


Para la sucesin de Fibonacci en necesario tener en cuenta:
Progresin de Fibonacci: 1,2,3,5,8,13,21,
Tiempo para calcular fib(40) es excesivo
fib() lleva a cabo multitud de clculos repetidos

public class fibonacci {


..
public static long fib( int n ) {
if( n <= 1 )
return n;
else
return fib( n-1 ) + fib( n-2 );
}
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 6


Programacin Dinmica
Concepto

Se puede rescribir un algoritmo recursivo como un algoritmo no recursivo que


sistemticamente guarda las respuestas de los subproblemas resueltos en una
tabla.
La programacin dinmica consiste en una tcnica que hace uso de esta
estrategia

Ejemplo: Problema del Cambio de Monedas


Para una divisa con monedas C1, C2, , CN (Unidades) Cul es el
mnimo nmero de monedas que se necesitan para devolver K
unidades de cambio?
Mtodo ineficiente

Supongamos que siempre existe una moneda de 1 unidad, con lo que


nos aseguramos de que el problema tiene siempre solucin.

Una estrategia simple para reuinir K unidades consiste en usar la


recursin como sigue:

1. Si con una sola moneda ya podemos devolver el cambio solicitado,


sta (1) es la cantidad mnima de monedas.

2. En caso contrario, para cada posible valor i podemos calcular de


forma independiente el nmero mnimo de monedas que se
necesitan para reunir i y K i unidades, y elegimos el i que
minimice la suma de ambos.

El cdigo para esta situacin es el siguiente:

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 7


public class CambioMonedasIneficiente {

/**

* Devuelve el mnimo nmero de monedas para devolver un


cambio.

* Es un algoritmo recursivo simple muy ineficiente.

*/

public static int devolverCambio (int [] monedas, int cambio,

int monedasDistintas ) {

int minMonedas = cambio;

//Bsqueda de una moneda que reuna el cambio exacto.

For ( int i = 0; i < monedasDistintas; i++)

if( monedas[ i ] == cambio)

return 1;

//no hay ninguna; se resuelve recursivamente.

for ( int j = 1; j <= cambio / 2 ; j++ ) {

int monedasActuales =

devolverCambio( monedas, j , monedasDistintas ) +

devolverCambio ( monedas, cambio - j,


monedasDistintas );

if ( monedasActuales < minMonedas )

minMonedas = monedasActuales;

return minMonedas;

Problema

Si ejecutamos el algoritmo para cambiar cantidades pequeas,


funciona perfectamente, pero al igual que ocurra en el clculo
de los nmeros de Fibonacci hay demasiado trabajo
redundante.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 8


Solucin

La solucin a dicho problema consiste en reducir recursivamente el


tamao del problema especificando una primera moneda. Por ejemplo,
para 63 unidades, podemos dar cambio de cada una de las formas
siguientes:

Una moneda de 1 unidad ms el nmero de monedas


necesarios para cambiar 62 unidades, calculado de forma
recursiva.

Una moneda de 5 unidades ms el nmero de monedas


necesarios para cambiar 58 unidades, calculado de forma
recursiva.

Una moneda de 10 unidades, ms el nmero de monedas


necesarios para cambiar 53 unidades, calculado de forma
recursiva.

Una moneda de 21 unidades ms el nmero de monedas


necesarios para cambiar 42 unidades, calculado de forma
recursiva.

Una moneda de 25 unidades ms el nmero de monedas


necesarios para cambiar 38 unidades, calculado de forma
recursiva.

En lugar de generar 62 problemas recursivamente, ahora solamente


realizamos cinco llamadas recursivas, una por cada tipo distinto de
moneda. A pesar de ello siguie siendo ineficiente, puesto que se repiten
clculos.

Por lo anterior expuesto se guarda la solucin en un vector, siendo su


cdigo:

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 9


public class CambioMonedasEfiecente {

/**Algoritmo de programacin dinmica para el problema de la


devolucin del cambio. Como resultado, el vector monedasUsadas se
rellena con el mnimo nmero de monedas necesario para cambiar
desde 0 hasta maxCambio unidades, y ultimaMoneda contiene una de
las monedas necesarias para hacer el cambio. */

public static void devolverCambio( int [ ] monedas,

int monedasDistintas, int maxCambio,

int [ ] monedasUsadas, int [ ] ultimaMoneda) {

monedasUsadas [ 0 ] = 0; ultimaMoneda [ 0 ] = 1;

for ( int unidades = 1; unidades <= maxCambio; unidades ++) {

int minMonedas = unidades;

int nuevaMoneda = 1;

for( int j = 0; j < monedasDistintas; j++) {

if ( monedas [ j ] > unidades ) //No se puede usar la moneda j

continue;

if ( monedasUsadas [ unidades - monedas [ j ] ] + 1 <


minMonedas ) {

inMonedas = monedasUsadas [ unidades - monedas [ j ] ] +1;

nuevaMoneda = monedas [ j ];

monedasUsadas [ unidades ] = minMonedas;

ultimaMoneda [ unidades ] = nuevaMoneda;

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 10


4.2 Algoritmos de
Recursin
Divide y Vencers
Son las rutinas en la que el algoritmo contiene al menos dos llamadas
recursivas. Los algoritmos divide y vencers son algoritmos recursivos
y constan de dos partes:

Dividir: se resuelven recursivamente problemas ms


pequeos.

Vencer: la solucin al problema original se consigue a partir


de las soluciones a los subproblemas.

Ejemplo: El problema de la subsecuencia de suma


mxima.

En este ejemplo se deber encontrar, en una secuencia de nmeros, una


subsecuencia contigua de suma mxima. La secuencia que presenta el autor
es:
{4, -3, 5, -2, -1, 2, 6, -2}

Se procede a dividirla en dos mitades como se muestra en la tabla de abajo,


pudindose presentar 3 casos:
Caso 1: est totalmente incluida en la primera mitad.
Caso 2: est totalmente incluida en la segunda mitad.
Caso 3: empieza en la primera mitad y termina en la segunda.

Entonces se puede calcular para cada elemento de la primera mitad, la suma


de la subsecuencia continua que termina en el elemento situado ms a la
derecha. Observando la tabla, hacemos esto recorriendo de derecha a
izquierda, partiendo desde el elemento situado entre las dos mitades:

Primera Mitad Segunda Mitad


4 -3 5 -2 -1 2 6 -2 Valores
4* 0 3 -2 -1 1 7* 5 Sumas
Suma desde el centro
(*denota el mximo para cada mitad)

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 11


El cdigo para realizar el clculo es el siguiente:

public class DivideYVenceras {


/**
* Algoritmo de obtencin de la secuencia de suma mxima.
* Encuentra la suma mxima de un subvector a (izquierdo..derecho)-
* No intenta mantener la mejor secuencia.
*/
private static int maxSumRec ( int [ ] a, int izdo, int dcho ) {
int maxSumIzdaBorde = 0, maxSumDchaBorde = 0;
int sumIzdaBorde = 0, sumDchaBorde = 0;
int centro = ( izdo + dcho ) / 2;

if ( izdo == dcho ) //Caso base


return a [izdo] > 0 ? a[izdo] : 0;

int maxSumIzda = maxSumRec ( a, izdo, centro );


int maxSumDcha = maxSumRec ( a, centro + 1, dcho );
for ( int i = centro; i >= izdo; i-- ) {
sumIzdaBorde += a [ i ];
if (sumIzdaBorde > maxSumIzdaBorde)
maxSumIzdaBorde = sumIzdaBorde;
}
for ( int j = centro + 1; j <= dcho; j++ ) {
sumDchaBorde += a [ j ];
if ( sumDchaBorde > maxSumDchaBorde )
maxSumDchaBorde = sumDchaBorde;
}

return max3( maxSumIzda, maxSumDcha, maxSumIzdaBorde +


maxSumDchaBorde);
}

//Rutina visible pblicamente.


public static int maxSubSum4 ( int [ ] a)
{
return maxSumRec ( a, 0, a.length - 1 );
}

private static int max3(int maxSumIzda, int maxSumDcha, int i) {


throw new UnsupportedOperationException("Not yet implemented");
}
}

Bsqueda Binaria
Concepto

En la bsqueda binaria es un mtodo que requiere que el vector est


ordenado.

Recordemos su funcionamiento, se busca en un vector ordenado A


examinando el elemento del centro, si es el elemento que buscamos
hemos terminado. En caso contrario, si el elemento que buscamos es

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 12


menor que el elemento del centro buscamos a la mitad izquierda del
vector, y si es mayor, en la mitad derecha.

Ejemplo: Bsqueda Binaria


El cdigo es el siguiente utilizando una interfaz comparable y la rutina
recursiva de la bsqueda binaria:

public interface Comparable {

int compares( Comparable rhs );

boolean lessThan( Comparable rhs );

public class BusquedaBinaria {

/**Lleva a cabo la bsqueda binaria estndar

* usando dos comparaciones por nivel.

* Esta es una gupia que llama al mtodo recursivo. */

public static int busquedaBinaria ( Comparable [] a, Comparable X)


throws Exception {

return busquedaBinaria( a, X, 0, a.length -1 );

/** Rutina recursiva oculta. */

private static int busquedaBinaria ( Comparable [] a, Comparable X, int


inf, int sup) throws Exception {

if ( inf > sup )

throw new Exception ("La busqueda binaria falla");

int med = ( inf + sup ) / 2;

if ( a [ med ].compares ( X ) < 0)

return busquedaBinaria ( a, X, med + 1, sup);

else if ( a [ med ].compares (X) > 0)

return busquedaBinaria ( a, X, inf, med -1 );

else

return med;

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 13


Ejemplos Grficos
Ejemplo: Dibujar una Regla

El siguiente ejemplo consiste en un programa que dibuja las marcas de una


regla, es necesario contar con un mtodo paint() el cual puede llamar a otros
mtodos.
El mtodo pintaRegla es la rutina recursiva llamada desde pain, que hace uso
del mtodo drawLine, parte de la librera java, la cual la puede descargar si no
la tiene instalada e incorporarla en su proyecto. Este mtodo pinta una lnea
desde un punto de coordenadas (x,y).

Su cdigo es el siguiente:

import java.awt.Graphics;

public class Regla {

private void pintaRegla (Graphics g, int izdo, int dcho, int nivel) {

if (nivel < 1)

return ;

int med = ( izdo + dcho ) / 2;

g.drawLine (med, 80, med, 80 - nivel * 5);

pintaRegla(g, izdo, med - 1, nivel - 1);

pintaRegla(g, med + 1, dcho, nivel -1);

Ejemplo:
} Dibujar un Fractal
Qu es un fractal?

Un fractal es una figura geomtrica o semigeomtrica compuesta por


fragmentos en una infinita variedad de tamaos, tales que cada uno de ellos es
una copia reducida del total, la parte contiene al todo.

El trmino fue propuesto por el matemtico Benot Mandelbrot en 1975 y


deriva del Latn fractus, que significa quebrado o fracturado. Tambin existen
muchas estructuras naturales que son de tipo fractal como el girasol.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 14


http://www.letralia.com/ciudad/cardonagamio/63fractales.htm
(20/05/2012)

Si bien el trmino "fractal" es reciente, los objetos hoy denominados fractales


eran bien conocidos en matemticas desde principios del siglo XX.

En general los fractales estn caracterizados por la presencia de infinito


detalle, longitud infinita y la ausencia de suavidad o derivabilidad. Los
fractales son la geometra adecuada para las formas irregulares de la
Naturaleza.

Algunas imgenes de fractales son:

Tringulo de Sierpinski

http://www.dmae.upm.es/cursofractales/capitulo1/frames.htm
(20/05/2012)

Estrella fractal

Estructura de datos en Java Weiss pgina 181

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 15


Veamos el cdigo de la Estrella fractal

import java.awt.Graphics;

public class EstrellaFractal {


//Pinta el dibujo de una estrella fractal
private void pintaFractal (Graphics g, int xCentro,
int yCentro, int cotaDim) {
int lado = cotaDim / 2;
if ( lado < 1 )
return;
//Clculo de lsa esquinas.
int izdo = xCentro - lado / 2;
int sup = yCentro - lado / 2;
int dcho = xCentro + lado / 2;
int inf = yCentro + lado / 2;
//Dibujo recursivo de los cuatro cuadrantes.
pintaFractal( g, izdo, sup, cotaDim / 2 );
pintaFractal( g, izdo, inf, cotaDim / 2 );
pintaFractal( g, dcho, sup, cotaDim / 2 );
pintaFractal( g, dcho, inf, cotaDim / 2 );
//Dibuja el cuadrado central, que se solapa con los cuadrantes.
g.fillRect(izdo, sup, dcho - izdo, inf - sup);
}
}

Ejemplos de Aplicaciones Numricas


Se pueden aplicar algoritmos recursivos para resolver problemas de la
Teora de los Nmeros. La teora de los Nmeros es de especial inters
en el campo de la seguridad de los datos.

Ejemplo: Exponenciacin Modular

public class ExponenciacionModular {


/**
* Devuelve x^n (mod p)
* Suponiendo que p es positivo y potencia ( 0, 0, p) es 1
*/
public static long potencia ( long x, long n, long p ) {
if ( n == 0)
return 1;
long tmp = potencia ( ( x * x ) % p, n / 2, p);
if ( n % 2 != 0)
tmp = ( tmp * x ) % p ;
return tmp;
}
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 16


Ejemplo: Mximo Comn Divisor

public class MaximoComunDivisor {

//Devuelve el mximo comn divisor.

public static long mcd ( long a, long b) {

if ( b == 0 )

return a;

else

return mcd ( b, a % b );

Ejemplo: Rutina Para Determinar el Inverso


Multiplicativo
public class InversoMultiplicativo {

//Variables internas de mcdCompleto

private static long x;

private static long y;

/** Sigue hacia atrs el algoritmo de Euclides para encontrar x e y


tales que mcd ( a, b ) = 1, ax +by =1 */

private static void mcdCompleto ( long a , long b ) {

long x1, y1;

if ( b == 0 ) {

x = 1; //Si a != 1, no hay inverso.

y = 0; // Omitimos esta comprobacin.

Else {

mcdCompleto( b, a % b );

x1 = x ; y1 = y;

x = y1;

y = x1 - ( a / b ) * y1;
/**
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 17


//Resuelve ax == 1 ( mod n ), suponiendo que mcd ( a, n ) = 1.

public static long inverso ( long a, long n ) {

mcdCompleto( a, n );

return x > 0 ? x : x + n;

5. Ordenacin
5.1 Conceptos Bsicos
La ordenacin es una aplicacin muy importante en la computacin ya que la
mayora de los datos producidos por un programa estn ordenados de alguna
manera.
Esto posibilita cmputos ms eficientes, por ejemplo la bsqueda en un vector
ordenado es ms sencilla que en uno desordenado.
Adems, una ordenacin inicial de los datos puede mejorar significativamente
un algoritmo.

Ordenacin

La ordenacin posibilita Ejemplo de Ordenacin: Rutina Simple para la


cmputos y algoritmos ms
eficientes. Deteccin de Duplicados

public class DeteccionDuplicados {


// Devuelve true si el vector contiene duplicados; false en otro caso

public static boolean duplicados ( Object [ ] a ) {


for ( int i = 0 ; i < a.length; i++)
for ( int j = i + 1; j < a.length; j++)
if ( a [ i ].equals ( a [ j ] ))
return true; //Encontrado un duplicado

return false; // No hay duplicados

}
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 18


Alternativas:

Ordenar el vector primero.

Los elementos duplicados aparecern contiguos.

Costo est dominado por la ordenacin.

5.2 Ordenacin por


insercin
Es el mtodo ms simple de ordenacin, y se lo considera apropiado cuando
son pocos elementos a ordenar. Sin embargo consume demasiado tiempo y si
queremos ordenar una gran cantidad de elementos este algoritmo resulta una
pobre eleccin.

El cdigo del mtodo de ordenacin por insercin utilizando la interfaz


Comparable es el siguiente:

public interface Comparable {


int compares( Comparable rhs );

boolean lessThan( Comparable rhs );


}

public class ordenacionInsersion


{
// ordenacionPorInsercion: ordena el vector a.
private static void ordenacionPorInsercion ( Comparable [] a)
{
for ( int p = 1 ; p < a.length; p++ )
{
Ordenacin Por Insercin Comparable tmp = a [ p ];
int j = p;
Es un mtodo simple for ( ; j > 0 && tmp.lessThan ( a [ j - 1 ]); j--)
apropiado para pocos a [ j ] = a [ j - 1 ];
elementos a ordenar. a [ j ] = tmp;
}
}
}

Anlisis de la Ordenacin por Insercin.

El algoritmo de ordenacin por insercin es O(N2)

Esta cota es alcanzable, ya que un vector de entrada de


ordenacin ordenado en orden inverso requiere un tiempo
cuadrtico. ste representa el peor caso.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 19


Si el vector de entrada ya viene ordenado, el tiempo de
ejecucin es O (N), ya que el test del bucle for ms interno falla
inmediatamente. ste representa el mejor caso.

El caso medio merece especial atencin.

Todo algoritmo que ordene mediante intercambio


de elementos adyacentes requiere, en promedio,
un tiempo (N2)

El nmero medio de inversiones del vector de


partida es N (N 1 ) /4. Cada intercambio elimina
exactamente una inversin, por lo que el nmero
de intercambios necesarios es (N2).

Caso Medio

Una inversin en un vector de nmeros es todo par (i, j ) con i < j y Ai >
Aj

Ejemplo

Dada la secuencia { 8,5,9,2,6,3} contiene diez inversiones que


corresponden a los pares (8,5) (8,2) (8,6) (8,3)(5,2)(5,3)(9,2)
(9,6)(9,3) y (6,3).

Se observa que el nmero de inversiones del vector de partida es igual


al nmero de veces que se ejecuta la lnea a [ j ] = a [ j - 1 ]; Por lo tanto
si al principio del algoritmo tenemos I inversiones, debemos realizar I
intercambios implcitos.

Se pueden calcular cotas precisas del tiempo de ejecucin del caso medio
del algoritmo de ordenacin por insercin, calculando el nmero medio
de inversiones en el vector.

5.3 Shellsort
Creado por Donald Shell, si bien no es el ms rpido conocido, mejor
de forma sustancial la ordenacin por insercin.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 20


Implementacin de Shellsort

public interface Comparable {

int compares( Comparable rhs );

boolean lessThan( Comparable rhs );

public class shellsort {

/** Shellsort, utilizando la secuencia sugerida por Gonnet.

* divide por 2.2 en vez de 2. */

public static void shellsort ( Comparable [ ] a) {

for ( int intervalo = a.length / 2; intervalo > 0;

intervalo = intervalo == 2 ? 1 : (int) (intervalo / 2.2) )

for ( int i = intervalo; i < a.length; i++ ) {

Comparable temp = a [ i ];

int j = i;

for ( ; j >= intervalo && temp.lessThan ( a [ j - intervalo] );

j -= intervalo)

a [ j ] = a [ j - intervalo ];

a [ j ] = temp ;

Consideraciones de Shellsort
Uno de los intervalos debe ser 1. En general es el ltimo
intervalo y en este caso el algoritmo es idntico a la
ordenacin por insercin.

Lo expuesto anteriormente garantiza que el vector se


ordene correctamente.

Shell sugiri empezar con un intervalo N/2, luego dividir


por la mitad hasta llegar a 1. Ejemplo: N=12,
inrecrementos 5,3,1.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 21


Rendimiento de Shellsort

El tiempo de Shellsort depende de los incrementos elegidos.


El anlisis del caso medio de Shellsort es un problema abierto.
O(N3/2), con N potencia de 2.
Peor Caso: O(N2), con incrementos de Shell.
o O(N3/2), si los incrementos consecutivos son primos entre
s.
o Si i = intervalo/2 es par, entonces i += 1 para hacer
intervalo impar.
o Dividir por 2.2 da un rendimiento excelente en la
prctica.
o Tiempo de ejecucin medio: 0(N5/4).
o Mejoras del 25- 35% para 100000 N 1000000

5.4 Mergesort
Es un algoritmo divide y vencers que ofrece una mejor cota. Consta de
3 pasos:

1. Si el nmero de elementos es 0 o 1 , acabar.

2. Ordenar recursivamente las dos mitades del vector.

3. Mezclar las dos mitades ordenadas en un vector ordenado.

Este algoritmo es 0 (N log N ) debido a que la mezcla ordenada de los


dos vectores ordenados puede realizarse en tiempo lineal.

Mezcla lineal de Vectores ordenados


El algoritmo se apoya en 3 contadores.

En cada paso el menor de los valores se copia en el vector de


salida.

Cuando uno de los vectores de entrada se acaba, el resto del


otro vector se copia en el vector de salida.

El tiempo necesario para la mezcla lineal es de 0(N).

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 22


Implementacin de Mergesort
public void mergeSort( Comparable[] a ){

Comparable[] vtmp = new Comparable[ a.length ];

mergeSort( a, vtmp, 0, a.length 1 );

private void mergeSort( Comparable[] a, Comparable[] vtmp, int izq, int


der ){

if( izq < der ){

int centro = ( izq + der ) / 2;

mergeSort( a, vtmp, izq, centro );

mergeSort( a, vtmp, centro+1, der );

mezclar( a, vtmp, izq, centro + 1, der );

}
}
private void mezclar( Comparable[] a, Comparable[] vtmp,

int izq, int der, int fin ){

int finIzq = der 1, aux = izq, numElem = finizq+1;

while( izq <= finIzq && der <= fin) //bucle principal

if( a[izq].menorQue( a[der] ) )

vtmp[aux++] = a[izq++];

else

vtmp[aux++] = a[der++];

while( izq < finIzq) //copiar resto primera mitad

vtmp[aux++] = a[izq++];

while( der < fin) //copiar resto segunda mitad

vtmp[aux++] = a[der++];

//copiar vector temporal en original

for( int i=0; i<numElem; i++, fin--)

a[fin] = vtmp[fin];

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 23


Consideraciones sobre MergeSort
Utiliza un espacio adicional lineal.
Esto representa una desventaja en la prctica.
Esta limitacin puede evitarse a costa de incrementar el
tiempo de ejecucin.
Rara vez se utiliza en ordenaciones en memoria.
Se puede implementar de manera no recursiva.

5.5 Quicksort
Es el algoritmo ms rpido conocido.

Su tiempo de ejecucin promedio es de O (N log N ).

Tiene un rendimiento cuadrtico en el peor caso, pero este caso


puede hacerse estadsticamente improbable con poco es
esfuerzo.

Este algoritmo es recursivo, constando de 4 pasos:

1. Si el nmero de elementos de S es 0 1, terminar.

2. Escoger un elemento cualquiera y de S. este elemento se


denominar pivote.

3. Hacer una particin de S {v} (el resto de elementos de


S) en dos grupos disjuntos: I = { x S {v } | x v } y D =
{x S {v } | x v}

4. Devolver el resultado de QuickSort(I), seguido de v y


seguido del resultado de QuickSort(D).

El pivote divide a los elementos del vector en dos grupos.

o Los menores que el pivote

o Los mayores que el pivote

Cualquier elemento puede ser pivote.

Los elementos iguales que el pivote (duplicados) van a una u


otra particin, pero siempre a la misma.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 24


Implementacin de QuickSort con particin con la
mediana de tres y un lmite para detectar vectores
pequeos.

public interface Comparable


{
int compares( Comparable rhs );

boolean lessThan( Comparable rhs );


}

public class QuickSort {


private static void insertionSort( Comparable [ ] a, int low, int high )
{
for( int p = low + 1; p <= high; p++ ) {
Comparable tmp = a[ p ];
int j;

for( j = p; j > low && tmp.lessThan( a[ j - 1 ] ); j-- )


a[ j ] = a[ j - 1 ];
a[ j ] = tmp;
}
}
/**
* Quicksort algorithm.
* @param a an array of Comparable items.
*/
public static void quicksort( Comparable [ ] a ) {
quicksort( a, 0, a.length - 1 );
}

private static final int CUTOFF = 10;

/**
* Method to swap to elements in an array.
* @param a an array of objects.
* @param index1 the index of the first object.
* @param index2 the index of the second object.*/
public static void swapReferences( Object [ ] a, int index1, int index2 ) {
Object tmp = a[ index1 ];
a[ index1 ] = a[ index2 ];
a[ index2 ] = tmp;
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 25


/**
* Internal quicksort method that makes recursive calls.
* Uses median-of-three partitioning and a cutoff of 10.
* @param a an array of Comparable items.
* @param low the left-most index of the subarray.
* @param high the right-most index of the subarray. */
private static void quicksort( Comparable [ ] a, int low, int high ) {
if( low + CUTOFF > high )
insertionSort( a, low, high );
else {
// Sort low, middle, high
int middle = ( low + high ) / 2;
if( a[ middle ].lessThan( a[ low ] ) )
swapReferences( a, low, middle );
if( a[ high ].lessThan( a[ low ] ) )
swapReferences( a, low, high );
if( a[ high ].lessThan( a[ middle ] ) )
swapReferences( a, middle, high );

// Place pivot at position high - 1


swapReferences( a, middle, high - 1 );
Comparable pivot = a[ high - 1 ];

// Begin partitioning
int i, j;
for( i = low, j = high - 1; ; ) {
while( a[ ++i ].lessThan( pivot ) );
while( pivot.lessThan( a[ --j ] ) );
if( i < j )
swapReferences( a, i, j );
else
break;
}

// Restore pivot
swapReferences( a, i, high - 1 );

quicksort( a, low, i - 1 ); // Sort small elements


quicksort( a, i + 1, high ); // Sort large elements
}
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 26


Implementacin de QuickSort de seleccin rpida
con particin con la mediana de tres y un lmite para
detectar vectores pequeos.

public interface Comparable {


int compares( Comparable rhs );

boolean lessThan( Comparable rhs );


}

public class QuickSelect


{
/**
* Method to swap to elements in an array.
* @param a an array of objects.
* @param index1 the index of the first object.
* @param index2 the index of the second object.
*/
public static void swapReferences( Object [ ] a, int index1, int index2 ) {
Object tmp = a[ index1 ];
a[ index1 ] = a[ index2 ];
a[ index2 ] = tmp;
}
/**
* Internal insertion sort routine for subarrays
* that is used by quicksort.
* @param a an array of Comparable items.
* @param low the left-most index of the subarray.
* @param n the number of items to sort.
*/
private static void insertionSort( Comparable [ ] a, int low, int high ) {
for( int p = low + 1; p <= high; p++ ) {
Comparable tmp = a[ p ];
int j;

for( j = p; j > low && tmp.lessThan( a[ j - 1 ] ); j-- )


a[ j ] = a[ j - 1 ];
a[ j ] = tmp;
}
}
private static final int CUTOFF = 10;
/**
* Internal selection method that makes recursive calls.
* Uses median-of-three partitioning and a cutoff of 10.
* Places the kth smallest item in a[k-1].
* @param a an array of Comparable items.
* @param low the left-most index of the subarray.
* @param high the right-most index of the subarray.
* @param k the desired rank (1 is minimum) in the entire array.
*/

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 27


private static void quickSelect( Comparable [ ] a, int low, int high, int k ) {
if( low + CUTOFF > high )
insertionSort( a, low, high );
else {
// Sort low, middle, high
int middle = ( low + high ) / 2;
if( a[ middle ].lessThan( a[ low ] ) )
swapReferences( a, low, middle );
if( a[ high ].lessThan( a[ low ] ) )
swapReferences( a, low, high );
if( a[ high ].lessThan( a[ middle ] ) )
swapReferences( a, middle, high );

// Place pivot at position high - 1


swapReferences( a, middle, high - 1 );
Comparable pivot = a[ high - 1 ];
// Begin partitioning
int i, j;
for( i = low, j = high - 1; ; ) {
while( a[ ++i ].lessThan( pivot ) )
;
while( pivot.lessThan( a[ --j ] ) )
;
if( i < j )
swapReferences( a, i, j );
else
break;
}

// Restore pivot
swapReferences( a, i, high - 1 );

// Recurse; only this part changes


if( k <= i )
quickSelect( a, low, i - 1, k );
else if( k > i + 1 )
quickSelect( a, i + 1, high, k );
}
}

Comparacin entre QuickSor y MergeSort


Ambos utilizan recursin.
QuickSort vs MergeSort
Ambos requieren un trabajo adicional ( Mezcla vs. Particin).
El paso de la particin
(QuickSort) se hace ms Quicksort no garantiza que el tamao de los subproblemas sea
el mismo, sin embargo el paso de particin se hace ms rpido
rpido que el de mezcla que el de mezcla.
(MergeSort).
o No se necesita un vector auxiliar

o El cdigo es compacto y eficiente.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 28


Anlisis de QuickSort
Caso Medio
El costo de QuickSort en promedio es de O(N log N).

Cada problema es en promedio la mitad del original.

El costo medio se obtiene haciendo la media de los costes,


dividido todos los posibles tamaos de los subproblemas.

Seleccin del Pivote


El pivote debe ser elegido asegurando que el peor caso no se
presente nunca.

Forma ms popular: Primer elemento. ste es aceptable si la


entrada es aleatoria pero para entradas ordenadas la divisin es
pobre.

Eleccin segura: elemento central. Para entradas ordenadas es


el pivote perfecto.

Mejor Eleccin: particin con mediana de tres.

Particin con la mediana de tres

La mediana de un grupo de N elementos es el [N/2]-esimo


menor elemento.

La mediana garantiza una divisin uniforme de los elementos.

Su calculo ralentizar considerablemente el algoritmo.

o Muestreo: obtemenos la mediana del subconjunto.

o Una muestra de tamao 3 proporciona una mejora del


caso medio.

o Se utilizan el primer, el ltimo y el elemento central


para el clculo.

o Esto simplifica la fase de particin.

8 1 4 9 6 3 5 2 7 0

Entre 8, 6 y 0 elegimos 6 como pivote.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 29


Ejemplo: Ordenacin de un vector numrico.
public void quiksort(int x[],int lo,int ho) {

int t, l=lo, h=ho, mid;

if(ho>lo) {

mid=x[(lo+ho)/2];

while(l<h) {

while((l<ho)&&(x[l]<mid))

++l;

while((h>lo)&&(x[h]>mid))

--h;

if(l<=h) {

t = x[l];

x[l] = x[h];

x[h] = t;

++l;

--h;

if(lo<h)

quiksort(x,lo,h);

if(l<ho)

quiksort(x,l,ho);

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 30


Ejemplo: Ordenacin de un vector numrico (mtodo
con retorno)
public class QuickSortRetorno {
//Regresa un arreglo ordenado por medio del algoritmo quicksort

public int[] quicksort(int numeros[]) {


return quicksort(numeros,0,numeros.length-1);
}

public int[] quicksort(int numeros[],int izq,int der) {


if(izq>=der)
return numeros;
int i=izq,d=der;
if(izq!=der) {
int pivote;
int aux;
pivote = izq;
while(izq!=der) {
imprimeArreglo(numeros);

while(numeros[der]>=numeros[pivote] && izq<der)


der--;
while(numeros[izq]<numeros[pivote] && izq<der)
izq++;

if(der!=izq) {
aux = numeros[der];
numeros[der]= numeros[izq];
numeros[izq]=aux;
}
}
if(izq==der){
quicksort(numeros,i,izq-1);
quicksort(numeros,izq+1,d);
}
}
else
return numeros;
return numeros;
}

public void imprimeArreglo(int arreglo[]) {


String imp="";
for(int i=0;i<arreglo.length;i++) {
if(i!=arreglo.length-1)
imp=imp+arreglo[i]+",";
else
imp=imp+arreglo[i]+"";
}
System.out.println(imp);
}
}

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 31


Conclusin

Con lo visto en esta unidad usted est en condiciones de plantear


algoritmos recursivos que resuelvan situaciones ms complejas con
algoritmos simples, principalmente si son series o sucesiones matemticas.

En el planteo de solucin de un caso es posible que los datos los necesite


ordenados, para ello deber utilizar un mtodo de ordenacin, depender
de la cantidad de elementos y la cantidad de comparaciones que deba
realizar para saber qu algoritmo conviene, segn su costo.

La recursin no solo la aplicar en matemtica y en mtodos de ordenacin


y bsqueda, tambin lo aplicar en la resolucin de las operaciones con
estructuras de datos dinmicas no lineales como rbol y Grafo.

Espero que le haya sido de utilidad lo aprendido en este mdulo.

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 32


Bibliografa Lectura 4
Mark Allen Weiss, Estructuras de Datos en Java, ed. Addison Wesley. 2000.

Deitel y Deitel, Java cmo programar , sptima edicin, ed. Pearson, 2008.

Pressman Roger, (2006), Ingeniera de Software. Un enfoque prctico 6ta.


edicin, Ed. McGraw Hill

ORACLE, Oracle 10g: Programacin Java, Gua del Alumno, 2003

www.uesiglo21.edu.ar

Taller de Algoritmos y Estructuras de Datos I Marcelo Bianchi | 33

You might also like