Recursividad



Comments



Description

Recursividad -> El área de la programación es muy amplia y con muchos detalles.Los programadores necesitan ser capaces de resolver todos los problemas que se les presente a través del computador aun cuando en el lenguaje que utilizan no haya una manera directa de resolver los problemas. En el lenguaje de programación C, así como en otros lenguajes de programación, se puede aplicar una técnica que se le dio el nombre de recursividad por su funcionalidad. Esta técnica es utilizada en la programación estructurada para resolver problemas que tengan que ver con el factorial de un número, o juegos de lógica. Las asignaciones de memoria pueden ser dinámicas o estáticas y hay diferencias entre estas dos y se pueden aplicar las dos en un programa cualquiera. 1.-Recursividad: La recursividad es una técnica de programación importante. Se utiliza para realizar una llamada a una función desde la misma función. Como ejemplo útil se puede presentar el cálculo de números factoriales. Él factorial de 0 es, por definición, 1. Los factoriales de números mayores se calculan mediante la multiplicación de 1 * 2 * ..., incrementando el número de 1 en 1 hasta llegar al número para el que se está calculando el factorial. El siguiente párrafo muestra una función, expresada con palabras, que calcula un factorial. "Si el número es menor que cero, se rechaza. Si no es un entero, se redondea al siguiente entero. Si el número es cero, su factorial es uno. Si el número es mayor que cero, se multiplica por él factorial del número menor inmediato." Para calcular el factorial de cualquier número mayor que cero hay que calcular como mínimo el factorial de otro número. La función que se utiliza es la función en la que se encuentra en estos momentos, esta función debe llamarse a sí misma para el número menor inmediato, para poder ejecutarse en el número actual. Esto es un ejemplo de recursividad. La recursividad y la iteración (ejecución en bucle) están muy relacionadas, cualquier acción que pueda realizarse con la recursividad puede realizarse con iteración y viceversa. Normalmente, un cálculo determinado se prestará a una técnica u otra, sólo necesita elegir el enfoque más natural o con el que se sienta más cómodo. Claramente, esta técnica puede constituir un modo de meterse en problemas. Es fácil crear una función recursiva que no llegue a devolver nunca un resultado definitivo y no pueda llegar a un punto de finalización. Este tipo de recursividad hace que el sistema ejecute lo que se conoce como bucle "infinito". Para entender mejor lo que en realidad es el concepto de recursión veamos un poco lo referente a la secuencia de Fibonacci. Principalmente habría que aclarar que es un ejemplo menos familiar que el del factorial, que consiste en la secuencia de enteros. 0,1,1,2,3,5,8,13,21,34,..., Cada elemento en esta secuencia es la suma de los precedentes (por ejemplo 0 + 1 = 0, 1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, ...) sean fib(0) = 0, fib (1) = 1 y así sucesivamente, entonces puede definirse la secuencia de Fibonacci mediante la definición recursiva (define un objeto en términos de un caso más simple de sí mismo): fib (n) = n if n = = 0 or n = = 1 fib (n) = fib (n - 2) + fib (n - 1) if n >= 2 Por ejemplo, para calcular fib (6), puede aplicarse la definición de manera recursiva para obtener: Fib (6) = fib (4) + fib (5) = fib (2) + fib (3) + fib (5) = fib (0) + fib (1) + fib (3) + fib (5) = 0 + 1 fib (3) + fib (5) 1. + fib (1) + fib (2) + fib(5) = 4. + 0 + 1 + fib (2) + fib (3) = 5 + fib (0) + fib (1) + 1. + 1 + fib(0) + fib (1) + fib (5) = fib (3) = 2. + 0 + 1 + fib(5) = 3 + fib (3) + fib (4) = 5. + 0 + 1 + fib (1) + fib (2) = 6 + 1 + fib (0) + fib (1) = 3 + 1 + fib (0) + fib (1) + fib (4) = 6. + 0 + 1 = 8 3. + fib (1) + fib (2) + fib (4) = Obsérvese que la definición recursiva de los números de Fibonacci difiere de las definiciones recursivas de la función factorial y de la multiplicación. La definición recursiva de fib se refiere dos veces a sí misma. Por ejemplo, fib (6) = fib (4) + fib (5), de tal manera que al calcular fib (6), fib tiene que aplicarse de manera recursiva dos veces. Sin embargo calcular fib (5) también implica calcular fib (4), así que al aplicar la definición hay mucha redundancia de cálculo. En ejemplo anterior, fib(3) se calcula tres veces por separado. Sería mucho más eficiente "recordar" el valor de fib(3) la primera vez que se calcula y volver a usarlo cada vez que se necesite. Es mucho más eficiente un método iterativo como el que sigue parar calcular fib(n). If (n < = 1) for (i = 2; i < = n; i ++) hifib = x + lofib ; return (n); { } /* fin del for*/ lofib = 0 ; x = lofib ; return (hifib) ; hifib = 1 ; lofib = hifib ; Compárese el número de adiciones (sin incluir los incrementos de la variable índice, i) que se ejecutan para calcular fib (6) mediante este algoritmo al usar la definición recursiva. En el caso de la función factorial, tienen que ejecutarse el mismo número de multiplicaciones para calcular n! Mediante ambos métodos: recursivo e iterativo. Lo mismo ocurre con el número de sumas en los dos métodos al calcular la multiplicación. Sin embargo, en el caso de los números de Fibonacci, el método recursivo es mucho más costoso que el iterativo. 2.- Propiedades de las definiciones o algoritmos recursivos: Un requisito importante para que sea correcto un algoritmo recursivo es que no genere una secuencia infinita de llamadas así mismo. Claro que cualquier algoritmo que genere tal secuencia no termina nunca. Una función recursiva f debe definirse en términos que no impliquen a f al menos en un argumento o grupo de argumentos. Debe existir una "salida" de la secuencia de llamadas recursivas. Si en esta salida no puede calcularse ninguna función recursiva. Cualquier caso de definición recursiva o invocación de un algoritmo recursivo tiene que reducirse a la larga a alguna manipulación de uno o casos más simples no recursivos. 3.- Cadenas recursivas: Una función recursiva no necesita llamarse a sí misma de manera directa. En su lugar, puede hacerlo de manera indirecta como en el siguiente ejemplo: a (formal parameters) b (formal parameters) b (arguments); a (arguments); {{ .. .. } /*fin de a*/ } /*fin de b*/ En este ejemplo la función a llama a b, la cual puede a su vez llamar a a, que puede llamar de nuevo a b. Así, ambas funciones a y b, son recursivas, dado que se llamas a sí mismo de manera indirecta. Sin embargo, el que lo sean no es obvio a partir del examen del cuerpo de una de las rutinas en forma individual. La rutina a, parece llamar a otra rutina b y es imposible determinar que se puede llamar así misma de manera indirecta al examinar sólo a a. Pueden incluirse más de dos rutinas en una cadena recursiva. Así, una rutina a puede llamar a b, que llama a c, ..., que llama a z, que llama a a. Cada rutina de la cadena puede potencialmente llamarse a sí misma y, por lo tanto es recursiva. Por supuesto, el programador debe asegurarse de que un programa de este tipo no genere una secuencia infinita de llamadas recursivas. 4.- Definición recursiva de expresiones algebraicas: Como ejemplo de cadena recursiva consideremos el siguiente grupo de definiciones: a. una expresión es un término seguido por un signo más seguido por un término, o un término solo b. un término es un factor seguido por un asterisco seguido por un factor, o un factor solo. c. Un factor es una letra o una expresión encerrada entre paréntesis. Antes de ver algunos ejemplos, obsérvese que ninguno de los tres elementos anteriores está definido en forma directa en sus propios términos. Sin embargo, cada uno de ellos se define de manera indirecta. Una expresión se define por medio de un término, un término por medio de un factor y un factor por medio de una expresión. De manera similar, se define un factor por medio de una expresión, que se define por medio de un término que a su vez se define por medio de un factor. Así, el conjunto completo de definiciones forma una cadena recursiva. La forma más simple de un factor es una letra. Así A, B, C, Q, Z y M son factores. También son términos, dado que un término puede ser un factor solo. También son expresiones dado que una expresión puede ser un término solo. Como A es una expresión, (A) es un factor y, por lo tanto, un término y una expresión. A + B es También vuelve a colocar pos en la posición que sigue en la expresión de mayor longitud que puede encontrar. length y ppos. respectivamente. A * B es un término y. términos y factores. int length. length. A continuación se codificará un programa que lea e imprima una cadena de caracteres y luego imprima "valida" si la expresión lo es y "no valida" de no serlo. sin embargo la cadena A + * B. length representa el número de caracteres en str. c = str [*ppos]. Si pos > = length. return (FLASE). printf ("%s". De manera similar. length. c=‘‘. pos. Suponemos también una función readstr que lee una cadena de caracteres. Ppos apunta a un puntero pos cuyo valor es la posición str de la que obtuvimos un carácter la última vez. pero no es un factor. expr (str. { . length. A * B + C es una expresión. ni un factor. pero no es un factor. Si expr(str. # include <ctype. getsymbregresa el carácter cadena str [pos] e incrementa pos en 1. comenzando en pos. pero no ocupa la cadena completa */ } /*fin del main*/ Las funciones factor y term se parecen mucho a expr excepto en que son responsables del reconocimiento de factores y término. Si pos < length. # include <stdio. "no valida"). Si no satisface ninguno. &length). # define FALSE = if (expr (str. getsymb regresa un espacio en blanco. sin embrago se presenta una función auxiliargetsymb que opera con tres parámetros: str. "valida"). { else } /* fin de getsymb*/ La función que reconoce una expresión se llama expr. getsymb (str. char str[]. # define TRUE 1 pos = 0. length. respectivamente. length. poniendo la cadena en str y su largo en length. Cada una intenta satisfacer uno de los criterios para la entidad que se reconoce. puede escribirse la rutina principal como sigue. Se usan tres funciones para reconocer expresiones. La biblioteca estándar ctype. ppos) char C. término y factor para ver que ninguna de ellas describe a la cadena A + * B. el resultado es FALSE (FALSO).h> int length. en consecuencia. /* la condición puede fallar por una de dos razones (o ambas). sin embargo (A + B) es las tres cosas. return ( c ). (A + B*) C y A + B + C son expresiones nulas de acuerdo con las definiciones precedentes. if (*ppos < length) (*ppos) ++. if (term( str. &pos) = = TRUE && por >= # define MAXSTRINGSIZE 100 length) main () printf ("%s". Primero. Esto puede mostrarse al aplicar la definición de una expresión de cada uno. Los códigos para estas rutinas se apegan bastantes a las definiciones dadas antes.h incluye una funciónisalpha que es llamada por una de las funciones siguientes. una expresión. Sería instructivo para el lector intentar aplicar la definición de expresión. Considérese. Una vez descritas las funciones expr y readst. { else char str [MAXSTRINGSIZE]. *ppos.un ejemplo de una expresión que no es ni un término ni un factor. ppos) /* buscando un término */ char str []. No es ni una expresión. También reinicializan pos en la posición que sigue al factor o término de mayor longitud que se encuentra en la cadena str. Cada uno de los ejemplos anteriores es una expresión valida. *ppos. Si se satisface uno de esos criterios el resultado es TRUE (VERDADERO). Str contiene la entrada de la cadena de cadena de caracteres. &pos) = = FALSE entonces no hay una expresión valida al inicio de pos. Si pos < length puede que se encuentre una expresión valida. ppos) = = FLASE) int length. ni un término. Regresa TRUE (o1) (VERDADERO) si una expresión valida comienza en la posición pos de str y FALSE (o0) FALSO en caso contrario.h> readstr (str. 1!. int c. ppos) ! = ‘+’) { /* se encontró la mayor expresión (un solo término). Es el caso de 0!. puede reconocerse un gran número de casos distintos que se deben resolver. en términos del más simple. Usa el programa común de biblioteca isalpha (esta función se encuentra en la biblioteca ctype.. Puede identificarse un caso "trivial" para el cual la solución no recursiva pueda obtenerse en forma directa. } /*fin de expr */ La rutina term que reconoce términos. ppos)). } /* fin de factor */ Las tres rutinas son recursivas. ppos)) ! = ‘)’ ) char str[]. 4! = 4 * 3! = 4 * 3 * 2! = 4 * 3 * 2 * 1! = 4 * 3 * 2 * 1 * 0! = 4 * 3 * 2] * ] = 24 . Examinaremos que significa lo anterior cuando se aplica la función factorial. es muy similar y será presentada sin comentarios. term y factor se llama a sí misma. quiere escribirse un programa para calcular 0!. primero hasta llegar a 0! y luego al regresar a 4!. return (TRUE). un ejemplo fundamental de un problema que no debe resolverse de manera recursiva. El siguiente paso es encontrar un método para resolver un caso "complejo" en términos de uno más "simple". int length. } /* fin de term */ La función factor reconoce factores y debería ser ahora bastante sencilla. ppos) if ((c = getsymb (str. Reposicionar pos para que señale la última posición de la expresión */ (*ppos) . return (expr(str. *ppos. Sin embargo. En general. 4! Es un caso más complejo que 3!. term (str. 5. Volviendo a examinar la función factorial. factor (str. La mayoría de los problemas pueden resolverse de una manera directa usando métodos no recursivos. ppos) ! = ‘+’) { (*ppos) -. Sin embargo.Programación Recursiva: Es mucho más difícil desarrollar una solución recursiva en C para resolver un problema específico cuando no se tiene un algoritmo. En el caso de la función factorial se tiene una definición de ese tipo. length. return (isalpha(c)). length. length. { length. length. que regresa al destino de cero si su carácter de parámetro es una letra y cero (o FALSO) en caso contrario.. Así. Si restamos 1 de 4 de manera sucesiva llegamos a 0. La transformación que se aplica al número a para obtener 3 es sencillamente restar 1. si se sigue la acción del programa para la cadena de entrada " (a * b + c * d) + (e * (f) + g) " se encontrará que cada una de las tres rutinas expr. length. int length. length. ppos) = = FALSE) return (FALSE).. en lo fundamental. dado que su solución iterativa es directa y simple. } /* fin del if */ /* en este punto. n! en términos de (n – 1)!. length. 2! Y así sucesivamente. revisar el siguiente símbolo */ if (getsymb(str. examinaremos los elementos que permiten dar una solución recursiva. dado que cada una puede llamar a sí misma da manera indirecta. Es decir. *ppos. que se define como 1. La transformación del caso complejo al simple resultaría al final en el caso trivial. dado que: n! = n * (n – 1)! Así. Pos ejemplo. que es el caso trivial. ppos) && getsymb (str. El factor es. Esto significaría que el caso complejo se define.. { if (factor(str. Se deberá buscar otro termino */ return (term(str. lo cual permite la reducción de un problema complejo a uno más simple. otros pueden resolverse de una manera más lógica y elegante mediante la recursión.h). ppos)). ppos) == ‘)’ )./* se ha encontrado un término. return (TRUE) . length. probablemente. ppos) char str[]. se podrá calcular 4! mediante la definición de n! en términos de (n – 1)! al trabajar. se ha encontrado un término y su signo más. cuando encaramos la tarea de escribir un programa para resolver un problema no hay razón para buscar una solución recursiva. No es solo el programa sino las definiciones originales y los algoritmos los que deben desarrollarse. en general. if (getsymb (str. Antes que nada. si se puede definir 4! en términos de 3! y. length. } /* fin del if */ return (factor(str. aquellas que son creadas por nuestro programa mientras se están ejecutando y que por tanto. la memoria está orientada a bytes y un entero ocupa 2 posiciones de memoria. Antes de indicar como deben utilizarse las susodichas funciones tenemos que comentar el operador sizeof. sin embargo puede que en otro sistema la máquina esté orientada a palabras (conjuntos de 2 bytes. cuya gestión debe ser realizada por el programador. de esta manera. b. el los sistemas PC. Así entendemos por asignaciones de memoria dinámica. es decir. Las dos funciones que nos permiten reservar memoria son:  malloc (cantidad_de_memoria). Al hacerlo. programas que puedan ejecutarse en cualquier máquina que disponga de un compilador de C. Un caso complejo fib(n) se reduce entonces a dos más simples: fib(n –1) y fib(n –2). puede desarrollarse una solución si se supone que se ha resuelto el caso más simple. se definieron dos casos triviales: fib(0) = 0 y fib(1) = 1. Por tanto. De nuevo. un puntero a cualquier cosa. así como otra función que nos permite liberarla. el caso complejo se transforma en un caso más simple al restar 1. nos devuelve el tamaño que ocupa en memoria un cierto tipo de dato. El fichero de cabeceras stdlib. Este operadores imprescindible a la hora de realizar programas portables. En general. en principio void. La versión C de la función factorial supone que está definido (n –1)! y usa esa cantidad al calcular n!. el compilador genera el código para esta operación automáticamente. podemos escribir programas independientes del tamaño de los datos y de la longitud de palabra de la máquina. es decir. y más concretamente estas asignaciones estáticas no eran otras que las declaraciones de variables en nuestro programa.  calloc (número_de_elementos. pues a * b es igual a a.. En este sentido las variables locales están asociadas a asignaciones de memoria dinámicas. El tipo del puntero es. es trivial el caso de b = 1. El operador sizeof (tipo_de_dato). a la hora de ejecutar estas funciones es aconsejable realizar una operación cast (de conversión de tipo) de cara a la utilización de la aritmética de punteros a la que aludíamos anteriormente. Esta memoria permanece asignada a la variable hasta que termine la ejecución del programa (función main). Los compiladores modernos suelen realizar esta conversión automáticamente. En resumen si no utilizamos este operador en conjunción con las conversiones de tipo cast probablemente nuestro programa sólo funciones en el ordenador sobre el que lo hemos programado. 6. Si no se ha podido reservar el tamaño de la memoria especificado devuelve un puntero con el valor 0 o NULL. tamaño_de_cada_elemento). En la definición de a * b. donde se requiere de dos casos triviales definidos de manera directa. de una serie de librerías de funciones estándar. Realmente las variables locales de las funciones se crean cuando éstas son llamadas pero nosotros no tenemos control sobre esa memoria. Aquí la recursión se basa únicamente en el segundo parámetro.h contiene las declaraciones de dos funciones que nos permiten reservar memoria. al final. suponiendo que ambas máquinas definan la misma precisión para este tipo. al caso trivial de b = 1. Fib(1) no puede definirse como fib(0) + fib(-1) porque la función de Fibonacci no está definida para números negativos. aunque en general una máquina orientada a palabras también puede acceder a bytes) y por tanto el tamaño de un entero sería de 1 posición de memoria. Cuando declaramos una variable se reserva la memoria suficiente para contener la información que debe almacenar. Por ejemplo.Asignación estática y dinámica de memoria: Hasta este momento solamente hemos realizado asignaciones estáticas del programa. . a + b puede definirse en términos de a * (b – 1) mediante la definición a * b = a * (b – 1) + a. Con respecto al ejemplo de la función de Fibonacci. lo que lleva. El lenguaje C dispone. como ya indicamos con anterioridad. Otra forma de aplicar estas ideas a otros ejemplos antes explicados. Estas dos funciones reservan la memoria especificada y nos devuelven un puntero a la zona en cuestión.Estos son los ingredientes esenciales de una rutina recursiva: poder definir un caso "complejo" en términos de uno más "simple" y tener un caso "trivial" (no recursivo) que pueda resolverse de manera directa. Esto se debe a la definición de fib(n) como fib(n –1) + fib(n – 2). puesto que se crean y destruyen durante la ejecución del programa. A estos punteros se le asigna una zona de memoria.sizeof(float)). con lo cual siempre debemos tener almacenados los punteros al principio de la zona que reservamos. compensa tener menos memoria (2 bytes para un entero) que incluir todo el código de llamada a malloc y comprobación de que la asignación fue correcta (esto seguro que ocupa más de dos bytes). donde puntero es el puntero devuelto por malloc o calloc. #include <stdlib. } float *mat. int *p_int. Con lo cual el comentario sobre lo engorroso que resultaba trabajar con un puntero a una variable simple. En segundo lugar tenemos que trabajar con un puntero con lo cual el programa ya aparece un poco más engorroso puesto que para las lecturas y asignaciones de las variables tenemos que utilizar el operador *. /* Aquí irían las operaciones sobre los datos */ p_int = (int *) malloc(sizeof(int)).1. a la hora de definir matrices dinámicas calloc es mucho más claro y además inicializa todos los elementos de la matriz a cero.Con todo lo mencionado anteriormente veamos un ejemplo de un programa que reserva dinámicamente memoria para algún dato. mat = (float *)malloc (20*sizeof(float)). en primer lugar porque aunque la variable sólo se utilice en una pequeña parte del programa. en el caso de las matrices dinámicas no existe diferencia alguna con una declaración normal de matrices. Obsérvese el uso de los operadores cast para modificar el tipo del puntero devuelto por malloc y calloc. Tenemos que liberar todos los bloque que hemos asignado. mat[0] = 5. En nuestro ejemplo anterior.. /* Aquí iría el código que libera la memoria */ mat = (float *)calloc(20. mat[2] = mat[1]*mat[6]/67.sizeof(float)). La función que nos permite liberar la memoria asignada con malloc y calloc es free(puntero). Nótese también que puesto que las matrices se referencian como un puntero la asignación dinámica de una matriz nos permite acceder a sus elementos con instrucciones de la forma: NOTA: En realidad existen algunas diferencias al trabajar sobre máquinas con alineamiento de palabras.h if ((p_int==NULL)||(mat==NULL)) #include <stdio. EL problema es el de "las torres de Hanoi".. Para termina un breve comentario sobre las funciones anteriormente descritas. } Este programa declara dos variables que son punteros a un entero y a un float.h> { main() printf ("\nNo hay memoria"). free(mat). así como la utilización del operador sizeof. . { exit(1). 7. Hay que tener cuidado a la hora de liberar la memoria. Como se puede observar no resulta rentable la declaración de una variable simple (un entero.Las Torres de Hanoi: A continuación se verá cómo pueden usarse técnicas recursivas para lograr una solución lógica y elegante de un problema que no se especifica en términos recursivos. como en el programa anterior) dinámicamente. La diferencia fundamental es que. cuyo planteamiento inicial se muestra en la figura a continuación. Básicamente da lo mismo utilizar malloc y calloc para reservar memoria es equivalente: mat = (float *)calloc (20. para el primero se reserva memoria para almacenar una variable entera y en el segundo se crea una matriz de veinte elementos cada uno de ellos un float.. la función free probablemente no podrá liberar el bloque de memoria. Si mientras actuamos sobre los datos modificamos el valor del puntero al inicio de la zona reservada.. podemos ahora escribir el código etiquetado como: /* Ahora iría el código que libera la memoria */ free (p_int).Ejemplos: 7. por ejemplo. como auxiliar */ return. towers (n – 1. En efecto. se pondrá mover éstos exactamente igual hacia B usando el C como auxiliar. frompeg. Para el caso de cinco discos en particular. mover el disco único de A a C y parar. Entonces podrá moverse el disco mayor de A a C y por último aplicarse de nuevo la solución recursiva para cuatro discos para moverlo de B a C. mover y regresar */ printf ("/n%s%d%s%c%s%c%". El objetivo es mover los discos al poste C usando B como auxiliar. topeg). Por lo tanto. supóngase que se conoce la forma de mover cuatro de ellos del poste A al otro. el mayor en A y en C ninguno. De manera análoga. 3. el área de datos debe contener elementos que representen a los cuatro. 5. topeg. cada uno de los cuales está sujeto a cambios en cada llamada recursiva. towers (n. En el poste A se ponen cinco discos de diámetro diferente de tal manera que un disco de diámetro mayor siempre queda debajo de uno de diámetro menor. Mover el disco superior de A a B n – 1 veces. B y C. Si n = = . usando int n. Así se habrá desarrollado una solución recursiva si se plantea una solución para n discos en términos de n – 1. frompeg.. Si n = = 2. Entonces. auxpeg. Adviértase que la solución se desarrollo mediante la identificación de un caso trivial (n = = 1) y una solución para el caso general y complejo (n) en términos de un caso más simple (n – 1). Mover el disco restante de A a C. "mover disco". usando C como auxiliar.. hay cuatro parámetros. se pueda plantear la solución para n – 1 discos. frompeg). "al poste". auxpeg) /* mover los n – 1 discos de arriba de A a B. 2. ¿Cómo puede completarse entonces el trabajo de mover el quinto disco? Cabe recordar que hay 3 postes disponibles. pero esta se puede representar por un valor temporal similar en el programa de simulación y no tiene que estar apilada. al final. Supóngase que se tiene una solución para n – 1 discos y que en términos de ésta. En lugar de concentrar la atención en una solución para cinco discos. Esto sucede porque en el caso trivial de un disco (al restar 1 de n de manera sucesiva se producirá. . de manera tal que los pasos 2 y 4 se ejecutaran en forma correcta. Ya se demostró que las transformaciones sucesivas de una simulación no recursivas de una rutina recursiva pueden conducir a un programa más simple para resolver un problema. Considérese la posibilidad de encontrar una solución. tpoeg). Sólo puede moverse el disco superior de cualquier poste a otro poste. Mover los disco n – 1 de B a C usando A como auxiliar Con toda seguridad este algoritmo producirá una solución completa por cualquier valor de n. de acuerdo con las reglas. 3. No hay variables locales. Ahora se verá si se puede desarrollar una solución.frompeg. hasta el valor para el que se desee encontrar una solución. Esto da como resultado la situación los cuatro primeros discos en el poste B. Supóngase que se supo cómo mover cuatro discos del poste A al C. "mover disco 1 del /* mover n – 1 discos de B hacia C empleando a A poste". por lo que los pasos 2 y 4 pueden ser ejecutados. auxpeg. y un disco mayor jamás puede quedar sobre uno menor. printf (" /n%s%c%s%c%". se puede establecer una solución recursiva de las torres de Hanoi como sigue: Para mover n discos de A a C usando B como auxiliar: 1. Considérese la posibilidad de encontrar tal relación. "al poste". { /* move remaining disk from A to C */ /* si es solo un disco. 4. el paso 1 será la solución correcta. como auxiliar */ char auxpeg. } /* fin del if*/ towers (n – 1. Hay un solo valor temporal que se necesita para guardar el valor de n – 1. Ahora se simulara la recursión del problema y se intentara simplificar la simulación no recursiva. topeg). 2. considérese el caso general den discos. 4. "del if (n = = 1) { poste" frompeg. se sabe entonces que hay una solución para n – 1 = = 1. usando el poste A como auxilia. frompeg. Si n = = 1. ni siquiera es claro que exista una.} /* fin de towers */ En esta función. topeg. 1) la solución es simple: sólo hay que el único disco del poste A a C. n. De esta forma se puede mostrar que la solución funciona para n = = 1..Hay tres postes: A. topeg. cuando n = = 3 ya se habrá producido una solución para n – 1 = = 2. En consecuencia. El problema se resolvería entonces. } /* fin del if */ short int retaddr.nparam. printf ("/n%s%d%s%c%s%c". push (&s. currarea. Por lo tanto. currarea. En primer lugar. área de datos actual a sus valores apropiados */ got start. ya que solo restaría un punto al que se . label2: /* se regresa a este punto desde la primera char temp.fromparam = ‘ ‘ . struct stack { -. int n. currarea. llamada recursiva */ short int i.retaddr = 2.fromparam = currarea. struct dataarea currarea.toparam = temp. pop (&s. &currarea).currarea.fromparam. Ahora se simplificará el programa.Hay tres puntos posibles a los que regresa la función en varias llamadas: el programa de llamada y los dos puntos que siguen a las llamadas recursivas. label3: /* se regresa a este punto desde la segunda currarea. una para cada una de las dos llamadas recursivas y otra para el regreso al programa principal. debe observarse que se usan tres etiquetas para indicar direcciones de regreso. "del poste".fromparam. s. Considérese la siguiente simulación no recursiva de towers: struct dataarea { switch (i) { int nparam. swicth (i) { start: /* Este es el inicio de la rutina simulada */ case 1: goto label1. &currarea). auxpeg) currarea. currarea. label1: return. Esto deja dos etiquetas de regreso. Si pudiera eliminarse otra más. currarea. el regreso al programa principal puede señalarse por un subdesborde en la pila.nparam = = 1) { case 2: goto label2. currarea.fromparam = frompeg. currarea.retaddr. char auspeg. topeg.nparam. simtowers (m.retaddr = 0. got start.auxparam = auxpeg. currarea.auxparam = temp.toparam). "al poste". "mover disco". /* Esta es la segunda llamada recursiva */ currarea. start: Label1: Label2: Label3: La dirección de regreso se codifica como un entero (1. &currarea). currarea.auxparam = currarea. currarea. } /* fin del switch */ char auxparam.nparam = 0.retaddr = 1. "mover disco 1 del poste".toparam. &currarea). i = currarea. /* colocar en la pila un área de datos simulados */ currarea.auxparam. Sin embargo. printf (" /n%s%c%s%c". }.rtaddr = 3. &currarea). auxparam = ‘ ‘ .auxparam. llamada recursiva */ currarea. --currarea.toparam) . temp = currarea.nparam. } /* fin del switch */ currarea. "al poste". } /* fin de simtowers */ pop (&s. sería innecesario guardar en la pila la dirección de regreso. push (&s. currarea. de la misma for que en la segunda versión simfact. topeg. char toparam. currarea. case 3: goto label3. se necesitan cuatro etiquetas. char fromparam. temp = currarea.toparam = ‘ ‘ . frompeg. currarea. push (&s. /* Esta es la primera llamada recursiva */ }.nparam = n.top = -1. frompeg. int top struct dataarea item [MAXSTACK]. i = currarea. 2 o 3) dentro de cada área de datos. struct stack s.fromparam.retaddr. case 1: goto label1. /* asignar parámetros y direcciones de regreso del currarea. { currarea. if (currarea. case 3: goto label3. case 2: goto label2.toparam = topeg. Se trata de un problema NP-Completo que no tiene solución para N=2 y N=3. se elimina la necesidad de guardar en la pila la dirección de regreso al simular la llamada externa (ya que se puede señalar mediante subdesborde y simular la segunda llamada recursiva. 7. Las acciones que ocurren en la simulación de esta llamada son las siguientes: 1. Las siguientes acciones se llevan a efecto: 6. Cuando la rutina simulada regresa puede hacerlo en forma directa a la rutina que llamó a la versión vigente. Se transfiere el control a /. En el área de datos vigente a 2. 4.. En consecuencia. / es la etiqueta del final del bloque del programa ya que la segunda llamada a towers aparece en la última instrucción de la función. 3. vertical o diagonal. Para N=4 tiene una única solución. Se eliminan de la pila y se fija el área de datos vigente como el área de datos eliminada de la pila. Los índices de dicha lista representan la columna en la que se encuentra una reina y el número que almacena la . ver Figura 1. auxpeg. de área de datos vigentes a 2. la seguida llamada a towers puede simularse en forma simple mediante: 1. se basa en colocar 8 reinas en un tablero de 8´ 8 (o N en un tablero de NxN. Para ver el gráfico seleccione la opción "Descargar" del menú superior Figura 1: Posible solución para el problema de las ocho reinas Este programa has sido muy estudiado por los matemáticos. Una forma de abordar el problema se lleva a cabo mediante la construcción de un predicado en Prolog del tipo siguiente: reinas (N. ya que no hay necesidad de salvar y volver a almacenar al área de datos de la rutina que llamada en este momento). y frompeg a los parámetros. Por lo tanto. Se coloca el área de datos vigente a 1 dentro de la pila. frompeg). tampoco hay razón para salvarla en la pila durante la simulación de la llamada. temp. 2. no tiene caso guardarla en la pila para que se vuelva a insertar y eliminar con el resto de los datos. No hay razón para ejecutar un regreso a la versión vigente. Se salta hacia el principio de la rutina simulada. No se volverá a hacer uso de la información del área de datos vigente a 1. Se salva la etiqueta de regreso. En la nueva área de datos vigente a 2. 7. El "salto" al principio de la rutina simulada. Solución). Ahora dirijamos nuestra atención a la segunda llamada recursiva y a la instrucción: towers (n – 1. El cambio de los parámetros en el área de datos vigente a sus valores respectivos. Siempre se eliminan elementos de la pila con éxito. Puesto que no hay razón para volver a usar esta área de datos. hay una solo dirección hacia la que se puede ejecutar un "salto" (la instrucción que sigue a la primera llamada). /. topeg. se fija la etiqueta de retorno a la dirección de la instrucción que sigue de inmediato a la llamada. Como los nuevos valores de las variables del área de datos vigente se obtendrán a partir de los datos antiguos de área de datos vigente. ya que ésta es destruida en la eliminación de los elementos en la pila tan pronto como se vuelve a almacenar. Sin embrago. Los datos se deben salvar en la pila sólo si se van a usar otra vez.El Problema de las Ocho Reinas: El problema de las ocho reinas y en general de las N reinas. 2. Por lo tanto. Para N=8 hay más de 80 soluciones dependiendo de las restricciones que se impongan. Ya que sólo queda una dirección de regreso posible. ésta queda lista parar regresar. de forma que en no puede haber dos piezas en la misma línea horizontal. donde N representa las dimensiones del tablero y el número de reinas y Solución es una lista que contiene la permutación de la lista de números que resuelven el problema. topeg. si el problema se generaliza). sólo para regresar de inmediato a la versión previa. auxpeg. se asignan los valores respectivos n – 1.2. el siguiente paso es volver a eliminar elementos de la pila y regresar. de manera que los valores sean intercambiables. 5. Después de completar la rutina simulada. a 1. 8. es necesario declarar una variable adicional. La única dirección de regreso que resta es la que sigue a la primera llamada recursiva.podría transferir el control si se eliminan los elementos de la pila con éxito. .N.L.[Y|Ys]):-X is Y+N.[Z|Zs]):-dame(Z. Además el procedimiento de backtracking está implícito en el propio motor de inferencia. Solucion):-rango(1. Así.2. permuta([].Solucion). e intentar repetir el proceso teniendo en cuenta la reina colocada.3]. sencillamente. por el método de prueba y error. o no.N]. para el ejemplo mostrado en la Figura 1. correcta.Y). dame(X.L1).4. la propia máquina Prolog se encarga de realizar el proceso de backtracking. Por tanto.N.N. lo que debemos hacer es volver al paso 2 para generar una permutación nueva. M.Como vemos es. Obtenemos una permutación del conjunto de números de la lista.1. Es muy importante comprender como funciona cada uno de los predicados para entender el funcionamiento del algoritmo general.Xs). Si analizamos y diseñamos nuestro problema tenemos que la forma de resolverlo se resume en los pasos siguientes: Para N. Prolog permite implementar los programas casi directamente a partir de las especificaciones realizadas a partir de un análisis y diseño de la solución desde un alto nivel de abstracción. En el caso de que cualquier predicado del consecuente falle.Ys).Solucion). permuta(L1. Comencemos a analizar la solución implementada.Zs).N. el problema se reduce a colocar una reina. Si la permutación no es correcta. luego se adapta perfectamente a un algoritmo de backtracking. una copia de las especificaciones realizadas más arriba. permuta(L.Ys. rango(Aux.N1.4.[Y|Ys]):-N1 is N+1..L1).[Y|Zs]):-dame(X. reina(N. Veamos el código que resuelve el problema: rango(N. Para tener más claras las ideas. hemos de generar un conjunto de permutaciones y seleccionar aquella que cumpla los requisitos impuestos por el juego. atacada(X. correcta Solución). tenemos que R=[2. N. atacada(X.Ys).. Solución): rango(1.Zs).[]).[X|Xs]. se obtiene una permutación y se comprueba si la permutación es. de modo clásico. El problema se resuelve con el predicado reina(N. Aux is N+1. [N]). obtenemos una lista de números comprendidos entre 1 y N: [1. M. observemos el árbol de ejecución general del objetivo reina(4.Y). permuta(Ys. correcta([]). Se genera el rango entre 1 y N. X is Y-N. Comprobamos que la permutación es correcta. Este problema es resuelto. dame(X.. permuta(L1. Cola). en caso contrario. correcta([X|Y]):-correcta(Y). atacada(X. rango(N.[Y|Ys]. [N|Cola]):-N<M. not atacada(X.posición o índice representa la fila donde la reina está colocada. luego este paradigma se adapta perfectamente a nuestro problema.Y):-atacada(X. correcta(Solucion). atacada(X. Con lo cual ya tenemos cubiertos los cuatro pasos fundamentales del algoritmo.1.3. . Básicamente.Solucion) en la Figura 2. deshacemos lo que llevamos hasta ahora y probamos con otra combinación. Si logramos poner todas las reinas el problema se ha resuelto. { int posiciones_en_columna[8].. que una reina actúa sobre todas las piezas situadas en la misma columna.Figura 2: Árbol de ejecución para el objetivo reina (4... printf ("\n\nPROBLEMA DE LAS OCHO REINAS:\n BOOLEAN reina_en_fila[8]. RANGO: Significado de los vectores: 7. Solucion) He aquí otro ejemplo sobre el problema de las ocho reinas.j) = TRUE. por las reglas del ajedrez. <PRINCIPIO ensayar> (i: entero) | | | +-SI no acertado ENTONCES inicializar el conjunto de posiciones de la reina i| | | | quitar reina ésima | | | +-FINSI +-REPETIR hacer la selección siguiente | | +-FINSI | +-SI segura ENTONCES | +-FINSI | | poner reina +-HASTA acertada O no hay más posiciones | | +-SI i < 8 ENTONCES <FIN> | | | LLAMAR ensayar (i + 1) Observaciones sobre el código: 1) Estudiar la función ensayar() a partir de este pseudocódigo. RANGO: 1.h> /* getch () */ /* rango de índice: 2. #define c(i) posiciones_en_columna[(i)-1] y la línea quitar reina del pseudocódigo: /* rango de índice: 1. esto (\) k-ésima es fácil solucionarlo con las siguientes macros: di(k): indicativo de que no hay reina en la diagonal #define c(i) posiciones_en_columna[(i)-1] invertida (/) k-ésima #define f(i) reina_en_fila[(i)-1] Dado que se sabe.16 dn(k): indicativo de que no hay reina en la diagonal En C. y por ello el proceso de selección de posiciones queda limitado a los ocho posibles valores del índice de fila j.j) = FALSE. 2) Vectores utilizados: int posiciones_en_columna[8].8 #define dn(i) reina_en_diagonal_normal[(i)+7] BOOLEAN reina_en_fila[8]..7 */ #include <stdio.8 #define di(i) reina_en_diagonal_inversa[(i)-2] BOOLEAN reina_en_diagonal_normal[15]. la línea poner reina del pseudocódigo es: c (i) = j. ").. . el parámetro i se convierte en el índice de columna.h> /* printf () */ #define di(i) reina_en_diagonal_inversa[(i)-2] #include <conio. #define f(i) reina_en_fila[(i)-1] y la condición segura del pseudocódigo: /* rango de índice: 1. #define TRUE 1 void ensayar (int i). f (j) = di (i + j) = dn (i . proceso ().. el primer elemento de cada vector tiene índice normal 0. printf ("\n\nPulsa cualquier tecla para finalizar. "). fila o diagonal del tablero se deduce que cada columna puede contener una y sólo una reina.j) #define dn(i) reina_en_diagonal_normal[(i)+7] /* Ficheros a incluir: */ /* rango de índice: -7. #define FALSE 0 /* Definiciones de las funciones: */ /* Variables globales: */ void main (void) BOOLEAN acertado.8 */ f (i) && di (i + j) && dn (i . RANGO: f(j) : indicativo de que no hay reina en la fila j-ésima 2. y que la elección de la situación de la reina i-ésima puede restringirse a los cuadros de la columna i. BOOLEAN reina_en_diagonal_inversa[15]. Por tanto. A partir de estos datos.7 c(i) : la posición de la reina en la columna i BOOLEAN reina_en_diagonal_inversa[15]. RANGO: 1..8 */ f (j) = di (i + j) = dn (i .. BOOLEAN reina_en_diagonal_normal[15].16 */ /* Macros: */ /* Prototipos de las funciones: */ #define BOOLEAN int void proceso (void). Primero se mostrara un pseudocodigo sobre el mismo y luego su respectiva codificación en el lenguaje C.  Recursos abstractos (simplicidad): consiste en descompones las acciones complejas en otras más simples capaces de ser resueltas con mayor facilidad. } } else } while (! acertado && j != 8). Tipos de programación Existen varias clases de programación. void ensayar (int i) } { void proceso (void) int j = 0. f (j) = di (i + j) = dn (i .  Estructuras básicas: existen tres tipos de estructuras básicas: . { for (i = -7.j.j) = TRUE. A través de los ejemplos que el individuopueda revisar. 8. i++) if (f (j) && di (i + j) && dn (i . { do register int i. for (j = 1. f (i) = TRUE.\n"). } } CONCLUSIÓN Se puede decir que la recursividad es una técnica de programación bastante útil y muy interesante de estudiar. i <= 8. tomando en cuenta que cada programador tiene su estilo de programar. pero existen otros tipos de programación. acertado = TRUE. Esta programación estructurada utiliza un número limitado de estructuras de control. Esta técnica incorpora:  Diseño descendente (top-dow): el problema se descompone en etapas o estructuras jerárquicas. dn (i) = TRUE. ensayar (1). else printf ("\n"). i++) j++. i <= 16. i++) c (i) = j. Programación estructurada (PE): La programación estructurada está compuesta por un conjunto de técnicas que han ido evolucionando aumentando considerablemente la productividad del programa reduciendo el tiempo de depuración y mantenimiento del mismo.j)) di (i) = TRUE. El ejemplo de las torres de Hanoi tanto como el ejemplo de las ocho reinas son problemas claves que tienen que ver directamente con lo que es la recursividad. i++) if (! acertado) { f (j) = di (i + j) = dn (i . j <= 8. { for (i = 1. Los explicaremos a lo largo del artículo. En la mayoría de los casos. acertado = FALSE. i = 1. i <= 7. dependiendo de los métodos utilizados y las técnicas empleadas. sea estática o dinámica. printf ("\n\nNO HAY SOLUCION. aunque puede que muchos de los lectores sólo conozcan una metodología para realizar programas. las técnicas se centran en programación modular y programación estructurada.j) = FALSE. aprenderá con más rapidez y sencillez lo que es programar recursivamente e incluir esta técnica cuando se le presente un problema como los que fueron mencionados anteriormente.getch (). i <= ensayar (i + 1). if (i < 8) if (acertado) { for (printf ("\n\nLA SOLUCION ES:\n\n"). for (i = 2. en realidad se tendrá que aplicar en cualquier programa al momento de su codificación. c (j) == i ? 1 : 0). j++) } printf ("%2d". La asignación de memoria. Los tipos o técnicas de programación son bastante variados. reduciendo así considerablemente los errores. selección e iteración. Se trata de una programación más lenta y laboriosa. El objeto es un conjunto complejo de datos y programas que poseen estructura y forman parte de una organización. Programación estructurada La programación estructurada es una técnica para escribir programas (programación de computadora). principalmente debido a las aplicaciones gráficas. o . o Estructuras repetitivas: son secuencias de instrucciones que se repiten un número determinado de veces. Programación funcional: Se caracteriza principalmente por permitir declarar y llamar a funciones dentro de otras funciones. siendo innecesario el uso de la instrucción o instrucciones de transferencia incondicional (GOTO. pueda dar soluciones inteligentes). Un objeto contiene varios datos bien estructurados y pueden ser visibles o no dependiendo del programador y las acciones del programa en ese momento. que todas las instrucciones son ejecutables sin que aparezcan bucles infinitos. Hoy en día las aplicaciones informáticas son mucho más ambiciosas que las necesidades de programación existentes en los años 1960. que integran el programa en su totalidad. La salida de una acción es la entrada de otra. En la programación modular. o Estructuras selectivas: en estas estructuras se evalúan las condiciones y en función del resultado de las mismas se realizan unas acciones u otras. Se suele utilizar para controlar los accesos de usuarios y programas a un recurso de forma simultánea. el programa principal coordina las llamadas a los módulos secundarios y pasa los datos necesarios en forma de parámetros. Para ello se utilizan únicamente tres estructuras: secuencia. Programación orientada a objetos (POO) : Se trata de una técnica que aumenta considerablemente la velocidad de desarrollo de los programas gracias a la reutilización de los objetos. EXIT FUNCTION. Se utilizan expresiones lógicas. obteniendo unos resultados lentos en las acciones. Un programa está estructurado si posee un único punto de entrada y sólo uno de salida. EXIT SUB o múltiples RETURN). Las principales ventajas de la programación estructurada son:  Los programas son más fáciles de entender  Se reduce la complejidad de las pruebas  Aumenta la productividad del programador  Los programas queden mejor documentados internamente. Programación concurrente: Este tipo de programación se utiliza cuando tenemos que realizar varias acciones a la vez. tales como la programación orientada a objetos y el desarrollo de entornos de programación que facilitan la programación de grandes aplicaciones. Ello ha llevado al desarrollo de nuevas técnicas. existen de "1 a n" caminos desde el principio hasta el fin del programa y por último. A su vez cada módulo puede contener sus propios datos y llamar a otros módulos o funciones. por lo que las técnicas de programación estructurada no son suficientes. El elemento principal de la programación orientada a objetos es el objeto. Programación modular: En la programación modular consta de varias secciones dividas de forma que interactúan a través de llamadas a procedimientos. Se trata de una programación basada en el cálculo de predicados (una teoría matemática que permite lograr que un ordenador basándose en hecho y reglas lógicas. El polimorfismo y la herencia son unas de sus principales características y por ello dedicaremos más adelante un artículo exclusivamente a tratar estos dos términos. Programación lógica: Se suele utilizar en la inteligencia artificial y pequeños programas infantiles.Estructuras secuénciales: cada acción sigue a otra acción secuencialmente. perdiendo su valor anterior. " es igual que " . ni se bifurca el flujo del programa. demuestra que todo programa puede escribirse utilizando únicamente las tres instrucciones de control siguientes:  Secuencia  Instrucción condicional. y se espera que después siga la condición lógica de control de la instrucción.Orígenes de la programación estructurada A finales de los años 1970 surgió una nueva forma de programar que no solamente daba lugar a programas fiables y eficientes. Estructura secuencial: Una estructura de programa es secuencial si las instrucciones se ejecutan una tras otra.  2º Se guarda el valor de y en x.  THEN señala el fin de la condición. El teorema del programa estructurado. es decir que una instrucción no se ejecuta hasta que finaliza la anterior. b PRINT a . Ampliando un poco el ejemplo anterior. intermedia. a modo de secuencia lineal. pero se mantiene una copia del contenido en auxiliar. THEN.  Iteración (bucle de instrucciones) con condición al principio. b END IF ELSE La instrucción selectiva anterior puede presentar uno de dos mensajes: a es mayor que b o a no es mayor que b. si es falso se exterioriza el segundo. " es menor que " . Estructura selectiva o de selección: La estructura selectiva permite que la ejecución del programa se bifurque a una instrucción (o conjunto) u otra/s. y END IF. en tres operaciones secuenciales. sino que además estaban escritos de manera que facilitaba su comprensión posterior. que es el valor inicial de x.  IF señala el comienzo de la instrucción condicional. b Este ejemplo permite considerar situaciones en las que se tiene más de dos alternativas. Ejemplo: INPUT x auxiliar= x y= auxiliar PRINT y INPUT y x= y PRINT x Esta secuencia de instrucciones permuta los valores de x e y.  El resultado es el intercambio de los valores entre x e y. el usuario no debe utilizar sus nombres salvo para este fin. Si bien los lenguajes de programación tienen un mayor repertorio de estructuras de control. pero hay situaciones en las que deben considerarse más casos y para ellos se puede repetir las veces que sea necesario la opcional ELSEIF. Las palabras clave IF. con estructuras anidadas: IF a > b THEN ELSE PRINT a . Ejemplo: IF a > b THEN PRINT a . le sigue la instrucción que se ejecutará si la condición es falsa. " es mayor que " .  END IF indica el final de la estructura. según un criterio o condición lógica establecida.  ELSE es opcional. según el resultado de la comparación entre a y b. se presenta el primer mensaje. . " es mayor que " . y después estará la instrucción a ejecutar si la condición es verdadera. Solamente con estas tres estructuras se pueden escribir todos los programas y aplicaciones posibles. proporcionada por el lenguaje. constituyen la propia estructura de la instrucción condicional (palabra reservadas). b PRINT a . ELSE. éstas pueden ser construidas mediante las tres básicas citadas. sólo uno de los caminos en la bifurcación será el tomado para ejecutarse.  1º Se guarda una copia del valor de x en auxiliar. " no es mayor que " . con ayuda de una variable auxiliar. En este caso se ha considerado tres. propuesto por Böhm-Jacopini. si el resultado de a > b es verdadero. b ELSEIF a < b THEN END IF PRINT a . El caso ejemplo se ha codificado en BASIC.  3º Se copia a y el valor de auxiliar. luego de ésta el programa seguirá su curso. Entonces pasa a ser a=1 y b=7. Cuando a=6 y b=7. constituye una buena práctica de programación comentar adecuadamente todos los programas.  DO WHILE: señala el comienzo del bucle ("haga mientras") y después de estas palabras se espera la condición lógica de repetición. b PRINT auxiliar IF a > b THEN LOOP REM hacer intercambio de variables ELSE auxiliar = a REM no hacer nada a=b END IF b = auxiliar PRINT REM imprimir diferencia en escala de uno en uno PRINT a. La instrucción no ejecutable REM permite la inserción de comentarios en cualquier parte del programa. El lenguaje utilizado en el ejemplo (BASIC). si la condición es verdadera pasa el control al cuerpo del bucle. (se repite la secuencia) . por lo que el mismo puede que no se ejecute nunca (cuando la condición es falsa desde un principio) o bien que se repita tantas veces como resulte y mientras la condición sea cierta. se escribe el valor de a en pantalla y se incrementa en una unidad. El bucle mientras. la condición es verdadera. en caso contrario el flujo salta directamente al final de la estructura. En el ejemplo se tienen definidas dos variables a y b. .. Las instrucciones INPUT permiten que el operador ingrese. . Anidamiento: El cuerpo de cualquier estructura puede ser instrucciones simples u otras estructuras. los valores deseados para las variables a y b. y se iteró 7 veces. que a su vez pueden contener a otras.Estructura iterativa: Un bucle iterativo o iteración de una secuencia de instrucciones. que al iniciarse el bucle contienen los valores a=0 y b=7. hace que se repita su ejecución mientras se cumpla una condición. b En el ejemplo la sentencia o instrucción CLS sólo tiene el efecto de "limpiar" la pantalla al inicio de la ejecución del programa. Ejemplo: a= 0 DO WHILE b > a a= a + 1 b= 7 PRINT a LOOP Esta instrucción tiene tres palabras reservadas WHILE.. La condición del bucle es b > a. pero de la siguiente forma: a= 0 WHILE b > a a= a + 1 b= 7 PRINT a WEND Que es absolutamente análoga.  LOOP: señala el final del cuerpo de la estructura de bucle. DO y LOOP. La salida por pantalla de este ejemplo es 0 1 2 3 4 5 6. aunque puede ser forzado o explícito por otra condición. saliendo de la misma. a auxiliar = auxiliar . en éste formato la palabra reservada WEND marca el fin del bucle y no se utiliza ni DO ni LOOP. donde el programador lo considere necesario. se repite mientras la condición sea verdadera. permite utilizar la misma estructura indicada. esta condición se comprueba o chequea antes de ingresar al cuerpo del bucle. saliendo por LOOP. el número de iteraciones normalmente está determinado por el cambio en la condición dentro del mismo bucle.. Cuando se llega a que a=7 y b=7. desde teclado y con un mensaje previo acorde.. en el cuerpo del bucle se escribe el valor de a en pantalla y luego se incrementa esa variable en una unidad. Entonces la condición ya resulta falsa y la instrucción WHILE finaliza. además de tener otras del tipo iterativas. Si a=0 y b=7. la condición sigue siendo verdadera.1 INPUT "Valor entero para b:". Ejemplo: CLS DO WHILE auxiliar > a INPUT "Valor entero para a:". Un método un poco más sofisticado es la programación por capas. 3. 6. Se incrementa el rendimiento de los programadores. programados y compilados por separado (en realidad esto no es necesario. pero sí es recomendable para su mejor mantenimiento y depuración). 9. una tarea de dedicación. están incluidas implícitamente en las instrucciones de selección e iteración. Inconvenientes de la programación estructurada El principal inconveniente de este método de programación es que se obtiene un único bloque de programa. Aunque no se usan de forma directa. pero no se la debe considerar como una panacea ya que el desarrollo de programas es. a DO WHILE auxiliar > a INPUT "Valor entero para b:". Las instrucciones de salto. 2. . Reducción del esfuerzo en las pruebas y depuración. que cuando se hace demasiado grande puede resultar problemático el manejo de su código fuente. 8. esfuerzo y creatividad. ya que no hay cuerpo allí. GOTO.de este modo se clarifica notablemente su lectura. tanto las técnicas de programación estructurada como las de programación modular. 7. comparada con la forma tradicional que utiliza GOTO. En la anterior estructura IF bien se puede omitir la porción ELSE. El seguimiento de los fallos o errores del programa ("debugging") se facilita debido a su estructura más sencilla y comprensible. puesto que las instrucciones están más ligadas o relacionadas entre sí. Un programa escrito de acuerdo a los principios de programación estructurada no solamente tendrá una mejor estructura sino también una excelente presentación. b auxiliar = auxiliar . se definen módulos independientes. cuando se programa hoy en día (inicios del siglo XXI) se utilizan normalmente. En realidad. Reducción de los costos de mantenimiento. La programación estructurada ofrece estos beneficios. quedan reservadas para construir las instrucciones básicas. durante su desarrollo y posterior para modificación o mantenimiento. Los bloques de código son casi auto-explicativos. de modo que lo siguiente es completamente equivalente: CLS REM imprimir diferencia en escala de uno en uno INPUT "Valor entero para a:". Análogamente a la depuración. por estar prohibida su utilización. es decir. en la que los módulos tienen una estructura jerárquica en la que se pueden definir funciones dentro de funciones o de procedimientos. Los programas son más fáciles de entender. b Características de la programación estructurada 1. programar es casi un arte. La estructura de los programas es clara. si es necesario. esencialmente. por lo que los errores se pueden detectar y corregir más fácilmente. modificar o extender los programas resulta más fácil. de forma conjunta y por lo tanto es muy común que cuando se hace referencia a la programación estructurada muchos entiendan que ella incluye también las técnicas modulares. si fuera realmente imprescindible. 4. lo que reduce y facilita la documentación. durante la fase de mantenimiento. pueden ser leídos de forma secuencial. no hay necesidad de hacer engorrosos seguimientos en saltos de línea (GOTO) dentro de los bloques de código para intentar entender la lógica. 5.1 IF a > b THEN PRINT auxiliar REM hacer intercambio de variables LOOP auxiliar = a END IF a=b PRINT b = auxiliar PRINT a. esto se resuelve empleando conjuntamente la programación modular. estrictamente no es así. Programas son más sencillos y más rápidos de confeccionar (y se facilita su optimización). en la práctica se los suele tomar como sinónimos de procedimientos y funciones.  La identidad es una propiedad de un objeto que lo diferencia del resto. en pleno auge. La programación modular es un paradigma de programación que consiste en dividir un programa en módulos o subprogramas con el fin de hacerlo más legible y manejable. llamado programación orientada a objetos. Al aplicar la programación modular. A su vez. un problema complejo debe ser dividido en varios subproblemas más simples. polimorfismo y encapsulamiento. juntamente con un más moderno paradigma. Programación orientada a objetos La programación orientada a objetos o POO (OOP según sus siglas en inglés) es un paradigma de programación que usa los objetos en sus interacciones. abstracción. Esta comunicación favorece a su vez el cambio de estado en los propios objetos.Si bien las metodologías en cuestión ya son de antigua data ("en plazos informáticos"). Si bien un módulo puede entenderse como una parte de un programa en cualquiera de sus formas y variados contextos. no debe confundirse el término "modulo" (en el sentido de programación modular) con términos como "función" o "procedimiento". Está basado en varias técnicas. Se presenta históricamente como una evolución de la programación estructurada para solucionar problemas de programación más grandes y complejos de lo que ésta puede resolver. Introducción Los objetos son entidades que tienen un determinado estado. los objetos disponen de mecanismos de interacción llamados métodos. aun en la actualidad la conjunción "Programación estructurada" y "programación modular" es una de la más utilizadas. propios del lenguaje que lo soporte. incluyendo herencia. Pero no necesaria ni estrictamente un módulo es una función o un procedimiento. En caso de que un módulo necesite de otro.  El comportamiento está definido por los métodos o mensajes a los que sabe responder dicho objeto. existe variedad de lenguajes de programación que soportan la orientación a objetos. Un módulo es cada una de las partes de un programa que resuelve uno de los subproblemas en que se divide el problema complejo original. completamente distinto. En la actualidad. que favorecen la comunicación entre ellos. Esto debe hacerse hasta obtener subproblemas lo suficientemente simples como para poder ser resueltos fácilmente con algún lenguaje de programación. Su uso se popularizó a principios de la década de los años 1990. divide y vencerás ó análisis descendente (Top-Down). . en las que no se separa el estado y el comportamiento. Un objeto contiene toda la información que permite definirlo e identificarlo frente a otros objetos pertenecientes a otras clases e incluso frente a objetos de una misma clase. Programación modular Diagrama del funcionamiento de un subprograma. para diseñar aplicaciones y programas informáticos. y estos a su vez en otros subproblemas más simples. al poder tener valores bien diferenciados en sus atributos. ya que el mismo puede contener muchos de ellos. dicho con otras palabras. es decir. será uno o varios atributos a los que se habrán asignado unos valores concretos (datos). qué operaciones se pueden realizar con él. comportamiento (método) e identidad:  El estado está compuesto de datos o informaciones. puede comunicarse con éste mediante una interfaz de comunicación que también debe estar bien definida. Ésta técnica se llama refinamiento sucesivo. Esta característica lleva a tratarlos como unidades indivisibles. Cada uno de estos módulos tiene una tarea bien definida y algunos necesitan de otros para poder operar. es su identificador (concepto análogo al de identificador de una variable o una constante). se hicieron muchas tentativas para crear nuevos lenguajes basados en métodos orientados a objetos. Fueron refinados más tarde en Smalltalk. La programación estructurada anima al programador a pensar sobre todo en términos de procedimientos o funciones. cumpliendo todas las características propias de la orientación a objetos. en gran parte debido a la influencia de C++. en la que los datos y los procedimientos están separados y sin relación. El Eiffel de Bertrand Meyer fue un temprano y moderadamente acertado lenguaje con esos objetivos pero ahora ha sido esencialmente reemplazado por Java. Esta propiedad destaca que una clase requiere de métodos para poder tratar los atributos con los que cuenta. y a la implementación de la máquina virtual de Java en la mayoría de navegadores. pero permitiendo algunas características imperativas de maneras "seguras". para las cuales la programación orientada a objetos está particularmente bien adaptada. Hacerlo podría producir el hábito erróneo de crear clases contenedoras de información por un lado y clases con métodos que manejen a las primeras por el otro. una extensión dellenguaje de programación C. Pascal. La POO difiere de la programación estructurada tradicional. Conceptos fundamentales La programación orientada a objetos es una forma de programar que trata de encontrar una solución a estos problemas. Las características de orientación a objetos fueron agregadas a muchos lenguajes existentes durante ese tiempo. . Su dominación fue consolidada gracias al auge de las Interfaces gráficas de usuario. que fueron confundidas por la explosión combinatoria de cómo las diversas cualidades de diferentes naves podían afectar unas a las otras. En este caso. La instanciación es la lectura de estas definiciones y la creación de un objeto a partir de ellas. Entre ellos destacan los siguientes:  Clase: definiciones de las propiedades y comportamiento de un tipo de objeto concreto. creado por Ole-Johan Dahl y Kristen Nygaard del Centro de Cómputo Noruego en Oslo. El programador debe pensar indistintamente en ambos conceptos. De esta manera se estaría realizando una programación estructurada camuflada en un lenguaje de programación orientado a objetos. desarrollado en Simula en Xerox PARC (cuya primera versión fue escrita sobre Basic) pero diseñado para ser un sistema completamente dinámico en el cual los objetos se podrían crear y modificar "sobre la marcha" (en tiempo de ejecución) en lugar de tener un sistema basado en programas estáticos. soporta una orientación completa a objetos. La idea surgió al agrupar los diversos tipos de naves en diversas clases de objetos. Introduce nuevos conceptos. primero definen objetos para luego enviarles mensajes solicitándoles que realicen sus métodos por sí mismos. La programación orientada a objetos se fue convirtiendo en el estilo de programación dominante a mediados de los años ochenta. En la programación estructurada solo se escriben funciones que procesan datos. incluyendo Ada. Para saltar este obstáculo. siendo responsable cada clase de objetos de definir sus propios datos y comportamientos. ya que lo único que se busca es el procesamiento de unos datos de entrada para obtener otros de salida. Los programadores que emplean POO. y en segundo lugar en las estructuras de datos que esos procedimientos manejan.Los métodos (comportamiento) y atributos (estado) están estrechamente relacionados por la propiedad de conjunto. BASIC. por su parte. Origen Los conceptos de la programación orientada a objetos tienen origen en Simula 67. carecían de las características de las cuales muchos programadores habían venido a depender. sin separar ni darle mayor importancia a alguno de ellos. PHP en su versión 5 se ha modificado. un lenguaje diseñado para hacer simulaciones. La adición de estas características a los lenguajes que no fueron diseñados inicialmente para ellas condujo a menudo a problemas de compatibilidad y en la capacidad de mantenimiento del código. se trabajaba en simulaciones de naves. En este centro. se habla también de programación dirigida por eventos. entre otros. Lisp. en cambio. en gran parte debido a la aparición de Internet. Los lenguajes orientados a objetos "puros". que superan y amplían conceptos antiguos ya conocidos. las funciones o los métodos pueden también ser abstraídos y cuando lo están. al mismo nivel de abstracción. Herencia: (por ejemplo. como si esos atributos y operaciones hubiesen sido definidos por la misma D. No es visible al programador que maneja una instancia de la clase. y "comunicarse" con otros objetos en el sistema sin revelar cómo se implementan estas características. Desde el punto de vista del comportamiento. herencia de la clase C a la clase D) Es la facilidad mediante la cual la clase D hereda en ella cada uno de los atributos y operaciones de C. que puede ser únicamente accedida y alterada por un método del objeto. las características siguientes son las más importantes:  Abstracción: denota las características esenciales de un objeto.  Propiedad o atributo: contenedor de un tipo de datos asociados a un objeto (o a una clase de objetos). Se corresponde con los objetos reales del mundo que nos rodea. se mantienen escondidos al programador y sólo pueden ser accedidos a través de otros métodos públicos. no es más que un contenedor interno del atributo del objeto o de un estado interno. Esto es así para mantener hegemónico el ideal de OOP. Los procesos. o la generación de un "evento" con un nuevo mensaje para otro objeto del sistema. El sistema maneja el evento enviando el mensaje adecuado al objeto pertinente. informar y cambiar su estado. o a objetos internos del sistema (del programa). La abstracción es clave en el proceso de análisis y diseño orientado a objetos. Un método puede producir un cambio en las propiedades del objeto.  Identificación de un objeto: un objeto se representa por medio de una tabla o entidad que esté compuesta por sus atributos y funciones correspondientes. identidad. es decir la acción que genera.  Objeto: entidad provista de un conjunto de propiedades o atributos (datos) y de comportamiento o funcionalidad (métodos) los mismos que consecuentemente reaccionan a eventos. así como la "función" es un procedimiento interno del método del objeto. pero como no pertenecen a la clase.  Componentes de un objeto: atributos. principalmente porque se suelen emplear conjuntamente. Esto permite aumentar la cohesión de los componentes del sistema. En comparación con un lenguaje imperativo. Algunos autores confunden este concepto con el principio de ocultación. Características de la POO Existe un acuerdo acerca de qué características contempla la "orientación a objetos". Es una instancia a una clase.  Mensaje: una comunicación dirigida a un objeto. .  Encapsulamiento: Significa reunir a todos los elementos que pueden considerarse pertenecientes a una misma entidad. a la reacción que puede desencadenar un objeto. relaciones y métodos. El proceso de abstracción permite seleccionar las características relevantes dentro de un conjunto e identificar comportamientos comunes para definir nuevos tipos de entidades en el mundo real. Por lo tanto. y cuyo valor puede ser alterado por la ejecución de algún método. que hace los datos visibles desde fuera del objeto y esto se define como sus características predeterminadas. Cada objeto en el sistema sirve como modelo de un "agente" abstracto que puede realizar trabajo. o un mensaje enviado por un objeto). una variedad de técnicas son requeridas para ampliar una abstracción. ya que mediante ella podemos llegar a armar un conjunto de clases que permitan modelar la realidad o el problema que se quiere atacar. que le ordena que ejecute uno de sus métodos con ciertos parámetros asociados al evento que lo generó.  Evento: Es un suceso en el sistema (tal como una interacción del usuario con la máquina. y que se utiliza para indicar distintas situaciones posibles para el objeto (o clase de objetos). cuya ejecución se desencadena tras la recepción de un "mensaje". una "variable". es lo que el objeto puede hacer. puede usar los mismos métodos y variables públicas declaradas en C.  Método: Algoritmo asociado a un objeto (o a una clase de objetos). Los componentes registrados como "privados" (private) también se heredan. donde se capturan sus comportamientos.  Estado interno: es una variable que se declara privada. También se puede definir como evento. La aplicación entera se reduce a un agregado o rompecabezas de objetos. Fue creado para hacer programas de simulación. Surge en los años 70. En la mayoría de los lenguajes híbridos que se extendieron para soportar el Paradigma de Programación Orientada a Objetos como C++ u Object Pascal. formando una jerarquía de clasificación. Un objeto es una abstracción de algún hecho o ente del mundo real que tiene atributos que representan sus características o propiedades y métodos que representan su comportamiento o acciones que realizan. pero tienen conexiones con otros módulos. eliminando efectos secundarios e interacciones inesperadas. esta última característica se llama asignación tardía oasignación dinámica.  Principio de ocultación: Cada objeto está aislado del exterior. y con el que gran parte de la teoría de la programación orientada a objetos se ha desarrollado. Estos módulos se pueden compilar por separado. Al igual que la encapsulación. ya que el entorno la asignará al crear un nuevo objeto y la liberará cuando nadie lo esté usando. Esto asegura que otros objetos no pueden cambiar el estado interno de un objeto de maneras inesperadas. Modularidad: Se denomina Modularidad a la propiedad que permite subdividir una aplicación en partes más pequeñas (llamadas módulos). es un módulo natural.  Herencia: las clases no están aisladas. Cuando esto ocurre en "tiempo de ejecución". y por tanto desvincular la memoria asociada. al llamarlos por ese nombre se utilizará el comportamiento correspondiente al objeto que se esté usando. solamente los propios métodos internos del objeto pueden acceder a su estado. asociados a objetos distintos. permitiendo un acceso directo a los datos internos del objeto de una manera controlada y limitando el grado de abstracción. Algunos lenguajes relajan esto. esta característica no existe y la memoria debe desasignarse manualmente. tales como las plantillas y la sobrecarga de operadores de C++. Lenguajes orientados a objetos Simula (1967) es aceptado como el primer lenguaje que posee las características principales de un lenguaje orientado a objetos. los objetos que hayan quedado sin ninguna referencia a ellos. Los objetos heredan las propiedades y el comportamiento de todas las clases a las que pertenecen. Todas las propiedades y métodos comunes a los objetos se encapsulan o se agrupan en clases. O dicho de otro modo.  Recolección de basura: la recolección de basura o garbage collector es la técnica por la cual el entorno de objetos se encarga de destruir automáticamente. en donde los "objetos" son la representación de la información más importante. La herencia organiza y facilita el polimorfismo y el encapsulamiento permitiendo a los objetos ser definidos y creados como tipos especializados de objetos preexistentes. Una clase es una plantilla o un prototipo para crear objetos. Cuando un objeto hereda de más de una clase se dice que hay herencia múltiple. Estos pueden compartir (y extender) su comportamiento sin tener que volver a implementarlo. Algunos lenguajes proporcionan medios más estáticos (en "tiempo de compilación") de polimorfismo.  Polimorfismo: comportamientos diferentes. por eso se dice que los objetos son instancias de clases. cada una de las cuales debe ser tan independiente como sea posible de la aplicación en sí y de las restantes partes. Resumen La programación orientada a objetos es un paradigma que utiliza objetos como elementos fundamentales en la construcción de la solución. Smalltalk (1972 a 1980) es posiblemente el ejemplo canónico. los lenguajes soportan la Modularidad de diversas formas. . las referencias y las colecciones de objetos pueden contener objetos de diferentes tipos. sino que se relacionan entre sí. Esto suele hacerse habitualmente agrupando los objetos en clases y estas enárboles o enrejados que reflejan un comportamiento común. y la invocación de un comportamiento en una referencia producirá el comportamiento correcto para el tipo real del objeto referenciado. El aislamiento protege a las propiedades de un objeto contra su modificación por quien no tenga derecho a acceder a ellas. Esto significa que el programador no debe preocuparse por la asignación o liberación de memoria. y cada tipo de objeto expone una interfaz a otros objetos que especifica cómo pueden interactuar con los objetos de la clase. pueden compartir el mismo nombre. NET  Genie  Visual FoxPro (en su versión 6)  Harbour  Visual Basic 6. OOLISP.Entre los lenguajes orientados a objetos se destacan los siguientes:  ABAP -> SAP Lenguaje orientado a eventos  Oz  ABL Lenguaje de programación de OpenEdge de  R Progress Software  Perl (soporta herencia múltiple. sino que son híbridos que combinan la POO con otros paradigmas.scala-lang.org/page.jsp  Ocaml Muchos de estos lenguajes de programación no son puramente orientados a objetos.x  Ruby con librería de objetos Class(y))  Smalltalk (Entorno de objetos puro)  D  Magik (SmallWorld)  Object Pascal (Embarcadero Delphi)  Vala  Gambas  VB. como OOCOBOL. Al igual que C++ otros lenguajes. Un nuevo paso en la abstracción de paradigmas de programación es la Programación Orientada a Aspectos (POA).0  Eiffel  Visual DataFlex  Fortran 90/95  Visual Objects  Java  XBase++  JavaScript (la herencia se realiza por medio de  Lenguaje DRP la programación basada en prototipos)  Lenguaje de programación  Lexico (en castellano) Scala (lenguaje usado  Objective-C por Twitter) http://www. OOPROLOG y Object REXX. han sido creados añadiendo extensiones orientadas a objetos a un lenguaje de programación clásico. Aunque es todavía una metodología en estado de maduración. . pero puede modificarse al  ActionScript 3 algoritmo linearization C3 por medio del  Ada módulo Class::C3 en CPAN)  C++  PHP (a partir de su versión 5)  C#  PowerBuilder  Clarion  Python  Clipper (lenguaje de programación) (Versión 5. cada vez atrae a más investigadores e incluso proyectos comerciales en todo el mundo. La resolución se  ActionScript realiza en preorden.
Copyright © 2024 DOKUMEN.SITE Inc.