Está en la página 1de 13

Fundamentos de Programacin Tema 4: Algortmica bsica

1 Introduccin
Existen dos tipos de algoritmos: los iterativos (bsicamente, utilizan bucles para resolver los problemas), y recursivos (se llaman a s mismos para resolver problemas). En cuanto a funcionalidad, los algoritmos ms bsicos son los de bsqueda y ordenacin. En este documento se describirn dos algoritmos de este tipo, el de bsqueda binaria y el de ordenacin por insercin (y el de bsqueda lineal, casi no tenido en cuenta por simple). Posteriormente, se aplicarn como ejemplo para introducir los algoritmos de tipo recursivo.

2 Algoritmos iterativos
2.1 Bsqueda 2.1.1 Bsqueda lineal
La bsqueda lineal consiste en, dado un vector o matriz, recorrerlo desde el principio hasta el final, de elelemento en elemento. Supngase un vector que almacena cadenas: la bsqueda lineal consistir en revisar todos los elementos hasta encontrar el que se busca. Entonces, se devuelve su posicin, o -1 en caso de no ser encontrado.
ALGORITMO BsquedaLineal CONSTANTES MaxElementos : ENTERO = 100 TIPOS Vector = vector[MaxElementos] de ENTERO FUNCION busquedaLineal(E v: Vector, numBuscado, numElem: ENTERO) : ENTERO VARIABLES i: ENTERO INICIO i 0 MIENTRAS v[ i ] <> numBuscado Y i < numElem i i + 1 FIN_MIENTRAS SI v[ i ] = numBuscado busquedaLineal i SINO busquedaLineal -1 FIN_FUNCION VARIABLES v: Vector num: ENTERO pos: ENTERO INICIO { Preparar el vector } DESDE i 0 HASTA MaxElementos - 1 v[ i ] i * 2 FIN_DESDE { Buscar } LEER( num );

pos = busquedaLineal( v, num, MaxElementos ); { Informar} SI pos > -1 ESCRIBIR( Encontrado en , pos ) SINO ESCRIBIR( No encontrado ) FIN_ALGORITMO

El funcionamiento del algoritmo es obvio: primero se le da al vector de enteros valores iniciales, y despus se le pide un nmero al usuario. Si la bsqueda lineal devuelve -1, entonces es que no se encontr; en otro caso, se devuelve la posicin en la que se encontr. El nico problema que tiene este algoritmo es que depende directamente de la longitud del vector. En este caso, sea como sea la mquina donde se ejecute, su velocidad de ejecucin depender de MaxElementos. A continuacin, se incluye la implementacin en C++ estructurado de este algoritmo.
// Algoritmo BsquedaLineal #include <cstdio> const int MaxElementos = 100; int busquedaLineal(int v[], int numBuscado, int numElem) { int i = 0; while( v[ i ] != numBuscado && i < numElem ) { ++i; } if ( v[ i ] == numBuscado) return i; else return -1; } int main() { int int int int

i; num; pos; v[MaxElementos];

// Preparar el vector for(i = 0; i < MaxElementos; ++i) { v[ i ] = i * 2; } // Pedir el num y buscar printf( "Introduzca un nmero a buscar: " ); scanf( "%d", &num ); pos = busquedaLineal( v, num, MaxElementos ); // Informar if ( pos > -1 ) printf( "\nEncontrado en %d.\n", pos ); else printf( "\nNo encontrado.\n" ); } return 0;

2.1.2 Bsqueda binaria


La bsqueda binaria consiste en, dado un vector, comprobar la mitad del vector, con lo que, segn el elemento a buscar, estar en una de las mitades. Este proceso se repite con la mitad donde est el elemento, comprobando si el medio es el elemento buscado, y as sucesivamente hasta encontrarlo (de estar). sto implica una limitacin bsica para que este algoritmo pueda funcionar: el vector tiene que haberse ordenado previamente, sus elementos deben estar ordenados para poder elegir la mitad correcta en la que buscar a continuacin. Como el algoritmo anterior, devuelve la posicin o -1 en caso contrario.
ALGORITMO BsquedaBinaria CONSTANTES MaxElementos : ENTERO = 100 TIPOS Vector = vector[MaxElementos] de ENTERO FUNCION busquedaBinaria(E v: Vector, numBuscado, numElem: ENTERO) : ENTERO VARIABLES alto: ENTERO bajo: ENTERO medio: ENTERO INICIO alto = numElem; bajo = 0; medio = bajo + ( ( alto bajo ) / 2 ) MIENTRAS v[ medio ] <> numBuscado Y medio > bajo SI numBuscado > v[ medio ] bajo = medio SINO alto = medio FIN_SI medio = bajo + ( ( alto bajo ) / 2 ) FIN_MIENTRAS SI v[ medio ] = numBuscado busquedaLineal medio SINO busquedaLineal -1 FIN_FUNCION VARIABLES v: Vector num: ENTERO pos: ENTERO INICIO { Preparar el vector } DESDE i 0 HASTA MaxElementos - 1 v[ i ] i * 2 FIN_DESDE { Buscar } LEER( num ); pos = busquedaBinaria( v, num, MaxElementos ); { Informar} SI pos > -1 ESCRIBIR( Encontrado en , pos ) SINO ESCRIBIR( No encontrado ) FIN_ALGORITMO

El funcionamiento del algoritmo es obvio: primero se le da al vector de enteros valores iniciales, y despus se le pide un nmero al usuario. Si la bsqueda binaria devuelve -1, entonces es que no se encontr; en otro caso, se devuelve la posicin en la que se encontr. A continuacin, se incluye la implementacin en C++ estructurado de este algoritmo.
// Algoritmo de bsqueda binaria #include <cstdio> const int MaxElementos = 100; int busquedaBinaria(int v[], int numBuscado, int numElem) { int alto = numElem; int bajo = 0; int medio = bajo + ( ( alto - bajo ) / 2 ); while( v[ medio ] != numBuscado && medio > bajo ) { if ( numBuscado > v[ medio ] ) bajo = medio; else alto = medio; medio = bajo + ( ( alto - bajo ) / 2 ); } if ( v[ medio ] == numBuscado) return medio; else return -1;

int main() { int int int int

i; num; pos; v[MaxElementos];

// Preparar el vector for(i = 0; i < MaxElementos; ++i) { v[ i ] = i * 2; } // Pedir el num y buscar printf( "Introduzca un nmero a buscar: " ); scanf( "%d", &num ); pos = busquedaBinaria( v, num, MaxElementos ); // Informar if ( pos > -1 ) printf( "\nEncontrado en %d.\n", pos ); else printf( "\nNo encontrado.\n" ); return 0; }

La velocidad de ejecucin mejora ampliamente la del algoritmo anterior. Ahora, el tiempo de ejecucin slo depende del nmero de veces que el nmero total de elementos del vector pueda dividirse entre 2, es decir log2 MaxElementos. El nico problema que presenta es que es necesario

tener el vector ordenado previamente.

2.2 Ordenacin 2.2.1 Ordenacin por insercin


La ordenacin por insercin precisa del doble de memoria de la que el vector ocupa. En un segundo vector, en principio vaco (aunque del mismo tamao mximo que el primero), se van introduciendo los elementos del primero, secuencialmente, pero insertndolos de manera ordenada. Al final del recorrido del vector original, todos los elementos estarn ordenados en el segundo vector.
ALGORITMO OrdenacinPorInsercin CONSTANTES MaxElementos : ENTERO = 100 TIPOS Vector = vector[MaxElementos] de ENTERO PROCEDIMIENTO ordenaInsercion(E/S v: Vector, E numElem: ENTERO) VARIABLES i: ENTERO j: ENTERO k: ENTERO v2: Vector numOcupados: ENTERO INICIO numOcupados 0 { Preparar el vector } DESDE i 0 HASTA MaxElementos - 1 v2[ i ] v[ i ] FIN_DESDE { Insertar ordenadamente } DESDE i 0 HASTA numElem 1 { buscar la pos } j 0 MIENTRAS j < numOcupados Y v[ i ] > v2[ j ] j j + 1 FIN_MIENTRAS { desplazar el vector } DESDE k numOcupados HASTA j v[ k + 1 ] = v[ k ]; FIN_DESDE { insertar ordenado en vector } numOcupados numOcupados + 1 v[ j ] = v2[ i ]; FIN_DESDE FIN_FUNCION VARIABLES v: Vector num: ENTERO pos: ENTERO i: ENTERO INICIO { Preparar el vector } DESDE i 0 HASTA MaxElementos SI ( ( i mod 2 ) == 0 )

SINO FIN_DESDE

v[ i ] i / 2; v[ i ] i * 2

{ Ordenar } LEER( num ); ordenaInsercion( v, MaxElementos ); { Informar } DESDE i 0 HASTA MaxElementos ESCRIBIR( v[ i ] ) FIN_DESDE FIN_ALGORITMO

El funcionamiento del algoritmo es obvio: primero se le da al vector de enteros valores iniciales desordenados, y entonces el vector se ordena, mostrndole el resultado al usuario. A continuacin, se incluye la implementacin en C++ estructurado de este algoritmo.
// Algoritmo OrdenacinPorInsercin #include <cstdio> const int MaxElementos = 10; void ordenaInsercion(int v[], int numElem) { int posVorg; int posDesp; int posOrd; int v2[MaxElementos]; int numOcupados = 0; // Preparar el vector auxiliar for(posVorg = 0; posVorg < numElem; ++posVorg) { v2[ posVorg ] = v[ posVorg ]; } // Insertar ordenadamente for(posVorg = 0; posVorg < numElem; ++posVorg) { // buscar la pos posOrd = 0; while ( posOrd < numOcupados && v2[ posVorg ] > v[ posOrd ] ) { ++posOrd; } // desplazar el vector for(posDesp = numOcupados; posDesp >= posOrd; --posDesp) { v[ posDesp + 1 ] = v [ posDesp ]; } // insertar ordenado en vector ++numOcupados; v[ posOrd ] = v2[ posVorg ]; } } return;

int main() {

int int int int

i; num; pos; v[MaxElementos];

// Preparar el vector for(i = 0; i < MaxElementos; ++i) { if ( ( i % 2 ) == 0 ) v[ i ] = 120 + ( i / 2 ); else v[ i ] = 120 + ( i * 2 ); } // Pedir el num y buscar ordenaInsercion( v, MaxElementos ); // Informar for(i = 0; i < MaxElementos; ++i) { printf( "%d ", v[ i ] ); } printf( "\n" ); return 0; }

La velocidad de ejecucin mejora ampliamente la alternativa ms simple, que es el algoritmo de la burbuja (no explicado en este documento). Es necesario tener en cuenta, sin embargo, que este algoritmo es una variacin del original: se utiliza un vector de destino, en un principio vaco, para evitar en lo posible que la eficiencia dependa del cuadrado de MaxElementos. La velocidad de ejecucin del algoritmo de ordenacin por la burbuja depende de MaxElementos2, mientras que en este caso suele comportarse, en media, mucho mejor que el de la burbuja, si bien, en el peor de los casos (curiosamente, que el vector ya est ordenado, pero a la inversa), es cierto que tamben depende de MaxElementos2.

3 Algoritmos recursivos
Los algoritmos recursivos se denominan de esta forma debido a que se llaman a s mismos para completar una ejecucin. Hacer esto sin control de ningn tipo supondra un error de ejecucin, ya que el programa se quedara eternamente llamando a la misma funcin. Por eso, en un algoritmo recursivo es necesario prestar atencin a dos casos: El caso base, el que termina la recursin. El caso repetitivo, el que realiza la recursin.

3.1 Clculo del factorial


Un ejemplo tpico es el clculo del factorial de un nmero. El factorial de un nmero n, factorial( n ), puede entenderse como n * factorial( n 1 ), siempre y cuando se tenga en cuenta que factorial( 1 ) es 1, es decir, no provoca ninguna llamada recursiva. Este ltimo caso es el caso base. El primero, n n * factorial( n 1 ), es el caso repetitivo. El algoritmo recursivo de clculo del factorial sera, por tanto:
FUNCION factorial(E n: ENTERO) : ENTERO INICIO SI ( n == 1 ) factorial 1 SINO factorial n * factorial( n 1 ) FIN_SI FIN_FUNCION

Esta misma funcin, en C++ estructurado sera:


int factorial(int n) { if ( n == 1 ) return 1; else return n * factorial( n 1 ); }

La sucesin de llamadas si se lanzase la ejecucin con n = 4, sera la siguiente:

1: factorial( 4 ) 2: factorial( 3 ) 3: factorial( 2 ) 4: factorial( 1 )

Se detendra en factorial( 1 ) porque esta llamada no provoca ninguna llamada recursiva, sino que ya es el caso base: devuelve 1. Es ahora cuando se produce el primer retorno. La cuarta llamada devuelve 1, por lo que la tercera llamada factorial( 2 ), puede ya continuar su ejecucin, slo tiene que sustituir la expresin en la que estaba suspendida: n * factorial( n -1). n es 2, y la llamada recursiva acaba de devolver 1, por lo que la llamada tercera devuelve el resultado de multiplicar 2 * 1, a la segunda llamada, factorial( 3 ). Esta llamada se haba quedado suspendida mientras se calculaba factorial( 2 ), en la ya conocida expresin n * factorial( n -1), puesto que n es 3. La expresin, por tanto, queda finalmente como 3 * 2, que es lo que la segunda llamada devuelve a la primera, la que inici el proceso, que se haba quedado suspendida en el mismo punto que las otras. Para esta primera llamada n es 4, y factorial( 3 ) acaba de devolver 6, por lo que la expresin n * factorial( n -1), quedara como 4 * 6, que es el resultado que finalmente devolvera la primera llamada, la que inicio la ejecucin.

Figura 1: Representacin grfica del proceso recursivo del clculo factorial para n = 4

Un esquema grfico de este proceso puede verse en la figura 1, en el que las flechas punteadas representan las llamadas que se producen inicialmente, que dejan suspendidas a las funcione llamadoras, y las flechas slidas representan el proceso inverso que ocurre despus de que se produzca el primer retorno.

3.2 Multiplicacin de dos nmeros enteros positivos


Un ejercicio similar al anterior sera el de la suma. Supngase que no existe el operador '*', y por lo tanto la multiplicacin se debe programar. La versin iterativa de este ejercicio sera:
FUNCION multiplicacion(E a, b: ENTERO) : ENTERO VARIABLES i: ENTERO resultado: ENTERO INICIO resultado 0 DESDE i 0 HASTA b - 1 resultado resultado + a FIN_DESDE suma resultado FIN_FUNCION

Pero esta funcin podra transformarse en recursiva, si se tiene en cuenta que la multiplicacion( a, b ) puede entenderse recursivamente como a + multiplicacion( a, b 1 ).
FUNCION multiplicacion(E a, b: ENTERO) : ENTERO INICIO SI b = 0 multiplicacion 0 SINO multiplicacion a + multiplicacion( a, b 1 ) FIN_SI FIN_FUNCION

As, la multiplicacin de tres por cuatro supondra las siguientes llamadas recursivas (los valores entre corchetes son las operaciones que se realizan una vez llegados hasta la base de la recursin): multiplicacion( 3, 4 ) = 3 + multiplicacion( 3, 3 ) [=3 + 6] multiplicacion( 3, 3 ) = 3 + multiplicacion( 3, 2 ) [= 3 + 6] multiplicacion( 3, 2 ) = 3 + multiplicacion( 3, 1 ) [= 3 + 3] multiplicacion( 3, 1 ) = 3 + multiplicacion( 3, 0 ) [= 3 + 0] multiplicacion( 3, 0 ) [= 0]

La programacin del algoritmo en C++ estructurado es prcticamente directa:


int multiplicacion(int a, int b) { if ( b == 0 ) return 0; else return ( a + multiplicacion( a, b 1 ) ); }

3.3 Bsqueda binaria


La bsqueda binaria es, por su propia naturaleza, recursiva: se busca el elemento en una mitad, despus en otra, dentro de la misma, hasta que finalmente se encuentra (o no). La transformacin de este algoritmo en recursivo depende por tanto de las variables alto y bajo, que por tanto deben pasar a formar parte de los parmetros de la funcin.
FUNCION busquedaBinaria(E v: Vector, numBuscado, bajo, alto: ENTERO) : ENTERO VARIABLES medio: ENTERO INICIO medio = bajo + ( ( alto bajo ) / 2 ) SI v[ medio ] <> numBuscado SI medio == bajo medio -1 SINO SI numBuscado > v[ medio ] bajo = medio SINO alto = medio FIN_SI medio = busquedaBinaria( v, numBuscado, bajo, alto ); FIN_SI FIN_SI busquedaBinaria medio FIN_FUNCION

El modo de trabajo de esta funcin recursiva es el mismo que el de la versin iterativa, con la diferencia de que cada llamada se encarga de slo una mitad del vector donde buscar, y en caso de no encontrar el elemento buscado, realiza una llamada recursiva hacia otra de las mitades. El proceso, para un vector de cuatro posiciones, en el que se busca el 10, sera el siguiente: 5

10

15

20

busquedaBinaria( v, 10, 0, 4 ) [=1] busquedaBinaria( v, 10, 0, 2 ) [= 1]

En cursiva se marcan los valores que se retornan despus del retorno del caso base. Qu sucedera si el elemento a buscar fuese el 11?

busquedaBinaria( v, 10, 0, 4 ) [=-1] busquedaBinaria( v, 10, 0, 2 ) [=-1] busquedaBinaria( v, 10, 0, 1 ) [ =-1]

Ntese que al ser divisin entera (puesto que los miembros de la expresin son enteros), el resultado de dividir 1 / 2 es 0. A continuacin, se incluye el cdigo fuente en C++ estructurado:

// Algoritmo BusquedaBinariaRecursiva #include <cstdio> const int MaxElementos = 100; int busquedaBinaria(int v[], int numBuscado, int bajo, int alto) { int medio = bajo + ( ( alto - bajo ) / 2 ); if ( v[ medio ] != numBuscado ) { if ( medio == bajo ) { medio = -1; } else { if ( numBuscado > v[ medio ] ) bajo = medio; else alto = medio; } } } return medio; medio = busquedaBinaria( v, numBuscado, bajo, alto );

int main() { int int int int

i; num; pos; v[MaxElementos];

// Preparar el vector for(i = 0; i < MaxElementos; ++i) { v[ i ] = i * 2; } // Pedir el num y buscar printf( "Introduzca un nmero a buscar: " ); scanf( "%d", &num ); pos = busquedaBinaria( v, num, 0, MaxElementos ); // Informar if ( pos > -1 ) printf( "\nEncontrado en %d.\n", pos ); else printf( "\nNo encontrado.\n" ); return 0; }

4 Conclusiones
En este tema se han visto algoritmos bsicos de bsqueda y ordenacin, a la vez que se ha introducido la recursividad. Tal y como se ha visto, el resultado es el mismo en ambos casos, sea el algoritmo recursivo o iterativo, y su eficiencia terica no cambia. Un factor que s se debe tener en cuenta es que el algoritmo recursivo supone realizar llamadas a funciones en lugar de iteraciones de bucles, y en general lo primero es bastante ms complejo, y consume ms recursos (como memoria en la pila de llamadas) que lo segundo. Siendo as, es posible plantearse siquiera la necesidad de utilizar algoritmos recursivos. Las ventajas de estos algoritmos no radican en su eficiencia, sino en la superior abstraccin que se obtiene con ellos (es decir, el diseo de ciertos algoritmos complejos se hace ms sencillo).

Supngase el siguiente ejemplo: una calculadora debe evaluar expresiones como (6+(5*4)) / 2, de manera que siempre haya una pareja de parntesis antes de cada subexpresin, y no se incluyan espacios. Por supuesto este problema se puede resolver de manera iterativa, pero de manera recursiva la solucin es mucho ms simple, como se puede ver a continuacin:
ALGORITMO Calculadora FUNCION calcular(E operando1: ENTERO, operador: CARACTER, operando2: ENTERO) : ENTERO VARIABLES resultado: ENTERO INICIO { Prec. Operador = '*' O '/', O '+' O '-' } CASO DE operador '*': resultado '/': resultado '+': resultado '-': resultado FIN_CASO calcular resultado FIN_FUNCION operando1 operando1 operando1 operando1 * / + operando2 operando2 operando2 operando2

FUNCION evaluar(E expresion: Cadena, E/S pos: ENTERO) : ENTERO VARIABLES operando1: ENTERO operando2: ENTERO operador: CARACTER INICIO { tomar primer operando } SI expresion[ pos ] = '(' pos pos + 1 operando1 = evaluar( expresion, pos ) pos pos + 1 SINO operando1 = convertirCadenaNumero( expresion, pos ) FIN_SI { tomar operador } operador = expresion[ pos ] pos pos + 1 { tomar segundo operando } SI expresion[ pos ] = '(' pos pos + 1 operando2 = evaluar( expresion, pos ) pos pos + 1 SINO operando2 = convertirCadenaNumero( expresion, pos ) FIN_SI evalua calcular( operando1, operador, operando2 ) FIN_FUNCION

FUNCION convertirCadenaNumero( E entrada : CADENA, E/S pos:ENTERO) VARIABLES aux: CADENA INICIO MIENTRAS entrada[ pos ] >= '0' Y entrada[ pos ] <= '9' Y pos < longitud( cadena ) aux aux + entrada[ pos ] pos pos + 1 FIN_MIENTRAS convertirCadenaNumero convertirANumero( aux ) FIN_FUNCION FUNCION evaluarExpresion(E entrada: CADENA) : ENTERO VARIABLES pos: ENTERO INICIO pos 0 evaluarExpresion evaluar( entrada, pos ) FIN_FUNCION VARIABLES entrada: CADENA INICIO LEER( entrada ); ESCRIBIR( resultado, evaluarExpresion( entrada ) ) FIN_ALGORITMO

{ atoi( aux ) en C++ }

La funcin convertirANumero() se ha marcado en negrita porque depende del lenguaje de programacin utilizando. En C++, esta funcin sera exactamamente atoi(), (convierte una cadena con un nmero en su interior a ese nmero). Las llamadas recursivas que se produciran para una la expresin matemtica anterior, seran las indicadas en la figura 2.

Figura 2: Esquema de ejecucin de la calculadora: evaluar( 5 * 4) es la base de la recusin en este ejemplo.

Este pseudocdigo demuestra que los algoritmos recursivos ayudan a un diseo de ms alto nivel, sin preocuparse tanto del cmo hacerlo, sino de qu es lo que hay que hacer.

También podría gustarte