4  Programación estructurada

En esta sección se hará una revisión de lo que es la programación estructurada.

En esta revisión se hace mención al control del flujo de un algoritmo/programa mediante estructuras de secuencia, selección y repetición.

Así mismo, se espera que al finalizar las actividades de esta sección, el estudiante tenga clara la manera en que se hace el diseño de un algoritmo bajo el paradigma de programación imperativa estructurada, ya sea mediante el uso de un diagrama de flujo o de pseudocódigo.

4.1 ¿Qué es la programación estructurada?

La programación estructurada es un conjunto de técnicas para desarrollar algoritmos fáciles de escribir, verificar, leer y modificar. La programación estructurada utiliza:

  • Diseño descendente. Consiste en diseñar los algoritmos en etapas, yendo de los conceptos generales a los de detalle. El diseño descendente se verá completado y ampliado con el diseño modular.
  • Recursos abstractos. En cada descomposición de una acción compleja se supone que todas las partes resultantes están ya resueltas, posponiendo su realización para el siguiente refinamiento.
  • Estructuras básicas. Los algoritmos deberán ser escritos utilizando únicamente tres tipos de estructuras básicas (secuencia, selección y repetición).

4.2 Teorema de Böhm y Jacopini

Para que la programación sea estructurada, los programas han de ser propios. Un programa se define como propio si cumple las siguientes características:

  • Tiene un solo punto de entrada y uno de salida.
  • Cada acción del algoritmo es accesible, es decir, existe al menos un camino que va desde el inicio, pasa por la acción y llega hasta el fin del algoritmo.
  • No tiene bucles infinitos.

El teorema de Böhm y Jacopini (Böhm y Jacopini 1966) dice que: “un programa propio puede ser escrito utilizando únicamente tres tipos de estructuras: secuencial, selectiva y repetitiva”. De este teorema se deduce que se han de diseñar los algoritmos empleando exclusivamente dichas estructuras, la cuales, como tienen un único punto de entrada y un único punto de salida, harán que nuestros programas sean propios.

4.3 Control del flujo de un programa

El flujo (orden en que se ejecutan las sentencias de un programa) es secuencial si no se especifica otra cosa. Este tipo de flujo significa que las sentencias se ejecutan en secuencia, una después de otra, en el orden en que se sitúan dentro del programa. Para cambiar esta situación se utilizan las estructuras que permiten modificar el flujo secuencial del programa. Así, las estructuras de selección se utilizan para seleccionar las sentencias que se han de ejecutar a continuación y las estructuras de repetición (repetitivas o iterativas) se utilizan para repetir un conjunto de sentencias.

4.3.1 Estructura secuencial

Una estructura secuencial es aquella en la cual una acción se ejecuta detrás de otra. El flujo del programa coincide con el orden físico en el que se sitúan las instrucciones:

Figura 4.1: Representaciones de la estructura secuencial

4.3.2 Estructura selectiva

Una estructura selectiva es aquella en que se ejecutan unas acciones u otras según se cumpla o no una determinada condición.

Cuando el resultado de evaluar la condición es verdad se ejecutará una determinada acción o grupo de acciones y si el resultado es falso se ejecutará otra acción o grupo de acciones diferentes:

Figura 4.2: Representaciones de la estructura selectiva

Si no es necesario ejecutar una determinada acción o acciones cuando el resultado es falso entonces se usa una versión simplificada o reducida de la anterior estructura selectiva:

Figura 4.3: Representaciones de la estructura selectiva simple

En este último caso, también se puede decir que cuando la condición es verdad se ejecutará una determinada acción o grupo de acciones y si el resultado es falso simplemente no se ejecutará.

4.3.3 Estructura iterativa

Las acciones del cuerpo de la estructura iterativa se repiten mientras o hasta que se cumpla una determinada condición. Es frecuente el uso de contadores o banderas para controlar el que las repeticiones continúen o finalicen.

“mientras” (while) es la estructura iterativa básica y lo que la caracteriza es que las acciones del cuerpo de la estructura se realizan siempre que la condición sea cierta, finalizando las repeticiones cuando la condición sea falsa. Además, siempre se pregunta por la condición antes de proceder a ejecutar el cuerpo de la estructura:

Figura 4.4: Representaciones de la estructura repetitiva “mientras”

Usualmente se habla de más de una estructura iterativa, pero el Teorema de Böhm y Jacopini garantiza que todo algoritmo se puede escribir usando únicamente un tipo de estructura secuencial, un tipo de estructura selectiva y un tipo de estructura iterativa. Esto quiere decir que no se requiere más de un tipo de estructura iterativa y que siempre se puede reescribir el algoritmo para utilizar un solo tipo de estructura iterativa (lo que sea que se pueda hacer con los otros tipos de estructuras iterativas, también se puede hacer con un tipo de estructura iterativa seleccionado, por ejemplo usando únicamente la estructura iterativa “mientras”).

4.3.4 Anidamiento

Tanto las estructuras selectivas como las repetitivas pueden ir anidadas, unas en el interior de otras.

Una potencial estructura selectiva múltiple no es necesaria ya que sería equivalente a tener varias estructuras selectivas anidadas:

si condición_1 entonces
  acciones_1
si_no
  si condición_2 entonces
    acciones_2
  si_no
    si condición_3 entonces
      acciones_3
    si_no
      acciones_4
    fin_si
  fin_si
fin_si

Cuando se inserta una estructura iterativa dentro de otra, la estructura interna ha de estar totalmente incluida dentro de la externa:

mientras expresión_lógica_1 hacer
  acciones_1
  mientras expresión_lógica_2 hacer
    acciones_2
  fin_mientras
  acciones_3
fin_mientras

¿Si la expresión_lógica_1 es verdadera de manera consecutiva un número de veces n_1 y si la expresión_lógica_2 es verdadera de manera consecutiva un número de veces n_2 en cada ocasión que expresión_lógica_1 es verdadera, la acción o grupo de acciones del cuerpo de la estructura iterativa interna cuántas veces se ejecutará? ¿Si tengo una operación innecesaria en el cuerpo de dicha estructura, qué tanto se ve afectada la eficiencia del algoritmo? (¿qué tanto aumenta su costo computacional? ¿qué tanto aumenta su tiempo de ejecución?)

4.4 Ejemplos

Ejemplo 4.1 Realice el análisis y diseño en pseudocódigo de un algoritmo que obtenga e imprima el valor absoluto (|x|) de un número real (x) dado por el usuario mediante el uso del teclado.

Para cualquier número real x, el valor absoluto de x se denota por |x| y se define como:

|x| = \begin{cases} x & \text{Si } x \geq 0 \\ -x & \text{Si } x < 0 \end{cases}

Algoritmo: Valor absoluto de un número real
var
  real: x
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo obtiene e imprime por pantalla el valor absoluto de un número real dado por el usuario mediante el uso del teclado”)
  escribir(“Por favor, ingrese un número real”)
  leer(x)
  si x < 0 entonces
    x <- - x
  fin_si
  escribir(“El valor absoluto del número dado es” & x)
fin

Ejemplo 4.2 Realice el análisis y diseño en pseudocódigo de un algoritmo que obtenga e imprima el resultado de aplicar la función signo \big(\mathrm{sign}(x)\big) a un número real (x) dado por el usuario.

La función signo de un número real x, denotada por \mathrm{sgn}(x), \mathrm{sign}(x) o \mathrm{signo}(x), se puede definir de la siguiente manera:

\mathrm{sign}(x) = \begin{cases} 1 & \text{Si } x > 0 \\ 0 & \text{Si } x = 0 \\ -1 & \text{Si } x < 0 \end{cases}

Algoritmo: Valor de la función signo evaluada en un número real
var
  real: x
  entero: res
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo obtiene el valor de la función signo evaluada en un número real dado”)
  escribir(“Por favor, ingrese un número real”)
  leer(x)
  si x > 0 entonces
    res <- 1
  si_no
    si x < 0 entonces
      res <- -1
    si_no
      res <- 0
    fin_si
  fin_si
  escribir(“La función signo evaluada en” & x & ” es igual a ” & res)
fin

Ejemplo 4.3 Realice el análisis y diseño en pseudocódigo de un algoritmo que determine e imprima el mayor de dos números reales dados por el usuario. ¿Qué considera que debería hacer el algoritmo cuando los dos números dados son iguales? ¿hay una única alternativa o debo escoger entre varias posibles formas de atender esa situación?

Si los dos números reales dados por el usuario son diferentes, es poco y bastante sencillo lo que hay que hacer, casi totalmente obvio.

Por otro lado, hay varias maneras de manejar la situación cuando los dos números dados son iguales. Una de ellas es seguir pidiendo, tantas veces como sea necesario, un número distinto a los recibidos hasta el momento; otra opción podría ser pedir, tantas veces como sea necesario, dos nuevos números; o incluso otra opción podría ser imprimir que ninguno de los dos es mayor y no hacer nada más.

Algoritmo: Mayor de dos números reales
//En este algoritmo, si los dos números dados son iguales, entonces se pedirá
//tantas veces como sea necesario un número distinto a los recibidos
var
  real: x, y
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo identifica el mayor de dos valores dados”)
  escribir(“Por favor, ingrese un primer número real”)
  leer(x)
  escribir(“Por favor, ingrese un segundo número real, distinto al anterior”)
  leer(y)
  mientras x == y hacer
     escribir(“Los números dados son iguales. Por favor, ingrese un número real, distinto a los dados previamente”)
     leer(y)
  fin_mientras
  si x > y entonces
    escribir(x & “es el mayor de los dos valores ingresados”)
  si_no
    escribir(y & “es el mayor de los dos valores ingresados”)
  fin_si
fin

Ejemplo 4.4 Realice el análisis y diseño en pseudocódigo de un algoritmo que obtenga e imprima el factorial (k!) de un número entero no negativo (k \geq 0). ¿Qué considera que debería hacer el algoritmo si el número entero dado por el usuario es negativo?

El factorial de un número entero positivo k, denotado k! se puede definir como el producto de todos los números enteros positivos menores o iguales que k: k! = (2)(3) \cdots (k-2)(k-1)(k) o lo que es lo mismo, k! = (k)(k-1)(k-2) \cdots (3)(2)

Además, es necesario tener en cuenta que, 0! = 1 y es buena idea tener en cuenta que, 1! = 1 \qquad 2! = 2

Por otra parte, una forma en que el algoritmo puede atender la situación en la cual el usuario ingresa números enteros no negativos, es seguir pidiendo valores hasta que el usuario ingrese un número entero no negativo.

Algoritmo: Factorial de un número entero no negativo
var
  entero: k, res
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo obtiene el factorial de un número entero no negativo dado”)
  escribir(“Por favor, ingrese un número entero no negativo”)
  leer(k)
  mientras k < 0 hacer
    escribir(“Por favor, ingrese un número entero NO NEGATIVO”)
    leer(k)
  fin_mientras
  si k < 2 entonces
    res <- 1
  si_no
    res <- k
    mientras k > 2 hacer
      k <- k - 1
      res <- res * k
    fin_mientras
  fin_si
  escribir(“El factorial del número dado es” & res)
fin

Ejemplo 4.5 Realice el análisis y diseño en pseudocódigo de un algoritmo que determine e imprima si un número entero mayor que uno (k > 1) dado por el usuario es primo o no lo es.

En matemáticas, un número primo es un número natural mayor que 1 que tiene únicamente dos divisores positivos distintos: él mismo y el 1. Por el contrario, los números compuestos son los números naturales que tienen algún divisor natural aparte de sí mismos y del 1, y, por lo tanto, pueden factorizarse.

Adicionalmente, se sabe que:

  • 2 y 3 son números primos
  • 2 es el único número par que es primo.
  • Un número impar solamente tiene divisores impares.
  • Si un número (k) NO es divisible por un número natural (i) mayor o igual que dos y menor o igual que su raíz cuadrada (2 \leq i \leq \sqrt{k} \equiv 2^2 \leq i^2 \leq k) entonces es primo.

Algoritmo: Es o no un número primo
var
  entero: k, i
  booleano: seguirBusc
  cadena: textoRes
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo determina si un número entero mayor que uno es primo o no lo es”)
  escribir(“Por favor, ingrese un número entero mayor que uno”)
  leer(k)
  mientras k < 2 hacer
    escribir(“Por favor, ingrese un número entero MAYOR QUE UNO”)
    leer(k)
  fin_mientras
  //Se usará ” ” para los que son primos y ” NO ” para los que no
  textoRes <- ” ”
  si k > 3 entonces
    si k % 2 == 0 entonces
      escribir(“2 SI es divisor de” & k)
      textoRes <- ” NO ”
    si_no
      escribir(“2 NO es divisor de” & k)
      i <- 3
      seguirBusc <- true
      mientras seguirBusc hacer
        si k % i == 0 entonces
          escribir(i & ” SI es divisor de ” & k)
          textoRes <- ” NO ”
          seguirBusc <- false
        si_no
          escribir(i & ” NO es divisor de ” & k)
          i <- i + 2
          si i * i > k entonces
            seguirBusc <- false
          fin_si
        fin_si
      fin_mientras
    fin_si
  fin_si
  escribir(“El número” & k & textoRes & “ES PRIMO”)
fin

Ejemplo 4.6 Realice el análisis y diseño en pseudocódigo de un algoritmo que obtenga e imprima una aproximación de la raíz cuadrada de un número real no negativo dado (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

Los babilonios aproximaban raíces cuadradas haciendo cálculos mediante la media aritmética reiteradamente. En términos modernos, se trata de construir una sucesión a_{0}, a_{1}, a_{2}, a_{3}, \dots dada por: a_{n} = \frac{1}{2} \left( a_{n-1} + \frac{c}{a_{n-1}}\right) Puede demostrarse que esta sucesión matemática converge de manera que, a_{n} \xrightarrow[n \to \infty]{} \sqrt{c}

Nos damos cuenta que, \begin{aligned} a_n &= \frac{1}{2} \left(a_{n-1} + \frac{c}{a_{n-1}}\right) \\ &= \left(\frac{1}{2}\right) a_{n-1} + \frac{(c/2)}{a_{n-1}} \end{aligned} y como punto de partida o valor inicial podemos utilizar: \begin{aligned} a_1 &= \frac{1 + c}{2} \\ &= \left(\frac{1}{2}\right) + (c/2) \end{aligned}

Algoritmo: Raíz cuadrada de un número real no negativo
var
  entero: n
  real: c, cMedios, a, aNew, eps
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo obtiene una aproximación de la raíz cuadrada de un número real no negativo dado (c). El algoritmo se detendrá cuando el cambio de una aproximación a la siguiente sea inferior a un número real positivo cercano a cero (epsilon)”)
  escribir(“Por favor, ingrese un número real no negativo (c)”)
  leer(c)
  si c == 0 o c == 1 entonces
    escribir(“La raíz cuadrada de” & c & ” es igual a ” & c)
  si_no
    escribir(“Por favor, ingrese un numero real positivo cercano a cero (epsilon)”)
    leer(eps)
    cMedios <- c / 2
    n <- 2
    a <- 0.5 + cMedios //Este es a_1
    aNew <- 0.5 * a + cMedios / a
    mientras a aNew > eps hacer
      n <- n + 1
      a <- aNew
      aNew <- 0.5 * a + cMedios / a
    fin_mientras
    escribir(“La raíz cuadrada de” & c & ” es aproximadamente igual a ” & aNew)
    escribir(“(con el epsilon dado igual a” & eps & “, la aproximación corresponde al término” & n & ” de la sucesión)“)
  fin_si
fin

Ejemplo 4.7 Realice el análisis y diseño en pseudocódigo de un algoritmo que calcule e imprima una aproximación de \cos(x),

\cos(x) = \sum\limits_{k = 0}^{\infty} \frac{(-1)^k}{(2k)!} x^{2k}

para un x dado por el usuario (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

Se tiene que, \begin{aligned} S_n = \left[ \sum\limits_{k = 0}^{n} \left( \frac{(-1)^k}{(2k)!} x^{2k} \right) \right] \xrightarrow[n \to \infty]{} \cos(x) \end{aligned} de donde observamos que, \begin{aligned} S_n &= \left[ \sum\limits_{k = 0}^{n} \left( \frac{(-1)^k}{(2k)!} x^{2k} \right) \right] \\ &= \frac{1}{1} x^0 - \frac{1}{2} x^2 + \frac{1}{(4)(3)(2)} x^4 - \frac{1}{(6)(5)(4)(3)(2)} x^6 + \cdots \\ &= 1 - \frac{x^2}{2!} + \frac{\left(x^2\right)\left(x^2\right)}{(4)(3)(2!)} - \frac{\left(x^2\right)\left(x^4\right)}{(6)(5)(4!)} + \cdots \end{aligned} es decir, la magnitud de cada término cumple que, \begin{aligned} a_{i} &= \frac{x^i}{i!}, \text{ para } i = 2k = 0,2,4,\dots \\ &= \frac{x^2}{(i)(i-1)} \frac{x^{i-2}}{(i-2)!} \\ &= \frac{x^2}{(i)(i-1)} a_{i-2} \end{aligned} y estos términos se suman o restan de forma intercalada.

El algoritmo debe detenerse cuando: \begin{aligned} \big|S_{n} - S_{n-1}\big| &< \varepsilon \\ a_{i} &< \varepsilon \\ \end{aligned} En este caso el valor absoluto no es necesario debido a que a_i = \frac{x^i}{i!} para i = 2n siempre es positivo.

Algoritmo: Coseno de un número real
var
  entero: i
  real: x, xCuadrNegat, eps, term, res
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo obtiene una aproximación del coseno de un número real dado (x). El algoritmo se detendrá cuando el cambio de una aproximación a la siguiente sea inferior a un número real positivo cercano a cero (epsilon)”)
  escribir(“Por favor, ingrese un número real no negativo (x)”)
  leer(x)
  res <- 1
  si x != 0 entonces
    escribir(“Por favor, ingrese un numero real positivo cercano a cero (epsilon)”)
    leer(eps)
    xCuadrNegat <- - x * x
    i <- 2
    term <- xCuadrNegat / i
    res <- res + term
    mientras term > eps o term < -eps hacer
      i <- i + 2
      term <- term * xCuadrNegat / (i * (i - 1))
      res <- res + term
    fin_mientras
  fin_si
  escribir(“El coseno de” & x & ” es igual a ” & res)
fin

Algoritmo: Coseno de un número real
var
  entero: i
  real: x, xCuadr, eps, magnTerm, res
  booleano: restar
inicio
  escribir(“Bienvenido”)
  escribir(“Este algoritmo obtiene una aproximación del coseno de un número real dado (x). El algoritmo se detendrá cuando el cambio de una aproximación a la siguiente sea inferior a un número real positivo cercano a cero (epsilon)”)
  escribir(“Por favor, ingrese un número real no negativo (x)”)
  leer(x)
  res <- 1
  si x != 0 entonces
    escribir(“Por favor, ingrese un numero real positivo cercano a cero (epsilon)”)
    leer(eps)
    xCuadr <- x * x
    i <- 2
    magnTerm <- xCuadr / i
    restar <- true
    res <- res - magnTerm
    mientras magnTerm > eps hacer
      i <- i + 2
      magnTerm <- magnTerm * xCuadr / (i * (i - 1))
      si restar entonces
        restar <- false
        res <- res + magnTerm
      si_no
        restar <- true
        res <- res - magnTerm
      fin_si
    fin_mientras
  fin_si
  escribir(“El coseno de” & x & ” es igual a ” & res)
fin

¿Cuál es mejor, la opción 1 o la opción 2? ¿por qué?

Ejemplo 4.8 Realice el análisis y diseño en pseudocódigo de un algoritmo que obtenga e imprima una aproximación de una raíz de una función de los reales en los reales dada, usando el método de Newton-Raphson (teniendo en cuenta todas las consideraciones del método). Imagine que las funciones matemáticas se pueden declarar y leer como cualquier otro de los tipos de variables que ya hemos visto. Además, no olvide hacer todas las pruebas de escritorio que considere necesarias para garantizar que el diseño es eficaz y eficiente.

La serie de Taylor, alrededor de un punto x_{n-1}, de la función f(x) es, f(x) = f(x_{n-1}) + f'(x_{n-1}) (x - x_{n-1}) + \frac{f''(x_{n-1})}{2!} (x - x_{n-1})^2 + \cdots

Si utilizamos únicamente los dos primeros términos de la serie y asumimos que a partir de algún n, x_{n} va a estar muy cerca a una raíz de la función f(x) entonces, 0 \approx f\left(x_{n}\right) = f\left(x_{n-1}\right) + f'\left(x_{n-1}\right) \left(x_{n} - x_{n-1}\right) al despejar x_{n}, se tendría que, x_{n} = x_{n-1} - \frac{f\left(x_{n-1}\right)}{f'\left(x_{n-1}\right)} y x_{n} \xrightarrow[n \to \infty]{} x para un x tal que f(x) = 0.

Es así que al usuario se le debe pedir la función f, la función f', un valor inicial x_0, al menos un \varepsilon positivo cercano a cero para determinar cuando detenerse y un máximo al que se espera que nunca llegue n, pero que sirva de tope máximo de iteraciones para evitar que el algoritmo se quede infinitamente buscando una solución.

Además, el algoritmo debe deternerse cuando:

  • \big|f'(x_n)\big| < \varepsilon_1, para evitar los efectos negativos de una división por cero o por un número muy cercano a cero.
  • \big|f(x_n)\big| < \varepsilon_2, que es cuando se logra el objetivo deseado, una aproximación de una raíz de la función f.
  • \big|x_{n} - x_{n-1}\big| < \varepsilon_3, que es cuando x_{n} es aproximadamente igual a x_{n-1}, lo que quiere decir que a partir de ese x_n será muy poco lo que se pueda avanzar en lograr el objetivo deseado.

Algoritmo: Método de Newton-Raphson para encontrar una raíz de un función
//Nos imaginaremos que las funciones matemáticas se pueden declarar y leer
//como cualquier otro tipo de variable
var
  función matemática de \mathbb{R} en \mathbb{R}: f, df
  real: x0, x, xNew, eps, dfx, fx, delta
  entero: maxIter, n
  booleano: seguir
  cadena: msg
inicio
  escribir(“Bienvenido”)
  escribir(“Método de Newton-Raphson para encontrar una raíz de un función”)
  escribir(“Por favor, ingrese la función objetivo (f)”)
  leer(f)
  escribir(“Por favor, ingrese la derivada de la función objetivo (f’)”)
  leer(df)
  escribir(“Por favor, ingrese un numero real positivo menor que 0.01 (epsilon)”)
  leer(eps)
  escribir(“Por favor, ingrese un numero entero positivo (máximo de iteraciones)”)
  leer(maxIter)
  escribir(“Por favor, ingrese un numero real (x_0)”)
  leer(x0)
  xNew <- x0 //Para empezar el siguiente mientras con x0 = x = xNew
  n <- 0
  seguir <- true
  mientras seguir == true y n < maxIter hacer
    x <- xNew
    dfx <- df(x)
    si -eps < dfx y dfx < eps entonces
      msg <- “La derivada evaluada en x_n se acerca demasiado a cero. df(x_n) =” & dfx
      seguir <- false
    si_no
      fx <- f(x)
      delta <- fx / dfx
      xNew <- x delta
      n <- n + 1
      si -eps < fx y fx < eps entonces
        msg <- “Se encontró un x para el cual |f(x)| < epsilon. f(x_n) =” & fx
        seguir <- false
      si_no
        si -eps < delta y delta < eps entonces
          msg <- “No hubo un cambio superior a epsilon de x_n a x_{n+1}. x_n - x_{n+1} =” & delta
          seguir <- false
        fin_si
      fin_si
    fin_si
  fin_mientras
  si seguir == true entonces
    msg <- “Se alcanzó el máximo de iteraciones:” & maxIter & “. x_0 =” & x0 & “. x_n =” & x & “. x_{n+1} =” & xNew
  fin_si
  escribir(msg)
  escribir(“x_0 =” & x0)
  escribir(“x_n =” & x)
  escribir(“f’(x_{n+1}) =” & df(xNew))
  escribir(“f(x_{n+1}) =” & f(xNew))
  escribir(“x_{n+1} =” & xNew)
fin

4.5 Ejercicios

Parte A

Realice el análisis y diseño de cada algoritmo requerido, usando únicamente los elementos incluidos previamente en este material (por ejemplo, no use el operador ^ o ** para una potenciación, ya que ese elemento intencionalmente no fue incluido previamente):

  1. Se convirtió en el Ejemplo 4.1.

  2. Se convirtió en el Ejemplo 4.2.

  3. Se convirtió en el Ejemplo 4.3.

  4. Un algoritmo que le pida al usuario una temperatura y la escala de dicha temperatura, grados Centigrados o grados Fahrenheit, para calcular e imprimir la temperatura equivalente en la otra escala con respecto a la dada por el usuario (si recibe grados centigrados devolverá grados Fahrenheit y viceversa).

  5. Un algoritmo que determine e imprima si la fecha del calendario gregoriano, asociada a un número de día, un número de mes y un número de año dados por el usuario, es una fecha válida o no.

  6. Un algoritmo que imprima el número de día, de mes y de año correspondiente a la fecha del día siguiente, a partir de un número de día, número de mes y número de año dados por el usuario para una fecha válida del calendario gregoriano.

  7. Se convirtió en el Ejemplo 4.5.

  8. Un algoritmo que obtenga e imprima el máximo común divisor de dos números enteros dados por el usuario.

  9. Un algoritmo que obtenga e imprima el mínimo común múltiplo de dos números enteros dados por el usuario.

  10. Se convirtió en el Ejemplo 4.4.

  11. Un algoritmo que obtenga e imprima la potencia k-ésima de un número real x, para k un número entero, con x y k dados por el usuario.

  12. Se convirtió en el Ejemplo 4.6.

  13. Un algoritmo que calcule e imprima una aproximación de \exp(x),

    \exp(x) = \sum\limits_{k = 0}^{\infty} \frac{x^k}{k!}

    para un x dado por el usuario (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

  14. Un algoritmo que calcule e imprima una aproximación de \ln(x),

    \ln(x) = 2 \sum\limits_{k = 0}^{\infty} \left[ \frac{1}{2k+1}\left(\frac{x-1}{x+1}\right)^{2k+1} \right]

    para un x>0 dado por el usuario (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

  15. Un algoritmo que calcule e imprima una aproximación de \sin(x),

    \sin(x) = \sum\limits_{k= 0}^{\infty} \frac{(-1)^k}{(2k+1)!} x^{2k+1}

    para un x dado por el usuario (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

  16. Se convirtió en el Ejemplo 4.7.

  17. Un algoritmo que calcule e imprima una aproximación de \arcsin(x),

    \arcsin(x) = \sum\limits_{k = 0}^{\infty} \frac{(2k)!}{4^k (k!)^2 (2k + 1)} x^{2k+1}

    para un -1 < x < 1 dado por el usuario (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

  18. Calcule e imprima una aproximación para el número \pi, teniendo en cuenta que,

    \frac{\pi}{4} = 4 \arctan \left(\frac{1}{5}\right) - \arctan \left(\frac{1}{239}\right)

    y que

    \arctan(x) = \sum\limits_{k = 0}^{\infty} \frac{(-1)^k}{2k+1} x^{2k+1}

    (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

  19. Un algoritmo que calcule e imprima una aproximación para el número áureo \varphi https://youtu.be/aopHcOm7a-w. Tenga en cuenta que,

    \varphi = \lim_{n \to \infty} \frac{F_{n+1}}{F_n}

    donde F_{n} denota el n-ésimo número de la serie de Fibonacci https://youtu.be/B6ztvqvZTsk (¿cuándo se consideraría que ya se llegó a una aproximación suficientemente buena? ¿en relación a ello, qué le pido al usuario como datos de entrada?).

  20. Un algoritmo que reciba las notas de una cantidad no predeterminada de estudiantes de cierta asignatura (dejando de recibir notas en el momento en que se introduzca un valor de nota fuera del rango válido entre 0 y 5) y que imprima la cantidad de estudiantes que aprobaron la asignatura y la cantidad de estudiantes que la perdieron.

  21. Un algoritmo que reciba las notas de una cantidad no predeterminada de estudiantes de cierta asignatura (dejando de recibir notas en el momento en que se introduzca un valor de nota fuera del rango válido entre 0 y 5) y que imprima el mínimo y el máximo de dichas notas.

  22. Un algoritmo que reciba una cantidad no predeterminada de número reales, en donde se le debe preguntar al usuario si desea de dejar de ingresar valores antes de leer cada valor, y que en el momento en que el usuario informe que desea dejar de ingresar valores se impriman la media y la varianza (poblacionales) de los valores ingresados hasta ese momento (¿qué debe hacer mi algoritmo con las potenciales situaciones en donde el usuario ingresa un único valor o cuando no ingresa valor alguno?). Recuerde que:

    \begin{aligned} \mu &= \frac{1}{N} \sum_{i = 1}^{N} x_i \\ \sigma^2 &= \frac{1}{N} \sum_{i = 1}^{N} \left(x_i - \mu\right)^2 = \left( \frac{1}{N} \sum_{i = 1}^{N} x_i^2 \right) - \mu^2 \end{aligned}

Parte B

Realice el análisis y diseño en pseudocódigo de cada algoritmo requerido, usando únicamente los elementos incluidos previamente en este material. Imagine que las funciones matemáticas se pueden declarar y leer como cualquier otro de los tipos de variables que ya hemos visto. Además, no olvide hacer todas las pruebas de escritorio que considere necesarias para garantizar que su diseño es eficaz y eficiente:

  1. Un algoritmo que obtenga e imprima una aproximación de una raíz, entre dos números reales dados, de una función de los reales en los reales dada, usando el método de bisección (teniendo en cuenta todas las consideraciones del método).

  2. Se convirtió en el Ejemplo 4.8.

  3. Un algoritmo que obtenga e imprima una aproximación de la derivada (derivada numérica) evaluada en un número real dado, de una función de los reales en los reales dada (bien definida y derivable alrededor del número real dado).

  4. Un algoritmo que obtenga e imprima una aproximación de la integral definida entre dos números reales dados, de una función de los reales en los reales dada (bien definida e integrable en el intervalo de integración), usando la regla del trapecio compuesta o regla de los trapecios.

  5. Un algoritmo que obtenga e imprima una aproximación de la integral definida entre dos números reales dados, de una función de los reales en los reales dada (bien definida e integrable en el intervalo de integración), usando la regla o método de Simpson (1/3) compuesto.

4.6 Referencias

Böhm, Corrado, and Giuseppe Jacopini. 1966. “Flow Diagrams, Turing Machines and Languages with Only Two Formation Rules.” Communications of the ACM 9 (5): 366–71.