Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Fundamentos de Programacion - Algoritmia Basica PDF
Fundamentos de Programacion - Algoritmia Basica PDF
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
// 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;
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;
// 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
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() {
// 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.
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.
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]
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
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?
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 );
// 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
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.
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.