27 de noviembre de 2017

Ejercicios selectos (matrices).

  1. Considere el Ejemplo 6.7. ¿Qué pasa si hay más inicializadores que elementos físicos? Pruebe dicha situación en los dos esquemas de inicialización planteados en el blog.
  2. Escriba un programa que pruebe la función suma del Ejemplo 6.8.
  3. Modifique la función leeMatriz del Ejemplo 6.7 para que almacene valores de tipo float en la matriz matriz. Una vez que haya probado su función, agréguela a la biblioteca de funciones del Ejemplo 6.8.
  4. Modifique la función imprimeMatriz del Ejemplo 6.7 para que imprima los valores de tipo float de la matriz matriz. Una vez que haya probado su función, agréguela a la biblioteca de funciones del Ejemplo 6.8.
  5. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void resta(float a[ ][N], float b[ ][N], float c[ ][N], int m, int n). La función deberá calcular en la matriz c la resta de la matriz a menos la matriz b de manera análoga a como lo hace la función suma del Ejemplo 6.8.
  6. Agregue la función resta del ejercicio anterior, a la biblioteca de funciones del Ejemplo 6.8.
  7. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void imprimeTranspuesta(float a[ ][N], int m, int n). La función deberá imprimir en la salida estándar la matriz transpuesta de a. La matriz transpuesta se genera convirtiendo los renglones en columnas y viceversa.
  8. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void transpuesta(float a[ ][N], float aT[ ][N],int m, int n). La función deberá generar en la matriz aT la matriz transpuesta de a. La matriz transpuesta se genera convirtiendo los renglones en columnas y viceversa.
  9. Agregue la función transpuesta del ejercicio anterior a la biblioteca de funciones del Ejemplo 6.8.
  10. Escriba un programa que defina y pruebe una función con el siguiente prototipo: int esSimetrica(float a[ ][N], int n). La función deberá determinar si la matriz a es o no simétrica. Una matriz simétrica es aquella que es igual con su matriz transpuesta.
  11. Agregue la función esSimetrica del ejercicio anterior, a la biblioteca de funciones del Ejemplo 6.8.
  12. Escriba un programa que defina y pruebe una función con el siguiente prototipo: void producto(float a[ ][N], float b[ ][N], float c[ ][N], int m, int n, int l). La función debe calcular el producto de las matrices  a y b almacenando el resultado en c. Para poder realizar este ejercicio, investigue cómo se define y obtiene el producto de matrices.
  13. Agregue la función producto del ejercicio anterior a la biblioteca de funciones del Ejemplo 6.8.
  14. Escriba una función que determine la suma de los elementos de la diagonal principal de una matriz.
  15. Escriba una función que determine la suma de los elementos de la diagonal secundaria de una matriz.
  16. Un cuadrado mágico es una matriz de n x n donde n es impar, en donde se colocan números enteros en cada una de sus entradas y que además cumple con las siguientes características:
    • La suma de los números de cualquier línea (horizontal, vertical o diagonal) será siempre la misma. A esta suma se le denomina constante mágica.
    • Todos los números almacenados en un cuadrado mágico deben ser distintos.
    Con base en lo anterior, escriba una función con el siguiente prototipo:

    int cuadradoMagico(int c[ ][N], int n);

    Dado un cuadrado c de dimensiones lógicas n, la función determina si representa (regresa 1) o no (regresa 0) un cuadrado mágico. Ejemplo:

    4     9     2
    3     5     7
    8     1     6
    es un cuadrado mágico.
  17.  Haga un programa que implemente el juego del gato (tic tac toe). En este programa el usuario jugará en contra de la computadora. Utilice una matriz de tipo char e inicialícela con espacios en blanco. Utilice "O" para la computadora y "X" para el usuario. Haga su algoritmo tan elaborado como considere, no permita que el usuario gane fácilmente; sin embargo, una posibilidad es que cuando sea el turno de la computadora, simplemente recorra la matriz buscando una posición que no esté ocupada.
  18. Considere una implementación básica del Juego de la Vida con base en una matriz de 0's y 1's. El Juego de la Vida fue diseñado por el matemático británico John Horton Conway en 1970.
 Arreglos de n dimensiones.
  1. Las figuras del arreglo tridimensional discutidas en el blog (Arreglos de dimensiones) muestran en gris algunos de los elementos del arreglo de tres dimensiones. Denote todos los elementos visibles (para ambas) de dicho arreglo siguiendo la nomenclatura del lenguaje C.
  2. De manera análoga a lo descrito en la entrada Arreglos de dos dimensiones, dibuje y describa la representación física en la memoria del arreglo tridimensional del ejercicio anterior. Sugerencia: la representación en memoria se realiza también de manera lineal: los renglones de la primera matriz, después los de la segunda matriz, etcétera, la representación de malla puede ayudarle a visualizar mejor la representación.
  3. Basándose en el Ejemplo 6.9, escriba un programa que generalice la idea planteada para definir, inicializar, e imprimir en la salida estándar un hipercubo.
  4. Basándose en el Ejemplo 6.9, escriba un programa que generalice la idea planteada para definir, inicializar, e imprimir en la salida estándar un arreglo de cinco dimensiones cada una con un tamaño distinto, por ejemplo: dos para la primera dimensión, tres para la segunda, cuatro para la tercera y así sucesivamente.

1 de agosto de 2017

Diseño y consideraciones.

Diseño básico de programas estructurados.
   La esencia del diseño mostrado a continuación está basado principalmente en las reglas para la formación de programas estructurados propuestas en [Deitel].

   Al conectar de forma arbitraria o poco cuidadosa los símbolos de los diagramas de flujo, se puede incurrir en diagramas de flujo no estructurados como el de la siguiente figura:

Diagrama de flujo no estructurado.

   La generación de diagramas de flujo estructurados requiere de utilizar estructuras de control con una única entrada y una única salida, de tal manera que sólo haya una forma de entrar y una de salir de la estructura de control.

   Las estructuras de control descritas en las entradas Diagramas de flujo y Pseudo código cumplen con lo anterior. Se deja como ejercicio al lector el validar y verificar dicha situación; en éste sentido, observe que aunque la estructura de selección múltiple en el diagrama de flujo parece tener más de una salida, sólo una de ellas es finalmente procesada, y que independientemente de la(s) sentencia(s) que haya(n) sido seleccionada(s), su salida converge a un único punto de salida representado por el conector.

   Reglas para la formación de algoritmos estructurados.
   A continuación se muestran las reglas para la construcción de algoritmos estructurados, mismas que bien podrían denominarse como “Algoritmo para la construcción de algoritmos estructurados”:
  1. Empiece con el diagrama de flujo más simple.
  2. Cualquier rectángulo (acción, sentencia, etc.) puede ser reemplazado por dos rectángulos de manera secuencial. Esta es la regla de apilamiento.
  3. Cualquier rectángulo puede ser reemplazado por cualquier estructura de control. Esta es la regla de anidamiento.
  4. Aplicar de manera sucesiva las reglas 2 y 3.
   En la siguiente figura se muestra la regla 1 y la aplicación repetida de la regla 2.

Aplicación de las reglas 1 y 2.

   La aplicación de las reglas anteriores derivan siempre en un diagrama de flujo estructurado con una apariencia clara de bloques constructivos [Deitel].

   Por otro lado, la siguiente figura muestra la aplicación de la regla de anidamiento al diagrama de flujo más simple que puede haber. Note que los bloques han sido substituidos por estructuras de selección doble y de repetición.

Aplicación de la regla 3.

   Por último, es importante resaltar que la aplicación de la regla 4 genera estructuras más grandes, más complejas y con un nivel de anidamiento más intrincado. Adicionalmente, cabe mencionar también que los diagramas de flujo que resultan de aplicar las reglas descritas anteriormente, constituyen el conjunto de todos los diagramas de flujo estructurados posibles, y por lo tanto, también el conjunto de todos los posibles programas estructurados [Deitel].

Consideraciones finales.
   La programación estructurada no debe confundirse con el conocimiento de un lenguaje de programación. La programación estructurada es un modelo de programación y es independiente del lenguaje que se utilice para su implementación.

   El estudio y el conocimiento de técnicas, así como el desarrollo de algoritmos previos a la fase de implementación, no sólo es una buena práctica de programación, sino un principio esencial de la Ingeniería de Software.

   Por todo lo anterior, si lo que se quiere es llevar a buen fin sus proyectos de programación y evitar dolores de cabeza innecesarios, debe tomar en consideración que, antes de enfrentarse en una lucha encarnizada teniendo a la computadora como su adversario y al lenguaje de programación como arma, debería, antes de escribir su primera línea de código, hacer un análisis profundo del problema a resolver, elaborar un plan de solución general, especificar gradualmente dicho plan, y realizar un conjunto de pruebas representativas.

21 de junio de 2017

Pruebas de escritorio.

   En las secciones correspondientes al algoritmo de Euclides de las entradas Diagramas de flujo y Pseudo código respectivamente, se muestran esencialmente el mismo algoritmo en dos notaciones distintas. El algoritmo de Euclides determina el Máximo Común Divisor (MCD) de dos números enteros positivos.

   Una prueba de escritorio es un tipo de prueba algorítmica que consiste en la validación y verificación del algoritmo a través de la ejecución de las sentencias que lo componen (proceso) para determinar sus resultados (salida) a partir de un conjunto inicial determinado de elementos (entrada).
 
    Por simplicidad y para facilitar la comprensión de la prueba se repetirá aquí la versión en diagrama de flujo, pero se puede y recomienda consultar también la versión en Pseudo código:
Algoritmo de Euclides para determinar el MCD.

   Suponga que desea saber cuál es MCD de 15 y 4. Una prueba de escritorio para el algoritmo de MCD consistiría, en primer lugar, de la identificación de las variables involucradas, es decir:

      m =
      n =
      r =

   La sentencia Obtener(m, n) (proceso de entrada) definirá los valores de las variables m y n respectivamente que, con base en lo planteado en el algoritmo y en lo que se desea saber sería:

      m = 15
      n = 4
      r =

mientras que r se define en función de la expresión r = m mod n, es decir:

      m = 15
      n = 4
      r = 3

el siguiente paso es verificar la condición del while (¿es el residuo r distinto de cero?). Como dicha condición es evaluada como verdadera (tres es distinto de cero), se tendría la siguiente secuencia de valores para las variables con base en las operaciones descritas si se sigue el lado derecho del diamante, mismas que constituyen las operaciones realizadas dentro del ciclo:

      m = 15, 4
      n = 4, 3
      r = 3, 1

para la primera iteración o ejecución del ciclo. Como el residuo es distinto de cero, la condición del ciclo es verdadera y sea realiza una segunda iteración, generando ahora los valores:

      m = 15, 4, 3
      n = 4, 3, 1
      r = 3, 1, 0
 
de donde puede verse, que el MCD de 15 y 4 es 1 (almacenado en n) cuando la condición del ciclo se hace falsa, debido a que el residuo es finalmente cero.

   Asegúrese de comprender el proceso descrito y genere usted mismo cada uno de los valores descritos para corroborar lo aquí expuesto. También utilice ahora los mismos valores pero intercambiados, ¿qué sucede? ¿qué secuencia de valores se genera? ¿cuál es el MCD? ¿cuál es ahora el MCD de 15 y 10? ¿cuál es el MCD de 10 y 15?

   Pruebe con otros valores hasta que comprenda el funcionamiento del algoritmo, y compruebe los resultados de sus pruebas de escritorio con los algoritmos descritos en diagrama de flujo y pseudo código respectivamente.


19 de junio de 2017

Pseudo código.

   El pseudo código es un tipo de notación algorítmica en el que se utiliza texto. Es un lenguaje artificial de especificación de algoritmos, caracterizado por:
  1. Mantener una sangría conveniente para la fácil identificación de los elementos que lo componen.
  2. Permitir la declaración de los datos (constantes y/o variables) manipulados por el algoritmo.
  3. Disponer de un conjunto pequeño y completo de palabras reservadas. Las palabras reservadas son propias del lenguaje artificial y no pueden ser utilizadas como identificadores de variables o módulos. Las palabras reservadas se muestran en negritas en la descripción de las estructuras de control correspondientes.
   El pseudo código se concibió para superar las dos principales desventajas del diagrama de flujo:
  1. Lento de crear.
  2. Difícil de modificar.
   A pesar de esto, los diagramas de flujo son una excelente herramienta para el seguimiento del flujo de un algoritmo, así como para transformar con relativa facilidad dichos algoritmos en programas.

Estructuras de control.
   Esta sección describe brevemente en su representación en pseudo código las estructuras de control. Todas las estructuras de control enunciadas siguen las mismas características descritas con anterioridad en las entradas correspondientes de Algoritmos (panorama general) y Algoritmos (definición y conceptos), por lo que se recomienda su revisión.

   Estructura secuencial.
   La siguiente lista de sentencias ilustra la representación en pseudo código de la estructura de control secuencial:

                                                       <Sentencia 1>
                                                       <Sentencia 2>
                                                                  .
                                                                  .
                                                                  .
                                                       <Sentencia n>
Estructura secuencial en pseudo código.

    Es claro que el orden en que se ejecutan o procesan las sentencias es el orden natural en el que aparecen: de izquierda a derecha y de arriba hacia abajo, exactamente de la misma forma en la que leemos un texto.
 
   Estructuras de selección.
   Las estructuras de selección simple, doble y múltiple se muestran a continuación. La condición debe tener una naturaleza de carácter booleano, es decir, que la condición pueda ser evaluada sin ambigüedad como verdadera o falsa:

                                                  if (condición)
                                                         <Sentencia(s)>

Estructura de selección simple (if) en pseudo código.

    Si condición es verdadera, se procesan el grupo de Sentencias (puede ser una o varias) correspondiente, si es falsa, se ignora.
 
                                                  if (condición)
                                                         <Sentencia(s) 1>
                                                  else
                                                         <Sentencia(s) 2>
Estructura de selección doble (if-else) en pseudo código.
 
    Para la estructura de control if-else, si condición es verdadera, se procesan el grupo de Sentencias 1 (puede ser una o varias) correspondiente, si es falsa, se procesan el grupo de Sentencias 2. 

                                                  if (condición 1)
                                                         <Sentencia(s) 1>
                                                  else if (condición 2)
                                                         <Sentencia(s) 2>
                                                  else if (condición 3)
                                                         <Sentencia(s) 3>
                                                                      .
                                                                      .
                                                                      .
                                                  else
                                                         <Sentencia(s) n + 1>

Estructura de selección múltiple anidada (if-else) en pseudo código.

    La estructura de control if-else se puede anidar, permitiendo con ello una mayor versatilidad, así por ejemplo, si condición 1 es verdadera, se procesan el grupo de Sentencias 1 (puede ser una o varias) y se ignoran todas las demás; si es falsa se evalúa la condición 2 y, si es verdadera, se procesan el grupo de Sentencias 2 y se ignoran todas las restantes y así sucesivamente con todas las condiciones y sentencias que existan.
    El último else es opcional. Si existe y ninguna de las condiciones anteriores fue verdadera, entonces se procesa el grupo de Sentencias n + 1.

                                                switch (indicador)
                                                        case <valor 1>:
                                                                <Sentencia(s) 1>
                                                        case <valor 2>:
                                                                <Sentencia(s) 2>
                                                                      .
                                                                      .
                                                                      .
                                                        case <valor n>:
                                                                <Sentencia(s) n>
                                                        default:
                                                                <Sentencia(s) n + 1>

Estructura de selección múltiple (switch-case) en pseudo código.

    La estructura de selección múltiple switch-case es un caso particular de selección múltiple anidada if-else. En ella, el indicador se evalúa y se compara con alguno de los valores (valor 1, valor 2, ..., valor n) para que, se coincide con alguno de ellos, se procese el grupo de Sentencias correspondiente.
    El default y su grupo de sentencias asociado es opcional. Si existe, y el indicador no coincidió con ninguno de los valores propuestos por los case, entonces se procesa el grupo de Sentencias n + 1.
 
    Estructuras de repetición.
   Finalmente, las estructuras de repetición “mientras” (while) y “hacer mientras” (do-while), son presentadas a continuación:


                                                   while (condición)
                                                         <Sentencia(s)>
                                                   end while

Estructura de repetición mientras (while) en pseudo código.
 
    En la estructura de repetición while, mientras la condición sea verdadera se procesará el grupo de Sentencias correspondiente. Una vez que la condición sea falsa, se ejecutará la primera sentencia que aparezca después del end while continuando el flujo de control secuencial a partir de ahí.

                                                   do
                                                         <Sentencia(s)>
                                                   while (condición)

Estructura de repetición hacer-mientras (do-while) en pseudo código.

       En la estructura de repetición do-while, mientras la condición sea verdadera se procesará el grupo de Sentencias correspondiente. Una vez que la condición sea falsa, se ejecutará la primera sentencia que aparezca después del while continuando el flujo de control secuencial a partir de ahí.
   
    Observe que la descripción y el funcionamiento de la estructura do-while es similar al de la estructura while, con la diferencia de que la expresión en el ciclo while es evaluada al principio, mientras que en el ciclo do-while es evaluada al final. Con base en lo anterior se tiene que:
  • Las sentencias de la estructura de repetición while se repiten de 0 a n-veces.
  • Las sentencias de la estructura de repetición do-while se repiten de 1 a n-veces.
   En esencia, esta es la única diferencia que existe entre las estructuras de repetición while y do-while. No pierda de vista esta simple pero fundamental diferencia.

   Pseudocódigo del algoritmo de Euclides.
   Esta sección muestra el pseudocódigo para el problema propuesto en la sección Algoritmo de Euclides: definición del problema de la entrada Algoritmos (definición y conceptos), por lo que convendría revisarla para tener una mejor comprensión del algoritmo.

   Se deja como ejercicio al lector el comparar el siguiente algoritmo en pseudo código con el de la sección Diagrama de flujo del algoritmo de Euclides de la entrada Diagramas de flujo.

                Algoritmo MCD
                    Inicio
                               Variables
                                          m, n, r de tipo entero

                               obtener (m, n)

                               r = m mod n

                               while (r != 0)
                                          m = n
                                          n = r
                                          r = m mod n
                               end while

                               imprimir (n)
                    Fin
Algoritmo de Euclides en pseudo código.


18 de mayo de 2017

Diagramas de flujo.

   Un diagrama de flujo es una notación algorítmica de tipo gráfica.

   Un diagrama de flujo es una herramienta gráfica de descripción de algoritmos, que se caracteriza por utilizar un conjunto de símbolos gráficos para expresar simbólicamente los flujos de control o el orden lógico en el que se realizan las acciones de un algoritmo.

   Aunque existe en la literatura una amplia variedad de representaciones para los símbolos utilizados en los diagramas de flujo, en este blog se adoptarán sólo cinco, mismos que se presentan a continuación:

Elementos gráficos de los diagramas de flujo y su significado.

Estructuras de control.
   Esta sección muestra los diagramas de flujo de las estructuras de control, para más detalles respecto a las estructuras de control, refiérase por favor a la entrada Algoritmos (panorama general).

   Estructura secuencial.
   La siguiente figura muestra el diagrama de flujo que representa a la estructura de control secuencial. La estructura y su funcionamiento se explican por sí mismas:
Estructura secuencial en diagrama de flujo.

   Estructuras de selección.
   Las siguientes figuras muestran los diagramas de flujo de las estructuras de selección:

(a) Estructura de selección simple (if) en diagrama de flujo.
(b) Estructura de selección doble (if-else) en diagrama de flujo.
   
(c) Estructura de selección múltiple (switch-case) en diagrama de flujo.

   Puede observarse en la figura (a), que en la estructura de selección simple se evalúa la condición, y si ésta es verdadera, se ejecuta un determinado grupo de sentencias; en caso contrario, las sentencias son ignoradas.

   Por otro lado, en la estructura de selección doble (b), cuando la condición es verdadera se ejecutará un determinado grupo de sentencia(s) 1, y si es falsa se procesará otro grupo diferente de sentencia(s) 2.

   Por último, en la estructura de selección múltiple se ejecutarán unas sentencias u otras según sea el valor que se obtenga al evaluar una expresión representada por el indicador. Se considera que dicho resultado debe ser de tipo ordinal, es decir, de un tipo de datos en el que cada uno de los elementos que constituyen el tipo, excepto el primero y el último, tiene un único predecesor y un único sucesor.

   Estructuras de repetición.
Las siguientes figuras muestran las estructuras de repetición básicas.

(a) Estructura de repetición while en diagrama de flujo.
(b) Estructura de repetición do-while en diagrama de flujo.
 
    Lo que caracteriza a la estructura de repetición “mientras” (while) como puede apreciarse en la figura (a), es que las sentencias del cuerpo del ciclo se procesan cuando la condición es verdadera, además de que la condición es verificada al principio, de donde se deduce que las sentencias se podrán ejecutar de 0 a n veces.

   Por otro lado, en la estructura de repetición “hacer mientras” (do-while), las sentencias del cuerpo del ciclo se ejecutan al menos una vez, y continúan repitiéndose hasta que la condición sea falsa. La verificación de la condición se realiza al final del ciclo (figura (b)), por lo que se deduce que las sentencias se ejecutarán de 1n veces; i.e., al menos una vez.

Diagrama de flujo del algoritmo de Euclides
   La siguiente figura  muestra el diagrama de flujo para el problema propuesto en la sección Algoritmo de Euclides: definición del problema de la entrada Algoritmos (definición y conceptos). La solución a dicho problema está determinada por el algoritmo de Euclides, mismo que se presenta a continuación en su versión de diagrama de flujo:

Diagrama de flujo para el algoritmo de Euclides.
 
   Para ser congruentes con la propuesta de solución realizada en la sección Estructura de un algoritmo de la entrada Algoritmos (definición y conceptos), además del algoritmo, se ha indicado en recuadros de menor intensidad, las especificaciones del proceso de entrada, del proceso de salida, y del proceso general de solución.

17 de mayo de 2017

Ejercicios selectos (bienvenido).

  1. Investigue qué secuencias de escape existen y para qué sirven. Escriba un programa que las incluya, y pruebe cada una de ellas.
  2. Escriba un programa en C que imprima su nombre completo en la pantalla en una sola línea.
  3. Escriba un programa en C que imprima su nombre completo en la pantalla pero dividido en tres líneas:
    1. En la primera línea su(s) nombre(s).
    2. En la segunda línea su primer apellido.
    3. En la tercera línea su segundo apellido.
  4. Diferentes culturas y pueblos tienen distintos tipos de leyendas y la disciplina de la computación no escapa a ellas. Se dice que todo aquel que se inicie en las maravillosas artes de la programación estructurada usando al lenguaje de programación C debe, si no quiere recibir la maldición de ser un mal programador toda su vida, escribir un programa que imprima, en la salida estándar el mensaje: Hola Mundo! Independientemente de sus creencias, evite el riesgo de la maldición y colabore con la perpetuidad de esta tradicional leyenda realizando este simple pero gratificante ejercicio de tal manera que, si el augurio se cumple, se dé por descartada al menos, la maldición...
  5. Investigue qué otros tipos de datos existen en C, qué representación tienen, cuál es el rango de números que pueden almacenar, y cuál es el especificador de formato que utilizan para las funciones printf y scanf respectivamente. Nota: no siempre es el mismo.
  6. Extienda el Ejemplo 2.5 para que considere todos los tipos de datos en C que investigó en el ejercicio anterior.  El Ejemplo 2.5 muestra el uso del operador unario (un operador unario se refiere a que el operador sólo necesita un operando para realizar su función; el operador + por ejemplo es un operador binario porque necesita dos operandos para realizar su función) sizeof para determinar el tamaño en bytes que ocupa el operando asociado. Observe también que el operador sizeof trabaja no sólo sobre tipos de datos (líneas 12 y 14), sino también sobre variables (líneas 11 y  13). El rango de los números representados por un tipo de dato depende del tamaño en bits del tipo de dato; para determinar el tamaño en bits multiplique el tamaño en bytes del tipo de dato y multiplíquelo por ocho.
  7. Experimente omitiendo intencionalmente el uso del operador “&” (ampersand) en la lectura de una variable como la que se hace en el Ejemplo 2.3. ¿Qué sucede cuando compila? ¿Qué pasa cuando se ejecuta?,  si el programa es ejecutado, ¿qué valor se guarda en la variable?
  8. Escriba una programa que basándose en el Ejemplo 2.3, realice la resta de dos números enteros decimales.
  9. Escriba una programa que basándose en el Ejemplo 2.3, realice la multiplicación de dos números enteros decimales.
  10. Escriba una programa que basándose en el Ejemplo 2.3, realice el módulo de dos números enteros decimales. ¿Qué podría pasar en este caso?
  11. Escriba una programa que basándose en el Ejemplo 2.3, realice la división de dos números enteros decimales ¿Qué podría pasar en este caso?
  12. Repita los ejercicios 8-11 con las consideraciones implementadas en el Ejemplo 2.4.
  13. Basándose en el Ejemplo 2.4, modifíquelo para que ahora trabaje con números con punto decimal (float). Haga lo propio para los ejercicios 8, 9 y 11; tome en cuenta que el especificador de formato a utilizar es ahora "%f".
  14. Dependiendo del IDE que esté utilizando, investigue y documéntese acerca de la depuración de programas. Si está compilando en línea, busque un tutorial del programa gdb; en cualquier caso, debe saber que la depuración no sólo le será de suma utilidad, sino que es una tarea fundamental de la programación.


Ejercicios selectos (el proceso).

  1. Busque y lea la historia tan interesante que existe atrás del lenguaje de programación C, y conteste al menos las siguientes preguntas:
    1. ¿Quién o quiénes fueron sus creadores?
    2. ¿Cómo fue su evolución?
    3. ¿En qué año surge?
    4. ¿Cuántas versiones han habido?
    5. ¿A qué se refiere el ANSI C?
  2. Investigue el concepto de código espagueti y compárelo con el concepto de programación estructurada. En base a lo anterior, ¿cuáles considera que serían las ventajas de tener una estructura dentro de un código fuente?
  3. Investigue qué lenguajes de programación además de C, soportan el paradigma estructurado.
  4. Investigue el concepto de Ingeniería de Software.
  5. Investigue el concepto de Proceso de Desarrollo de Software.


Bibliografía sugerida.

  1. Abellanas, M. y Lodares D., "Análisis de Algoritmos y Teoría de Grafos". Macrobit ra-ma.
  2. Deitel, H. M. y Deitel, P. J., "Cómo Programar en C/C++", Prentice Hall.
  3. Joyanes, Aguilar L., "Metodología de la Programación: Diagramas de Flujo, Algoritmos y Programación Estructurada", Mc Graw Hill.
  4. Kernighan, B.W. y Pike R, "La Práctica de la Programación", Prentice Hall.
  5. Kernighan, L., Brian, A.H., y Ritchie, K., Dennis, "El Lenguaje de Programación C", Prentice Hall.
  6. Knuth, Donald Ervin., "The Art of Computer Programming", Vol. 1, 3rd. ed. Addison-Wesley.
  7. Pérez-Aguila, R., "Una Introducción a las Matemáticas para el Análisis y Diseño de Algoritmos", El Cid Editor 2012. 
  8. Schildt, Herbert, "C: the Complete Reference", Osborne, Mc Graw Hill.
  9. Ullman, Jeffrey. D.,  "Fundamental Concepts of Programming Systems", Addison-Wesley Publishing Company Inc.
  10. Wirth, Niklaus, "Algoritmos y Estructuras de Datos", Prentice Hall.

Algoritmos (definición y conceptos).

   El concepto de algoritmo forma parte esencial de los fundamentos de la computación. La matemática discreta y en particular la matemática constructivista, son tan antiguas como la propia matemática, y trata aquellos aspectos para los cuales existe una solución constructivista; esto es, no se conforma con demostrar la existencia de solución, sino que se pregunta cómo encontrar dicha solución.

   El término algoritmo es muy anterior a la era de la computación: proviene hasta donde se sabe de Mohammed al-Khowârizmî (cuyo apellido se tradujo al latín en la palabra algoritmus), quien era un matemático persa del siglo IX que enunció paso a paso las reglas para sumar, restar, multiplicar y dividir números decimales. En este mismo contexto también debe recordarse el algoritmo de Euclides obtenido aproximadamente 300 años antes de nuestra era.

Definición y conceptos.
   En términos generales se define un algoritmo, como el método para resolver un determinado problema, donde el ejecutor de las instrucciones que realiza la tarea correspondiente se denomina procesador.

   Existen algoritmos que describen toda clase de procesos, por ejemplo:
  • Las recetas de cocina.
  • Las partituras musicales.
  • Cambiar la llanta de un auto, etc.
   El procesador realiza dicho proceso siguiendo o ejecutando el algoritmo correspondiente. La ejecución de un algoritmo requiere de la ejecución de cada uno de los pasos o instrucciones que lo conforman.

   Por otro lado, un algoritmo debe estar expresado de tal forma que el procesador lo entienda para poder ejecutarlo. Se dice que el procesador es capaz de interpretar un algoritmo, si el procesador puede:
  1. Entender lo que significa cada paso.
  2. Llevar a cabo (procesar) la sentencia correspondiente.
   Esto significa que para que un algoritmo pueda ser correctamente ejecutado, cada uno de sus pasos debe estar expresado de tal forma que el procesador sea capaz de entenderlos y ejecutarlos adecuadamente.

   Ahora bien, el término de algoritmo puede tener diferentes connotaciones dependiendo del contexto del que se hable; en nuestro caso el contexto es el de programación, esto es, el de utilizar a la computadora como herramienta para la resolución de problemas.

   En este sentido, se definirá un algoritmo como un conjunto finito de instrucciones que especifican la secuencia ordenada de operaciones a realizar para resolver un problema.

   Con base en lo anterior, si el procesador del algoritmo es una computadora, el algoritmo debe estar expresado en forma de un programa, el cual se escribe en un lenguaje de programación. Un lenguaje de programación es lenguaje artificial que se utiliza para expresar programas de computadora, y a la actividad de expresar un algoritmo en un lenguaje de programación determinado se le denomina programar.

   Cada paso del algoritmo se expresa mediante una instrucción o sentencia en el programa, por lo que un programa consiste en una secuencia de instrucciones en donde cada una de las cuales especifica ciertas operaciones a realizar por parte de la computadora.

Uso de la computadora en la resolución de problemas a través de programas.
   En general, se escriben algoritmos para resolver problemas que no son tan fáciles de resolver a primera vista, y de los que se necesita especificar el conjunto de acciones que se llevarán a cabo para su resolución. Además, como lo que interesa es resolver problemas utilizando a la computadora como medio, los algoritmos tendrán la finalidad de ser traducidos en programas, razón por la cual es conveniente el mencionar el proceso general de resolución de problemas a través de programas, mismo que abarca desde que se dispone de un algoritmo, hasta que la computadora lo ejecuta.

   El proceso general de resolución de problemas a través de programas se muestra a continuación:
  1. Algoritmo.
  2. Programación.
  3. Programa en lenguaje de alto nivel.
  4. Traducción.
  5. Programa en código de máquina.
  6. Ejecución.
   Existen diferentes formas de traducir un algoritmo a un programa. En el proceso mostrado con anterioridad, se ha escogido la representación en un lenguaje de programación de alto nivel debido a que los lenguajes de éste tipo proporcionan un mayor nivel de abstracción y semejanza con el lenguaje natural (inglés).

   Es importante recordar que las computadoras tienen su propio lenguaje (binario), por lo que es necesario un proceso de traducción (realizado por el compilador o intérprete), para que se traduzca el conjunto de sentencias escritas en un lenguaje de programación de alto nivel (código fuente), a un conjunto de instrucciones que sean comprensibles para la computadora (código de máquina o código objeto).

   Aunque lo que sucede en cada una de las etapas del proceso involucrado es algo mucho más complejo que lo que aquí se ha sucintamente descrito, si todo está bien, el resultado de dicho proceso será la ejecución del programa basado en el algoritmo.

   Teniendo en consideración lo anterior, debería ser claro que el papel del algoritmo es fundamental, ya que sin algoritmo no puede haber programas, y sin programas, no hay nada que ejecutar en la computadora.

   Por otro lado, es importante también mencionar y enfatizar que un algoritmo es independiente tanto del lenguaje de programación en que finalmente se transcribe, como de la computadora en la que se ejecuta, por lo que no deberían ser diseñados en función de ello.

   Finalmente, otro aspecto muy importante dentro de los algoritmos es el del análisis. El análisis y diseño formal de algoritmos es una actividad intelectual considerable y no trivial, ya que el diseño de buenos algoritmos requiere creatividad e ingenio por un lado, y por el otro que, en general, no existe un algoritmo que diseñe un algoritmo a partir de la descripción de un determinado problema. El análisis formal de algoritmos está fuera de los alcances de este blog.

Cinco importantes condiciones de un algoritmo.
   Los algoritmos, además de ser un conjunto finito de reglas que dan lugar a una secuencia de operaciones para resolver un tipo específico de problemas, deben cumplir con cinco importantes condiciones [Abellanas] mismas que son descritas a continuación:
  1. Finitud: un algoritmo tiene que acabar siempre tras un número finito de pasos (un procedimiento que tiene todas las características de un algoritmo, salvo que posiblemente falla en su finitud, se conoce como método de cálculo.).
  2. Definibilidad: cada paso de un algoritmo debe definirse de modo preciso; las acciones a realizar deben estar especificadas para cada caso rigurosamente y sin ambigüedad.
  3. Conjunto de entradas: debe existir un conjunto específico de elementos donde cada uno de ellos, constituye los datos iniciales de un caso particular del problema que resuelve el algoritmo. A este conjunto de elementos se le denomina conjunto de entradas del algoritmo.
  4. Conjunto de salidas: debe existir un conjunto específico de elementos donde cada uno de ellos, constituye la salida o respuesta que debe obtener el algoritmo para los diferentes casos particulares del problema. A este conjunto de elementos se le denomina conjunto de salidas del algoritmo, y para cada entrada del algoritmo debe existir una correspondiente salida asociada, misma que constituye la solución al problema particular determinada por dicha entrada.
  5. Efectividad: un algoritmo debe ser efectivo. Todas las operaciones a realizar por el algoritmo deben ser lo bastante básicas para poder ser efectuadas de modo exacto y en un lapso de tiempo finito por el procesador que ejecute el algoritmo.
   Las cinco condiciones anteriores reducen significativamente el espectro tan amplio que hasta ahora se ha manejado como algoritmo. Así por ejemplo, aunque una receta de cocina podría considerarse como un algoritmo, es común encontrar expresiones imprecisas en ella que dan lugar a ambigüedad (violando la condición 2), tales como “añádase una pizca de sal”, “batir suavemente", etc., invalidando con ello la formalidad de un algoritmo dentro del contexto que nos compete e interesa.

Estructura de un algoritmo.
   Aunque no existe una única forma de representar un algoritmo (vea [Abellanas}, [Deitel], [Joyanes], [Ullman], [Wirth], etc., cada uno tiene formas diferentes aunque esencialmente iguales de representar un algoritmo), la estructura general de un algoritmo debería ser como la que se muestra a continuación:

      Algoritmo <nombre_del_algoritmo>
      Inicio
               definición de constantes
               definición de variables

               sentencia 1
               sentencia 2
                      .
                      .
                      .
               sentencia n
      Fin

   Cuando se escribe un algoritmo es recomendable que, aunado a la estructura general y partiendo de la descripción del problema y de un análisis inicial, se determine la entrada, la salida y el proceso general de solución del algoritmo.

   Con la finalidad de mostrar dicho procedimiento, a continuación se describirá brevemente un ejemplo clave y clásico en los algoritmos: el algoritmo de Euclides.

Algoritmo de Euclides: definición del problema.
   Dados dos números enteros positivos m y n, encontrar su máximo común divisor (MCD), es decir, el mayor entero positivo que divide a la vez a m y a n.
Entrada: dos números enteros positivos m y n.
Salida: un número que representa el MCD (Máximo Común Divisor) de m y n.
Proceso: la solución está basada en el residuo de la división de los operandos m y n. Si el residuo es 0 entonces hemos terminado; en otro caso, habrá que hacer intercambio de valores y continuar con el proceso.
   El ejemplo del algoritmo de Euclides se utilizará para mostrar el proceso de solución que se debería seguir para resolver un problema. A partir de este análisis inicial, la idea será entonces ir especificando la entrada, la salida y el proceso de solución en un refinamiento progresivo, de tal forma que eventualmente se genere un algoritmo más específico, definido, y funcional.

Pruebas de algoritmos.
   Una vez que se ha generado un algoritmo que parece correcto, una de las partes más importantes dentro de su diseño es la referente a las pruebas. La fase de verificación y validación del algoritmo con respecto a los datos de entrada, es un aspecto sumamente importante de su diseño.

   Una vez que se tiene una solución algorítmica de un problema, no se debería suponer que funcionará bien siempre. En el diseño del algoritmo se deben considerar al menos algunos casos de prueba. En este sentido, es habitual que el dominio de trabajo de un algoritmo sea un conjunto de elementos, en cuyo caso sería deseable el saber por ejemplo, ¿cómo se comporta el algoritmo en los límites del conjunto?, dado un mismo dato de entrada, ¿se obtiene siempre la salida esperada?, dado un mismo dato de entrada, ¿se obtiene siempre la misma salida?, etc.
 
   La fase de prueba de los algoritmos es una parte fundamental dentro del diseño del mismo, y debe ser una práctica habitual de cualquier programador, ya que es una importante técnica de programación, y un principio esencial de la Ingeniería de Software. Es una grave defecto, y fuente de múltiples calamidades, el realizar las pruebas una vez que el programa ha sido realizado, con el programa pueden extenderse las pruebas, hacerse más pruebas, repetir las hechas al algoritmo, pero no esperar a hacer pruebas hasta que el programa esté hecho.
 

   Las técnicas y métodos formales de pruebas están fuera de los alcances de este blog, pero a este nivel, es suficiente saber que es conveniente realizar pruebas sobre los algoritmos desarrollados.
 
    En la entrada Pruebas de escritorio se muestra un ejemplo de la realización  de dicho tipo de pruebas para el algoritmo de Euclides.