Está en la página 1de 225

,

COMPUTACION I
Lógica, resolución de problemas,
algoritmos y programas
FUNDAMENTOS DE INFORMÁTICA FUNDAMENTOS DE PROGRAMACIÓN
Lógica, resolución de problemas, Algoritmos y estructura de datos
programas y computadoras (Segunda edición)

ALLEN B. TUCKER LUIS JOYANES AGUILAR


Bowdoin College Departamento de Lenguajes y Sistemas
Informáticos y de Ingeniería de Software
W. JAMES BRADLEY
Escuela Universitaria de Informática
Calvin College
Universidad Pontificia de Salamanca en Madrid
ROBERT D. CUPPER
Allegheny College

DAVID K. GARNICK
Bowdoin College

Traducción:

Manuel Ortega Ortiz de Apodaca


Universidad Europea de Madrid

Revisión técnica:

Antonio Vaquero Sánchez


Escuela Superior de Informática
Universidad Complutense de Madrid

MADRID. BUENOS AIRES. CARACAS. GUATEMALA. LISBOA. MÉXICO


NUEVA YORK. PANAMÁ. SAN JUAN. SANTAFÉ DE BOGOTÁ. SANTIAGO· SAO PAULO
AUCKLAND. HAMBURGO • LONDRES. MILÁN. MONTREAL • NUEVA DElHI • PARís
SAN FRANCISCO. SIDNEY • SINGAPUR • STo LOUIS • TOKIO. TaRaNTa
Prólogo
\
En el pasado próximo, la Universidad Nacional Abierta ofrecía, en el curso introductorio a la
Computación, contenidos programáticos que incluían elementos de la globalidad de temas que
comprende esta disciplina. Al abarcar tan amplio espectro se debilitaba el objetivo final del cur-
so (resolver problemas algorítmicos con la ayuda de la' computadora digital), el cual, en térmi-
nos prácticos, lo reflejaba la ponderación de los objetivos evaluables de la asignatura.
Este nuevo texto delimita los temas tratados a los estrictamente necesarios para el cabal do-
minio del objetivo de aprendizaje general del curso, logrando tratar los temas de importancia con
mayor claridad, extensión y profundidad. Otros temas de la disciplina son trasladados en el dise-
ño curricular a un curso introductorio en Informática y a otros cursos posteriores de los ejes de
Computación y Sistemas de Información de la carrera de Ingeniería de Sistemas.
El texto guía del nuevo curso, un esfuerzo editorial conjunto de la Universidad Nacional
Abierta y McGraw-Hill Interamericana de Venezuela, S. A., aglutina el tratamiento de temas que
van desde conjuntos y funciones, lógica, problemas algorítmicos y su solución, hasta la codifica-
ción de estas soluciones en lenguajes computacionales de alto nivel, por autores de reconocida
trayectoria en la informática y su enseñanza, como son los profesores Allen B. Tucker y David
Garnick, del Bowdoin College; W. James Bradley, del Calvin College; Robert D. Cupper, del
Allegheny College, y Luis Joyanes Aguilar, de la Universidad Pontificia de Salamanca. Los con-
tenidos son selectivamente tomados de los textos Fundamentos de Informática, lógica, resolución
de problemas, programas y computadoras, de los cuatro primeros autores, y Fundamentos de
Programación, algoritmos y estructura de datos, del último, con el objetivo de conformar un tex-
to que cubra los requisitos específicos en términos de contenidos, del nuevo Plan de Curso de la
asignatura Computación I de la carrera de Ingeniería de Sistemas de la Universidad Nacional
Abierta, diseñado por los profesores Edgar Blanco, NelIy Mendoza y Juana Marrero como Pro-
yecto Especial del área de Actualización de Cursos del Vicerrectorado Académico de la mencio-
nada institución.
DR. EOGAR BLANCO
ING. NELLY MENOOZA
ING. JUANA MARRERO

UNIVERSIDAD NACIONAL ABIERTA


Estudios Profesionales I
Computación I (301)
Diseño de Instrucción Especialista en evaluación Coordinador de Ingeniería
Edgar Blanco (UNA) Carmen Velásquez (UNA) de Sistemas
Juana Marrero (UNA) Dr. Luis Márquez Gordones
NelIy Mendoza (UNA)

v
UNIVERSIDAD NACIONAL ABIERTA

N° de Rfig¡~tl'O .....2.Q.,§.9..5.../t.:.f¿..-::t -~
Centro de ReQursos Múltiples

e.t. METROPOLITANO

COMPUTACIÓN l. Lógica, resolución de problemas, algoritmos y programas

No está permitida la reproducción total o parcial de este libro, ni su tratamiento infor-


mático, ni la transmisión de ninguna forma o por cualquier medio, ya sea electrónico,
mecánico, por fotocopia, por registro u otros métodos, sin el permiso previo y por escri-
to de los titulares del Copyright.

DERECHOS RESERVADOS © 2000, respecto a la primera edición en español, por


McGRAW-HILL/INTERAMERICANA DE ESPAÑA, S. A. U.
Edificio Valrealty, 1." planta
Basauri, 17
28023 Aravaca (Madrid)

ISBN: 84-481-2545-2
Depósito legal: M. 40.896-1999

Compilado de las siguientes obras:


Allen B. Tucker; W. James Bradley; Robert D. Cupper, y David K. Garnick.
FUNDAMENTOS DE INFORMÁTICA. Lógica, resolución de problemas, programas
y computadoras.
ISBN: 84-481-1875-8
DERECHOS RESERVADOS © 1994, respecto a la primera edición en español. por
McGRAW-HILUINTERAMERICANA DE ESPAÑA, S. A. U.

Luis Joyanes Aguilar. FUNDAMENTOS DE PROGRAMACIÓN. Algoritmos y estruc-


tura de datos (Segunda edición).
ISBN: 84-481-0603-2
DERECHOS RESERVADOS © 1996, respecto a la segunda edición en español, por
McGRAW-H1LUINTERAMERICANA DE ESPAÑA, S. A. U.

Esta obra se terminó de


Imprimir en junio del 2005
Litográfica Ingramex
Centeno Núm. 162 - 1
Col. Granjas Esmeralda
Delegación Iztapalapa
09810 México, D.F.
Nota del editor

COMPUTACIÓN l. Lógica. resolución de problemas, algoritmos y programas es una obra naci-


da de la unión de dos textos para atender a las necesidades de la Universidad Nacional Abierta.
Dichos textos son los siguientes:
• Tucker, A. B.; Bradley, W. 1.; Cupper, R. D., Y Garnick, D. K.: Fundamentos de Informática.
Lógica, resolución de problemas, programas y computadoras. Madrid, McGraw-Hill. 1994.
• Joyanes Aguilar, L.: Fundamentos de programación. Algoritmos y estructura de datos. Madrid,
McGraw-Hill. 1996.
Se ha respetado el formato de ambas obras, de ahí que el tamaño y tipos de letra son distin-
tos. Asimismo, se ha mantenido la numeración de los capítulos de cada libro. Por ello, COMPU-
TACIÓN I. Lógica. resolución de problemas, algoritmos y programas comienza en el Capítulo 2,
continúa hasta el Capítulo 6 (correspondientes a la obra de Tucker y otros) y finaliza en los Ca-
pítulos 1 a 5 (pertenecientes al texto de Joyanes).

Madrid. octubre de 1999

I vi
Contenido
Prólogo . v
Nota del editor . vi

Capítulo 2. Conjuntos y funciones . I


2.1. Conjuntos . 2
2.1.1. Relaciones entre conjuntos: los diagramas de Venn . 4
2.1.2. Variables tipos y estados . 4
2.1.3. Operaciones entre conjuntos .. 8
2.1.4. Propiedades de las operaciones entre conjuntos .. 10
2).5 Conjuntos de cadenas de caracteres . JI
EJercIcIos . 13
2.2. Funciones . 15
2.2.1. Conceptos básicos . 16
2.2.2. Funciones continuas y discretas . 18
2.2.3. Formas alternativas de definir funciones . 20
Ejercicios . 22
2.2.4. Funciones uno-a-uno y funciones inversas . 24
2.2.5. Funciones booleanas, enteras, exponenciales y logarítmicas .. 28
2..2.6.. . Series finitas y funciones relacionadas . 34
EJerCICiOS . 33
2.3. Sumario . 38
Ejercicios . 38
Capítulo 3. Lógica : . 41
3.1. Lógica proposicional . 41
3.1.1. Representaciones de frases en castellano utilizando la lógica proposicional . 44
3.1.2. Evaluación de proposiciones: Tablas de verdad . 47

~j~:ciciOf~~~·~·I.~.~.~~.~ ..:::::::;::;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
51
52
3.2. Razonamiento con proposIcIOnes . 54
3.2.1. Equivalencia . 54
3.2.2. Propiedades de la equivalencia .. 55
3.2.3. Reglas de inferencia: La idea de demostración . 58
3.2.4. Estrategias de demostración . 61
3..2.5 Resolución de problemas de la vida real . 65
EJercIcIos . 66
3.3. Lógica de predicados . 69
3.3.1. Los cuantificadores universal y existencial .. 71
3.3.2. Otros cuantificadores . 76
3.3.3. Variables libres y ligadas . 76
3.4. Predicados y programas . 77
3.4.1. El estado de un cálculo .. 77
3.4.2. Cuantificadores y programación: bucles . 78
3.5. Razonamiento con predicados: prueba por inducción .. 81

~j::::~~~s.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::~::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
86
3.6. 86
Capítulo 4. Problemas algorítmicos y su solución . 89

~I;r~~~~o.~.~.:.~~~~~.~.~.~ . :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
90
4.1. 93
4.2. Definición de problemas y descripción de algoritmos . 93
4.2.1. Los estados inicial y final de un algoritmo: entrada y salida . 93
4.2.2. Los estados intermedios de un cálculo: introducción de variables . 95

vii
viii Contenido

~~e:~~~j~S~ig~;ft~'i'~~"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
97
4.3. 98
4.3.1. Sintaxis y semántica . 100
4.3.2. Repetición y bucles: inicialización, invarianza y terminación . 103
4 ..3.3 Tres visiones de la misma solución al problema .. 108
EjercIcIos , 111
4.4. Más problemas algorítmicos .. 112
4.4.1. Cálculo de a" .. 113
4.4.2. Contar palabras de un texto . 116
I 4.4.3.. . Representación del problema de Las Tres-en-Raya .. 121
EjercIcIOs . 126
4.5. Resumen . 130
Capítulo 5. Resolución de problemas algorítmicos . 131
5.1. Necesitamos una metodología . 131
5.1.1. Generalidades sobre el método MAPS .. 132
5.2. Construcción de software para su reutilización: la rutina .. 134
5.2.1. Encapsulamiento de rutinas para la reutilización. Abstracción procedimental .. 134
5.2.2. Identificación de rutinas nuevas y definición de sus abstracciones .. 140
5.2.3. Funciones recursivas: Una alternativa a la repetición .. 144
5.2.4. Aprendizaje de rutinas ya existentes: Bibliotecas y características de los lenguajes 146
5.2.5. Selección y reutilización de tipos de datos y estructuras .. 147
5.2.6. A.rr~~s de ,cadenas de carac~eres .. 149
5.2.7 TlplÍlcaclon fuerte y coerclOn . 150
EjerCICIos . 152
5.3. Resolución de un problema utilizando la metodología MAPS .. 156
5.3.1. El diálogo .. 156
5.3.2. Las especificaciones . 158
t~:c·iciO;a ~.~.~~~~~~~.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
159
.. 161
5.4. Definiciones de abstracciones: unificación de rutinas viejas con ideas nuevas .. 162
5.4.1. Reutilización de rutinas .. 163
5.4.2. Utilización de la creatividad para diseñar nuevas rutinas .. 166
5.4.3. Utilización del conocimiento del dominio en el diseño de rutinas nuevas . 167
5.5. Terminación del caso de estudio .. 169
5.5.1. Coditicación .. 169
5.5.2. Prueba y veriticación .. 171
5.5.3. Presentación . 171
5.6. Resumen . 173
Ejercicios . 174
Capítulo 6. Robustez y prueba de los algoritmos . 177
6.1.
6.2.
..
g~~~~~~~n ~ ~~~~~.~e.~ .:::::::::::::::::::::::::::::::::::::::::::::::::::::::
Resolución de problemas de procesamiento de texto, utilizando MAPS: Criptografía ..
178
182
182
6.3. R~sol.u~ión de problemas gráticos utilizando MAPS: el Juego de la Vida . 187
EjerCICIOs . 196
6.4. Garantía de la robustez. Diseño de casos de prueba .. 196
6.4.1. Ejemplo: Prueba de un procedimiento o función completos .. 197
6.4.2. Ejemplo: Prueba de un programa completo . 202
6.5. Garantía de corrección: verificación de programas .. 203
6.5.1. Tableau de demostración . 203
6.5.2. La regla de inferencia de la asignación .. 206
6.5.3. Reutilización de las reglas de inferencia de la lógica . 209
6.5.4. Reglas para las condicionales .. 209
6.5.5. Verificación de bucles . 212
6.5.6. Verificación formal frente a verificación informal de programas .. 216
6.6. Resumen .. 217
Ejercicios . 218
Capítulo 1. Algoritmos y programas .. 221
1.1. Los sistemas de procesamiento de la información .. 222
Contenido ix

1.2. Concepto de algoritmo . 224


1.2.1. Características de los algoritmos . 225
1.3. Los lenguajes de programación . 226
1.3.1. Instrucciones a la computadora . 227
1.3.2. Lenguajes máquina . 227
1.3.3. Lenguajes de bajo nivel . 228
1.304. Lenguajes de alto nivel . 229
1.3.5. Traductores de lenguaje . 230
\ 1.3.5.1. Intérpretes .. 230
1.3.5.2. Compiladores . 231
1.3.6. La compilación y sus fases .. 231
lA. Datos, tipos de datos y operaciones primitivas .. 233
104.1 . Datos numéricos . 233
104.2. Datos lógicos (booleanos) . 235
104.3. Datos tipo carácter y tipo cadena . 235
1.5. Constantes y variables . 236
1.6. Expresiones . 237
1.6.1. Expresiones aritméticas .. 238
1.6.1.1. Reglas de prioridad . 240
1.6.2. Expresiones lógicas (booleanas) . 242
1.6.2. l. Operadores de relación .. 242
1.6.2.2. Operadores lógicos .. 244
. 1.?2.3. Prioridad de los operadores en las expresiones lógicas .. 245
1.7. FunClOnes mternas . 246
T.~~~era~~~~=c~~~~:~~~ti~~':::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
247
1.8. 248
1.8.2. Asignación lógica . 249
1.8.3. Asignación de cadenas de caracteres .. 249
1.804. Conversión de tipo .. 249
1.9. Entrada y salida de información .. 251
~j~t;~t~¡':e~.~~.~~~.~.~~.~.~~~~~ ..~~.~~~~~~~ ..:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
251
257
Capítulo 2. La resolución de problemas con computadoras y las herramientas de programación . 259
2.1. La resolución de problemas . 260
2.2. Análisis del problema . 260
2.3. Diseño del algoritmo . 261
2.3.1. Escritura inicial del algoritmo . 263
204. Resolución del problema mediante computadora . 265
2.5. Representación gráfica de los algoritmos . 266
2.5.1. Diagramas de flujo .. 266
2.6. Diagramas de Nassi-Schneiderman (N-S) . 276
2.7. Pseudocódigo . 278
A.ctivid.ades de programación resueltas . 279
EJercIcIOs . 284
Capítulo 3. Estructura general de un programa . 287
3.1. Concepto de un programa . 287
3.2. Partes constitutivas de un programa . 288
3.3. Instrucciones y tipos de instrucciones . 289
304. Tipos de instrucciones . 290
304.1. Instrucciones de asignación . 290
304.2. Instrucciones de lectura de datos (entrada) . 291
304.3. Instrucciones de escritura de resultados (salida) . 292
304.4. Instrucciones de bifurcación . 292
3.5. Elementos básicos de un programa . 293
3.5.1. Bucles .. 294
3.5.2. Contadores . 296
3.5.3. Acumulador . 299
3.504. Decisión o selección . 300
3.5.5. Interruptores . 301
x Conten ido

304
3.6. Escritura de algoritmos/programas 304
3.6.1. Cabecera del programa o algoritmo 304
3.6.2. Declaración de variables 305
3.6.3. Declaración de constantes numéricas 305
3.6.4. Declaración de constantes y variables carácter 306
3.6.5. Comentarios 307
3.6.6. Estilo de escritura de algoritmos/programas 308
A~\Ív~d.ades de programación resueltas . 313
EjercIcIOs 3 15
Capítulo 4. Introducción a la programación estructu rada 316
4. l. Técnicas de programación 316
4.2. Programación modular 317
4.2.1. Tamaño de los módulos 318
4.2.2. Implementación de los módulos 319
4.3. Programación estructurada 319
4.3.1. Recursos abstractos 319
4.3.2. Diseño descendente (top-dow n) 320
4.3.3. Teorema de la programación estructurada: estructuras básicas 320
4.4. Estructura secuencial 324
4.5. Estructuras selectivas 324
4.5.1. Alternativa simple (si-entonces / if-then) 325
4.5.2. Alternativa doble (si-entonces-si_no / if-then-else) 331
4.5.3. Alternat.i~a múltiple (según-sea, caso de / case) 337
4.6. Estructuras repettttvas 340
4.6.1 . Estructura mientras (<<while») 345
4.6.2. Estructura repetir (<<repeab» 348
4.6.3. Estructura desde/para (<<for») 352
4.6.4. Salidas internas de los bucles 358
4.7. Estructuras de decisión anidadas 362
4.8. Estructuras repetitivas anidadas 364
4.9. La instrucción ir_a (<<goto») 365
A.ctiv.i~ades de programación resueltas 380
EjercIcIOs 381
Referencias bibliográficas 383
Capítulo 5. Subpro gramas (subalgoritmos): procedimientos y funciones 384
5.1. Introducción a subalgoritmos o subprogramas 386
5.2. Funciones 387
5.2.1. Declaración de funciones 388
5.2.2. Invocación a las funciones 393
5.3. Procedimientos (subrutinas) 394
~.3.1. Sustitución de argumentos/parámetros 398
5.4. Ambito: variables locales y globales 401
5.5. Comunicación con subprogramas: pa<¡o de parámetros 402
5.5.1. Paso de parámetros 403
5.5.2. Paso por valor 404
5.5.3. Paso por referencia 405
5.5.4. Comparaciones de los métodos de paso de parámetros 407
5.5.5: Síntesis de. la.transmisión de ~arámetros 410
5.6. FuncIOnes y procedimientos como parametros 412
5.7. Los efectos laterales 412
5.7.1. En procedimientos 413
5.7.2. En funciones 414
5.8. Recursión (recursividad) 417
A.ctiv.id.ades de programación resueltas 422
EjercIcIos
CAPíTULO 2
CONJUNTOS Y FUNCIONES

En este capítulo introducimos las nociones matemáticas básicas sobre los con-
juntos y las funciones, junto con su notación. Estas nociones son fundamenta-
les para la disciplina de la informática. Más adelante veremos cómo los con-
juntos y funciones se entremezclan con las nociones claves de la informática y
su aplicación. Estos conceptos y notaciones se utilizarán a lo largo de todo el
texto. (Algunos lectores encontrarán estas materias familiares. Sin embargo,
esta presentación tiene como objetivo reorientar estas ideas, quizá familiares,
hacia aquellas partes de la informática en que se utilizan.)
¿En qué forma se relacionan los conjuntos y funciones con la informática?
Generalmente, podemos considerar un programa para una computadora,
como la realización de una función o como una correspondencia entre dos
conjuntos: la entrada del programa que representa un elemento particular del
dominio de la función, y la salida que representa el resultado de aplicar la
función a dicha entrada. Introduciremos esta idea en este capítulo, ejercitándo-
la en las prácticas de laboratorio que le acompañan. En los Capítulos 4, 5 Y6
afianzaremos este concepto, ejercitándonos en la programación y mediante la
discusión exhaustiva de programas. Los conjuntos y las funciones están tam-
bién íntimamente relacionados con la propia computadora como dispositivo.
Así, podemos decir que una computadora es una máquina que realiza una fun-
ción determinada, cuando ejecuta un programa que realiza, paso a paso, dicha
función. También se insiste en esta idea en las prácticas de laboratorio que
acompañan a este capítulo. También, en el Capítulo 7, insistiremos sobre este
concepto al estudiar la estructura de las computadoras.
Algunos conjuntos son especialmente importantes para la programación y
para las computadoras. Por ejemplo, los números enteros y reales, los valores
booleanos, los caracteres que encontramos en el teclado de la computadora (el
conjunto de caracteres ASCII) y las «cadenas» * construidas con esos caracte-

* N. del T.: De las diversas traducciones que suelen realizarse de la palabra inglesa «string»,
hemos preferido la acepción «cadena» o «cadena de caracteres», que serán utilizadas de forma
indistinta.
Compu tación l. Lógica, resoluc ión de problem as, algoritm os
y progra mas
2

todos ellos
res, representan conjuntos que son esenciales para la informática;
progra mación y las compu tadora s moder-
están incluidos en los lenguajes de
la corresp ondenc ia que existe en progra ma-
nas. En este capítulo estudiaremos
concep to de «tipo~~. Alguna s funcion es resulta n
ción entre estos conjun tos y el
funcion es
de vital import ancia en programación. Entre ellas se incluyen las
expone nciales y logarít micas, y funcion es que se repre-
discretas, la{> funciones
sentan mediante series finitas.
en des-
Estudiaremos todas ellas en este capítulo, ponien do especial interés
tacar por qué son importantes para la informática.

2.1. CONJUNTOS
números.
En la vida cotidia na son corrientes las colecciones de objetos o
as de nuestra clase, la colecci ón de
Podem os pensar en la colección de person
vil, o la colecci ón de los nombr es
dígitos y letras de la matrícula de un automó
erto ü'Hare de Chicag o. Estas colecci ones,
de aerolíneas que vuelan al aeropu
ción
en el lenguaje matemático, reciben el nombr e de conjuntos. Una descrip
más precisa la podemos realiza r de la forma siguien te.

Definición. Un conjun to es cualquier colección bien definida de


objetos. A
rís-
menudo, los objetos que forman un conjun to compa rten alguna caracte
de los objetos
tica en común. Un elemento de un conjun to es cualquiera
ce a
que lo constituyen. Se dice que un elemento de un conjun to pertene
elemen to x, para expre-
dicho conjunto. Si S es un conjun to que contiene el
sar que x pertenece a S escribiremos x E S.
tos que lo
Una de las formas de describir un conjun to es encerr ando los elemen
conjun to de los días laborab les
compo nen entre corchetes {y}. Por ejemplo, el
de la semana se puede describir como:

{Lunes, Martes, Miércoles, Jueves, Viernes}


tos:
Las Ecuaciones 2.1 a 2.7 son otros ejemplos de definición de conjun
(2.1)
{O, 1, 2, 3,4, 5, 6, 7, 8, 9} es el conjun to cuyos elementos son los
dígitos decimales.
(2.2)
{O, 1} es el conjun to de dígitos que se utiliza para escribir números
binarios.
(2.3)
{a, b, c, ..., z} es el conjun to de letras minúsculas del alfabeto.
(2.4)
{+, -, *, /} es el conjunto de las cuatro operaciones aritméticas
más importantes.
(2.5)
{1, 2, ..., 100} es el conjunto de números enteros del 1 al 100, ambos
inclusive.
Conjuntos y funciones 3

Notación. Obsérvese la utilización de la coma y los puntos suspensivos.


Las comas separan elementos individuales del conjunto, mientras que los
puntos suspensivos (...) denotan la repetición de un patrón claramente esta-
blecido, como ocurre en las Ecuaciones 2.3 y 2.5.

También podemos definir un conjunto, estableciendo las propiedades que de-


btln satisfacer todos sus elementos, por ejemplo:

{xix es entero y 1 ~ x ~ lOO} (2.6)

Esta notación requiere una variable al comienzo, una barra vertical y, al final,
una descripción de los elementos. Con esta notación, la Ecuación 2.6 se lee «el
conjunto de los x tales que x es un número entero entre 1 y 100, ambos
inclusive». Claramente, este ejemplo describe el mismo conjunto de la Ecua-
ción 2.5. La Ecuación 2.7 es otro ejemplo de esta alternativa para la descrip-
ción de conjuntos:

{plp es un polinomio de grado 2} (2.7)

Ejemplos de elementos del conjunto definido por la Ecuación 2.7 son los si-
guientes:

x2 + 2x + 1 .01n 2 - 100n - 3 SOOOy2 - 67

Algunos conjuntos tienen un número finito de elementos, mientras que otros


tienen un número infinito de ellos. Por ejemplo, los conjuntos definidos por las
Ecuaciones 2.1 a 2.6 son finitos, mientras el conjunto definido por la Ecua-
ción 2.7 es infinito.

Definición. Se denomina cardinalidad de un conjunto al número de ele-


mentos del que consta. El conjunto vacío es aquel que no contiene ningún
elemento y, por tanto, su cardinalidad es O. El conjunto vacío se simboliza
por {} o por </>.

Por ejemplo, la cardinalidad del conjunto definido por la Ecuación 2.3 es 26.
Existen cuatro conjuntos que tienen una importancia especial, tanto para la
informática como para las matemáticas:

N {O, 1, 2, 3, ...} número naturales


Z {..., -3, -2, -1, 0,1,2,3, oo.} los enteros
R {xl- infinito < x < + infinito} los números reales
{O, 1, 2, ..., n - 1} los enteros módulos n
4 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Los tres primeros son infinitos, mientras que el último es finito y tiene cardina-
lidad n. Por ejemplo, el conjunto Z2 = {O, 1} tiene cardinalidad 2; el conjunto
Z10 = {O, 1, ..., 9} tiene cardinalidad 10.

2.1.1. Relaciones entre conjuntos: los diagramas de Venn

El orden en 'que aparecen enumerados los elementos de un conjunto no tiene


especial relevancia; además, la duplicidad de elementos es redundante. Es decir,
{a, b, e} y {b, a, e, a} representan el mismo conjunto.
Es bastante habitual que los elementos de un conjunto sean también miem-
bros de otro diferentes. Por ejemplo, todos los elementos del conjunto V =
= {a, e, i, o, u} son también elementos del conjunto L = {a, b, z}. La
'00'

definición siguiente sirve para formalizar estos conceptos:


Definición. Si dos conjuntos A y B contienen los mismos elementos, se dice
que son iguales y se simboliza escribiendo A = B. Si todos y cada uno de
los elementos de A también lo son de B, y se simboliza por A e B. Si A es
un subconjunto de B, decimos que B es un superconjunto de A y se simboliza
por A ::J B. Si se verifica que A i= B, Yademás A e B, entonces se dice que
A es un subconjunto propio de B. Si todos los elementos de un conjunto o de
una colección de conjuntos se obtienen de un conjunto común, a este con-
junto se le denomina conjunto universal.
Obsérvese la diferencia que existe entre elemento y subconjunto. Por ejemplo,
sea A = {O, 1, 2, 3, 4, 5, 6, 7}. Entonces el dígito O es un elemento de A
(simbolizado por OE A), yel conjunto {O} es un subconjunto de A (y se denota
por {O} e A). Sería incorrecto decir que O es un subconjunto de A, puesto que
no es un conjunto. De forma análoga, sería incorrecto decir que {O} es un
elemento de A.
Frecuentemente, se representan los conjuntos utilizando diagramas de
Venn (véase Fig. 2.1). Un diagrama de Venn es un dibujo en el que los conjun-
tos se representan como círculos etiquetados, dentro de una caja rectangular,
que representa el conjunto universal del que se dibujan los elementos.

2.1.2. Variables tipos y estados

Dos nociones fundamentales de la informática son las de variable y estado.


Una variable es un mecanismo de notación que se toma prestado de las mate-
máticas, que permite representar ideas complejas mediante un simple símbolo,
y que aporta concisión y precisión. Frecuentemente, leemos frases como: «Re-
presentemos mediante x una letra del alfabeto». A continuación, podemos
utilizar el símbolo x y entender de forma precisa lo que significa, puesto que se
ha definido anteriormente el símbolo, para representar la noción de «cualquier
elemento del conjunto L = {a, b, oo., z}. Cuando se utiliza de esta forma, x
recibe el nombre de «variable», puesto que su valor puede variar entre diversas
alternativas (26 para ser exactos).
Conjuntos y funciones 5

al

bl

Figura 2.1. Diagramas de Venn. a) Dos conjuntos A y B que pueden


tener varios elementos comunes. b) La relación A e B.

Definición. Una variable es un símbolo que se asocia con un valor simple,


que de hecho puede ser un elemento de un conjunto bien definido. A lo
largo de los pasos que implican un cálculo, el valor de una variable puede
cambiar de ser uno de los miembros del conjunto a ser otro distinto. El
conjunto sobre el que se define una variable recibe el nombre de tipo de la
variable.

Por ejemplo, la variable x que se ha definido anteriormente es del tipo L (o de


manera informal, del tipo letra).
Otra noción fundamental en la informática es la de estado. Por ejemplo, un
programa que juegue al ajedrez tiene muchos estados posibles -uno por cada
posible colocación de las piezas sobre el tablero-o Un programa gráfico debe
controlar millones de «pixels» [puntos de una pantalla de un monitor, cada
uno de los cuales puede estar iluminado (on) o no iluminado (ofl)]. Su estado,
en cada momento, es el valor on u off que tienen el millón de pixels. La noción
de estado se puede definir más formalmente de la forma siguiente:

Definición. Supongamos que tenemos una lista de variables Xl' X 2 ' .oo, X n
cuyos tipos están definidos por los conjuntos 51' 52' oo., Sm respectivamente.
os y progra mas
6 Compu tación l. Lógica, resoluc ión de problem as, algoritm

es S2' y así
Supóngase que el valor de Xl es SI E SI' el valor de X2 S2 E

sucesivamente. Entonces se dice que la expresión

los
representa el estado de las variables xl' X 2 , ..., X w El conjun to de todos
e espacio de estados de esas
estados postbles de Xl' X 2 , ..., X n recibe el nombr
variables.

Esta import ante noción se ilustra en los Ejemplos del 2.1 al 2.3:

Ejemplo 2.1. Supon gamos que un progra ma de compu tadora tiene dos
tipo el
variables i y j, utilizadas como contadores. Ambas tienen como
podría
conjun to de los números naturales. El estado inicial del progra ma
ser:
i=O y j=O

Un estado posterior podría ser


i=5 Y j=6

Ejemplo 2.2. Si los pixels contro lados por un progra ma son Xl' X 2' ...,
XIOOOOOO, un estado de ese progra ma tendría
1 millón de entrad as, una por
cada variable. Por ejemplo, la expresión

Xl = on y X2 = off y y XIOOOOOO on

describe un posible estado del progra ma.

Ejemplo 2.3. Considérese un progra ma con tres variables, las tres


del tipo
rea l (la analogía en progra mación al conjun to R). Tres posible s estados
de este progra ma son:

X
O
-3,1
e
e
y
y
0, junto con z =
J2 junto con z = 1
°
n. 1789
X = -1 e y "2 Junto con z
ma,
Frecuentemente, es de gran ayuda visualizar el estado de un progra
. Por ejempl o, los puntos de la Figura 2.2 están repre-
como puntos del espacio
de la Figu-
sentados los dos estados del Ejemplo 2.1. La gráfica tridimensional
marcad o el
ra 2.2b representa el espacio estado del Ejemplo 2.3, donde se ha
Cuand o en un
punto correspondiente a uno de los estados del programa.
produc e es como si
progra ma las variables cambia n de estado, el efecto que se
nos desplazáramos de un punto a otro del espacio de estados .
Conjuntos y funciones 7

7 ~""---r-..---r---..-....-....,
y
61--+--+......,f-+--+~r-;

51--+--+......,f-+--+-+--I
41--t--+-+-+-+-+--;
31--+--+""",'--+--+-+--1
.;.- x
21-+--+---1'--+--+-+--1

o t-Jf-+-+-+--+-t-+--
- 1 L..-.J----L_.1.--L---J,_.L---.J z
-1 O 2 3 4 5 6
a) b)

Figura 2.2. Representación gráfica del estado de un programa.


al Dos estados del espacio de estados del Ejemplo 2.1. bl Un
estado del espacio de estados del Ejemplo 2.3.

Considérese un programa con tres variables reales z, x e y (variable rea l


significa lo mismo que variable del tipo rea l). Supóngase que el programa
contiene una instrucción de la forma

x:= 1.0

Esta instrucción asigna el valor de 1.0 a la variable x. Si no se ha realizado


ninguna restricción sobre las otras dos variables z e y, todo el plano x = 1 es el
conjunto de posibles estados de este programa, después de que se ejecute la
asignación (véase la Figura 2.3).

/
/1I
I
I
I
I
;-+----x

Figura 2.3. El estado x = 1 en un programa


con tres variables reales z, x e y.
8 Computación l. Lógica, resolución de problemas, algoritmos y programas

2.1.3. Operaciones entre conjuntos

Considérense los conjuntos siguientes:


s {O, 1, 01, 10, 11, OO}
T {l, 10, 100, 1000, 10000}
\
Los números 1 y 10 son los únicos elementos que pertenecen a ambos conjun-
tos, pero existen entre S y T otros nueve elementos diferentes. Los conceptos de
intersección, unión y diferencia de conjuntos nos ayudan a describir en diferen-
tes maneras la existencia de elementos comunes entre conjuntos.

Definición. Sean A y B dos conjuntos. La unión de A y B simbolizada por


A u B, es aquel conjunto cuyos elementos pertenecen como mínimo a uno
de los dos conjuntos, A o B. La intersección de A y B, simbolizada por A (\ B,
es el conjunto que incluye aquellos elementos que pertenecen tanto a A como
a B. La diferencia (o complemento relativo) de A y B, simbolizado por A \ B, es
el conjunto que contiene aquellos elementos que están en A, pero no en B.

La Figura 2.4 representa tres diagramas de Venn, en los que las áreas
sombreadas representan la unión, la intersección y la diferencia.

b)

e)

Figura 2.4. Diagramas de Venn: a) Unión. b) Intersección. e) Diferencia de dos


conjuntos A y 8.
Conjuntos V funciones 9

Es posible escribir algunos ejemplos adicionales, utilizando la notación de


conjuntos. Sean a = {1, 3, 5, 7, 9}, Y B = {- 3, - 1, 1, 3, 5}. Las relaciones
siguientes son ciertas:

AnB {1, 3, 5}
A u B {-3, -1,1,3,5,7, 9}
A \ B p, 9}
Como ejemplo adicional, supóngase que

A = {plp es un polinomio de la forma n 2 + bn, donde b es una constante real}


B = {plp es un polinomio de la forma n 2 + e, donde e es una constante real}

Dos posibles elementos del conjunto A serían n 2 + n y n 2 + 4,5n, mientras que


del B serían n2 - 3 Y n2 + 7. Las siguientes relaciones son ciertas:

AnB {n2 }
AuB {plp es un polinomio de la forma n 2 + bn + e,
donde b = O o e = O}
A \ B {plp es un polinomio de la forma n 2 + bn, donde b "# O}

El conjunto vacío <p es extremadamente útil. Por ejemplo, si e es el conjun-


to de consonantes (excluida la y), y V el conjunto de vocales (incluida la y),
entonces e n V no contiene elementos.

Definición. Decimos que dos conjuntos son disjuntos si su intersección es


el conjunto vacío.

Definición. El complementario de un conjunto S es aquel conjunto Si al


que pertenecen todos los elementos del conjunto universal U, que no per-
tenecen aS.

Por ejemplo, si e y S representan el conjunto de consonantes y vocales, respec-


tivamente, y U es el alfabeto completo, entonces C' = V Y V' = C. Es decir,
todas las letras que no son consonantes son vocales y viceversa. (Se ha supues-
to que la letra y es una vocal). La Figura 2.5 ilustra los conceptos de conjuntos
disjuntos y complementarios utilizando diagramas de Venn.

Ejemplo 2.4. Supongamos que x e y son enteros. Sea S el espacio de


estados compuesto por todos los posibles pares de enteros (x, y). Si defini-
mos los conjuntos A = {(x, y)lx ;?; Oe y ;?; O}, YB = {(x, y)lx < Oe y < O},
entonces A y B son disjuntos. Esto se ilustra en la Figura 2.6, donde las
áreas sombreadas representan los conjuntos A y B. Obsérvese que B "# A'
puesto que A' = {(x, y Jlx < O o y < O}.
10 Computación l. Lógica, resolución de problemas, algoritmos y programas

al bl

Figura 2.5. Representación mediante diagramas de Venn de:


al Conjuntos disjuntos. bl Conjuntos complementarios.

Figura 2.6. Conjuntos disjuntos.

2.1.4. Propiedades de las operaciones entre conjuntos

Las operaciones aritméticas entre números tienen determinadas propiedades,


como asociatividad, conmutatividad, inversa, y así sucesivamente. Hacemos
uso de estas propiedades, de forma casi automática, cuando simplificamos
ecuaciones algebraicas. Los conjuntos y sus operaciones tienen propiedades
similares, y las podemos utilizar para simplificar operaciones complejas entre
conjuntos. Las principales propiedades de las operaciones entre conjuntos se
resumen en la Tabla 2.1.
Conjuntos y funciones 11

Tabla 2.1. Propiedades y operaciones de los conjuntos. (A y B son conjuntos;


U es el conjunto universal sobre el que están definidos A y B)

Conmutatividad Ley de la contradicción


AnB==BnA A n A' == <p
AuB==BuA

\ Asociatividad u -simplificación
A n (B n C) == (A n B) n C A uA == A
A u (B u C) == (A u B) u C Au<p==A
AuV==V
Distributividad
A n (B u C) == (A n B) u (A n C)
A u (B n C) == (A u B) n (A u C)

Leyes de Morgan n-simplificación


(A n B)' == A' u B ' A nA == A
(A u B)' == A' n B' AnV==A
An<p==<p
Propiedad de la negación
(A')' == A Complementariedad

Ley de exclusión del término medio V' <p


<P' == V
A u A' == U

2.1.5. Conjuntos de cadenas de caracteres

El tipo s tri ng (cadena de caracteres) * es muy importante en informática.


Informalmente, puede definirse una «cadena» como una secuencia de elemen-
tos contiguos, pertenecientes todos a un conjunto universal conocido como
alfabeto. A continuación, se pueden ver algunos ejemplos de conjuntos cuyos
elementos son cadenas de caracteres.

Ejemplo 2.5. El conjunto {aa, ab, ba, bb} es el conjunto de todas


las posibles cadenas de dos elementos, construidas a partir del alfabeto
{a, b}.

* N. del T.: El tipo string es un tipo característico de la mayoría de los lenguajes de


programación modernos. Sin embargo, al tratarse de un capítulo dedicado a los conjuntos, tradu-
ciremos el término string al castellano. En capítulos posteriores, en los que aparece el tipo como
perteneciente a un lenguaje de programación, dejaremos el término sin traducir.
12 Computación l. Lógica, resolución de problemas, algoritmos y programas

Ejemplo 2.6. M = {abe, acb, bae, bca, eab, eba} es el conjunto


formado por todas las posibles colocaciones o permutaciones de las tres
letras a, b Y e. Obsérvese que existen seis permutaciones distintas.

Ejemplo 2.7. S = {O, 1,01,001, 100, 0100} es un conjunto de cadenas del


alfabeto {O, 1}. Existen otras muchas cadenas obtenidas de este alfabeto,
tales como {l, 10, 100, 1000, 10000}, Ylos propios {O, 1}. Es posible estable-
cer restricc'iones a las cadenas. Por ejemplo, el conjunto {O, 1,01, 10, 11, OO}
es el conjunto de todas las cadenas del alfabeto {O, 1} que tienen longitud
1 ó 2.

Ejemplo 2.8. F = {abed, abdc, aebd, aedb, adbe, adeb, baed,


bade, bead, beda, bdae, bdea, eabd, eadb, ebad, ebda,
edab, edba, dabe, daeb, dbae, dbea, deab, deba} es el conjun-
to de todas las permutaciones de las letras a, b y c. Esto puede escribirse
alternativamente como F = {plp es una permutación de abed}. Obsérvese
que existen 24 permutaciones distintas.

Ejemplo 2.9. Si A = {sls es una cadena sobre {O, 1} que comienza por 1},
entonces A' = {E} U {sls es una cadena sobre {O, 1} que comienza por 1}.
En este caso, el símbolo E simboliza la cadena vacía que no contiene ningún
carácter. La notación {E} significa «el conjunto que tiene un solo elemen-
to», que es la cadena vacía. El conjunto vacío <p, por el contrario, no
contiene ninguna cadena.

Ejemplo 2.10. Sea D el conjunto formado por todas las permutaciones de


abed, que terminan en d. Entonces D = { abed, aebd, bacd, bead,
cabd, ebad}. Si N = {plp es una permutación de abed}, entonces D es
un subconjunto propio de N, por lo que la intersección de D y N es el
propio conjunto D y la unión entre D y N es N.

Ejemplo 2.11. Sea U = {ulu es cualquier cadena sobre el alfabeto {O, 1},
incluida la cadena vacía E}. Entonces U es el conjunto universal de varios
conjuntos definidos sobre el mismo alfabeto, tales como S = {E, 0,1,00,01,
10, 11} Y T = {O, 01, 001, 0001, oO.}. Obsérvese que ni S es subconjunto de T,
ni T lo es de S. En la mayoría de los casos, el conjunto universal sobre el
que se definen conjuntos de cadenas particulares es tan evidente que no
suele mencionarse explícitamente.

Ejemplo 2.12. Supóngase que tenemos los dos conjuntos de cadenas si-
guientes:

A {plp es una permutación de la cadena abed,


que comienza por a}
{abed, abdc, acbd, aedb,adbe, adcb}
Conjuntos y funciones 13

D {plp es una permutación de la cadena abcd que termina en d}


{abcd, acbd, bacd, bcad, cabd, cbad}

Entonces las operaciones intersección, unión y complemento relativo dan


origen a las cadenas siguientes:

AnD {abcd, acbd}


AuD {abcd, abdc, acbd, acdb, adbc, adcb, bacd,
bcad, cabd, cbad}
A \ D {abdc, acdb, adbc, adcb}

Obsérvese que tanto A como D tienen seis miembros, mientras que A u B


tiene diez miembros, puesto que abcd y a cbd son elementos de ambos
conjuntos.

Ejercicios

2.1. Supongamos que i = 1 Yj = 1 es el estado inicial. Describir el estado al


que se llega después de realizar cada una de las asignaciones de los
apartados a) al e). Hacer una representación bidimensional del plano
xy, y dibujar el punto que corresponde al estado inicial y a los distintos
estados a que conducen las asignaciones de los apartados a) al e).

a) ; := 2;
j .- ; ;
b) ; .- ; + 1;
j .- j + 2;
e) j .- j + 1 r.
; .- i + 1 r.
j .-
j + ; r.

2.2. Expresar en notación de conjunto los descritos mediante las frases si-
guientes:

a) Las cadenas sobre el alfabeto {O, 1} de longitud menor o igual que


5, que contengan uno o dos ceros.
b) Los enteros pares.
e) El conjunto de todas las cadenas sobre el alfabeto {a, b, e} que
no contengan símbolos duplicados.
d) El conjunto de todas las permutaciones de la cadena 012.
e) El conjunto de todos los polinomios de grado 3 ó menor que ten-
gan a x como un factor.
14 Computación /. Lógica, resolución de problemas, algoritmos y programas

2.3. °
SeaA = {(x, y)lx ~ e y ~ O} Y B = {(x, y)lx ~ 1 e y ~ 1}. Identificar
cada uno de los conjuntos siguientes sobre el plano xy, sombreando el
área apropiada.
a) A n B
b) A u B
e) A \ B
d) B ~A
e) A'

2.4. Supóngase que s y t son cadenas sobre el alfabeto {a, b, ..., z}. Es posible
definir eoneat (s, t> como la cadena que resulta de poner t a conti-
nuación de s (concatenar). Por ejemplo, si s = abe y t = xy, entonces
eoneat(s,t) = abexy. Del mismo modo, si S y Tson conjuntos de
este tipo de cadenas, podemos definir eoneat (S, T) como el conjunto
de todas las cadenas, que resultan de concatenar una cadena arbitraria s
de S, y otra t de T. Supongamos, por ejemplo, que S = {e, x, xy,
xyy}, y T = {e, y, yx, yxx}. Se pide encontrar cada uno de los
conjuntos siguientes:
a) eoneat (S, T>
b) eoneat(T ,S)
e) S u T
d) S n T
e) S \ T
f) T \ S
2.5. Supongamos que S y T son conjuntos de cadenas. Encontrar dos ejem-
plos de parejas S y T, tales que eoneat(S, T> = S u T.
2.6. Dibujar un diagrama de Venn para los conjuntos A y B, Ysombrear las
partes correspondientes a los conjuntos siguientes:
a) (A' n B') u (A n B)
b) (A u BY u (A \ B)

2.7. ¿Cuántas cadenas de longitud 2 ó menor existen sobre el alfabeto (O, I)?
2.8. Decir cuáles de las afirmaciones siguientes son verdaderas y cuáles
falsas.
a) {2, 3, 4} u {2, 4, 5, 6} = {2, 3,4, 5, 6}
b) {2, 3, 5} n {2, 4, 5, 6} e {2, 3, 6}
e) {3, 4, 6, 8} \ {2, 4, 7, 8} ::J {3}
d) (<(> n {3, 5, 7, 8}) u {3, 7} = {3, 5, 7, 8}
e) {2, 3, 5} = {5, 2, 3, 2}
f) {2, 4, 5} e {I, 2, 4, 5, 6}
g) {3, 4, 5} e {I, 2,4, 5, 6}
h) {I, 2, 6} ::J {I, 2,4,5, 6}
i) «> e {2, 5}
Conjuntos y funciones 15

2.9. Supóngase que:

S {1, 2, 3, 4, 5}
T {xlO < x < 10 Y x es impar}
U Z16

\ Escribir el conjunto de elementos de los conjuntos siguientes:


a) S n T
b) (S u T)'
e) (S u T) \ S

2.10. Los conjuntos siguientes se han definido en forma de reglas. Reescribir


las definiciones en forma de lista de elementos.
a) {xix E N, x ::::; 10, Y x es par}
b) {plp es una permutación de la cadena 012}

2.2. FUNCIONES

Un concepto fundamental para la informática es el de función. En su forma


más simple, unafuneión es una tabla con dos columnas que indican una corres-
pondencia entre los valores de la primera columna y los de la segunda.

Ejemplo 2.13. Considérese la tabla siguiente.

1 2
2 5
3 10
4 17

Esta es una función que relaciona los enteros de la primera columna con
los correspondientes de la segunda. Si quisiéramos extender la función, no
sería dificil deducir que la quinta fila constaría de los pares 5 y 26. Es decir,
existe un criterio, sugerido por las cuatro filas de la función, que permite
generalizarla para cualquier número de valores.

Ejemplo 2.14. La tabla siguiente es ligeramente diferente:

1 2
2 5
1 7
3 2
2 6
16 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Puesto que en la columna de la derecha aparecen números duplicados, esta


tabla no parece representar una relación estable y fiable. Parece no ajustar-
se a ninguna regla gracias a la cual, a partir de un valor de la izquierda,
pueda calcularse el de la derecha.

Los valores no-ambiguos -es decir, que a cada valor de la primera columna lc
correspondA uno de la segunda-, son básicos para la idea de función.
La Figura 2.7 muestra dos funciones más, en este caso utilizando un gráfico
en lugar de una tabla. Obsérvese que en la Figura 2.7a cada valor de x se
corresponde con uno de y, y de la misma forma, cada uno de y se corresponde
con uno de x. En la Figura 2.7b cada x se corresponde con uno de y; sin
embargo, para un mismo y existen más de un valor de x. Para poder trabajar
con funciones es necesario definir el concepto de forma más precisa. Clarifique-
mos algunas de las ideas acerca de la correspondencia presentadas en los
ejemplos.

y y

a) b)

Figura 2.7. Dos representaciones gráficas de funciones: al Cada x se corres-


ponde con un y. bl Cada x se corresponde con una y, pero una misma y se
corresponde con varias x.

2.2.1. Conceptos básicos

Una función siempre se define utilizando dos conjuntos.

Definición. Sean X e Y conjuntos. La notación (x, y) donde x E X e y E Y,


recibe el nombre de par ordenado. El producto cartesiano de X e Y, simboli-
zado por X x Y, es el conjunto {(x, y)lx E X e y E Y}. Es decir, X x Yes el
conjunto de todos los pares ordenados (x, y) que verifican que x E X e y E Y.
Conjuntos y funciones 17

Ejemplo 2.15. Sea X = Y = R. Entonces, X x Yes el sistema y coordena-


das xy que se muestra en la Figura 2.8a. Si 1 = J = Z, entonces 1 x J es el
conjunto de todos los puntos del plano cartesiano, cuyas coordenadas son
enteras. Los puntos de 1 x J son las intersecciones de las líneas verticales y
horizontales de la Figura 2.8b.

7
y 6
5
4
3

x 2

O
-1
-1 O 2 3 4 5 6
al b)

Figura 2.8. Representación gráfica de: a) El producto cartesiano


de reales. b) El producto cartesiano de enteros.

Una función es un caso particular de producto cartesiano, es un conjunto de


pares en el que dos elementos distintos no pueden tener igual el primer elemen-
to del par. De una manera formal, una función puede definirse.

Definición. Sean X e Y dos conjuntos. Una función es un subconjunto f de


X x Y, tal que cada x E X se encuentra emparejado con uno, y sólo un
elemento y E Y. El conjunto X recibe el nombre de dominio de la función!, e
y el de rango de f

En el contexto de una representación tabular de las funciones podemos pensar


en el dominio como en la primera columna de la tabla, y en el rango como en
la segunda. La propia función es la correspondencia que existe entre los núme-
ros de las dos columnas. De una forma similar, cuando observamos una repre-
sentación gráfica de una función, se puede identificar el eje x como el dominio
y el y como el rango, y la función con el conjunto de pares (x, y) que constitu-
yen la gráfica (por ejemplo, como se muestra en la Figura 2.7). En cualquier
caso, la noción de que cada valor x sólo puede tener un y asociado, resalta el
concepto de dependencia o estabilidad de la correspondencia.
18 Computación l. Lógica, resolución de problemas, algoritmos y programas

Ejemplo 2.16. Supongamos que X = {l, 2, 3, 4} e Y = {2, 4, 6, 8}.


Supongamos también que tenemos el conjunto de pares f = {(l, 2),
(2,4), (3, 6), (4, 8)}. Entonces f es una función; el conjunto {l, 2, 3, 4} es el
dominio; y {2, 4, 6, 8} su rango. Obsérvese que para cada x E X existe un
único y E Y emparejado con él. Obsérvese también que esta corresponden-
cia particular puede representarse mediante la regla y = 2x.
\
Supongamos de nuevo que X = {l, 2, 3, 4}, Yconsidérese el conjunto g = {(1,2),
(2, 4), (3, 6)}. En este caso, g no es una función con dominio X, puesto que
existen elementos de X para los que g no está definida. Aquellos conjuntos que
tienen todas las propiedades de las funciones, pero que no están definidas
sobre todo el dominio, reciben el nombre de funciones parciales. Las funciones
parciales se utilizan a menudo en la informática.
Aunque una función exige que cada x· del dominio tenga un valor del
rango, y asociado con él, no es necesario que cada valor y del rango correspon-
da a un x. Por ejemplo, si X = Y = {l, 2, 3, 4}, Ydefinimos la función y = 4
para todo valor de X, entonces los valores 1, 2 Y 3 no se corresponden con
ninguno de X. Finalmente, sea h = {(l, 2), (2, 4), (3, 6), (1, 8), (4, lO)}. El
conjunto h no es tampoco una función, puesto que existe un valor de x que se
corresponde con dos valores distintos de y.
Los términos siguientes suelen utilizarse al referirnos a funciones.

Definición. Seafuna función con dominio X y rango Y. Se suele simbolizar


esta relación como f: X ~ Y. Cuando se han especificado el dominio y el
rango, escribimos y = f(x) para simbolizar la relación que f representa
entre cada elemento del dominio X, y el correspondiente del rango Y. En
esta notación, x recibe el nombre de variable independiente. El conjunto de
pares {(x, f(x))lx E X Y f(x) E Y) se utiliza para definir la gráfica de la
función!

Ejemplo 2.17. Si X = Y = R e y = f(x) = 2x - 1, x es la variable inde-


pendiente, e y es la dependiente. La gráfica de f se muestra en la Figura 2.9.

2.2.2. Funciones continuas y discretas

Las funciones suelen caracterizarse dependiendo de si pueden visualizarse me-


diante una linea continua o no. Si no es posible, la función tiene «huecos» en
su representación en el plano xy, y recibe el nombre de función discreta.

Ejemplo 2.18. Sea X = Y = R. La función valor absoluto, dada por y = Ixl


puede definirse mediante la regla

Ixl x cuando x ? O
- x cuando x < O
Conjuntos y funciones 19

_ _ _ _ _ _ _ _.......,.... x

Figura 2.9. Gráfica de la función ((x) 2x - 1.

La Figura 2.10a muestra la gráfica de y = Ixl cuando el dominio y el rango


de la función es R (dando origen a una función continua). Si X = Y = Z, la
función es ligeramente diferente, como se muestra en la Figura 2.10b (una
función discreta).

y y

(-3,3) • .(3,3)
(-2,2). • (2,2)
(-1,1) • • (1,1)

-----IlI'-----x (0,0) ------+------x (0,0)

a) b)

Figura 2.10. Gráfica de y = IxI. a) Una función continua. b) Función discreta.

Ejemplo 2.19. Lafunción suelo l_x_lse define como el mayor entero menor
o igual que x. Esta función tiene como dominio a R y como rango a Z. Por
tanto, si x = n.d¡d 1 d 3 ... (donde n es un entero y di cualquier dígito), enton-
ces l_x_1 = n cuando x ~ 0, o x es un entero, y l_x_1 = n - 1 en caso
contrario. Por tanto, para números reales positivos, la función l_x_1 es el
Compu tación l. Lógica, resoluc ión de problem as, algoritm os
y progra mas
20

negati-
resultado de eliminar la parte decimal de x, y para números reales
elimin ar la parte deci-
vos que no sean ya enteros, l_x_1 es el resultado de
muestr a en la Figura 2.11.
mal y restarle uno. La gráfica de esta función se
I-x-j que se define como el
Una función muy relacionada es la función techo
calcula elimin ando la parte decima l
menor entero mayor o igual que x. Se
dole uno. Las funcion es suelo y techo
de x, y Isi x no fuera entero, sumán
i-
también se conocen como funciones mayor entero y menor entero, respect
vamente.

utili-
La función valor absoluto puede calcularse en Pascal, directamente,
variabl e o expres ión del
zando la expresión abs (x), donde x puede ser una
es suelo y techo no tienen una
tipo rea lo; ntege r. Sin embargo, las funcion
runC< x)
analogía exacta en Pascal. La función más próxim a es la función t
ión de tipo rea l en integer eliminan-
que convierte cualquier variable o expres
(3.14 ) = 3,ytru nC<- 3.14) = -3.
do los decimales. Por tanto, trunc

2.2.3. Formas alternativas de definir funciones

a, una
Hemos visto cómo se pueden definir funciones utilizando una fórmul
conjun to de pares ordena dos. Las
gráfica xy, una tabla de dos columnas y un
funciones pueden definirse de varias formas alterna tivas.

definir las
Definición recursiva de funciones. Algunas veces es conveniente
ferenci arse reci-
funciones en términos de sí mismas. Esta capacidad de autorre
reciben el nombr e de
be el nombr e de recursión, y las funciones así definidas
funciones recursivas.

•,, ,
:-2
I

'-1 O ,1 '2 :3 x

Figura 2.11. La función suelo I_x_l.


Conjuntos y funciones 21

Ejemplo 2.20. Supongamos X = Y = N, Y que la funciónf(n) se define de


la forma siguiente:

f(n) 1 cuando n-O


3 + f(n - 1) cuando n > O

\ Con la definición conocemos un valor inicial para la función; es decir, su


valor cuando x = o. Mientras conozcamos cómo se puede calcular cada
valor de f a partir del anterior, no es necesario que conozcamos una regla
explícita para calcular f(n) directamente. Es decir, el valor de f(n) pue-
de calcularse aplicando reiteradas veces la relación de recurrencia a valo-
res crecientes de n, comenzando por n = o. Por tanto, primero calculamos
f(O) = 1, después lo utilizamos para calcular f(l) = 3 + f(O). Esto puede
hacerse repetidas veces, obteniéndose la tabla siguiente:

n f(n)
~ _ . _ ~

O 1
1 4
2 7
3 10

En algunas ocasiones es fácil deducir una fórmula no-recursiva para calcu-


lar f(n) a partir de la recursiva, y en otros casos es muy dificil. Por ejemplo,
el caso del Ejemplo 2.20 es relativamente senciJIo, extrayendo el criterio de
la tabla para obtener la fórmula equivalente f(n) = 3n + 1.

Ejemplo 2.21. Considérese la función recursiva:

f(n) = O cuando n-O


= 1 cuando n - 1
= f(n - 1) + f(n - 2) cuando n > 1

Aplicando reiteradas veces la relación de recurrencia se puede obtener la tabla:

n f(n)
O O
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
22 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Esta función recibe el nombre de serie de Fibonacci. Es una función de gran


utilidad en diversas áreas de las matemáticas aplicadas. Sin embargo, es
dificil, sin un conocimiento más profundo de las matemáticas, encontrar
una forma de la función que la calcule directamente para cualquier valor
de n.
\
Utilización de un programa para definir una función. Las funciones pueden
definirse también utilizando un programa de computadora en el cual se descri-
ba cómo puede calcularse un valor de rango, utilizando un valor predetermi-
nado del dominio. Formalmente, un programa de estas características recibe el
nombre de algoritmo. La construcción de algoritmos computacionales para
una gran variedad de funciones es el objetivo de los Capítulos 4 al 6.

Ejemplo 2.22. Podemos definir un algoritmo como un conjunto bien defi-


nido de pasos, que transforman una entrada (de un conjunto de entradas
disponibles) en la salida correspondiente. Es decir, describe cómo calcular
el valor de una funciónf(x) para cada valor particular x del dominio. Por
ejemplo, la secuencia siguiente es un algoritmo (escrito en lenguaje Pascal)
que calcula los valores de la función n = Hog2n-l, donde x es un número
real arbitrario, mayor o igual que 1. En este caso, el valor de x es la entrada
del algoritmo, y el de n la salida de la función.

read(x);
n := o;
whi le x >= 1.0 do
begin
x:=x/2.0;
n := n + 1
end;

Cuando se utiliza un algoritmo para calcular una función, el conjunto de


entradas permitidas (en este caso R) se corresponde con el dominio de la
función; el conjunto de salidas (en este caso N) se corresponde con el rango; y
los pasos de que consta definen procedimentalmente la correspondencia exis-
tente entre cada valor del dominio y el correspondiente del rango. Otras for-
mas de definir funciones (véase Ejemplo 1.11) no describen ningún proceso
particular, mediante el cual pueda calcularse la función. Los programas con-
trolan el proceso.

Ejercicios

2.11. ¿Cuáles de los conjuntos de pares ordenadores siguientes son funciones?


a) {(1, 1), (1, n), (1, lOO)}
b) {el, 1),(3,7),(5,13),(4, 19)}
e) {( - 1, 1), (O, 1), (1, 1), (1, 1), (3, l)}
Conjuntos y funciones 23

d)
e)
{(x,
{(l,
y)lx ~ ° e y = x}
a), (2, b), (3, a), (4, b), (5, e)}
f) {(l, a), (2, b), (1, e), (2, d), (3, e)}
g) {(l, e), (2, d), (3, e), (4, b), (5, a)}

2.12. Determinar el dominio y el rango de cada una de las funciones siguien-


tes:
a) h(x) = x mod 17, para x = 0, 16, 17, 32 Y 69. (La función mod
calcula el resto entero que resulta de la división del primer argu-
mento por el segundo.)

e) l_x_1 para x = 10, n, y -1,5. °


b) b(x) = (-1:::;; x :::;; 10), para x = -2, -1,0,10 Y 15.

n
d) Ixl para x = -1,0,1, -49,5 Y 6".

2.13. Definir las funciones que cumplan lo siguiente:


a) Tome un entero positivo y calcule el resto después de dividirlo
por 67.
b) Trunque un número real a la mitad de la décima más próxima (por
ejemplo, 19.567 quedaría 19,5).
e) Asigne verdadero si el número es positivo, y falso si es cero
o negativo.
d) Crear una secuencia de valores para cada uno de los cuales es el
doble del anterior más uno. El primero es 1.

2.14. Tabular unos pocos valores de las siguientes ecuaciones de recurrencia,


y deducir una expresión compacta para f

a) f(n) 3 para n 0, y f(n) = f(n - 1) + 5 para n > O.


b) f(n) = 1 para n = 0, y f(n) = 2f(n - 1) para n > O.

2.15. Marcar un punto en la gráfica (i, j) de la Figura 2.8b, para cada uno de
los estados marcados con A y B, en el segmento de programa siguiente.

; := 1;
j := 1;
(estado A}
;:=;+1;
j:=j+1;
{estado B}

2.16. Considérese la función f(x) = (x mod 2 = O).


a) ¿Cuál es el dominio de esta función?
b) ¿Cuál es el rango?
e) ¿Es una función parcial o total?
24 Computación ,. Lógica, resolución de problemas, algoritmos y programas

2.17. La función Ancestros tiene como entrada un número de generación, y


devuelve el número de ancestros que tuvo en todas las generaciones
precedentes, suponiendo que cada persona de cada generación tiene
exactamente dos padres. Es decir, Ancestros(l) = 2, Ancentros(2) = 4, Y
así sucesivamente. En particular, Ancestros(O) = 1, suponiendo que cada
persona es ancestro de sí mismo o sí misma. Enunciar una definición
re~ursiva para la función Ancestros.

2.2.4. Funciones uno-a-uno y funciones inversas

Supongamos que f es una función con dominio X y rango Y. Entonces, como se


vio en la Figura 2.7b, a cada x le corresponde uno y sólo un valor de y. Sin
embargo, cada valor de y puede corresponder a más de un valor de x. Una
función de este tipo se representa en la Figura 2.12. Sin embargo, si quisiéra-
mos «invertir el proceso, comenzando por y, el resultado obtenido no sería una
función correcta». Es decir, alguna y se correspondería con más de un valor de
x, mientras que algunas de ellas no se corresponderían con ningún x. La
función de la Figura 2.12b es diferente ---en este caso podemos invertir el
proceso y seguimos obteniendo una función-o Esto es debido a que a cada x
le corresponde una y distinta. La definición siguiente formaliza estas ideas.

Definición. Sea f: X -+ y una función. La función f es una función uno-


a-uno si a cada x diferente le corresponde un y diferente para cada x E X e
y E Y. Se dice que dos conjuntos x e Y están en una correspondencia uno-
a-uno si existe una función f: X -+ y que es uno-a-uno.

Ejemplo 2.23. Las funciones resultantes de permutaciones son ejemplos


clarificadores de las correspondencias uno-a-uno. Por ejemplo, considérese

y y

_~ .... "",_ _..., x


--..,~--f----- x

al bl

Figura 2.12. a) Función no-uno-a-uno. b) Función uno-a-uno.


Conjuntos y funciones 25

la cadena abcd y su permutación bdac. Si hacemos X = Y = {a, b, e,


d} esta permutación puede considerarse como una función definida como:

p(a) e
p(b) a
p(c) d
p(d) b

Es decir, p indica en qué posición se deben colocar los diferentes elementos de


una cadena arbitraria de a, b, e y d. La función p es también una función
uno-a-uno, puesto que x diferentes se corresponden con y's distintos, y el rango
de p = {a, b, e, d} = y.

Definición. Sea f : X --. Y una función uno-a-uno. Entonces f - 1 es una


función con dominio Y y rango X, que verifica que si y = f(x), entonces
x = f - 1 (y). La función f - 1 recibe el nombre de función inversa de f

No todas las funciones tienen inversa. Por ejemplo, supongamos X = Y =


= {a, b, c,d},yf(a) = b,f(b) = c,yf(c) =f(d) = d,como se muestra en
la Figura 2.13.

x y

a.~ • a

b •
• b
~
c.~ • e

d • ------+~ •d
f

Figura 2.13. Una función f que no es uno-a-uno, por lo que no tiene inversa.

Entonces, si la inversa de f existiera,f - 1 (d) tendría como valores asociados e y


d, lo que invalidaría af -1 como una función legítima. Por tanto, para que una
función pueda tener inversa ha de ser una relación uno-a-uno.
os y progra mas
26 Compu tación ,. Lógica, resoluc ión de problem as, algoritm

uno,
Ejemplo 2.24. El Ejemplo 2.23, puesto que p es una función uno-a-
, p - puede definir se de la forma siguien te:
tiene inversa. Esta inversa 1,

p-l(a) b
p-l(b) d
p-l(e) a
p- l(d) e

cubrir
Obsérvese que p-l(p(a )) = p-l(e) = a. De forma general, para
x X, P- (p(x) = x. Análo-
cualqu ier función invertible p : X -+ Y, y cada E 1

gamente, para cada y E Y, p(p-l(y )) = y.


e en la
Ejemplo 2.25. Las funciones uno-a- uno se emplea n habitu alment
ation Interch ange (AS-
codificación. El American Standa rd Code for Inform
127 para cada carácte r
CII) define una equivalencia numér ica entre O y
en un teclado estánd ar de una
regular o de contro l que puede ser pulsad o
je de
compu tadora . Esta función es uno-a- uno, y está incluid a en lengua
e de ord (por valor orddina l). Es decir,
progra mación Pascal con el nombr
númer o
para cada carácte r que encont ramos en el teclado, o rd(x) es un
ordina les de alguno s caracte res familia res
entero entre O y 127. Los valores
del teclado se pueden ver debajo :

ord ( , O' ) = 48
ord('A ') =-65
ord( '=') = 61
ord ( , a') = 97

ondien -
Una lista comple ta de los caracte res ASCII y sus ordina les corresp
tes puede encont arse en el Apénd ice A.

uni-
El propós ito de la función ord es definir un esquem a de codificación
del teclado . Este esquem a lo utilizan
forme para represe ntar los caracte res
La existen-
todos los constru ctores de software y hardw are de compu tadora s.
mbio fácil de inform ación entre distinto s
cia de este estánd ar permit e el interca
tipos de compu tadora s.
uno; si
Obsérvese que, para ser eficaz, la función o rd tiene que ser uno-a-
a una forma
un código estuvie ra asigna do a dos caracte res distintos, no existirí
ificar el código . Puesto que es una función uno-a- uno, la
consistente de descod
ter). Si n
función ord tiene en Pascal su inversa, denom inada eh r (por Charac
r del teclado
es un entero en el interva lo 0-127, entonc es eh r (n) es el carácte
corresp ondien te del entero. Por ejemplo:

ehr(48 ) = 'O'
ehr(65 ) = 'A'
ehr(61 ) = '='
eh r (97) = 'a'
Conjuntos V funciones 27

Obsérvese que, puesto que ord y chr son inversas, una de la otra, se verifica:

chr<ord<x» =x y ord<chr<n» =n

Para cualquier entero entre O y 127, Y cualquier carácter del teclado.

\ Ejemplo 2.26. La criptografia (codificación de mensajes secretos) es un


área de la informática que utiliza muchos de los principios que hemos
discutido. Por ejemplo, el cifrado de César, un esquema de codificación en
el que cada letra de un mensaje es reemplazada por otra letra diferente del
alfabeto, la tercera letra detrás de ella. Entonces el mensaje:

SERGEANT PEPPERS LONEL y HEARTS CLUB BAND

se codifica en el cifrado de César como:

VHUJHDGW SHSSHUV ORGHOB KHDUWV FOXE EDGG

El cifrado de César es una permutación de las letras del alfabeto, y por


tanto una función uno-a-uno, cuyos dominio y rango son el alfabeto de
letras mayúsculas. La descodificación del lenguaje implica el descubrimien-
to de la función inversa a la que fue utilizada al cifrarlo.

La gran desventaja del cifrado de César es lo relativamente fácil que es


descodificarlo. Podemos construir un método más dificil de descodificar, utili-
zando una permutación aleatoria y haciendo sustituciones en base a ella. Utili-
zando la tabla de sustitución:

ABCDEFGHIJKLMNOPGRSTUVWXYZ
JSATERNICFHPLUGWVYDZOMXGBK

Permite codificar el mensaje:

SERGEANT PEPPERS LONELY HEARTS CLUB BAND como


DEYNEJUZ WEWWEYD PGUEPB IEJYZD APOS SJUT

Si el mensaje cifrado es lo suficientemente largo, incluso un cifrado aleatorio


puede descodificarse contando el número de veces que aparece cada letra,
comparándola con el número de veces que aparece cada letra en un párrafo en
castellano. Algunos esquemas de encriptación más sofisticados emplean funcio-
nes de descodificación que encriptan bloques de letras. La criptografía es un
campo de estudio excitante y que encierra grandes desafíos. El concepto de
función uno-a-uno aporta un criterio preciso para definir la noción de conjun-
tos que tienen el mismo número de elementos, y la noción de tamaño de un
conjunto.
28 Computación l. Lógica, resolución de problemas, algoritmos y programas

Definición. Dos conjuntos A y B tienen el mismo número de elementos, si


están en una correspondencia uno-a-uno. Un conjunto que está en una
correspondencia uno-a-uno con Zn para algún valor n natural, se dice que
tiene cardinalidad * n. Dicho conjunto tiene n elementos y recibe el nombre
de conjunto finito. Los conjuntos que no tienen cardinalidad n para ningún
valor de n, reciben el nombre de conjuntos infinitos.
\

La mayoría de los conjuntos que nos encontraremos en la informática son


finitos. Por ejemplo, el conjunto de caracteres ASCII del Ejemplo 2.25 tiene
cardinalidad 128. Incluso el conjunto de enteros que se utiliza en la informática
es finito -para muchas computadoras, su cardinalidad es 2 16; es decir, 65.536.
Este es el tipo; nteger que utiliza el lenguaje de programación Pascal, y que
difiere de la noción matemática del conjunto Z por razones evidentes. En
resumen, el rango de los enteros en matemáticas es infinito, mientras que el
rango de los; ntege r en programación es finito. Nociones complementarias
sobre el tipo; nteger se introducen en el manual de laboratorio.

2.2.5. Funciones booleanas enteras, exponenciales


y logarítmicas

Existen un conjunto de funciones especiales que aparecen con la suficiente


frecuencia en la informática como para justificar un breve estudio.

Ejemplo 2.27. Supongamos que X = Z e Y = {verdadero, fa l so}.


Sea f(x) = (O ~ x ~ 100). Entonces, f(l) = verdadero, f(102) =
fa l so, f( - 1) = fa l so, y así sucesivamente. El dominio de f es el de los
enteros y el rango el conjunto {verdadero, falso}.

Este es un ejemplo de un importante conjunto de funciones, conocidas como


booleanas en honor del matemático inglés George Boole (1815-1864), que sen-
tó las bases y comenzó el desarrollo de la lógica simbólica. Esta funciones
reciben también el nombre de predicados. Estudiaremos detenidamente los
predicados y la lógica en el Capítulo 3.

Ejemplo 2.28. Sea b mod p el resto entero resultante de dividir el entero b


por el entero p. Así, por ejemplo, 8 mod 3 = 2, 15 mod 5 = O, Y así
sucesivamente. Obsérvese que O ~ b mod p ~ p - 1. Si X = N, Y = Z23 y
h(b) = b mod 23, entonces h(46) = O, h(70) = 1, Y así sucesivamente.

* N. del T.: En castellano se suele utilizar el término cardinalidad o simplemente cardinal


de un conjunto.
Conjuntos y funciones 29

La función h es un ejemplo sencillo de una clase de funciones conocidas como


funciones hash*, que se suelen utilizar en programas de computadoras que
almacenan y recuperan información de archivos muy grandes.

Ejemplo 2.29. Otro tipo de funciones que aparecen frecuentemente en


informática es la clase de funciones polinómicas, tales como x Z, 2x 3 + 1,
, x 5. - 5x, y así sucesivamente. La forma general de una función polinó-
mIca es:

p(x) = a nx n + a n-l xn - l + '" + a l x + a o

Así, si n = 3, a 3 = 2, a z = al = O, ya o = 1, entonces p(x) = 2x 3 + 1.

Las funciones exponenciales y logarítmicas son dos clases de funciones con una
importancia tal en la informática, que es necesario estudiarlas cuidadosamente.
Comencemos por la función exponencial.

Definición. Sean X = Y = R, Y seanf(x) 2x • La función f(x) recibe el


nombre de función exponencial con base 2.

Puesto que 20 = 1 Y 2 -x = 1/2x , es posible tabular algunos valores de la


función exponencial y perfilar su gráfica, que se muestra en la Figura 2.14.

x Z'
-2 1/4
-1 1/2
O 1
1 2 x
2 4
3 8
4 16

Figura 2.14. La función exponencial y su representación gráfica.

* N. del T.: Es extraordinariamente dificil traducir al castellano el término «hash». Su


traducción literal sería picadillo. Más en consonancia con el concepto sería traducir «hashtable»
por tabla asociativa, «hash functiom> por función asociativa, «hashing» por asodatividad, aunque
aquí no lo hemos hecho.
30 Computación l. Lógica, resolución de problemas, algoritmos y programas

La principal características de la función exponencial es su crecimiento


rápido. Si la x se incrementa en 1, f(x) se dobla, puesto que 2x + 1 = 2 x 2x .
Los dos acertijos siguientes ilustran esta propiedad; el primero se debe a los
franceses y el segundo a los persas.

Ejemplo 2.30. Supongamos que tenemos un estanque en nuestra propie-


dad. En el estanque crecen nenúfares, y hemos observado que la superficie
del estanque que recubren éstos se dobla cada dia. Al principio, el creci-
miento era pequeño, puesto que el área que cubrían era escaso. Sin embar-
go, si llegasen a cubrir todo el estanque, eliminarían todos los demás tipos
de vida en él. Después de 29 días observamos que la mitad del estanque
está totalmente cubierto. ¿Cuántos días nos quedan para poder salvar el
estanque? (La respuesta, por supuesto, es 1 día).

Ejemplo 2.31. Cierto individuo ofreció regalar al rey un precioso tablero


de ajedrez si el rey le daba a él un grano de trigo en la primera casilla, dos
granos sobre la segunda, cuatro sobre la tercera, y así sucesivamente hasta
que el tablero estuviera cubierto completamente. Pensando de forma super-
ficial sobre lo que le pedían, el rey aceptó. El rey no tardó mucho en caer en
la bancarrota.

Estimemos cuánto arroz debía dar el rey al individuo. Antes de ello, recorde-
mos algunas propiedades algebraicas de las funciones exponenciales. Las cua-
tro funciones que necesitamos son las siguientes:

2m 2m 2m +" (2.8)
m 2m -"
2 /2" (2.9)
(2 m )" 2m" (2.10)
2m!" = .::12 m (2.11 )

donde m y n son números naturales (estas propiedades se verifican incluso si n


y m son reales, aunque nuestras aplicaciones sólo requerirán el caso más sim-
ple).
No es dificil verificar esas propiedades. En la Ecuación (2.8), el lado izquier-
do es el producto de m doses seguido del de n doses. El resultado es el produc-
to de m + n doses. La verificación de la Ecuación (2.9) se obtiene de forma
análoga. La parte izquierda de la Ecuación (2.10) es el producto de n copias de
2m; es decir, el producto de n copias de un producto de m doses. Por tanto, es el
producto de mn doses. Para verificar la Ecuación (2.11), utilizamos la propie-
dad de que la Ecuación (2.10) puede extenderse al caso en que m y n sean
números racionales. Es decir, 2m !" puede escribirse como (2 m )l!", por lo que se
obtiene directamente la Ecuación (2.11) utilizando el hecho de que 2 1!" =.::12.
Podemos ahora estimar la cantidad de granos de trigo que el donante del
tablero de ajedrez habría recibido si el rey hubiera sido capaz de cumplir el
Conjun tos y funcion es 31

compro miso. Consid eremos los primer os cuadra dos. En la casilla


1 habría que
deposi tar 1 (= 2°) granos de trigo; en la casilla 2, 2 ( = 2 1) granos ; en
2 la casilla 3,
4 (= 2 ) granos; y así sucesivamente. Así, el día 64 el individ uo
tendría que
recibir 2 63 granos de trigo del rey. Pero:

263 260 X 2 3 = (2 1 °)6 X 23


210 1024 ~ 1000 = 10 3
Por tanto: 263 ~ (10 3)6 X 8 = 8 X 10 18

Si hacem os una estima ción de 50 granos de trigo por cada gramo


, esto es
aproxi madam ente igual a 1,6 x 10 11 tonelad as métricas de trigo,
que excede
con creces la produc ción de arroz de todo el mundo .
La función exponencial es una función uno-a- uno; así, si Xl =f. x ,
2 entonc es
2" =f. 2". Su rango es el conjun to de númer os reales no negativos. Por
tanto, si
restrin gimos el domin io al conjun to {x E Rlx > O}, entonc es el domin
io de la
función exponencial será igual al rango (véase de nuevo la Figura
2.14). En este
caso, la función es invertible. La función inversa de una exponencial
recibe el
nombr e de función logarítmica.

Definición. Sea X = R e Y = {x E Rlx > O}. Seaf(x ) = 2x • Enton ces'¡-l


recibe el nombr e de función logarítmica en base 2 y se represe
nta por
log2(x).

Recuérdese que una función y su inversa cumple n fU - 1 (x»


= x y
f-IU( x» = x. Esta relació n nos conduc e a las ecuaci ones siguien
tes, que
relacio nan entre sí ambas funciones:

(2.12)

(2.13)

Ejemplo 2.32. Puesto que 2 3 = 8,log 8 = 3. Es decir, 2 3 =


2 8 respon de a
la pregun ta: «¿Qué obtend remos cuando elevamos el númer o 2 al
cubo?».
Por el contra rio, log2 8 = 3 respon de a la pregun ta: «¿A qué potenc
ia hay
que elevar 2 para obtene r 8?». Del mismo modo, tendrem os que log2
16 = 4,
Y log2 1024 = 10.

En la Figura 2.15 se muestr a la gráfica de la función logarítmica.


La principal
caracte rística de la función expone ncial es su rápido crecimiento.
Por el con-
trario, la princip al caracte rística de la función logarít mica es su
crecimiento
lento. Obsérvese que log2 8 = 3 Y log2 16 = 4. Es decir, añadie
ndo 1 a x se
dobla 2x , mientr as que al doblar x sólo se le añade 1 a log2 x.
Otra forma de compa rar las funciones exponencial y logarítmica
es la si-
guiente: y = 2X significa que el númer o 1 debe ser doblad o x
veces para
y prog ram as
ón de prob lem as, algo ritm os
32 Com puta ción l. Lógica, reso luci

Figura 2.15. La fun ción loga rítm ica.

debe ser
de 2, y = log2 x significa que x
obtener y. Si x es una potencia ifica que
r 1. Por ejemplo, log2 16 = 4 sign
dividido por 2 y veces, par a obtene (2.14) a
16 debe ser dividido por 2 cua tro
veces par a obt ene r 1. Las Ecuaciones ica.
rítm
(2.18) describen algunas propied
ades adicionales de la [unción loga

(2.14)
log2 1 O
(2.15)
log2 2 1
(2.16)
log2 mn log2 m + log2 n
(2.17)
log2 (m/n) log2 m - log2 n
(2.18)
n
log2 (m ) n log2 m

loga-
depende del hecho de que la función
La verificación de esas expresiones Ecu acio nes (2.1 4)
l. Abajo verificamos las
rítmica es la inversa de la exponencia ficación de las restantes.
veri
a (2.16), dejando par a el lector la x. Entonces
4) sup ong amo s que log2 1 =
Par a verificar la Ecuación (2.1
5), sea log2 x = x.
ficar la Ecuación (2.1
2x = 1, por lo que x = O. Par a veri Par a verificar la Ecuación (2.16), sea
= 1.
Entonces 2 = 2, resultando x x y
X
Y • Por tan to, nm = 2 + , y
onc es m = 2x
y n = 2
x = log2 m e y = log2 n. Ent = log2
bin and o ambas, obtenemos log2 mn
por tan to, x + y = log2 (mn). Com
m + log2 n.
Conjuntos y funciones 33

Ejemplo 2.33. logz 3 = 1,585 Y logz 5 = 2,322. Podemos utilizar esta


información para calcular las expresiones siguientes:

a) logz 6 = logz (2 x 3) = logz 2 + logz 3 = 1 + 1,585 = 2,585


b) logz 1,6 = logz (8/5) = logz 8 - logz 5 = 3 - 2,322 = 0,678
e) logz 0,5 = logz (1/2) = logz 1 - 1 = - 1

Pascal incorpora las funciones exponencial y logarítmica, pero en base e en


lugar de en base 2. Se simbolizan mediante exp y l n respectivamente. Es
decir, dado x para calcular en Pascal eX escribiremos la expresión exp(x).
Conocido x para calcular en Pascal loge x, escribiremos l n ( x). Para más
detalles véase el manual de laboratorio.

Ejercicios
2.18. Una alternativa en criptografia para el cifrado de César es la utilización
de una clave de codificación, que consiste en un patrón que se repite e

°
indica al descodificador a qué distancia a la derecha en el alfabeto se
encuentra la letra correcta. Por ejemplo, A significa «desplaza letras»;
B significa «desplaza 1 letra», y así sucesívamente. Por tanto, si la clave
de codificación es ABCDABC..., el mensaje (en inglés) FOURSCO-
REANDSEVEN se codificaría FPWRTEOSGAOFSFXEO. Invertir el
proceso para el mensaje codificado (también en inglés) OIVOCGIO-
PASPIB. ¿Es este esquema una función de caracteres uno-a-uno? ¿Es
una función uno-a-uno de cadenas de caracteres?

2.19. Dibújense las gráficas de las funciones siguientes. Supóngase que el


dominio es R (excepto para el apartado e que es N).
a) 12xl
b) lx/3J
e) logz (2x)
x
d) 2>+
x mod 7
e)
f)
Ilogz xl
rIxll
g)
2.20. Simplificar las expresiones siguientes:
a) 3a x 3 -a
b) (2 Z)3
e) 27"/ 3
d) 23/2 4
e) logz 4
f) log3 (1/27)
g) logz 2"
h) 3 log3 (x + 1)
34 Computación l. Lógica, resolución de problemas, algoritmos y programas

2.2.6. Series finitas y funciones relacionadas

Los procesos acumulativos son frecuentes en la informática. Por ejemplo, al-


gunos programas repiten un determinado cálculo. Cada repetición consume
una cierta cantidad de tiempo y memoria. El programador tiene que ser ca-
paz de esÜmar la cantidad de tiempo y espacio que consumirá el programa,
sin necesidad de ejecutarlo. Tales cálculos suelen implicar la suma de lar-
gas listas de números o expresiones. Para facilitar todo ello, y otras expresio-
nes relacionadas, se ha desarrollado la denominada notación sigma. Esta nota-
ción utiliza la letra griega L (sigma) para simbolizar la «suma», un índice que
define el intervalo de valores para los que se realiza la suma, y la función que
hay que sumar. Supongamos que se quiere escribir la suma de los 20 primeros
enteros, 1 + 2 + 3 + ... + 20, utilizando notación sigma. Escribiremos
entonces:

Esto se lee, «calcular la suma de los i enteros comprendidos entre 1 y 20,


ambos inclusive». El número 20 es el límite superior y el 1 el límite inferior,
siendo la variable i la variable índice de la sumatoria.

Ejemplo 2.34. A continuación, se muestran varios ejemplos de notación


sigma:

n
a) i~l i 1 + 2 + 3 + ... + (n - 1) + n.

n
b) i~ 3i O + 3 + 6 + 9 + '" + 3(n - 1) + 3n.

n
e) L
i= 1
(3i + 2) 5 + 8 + 11 + .. , + (3n - 1) + (3n + 2).

1 + 2 + 3 + ." + (n - 2) + (n - 1).

e) i~ i 2 + 3 + 4 + ... + (n - 1) + n.

n
f) i~ i(3i + 2) = 5 + 16 + 33 + ... + (n - 1)(3n - 1) + n(3n + 2).
Conjuntos y funciones 35

De hecho, cualquier función de una variable índice i, puede sumarse de esta


forma. Considérense las relaciones generales siguientes:

n
I f(i)
i=m
= f(m) + f(m + 1) + ... + f(n) (2.19)

n
)'
L....J
a·I = a1 + a2 + .,. + an (2.20)
j= l

Obsérvese la utilización de subíndices en la Ecuación (2.20). Este tipo de nota-


ción suele utilizarse para diferenciar los elementos de una lista, siendo al el
primero, a z el segundo, y así sucesivamente. Utilizaremos a menudo esta idea
de lista en el diseño de programas.

Propiedades de las series finitas. Las Ecuaciones (2.21) a (2.24) reflejan algunas
propiedades básicas de las sumas utilizando la notación sigma.

¡=1
I a¡ al (2.21)

n n

¡= 1
I a¡ al + ¡~a¡ (2.22)

n n~l

¡= 1
I a¡ an + I
¡= 1
a¡ (2.23)

n m n

¡= 1
I a¡ I
¡= 1
a¡ + I
¡=m+l
a¡ SI 1 ~m < n (2.24)

Que se pueden justificar de la forma siguiente: En la Ecuación (2.21) los límites


inferior y superior son iguales, por lo que existirá un único término, el corres-
pondiente al valor común de los límites. En la Ecuación (2.22) el lado derecho
de la expresión es simplemente igual al izquierdo, con el primer término escrito
por separado. En la Ecuación (2.23), el lado derecho es igual al izquierdo, pero
con el último término escrito por separado. En la Ecuación (2.24), el lado
derecho es igual al izquierdo separado en dos sumas -una hasta m, y otra
desde m + 1 hasta n.
Las Ecuaciones (2.25) a (2.29) resumen otras importantes propiedades adi-
cionales de las sumas en series finitas.

I
j= 1
e en donde e es una constante (2.25)

n n
I
¡= 1
(f(i) e I
i= l
f(i) (2.26)
36 Computación ,. Lógica, resolución de problemas, algoritmos y programas

n n n
I
j= 1
(f(0 + g(i)) i~ f(O + j~ g(i) (2.27)
n n n
I
j= 1
(fU) - g(i)) I
j= 1
f(i)
j=l
I g(i) (2.28)
n
i~ (f(i + 1) - f(i)) = f(n + 1) - f(l) (2.29)

Es posible justificar estas ecuaciones haciendo las consideraciones siguientes:


En la Ecuación (2.25), dIado izquierdo es el propio valor c sumado a sí mismo
n veces. En la Ecuación (2.26), dIado derecho es igual al izquierdo, tras sacar c
factor común de la suma. En la Ecuación (2.27), el lado derecho es igual al
izquierdo, pero con los términos reagrupados. La Ecuación (2.28) se deja como
ejercicio. La Ecuación (2.29) se conoce como las series telescópicas. El lado
izquierdo puede escribirse como:

(f(2) - f(l)) + (f(3) - f(2)) + ... + (f(n + 1) - f(n))

donde podemos ver que todos los términos entre f(n + 1) y f(l) se cancelan
mutuamente, por lo que el resultado seráf(n + 1) - f(l).

Algunas series importantes para la informática. No suele ser habitual la mode-


lización de procesos acumulativos, como sumas finitas, utilizando notación
sigma. Generalmente, es deseable encontrar una fórmula que nos calcule el
resultado en función de los límites inferior y superior. Se han obtenido estas
fórmulas para algunas de las series más comunes, y es conveniente memorizarlas.
Una de tales series, que se utiliza frecuentemente en la informática, es la
serie aritmética (también conocida como progresión aritmética):

1 + 2 + 3 + ... + (n - 1) + n

Existe una anécdota interesante sobre esta serie. Cad Friedrich Gaus (1777-
1855) fue uno de los matemáticos más destacados del siglo XIX. Cuando era
joven le enviaron a un internado, más conocido por su estricta disciplina que
por su calidad académica. Como castigo, él y sus compañeros deberían realizar
el ejercicio de sumar los 100 primeros enteros. Apenas el profesor había enun-
ciado el problema, Gauss levantó su mano dando la respuesta correcta. Gauss
había descubierto una forma sencilla de sumar la serie aritmética para n =
100. Gauss escribió la suma horizontalmente en orden creciente, y debajo
escribió la misma suma, pero en orden decreciente, haciendo después la suma
término a término:

1 + 2 + 3 + + 99 + 100
+ 100 + 99 + 98 + + 2 + 1
101 + 101 + 101 + + 101 + 101
Conjuntos y funciones 37

Gauss pudo observar que el resultado era la mitad de 101 x 100, o 5050. El
profesor de Gauss reconoció que el muchacho tenía un gran talento para las
matemáticas, y recomendó a su padre que lo enviara a un colegio en el que
pudiera sacar mejor partido de su capacidad. De hecho, podemos utilizar el
método abreviado de Gauss para sumar series en general:

1 + 2 + 3 + + (n 1) + n
+ n + (n 1) + (n 2) + + 2 + 1
(n + 1) + (n + 1) + (n + 1) + + (n + 1) + (n + 1)

Puesto que hay n términos en la suma, el resultado de la suma anterior pue-


de escribirse n(n + 1). Por tanto, la suma de la serie aritmética original es
n(n + 1)/2, puesto que se ha sumado consigo misma para obtener la última
línea. La forma compacta de la suma de esta serie, junto con la de otras tam-
bién importantes, se muestran en las Ecuaciones (2.30) a (2.33).
n
n(n + 1)
i~l 2
(2.30)

n
n(n + 1)(2n + 1)
i~1 i2
6
(2.31 )

n
n 2 (n + 1)2
I
i= 1
i
3
4
(2.32)

n
I 2i 2n+ 1 _
1 (2.33)
i= 1

Las Ecuaciones (2.30) a (2.32) se las conoce con el nombre de p-series por ser de
la forma:
n

I
i= 1
iP

para algún valor de p. La Ecuación (2.32) es un ejemplo de la que se conoce


como serie geométrica o progresión geométrica.

Ejemplo 2.35. A continuación, se estudian dos ejemplos en los que se ve


cómo pueden utilizarse esas propiedades para calcular sumas de series.
15
15(15 + 0_
i~1 i 2
120

20 20 6
20(21) 6(7)
i~i i~1 JI -2- 2
210 - 21 189
V progra mas
38 Compu tación ,. Lógica, resoluc ión de problem as, algoritm os

Ejemplo 2.36. Considérese el proble ma discutido en el Ejemplo 2.31. Es


granos
posible utilizar la Ecuación (2.33) para calcular el númer o total de
de arroz que se acumu larían en 64 días, de la forma siguiente:

Obsérvese que 264 = 2 X 2 = 2 + 2 , luego 2 - 1 = 2 + 2


63 63
63 63 63 64 - 1.

recibid os el día 64 fue 263 .


Recuérdese que el número de granos de trígo
que la
Por tanto, el número de granos recibidos el día 64 era de ¡uno más
días anterio res! En genera l, las series
suma de los recibidos en todos los
geométricas tienen esta caracte rística. Es decir,

1 + 2 + 4 7, que es 1 menos que 8


1 + 2 + 4 + 8 = 15, que es 1 menos que 16
creci-
y así sucesivamente. Esto aporta otro punto de vista sobre el rápido
miento de las funciones exponenciales.

2.3. SUMARIO
conjun-
En este capítulo se han introducido algunas ideas fundamentales sobre
mayor ía de estas nocion es tienen directa se
tos y funciones. Como se sugirió, la
ática. En este libro utilizar emos los con-
importantes aplicaciones en la inform
que esta
ceptos de conjun to y función siempre que sea necesario. Veremos
y exacta, produc e grande s benefic ios en la solu-
notación matemática, concisa
áticos, sean los propio s proble mas de natura leza
ción de problemas inform
tienen un
matem ática o no. Las propiedades de los conjuntos y las funciones
lo 3, y en el
gran paralelismo con las de la lógica, como veremos en el Capítu
tanto, un conoci-
diseño de compu tadora s, como se verá en el Capítu lo 7. Por
import ancia en el
miento profundo de los conjun to y las funciones es de vital
estudio de la disciplina informática.

Ejercicios

2.21. Desarr ollar cada una de las sumato rias siguientes:


Conjuntos y funciones 39

o
d) i~ a¡

2.22. Expresar las series siguientes en notación sigma:


a) 17 + 21 + 25 + ... + 65.
b) 2 + 5 + 10 + 17 + .. , + 101.
e) La suma de los números impares de O a 1000, inclusive.
d) La suma de los enteros positivos menores que 300 y que sean divisi-
bles por 3.
e) La suma de las raices cuadradas de todos los enteros positivos
menores que 1000.

2.23. a) Justificar la Ecuación (2.28).


b) Justificar que la suma de los n primeros números impares es n 2 •

2.24. Calcular la suma de las expresiones desarrolladas en el Ejercicio 2.22.

2.25. Calcular las sumas siguientes:


25 n
a) ¡¡i e) ¡~1 2

13 5
b) i~ 2i(i + 1) f) ¡~ (2¡ + 1)
12 5
e) ¿ 3¡2
¡=6
g) i~ ¡2

10 O
d)
k~5 2k h) ¿1 a¡
i=

2.26. Un programa de computadora ordena una lista L = (al' a 2, ..., a n ) en la


forma siguiente. Examina cada elemento de la lista, selecciona el mayor,
y lo intercambia con an0 A continuación, repite el mismo proceso para la
sublista (a l ' a 2 , ... , a n _ 1)' El programa termina cuando sólo quede a 1 en
la sublista.
a) Utilizar notación sigma para escribir una expresión que refleje el
número de exámenes realizados por el proceso.
b) Sumar la expresión.

2.27. En Nepal, el arroz se cultiva a menudo en terrazas formadas en las


laderas de las colinas. Una colección de estas terrazas, observadas a
40 Computación l. Lógica, resolución de problemas, algoritmos y programas

vista de pájaro, se puede ver en la Figura 2.16. El cuarto de círculo


interior, correspondiente a la cima, no está cultivado. Moviéndonos
hacia abajo por la colina, los parterres se alternan con zonas escarpadas
no cultivadas. Supongamos que, para una determinada colina, el radio
del círculo de la cima es de 75 metros, y cada círculo concéntrico de ahí
hacia abajo tiene 50 metros. Supongamos que hay 10 parterres planta-
dos.'
a) Utilizar la notación sigma para escribir una expresión que refleje el
área total cubierta por los parterres de arroz.
b) Calcular el área total cubierta por parterres.

75 125 175 225 275 325


Metros

Figura 2.16. Panorámica a vista de pájaro


de los parterres de Nepa!.

2.28. Si comenzamos con una célula de levadura el día O, y al final de cada


día cada célula se divíde en dos, ¿cómo de grande será la colonía des-
pués de n días?
CAPíTULO 3
LÓGICA

La lógica está adquiriendo un papel cada vez más preponderante en la infor-


mática. De hecho, algunas personas llegan tan lejos como para afirmar que la
informática no es más que lógica aplicada. En este texto, la lógica tiene tres
distintas e importantes aplicaciones: se utiliza la lógica en programación para
construir expresiones lógicas (Capítulo 4); se utiliza la lógica para escribir pre y
poscondiciones y otros asertos que describen el comportamiento de los progra-
mas (Capítulos 5 y 6); Yse utiliza la lógica como fundamento para el diseño de
las computadoras mismas (Capítulo 7).
En las Secciones 3.1 y 3.2 se introduce la lógica proposicional, que es un
sistema para razonar y hacer cálculos con proposiciones. La lógica proposicio-
nal nació entre los años 1847 y 1854, fruto de los trabajos de George Boole.
Boole observó que existía una gran similitud entre las operaciones lógicas and
y or, y las operaciones aritméticas producto y suma. Boole desarrolló un siste-
ma para la manipulación de expresiones lógicas, que era tan preciso para
manipular esas expresiones como la aritmética para manipular números.
En la Sección 3.3 se extiende la noción de lógica proposicional a otra más
amplia, conocida como lógica de predicados. Esta extensión nos permite utili-
zar la lógica en informática de una forma muy constructiva. Por ejemplo, la
utilización de los denominados cuantificadores lógicos está estrechamente rela-
cionada con la idea de bucles en los programas.
También se introducen las ideas de tautología y demostración. Estos con-
ceptos serán revisados en el Capítulo 6, en yuxtaposición con los conceptos
paralelos de prueba y verificación. Lógica y demostración son tan fundamenta-
les para la informática como para otras disciplinas, tanto artísticas como cien-
tíficas; por tanto, este capítulo será útil en otras áreas diferentes.

3.1. LÓGICA PROPOSICIONAL


La idea básica de Boole fue la de analizar patrones de argumentaciones lógi-
cas, utilizando símbolos para representar tanto aseveraciones simples como los
ón de prob lem as, algo ritm os
y pro gra mas
42 Com puta ción l. Lógica, reso luci
de lógica
la lógica se con oce por el nom bre
pro pio s patr one s. Est a par te de pos icio nal,
s lógica simbólica). En lógica pro
proposicional (o en algu nos caso e) o falso
r sólo dos valores verdadero (tru
una frase (o proposición) pue de tene :
ejemplos típicos de proposiciones
(false). Las ora cion es siguientes son
usa de
I
ulo isósceles, que tiene una hip oten
• El áre a de un triá ngu lo rect áng
lon gitu d 2 es 1/2.
(1 3 5 7 6 4) son men ore s que 10.
• Tod os los elem ento s de la lista
• 3 + 5 = 7. en-
jum ps ove r the lazy dog» está n pres
• En la frase «Th e qui ck bro wn fox és.
letr as del alfa beto ingl
tes, al men os una vez, tod as las ficación
del Cal vin College, alca nzó la cali
• Nin gún alu mn o de info rmá tica
de B + en el cur so de 1990.
preci-
o las ante rior es, de una form a más
Con obj eto de exp resa r frases com Tab la 3.1 se
a escribir pro pos icio nes . En la
sa, util izam os un estilo formal par ibir pro pos i-
lógicos que se util izan al escr
pre sen ta una lista de los operadores
ciones.
iendo
una exp resi ón que se con stru ye sigu
Definición. Un a proposición es
las reglas siguientes:
(false) son pro pos icio nes .
Regla 1: Verdadero (true) y falso ocido
o tipo sea {verdadero, falso} (con
Regla 2: Cua lqu ier variable cuy pos ició n.
com o tipo boo lean o) es una pro
tam bién lo es ('" p).
Regla 3: Si p es una pro pos ició n, /\ q),
nes, tam bién lo son (p v q), (p
Regla 4: Si p Y q son proposicio
(p => q), (p <=> q).

ítu-
es recursiva, según se vio en el Cap
Obsérvese que la definición ante rior ino s de una o
nen una pro pos ició n en térm
lo 2. Es decir, las reglas 3 y 4 defi con stru idas util i-
cuales pue den hab er sido
más pro pos icio nes ya existentes, las ten
ame nte. Sin emb arg o, deb ido a que exis
zan do las reglas 3 y 4, Y así sucesiv 1 y 2, que no util izan
s util izan do las reglas
pro pos icio nes elementales form ada algo es
nici ón, el pro ces o par a dete rmi nar si
la pal abr a proposición en la defi es dec ir, la defi nici ón no
reglas, no es infinito;
una pro pos ició n, util izan do estas
es circular. ipu -
os con el cálculo pro pos icio nal, man
Por el mo men to, cua ndo trab ajem pos ició en
n
ntar emo s sim bol izar nin gun a pro
laremos sólo var iabl es y no inte las varia-
mos formas de inte rpre taci ón de
particular. Má s ade lant e intr odu cire diseño de
el mu ndo de la pro gra mac ión y del
bles, que son de gra n util idad en
las com put ado ras.
Lógica 43

Tabla 3.1. Los operadores lógicos

Equivalente
Símbolo Significado
en castellano

no Negación
V o Disyunción
/\ y Conjunción
=> implica Implicación
-= si y solo si Eq uivalencia

Después de ver algunos ejemplos de la aplicación de estos operadores, estudia-


remos otras interpretaciones.

Ejemplo 3.1. Si p, q y r son variables booleanas, entonces las siguientes


expresiones son proposiciones válidas:

p
q
c-· p)
(p v q)
(p v (q v r»
(p =o> (q v (r =o> (p ~ q»»

De las Reglas 2 y 3 puede deducirse, de forma inmediata, que las cuatro


primeras son expresiones. Para la quinta expresión puede aplicarse reitera-
damente la cuarta regla, siguiendo los siguientes pasos:

p, q y r son proposiciones por la segunda regla.


(q v r) es una proposición según la regla 4.
(p v (q v r»
es una proposición según la regla 4.

Una línea de razonamiento similar sirve para demostrar que el sexto ejem-
plo es una proposición:

p, q y r son proposiciones por la segunda regla.


(p ~ q) es una proposición según la regla 4.
(r =o> (p ~ q» es una proposición según la regla 4.
(q v (r =o> (p ~ q))) es una proposición según la regla 4.
(p =o> (q v (r =o> (p ~ q»))) es una proposición según la regla 4.

i Ejemplo 3.2. Las expresiones siguientes no son proposiciones:

(p)
(p q) p)

I ~ (p)
44 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Observando el Ejemplo 3.2, si somos estrictos en la aplicación de las reglas,


los paréntesis juegan un papel crucial en la determinación de si una expresión es
una proposición. Sin embargo, podemos prescindir de ellos siempre que el signi-
ficado de la expresión esté claro. Por ejemplo, podemos escribir '" p en lugar de
('" p), p v q en lugar de (p v q), y así sucesivamente.
Sin embargo, la eliminación descuidada de los paréntesis puede conducir-
nos a am~igüedades. Por ejemplo, no está claro si p v q /\ r significa

((p v p) /\ r) o bien,
(p v (q /\ r))

Para evitar estas ambigüedades, se asignan prioridades a los operadores:


'" tiene la prioridad más alta; /\ tiene la siguiente más elevada; y v, =:> y ~
tienen sus prioridades por este mismo orden. De esta forma, la expresión
p v q /\ r siempre se debe interpretar como (p v (q /\ r)).

3.1.1. Representación de frases en castellano


utilizando la lógica proposicional

No siempre es posible representar cualquier frase arbitraria, de forma simbóli-


ca, utilizando proposiciones o predicados. Es decir, muchas frases no son de-
clarativas y, por tanto, no puede afirmarse que sean verdaderas o falsas. Por
ejemplo, «¡cierra la puerta!» es una orden, y ¿fuiste a la clase de anatomía de
ayer, del profesor Davidson? es una pregunta. Sin embargo, existen gran canti-
dad de frases que pueden representarse como proposiciones y, más aún, que
pueden representarse como predicados. En informática, la mayoría de las cosas
que solemos querer representar simbólicamente, no son ambiguas, tienen un
valor verdadero o falso, también no ambiguo y son expresables en forma de
proposición o de predicado.
Obsérvese que en la lógica proposicional existen cinco operadores. De he-
cho, solamente es necesario uno; el resto pueden obtenerse a partir de éste
(véanse los ejercicios de la Sección 3.2). La lengua castellana ofrece una gran
cantidad de formas para expresar la misma idea; así, al representar frases
mediante proposiciones evitaremos muchos matices del significado, centrando
nuestra atención en la estructura lógica de las oraciones. Del mismo modo,
utilizaremos la misma estructura lógica para expresar diferentes giros en caste-
llano. Por ejemplo, si representamos la frase «Phogbound quiere ser senador»
mediante la variable p, las oraciones siguientes pueden representarse ambas
mediante la proposición '" p:

Phogbound no quiere ser senador.


No es verdad que Phogbound pretenda ser senador.

De forma similar es posible simbolizar las frases siguientes, como p /\ q si


realizamos una asignación apopiada a las variables p y q:
Lógica 45

El número n es un número primo menor que 100.


10 ~ x ~ 100
Henry Higgins era un soltero recalcitrante, pero Eliza Doolitte conquistó
su corazón.
Aunque había sido un traficante de esclavos, John Newton terminó siendo
un decidido oponente a la esclavitud.
I
La primera de las oraciones anteriores puede descomponerse en dos frases
-«n es un número primo» y «n es menor que 100»- aunque están condensa-
das en una utilizando, según es costumbre en el lenguaje coloquial. La segunda
también contiene dos frases ocultas, aunque en este caso el método de síntesis
corresponde a las matemáticas. En la tercera, el adverbio pero tiene en lógica
significado de la operación y; introduce un matiz en la frase que no es necesa-
rio para la construcción de las proposiciones. En la cuarta frase, la proposición
aunque indica la presencia de una y, a la vez que sirve para remarcar el con-
traste.
Podemos simbolizar la frase:

Algunos han nacido para la grandeza, algunos alcanzan la grandeza y


algunos soportan sobre sí la grandeza

tanto por ((p 1\ q) 1\ r) como mediante (p 1\ (q 1\ r)). En la Sección 3.2 se verá que
ambas expresiones lógicas son equivalentes, y que pueden eliminarse los parén-
tesis, quedando, por tanto, la expresión p 1\ q 1\ r. En la frase anterior, la prime-
ra coma equivale al operador 1\.
El operador v puede tener en castellano dos significados ligeramente dife-
rentes, por ·10 que es necesario distinguirlo. En la frase:

Este banco proporcionará crédito a todo el que mantenga un saldo medio de


500 dólares o que tenga un certificado de depósito de más de 5000 dólares.

El banco, evidentemente, no intenta excluir a aquellas personas que cumplan


simultáneamente ambos requisitos. Por el contrario, podemos imaginar a un
padre diciendo: «Puedes ir a la piscina o a la pista de patinaje», que quiere
decir que «puede ir a uno u otro lugar, pero no a ambos». Mientras el banco
utiliza la o inclusiva que significa que ambas posibilidades son aceptables, el
padre utiliza la o exclusiva que significa que «una de las dos posibilidades es
aceptable, pero no ambas a la vez».
En proposiciones como p => q, p recibe el nombre de antecedente y q el de
consecuente. Un ejemplo en castellano de una implicación lógica es:

Si Phogbound resulta elegido, entonces estaremos apañados.

Aunque el adverbio entonces no suele incluirse en el lenguaje coloquial. Si


simbolizamos mediante p la frase «Phogbound resulta elegido», y mediante q a
«estaremos apañados», podemos representar toda la frase como p => q (que
IGIL.JIUIJIM•...i.IISIIISlIILllj!IJ"".!lI!C,;aIllJIII..lI!I!d!ll!lll.. 1!lII!fIlI""""",_4-~·'
\IIl,

46 Compu tación ,. Lógica, resoluc ión de problem as, algoritm os


y progra mas

significa «p implica q»). De forma alternativa, es posible reescribir esa


frase en
la forma

Estaremos apañados si Phogb ound resulta elegido,

aunque la frase anterio r puede seguir representándose de forma


simbólica
como p => q.
Obsérvense las distintas utilizaciones que se hacen de la proposición
si, en
las siguientes frases:

Compr aré entrad as si representan Nuestr o barrio.


Compr aré entrad as sólo si representan Nuestr o barrio.

Si representamos mediante r la proposición «representan Nuestr o


barrio» y
mediante s la proposición «compraré entradas», la primer a frase puede
repre-
sentarse en la forma r =::> s. Cualqu ier person a que pronuncie esta frase
no está
afirmando nada acerca de lo que haría si se representase Maebeth. En
cambio,
la última frase tiene un significado ligeramente diferente, y que se
simboliza
mediante s => r. Esta frase puede rehacerse de la forma «si compr
o entrad as,
entonces es que se representa Nuestr o barrio»; en definitiva, que la
única cir-
cunstancia que hará que compre entrad as es que se esté representando
Nuestr o
barrio»; En estas circunstancias, se dice que la proposición s =::>
r es la proposi-
ción recíproca de r =::> s.
Otra propie dad import ante de la implicación p =::> q es que no es necesa
rio
supone r la existencia de causalidad entre p y q. Por ejemplo, cuando
decimos:
Si George toca bien la viola, entonces le seleccionarán para la orques
ta,
implícitamente asumimos que si George es elegido para tocar en la orques
ta, es
porque toca bien. La frase:

Si 1 + 1 = 2, entonces el sol es el centro del sistema solar.

También se simboliza por p => q que, desde el punto de vista de la


lógica, es
tan válida como la afirmación de George y la orquesta.
La causalidad es otra de las cosas que podemos expresar en lenguaje
natu-
ral, pero que no podemos expresar en el cálculo proposicional.
La expresión p ~ q se utiliza para indicar que dos proposiciones
son
lógicamente equivalentes; es decir, que una implica la otra y vicever
sa. Un
ejemplo típico es el siguiente:

Sea T un triángulo de lados a, b y e. Entonces a 2 + b 2 = e 2 sólo si


T es un
triángulo rectángulo.

Si representamos por p «a 2 + b 2 = e 2 », y por q «T es un triángu


lo rec-
tángulo», la frase «a 2 + b2 = e 2 si y sólo si T es un triángulo rectáng
ulo»
Lógica 47

se representa mediante P ~ q. Es decir, que son ciertas simultáneamente


tanto P => q como q => p.

Ejemplo 3.3. Se pueden representar como proposiciones oraciones en-


teras. Veamos un ejemplo relativamente complicado: «Si conseguimos
los contratos de Dynamic Systems o de Metadyne, van Alstyne o Liu
realizarán tanto el análisis como el diseño; a Frederick no se le asignará
el diseño, y Thompson será despedido». Para poder escribir la proposi-
ción equivalente a esta frase, primero tenemos que asociar variables a
cada parte de la oración.

DS Conseguir el contrato de Dynamic Systems.


MD Conseguir el contrato de Metadyne.
VAA Se le asigna el análisis a van Alstyne.
L/VA Se le asigna el análisis a van Liu.
VAD Se le asigna el diseño a van Alstyne.
L/VD Se le asigna el diseño a Liu.
FRA Se le asigna el análisis a Frederick.
TF Thompson es despedido.

Con estas definiciones de variables, la oración anterior puede represen-


tarse:

(DSvMD) =>
(((VAA 1\ VAD) v (L/VA 1\ L/VD» 1\ "" FRA 1\ TF)

3.1.2. Evaluación de proposiciones: Tablas de verdad

Recuérdese que en el Capítulo 2 definíamos el concepto de estado de una


variable. Esta noción puede aplicarse también a las proposiciones.

Definición. Sean PI' ..., Pn las variables de una proposición. El estado de


esas variables PI' ..., Pn es el resultado de asignar los valores booleanos
verdadero o falso a cada una de ellas.

Por ejemplo, supongamos que tenemos la proposición P v q. Puesto que la


proposición consta sólo de dos variables, cada una de las cuales puede tomar
sólo dos valores posibles (verdadero o falso), existen sólo cuatro posibles esta-
dos para las variables de la proposición P v q. De forma alternativa, podemos
considerar la proposición P v q como una función en dos variables P y q, con
dominio {verdadero, falso} x {verdadero, falso} y con rango {verdadero, falso}.
Así, al contrario que las funciones con dominios infinitos (tales como N o R), la
función P v q tiene un dominio tan reducido que pueden tabularse sus valores
para todos los estados posibles de sus variables. Estas tablas reciben el nombre
48 Computación /. Lógica, resolución de problemas, algoritmos y programas

de tablas de verdad. La tabla de verdad de la función p v q se muestra en la


Figura 3.1.

p q pvq

verdadero verdadero verdadero


verdadero falso verdadero
falso verdadero verdadero
falso falso falso

Figura 3.1. Tabla de verdad para la función pv q.

En la tabla, se puede apreciar que el valor de la función es verdadero siempre


que cualquiera de las variables, o ambas, valga verdadero; en caso contrario
valdrá falso. Esta definición de la función es consistente con nuestra idea intui-
tiva acerca de la disyunción, que se ilustró en la Sección 3.1.1. Supongamos
que las variables p y q representan las proposiciones siguientes:

p «Tenemos un saldo medio mayor de 500 $.»


q «Tenemos un certificado de depósito de más de 5000 $.»

Entonces, la proposición p v q vale verdadero, siempre que p y q, o ambas a la


vez, sean verdaderas; en otro caso (p v q) valdrá falso.
Las tablas de verdad de las otras operaciones de la lógica proposicional,
pueden construirse de forma análoga. Estas tablas se muestran en la Figura 3.2.
La mayoría de los valores de estas operaciones son consistentes con las ideas
intuitivas introducidas en la Sección 3.1.1. Asi, la negación siempre devuelve el
valor contrario al de la variable, y la conjunción lógica sólo se evalúa a verda-
dero cuando ambas variables valen verdadero simultáneamente.
El operador implicación precisa una discusión más cuidadosa, sobre todo
en el caso en que p vale falso. Lo anterior, no es dificil de entender si considera-
mos el ejemplo siguiente:

Si representan Nuestro barrio, entonces compraré entradas.

La frase anterior se representa por p ~ q. De hecho, si no se representa


Nuestro barrio, la variable p vale falso. Sin embargo, no está garantizado que
no queramos comprar entradas aunque no se represente Nuestro barrio; en su
lugar, podemos comprar entradas para Línea de coros. Puesto que la falsedad
de p no garantiza la falsedad de q, podemos permitir que la proposición resul-
tante sea, en este caso, verdadera. (Otra justificación para esta decisión se
presentará en la Sección 3.2.4.)
La tablas de verdad se pueden construir para proposiciones de complejidad
arbitraria. Cuando una proposición involucra a más de un operador, la evalua-
ción de su valor se realiza siguiendo el orden de prioridad de los operadores.
Esto se ilustra en el Ejemplo 3.4.
Lógica 49

P q pAq
P ~p

verdadero verdadero verdadero


verdadero falso verdadero falso falso
falso verdadero falso verdadero falso
falso falso falso

a) b)

p q p=q p q p<:o>q

verdadero verdadero verdadero verdadero verdadero verdadero


verdadero falso falso verdadero falso falso
falso verdadero verdadero falso verdadero falso
falso falso verdadero falso falso verdadero

e) d)
Figura 3.2. Tablas de verdad para proposICiones: (a) Negación; (b) conjun-
ción; (e) implicación; (d) equivalencia.

Ejemplo 3.4. Para construir la tabla de verdad de (p v q) p comenza- = '"


mos por p y q; a continuación, se deriva la tabla para cada subproposición
(p v q) y ~ p, combinando finalmente los resultados utilizando la tabla de
verdad de la operación =. Habitualmente se trabaja siempre de izquierda a
derecha, tal y como se muestra abajo:

p q pvq ~p (p v q) = ~p

verdadero verdadero verdadero falso falso


verdadero falso verdadero falso falso
falso verdadero verdadero verdadero verdadero
falso falso falso verdadero verdadero

Una forma alternativa de construir tablas de verdad es la de escribir la opera-


ción una única vez y colocar el resultado de la evaluación de cada subproposi-
ción debajo del operador correspondiente. Si lo hacemos así, debemos tener
cuidado de rellenar las columnas en el orden adecuado, como se muestra en la
tabla de verdad siguiente:

p q (p v q) = ~p

verdadero verdadero verdadero falso falso


verdadero falso verdadero falso falso
falso verdadero verdadero verdadero verdadero
falso falso falso verdadero verdadero
50 Computación ,. Lógica, resolución de problemas, algoritmos y programas

En este caso, comenzamos rellenando las columnas de p y q; a continua-


ción, rellenamos la columna de (p v q); a continuación, la de "-' p, y finalmente
la columna correspondiente a =>. La tabla de verdad de la proposición comple-
ta se corresponde con los valores de la última columna obtenida, que no se
corresponde forzosamente con la columna de más a la derecha.
El número de filas de una tabla de verdad crece exponencialmente con el
número de variables presentes en la proposición. Es decir, si una proposición
tiene una variable como, por ejemplo, "-' p, su tabla de verdad tiene dos filas.
Proposiciones con dos variables como p v q tienen tablas de verdad de cuatro
filas. Proposiciones de tres variables tendrán ocho filas, puesto que existen
2 3 = 8 formas diferentes de asignar los valores verdadero y falso a las tres
variables. En general, el tamaño de una tabla de verdad con n variables será de
2n filas. Por este motivo, si trabajamos con más de cuatro o cinco variables, el
tamaño de las tablas de verdad resulta inmanejable.
Una forma de acortar la longitud de las tablas de verdad es mediante la
búsqueda de condiciones «da igual». Por ejemplo, cuando evaluamos la propo-
sición (p v p), si p vale falso, no importa lo que valga q; la proposición se
evaluará a verdadero en cualquiera de los casos. Es decir, el valor de q es una
entrada «da igual», y 10 denotaremos mediante un guión (-) en la columna
correspondiente de la tabla de verdad. Con esto, podemos reducir en uno el
número de filas de la tabla de verdad para p v q:

p q pvq

verdadero verdadero
falso verdadero verdadero
falso falso falso

Utilizando esta técnica, se pueden reducir de forma significativa el número de


líneas de las tablas de verdad de expresiones complejas, como se muestra en la
Figura 3.5.

Ejemplo 3.5. La proposición (p /\ q) => (r v (p => s)) podría tener una tabla
de verdad completa de 16 líneas. Sin embargo, utilizando condiciones «da
igual» podemos reducir su tamaño a cinco lineas, tal y como se muestra
debajo:

p q r s (p 1\ q) => (r v (p => s))

falso falso verdadero


falso falso verdadero
verdadero verdadero verdadero verdadero verdadero verdadero verdadero
verdadero verdadero falso verdadero verdadero verdadero verdadero verdadero
verdadero verdadero falso falso verdadero falso falso falso
Lógica 51

El proceso de construcción de tablas de verdad con entradas «da igual»


puede ser bastante complejo, tal y como sugiere el ejemplo. Cursos avanzados
en lógica yen diseño de circuitos tratan este tema más cuidadosamente. Intuiti-
vamente podemos ver que, si reducimos el número de filas en una tabla de
verdad, reduciremos también la complejidad de cualquier circuito representa-
do por la proposición. Esta es una de las actividades cruciales en el diseño de
l(ls computadoras modernas: un objetivo primordial es la minimización del
número de circuitos y, por tanto, la reducción del costo de fabricación de la
computadora. Estas ideas serán abordadas en el Capítulo 7.

3.1.3. Tautologías

Algunas proposíciones se distinguen por el hecho de que son siempre verdaderas.


Es decir, cualquiera que sea la asignación de valores a sus variables, siempre se
obtendrá el valor verdadero en la última columna de la evaluación de la propo-
sición. Formalmente, estas proposiciones reciben el nombre de tautologías.
Definición. Una tautología es una proposición que se evalúa a verdadero
en cualquier estado; una contradicción es una proposición que se evalúa a
falso en cualquier estado.
Podemos comprobar si una proposición es una tautología sin más que escribir
su tabla de verdad. También existen otros métodos para comprobar si una
proposición compleja es o no una tautología. Estos métodos implican el desa-
rrollo de demostraciones, y se estudiarán en la Sección 3.2.3. Las demostracio-
nes de la lógica juegan un papel trascendental en la informática, por lo que les
dedicaremos un interés especial en este capítulo.
Ejemplo 3.6. Para verificar que la proposición (p 0= q) /\ p) 0= q es una
tautología, podemos desarrollar su tabla de verdad y examinar la columna
más a la derecha para comprobar que todas las entradas son verdadero.

p q ((p =;. q) /\ p) =;. q

verdadero verdadero verdadero verdadero verdadero


verdadero falso falso falso verdadero
falso verdadero verdadero falso verdadero
falso falso verdadero falso verdadero

Ejemplo 3.7. La proposlcIOn' p /\ P es una contradicción. Esto puede


"V

verse en la tabla de verdad siguiente:

verdadero falso falso


falso verdadero falso
52 Computación l. Lógica, resolución de problemas, algoritmos y programas

Esto confirma algo intuitivo: es imposible para una proposición ser simul-
táneamente verdadera y falsa. Igualmente, p v '" p es una tautología.

p pv ~p

verdadero falso verdadero


falso verdadero verdadero

La aseveración de que p v '" p es una tautología recíbe el nombre de ley del


exclusión del caso intermedio. Es decir, para cualquier proporción, ella misma o
su negación deben ser cierta; no existe una tercera posibilidad. Esto significa que
algunas frases que son comunes en el lenguaje coloquial, como «la frase que está
leyendo ahora es falsa», están excluidas del cálculo proposicional. Se han desa-
rrollado otros modelos lógicos que no incluyen la ley de exclusión del caso
intermedio; estos modelos son de gran utilidad en el estudio de las bases de
datos. La ley de exclusión del término medio, junto con el hecho de que la
proposición p 1\ "-' P es una contradicción, nos indica que cualquier proposición,
o es una tautología o no lo es; no puede ser ambas cosas al mismo tiempo.

Ejemplo 3.8. La proposición '" (p 1\ q) Y ('" P v '" q) tienen la misma tabla


de verdad.

p q ~p ~q ~(P/\q) (~pv~q)

verdadero verdadero falso falso falso falso


verdadero falso falso verdadero verdadero verdadero
falso verdadero verdadero falso verdadero verdadero
falso falso verdadero verdadero verdadero verdadero

La aseveración de que "-' (p 1\ q) Y ('" P v '" q) tienen la misma tabla de


verdad, constituye una de las tres aseveraciones que conjuntamente constitu-
yen las denominadas Leyes de Margan. Estas leyes son muy útiles en la simplifi-
cación de proposiciones compuestas, especialmente si contienen negaciones. La
segunda Ley de Morgan se analiza en el ejercicio 3.6.

Ejercicios

3.1. Razonar cuáles de las frases siguientes constituyen una proposición:


a) En esta página hay 512 palabras exactamente.
b) 314159 es un número primo.
e) Xl + 2x + 1 = 13.
d) La edad del universo es de unos 17 billones de años, aproximada-
mente.
e) Disparad al pianista.
Lógica 53

f) x - y = o.
g) ¿Por qué es azul el cielo?
h) La hipotenusa es el lado más corto de un triángulo rectángulo.

3.2. Construir las proposiciones siguientes a partir de variables, aplicando


una regla de la definición en cada paso.
u) (p => (p /\ q))
h) -=
((( "-' p) q) /\ ( "-' r))
e) «(p v ( "-' q)) /\ (p V q)) => p)

3.3. Representar las frases siguientes, de forma simbólica, utilizando proposi-


ciones. Definir claramente qué significa cada variable.
a) -l~x~l,-l~y~l
b) Aunque Manderson tenía una mente fina y analítica y grandes rique-
zas, estaba incomprensiblemente loco.
e) Bien Trent estaba en París mientras Mabel estaba en Londres, bien
Trent estaba en Venecia mientras Mabel estaba en París.
d) Una definición es recursiva sólo si incluye un paso de inicialización y
otro de inducción.
e) Siempre que Thomas y Dumars disparen bien, los Pistons ganarán, a
no ser que Rodman esté lesionado y Laimber batee mal, en cuyo
caso los Pistons perderán.
f) Si el grupo de diseño cometiera un error, serían los principales res-
ponsables, y los demás tendrían una responsabilidad secundaria; si el
grupo de programadores cometiera un error de codificación, enton-
ces el grupo de Ted o el de Louis sería el principal responsable; si el
error fuera de prueba, tanto el grupo de FouSen como el de Rita
serían los principales responsables.

3.4. De acuerdo con la definición, ¿cuáles de las siguientes son proposiciones


válidas?
verdadero
(verdadero)
~P/\q

p => "-'q)
(p V « "-' p) /\ q))
(p => p) /\ « "-' q) => q)

3.5. Construir las tablas de verdad de las proposiciones siguientes, utilizando


condiciones «da igual» siempre que resulte apropiado. Identificar las tau-
tologías.
a) «p => q) /\ p) => q
b) «pp => r) /\ (q => ~r)) => (p /\ q)
e) p /\ (q V r) => S
d) «(p =>q) /\ (q => r)) => (p => r))
54 Computación ,. Lógica, resolución de problemas, algoritmos y programas

e) ((p => (q /\ "-'q)) => p)


f) (p => q) V (p <o:> '" q)

3.6. Demostrar que '" (p v q) y ("-' P /\ "-' q) tienen la misma tabla de verdad.

3.2. RAZONAMIENTO CON PROPOSICIONES


1
En esta sección se analizan algunos aspectos fundamentales del razonamiento
con proposiciones. En este contexto, razonamiento significa desarrollo de nueva
información a partir de otra dada, expresada en forma de proposiciones (y
como predicados).
Es necesario desarrollar métodos que permitan determinar cuándo dos
proposiciones son equivalentes, de forma que una pueda ser sustituida por la
otra. Es también necesario encontrar una forma alternativa a las tablas de
verdad, para determinar cuándo una proposición arbitraria es una tautología.
Finalmente, es necesario explorar cómo los métodos de razonamiento pueden
ayudarnos a resolver problemas lógicos que están planteados en castellano.
Todas estas técnicas constituyen una preparación necesaria para una fase pos-
terior, en la que utilizaremos técnicas de razonamiento para analizar varias
características de programas escritos en Pascal (Capítulo 6) y en el diseño de
computadoras (Capítulo 7).

3.2.1. Equivalencia
Considérese la proposición (p /\ q) V (p /\ "-' q), cuya tabla de verdad se mues-
tra abajo:

p q (p /\ q) V (p /\ ~q)

verdadero verdadero verdadero


verdadero falso verdadero
falso verdadero falso
falso falso falso

Los valores de la última columna de la tabla son exactamente los mismos que
los de la columna de la p; por tanto, podemos decir que (p /\ q) V (p /\ '" q) es en
cierta manera equivalente a p. Por tanto, si en alguna situación nos encontra-
mos con (p /\ q) V (p /\ '" q) podemos sencillamente reemplazarla por p.
En cuanto empezamos a razonar con proposiciones, se ve la necesidad de
simplificar proposiciones complejas. Nuestro propósito ahora será el de definir
qué significa que dos proposiciones sean equivalentes, y el ver cómo esta equi-
valencia permite simplificar expresiones complejas.
Definición. Se dice que dos proposiciones p y q son equivalentes si tienen
el mismo valor para cada estado. En otras palabras, p y q son equivalentes
si tienen la misma tabla de verdad.
Lógica 55

Por tanto, una forma de comprobar si dos proposiciones son equivalentes es


escribiendo su tabla de verdad. Existe otro método que generalmente es más
eficaz, basado en el siguiente teorema.

Teorema. Dos proposiciones p y q son equivalentes y lo simbolizamos


como p == q, si y sólo si la proposición p <:o> q es una tautología.

El teorema no es dificil de probar. Primero, supongamos que p y q son


equivalentes. Entonces en cualquier estado ambas son bien verdaderas bien
falsas. Por tanto, en cualquier estado p <:o> q es bien (verdadero <:o> verdadero)
bien (falso <:o> falso) haciendo de p <:o> q una tautología. Por el contrario,
supongamos ahora que p <:o> q es una tautología, y que S es un estado en que p
es falso. Puesto que p <:o> q es una tautología, q tiene que ser también falso.
Igualmente, si S es Un estado en que p es verdadero, entonces q también debe
serlo en ese estado.

3.2.2. Propiedades de la equivalencia


La equivalencia tiene una serie de propiedades que se verifican para todas las
proposiciones, y que son de gran utilidad en varias aplicaciones de la lógica.
En la Tabla 3.2 se resumen las más importantes.

Tabla 3.2. Propiedades de la equivalencia

Conmutatividad' Asociatividad
pAq == qAP P A (q A r) == (p A q) A r
pv q == qvp p v (q v r) == (p v q) v r

Distributividad Leyes de Morgan


p A (q V r) == (p A q) V (p A r) ~(pvq) == ~pA ~q
p v (q A r) == (p v q) A (p V r) ~(pAq) == ~pv ~q

Ley de la implicación Ley de la bicondicionalidad


p =q == ~pvq p<o>q==(q=q)A(q=p)

Propiedad de la negación Identidad


~(~p) == p p == p
Ley de la exclusión del término medio Ley de la contradicción
p v ~p == verdadero pA ~p == falso
v -simplificación A -simplificación
pvp == p pAp == P
p v verdadero == verdadero P A verdadero == p
p v falso == p p A falso == falso
pV(pAq) == P p A(p V q) == p
56 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Obsérvese que muchas de estas expresiones tienen una paralela en las que
fueron estudiadas para los conjuntos (véase Tabla 2.1). Además, muchas de
estas propiedades tienen también una análoga en la aritmética. Así, las propie-
dades conmutativa, asociativa y distributiva permiten la simplificación de ex-
presiones aritméticas. De una forma similar la asociatividad, conmutatividad y
distributividad introducidas en la Tabla 3.2 permiten la simplificación de pro-
posiciones lógicas.
Estas propiedades se utilizan de diversas maneras. Por ejemplo, la conmu-
tatividad se utiliza para reordenar las proposiciones y así poder realizar otras
simplificaciones. La asociatividad permite la eliminación de paréntesis. Por
ejemplo, puesto que p /\ (q /\ r) == (p /\ q) /\ r, de forma equivalente es posible
escribir p /\ q /\ r. Las leyes de la distributividad permiten la factorización de las
proposiciones. Esta leyes totalmente equivalente a la ley aritmética que permi-
te escribir 3 x 5 + 3 x 7 = 3 x (5 + 7). Las leyes de Morgan son de gran
utilidad cuando se trabaja con expresiones que incluyen negaciones. La ley de
la implicación permite escribir expresiones equivalentes a la implicación, pero
utilizando sólo v, /\ Y "'.
Antes de utilizar las leyes de la equivalencia es necesario convencerse de
que son correctas. Para ello es suficiente construir una tabla de verdad para
cada lado de la equivalencia y verificar que ambas tablas son idénticas. De-
mostraremos lo anterior para la ley de la implicación, dejando el resto como
ejercicio.

Ejemplo 3.9. Para demostrar que (p ~ q) == ('" p v q), podemos construir


sus tablas de verdad de la forma siguiente:

p q p=q -pvq (p = q)~(-pvq)

verdadero verdadero verdadero verdadero verdadero


verdadero falso falso falso verdadero
falso verdadero verdadero verdadero verdadero
falso falso verdadero verdadero verdadero

Simplificación de proposiciones mediante la utilización de las propiedades de la


equivalencia. Centremos nuestra atención en la simplificación de proposicio-
nes. Entendemos por simpllfzcación el proceso de modificar una proposición
para que resulte más útil a un determinado propósito, como el conseguir que
contenga menos variables y/o operadores que la proposición original.
Para simplificar una proposición, escribimos una serie de pasos numerados.
Cada paso se compone de una proposición en la parte izquierda, y de una
referencia a una ley particular en la parte derecha. La proposición de la iz-
quierda es siempre equivalente a la del paso inmediatamente anterior, en vir-
tud de la propiedad escrita en la derecha. Por ejemplo, supongamos que se
desea reducir la proposición (p ~ q) v (p ~ r). En el paso 1 se escribe la
proposición, y los pasos 2 al 4 contienen las proposiciones equivalentes.
Lógica 57

1. (p => q) /\ (p => r)
2. (- p v q) /\ ( - P v r) Ley de implicación
3. '" p v (q /\ r) Distributividad
4. p => (q /\ r) Ley de la implicación

Donde puede observarse que la proposición del paso 2 resulta de la del paso 1,
tras la aplicación de la ley de la implicación. La del paso 3 se obtiene de la
del '2 aplicando la distributividad, y la del 4 se obtiene de la del 3 por la ley de
la implicación. El Ejemplo 3.10 es más complicado:

Ejemplo 3.10. En el Ejercicio 3.5a se utilizó una tabla de verdad para


demostrar que la proposición ((p => q) /\ p) => q es una tautología. Pueden
utilizarse las propiedades de la equivalencia para demostrar de nuevo que
lo es. En este caso demostraremos que la proposicíón anterior es equivalen-
te a la proposición simple verdadero.

1. ((p => q)/\p) => q


2. ((-pvq)/\p) => q Ley de implicación
3. (( - P /\ p) V (q /\ p)) => q Distritibutividad
4. (falso v (q /\ p)) => q Contradicción
5. (q /\ p) => q v -simplificación
6. - ((q /\ p) /\ - q) Ley de la implicación
7. - ((p /\ q) /\ - q) Conmutatividad
8. - (p /\ (q /\ - q)) Asociatividad
9. -(p/\falso) Ley de la contradiccíón
10. - falso /\ -simplificación
11. verdadero Negación

El ejemplo 3.10 se ha desarrollado con excesivo detalle; cada propiedad se


ha aplicado en un paso separado. Cuando nos familiaricemos más con estas
propiedades, será posible abreviar los cálculos. De hecho, es lo mismo que en
el caso del álgebra, donde nuestra familiaridad y costumbre en la manipulación
de expresiones complejas nos permite la simplificación mediante la aplicación
de varias reglas a la vez. Como ejemplo, obsérvense las dos simplificaciones
siguientes:

1. 5x + 1 = 3x + 2 1. 5x + 1 = 3x + 2
2.2x=1 2. (5x + 1) - 1 = (3x + 2) - 1
3. x = 1/2 3. 5x + (1 - 1) = 3x + (2 - 1)
4. 5x + O = 3x + 1
5. 5x = 3x + 1
6. 5x - 3x = (3x + 1) - 3x
7. (5 - 3)x = (1 + 3x) - 3x
8. 2x = 1 + (3x - 3x)
9. 2x = 1 + (3 - 3)x
10. 2x = 1 + Ox
58 Computación ,. Lógica, resolución de problemas, algoritmos y programas

11. 2x=1
12. (1/2)2x = 1/2
13. ((1/2)2)x = 1/2
14. Ix = 1/2
15. x = 1/2

La simplificación en tres pasos de la izquierda es lo habitual cuando se


conocen bien las propiedades de la aritmética, mientras que la simplificación
en 15 pasos es lo habitual en los aprendices del álgebra. Con este libro se
pretende alcanzar un conocimiento suficiente de la lógica, de manera que las
simplificaciones se parezcan más a la de la izquierda, es decir, que se realicen
aplicando varias leyes en cada paso. Sin embargo, el conocimiento de las
propiedades en que se basan estos argumentos, es fundamental para entender
claramente el proceso realizado.
El ejemplo 3.11 muestra una serie de pasos más corta, en la simplificación
de la proposición (p => (q /\ r)) /\ (~p => (q /\ s)). En ella q aparece como parte
del consecuente de dos condiciones diferentes, que están conectados con el
operador /\. En la simplificación se extrae q de ambas, de forma que la propo-
sición puede escribirsé como la conjunción de q con dos implicaciones.

Ejemplo 3.11. Simplificar la proposición (p => (q /\ r)) /\ (~p => (q /\ s)):

1. (p => (q /\ r)) /\ ( ~ p => (q /\ s))


2. ( ~ p v (q /\ r)) /\ (p V (q /\ s)) Ley de la implicación, negación
3. ( ~ p v q) /\ ( ~ P v r) /\ (p V q) /\ (p V s) Distributividad, asociatividad
4. (( ~ P /\ p) V q) /\ ( ~ P v r) /\ (p V s) Conmutatividad,
distributividad
Ley de la contradicción
v -simplificación
Ley de la implicación

3.2.3. Reglas de inferencia: La idea de demostración

Cuando se estudia la lógica, el primer objetivo es el de conseguir patrones de


razonamiento. Sería deseable encontrar una metodología que nos permitiera
afirmar cuándo una determinada conclusión es válida -es decir, si es o no una
tautología-o Sabemos que las tablas de verdad son un buen instrumento
cuando trabajamos con proposiciones sencillas, pero que resultan tediosas
cuando las proposiciones contienen más de tres o cuatro variables u operado-
res.
Por tanto, pretendemos encontrar una metodología alternativa que nos
permita llegar a conclusiones válidas de una forma legítima. En dicha metodo-
logía, denominada demostración, el criterio de razonamiento consiste en el
encadenamiento de una serie de pasos o inferencias, cada una de las cuales es
encadenable con la siguiente de una manera justificable formalmente. El proceso
Lógica 59

de demos tración es una extensión de la metodología desarro llada para


simpli-
ficar proposiciones. Sin embarg o, las demostraciones contienen un
ingrediente
adicional de gran importancia: se realiza una inferen cia a partir de
una suposi-
ción. Las inferencias de una demos tración se realizan aplican
do un reducido
conjun to de reglas de inferencia. Es decir, las reglas de inferencia
permiten
obtene r proposiciones a partir de otras, de las que se conoce n si son
verdade-
ras o se supone que lo son. Estas reglas se resumen en la Tabla 3.3.

Definición. En el cálculo proposicional, una demost ración es


una secuen-
cia de pasos que sirven para demos trar que una determ inada propos
ición p
es una tautología. Cada paso de la demos tración es bien una propos
ición
ya demos trada, bien una propos ición que se ha obteni do de otra
anterio r
utilizando una regla de inferencia, o bien una propos ición que se introdu
ce
como suposición para prepar ar un paso posterior. Este último
tipo de
proposiciones (suposición) lo simbolizamos ponién dolo entre corche
tes
cuadra dos [y]; todos los demás pasos se acomp añan de una justific
ación; y
el último paso de la demos tración debe ser la propia p.

La sencilla prueba del ejemplo 3.12 muestr a que p => p es una tautolo
gía.
Podría mos demos trar esto utilizando una tabla de verdad, pero represe
nta una
buena oportu nidad para introdu cir la notació n y estilo de las demos
traciones.
Ejemplo 3.12. Demos trar p => p:
1. [p]
2. p 1
3. p => p =>-introducción, 1, 2

La primer a línea contiene la suposición [p]. Al comienzo de la demos


tración,
podem os supone r cualquier cosa que creamos que puede llevarnos
a la con-
clusión.
La suposición, junto con los pasos subsiguientes que se obtienen
de ellas,
sueñalen presentarse sangrados, de forma que la lógica de pasos subsigu
ientes
pueda distinguirse del resto de la prueba. Puesto que supone mos
que p es
verdadero, se puede deducir que p es verdadero, luego el paso 2 se obtiene
del 1
directamente. Colocamos un «1» en la colum na de la derecha de la
demos tra-
ción como justificación del paso realizado. A contin uación utilizam
os la regla
de inferencia de la =>-introducción (sustituyendo p por q en la fórmul
a general
de la Tabla 3.3) con lo que se concluye en el paso 3 que p =>
p es una
tautología.
La Tabla 3.3 fue desarro llada por el matem ático alemán Gerha rd Gentze
n.
En ella se reflejan las reglas de inferencia que se utilizan en el razona
miento
normal, y que constituyen las bases de la mayor ía de los estudios
y aplicacio-
nes de la lógica formal. Cada regla de la tabla tiene la forma:
p
q
60 Computación l. Lógica, resolución de problemas, algoritmos y programas

Tabla 3.3. Reglas de inferencia

=-introducción ~-introducción

[p] p=q
-q-- q=p
p=q p~q

=-eliminación ~-eliminación

(modus ponens) p~q p~q

p=p p=q q=p


-p- ~ -introducción
q
(modus tollens) [p]
p=q falso
~p

::!L
~p
~ -eliminación

falso
p
/\ -introducción
p
v -introducción
-q- -p- -q-
P/\q pvq pvq
/\ -eliminación v -eliminación
P/\q P/\q [p] [q]
-p- -q-
pv q r r
r

La propoSlclOn p que aparece encima de la línea indica un paso anterior


(suposicióq u otro) que ya aparece en la demostración. La proposición q que
aparece debajo de la línea puede obtenerse o ser iriferida en el siguiente paso de
la demostración. Asi, la regla denominada =>-introducción en las tablas permite
realizar la inferencia p => q en cualquier paso de la demostración, en la que p
aparece como suposición y q como conclusión en un paso subsiguiente.
Una comparación cuidadosa entre estas reglas y algunas de las propiedades
de la equivalencia que se muestran en la Tabla 3.2, pone de manifiesto algunas
similitudes interesantes. Por ejemplo, la ley de la bicondicionalidad dada en
la tabla, conduce directamente a las dos reglas de inferencia denominadas
<=>-introducción y <=>-eliminación de la Tabla 3.3. De forma similar, la ley de la
contradicción de la Tabla 3.2 conduce directamente a la regla de inferencia de
la ~ eliminación. Sin embargo, muchas de las reglas de inferencia no tienen
ningún precedente, entre las propiedades de la equivalencia.
Las reglas de inferencia pueden utilizarse como alternativa a las tablas de
verdad en la demostración de que una determinada proposición es una tauto-
logía. Sin embargo, como veremos más adelante, las demostraciones son más
Lógica 61

versátiles que las tablas de verdad, puesto que las tablas de verdad son inmane-
jables cuando analizamos las proposiciones de más de tres o cuatro variables, y
además no aportan ninguna base para razonar con proposiciones de forma
deductiva.
Podemos autoconvencernos, de forma intuitiva, de que las reglas de infe-
rencia tienen sentido. Por ejemplo, la regla de la 1\ -introducción nos dice que si
hllmos demostrado que p y q son tautologías en dos pasos anteriores cuales-
quiera de la demostración, en el paso actual podemos concluir que p 1\ q es una
tautología. De forma similar, la regla de la ==>-eliminación (conocida familiar-
mente como modus ponens), simplemente establece que, si en un paso anterior
hemos demostrado que p ==> q y que p es una tautología, en el paso actual
podemos concluir que q es una tautología.
En las demostraciones procederemos paso a paso para conseguir, en cada
uno de ellos, una nueva tautología que nos acerque más a nuestra meta -la
últíma proposición de la prueba--. Justificaremos cada paso realizado, bien en
la utilización de una regla de equivalencia (mediante la sustitución de una
parte derecha de una expresión de la Tabla 2.3, por una parte izquierda, o vi-
ceversa), bien mediante la utilización de una regla de inferencia. Adicionalmen-
te a lo anterior, es preciso desarrollar estrategias de demostración; es decir, mé-
todos de realizar demostraciones que puedan ser de utilidad en diferentes
situaciones. Estas situaciones se dan no sólo en la lógica, sino también en
varias aplicaciones de la informática, las matemáticas y la ciencia en general.

3.2.4. Estrategias de demostración

En esta sección se estudian algunas estrategias básicas para demostrar que una
proposición es una tautología, mediante la utilización de las propiedades de la
equivalencia y las reglas de inferencia.

Demostración deductiva. Una demostración deductiva es aquella en que se


utilizan las reglas de inferencia para deducir las conclusiones, después de haber
realizado una o más suposiciones para comenzar el proceso de demostración.
El ejemplo 3.13 es un ejemplo sencillo.

Ejemplo 3.13. Demostrar que p ==> (q ==> p)

1. [p] Suposición
2. [q] Suposición
3. p 1
4. q => p =>-introducción, 2, 3
5. p ==> (q => p) =>-introducción, 1, 4

Si se intenta demostrar que una proposición de la forma p ==> q es una tautolo-


gía, es aconsejable comenzar suponiendo p y tratar de encontrar una serie de
pasos que nos conduzcan a q. Si lo conseguimos, el último paso de la demos-
62 Computación J. Lógica, resolución de problemas, algoritmos y programas

tración será la propia p ~ q. Esta estrategia fue utilizada en los Ejemplos 3.12
y 3.13.

Utilización del modus ponens. Supongamos que alguien dice: «Si abres la
jaula, se escapará el tigre» y supongamos que yo abro la jaula. La conclusión
seguramente será, por modus ponens, que el tigre escapará. Análogamente, si
oimos a alguien decir: «Si representan Nuestra Ciudad, compraré entradas», y
descubrimos que están representando Nuestra Ciudad, podemos concluir que
esa persona comprará entradas. En el Ejemplo 3.14 podemos ver cómo se
utiliza el modus ponens en una demostración.

Ejemplo 3.14. Demostrar que ((p ~ q) 1\ (r ~ p) 1\ r) ~ q:

1. ((p ~ q) 1\ (r ~ p) 1\ r) Suposición
2. r ~ p 1\ -eliminación, 1
3. r 1\ -eliminación, 1
4. p Modus ponens, 2, 3
5. p ~ q 1\ -eliminación, 1
6. q Modus ponens, 4, 5
7. ((p ~ q) 1\ (r ~ p) 1\ r) ~ q ~-introducción, 1,6

Utilización de la I\-eliminación y modus tollens. En el Ejemplo 3.15 se desa-


rrolla una demostración utilizando una combinación de las reglas de inferencia
I\-eliminación y modus ponens. Como en casos anteriores, se parte de suposi-
ciones cuidadosamente elegidas y trabajamos deductivamente a partir de ellas.

Ejemplo 3.15. Demostrar ((p ~ q) 1\ '" q) ~ '" p:

1. [(p ~ q) 1\ '" q] Suposición


2. p ~ q 1\ -eliminación, 1
3. '" P 1\ -eliminación, 1
4. '" P Modus tollens, 2, 3
5. ((p ~ q) 1\ '" q) ~ '" p ~-introducción, 1, 4

Utilización de la '" -eliminación y de la '" -introducción. La segunda regla de


'" -eliminación dice que cuando partimos de una suposición falsa cualquier
conclusión p es una tautología. Así, podemos realizar una argumentación lógi-
ca muy sofisticada en la que una suposición no válida (no importa cómo de
nimia pueda ser ésta) hace que todos nuestros razonamientos sean erróneos.
Muchas argumentaciones pueden ser válidas, tanto en cuanto no dependan de
suposiciones falsas.

Ejemplo 3.16. Demostrar, sm utilizar modus tollens, la proposición


((p ~ q) 1\ "'q) ~ p:
Lógica 63

1. Suposición
2. A -eliminación, 1
3. A -eliminación, 1
4. [p] Suposición
5. q Modus ponens, 2
6. "'q 3
1- falso '" -eliminación 5, 6
8. '" P '" -introducción, 4
9. ((p ~q)A "'q) ~ ",p ~-introducción, 1, 8

El ejemplo 3.17 es algo más complicado.

Ejemplo 3.17. Demostrar (p ~ (q ~ r)) ~ ((p ~ q) ~ (p ~ r)):


1. [p ~ (q ~ r)]
2. [p ~ q]
3. [p]
4. q
5. q~r

6. r
7. P~ r
8. (p ~ q) ~ (p ~ r)
9. (p ~ (q ~ r)) ~ ((p ~ q) ~ (p ~ r))
(La justificación de los distintos pasos de la demostración se dejan al lector
como ejercicio.)
Utilización de la bicondicionalidad en las demostraciones. Para demostracio-
nes de proposiciones de la forma p ~ q, una estrategia que suele ser eficaz es
demostrar primero que p ~ q y después que q ~ p, utilizando posteriormente
la regla de la ~-introducción como último paso de la demostración.
Ejemplo 3.18. Para demostrar que (p ~ q) ~ ('" p v q), primero se de-
muestra que (p ~ q) ~ ('" p ~ q) es una tautología (pasos 1-13), y poste-
riormente se demuestra que ('" p v q) ~ (p ~ q) es una tautología (pasos
14-22). Finalmente, se aplica la regla de la ~-introducción como último
paso de la demostración. La estrategia seguida en los pasos 1-11 para
demostrar que p ~ q puede realizarse suponiendo '" p (paso 2) o bien q
(paso 8).
1. ['" p v q] Suposición
2. ["'p] Suposicion
3. [p] Suposición
4. pA ",p A-introducción, 2, 3
5. falso '" -introducción, 4
6. q '" -eliminación, 5
7. p~q ~-introducción, 3, 6
8. [q] Suposición
pro gra mas
ón de prob lem as, algo ritm os y
64 Com puta ción l. Lógica, reso luci

[p] Sup osic ión


9.
q 8
In =>-introducción, 9, 10
11. p= >q
p => q v-eliminación, 1, 2
12.
q) =>- in troducción
13. ( '" P v q) => (p =>
.[p => q] Sup osic ión
14. dio
pv "'p Ley de exclusión del cas o inte rme
15.
[p] Sup osic ión
16.
q Modus ponens, 14, 16
17.
"'p vq v -introducción, 17
18.
["'p ] Sup osic ión
19.
'" p v q v -introducción, 19
20. 20
v -eliminación, 15, 16, 18, 19,
21. "'p vq cción, 14, 21
(p q) => ('" p v q) =>-introdu
22. =>
~-introducción, 13, 22
23. (p => q) ~ ('" p v q)

(reducción al absurdo). La regl


a de inferencia
Demostración por contradicción ostr ació n
una imp orta nte estr ateg ia de dem
de la '" -int rod ucc ión es la base de típi ca de esta
contradicción. Un a apli cac ión
con ocid a com o demostración por ngu lo de lado s
ser la siguiente: Sea T un triá
técnica en las mat emá tica s pod ría es un tríá ngu lo
rem os dem ostr ar que T no
2, 3 Y 4. Sup ong amo s que que rect áng ulo , pod e-
io, que T es un triá ngu lo
rect áng ulo. Si sup one mo s lo con trar d de la hip oten usa ,
Pitá gor as que la lon gitu
mo s con clui r por el teor ema de de los otro s dos lado s;
elev ada al cua dra do, es igual a la sum a de los cua dra dos izan do la
o, 4 + 9 i= 16. Est o con duc e, util
esto es, 2 + 3 = 4 • Sin emb arg
2 2 2
ión, la hipó tesi s orig inal
es, por la '" -ind ucc
eliminación, al valo r falso. Ent onc
es tam bién falsa.
-T es un triá ngu lo rec tán gul o- tau to-
rem os dem ostr ar que «p => q) /\ p) => q es una
Sup ong amo s que que neg ació n es
cua lqu ier cas o -es decir, que la
logía. Sup ong amo s que es falsa en con trad ic-
esta sup osic ión nos con duc e a una
una tau tol ogí a- y ana lice mos si
a en el ejem plo 3.19.
ción. Est a es la estr ateg ia seg uid
Dem ostr ar «p => q) /\ p) => q:
Ejemplo 3.19.
Sup osic ión
1. ['" «(p => q) /\ p) => q)]
2. ",(" ,«p => q)/ \p) vq) =>-eliminación, 1
Ley de Mo rga n, /\-eliminación,
2
3. '" q /\-eliminación, 2
4. «p => q) /\ p)
/\ -eliminación, 4
5. (p => q)
p
/\-eliminación, 4
6.
7. q Modus ponens, 5, 6
Fal so '" -eliminación, 3, 7
8.
«p => q) /\ p) => q
'" -introducción, 1, 8
9.
Lógica 65

3.2.5. Resolución de problemas de la vida real


La lógica y los métodos de demostración pueden utilizarse en una gran
variedad de problemas lógicos o problemas de la vida real, en los que puede
obtenerse información nueva, a partir de una pequeña cantidad de informa-
ción inicial. En los Ejemplos 3.20 y 3.21 se utilizan las propiedades de la
~quivalencia, las reglas de inferencia y las técnicas de demostración:

Ejemplo 3.20. Los deseos son caballos, a condición de que los caballos
no vuelen. También, los mendigos no cabalgan, a condición de que los
deseos no sean caballos. Si se da el caso de que los mendigos cabalgan y
los deseos no sean equinos, entonces los caballos vuelan. Si la imposibi-
lidad de los caballos para volar y la imposibilidad de los mendigos para
cabalgar no son alternativas, entonces los mendigos no son ricos. Pero
los mendigos cabalgan, ¿son ricos los mendigos?
El primer paso para resolver problemas como este, es el de introdu-
cir variables que representen cada una de las proposiciones básicas que
contiene. Como suele ser habitual en problemas de la vida real, como es
este, las frases constituyen un pequeño jeroglífico que hay que descifrar.
En cualquier caso, supongamos que introducimos las variables siguientes:
W Los deseos son caballos
HF Los caballos vuelan
BRD Los mendigos cabalgan
BRCH Los mendigos son ricos
Es posible representar las cinco frases de que consta el problema origi-
nal mediante las proposiciones siguientes, respectivamente:
1. -HF ~ W
2. - W ~ - BRD
3. - (BRD /\ - W) ~ HF
4. - ( - HF v - BRD) ~ - BRCH
5. BRD
Utilizándolas igual que utilizaríamos suposiciones en una demostración
ordinaria, es posible escribir la serie de inferencias siguiente, haciendo
referencia la última a la variable BRCH que es la que nos interesa:
6. W Modus tollens, 2, 5
7. -BRDv W ~ HF Leyes de Morgan, 3
8. -BRDv W v-introducción, 6
9. HF Modus ponens, 7, 8
10. BRD/\HF /\-introducción, 5, 9
11. (HF /\ BRD) ~ - BRCH Leyes de Morgan, 4
12. -BRCH Modus ponens, 10, 11
Con lo que se demuestra que los mendigos no son ricos.
66 Computación /. Lógica, resolución de problemas, algoritmos y programas

Ejemplo 3.2.1. Los martes, o Timson está en el cementerio o Agnes


está en la sacristía. No es posible encontrar a Timson en el cementerio
sin Stanley. Stanley sólo abandona los martes el cementerio, cuando va
a pasear con Agnes. Si Hutchinson cometió el robo, Stanley no estaba
en el cementerio. El robo se produjo en martes. ¿Pudo ser Hutchinson
el ladrón?
Utilicemos las variables siguientes:
p Timson estaba en el cementerio
q Agnes estaba en la sacristía
s Stanley estaba en el cementerio
h Hutchinson fue el ladrón
u El robo fue en martes
Ahora es posible simbolizar el párrafo problema de la forma siguiente:
1. u => (p V q)
2. p => S
3.
4.
5.
Para poder deducir algo sobre la variable h, que es la que nos interesa,
podemos razonar de la manera siguiente:
6. pvq Modus ponens, 1, 5
7. [q] Suposición
8. S Modus Tollens, 3, 7
9. q=>s =>-introducción, 7, 8
10. S v-eliminación, 2, 6, 9
11. ~h Modus tollens, 4, 10
Por 10 que Hutchinson no fue el ladrón.

Ejercicios
3.7. Utilizar tablas de verdad para demostrar que las expresiones siguientes
son equivalencias.
a) p v (q /\ r) == (p v q) /\ (p V r)
b) ~(pvq) == ~p/\ ~q
c) p => q == ~(p /\ "'-'q)
d) p v falso == p
e) p /\ verdadero == p
f) P /\ (p V q) == p

3.8. Encontrar todas las correspondencias entre las propiedades de la equiva-


lencia entre proposiciones (Tabla 3.2), y las propiedades de la equivalen-
cia entre conjuntos (Tabla 2.1).
Lógica 67

3.9. Simplificar las proposiciones siguientes, encontrando otra equivalente


con menos operadores y/o variables:
a) (pvq)/\("'pv"'q)
b) (p /\ q) V (p /\ '" q) V ( '" P /\ q) V ( '" P /\ '" q)
e) p v ( '" P /\ q)
d) '" p ~ p
e) p ~ '" p
f) ((p /\ q) ~ p) ~ (p v q)
g) (p~(q/\"'q))~ "'p
3.10. La función nand se define mediante la tabla de verdad siguiente:

p q p nand q

verdadero verdadero falso


verdadero falso verdadero
falso verdadero verdadero
falso falso verdadero

Demostrar que los operadores "', /\, V Y ~ pueden definirse utilizando


la función nand. Es decir, encontrar proposiciones construidas sólo con
p's, q's y operadores nand cuyas tablas de verdad sean iguales a las de
'" p, p v q, P /\ q Y P ~ q, respectivamente.
3.11. Justificar los pasos realizados en cada una de las demostraciones si-
guientes:
a) ((p ~ q) /\ (q ~ r)) ~ (p ~ r)
(( '" p v q) /\ ( '" q v r)) ~ ('" p v r)
'" (( '" p v q) /\ ('" q v r)) v ('" p v r)
( '" ( '" p v q) v '" ( '" q v r)) v ( '" p v r)
(p /\ '" q) V (q /\ '" r) v ( '" p v r)
((p /\ '" q) V '" p) v ((q /\ '" r) v r)
( '" p v (p /\ '" q)) v (r v (q /\ '" r))
(",pvp)/\("'pv ",q))v(rvq)/\(rv "'r))
verdadero /\ ( '" P v '" q) v (r v q) /\ verdadero
( '" p v '" q) v (r v q)
'" p v (q v '" q) v r
'" p v verdadero v r
verdadero
b) (p~(q/\"'q))~ "'p
(p ~ falso) ~ '" p
'" (p /\ '" falso) ~ '" p
'" (p /\ verdadero) ~ '" p
~p ~ "'p

"'pvp
verdadero
68 Computación l. Lógica, resolución de problemas, algoritmos y programas

3.12. Demostrar que las proposiciones siguientes son tautologías, utilizando


reglas de equivalencia que las hagan equivalentes al valor verdadero:
a) (-pvq)=>(qv-p)
b) (p => q) ~ (-q => -p)
e) (p => - p) => - p
d) «p v q) 1\ ""' p) => q
e) (p v q) 1\ (p => s) 1\ (q => s)) => S
f) «p => q) 1\ (p => r)) => (p => q 1\ r)
g) «p => q) 1\ (r => s)) => «p v r) => (q V s))
h) «p => q) 1\ (r => s) 1\ ( - q v s)) => ""' P V - r

313. Utilizando reglas de inferencia, demostrar cada una de las tautologías


del Ejercicio 3.12.

3.14. Dada la proposición «p v q) 1\ ""' p) => q.


a) Demostrar que es una tautología, utilizando una tabla de verdad.
b) Demostrar que es equivalente a la oración siguiente, si se realiza
una asignación apropiada de variables a cada frase.
«O los Red Sox son mejores que los A-es o los Piratas son
mejores que los Red. Los Red Sox no son mejores que los A-es. Por
tanto, los Piratas son mejores que los Red.»
e) Demostrar que la proposición es una tautología, utilizando las
equivalencias y reglas de inferencia apropiadas. Ayuda: comenzar la
demostración suponiendo que el lado derecho de la implicación es
verdadero y concluirla utilizando la regla de la =>-introducción.

3.15. Desarrollar una demostración para cada una de las tautologías siguien-
tes, utilizando reglas de inferencia.
a) «p => r) 1\ (r => q)) => (p => q)
b) «p v q) 1\ (p => s) 1\ (q => s)) => S

3.16. En la isla Paradoja, todos sus residentes pertenecen a uno de dos clanes.
Los miembros de uno de los dos clanes siempre dicen la verdad, mien-
tras que los del otro siempre mienten. Un visitante de la isla Paradoja se
encuentra con tres nativos. Einstein, Planck y Bohr. Einstein dice: «o
bien Bohr o bien yo pertenecemos a un clan distinto de los otros dos.»
¿A qué clan pertenece Bohr? *

3.17. Abigail, Bridget y Claudia cenan a menudo juntas. Después de cenar


toman café o té. Si Abigail pide café, entonces Bridget pide lo mismo
que Claudia. Si Bridget pide café, entonces Abigail pide lo contrario de
lo que pida Claudia. Si Claudia pide té, entonces Abigail pide lo mismo

* Adaptado del libro de GEORGE J. SUMMERS, titulado The Great Book C?f Mind Teasers and
Mind Puzzles, Ed. Sterling Publishing Co., Nueva York, 1986.
Lógica 69

que Bridget. ¿Cuál de las tres (si es que existe) pide siempre lo mismo
después de cenar?

3.3. LÓGICA DE PREDICADOS


La programación puede considerarse como una actividad dirigida por un objeti-
vo. ~s decir, el propósito de un programa puede describirse como la consecución
de una tarea, que genera una salida de un determinado tipo. Por tanto, el punto
de partida en la construcción de un programa es la definición de la salida que se
espera que produzca. Otra tarea a completar en la construcción de programas
está estrechamente relacionada con la anterior. Definir un conjunto de condicio-
nes que describan todas las entradas posibles para las que el programa produzca
la salida deseada. Suele definirse primero la salida, porque describe el objetivo
del programa. Para entender esto, considérese el problema de encontrar todas
las posibles rutas para viajar en automóvil desde Cleveland a Dretoit. Para
resolver este problema, no solemos analizar todas las carreteras que salen de
Cleveland y ver cuáles llegan a Detroit; lo normal es localizar Detroit y desde
ahí ver sólo cuáles son las posibles carreteras que llegan desde Cleveland.
Para el diseño de problemas, necesitamos un lenguaje muy preciso para
describir entradas y salidas. Por ejemplo, supongamos que queremos construir
un programa que reorganice una lista de 30 enteros, de forma que los deje en
orden descendente (a este proceso se le conoce vulgarmente como ordenación).
Podemos describir, informalmente, las entradas y salidas de la forma siguiente:
Entrada = Cualquier lista de treinta enteros
Salida = Los 30 enteros originales, en orden descendente
Sin embargo, esta forma de descripción suele no ser lo suficientemente precisa
para definir un problema. Por ejemplo, no se da un criterio para determinar
que los enteros están en orden descendente. Esto puede parecer trivial, puesto
qe todos tenemos una idea clara de lo que esto significa. Esto es debido a que,
por la experiencia que compartimos, damos por hecho que conocemos esta
información. Sin embargo, una computadora no tiene estas ventajas, y en
algún punto del proceso de programación tenemos que indicarle un método
con el que determinar si la lista de enteros está ordenada en orden descendente
o no. Por tanto, una descripción más precisa es la siguiente:
Entrada Cualquier lista A = (el' el' ..., e 30 ) de enteros.
Salida Una lista B = (e'¡, e~, ..., e30) permutación de A para la que se
verifica que eí+ 1 ~ eí para cada entero i en el intervalo 1 al 29,
ambos inclusive.
Esta descripción alternativa clarifica la relación entre la entrada y la salida, y
aporta una definición no ambigua de lo que significa «en orden descendente».
Aunque nuestra segunda definición es más precisa que la primera, también
es más prolija. Es, por tanto, necesario encontrar una notación más sintética
para expresar los estados de entrada, salida y otros intermedios del programa.
y progra mas
70 Compu tación l. Lógica, resoluc ión de problem as, algoritm os

no es más
Esta notaci ón puede ser la denom inada «lógica de predicados», que
icional presen tada en las Seccio nes 3.1
que una extensión de la lógica propos
ce la notació n básica de la lógica de predica -
y 3.2. En esta sección se introdu
necesa rias para trabaja r con ella y utilizar la en la
dos, junto con las nociones
descripción de los estados de los progra mas.
formas
Las descripciones de entrad a y salida anteriores no son más que
predic ado
relajadas 'de predicados. De forma intuitiva, podem os consid erar un
inado valor,
como una frase que incluye variables, las cuales, al tener un determ
convierten la frase en una proposición.
ti-
Definición. Un predicado es un aserto constit uido por consta ntes aritmé
y los valores lógicos verdad ero y
cas y boolea nas (números enteros, reales
operac iones aritmét icas (=, *, etc.);
falso); variables aritméticas y booleanas,
lógicos
operaciones relacionales ( <, ~, >, ~, =, i=, E, etc.) y operad ores
valor verdad ero o falso depend iendo de los
(1\, v, ~, etc.), que tiene el
estados de sus variables.
expresiones
En Pascal, este tipo de predicados se conocen con el nombr e de
booleanas, y tienen gran cantida d de usos en progra mación .
preci-
En la Sección 3.1 se definió la sintaxis de las proposiciones de forma
sin embarg o, en su lugar,
sa. Podría mos hacer lo mismo con los predicados;
expresiones
utilizaremos nuestros conocimientos y experiencia con ese tipo de
alment e álgebra y progra ma-
en otros campo s de las matemáticas~especi
idos sobre propos iciones . Comence-
ción- junto con los conocimientos adquir
mos destac ando que cualquier proposición es tambié n un predica do.

Las
Ejemplo 3.22. Las proposiciones siguientes son tambié n predicados.
expresiones equivalentes en Pascal se muestr an a la derech a.
x < 10 x < 10
i 2 + / = 25 i*i + j*j = 25
al < a 2 1\a 2 < a 3 (a[1] < a([2]) and (a[2] < a[3])

necesario
Obsérvese en el tercer predic ado del Ejemplo 3.22 que, aunque no es
ntar el predica do, si 10 es en la expres ión en
utilizar paréntesis para represe
es debido a que los operad ores 1\ y v tienen menor
Pascal equivalente. Esto
priorid ad que los operad ores relacio nales «, ~, >, etc.). Sin embarg o, en
operad ores
Pascal, los operad ores and y or tienen una priorid ad mayor que los
para conse-
relacionales «, ~, >, etc.) en una expresión booleana. Por ello,
sis *.
guir una interpr etación equivalente, es necesaria la utilización de parénte
Las expresiones siguientes no son predicados:
i + 1
y2 < 10 i=
x := x + 1

se el
* Para una mayor información sobre las expresiones booleanas en Pascal, consúlte
manual de laborato rio.
Lógica 71

i + 1 no es un predicado, puesto que no tiene un valor lógico; simple


mente es
una expresión aritmética que tiene un valor numérico. y2 < 10 :1: podría
serlo,
si hubiera otra expresión a la derecha del símbolo :1:; pero, tal y como
está
escrita, carece de significado. La expresión x := x + 1 es una instrucción
válida
en Pascal, pero no es una expresión que tenga un valor lógico.
Es posible evaluar un predicado reemplazando todas sus variables por
sus
valpres, en un determinado estado, y calcular el resultado utilizando
las leyes
de la aritmética. Así, x < 10 se evalúa a verdadero en el estado x = 1,
Ya falso
en el estado x = 11. Decimos que x = 1 es un estado que satisface el
predica-
do x < 10. A un predicado que posee estado en el que puede ser satisfec
ho, se
le denomina satisfactible. Por ejemplo, el predicado x < 10 es satisfac
tible,
mientras que el predicado 11 < 10 no es satisfactible.

Definición. Un predicado que se satisface en cada estado se dice que es


válido.

Por ejemplo, si i es un entero, el predicado i ~ Ov i ~ 1 es válido, puesto


que
es verdadero para cualquier valor de i. Por tanto, el concepto de validez
para
los predicados es como la noción de tautología para las proposiciones
.
Ejemplo 3.23. Los predicados se utilizan frecuentemente en la definic
ión
de conjuntos. Por ejemplo, en cada una de las definiciones siguien
tes, el
conjunto definido contiene todos los estados que satísfacen el predic
ado
que haya la derecha de la barra vertical. El aparta do d utiliza cadena
s de
caracteres como dominio; el dominio del resto de las variables es el conjun
-
to de los números naturales N.

a) {(i, j) I i < j}
b) {(i,j) I i 2 + / = 25)}
c) {(a o, al' a 2) I a o = máx(a 1 , a2)}
d) {(Si' S2) / Sl es un prefijo para S2}
e) {(i, j) I i < j v j = O}
f) {(i, j) I i 2 + / = 25 1\ j > 2}

3.3.1. Los cuantificadores universal y existencial

Recuérdese de un ejemplo anterior, que es posible describir la salida


de un
programa como una lista de la forma

B = (e'¡, eí, ..., e30)

que es una permutación de otra lista A, y que tiene la propiedad de que


para i
entre 1 y 29, ambos inclusive, ej+ 1 ~ ej. Esta frase es la conjunción
de dos
72 Computación l. Lógica, resolución de problemas, algoritmos y programas

predicados. El primero, «B es una permutación de A», es un predicado con


treinta variables. El segundo, «para cada i entre 1 y 29, ambos inclusive, ei+ 1
~ ~ ei», contiene 29 predicados de la forma ei + 1 ~ e¡, uno por cada entero
entre el 1 y el 29. Es decir, sirve como abreviatura para el predicado

Debido a que expresiones como «para cada», «para todo», «para cada
uno» aparecen con frecuencia en matemáticas y lógica, todos ellos se simboli~
zan de forma abreviada con el símbolo V, denominado cuantificador universal.
En el ejemplo, podemos escribir la frase:

«para cada i entre 1 y 29, ambos inclusive, ei+ 1 ~ ei como


Vi E {1, 2, ..., 29}: e¡+ l ~ e;

donde, a continuación del símbolo V, aparece un predicado que indica el domi-


nio de la variable i, y a continuación de este otro predicado que indica 10 que
es verdadero cuando la variable toma valores en ese dominio.
De forma general, el predicado siguiente puede construirse con los predica-
dos R(i) y P(i):

V R(i) : P(i)

Donde R(i) se utiliza para describir el dominio de i, y el predicado P(i) debe


satisfacer para todos los valores de i pertenecientes a su dominio, para que el
predicado V P(i) : R(i) resultante sea válido -es decir, valga verdadero en todos
los casos-o Este tipo de predicados puede utilizarse para sintetizar conjuncio-
nes como P(i 1 ) 1\ P(i 2 ) 1\ ..., donde i l , i 2 ••. , simbolizan todos los valores de i que
satisfacen R.

Ejemplo 3.24. Cada una de las frases de abajo va seguida de su represen-


tación como predicado, que utiliza el cuantificador universal:

a) Cada elemento e¡ de la lista desde el hasta e21 es distinto de 9.

Vi E{l, 2, oo., 21}: e¡ =1= 9

b) Todos los ej entre em y en son números positivos.

Vj E {m, m + 1, ..., n}: ej > O

c) El punto (a, b) está arriba y a la derecha de cada punto (i, j) de alguna


región rectangular R del plano xy (véase Figura 3.3).

V (j, j) E R: i < a 1\ j < b


Lógica 73

'(a, bl

--r----------- x
Figura 3.3. Representación gráfica del
predicado V (j, j) E R: i < a /\ j < b.

d) En una lista A = (el' e2, 000' en) los elementos hasta elj-ésimo, excluido
él mismo, están colocados en orden creciente (véase Figura 3.4)0

Vi E {l, 2, .. o,j - 2}: e¡ ~ e¡+l

(1 3 5 6 7 9 2 8 4)

I
e,
I
ej

Figura 3.4. Ejemplo de predicado.

e) La proposición P(p, q, r ) es una tautología.

V p, q, r E {verdadero, falso}: P(p, q, r)

Obsérvese que no se realiza ninguna exigencia de. que las proposiciones


sean siempre verdaderas. El apartado e requiere algún comentario adicional:
P(p, q, r) es una proposición; sin embargo, la frase «la proposición P(p, q, r) es
una tautología» puede reescribirse «para todos y cada uno de los estados de
las variables p, q y r, P(p, q, r) es verdadera. Por tanto, con el cuantificador
universal, el aserto P(p, q, r) del apartado e vale verdadero en todos sus ocho
posibles estados.
Hay veces en que el cuantificador del predicado puede omitirse, si está
claro por el contexto -es decir, cuando el conjunto universal ha sido expresa-
do con claridad-o Por ejemplo, si sabemos que i es un entero (como suele
ocurrir frecuentemente), es posible escribir:

Vi:i<i+l
74 Computación l. Lógica, resolución de problemas, algoritmos y programas

De forma similar, siempre es posible escribir

V R(i) : P(i)

como Vi : R(i) ~ P(i). Por ejemplo

(Vi E {l, ..., 2l}: e¡ "1= 9)


es lo mismo que (Vi: 1 ~ i ~ 21 ~ e¡ "1= 9).

El otro cuantificador de interés es el cuantificador existencial, simbolizado


por 3. Su significado es «existe», o bien «existe al menos» o «para algúo». Se
utiliza para abreviar múltiples ocurrencias de disyunciones cuando varios pre-
dicados similares están relacionados con este operador.

Ejemplo 3.25. Las frases siguientes se acompañan con su representación


abreviada utilizando el cuantificador existencial:
a) Existe un elemento de la lista (el' e 2 , ••• , en) que es cero.

3iE {l, ...,n - l}:e¡ = O

b) Los elementos de (el' e 2 , •.•, en) no están todos en orden ascendente.

3j E {l, ..., n - l}: ej + 1 > ej

e) Algún punto del conjunto del plano R, se encuentra en el tercer cua-


drante.
3 (i, j) E R : i < O /\ j < O

d) Existen enteros a b y e, distintos de cero, que verifican a 3 + b3 c3 •

e) Existe alguna fila del tablero que no tiene ninguna pieza.

3 una fila R en el tablero: R no tiene piezas

f) Existe una frase en este texto con al menos una falta de ortografía.

3 una frase S en este texto: S tiene al menos una falta de ortografía.

El cuantificador universal, al igual que el existencial, representa una notación


abreviada. Por ejemplo, 3 i E {l, ..., n}: e¡ = Oes la notación abreviada de (el =
= O) v (e 2 = O) v ... v (en = O). Es decir, al igual que el cuantificador universal
representa una forma abreviada de una secuencia de operaciones and, el cuan-
tificador existencial representa una forma abreviada de secuencias de oro
Lógica 75

La relación entre ambos cuantificadores podem os encont rarla en la


genera-
lización de las leyes de Morga n siguiente:

- V R{i) : P(i) = 3 R{i): - P(i) (3.1)


- 3 R(i) : P(i) = V R(i): - P(i) (3.2)
La Ecuaci ón (3.1) dice que la frase «no es cierto que todos los íes en
el dominio
satisfacen p» es equivalente a «para algún i del dominio, P es falso».
La Ecua-
ción (3.2) tiene una interpr etación similar: la frase «no es cierto que
algún i en
el domin io satisfa te P» es equivalente a la frase «para cada i en el
dominio, P
es falso».
Estas son generalizaciones de la versión simple de las leyes de Morga
n, que
fueron introdu cidas anterio rmente en este capítulo. Téngase en cuenta
que los
cuantificadores utilizados ahora represe ntan abrevi aturas de conjun
ciones y
disyunciones. Por ejemplo, las equivalencias siguientes son interpr
etacio-
nes válidas de las Ecuaciones (3.1) y (3.2), cuando el rango de i es
el conjun-
to {1, 2}:

-(P(l) /\ P(2)) - -P(I) v -P(2) (3.3)


-(P(I) v P(2)) -P(I) /\ -P(2) (3.4)
Ejemplo 3.26. A continu ación, se muestr an dos ejemplos concretos:

a) - 3i E {1, oo., n}: e¡ = O es equivalente a

Vi E {l, .oo, n}: e¡ :f. O.


b) - 3i E {1, oo., 21}: e¡ :f. 9 es equivalente a

Vi E {1, oo., 21}: e¡ = 9.


A menud o son necesarios ambos cuantificadores para expres ar un
único
predicado. Por ejemplo, supong amos que querem os expres ar que,
simbólica-
mente, B = {e'}, eí, e~o} es una permu tación de A = {el' e , •.. , e }.
OO"

2 30
Supon gamos tambié n que todos los a¡ son distintos. Es posible expres
ar todo
lo anterio r como:

perm (B, A) = Vi E {l, oo., 30} 3j E {1, oo., 30}: ei = ej'


Dicho en castellano, B es una permu tación de A, si para cada elemen
to ei de B
existe un elemento idéntico ej en A, aunque no necesariamente en
la misma
posición (es decir, los índices i y j no necesitan ser idénticos para
que e; = ej
sea verdadero).
76 Computación l. Lógica, resolución de problemas, algoritmos y programas

3.3.2. Otros cuantificadores


En informática suelen utilizarse otros cuantificadores junto con los dos ante-
riores, 3 y V. Estos cuantificadores pueden verse a continuación:

Num R(i): P(i)


Mio R(i): P(i)
Max R(i): P(i)
Sum R(i): f(i)
Prod R(i): f(i)

Num R(i): P(i) da el número de valores de i en el rango determinado por R, que


satisfacen el predicado P. Mio devuelve el valor mínimo de la función f(i),
donde i está restringida al rango especificado por R. Max es similar a Mio,
pero devuelve el valor máximo. También se ha visto la función Sum, que se
utilizó en la Sección 2.4 (donde se simbolizó mediante la letra griega L). Prod
es similar a Sum, pero simboliza el producto en lugar de la suma. (En matemá-
ticas suele simbolizarse el producto mediante la letra griega n.)

Ejemplo 3.27. Las expresiones siguientes muestran la utilización de estos


cuantificadores. El valor que resulta de evaluarlas aparece a la derecha:

a) Num i {O, oo., 3}: i 2 > i


E 2
b) Mio i E {O, ..., 3}: i 2 > i 2
e) Max i E {O, oo., 3}: i2 > i 3
d) Sum i E {O, ..., 3}: i 2 - i
e) Prod i E {l, .oo, 4}: i 2 > i °+ °+ 2 + 6 = 8
1 x 4 x 9 x 16 = 576

3.3.3. Variables libres y ligadas


Compárese

con la expresión cuantificada

Vi E {l, oo., 29}: ei+l ~ e;


En la primera expresión, i está irrestringida; puede tomar cualquier valor en el
dominio que deseamos definir. En este caso, i recibe el nombre de variable
libre. Sin embargo, en el segundo caso, la variable i está restringida por el
cuantificador universal a tomar valores en un intervalo específico. En este caso,
se dice que i es una variable ligada.
En general, todas las variables cuyos valores están restringidos a un deter-
minado dominio, por la aplicación de un cuantificador, son ligadas; variables
Lógica 77

que no están ligadas son libres. (Veremos que la misma distinción entre varia-
bles libres y ligadas existe entre variables globales y locales, respectivamente,
cuando definamos procedimientos y funciones en programación.)
Considérese la expresión

Vi E {m, ..., n}: e¡ :::;; K


En esta expresión, i es una variable ligada, mientras que m, n y K son libres.
Suele ser habitual la coexistencia de variables libres y ligadas.

3.4. PREDICADOS V PROGRAMAS

En el Capítulo 5, se utilizarán con frecuencia predicados para discutir sobre


programas y resolución de problemas. La idea de predicado, que define exacta-
mente las condiciones bajo las que debe ejecutarse un programa, es fundamen-
tal para los objetivos descritos en la frase anterior. Es también fundamental la
correspondencia entre predicados cuantificados y bucles de un programa. En
la sección siguiente se introducen ambas ideas.

3.4.1. El estado de un cálculo

Supóngase que en un programa de una computadora se tiene una instrucción


como la siguiente:
; := O

que asigna el valor O a la variable ;. Supóngase también que el programa es


tan simple que sólo utiliza las variables; y j. Es posible utilizar un predicado
para describir el estado del programa después de la ejecución de la instrucción
; := O

Tal predicado recibe el nombre de poscondición de la instrucción ; : = O.


Supóngase que conocemos que, antes de la ejecución de esta instrucción, las
variables cumplen i > j 1\ j > 10. El predicado anterior recibe el nombre de
precondición, debido a que describe el conjunto de todos los estados que pue-
den darse antes de ejecutar la instrucción.

Definición. Una poscondición de una instrucción o grupo de instrucciones


de un programa, es un predicado que describe el conjunto de todos los
estados en que estas instrucciones pueden terminar, después de que sean
ejecutadas. U na precondición para esa instrucción o grupo de instrucciones,
es un predicado que describe el conjunto de estados en los que pueden estar
todas las variables del programa, inmediatamente antes de ejecutarse la
instrucción o grupo de instrucciones.
78 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Podemos pensar en la poscondición como un predicado que describe el


conjunto objetivo o el propósito de nuestro programa. Obsérvese que, si se
considera una instrucción o conjunto de instrucciones como una función, la
precondición representa su dominio, y la poscondición su recorrido. Más ade-
lante, se verá cómo la precondición y la poscondición sirven de ayuda en la
construcción y verificación de programas.

3.4.2. Cuantificadores y programación: bucles

Existe una estrecha correspondencia entre la utilización de cuantificadores en


lógica y la de bucles en programación. Es decir, al igual que los cuantificadores
universal y de existencia permiten en la lógica abreviar una serie de tediosas
secuencias de operaciones /\ y v, los bucles sirven en programación para
abreviar largas secuencias de instrucciones. Tendremos muchas ocasiones de
hacer traducciones entre esas dos formas de expresarse.
Es habitual tener que escribir instrucciones de programas que comprueben
si los predicados cuantificados VR(i): P(i) y 3R(i): P(i) son válidos. Cuando el
rango de la variable i, expresada en R(i) es finito, esta prueba puede realizarse
utilizando un bucle for de Pascal. Supongamos que tenemos la lista A =(el'
e2 , ... , e30 ) Yqueremos conocer si todos los miembros de esta lista son positivos;
es decir, se quiere establecer si el predicado siguiente es válido:

Vi E {l, ..., 30}: ei > O

Una forma posible de comprobar esto sería escribir una larguísima instrucción
condicional, asignando a la variable booleana vá l ; do el valor verdadero o
falso:

i f (e[1]>O and (e[2]>0> and ..• and (e[30]>O>


then válido:=true
eLse válido:=false;

De forma alternativa, tras aplicar la ley de Morgan, podríamos escribir:

válido:=true;
if e[1] <= O
then válido:=false;
if e[2] <= O
then válido:=false;

if e[30] <= O
then válido:=false;

Es decir, el predicado original no es válido, SI es posible que uno o más


elementos de la lista cumplan ei ~ O.
Lógica 79

Para evitar problemas de escritura, en esta y en similares situaciones, es


posible utilizar la instrucción for para expresar la misma idea. Es posible
sustituir la serie de instrucciones «if-then» por el bucle siguiente:

válido:=true;
for i :=1 to 30 do
jf e[i] <= O
then vá l i do :=fa lse;

Sencillamente, se ha utilizado un truco bastante común en programación: se ha


supuesto que el predicado es válido para todos los valores del rango de i, y se
intenta demostrar lo contrario examinando los 30 casos de forma individual.
Puede seguirse una estrategia de programación similar, cuando se imple-
menten predicados como instrucciones, que involucren otros cuantificadores.
Ejemplos de esto pueden verse en los Ejemplos 3.28 y 3.29:

Ejemplo 3.28. El predicado «Existe un número en la lista A = (e l' e2' ... ,


e 30 ) cuyo valor es cero», puede reescribirse como :Ji E {l, ..., 30}: e¡ = O. Un
bucle para probar la validez del predicado sería:

vál ido:=false;
for i :=1 to 30 do
ife[i]=O
then válido:=true;

Ejemplo 3.29. La frase «El número de elementos de la lista A = (el' ez' ...,
e30 ) cuyo valor es cero», puede reescribirse como Num i E {l, ..., 30}: e¡ = O.
El programa siguiente comienza con la suposición de que no existen en A
elementos que cumplan esta condición. Posteriormente, se corrige metódi-
camente la suposición (añadiendo 1 a la variable contador numero), cada
vez que se encuentre un cero en la lista.

número:=O
for i :=1 to 30 do
ife[i]=O
then número:=número+1;

Una discusión más detallada sobre el bucle for, puede encontrarse en el


manual de laboratorio.

Ejercicios

3.18. Evaluar los predicados siguientes, en cada uno de los estados 1 y 11:

(1) i = O 1\ j = 1 1\ k = - 1 (JI) i = - 1 1\ j = 1 1\ k = O
80 Computación l. Lógica, resolución de problemas, algoritmos y programas

a) i< lO/\j< 10
b) -1 < j :::; i
e) i + j > k
d) i3 = / = p

3.19. Evaluar cada uno de los predicados siguientes. Supóngase, en todos los
casos, que el dominio es N.

a) Vi: i < i + 1 h) Vi 3j : i - j = O
b) Vi: i2 < i i) 3i Vj : i - j = O
e) Vi, j : i 2 + / > O j) Mio i E {1, ..., 6} : (i2 - 6i) = 3
d) 3i: i 2 = 5i - 6 k) Vi 3j : i x j = O
e) 3i, j : i2 + j2 :::; 25 l) Sum i E {l, , lO} : i - 5
f) Vi 3j : j = O m) Prod i E {l, , n} : i
g) Max i E {l, ..., lOO} : i + 2 = 101

3.20. Supóngase que se define un tablero de ajedrez como {(i, j)ll :::; i :::;
:::; 8 /\ 1 :::; j :::; 8}. Supongamos que el par (1, 1) define el cuadrado de
la esquina inferior izquierda del tablero, la casilla es negra, y las blancas
ocupan inicialmente las dos filas inferiores del tablero. Sea p una fun-
ción que asigna a cada casilla del tablero el nombre de la pieza que está
sobre ella. Así, si la casilla (4, 7) está vacía, escribiremos p(4, 7) = vacía;
si la casilla (1, 5) contiene el rey blanco, escribiremos p(l, 5) = RB.
Escribir predicados para describir las frases siguientes:
a) La reina blanca ha sido capturada.
b) Las negras conservan sus alfiles.
e) Una torre blanca está en la misma fila que la reina negra.
d) Un peón blanco está atacando al rey blanco.
e) Una torre blanca está atacando a la reina negra.
f) Un alfil negro está atacando al rey negro.

3.21. Identificar las variables libres y ligadas de las expresiones siguientes:


a) Vi E{O, oo., n} : i3 - i2 < 100
b) 3i E N : "In E {1, .oo, i} : n 2 = i
e) (O:::; m :::; p) /\ (Vw E R : w < m /\ w2 < p)

3.22. Considérese la situación del juego de las tres-en-raya descrita en el


dibujo. Supóngase que la notación s(i, j) = v significa que la cuadrícula
de la fila i y la columna j contienen el valor v (donde v puede ser una X,
una O o estar vacía).
a) Escribir un predicado que describa la situación concreta del dibujo.
b) Escribir un predicado que describa el mejor movimiento que pue-
dan realizar las O, a continuación.
Lógica 81

columna
fila 1 2 3

2
xO
3
X
e)
Escribir un predicado que describa todos los estados en que las X
pueden ganar, en la columna de enmedio; es decir, todos los estados en
que las X ocupan dos casillas de la segunda columna y la otra casilla
está vacía.
d) Escribir un predicado cuantificado, que describa la imposibilidad de
que las X ganen en la segunda columna.
e) Escribir otro que describa la imposibilidad de las X de ganar en cada
columna.
f) Escribir un predicado que describa la imposibilidad de que ganen las X.

3.23. Describir las situaciones siguientes, utilizando predicados y cuantificado-


res. Supóngase que A = (el' e2, ..., e,,) es una lista de caracteres ASCII.
a) Todos los caracteres de la lista son idénticos.
b) La letra x no está en la lista.
e) Algún carácter de la lista es la z.
d) La lista está ordenada alfabéticamente, de menor a mayor.
e) No existen en la lista dos caracteres iguales.
f) Algún par de caracteres de la lista son iguales.
g) La letra y aparece exactamente dos veces en la lista.
h) La lista B = (b l , b2 , •.., b,,) es una sublista de A.
i) La variable cont almacena el número de puntos y comas de la lista.
j) La lista B es idéntica a la A.
k) La lista B es idéntica a la A, excepto que algún cero de la lista ha sido
reemplazado por una letra o.
l) El número 3621 no es primo.

3.5. RAZONAMIENTO CON PREDICADOS:


PRUEBA POR INDUCCION

El razonamiento con predicados es similar al razonamiento con proposiciones,


puesto que existen también reglas de inferencia que gobiernan los pasos a seguir.
Frecuentemente, se asignan valores representativos a las variables de un predica-
do (convirtiéndolo en una proposición) y se utilizan las reglas de inferencia para
82 Compu tación ,. Lógica, resoluc ión de problem as, algoritm os
y progra mas

proposiciones. Sin embargo, el razonamiento con predicados implica


algunas
reglas adicionales que se resumen en la Tabla 3.4.
Como sugiere la Tabla 3.4, las reglas de la 3-introducción y de a 3-elimi
na-
ción recuerdan a las leyes de Morga n (véanse Ecuaciones (3.1) y (3.2)).
Las reglas
de la V-introducción y de la V-eliminación también son ampliamente
utilizadas
en las demostraciones. La primera indica que si es posible establecer
que R => P
es válida, es posible afirmar, en un paso posterior de la demostración
, que
también es' válido V R(i): P(i). La segunda indica que si V R(i): P(i)
es válida,
entonces para cada valor particular i de i, también es válida R(io) =>
o P(i o)'

Tabla 3.4. Reglas de inferen cia para predica dos cuantif icados

V-introducción V-eliminación
R-=P V R(i): P(i)
V R(i): P(i) R(i o) -= P(i o)
3-introducción 3-eliminación
V R(i): P(i) 3 R(O: P(i)
~3 R(i): ~P(i) ~ V R(i): ~ P(i)

Supongamos que se quiere demos trar que todos los enteros pares mayore
s
que 2 no son primos. En este caso, R es el predicado:

i no un entero /\ (i > 2) /\ i es par

y P es el predicado:

i no es primo

Es necesario ver que R => P es válido. Esto se suele hacer seleccionando


un io
representativo, que satisfaga R, y demostrando que también satisface
P. Así, la
prueba podría comenzarse:

Sea io cualquier entero par que verifique i > 2.


o
De la suposición anterior concluimos que i no es primo (puesto que para
que un
número sea par, es necesario que sea divisible por 2). Por tanto, conclu
imos que
todos los enteros pares mayores que 2 no son primos.

Prueba por inducción. La validez de predicados que contienen cuantif


icadores,
puede muchas veces establecerse mediante el método conocido por
prueba por
inducción (o inducción). Este es un concepto tan import ante
para la programa-
ción como para las matemáticas. Es decir, los métodos de demostración
inducti-
vos se utilizan en la verificación formal de programas (véase Capítu
lo 6). La
Lógica 83

inducción tiene también una relación muy estrecha con el concepto matemático
de funciones recursivas (como se vio en el Capítulo 2) y una gran aplicación en la
resolución de problemas y en programación (véase Capítulo 5).
Para demostrar la validez del predicado VR(n): P(n) en el caso especial en que
R(n) tiene la forma n E {l, 2, "'}' podemos considerar dos casos por separado.

Oaso base: Demostración de P(l).


Paso de inducción: Demostración de P(i) ::::- P(i + 1) para todo i ~ 1.

El caso base, simplemente establece la validez de P para el valor inicial, i = 1. El


paso de inducción establece que, si podemos demostrar que P(i) ::::- P(i + 1), para
cualquier i, entonces se demuestra la validez de P(l) ::::- P(2) ::::- oo • •Puesto que
P(l) es válido, también lo será P(2) [puesto que P(l) ::::- P(2)] y también lo será
P(3) [puesto que P(2) ::::- P(3)], y así sucesivamente. .
La idea de la inducción puede ilustrarse mediante una analogía. Suponga-
mos que alguien quiere subir una escalinata. Sólo serán necesarias dos fases para
describir el proceso -la fase de alcanzar el primer peldaño y la de subir desde
un peldaño hasta el siguiente-. Utilizando estas dos instrucciones es posible
subir cualquier escalinata, sin tener en cuenta cómo tenemos que situarnos en un
escalón determinado. Además, estas dos acciones hacen irrelevante, para com-
pletar el proceso de inducción, lo larga que sea la escalera. El hecho de alcanzar
el primer escalón es equivalente a probar el caso base P(1). El hecho de saltar del
i-ésimo escalón al i + 1, es análogo a probar el paso de inducción P(i) ~ P(i +
+ 1) para cada i ~ 1. La expresión Vn E {l, 2, oo.}: P(n) establece que P(n) es
verdadero para todo n, y es análogo a la posibiliad de saltar de uno a otro
escalón de todas las escaleras.
Mientras no podamos utilizar una tabla de verdad para justificar el proceso
de inducción, podemos justificarlo utilizando el modus ponens y basándonos en
algunas propiedades de los números naturales. Es decir, si sabemos que P(l) es
verdadero y que Vi E {l, 2, oo.}: P(i) ::::- P(i + 1) es posible escribir las expresiones
siguientes, sin más que sustituir i por diferentes números enteros:

P(l)
P(l) ~ P(2)
P(2) ::::- P(3)

Combinando las dos primeras líneas es posible concluir, utilizando modus po-
nens, que P(2) es válido. Combinando la veracidad de P(2) con la tercera línea, es
de nuevo posible concluir que P(3) es verdadero. Es posible continuar de esta
forma indefinidamente hasta demostrar que Vi E {l, 2, oo.}: P(n) es válido.

Ejemplo 3.30. Supongamos que St; desea probar por inducción que Vn > o:
n n(n + 1)
j~l j = 2 . Consideremos los dos casos:
84 Computación l. Lógica, resolución de problemas, algoritmos y programas

n
n(n + 1)
Caso base. Demostrar que Lj 2
j= 1

Paso de inducción: Demostrar que

" i( i + 1) i ~1 . (i + 1)((i + 1) + 1)
¿j= ~ ¿J
j=l 2 j=l 2

El caso base es evidente si utilizamos la aritmética. Es decir, 1 = 2/2. El paso


de inducción tampoco es muy dificil de probar, si se utilizan las propiedades
de las sumatorias finitas, introducidas en la Sección 2.2.6.

i+ 1 i
¿j L j + (i + 1) Propiedad de L
j= 1 j=l
i
i(i + 1) i(i + 1)
2
+ (i + 1) Suponiendo L j =
2
j= 1

i(i + 1) 2(i + 1)
+ 2 Aritmética
2
(i + 1)((i + 1) + 1)
Aritmética
2

A primera vista, puede parecer que la inducción matemática es una forma circu-
lar de razonamiento. Sin embargo, lo que está realizándose es, en alguna medida,
un «razonamiento hipotético». Lo que decimos es «Supongamos que P(n) es
cierto. ¿Podemos deducir de ello que P(n + 1) también lo es?». Esto no demues-
tra la validez de P(n + 1), sino sólo que P(n) implica P(n + 1). Considérese de
nuevo la analogía con la ascensión de la escalinata. En realidad, lo que estamos
diciendo es «Supongamos que nos encontramos en el n-ésimo escalón, ¿puedo
desde aquí ir al siguiente?». Esto no supone afirmar que sepamos llegar al
n-ésimo escalón, sino que estando en él somos capaces de alcanzar el n + l-ésimo.
Es la combinación de las capacidades de saltar de uno al siguiente, y la de
alcanzar el primer escalón (es decir, probar P(I)), lo que permite alcanzar el n-
ésimo.

Ejemplo 3.31. Demostrar que si O < 1 r < 1, entonces


n 1 - rn+1
¿ ri = - - -
i=O 1 - r

Comencemos con n = O. Los lados izquierdo y derecho (LHS y RHS) de la


ecuación anterior se convierten en:
Lógica 85

LHS rO = 1, para r =1= O


i=O

1 r
RHS = = 1, para r =1= 1
1 - r

E&to completaría la demostración del caso base. Supongamos ahora que


n 1 - rn+1
I ri = ---- (3.5)
i=O 1 - r
Demostraremos que esto implica
n+1 1 _ rn + 2
I
i=O
r
i
=
1 - r

Sumando r n+ 1 a ambos lados de la ecuación (3.5), obtenemos


n+1 n+1
LHS = I ri + rn + 1 = I ri
i=O i=O

1 - rn+1
RHS + r n+1
1 - r
1 - r n+2
1 - r

Ejemplo 3.32. Supongamos que S es un conjunto finito que tiene n ele-


mentos. Demostrar mediante inducción que S tiene 2n subconjuntos.

Solución. Denotemos por P(S) la colección de subconjuntos de S. Supon-


gamos que n = O. Entonces, S = <j>. Por tanto, P(S) = {<j>} y S tiene 1 (= 2°)
subconjuntos. Este es el caso base para la prueba inductiva.
Supongamos ahora que cualquier conjunto S de n elementos tiene 2n
subconjuntos. Para probar el paso de inducción, sea T un conjunto arbitra-
rio con n + 1 elementos.

Los subconjuntos de T pueden dividirse en dos grupos ~aquellos que in-


cluyen a t n + 1 Ylos que no-o Cada subconjunto {t 1 , t 2 , ••• , t n } es también un
subconjunto de T. Por tanto, por hipótesis de la inducción 2n subconjuntos
de T no incluyen a t n + l' Cada subconjunto que incluye t n + 1 puede conside-
rarse como la unión de un subconjunto de {t l ' t 2' ... , tn } con {t n + 1}' Luego
existen 2n subconjuntos que incluyen t n + l' Por tanto, existen 2n + 2n = 2 x
x 2n = 2n + 1 subconjuntos.
86 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Una forma de visualizar la demostración es escribiendo la lista de subcon-


juntos siguiente:

Sin incluir tn + ¡ Incluyendo t n + ¡


4J {tn+d
{td {ti' t n + ¡}
{t 2 } {t 2' t n + ¡}
{ti' .t 2 } {t l' .t 2 , t n + ¡}

{ti' t 2 , oo., t n } {t¡,"t 2 , .•., t n, t n + ¡}

La lista de la izquierda está construida con todos los subconjuntos de {tl' t 2 ,


..., t n ). La lista de la derecha se ha construido haciendo la unión de cada uno
de los elementos de la lista de la derecha con {t n + ¡}. Luego hay exactamente
2n elementos en cada lista.
Obsérvese que, en contradicción con su nombre, la demostración por induc-
ción implica razonamiento deductivo, en lugar de inductivo. El razonamiento
inductivo hace referencia a una técnica habitual en la ciencia, denominada méto-
do científico; este método consiste en inferir principios generales a partir de casos
particulares. Por ejemplo, el astrónomo Kepler utilizó el razonamiento inductivo
para inferir las leyes del movimiento planetario a partir de un voluminoso con-
junto de datos experimentales que había recogido. Sin embargo, la inducción
matemática sigue un típico proceso deductivo -partiendo de axiomas, teoremas
previamente demostrados define nuevos teoremas utilizando reglas de inferencia.

3.6. RESUMEN
En este capítulo hemos estudiado los fundamentos de la lógica, prestando espe-
cial atención a su utilización en la informática. La lógica proposicional, la equi-
valencia y las demostraciones se han revelado como inestimables herramientas
en la resolución de problemas. El cálculo de predicados tiene una utilización
directa en Pascal, en forma de expresiones booleanas. Se han introducido méto-
dos deductivos de demostración, incluida la inducción, útiles para el estudio del
diseño de programas y la verificación, que se verán en capítulos posteriores.

Ejercicios
3.24. Demostrar que la suma de los n primeros números impares es n 2 •

3.25. Supongamos que la función Factorial: N ~ N se define recursivamente de


la forma siguiente:
Factorial (1)
Factorial (n) = n x Factorial (n - 1) para n > 1
Lógica 87

Demostrar por inducción, sobre n, que esta definición es equivalente a la


definición no recursiva:
Factorial (n) = 1 x 2 x ... x (n - 1) x n

3.26. Supongamos que la función f(n) se define mediante la siguiente regla:


f(O) = 1
f(n + 1) = 2f(n) para todo n > O
Demostrar por inducción que f(n) = 2n •

3.27. Demostrar la generalización de la ley de Margan, siguiente:

(Nota: U A¡ significa A o u Al U ... U A n)


;=0

n
(Nota: nA
i=O
i significa A o n Al n ... n A n)

3.28. Verificar las relaciones siguientes utilizando la inducción matemática:


n
n(n + 1)(n + 2)
a) L (i(i + 1»)
3
i=O

L
n
n(n + 1)(2n + 1)
b) ¡2
i=O 6
n
n2 (n + 1?
c) L ¡3
4
i=O
CAPíTULO 4
PROBLEMAS ALGORíTMICOS
V SU SOL UCi ÓN

El conjunto de problemas que son resolubles utilizando una compu tadora


es,
indudablemente, muy extenso. Muchos de estos problemas son de índole
mate-
mática, y requieren la utilización de números y la aplicación de princip
ios
matemáticos. Otros tienen naturaleza gráfica, e implican la manipulación
de
objetos gráficos, tales como puntos, líneas, rectángulos y círculos. El
otro gran
grupo implica la manipulación de textos en lengua castellana y, por
tanto,
implica la manipulación de caracteres alfabéticos y palabras como unidad
es de
información básicas.
Muchos de los problemas que debemos resolver con computadoras, autén-
ticamenteimportantes, fuerzan a la manipulación conjunta de dos o tres
de los
dominios anteriores -mate mática s, gráficos y texto-- . Por ejemplo,
si utiliza-
mos una compu tadora para resolver un problema que simula el juego
de las
tres en raya, no querremos sólo visualizar la secuencia de movimientos
(descri-
bible como texto), sino que queremos ver en qué situación se encuen
tra el
tablero en cada momento (descripción gráfica). Un estudio cuidadoso
de pro-
blemas algoritmicos debe incluir elementos de esos tres dominios,
bien por
separado, bien en combinación con otro.
Existe una ventaja adicional si se estudian los problemas computaciona
les
de las matemáticas, los gráficos y los textos de forma conjunta: mucho
s de los
principios que se aprenden al estudiar una de estas áreas, por ejempl
o las
matemáticas, son también de aplicación en las demás. Esto sugiere la existen
cia
de una serie subyacente y única de principios, que gobiernan el compo
rtamien-
to de computadoras y programas. De hecho, estos principios dirigen
nuestra
forma de aborda r un problema cuando tratamos de resolverlo con una
compu-
tadora. Este conjunto de principios recibe el nombre de resolución de problem
as
algorítmicos. En este capitulo se introducen las ideas1undamentales
de la reso-
lución de problemas algorítmicos, y se presentan una gran variedad de
proble-
mas matemáticos, gráficos y de procesamiento de textos, que ilustra
n estas
ideas.
Compu tación /. Lógica, resoluc ión de problem as, algoritm os
y program as
90

4.1. ALGORITMOS Y PROBLEMAS


micos y
Los conceptos de proble mas algorítmicos, algoritmos, lenguajes algorít
para la activid ad de resolver
compo rtamie nto algorítmico son fundamentales
para la discipl ina de la inform ática.
proble mas con compu tadora s y, por tanto,
un conoci miento firme sobre lo que es
f En consecuencia, necesitamos adquir ir
y lo que es un proble ma algorít mico, con
un algorit mo (y sobre lo que no es),
mas. La
objeto de llevar a cabo de forma eficaz la activid ad de resolver proble
definición siguiente sirve como punto de partida para esta discusi ón.

n una
Definición. Un algoritmo es una lista de instrucciones que realiza
de un proces o que garant iza que resuelve
descripción paso a paso y precisa
pertene zca a un tipo determ inado, y que termin a
cualqu ier proble ma que
después de que se hayan llevado a cabo un númer o finito de pasos.
mas, o
Los algoritmos se escriben o diseñan con el propós ito de resolver proble
más exactamente, proble mas algorít micos.

tual o
Definición. Un problema algorítmico es cualquier problema, concep
práctico, cúya solución puede expres arse median te un algorit mo.

tanto den-
En la vida cotidia na encont ramos muchos proble mas algoritmicos,
especia lizado de la inform ática. Por ejem-
tro como fuera del campo altame nte
prepar ar pollo a la cazado ra constit uye un
plo, una determ inada receta para
ma genera l de prepar ar pollo a la cazado ra es
algoritmo, mientras que el proble
o. En la Tabla 4.1 se presen tan alguno s ejem-
el proble ma algoiítmico asociad
plos comunes de algoritmos y de proble mas algorítmicos.

Tabla 4.1. Alguno s algorit mos y proble mas algorít micos

Algoritmo Problem a algorítmico

Conjun to de instrucciones para tejer un jersey Tejer un jersey.


Un itinerario particu lar para recorre r el mundo Dar la vuelta al mundo.
Un plan de trabajo para cursar una licenciatura Cursar una licenciatura.

existen
Para cada proble ma algorítmico como «cursar una licenciatura»,
utilizar se como solució n. Por ejem-
varios algoritmos alternativos que pueden
ingenie ría en inform ática, seguirá un
plo, un alumn o que desee cursar una m-
en el que desee licenci arse en ciencia s medioa
algoritmo muy diferente que
bientales o en filosofía.
alcs
En cualquier caso, los algoritmos de la Tabla 4.1 exhiben las princip
ación
características señaladas en la definición: exactitud, efectividad y termin
tarse
garantizada. Cuand o se ejecutan determ inados algoritmos pueden comple
llevar mucho
en un pequeño intervalo de tiempo, mientras que otroS' pueden
Problemas algorítmicos y su solución 91

tiempo. Sin embargo, la ejecución de todos los algoritmos debe terminar. Por
ello, cualquier descripción paso a paso de un proceso que no termine, no es un
algoritmo. Por ejemplo, el proceso de escribir todos los números enteros posi-
tivos, uno a uno, no es un algoritmo, puesto que no terminaría nunca.
En la informática se asocia la noción de algoritmo con la de un proceso
que debe ser ejecutado por una computadora, en lugar de por una persona. En
princjpio, cualquier algoritmo que diseñemos para una computadora puede ser
realizado a mano (suponiendo que disponemos del tiempo necesario, una piza-
rra o suficiente papel). En realidad, encargamos a la computadora la ejecución
de los pasos que componen un algoritmo, porque es capaz de completarlo en
un tiempo mucho menor del que nosotros emplearíamos, y porque es menos
proclive a cometer errores que nosotros.
Sin embargo, no todos los algoritmos pueden ser ejecutados por computa-
doras. Las computadoras sólo pueden ejecutar algoritmos que se componen de
acciones individuales que pueden entender y realizar. Por ejemplo, la prepara-
ción de pollo a la cazadora implica acciones como «encender el horno» o
«deshuesar el pollo», tareas para las que una computadora está bastante mal
preparada. Por tanto, es necesario conocer bien cuáles son las tareas que puede
realizar una computadora, de forma que diseñemos algoritmos que contengan
sólo ese tipo de tareas.
Como punto de partida, considérese el sencillo modelo computacional de la
Figura 4.1.

ENTRADA I----~.I PROCESO ----~.I SALIDA

Figura 4.1. El modelo de computación entrada-procesa-salida.

En este modelo, un proceso de cálculo se compone de tres partes: una entrada,


un proceso y una salida. La entrada la constituyen un conjunto de informacio-
nes que necesitan los pasos de que se compone el algoritmo para llevar a cabo
la tarea; el proceso contiene (una descripción de) los pasos del algoritmo; final-
mente, la salida la constituye el resultado que se obtiene ejecutando los pasos,
con los datos de entrada.
Por ejemplo, el procedimiento para calcular la calificación media (CM) es
un algoritmo. En este caso, la entrada puede ser cualquier lista de valores
numéricos dentro del rango Oa 4. (O = suspenso, 1 = aprobado, 2 = notable,
3 = sobresaliente, y 4 = matrícula de honor); la salida será la media de esos
valores (es decir, el cociente entre la suma de todos y el número de valores); yel
proceso es (una descripción de) el conjunto de pasos individuales que deben
seguirse para obtener el resultado. Ese conjunto de pasos puede resumirse en
un lenguaje-castellano descriptor de procesos, en la forma siguiente:
92 Computación l. Lógica, resolución de problemas, algoritmos y programas

Paso 1. Obtener la lista de calificaciones.


Paso 2. Calcular n = número de notas de la lista.
Paso 3. Calcular Sum = la suma de las calificaciones de la lista.
Paso 4. Calcular CM = Sum¡n.
Paso 5. Mostrar CM.
Cuando se escriben este tipo de descripciones de procesos, se realizan algu-
nas' suposiciones tácitas sobre el comportamiento del modelo entrada-proceso-
salida de la Figura 4.1. La primera hipótesis es que el número de calificaciones
es indeterminado, aunque nunca infinito. La segunda hipótesis es que para
poder calcular cualquier cosa sobre las calificaciones, el algoritmo necesita
obtenerlas primero ---es decir, trasladarlas fisicamente a la parte proceso del
modelo, desde la parte entrada del mismo.
Cuando se utiliza una computadora en la resolución de este problema
algorítmico, las instrucciones que contienen los pasos individuales, junto con
los valores numéricos de Sum, n y CM se almacenan fisicamente en un lugar
llamado memoria, que se encuentra unido a la unidad central de procesamiento
(CPU o procesador) de la computadora. Toda la información de entrada o
salida, se encuentra físicamente separada del procesador en dispositivos como
el teclado, el monitor o un archivo en disquete. Un diagrama de las unidades
de una computadora que se corresponde con el modelo entrada-proceso-salida
puede verse en la Figura 4.2.

Procesador/
[ Teclado/Ratón
I
- - - - '--_M_em_o_ri_a_--..JI--- Pantalla de monitor

Figura 4.2. Computadora correspondiente al modelo.

La tercera suposición que realizaremos es que los pasos de la descripción


del proceso se realizarán en el orden en que están escritos. Por ejemplo, difícil-
mente podríamos realizar el Paso 5 sin haber realizado antes los pasos 1 al 4,
además de ser improbable que hubiéramos podido calcular la CM.
Nuestra cuarta suposición es que cada uno de los pasos es eficaz ---es decir,
que contiene sólo acciones del repertorio de las realizables por la computado-
ra-. Por ejemplo, hemos asumido que la orden «Mostrar» (Paso 5) está en el
repertorio de instrucciones de la computadora. Por tanto, siempre que se eje-
cute alguna instrucción que contenga la palabra «Mostrar», la información de
salida aparecerá en el monitor, independientemente de cuál sea el paso que la
contiene. (En este caso, mostrar aparece muy tarde. No existe ninguna restric-
ción del número de veces que mostrar puede estar en un proceso.)
Las cuatro suposiciones son ciertas, de hecho, para todos los algoritmos
que construimos para las computadoras. Las computadoras ejecutan las ins-
trucciones de una forma obediente, comenzando por la primera y ejecutando
sólo aquellas que le son inteligibles; es decir, instrucciones que describen tareas
dentro de su propio repertorio.
Problemas algorítmicos y su solución 93

Ejercicio

4.1. Describir un problema algorítmico que nos encontremos en la vida dia-


ria, similar a los discutidos en la Sección 4.1. Dar dos algoritmos alterna-
tivos para solucionar el problema.

,
4.2. DEFINICiÓN DE PROBLEMAS Y DESCRIPCiÓN
DE ALGORITMOS

En la resolución de problemas algorítmicos, el nivel de precisión -tanto en la


descripción del propio problema como en la solución algorítmica al mismo-
debe ser detallada y rigurosa. Por ejemplo, nuestra descripción del algoritmo
de CM, de la Sección 4.1, puede no ser lo suficientemente rigurosa para incor-
porarlo a una computadora. Es necesario un mayor nivel de detalle por dos
razones fundamentales:
• El problema, sus limitaciones y su solución deben describirse de forma
clara para el lector humano.
• La solución debe resolver completamente el problema -es decir, debe
aportar a la computadora toda la información necesaria para que pro-
duzca una salida correcta para todas las posibles variaciones de la en-
trada.
En el problema CM, la impresión en el proceso de descripción deja sin respon-
der la pregunta de cuántas puntuaciones constituirán la entrada. ¿Existe un
número máximo de puntuaciones? ¿Es posible suponer que siempre habrá un
número de puntuaciones distinto de cero? Si no está permitida una entrada
consistente en cero puntuaciones, esto debería expresarse explícitamente. Sin
embargo, si se permite una entrada de cero puntuaciones, es necesario modifi-
car la descripción del proceso, de forma que evitemos la división por cero
(Paso 4), y que sea posible escribir el mensaje adecuado.
Esta breve discusión enfatiza sobre la necesidad de encontrar una manera
más sintética y formal de describir un problema algorítmico y sus soluciones.
En el resto de esta sección se introducen los rudimentos de un lenguaje de
descripción de algoritmos. En la Sección 4.3 se introducirán los rudimentos de
un lenguaje de descripción de soluciones a problemas, y se demostrará que estos
dos tipos de lenguajes están relacionados mutuamente.

4.2.1. Los estados inicial y final de un algoritmo:


entrada y salida

Para formalizar el modelo de cálculo entrada-procesa-salida, introduciremos


primero la noción de estado de un algoritmo. Intuitivamente, podemos enten-
der la idea de «estado» de un algoritmo como una especie de instantánea que
describe cómo están las cosas inmediatamente después (o antes) de que se haya
94 Computación l. Lógica, resolución de problemas, algoritmos y programas

ejecutado un paso de un algoritmo. Por ejemplo, el estado del proceso descrito


en la Sección 4.1 podria ser una instantánea de sus entradas, sus salidas y cada
una de sus variables Sum, n y CM en el instante particular en que cada uno de
los pasos, del 1 al 5, ha sido ejecutado.
Existen dos pasos de interés especial: el estado inicial y el fina!. El estado
inicial lo constituye una descripción de la entrada, antes de que se ejecute el
primer paso del algoritmo y, por tanto, recibe el nombre de precondieión del
algoritmo (o pre). El estado final es una descripción de las entradas y las salidas
después de que se haya ejecutado el algoritmo, y reciben el nombre de poseon-
dición del algoritmo (o post). Ambas suelen escribirse utilizando la notación
convencional de la lógica, introducida en el Capitulo 3:

{pre: ent rada = una expresión que describe todas las posibles entradas
del problema}
{post ent rada =0/\ sa l i da = una descripción de todas las salidas que
pueden darse para cada entrada}

Si denotamos por Notas! la primera calificación de la lista, por Notas z la


segunda, y así sucesivamente, la descripción siguiente es una descripción preci-
sa del problema de la calificación media, en forma de precondición y poscondi-
ción:

{pre: entrada = (Notas" Notas" .. "' Notas,> /\ n> O /\


\ti E {1, .•. , n} :Notas, E (O, ... , 4))
{pos t: ent rada = 0 /\
salida = Sum i E {1, ... , n}:Notas,ln}

Obsérvese cómo se utiliza en la precondición y la poscondición el lenguaje de


la lógica de predicados, estudiado en el Capítulo 3. Cuando utilizamos la
lógica de predicados de esta forma, se dice que realizamos asertos acerca de los
estados inicial y final del proceso.
El lenguaje de la lógica permite, en la expresión del estado de un cálculo,
una concisión y precisión que no podemos alcanzar con el lenguaje castellano.
En el ejemplo, el estilo de descripción no deja duda sobre la exclusión del caso
de que existan cero entradas en el problema de la CM. Por tanto, la solución
algorítmica a este problema no tiene que ocuparse de este caso.
El haber escríto entrada = 0 en la poscondición significa que, al final del
proceso, la entrada estará vacía. Es decir, todas las entradas habrán sido leídas
por el proceso, pero no pueden ser leídas de nuevo. Esta peculiaridad de los
estados inícial y final reflejan una forma peculiar de procesar la entrada y la
salida en una ejecución del algoritmo. Es decir, el acto de obtener las entradas
consiste en una serie de pasos discretos, en los cuales: 1) un valor no puede ser
obtenido de nuevo después de haberse obtenido una vez; y 2) los valores
individuales de la entrada se obtienen exactamente en el orden en el que se
introducen por el teclado.
Problemas algorítmicos y su solución 95

Con objeto de enfatizar la estrecha relación existente entre la especificación


de un algoritmo y la descripción del proceso correspondiente, encerramos la
descripción del proceso entre la precondición y la poscondición, como se
muestra en la Figura 4.3.

(pre: entrada = <Notas" Notas" .. "' Notas") 1\ n> O


1\ \fi,E (1, ... , n) :Notas, E (O, ... ,4)) }
Paso 1. Obtener La Lista de caLificaciones
Paso 2. CaLcuLar n = número de notas de La Lista \ Descrip-
Paso 3. CaLcuLar Sum= La suma de Las caLificaciones de La Lista \ ción del
Paso 4. CaLcuLar CM ~ Sum/n J proceso
Paso 5. Mostrar CM

{post: entrada ~01\


saL ida = Sum i E (1, ... , n} :Notas,ln}

Especificaciones

Figura 4.3. Unificación de la descripción de un proceso y su especificación.

4.2.2. los estados intermedios de un cálculo:


introducción de variables

Los estados intermedios resultantes de cada paso intermedio de un cálculo


pueden describirse también mediante asertos. Contrariamente al caso de la
precondición y la poscondición, en este caso es necesario considerar valores
intermedios que es necesario calcular para obtener los resultados finales, así
como los cambios que se producen en las propias entradas al incorporar valo-
res individuales al proceso de cálculo. Por ejemplo, en el algoritmo de CM, los
valores intermedios se identifican por n (el número de calificaciones); Sum (la
suma de calificaciones individuales, Notas;, donde i = 1, oo., n); y CM (el prome-
dio de notas resultante).
Estos valores intermedios se identifican simbólicamente, puesto que es im-
posible predecirlos previamente a una ejecución concreta del algoritmo. Por
ejemplo, una ejecución puede calcular el promedio de tres calificaciones (y por
tanto, n = 3), Y otra diferente puede calcular el promedio de 24 puntuaciones
(n = 24). La misma situación es esperable para las propias puntuaciones o
para su suma, para diferentes ejecuciones. Por ello, capturamos esta noción de
valor variable de la suma utilizando un nombre apropiado, como Sumo

Definición. Una variable es un nombre simbólico que se asocia con un


valor o serie de valores particulares, durante una ejecución concreta de un
algoritmo, pero cuyo valor no puede predeterminarse en el momento en
que se construye el proceso del algoritmo.
96 Computación l. Lógica, resolución de problemas, algoritmos y programas

Por tanto, n, Sum y CM son variables del problema de la CM.


Podemos utilizar variables siempre que deseemos describir un estado inter-
medio de un cálculo. Por ejemplo, es posible describir el aserto:

{ent rada ~ 0/\ Sum = Sum i E {1, .•. , n l : Notas;

Para describir el estado de los cálculos, después de que se hayan completado


los pasos 1, 2 Y 3. Esto es una abreviatura de «Se ha obtenido la entrada, el
tamaño de la entrada (n) ha sido determinado, y se ha calculado la suma de los
n valores de entrada y se ha identificado este valor con la variable Sum».
Con objeto de localizar con precisión dentro del proceso dónde es cierto el
aserto, lo colocamos justo inmediatamente detrás del paso del algoritmo que lo
hace verdadero. Una versión aumentada de la descripción del algoritmo del
proceso CM podria ser la siguiente:

Paso 1. Obtener la lista de cal i fi caciones


Paso 2. Calcular n ~ número de notas de la lista
Paso 3. Calcular Sum = la suma de las calificaciones de la lista

{entrada =0/\ Sum = Sum i E {1, ..• , n} :Notas;}

Paso 4. Calcular CM = Sum/n


Paso 5. Mostrar CM

En general, se puede colocar un aserto entre dos pasos cualquiera de un cálcu-


lo, y éste puede decir mucho o poco sobre el estado de los cálculos en ese
momento del cálculo. Lógicamente, si se colocan asertos antes del primer paso
y después del último, ambos deben ser consistentes con la precondición y la
poscondición, respectivamente. Por tanto, es posible colocar junto con la pre-
condición y la poscondición un conjunto completo de asertos, con objeto de
clarificar la descripción de los pasos de un algoritmo, tal y como se muestra en
la Figura 4.4.
En la práctica, el nivel de detalle que se muestra en la Figura 4.4 en los
asertos intermedios es excesivo, puesto que la mayoría de la información puede
deducirse de un aserto próximo. Es decir, existe mucha información redundan-
te en esta descripción, lo que la hace inútil. Por ejemplo, cada aparición de
entrada = 0 en los asertos después de los pasos del 2 al 4, puede eliminarse,
puesto que es redundante con la información que aparece en el aserto de detrás
del paso 1. De forma más general, cualquier aserto intermedio puede eliminar-
se si su contenido es directamente deducible de un aserto vecino.
Una especificación, descripción de proceso y anotaciones intermedias más
útiles se muestran en la Figura 4.5. El propósito para el que se utilizan los
asertos intermedios para ilustrar los pasos de un proceso es doble. Primero, los
asertos aportan información para cualquiera que necesite comprender el efecto
de los pasos individuales del proceso de cálculo. Segundo, son un vehículo
eficaz en el proceso de verificación de que ese conjunto de pasos resuelve
verdaderamente el problema original que se planteó en forma de precondición
Problemas algorítmicos y su solución 97

{pre: entrada = (Notas" Notas., ••. , Notas,) /\ n > O


/\ Vi E {1, ... , n} :Notas; E {O, " .. , 4}}
Paso 1. Obtener la lista de calificaciones
{entrada = 0} - - - - - - - - - - - - - - - - - - - - - - - - - - - ,
Paso 2. Calcular n ~ número de notas de la lista
{entrada =0/\ n > O}
Paso 3. Calcular Sum = la suma de las calificaciones de la lista
{entrada =0/\ Sum = Sum i E {1, ... , n} :Notas;}
Paso 4. Calcular CM = Sum/n
(entrada =0/\ Sum = Sum i E (1, .. "' n) :Notas, /\ CM = Sum/n} _
Paso 5. Mostrar CM
{post: entrada =0/\
sal ida = Sum i E {1, " .. , n} :Notas,!n}

A s e r los i n I e rme dio S _--'-----'-.-JL-'

Figura 4.4. Introducción de asertos intermedios en la descripción


del algoritmo del problema CM.

y poscondición. Obsérvese que existe una estrecha correspondencia entre esta


forma de describir los pasos de un algoritmo y el modelo original de entrada-
proceso-salida descrito en la Figura 4.1. Es decir, la precondición se correspon-
de con la entrada, el paso del algoritmo describe el proceso, y la poscondición
caracteriza la salida.
Ahora que podemos describir con más precisión las transiciones entre esta-
dos en un proceso algorítmico, en la Sección 4.3 y posteriores trataremos de
conseguir un nivel de precisión comparable en el proceso de descripción de los
pasos de que consta el propio algoritmo.

(pre: entrada = (Notas" Notas., , Notas,) /\ n > O


/\ Vi E (1, .. "' n) :Notas, E {O, , 4}}
Paso 1. Obtener la lista de calificaciones
Paso 2. Calcular n = número de notas de la lista
(entrada =0/\ n > O)
Paso 3. Calcular Sum = la suma de las calificaciones de la lista
Paso 4. Calcular CM = Sum/n
(entrada =0/\ Sum ~ Sum i E (1, ... , n) :Notas; /\ CM = Sum/n}
Paso 5. Mostrar CM
(pos t: ent rada = 0 /\
salida = Su.. i E (1, ... , n) :Notas,!n}

Figura 4.5. Una versión del problema del cálculo del promedio
anotada parcialmente.

Ejercicios

4.2. Describir en castellano el conjunto de pasos de un proceso algorítmico


que calcule y muestre el conjunto de calificaciones que están por encima
Compu tación l. Lógica, resoluc ión de problem as, algoritm os
y program as
98

este
del prome dio para una lista de calificaciones arbitra ria. Considérese
una extens ión del proble ma CM. Ser muy genera l al
proble ma como
describir los pasos del proceso. ¿Que variable adicional es necesa ria?
Ejercicio
4.3. ¿Cuáles serían la precondición y poscondición del proceso del
4.2? Definir una lista sencilla y la salida corresp ondien te que daría el
algorit mo constru ido.

4.3. LENGUAJE ALGORíTMICO


descripción
El conjun to de pasos individuales que, combinados, forman la
estilo lingüístico
comple ta de un proceso o algoritmo, deben escribirse con un
un medio inadec uado
muy preciso. El castellano, por su propia natura leza, es
de cinco pasos, mostra do
para conseguir la precisión adecuada. El algorit mo
es demas iado vago para
en la Sección 4.2 para calcular la calificación media,
ser útil.
en la
El tipo de lenguajes que tienen la suficiente precisión para ser útiles
nombr e de lenguaj es algorít micos o len-
descripción de algoritmos, reciben el
jes de progra mación difieren del castell ano
guajes de programación. Los lengua
en tres puntos esenciales:
s
1. Los lenguajes de progra mación tienen un vocabu lario y una sintaxi
mas sólo pueden describ ir algorit -
muy limitados. Por tanto, los progra
describ ir otros tipos (no algorít micos) de
mos, y son inadecuados para
prosa.
s
2. El vocabu lario de un lenguaje de progra mación contiene sólo aquello
tipos de acciones básicas que una compu tadora puede entend er y reali-
las
zar, y no otras. Por ejemplo, un lenguaje de progra mación soport a
operaciones aritméticas habituales (suma, resta, multiplicació n, divi-
sión, comparación); acciones propia s del procesamiento de texto, accio-
Las
nes de procesamiento de gráficos y acciones de entrad a/salid a.
acciones que una compu tadora no puede realiza r son mucha s y varia-
das -corre r, sacar de banda, recibir, sentir, hacer quiche y crear pintu-
ras al óleo- son algunas de las acciones que no se encuen tran dentro
de vocabulario de una compu tadora .
permite
3. La sintaxis de un lenguaje de progra mación es muy rígida, y no
muchas variaciones de estilo. Por ejempl o, el cálculo del cocien te entre
Sum y n se expresa como Sum/n y no existe forma alterna tiva.
que
La descripción de un proceso o algoritmo en un lenguaje de progra mación
e de progra ma. Los progra mas son ejecu-
difiere del castellano recibe el nombr
en castella-
tables por las compu tadora s, mientras que los algoritmos descritos
de progra mación están diseñad os para parece rse al
no no. (Algunos lenguajes
idioma.
inglés, por lo que los progra mas escritos en él parece n estarlo en este
expres ión
El COBO L es uno de estos lenguajes. Sin embargo, la capaci dad de
mación .)
en inglés no es posible alcanz arla en ningún lenguaje de progra
Problemas algorítmicos y su solución 99

Existen varios lenguajes de programación entre los que podemos elegir


para describir algoritmos. Algunos están diseñados especialmente para la ense-
ñanza y el aprendizaje de los algoritmos, las computadoras y la resolución de
problemas algorítmicos. Pascal es un lenguaje de este tipo, y lo utilizaremos en
los ejemplos que pondremos a lo largo de este texto. Otros lenguajes de pro-
gramación populares son C, Modula-2, Ada, FORTRAN, COBOL, LISP,
ScheQ1e y ensambladores a nivel máquina. Introduciremos los lenguajes en-
sambladores en el Capítulo 7. Algunos de los lenguajes mencionados antes son
bien conocidos por los programadores profesionales (como FORTRAN y CO-
BOL), pero no son adecuados para el aprendizaje y la enseñanza del proceso
de resolución de problemas algorítmicos.
Hasta el momento, nos hemos familiarizado con la sintaxis del Pascal,
gracias al manual de laboratorio adjunto. Por ello, no es difícil comprender
que el programa de la Figura 4.6 es una implementación en este lenguaje del
problema CM, que habiamos descrito informalmente en castellano.
program caLcuLaCM;
{Esto es un programa que caLcuLa La CM de una serie de una o más
caLificaciones numéricas, cada una de eLLas en eL rango O.. 4,
que se introducirán por eL teclado}
uses
Listas;
val'
Notas: Li stas;
i,n:integer;
Sum, CM: rea L;
begin
{pre: entrada = (Notas" Notas" .. "' Notas") 1\ n> O
1\ \1 i E {1, ... , n} : No t a s; E {O, ... , 4} }

(Paso 1. Obtenemos La Lis ta de ca Lif i cac iones}


WriteLn ('Introducir La Lista de calificaciones: ');
ReadLista (Notas);

{Paso 2. CalcuLar n o número de caLificaciones en La lista)


n := LongL i sta(Notas);
i f n > O then
begin
{entrada ~01\ n > O}
{Paso 3. CalcuLamos Sum = La suma de Las caLificaciones de la Lista}
Sum := O:
i := 1 ;
whi le i <= n do
begin
Sum :~ Sum + Notas[i];
i :~ i +1
end
end;

{Paso 4. CaLcuLamos CM = Sum/n)


CM = Sum/n;
(Sum ~ SUII iE (1, ... , n) :Notas; 1\ CM = Sum/n}
100 Computación l. Lógica, resolución de problemas, algoritmos y programas

{Paso 5. Se muestra CM}


WriteLn( 'La CM de esas calificaciones es =', CM : 5 : 2)
end [if]
{post entrada =01\
salida ~ SUIl i E {1, ... , n}:Notas,ln}
end. {CalculaCM}

Figurp 4.6. Programa Pascal que se corresponde con la descripción


de proceso de la Figura 4.5.

Los cinco pasos originales de la descripción del proceso se incluyen aqui


como comentarios entre corchetes, de forma que la correspondencia entre estos
casos y el grupo de instrucciones Pascal sea fácilmente identificable. La pre-
condición, la poscondición y los asertos intermedios también se ponen de
relieve mediante comentarios entre corchetes. Puesto que se han escrito como
comentarios de Pascal, el programa puede ser ejecutado en una computadora
directamente, sin necesidad de hacer ninguna modificación.
El programa introduce algunos elementos nuevos que no se han tratado en
el manual de laboratorio. Los introduciremos en la sección siguiente (para una
descripción más detallada, véase el manual de laboratorio). Para el propósito
de la discusión que nos ocupa, pondremos un énfasis especial en la correspon-
dencia entre el lenguaje de programación (como vehiculo para describir de
forma precisa el proceso algorítmico) y la especificación (como vehiculo para
describir de forma precisa el problema lingüístico a resolver).

4.3.1. Sintaxis y semántica


El conjunto de instrucciones Pascal de la Figura 4.6 representa una implemen-
tación del problema CM, en un lenguaje de programación concreto. El signifi-
cado de cada instrucción está definido de forma estricta, y trata de expresar de
una forma más rigurosa el conjunto de pasos que componen el algoritmo,
originalmente expresado en castellano. Por ejemplo, la frase

Obtenemos la lista de calificaciones

que constituye el primer paso de la descripción de la Figura 4.3, se convierte en


dos instrucciones de Pascal:

WriteLn ('Introducir la lista de calificaciones:');


ReadLista (Notas);

La instrucción Wr; te Ln indica al usuario qué tipo de entrada se pretende


obtener, y la instrucción ReadL; sta cumple la tarea de conseguir la lista de
enteros.

Listas. Este programa hace us\> del tipo L; s ta y sus operaciones relaciona-
das, que se resumen en la Figura 4.7. Los programadores pueden utilizar el
Problemas algorítmicos y su solución 101

B~
Entrada
RadLi sta
;::1:7.: :~"g3l
,,----..@
e List0 InserLista
DeleteLista

) oo.b.. " " , ["di ,,]

¡salida I GV
Figura 4.7. Resumen de las operaciones de Lista.

tipo Lis ta y sus operaciones, sin más que hacer la declaración uses Lis ta;
después de la cabecera del programa. (Las definiciones están ocultas al progra-
ma, aunque su utilización se ha descrito cuidadosamente en el manual de
laboratorio.)
Un programa Pascal que incluye la unidad Lista está capacitado para
obtener, procesar y escribir listas completas de enteros, en lugar de trabajar con
un número cada vez. Esto supone una ayuda inestimable para la resolución de
muchos problemas algorítmicos, y la utilizaremos extensivamente en este texto.
Una variable que se declare como de tipo Lista, contendrá una lista de
valores completa, como ocurre con la variable Notas declarada en el proble-
ma CM, con el objetivo de almacenar una lista de calificaciones completa:

var Notas: Li sta;

El procedimiento ReadL i sta (Notas) obtiene del dispositivo de entra-


da una lista de enteros, suponiendo que la lista está encerrada entre paréntesis,
y se asigna su valor a la variable Notas. Así, por ejemplo, si se teclea la
entrada:

(3 2 1 3)

El procedimiento ReadL i st (Notas) dejará la variable Notas en el estado

Notas = (3 213)

La función LenghtLista (Notas) devuelve el número de elementos


que componen la lista, mientras que la expresión Nota s [i ] devuelve el valor
del i-ésimo elemento de la lista Notas. El entero i recibe el nombre de índice
Compu tación l. Lógica, resoluc ión de problem as, algoritm os
y progra mas
102

que Leng-
del elemento devuelto. Así, por ejemplo, un caso posible podría ser
va el valor integ er 4, y Notas [3] devuelva el
htLis ta (Nota s) devuel
Lis ta
valor real 1. Una díscusión más comple ta de las operacíones sobre
torio. Es
mostra das en la Fígura 4.7, puede encont rarse en el manua l de labora
utilicen
conveniente repasa r esta discusión antes de constru ir progra mas que
listas.

asertos
Limitaciones del conjunto de caracteres ASCn. Cuand o se incorp oran
es necesa rio record ar las
y otros comentarios dentro del texto de un progra ma,
que es el estánd ar de los carac-
limitaciones del conjun to de caracteres ASCn,
ctores de
teres que se pueden codificar y que utilizan la mayor ía de los constru
de Americ an Standa rd Code for Infor-
compu tadora s. (ASCII es un acróni mo
conjun to de caracte res estánd ar, es posi-
mation lnterchange)*. Utilizando un
entre compu tadora s diferentes. (Como entre
ble la transferencia de información
res ASCII
Macin tosh y PCs de IBM, por ejemplo). La lista comple ta de caracte
y su. codifícación se incluye en el Apénd ice A.
les,
Los científicos suelen utilizar letras gríegas y otros símbolos especia
áticas o al
subíndices, superíndices, al escribir expresiones lógicas y matem
escribir asertos. En el proble ma de CM se utilizaron, por ejemplo, No t a Si' 0
los Capí-
y ~ estilo matemático, contin uando con la notació n convencional de
aparec er dentro de
tulos 2 y 3. Desgraciadamente, esos caracteres no pueden
conjun to de caracte res
un progra ma Pascal, por no estar incluidos dentro del
ma Pascal,
ASCII. Por ello, cuando convertimos un algorit mo en un progra
la sustitu ción de los símbolos
utilizamos un convenio de sustitución ASCn para
más comunes, como el de la Tabla 4.2.

Tabla 4.2. Sustitu tos ASCII para los símbol os matem áticos
Sustituto
Símbolo
Significado ASCII
matemátíco

Selección del i-ésimo elemento de la lista Notas [;]


Nota Si vacío
o Conjun to vacío
Sumato ria Sum
LO Sum ~
~ Menor o igual
Cuantificador universal para todo
V
Cuantificador existencial exíste
3
Á
Á Conjunción
Disyunción or
V
Pertenencia a conjunt o ín
E

bio de Información.
• N. del T.: Estánda r Americano de Codificación para el Intercam
Problemas algorítmicos y su solución 103

Utilizando las equivalencia de la tabla anterior, algunas de las expresiones


matemáticas de la Figura 4.6, podrían escribirse utilizando sustitutos ASCII
aceptables de la forma siguiente:

entrada =01\ n > O entrada = vac;o 1\ n > O


8u.; E (1, ... , n): Notas, 8um; ;n {1, .. "' n}: Notas[i]

4.3.2. Repetición y bucles: inicialización, invarianza


y terminación

Un nuevo e importante concepto que encontramos en la solución al problema


CM es el de bucle.

Definición. Un bucle es una secuencia de instrucciones que pueden ejecu-


tarse O, I ó un número finito de veces n.

El concepto de bucle es uno de los más importantes de todos los que se utilizan
en el diseño de algoritmos. ¿Por qué necesitan bucles los programas? Existen
dos razones fundamentales:
1. Economía de las expresiones.
2. Impredecibilidad del tamaño de la entrada.
Ambas razones para la existencia de bucles se pueden comprender si analiza-
mos la situación siguiente: Supongamos que es necesario diseñar un algoritmo
que calcule S como la suma de seis calificaciones, dadas por las variables 91 a
96. Una forma de calcularlo sería realizando la declaración y secuencia de
instrucciones Pascal siguiente:

val" 91,92,93,94,95,96: real;

S := O;
8:=8+91;
8 := 8 + 92;
8 := 8 + 93;
8:=8+94 ;
8:=8+95;
8 := 8 + 96;

Esta alternativa es absolutamente torpe. Si tuviéramos que extender el proble-


ma mínimamente, y necesitásemos sumar 100 calificaciones, la elección realiza-
da sería bastante poco brillante.
Bastante más dificil, e incluso bordeando lo imposible, sería extender el
algoritmo para el caso en que el número de calificaciones fuera impredecible
(aunque finito); por ejemplo, n. Naturalmente, el valor de n no puede predecirse
Compu tación l. Lógica, resoluc ión de problem as, algoritm os
y program as
104

pueda
en el momento en que se escribe el programa, de forma que el progra ma
para cada valor particu lar de n, en el mome nto en que
calcular el valor de Sum
en una
el progra ma es ejecutado o, como dicen algunos, corrido. Es decir,
mismo
ejecución del programa, n puede valer 6, y en la siguiente ejecución del
progra ma 100, en otra quizá 1000.
an
Lo que ,necesitaremos es que el lenguaje aporte facilidades que permit
generalizaciones de dos tipos:

1. La posibilidad de asignar un único nombre a una lista de números


arbitrariamente larga.
que
2. La posibilidad de escribir instrucciones o grupos de instrucciones
puedan ser ejecutados reiteradas veces, pero que sean suscep tibles de
ser interpretados de una forma diferente en cada repetic ión.

ió en la
La primera posibilidad la aporta el tipo Lis ta, tal y como se describ
ción de Pascal
sección anterior. La segunda capacidad la aporta la instruc
instruc ción, o grupo de
wh i l e, que permite ejecutar reiteradas veces una
cada repetic ión. Esto es
instrucciones, pero con interpretaciones diferentes en
cción que nos calcula ba la
lo que necesitamos para simplificar la torpe constru
suma de n valores.
Ilustremos estas capacidades por separado. Supongamos que se declara
91 a 96.
sólo una variable 9, del tipo Lis ta, en lugar de las seis variables de
ma origina l en la forma siguien te, sin
Entonces, es posible cambiar el progra
cambiar su significado:

varg: Lista;

5 := O;
5 :=5+g [1];
5 := 5 + g[2];
5 := 5 + g[3];
5:=5+ g[4];
5 := 5 + g[5];
5 :=5+g [6];

Es decir, la poscondición de la secuencia anterior seguirá siendo:

5 = Sum i E {1, ... , 6}: g[ i]

haciendo una única declaración, en lugar de seis.


del
Pero las seis instrucciones últimas son idénticas, a excepción del valor
largo de
índice de la referencia a la lista 9 [i ]; es decir, i varia entre 1 y 6 a lo
forma que
las instrucciones. Con objeto de generalizar esas instrucciones de
o i, que reco-
sean idénticas, necesitaremos introducir una variable, por ejempl
rra la secuencia de valores que se muestra en la Figura 4.8:
Problem as algorítm icos y su solucíó n 105

8 := O;
i := 1;
8:=8+ g[i];
i :=i+1 ;
8:=8+ g[i];
i := i + 1 ;
8:=8+ g[i];
i:=i+ 1;
'8 := 8 + g[i];
i:=i+ 1;
8:=8+ g[i];
i:=i+ 1;
8 ;= 8 + 9 [i];
i:=i+ 1;

Figura 4.8. Suma de seis elemen tos: un paso hacia la genera lización .

Parece como si estuviéramos retrocediendo, puesto que ahora tenemo


s que
escribir el doble de instrucciones, por lo que el nuevo método parece
una
locura. Sin embargo, cada pareja de instrucciones a partir de las dos
primeras
es idéntica:
whi le b do
begin
5; s; ... ; s
end

Figura 4.9. Forma genera l de la instruc ción wh i le: b es


una expres ión boo-
leana; s; s; •.• ; s es cualqu ier secuen cia de instruc ciones
en Pascal.
8:=8+ g[i];
i:=i+ 1;
y la secuencia entera sigue satisfaciendo la poscondición.
La instrucción whi l e permite repetir el par de instrucciones seis veces.
Su
forma general se presenta en la Figura 4.9, y una descripción más
detallada
puede encontrarse en el manual de laboratorio.
Cuando se ejecuta una instrucción wh i l e, primero se evalúa la condic
ión b.
Si el resultado de la evaluación es verdadero, se ejecuta la secuencia
.•. ; s una vez. La condición b se vuelve a evaluar, y si el resulta s;
s;
do es
de nuevo verdadero, se vuelve a ejecutar la sentencia. Esta pareja de
acciones
se ejecutan reiteradamente, hasta que la condición resulta evaluada
a falso,
momento en que se concluye la repetición de las instrucciones y se
termina
la ejecución del wh ; le.
Sin embargo, cuando utilizamos un bucle wh ; l e, debemos tomar una
serie
de precauciones inicia/espara que éste se ejecute bajo control. Es decir,
la ejecu-
ción del bucle debe terminar después de un número finito de repetic
iones, y
una vez terminado, debe satisfacerse la poscondición. Por esta razón,
a los
bucles suele denominárse!es bucles controlados. La estructura de un bucle
con-
trolado, que incluye tanto la inicialización como la instrucción wh
i l e, se
muestra en la Figura 4.10:
106 Computación l. Lógica, resolución de problemas, algoritmos y programas

Instrucciones de inicialización
while b do
begin
s;s; ... ;5
end;

Figura 4.10. Estructura general de un bucle controlado en Pascal.

Por tanto, los requisitos para que un bucle sea controlado son que las instruc-
ciones de inicialización, la expresión booleana b y las instrucciones s; Si
... i s estén escritas de tal forma que: 1) se garantice la terminación del bucle;
y 2) se satisfaga la poscondición del bucle.
En nuestro ejemplo, las instrucciones de inicialización S : = O e ; : = 1, la
expresión booleana ; <= 6, Ylas instrucciones S : = S + 9 [ i] e ; : = ; + 1,
combinan y satisfacen estos requisitos. Es decir, nuestro ejemplo puede reescri-
birse como el bucle controlado de la Figura 4.11.

5 := O;
i := 1;
while i <~ 6 do
begin
5:=5+g[i];
i := i + 1
end¡

Figura 4.11. Suma de los seis elementos de una lista. Generalización.

Obsérvese que en la Figura 4.11 la condición; <= 6 describe con exactitud la


condición bajo la cual el bucle debe seguir repitiéndose, con objeto de conse·
guir el equivalente a los seis pares de instrucciones como las que aparecen en la
versión original de la Figura 4.8.
Utilizando el método de los bucles controlados, se ha conseguido una
generalidad y economia en las expresiones, que en otro caso habría sido dificil
de conseguir. Obsérvese que el paso 3 de la solución al problema CM ( el bucle
whi le de la Figura 4.6) es prácticamente idéntico al bucle whi le de la Figura
4.11. Los dos bucles difieren sólo en la forma de las expresiones booleanas que
gobiernan el número de repeticiones del bucle, y en el nombre de las variables.
En la Figura 4.11, el bucle se repetirá exactamente seis veces; mientras que en
el bucle original, el número de iteraciones viene determinado por n, o número
de calificaciones en la lista Notas. Gracias a la generalidad que nos aporta la
introducción de la variable n, es posible resolver el problema CM. Sin n, la
solución estaría limitada a la que calcula el promedio de un número predeter-
minado de calificaciones. La permanencia de esta restrícción supondría una
situación bastante insatisfactoria no sólo para este problema, sino que no
satisfaría los requisitos de generalidad exigidos a las soluciones algorítmicas.
Invarianza de bucles. Un aspecto importante en el diseño de soluciones a
problemas mediante la utilización de bucles, es el poder garantizar que el bucle
Problem as algorítm icos y su solució n 107

termina, y que después de esto se satisface exactamente la poscondición


para la
que fue diseñado. Para conseguir este propós ito se utiliza un tipo especia
l de
aserto, llamado invariante del bucle (o simplemente invariante).
Informalmente, podemos pensar en el bucle como en un aserto sobre
el
estado de los cálculos, que vale verdadero (es decir, que no varía)
antes y
después de cada iteración del bucle, mientras se esté ejecutando. Es
habitual
diferenciar los invariantes de otro tipo de asertos de los programas,
porque
guarda n una relación especial con los fragmentos de progra mas en
los que
están incluidos. El aserto siguiente es un invariante del bucle del
progra-
maCM .

(inv: Sum = Su. j in (1, """' i - 1}: Notas[ j] /\ 1 <= i <= n + 1}

Para demos trar que esto es, sin duda, el invariante del bucle, debem
os
compr obar que cada valor de ; durant e la ejecución del bucle:

1. Sum es la suma de las i - 1 primeras calificaciones en la lista Not


a s; y
2. El valor de ; está comprendido siempre entre 1 y el valor que causa
la
terminación del bucle.
Considérese el ejemplo en el que Notas = (3 2 1 3), por lo que n
= 4. El
estado antes de cada iteración del bucle, y la comprobación corresp
ondiente
del valor del invariante, puede sintetizarse de la forma siguiente:

Antes
de la Estado Comprobación del invariante
iteración

1 S=O /\;=1 J\n= 4 Sum = Sum j ; n {1, ""., O}: No t a s [j]


J\1< =1< =5
2 S=3/ \;=2J \n=4 Sum= Sum j in {1, •.. , 1}: Notas [j]
/\1< =2< =5
3 S=5J \;=3/ \n=4 Sum= Sum j in {1, •.• , 2}: Notas [j]
J\1< =3< =5
4 S=6/ \i=4J \n=4 Sum = Sum j ; n {1, ..• , 3}: No t a s [j]
J\1< =4< =5
5 S=9J \i=5J \n=4 Sum = Sum j in {1, •.. , 4}: Notas [j]
J\1< =5< =5

Puesto que la quinta iteración no tiene lugar (se produce la salida del
wh i Le,
puesto que; <= n cambia a falso), el invariante conduce al cumplimiento
de la
poscondición al salir del bucle. Esto es,

Sum = Su.. j in (1, "."' i - 1}: Notas [j] /\ 1 <= i <~ n+1
prog ram as
ón de prob lem as, algo ritm os y
108 Com puta ción ,. Lógica, resoluci

que por sí mismo acaba siendo:


Not a s [j]
Suro = Sum j ; n {1, ... , n}:

Cua ndo el valor de ; alcanza n +


1. ruc-
la rela ción entr e el inv aria nte del bucle y la secuencia de inst
¿Cuill es Es nec esar io que
yen el cuerpo del bucle?
ciones s; s; ... ; s que constitu cion es
relación entre las instruc
estudiemos en nuestro ejemplo la

Suro := Suro + No tas [;]i


; := ; + 1

y el invariante
1 <= ; <= n + 1
n {1, ... , ; - 1 }: Not as [j]
1\
Suro = SUII j ;
etívos en
de forma intencionada con dos obj
El cuerpo del bucle se ha diseñado
mente:
1. El cuerpo preserve el invaria
nte (valga verdadero).
sión hacia la terminación del bucle.
2. El cuerpo produzca una progre
do lo
hacer ni más ní menos que esto -to
En general, los bucles no tienen que
demás es irrelevante.
ma
4.3.3. Tres vision~s de la misma solución al proble
",.

les de
lta de la composición de tres nive
El pro gra ma de la Figura 4.6 resu la descripción del proceso, que describe
cal;
lenguaje distintos: el código en Pas ma; y
tell ano qué ocu rre en cad a pas o del proceso que describe el progra
en cas ceso.
antes y después de cad a paso del pro ir,
la especificación formal que aparece dec
nivel elevado de redundancia; es
Los tres niveles jun tos suponen un estilos diferentes -alg orí tmi co formal
tres
expresan los mismos conceptos en no) y declarativo formal (especificac
io-
ico info rma l (castella
(Pascal), algorítm
nes). de que una solución pro gra mad a
a un
Est a redundancia enfatiza el hecho ienc ias:
te par a tres tipos de aud
algoritmo se escribe simultáneamen
1. La computadora.
grama.
2. La gente que diseña y lee el pro del programa.
la cor recc ión
3. La gente que verifica
ado ra
nta el código en Pascal: la com put
La com put ado ra sólo tiene en cue ores hu-
realizar el proceso. Los programad
seguirá con exactitud el código al en
en Pascal, los comentarios escritos
manos se interesarán por el código quieren entender y desarrollar correc-
s si
castellano, y las pre y poscondicione
Problemas algorítm icos y su solució n 109

tamente el código y, posteriormente, ampliar o mejorar el programa.


Los veri-
ficadores humanos del progra ma estarán interesados en el código de
Pascal, la
precondición, la poscondición y en los asertos intermedios mediante los
cuales
obtendrán una demostración rigurosa de la corrección del programa.
En la Figura 4.12 se muestra lo que consideraría la compu tadora
de la
parte en Pascal del progra ma que soluciona el problema CM.

progra ll ca l cu laCM;
uses
Listas ;
var
Notas : Listas ;
i, n : intege r;
Sum, CM: real;
begin
WriteL n ('Intro ducir la lista de califi cacio nes:') ;
ReadL ista (Notas );
n := LongL ista(N otas);
if n > O then
begin
Sum := O;
i := 1 ;
while i <= n do
begin
Sum :=Sum +Nota s[i];
i := i + 1
end;
CM := Sum/n;
WriteL n( 'La CM de esas cal ificac iones es = " CM : 5
2)
end
end. (Calcul aCM}

Figura 4.12. El progra ma CM, desde el punto de vista de la compu tadora


.

Los programadores noveles suelen confundir el punto de vista de la


com-
putado ra con el del lector. Es decir, suelen presentar sólo el progra
ma como
solución a un problema algorítmico. Sin embargo, esta representación
, por si
misma, no es suficiente para la construcción y presentación de progra
mas
fiables.
Cuand o nos comprometemos en el proceso de resolver problemas algorít
-
micos, nuestra intención debe ser la de desarrollar programas comen
tados
completamente, de forma que estas anotaciones nos muestren la definic
ión del
problema original, y los sucesivos pasos que hemos seguido en su
solución.
Para el problema de CM, el progra ma completo es, más o menos,
como la
versión mostra da en la Figura 4.13.

progra m calcula CM;


{esto es un program a que calcul a la CM de una serie de
una o más
110 Computación l. Lógica, resolución de problemas, algoritmos y programas

calificaciones numéricas, cada una de ellas en el rango O.. 4, que


se introducirán por el teclado}
uses
Listas;
var
Notas: Listas;
i, n : integer;
Sum, CM : real;
begin '
{pre: entrada = (Notas" Notas" ••• , Notas n ) /\ n > O
/\ Vi E {1, ... , n}:Notas; E {O, ... , 4}}

{Paso 1. Obtenemos la lista de calificaciones}


WriteLn('Introducir la lista de calificaciones');
ReadLista(Notas);

{Paso 2. Calcular n o número de calificaciones en la lista}


n := LongLista(Notas);
if n > O then
begin

{Paso 3. Calculamos Sum= la suma de las calificaciones de la lista}


Sum := O;
i := 1;
while i <= n do
begin
Sum := Sum + Notas[i J;
i := i + 1
end;

{Paso 4. Calculamos CM = Sum/n}


CM = Sum/n;

{Paso 5. Se muestra CM}


WriteLn('La CMde esas calificaciones es= " CM 5 2)
end {if}
{post: entrada =0/\
salida = Su.. i E {1, ... , n}: Notas;!n}
end. {CalculaCM}

Figura 4.13. El mismo programa, desde el punto de vista del lector


y del desarrollador.

La versión de la Figura 4.13 difiere de la versión de la Figura 4.6, en que


excluye los asertos intermedios. Estos asertos son útiles cuando el programa es
probado y verificado. La prueba y verificación de programas se introduce
separadamente en el Capítulo 6.
Problemas algorítm icos y su solució n 111

Ejercicios
4.4. La lista siguiente contiene la lectura de las temperaturas diarias de una
semana de verano.

Temps = (78 62 71 73748 584) *

a) ¿Cuál es el índice de la lista Tems, de la temperatura máxima de la


semana?
b) ¿Cuál es el valor de la temper atura máxima?
e) ¿Cuán tas temperaturas estuvieron por debajo del promedio sema-
nal?

4.5. Supongamos que necesitamos escribir un progra ma que calcule, para


una lista de siete temperaturas como la del ejemplo anterior, todas
las
respuestas a las cuestiones de dicho ejemplo.
a) Escribir una precondición que describa el conjun to de todas las
listas de entrad a válidas para el programa.
b) Dar una poscondición que describa, para las entrad as
válidas, las
salidas que debe mostra r el programa.
e) ¿Cuál de todas las cuestiones precisa la utilización de un bucle para
que el progra ma pueda contestarla?

4.6. Debajo, se muestra un bucle contro lado que calcula la temperatura


máxima de una semana, y la deja en la variable Max.

Max := Temps [1]¡


i := 2;
while i <= Lenght Li sta (Temps ) do
i f Temps [i] > Max then
Max := Temps [i]¡

a) Trazar una ejecución de los pasos de bucle, utilizando los valores de


Temp s del Ejercicio 4.4. Trazar significa escribir los valores
que
toman las variables ; y Max, en relación a los valores de
Temps [; ], al comienzo de cada iteración.
b) Después de examinar esos valores y examinar el compo rtamie
nto
del bucle para cualquier lista de valores, escribir un invariante para
el bucle.

4.7. Escribir un bucle contro lado que satisfaga las especificaciones siguien
-
tes:

{pre: L ~ (e" e" ••. , en) /\ n > O)

• N. del T.: Expresadas en grados Farenheith.


112 Computación ,. Lógica, resolución de problemas, algoritmos y programas

{post: SumPares = La suma de todos Los eLementos de L con índíce par 1\


Sumlmpar = La suma de todos Los eLementos de L con subíndice ímpar}

4.8. Escribir las instrucciones de Pascal que calculen el promedio de las siete
temperaturas de la lista del Ejercicio 4.4, asignando el valor a la varia-
ble TemProm. Dar las precondiciones y postcondiciones de esas instruc-
ciones, así como el invariante del bucle controlado que contienen.

4.9. Escribir las instrucciones en Pascal que cuenten el número de tempera-


turas de la lista del Ejercicio 4.4, que estén por debajo de valor medio
(TemProm) y dejen este valor en la variable entera ndebajo. Escribir
la precondición y la poscondición para esas instrucciones, asi como el
invariante del bucle que contienen. ¿Están esas precondiciones relacio-
nadas con las poscondiciones del Ejercicio 4.8? ¿Qué sugiere esto sobre
el orden en que esos dos grupos de instrucciones deberían escribirse si
las usásemos para resolver el Ejercicio 4.4c?

4.10. ¿Qué mostraria el siguiente fragmento de programa?:

var x, y: reaL;
m, n, p: ínteger;
begin
readln (x, m);
read (y);
read (n, p);
wríte (n, x);
wríteln (p);
wríteln (y, m);
end.

si se le suministrase la entrada siguiente:

13.215
23.7
236575
354679

4.4. MÁS PROBLEMAS ALGORíTMICOS

En esta sección nos alejamos del ya familiar problema de CM, e introducimos


otros problemas algorítmicos junto con sus soluciones. Estos nuevos ejemplos
ilustran el conjunto de problemas que son susceptibles de solucionarse utili-
zando una computadora. También nos servirán para ilustrar cómo los tres
estilos diferentes (descripción en castellano de los pasos individuales, especifi-
caciones y otras aserciones, y codificación en lenguaje Pascal) se utilizan para
realizar una descripción y resolución completa del problema.
Problemas algorítmicos y su solución 113

4.4.1. Cálculo de el
Desarrollemos un programa llamado Pot en c i a que calcule la b-ésima poten-
cia de a, o ab , donde a y b son enteros y el resultado un número real. Por
ejemplo, escrito como una función de Pascal Potencia(2,3) calcularia el
valor 2 3 = 8.
El\ la construcción de una solución, debemos precisar previamente no sólo
el resultado esperado, sino también el rango de enteros para los que el progra-
ma está bien definido; es decir, sus precondiciones. En particular, debemos
especificar que ciertas parejas de valores de a y b, tales como a = OY b = - 1,
deben ser excluidas, puesto que podrian conducirnos a errores computaciona-
les. (En este caso, Potenci a (0,1) representaría 0- 1 ó 1/0, que representa
una división con resultado indefinido). También debemos tener en cuenta las
limitaciones del lenguaje de programación en el que expresamos el algoritmo.
Por ejemplo, muchas versiones de Pascal restringen el rango de valores de los
enteros al rango - 215 ... 2 15 - 1. Si identificamos estos valores con Mi nlnt y
Maxlnt, respectivamente, entonces las precondiciones de Potencia pueden
descartar todos aquellos valores de a o b que conduzcan un resultado que
exceda a Minlnt o Maxlnt. Por ejemplo, a = 2 Y b = 16 darían como
resultado 216 , que sería un valor mayor que Maxlnt para muchas versiones
Pascal.
Teniendo presentes estas condiciones, las precondiciones y poscondiciones
siguiente podrían ser apropiadas para diseñar el programa de la potencia.

{pre: a y b son enteros A (a", O v a =OA b '" O)


b
A Minlnt '" a '" Maxlnt}
{Pos t: resu l t = a b }

Para los valores de a y b que satisfagan esta precondición, el resultado ab


podría definirse como:

Definición ab = 1 if b O
if b > O
: ':/~av'~ ~'.:~ I if b < O

Lb-a es
En la construcción de una solución al problema, debemos tener en cuenta que
el valor de b, que gobierna el número de veces que se calcula a por a, es una
variable. Por tanto, cualquíer solución que calcule el producto de las bases
debe contener un bucle.
El diseño del bucle se ve facilitado si analizamos el caso en que b es
pequeño, y la varíable P = ab resultante se calcula en la forma de la Tabla 4.3.
De los casos de esta tabla podemos hacer las observaciones siguientes:
1. El número de iteraciones del bucle es b - 1.
114 Computación ,. Lógica, resolución de problemas, algoritmos y programas

2. La inicialización del bucle se realiza mediante la asignación P : = a.


3. La instrucción que debe repetirse b -1 veces es la asignación P := P * a.
Este ejemplo nos sugiere nuevas ideas sobre la naturaleza del invariante del
bucle. Esto es, de la i -ésima iteración del bucle, donde 1 ~ i ~ b, el valor de P
viene dado por
,
{; nv: P = a i A 1 <= ; <= b}

La salida del bucle se producirá inmediatamente antes de la b-ésima iteración


(que nunca tendrá lugar). Ahora i = b Yel invariante se convierte por sí mismo
en P = ab, que está bastante próximo de la poscondición del problema.

Tabla 4.3. Resultados intermedios para el problema de la potencia

VaLor de b CáLcuLo de a b Poscond;c;ón

1 P := a {P = a}
2 P := a;
P := P * a {P=a*a}
3 P := B;
P := P * a;
P := P * a {P=a*a*a}
4 P := B;
P := P * a;
P :=P*a;
P := P * a {P=a*a*a*a}

Entonces, si utilizamos la variable i para controlar el número de iteracio-


nes del bucle, tomará todos los valores del rango de enteros comprendidos
entre 1 y b ~ 1, para que el cálculo de P sea correcto. Estas consideraciones
nos conducen al programa Pascal comentado, que se muestra en la Figura 4.14.

¿Quién comprueba las precondiciones? Cuando observamos la solución a al-


gún problema algoritmico, tales como el de la Potencia o el de la CM,
es normal preguntarse: ¿de quién es responsabilidad comprobar el cumpli-
miento de las precondiciones?, ¿del programa o del usuario del programa?
Nos gustaría tener algún tipo de garantía operativa de que ninguna entrada
diferente de aquellas para las que fue diseñado el programa se producirá y
tendremos así garantizado que no se nos contaminará ni peligrará la inte-
gridad del programa.
Consideremos el programa Potencia, para el que la precondición esta-
blece que, para algunos valores de a y b, el resultado a b puede estar fuera del
conjunto de valores enteros que la computadora puede reconocer. Desgracia-
damente, esta precondición del programa Potenc i a no puede ser comproba-
da explicitamente por el propio programa, puesto que la acción de calcular a b
Problemas algorítmicos y su solución 115

para valores inadecuados provoca un error en tiempo de ejecución; un mensaje


similar a ; nteger overf Low aparecerá en el monitor, y la ejecución con-
cluirá repentinamente.

progra. Potencia;
{Este programa calcula la b-ésima potencia de a)
varP: real;
1I, b, i: i nteger;
begin
{Pre : ent rada = a b A (a <> O or a ~ O A b <> O)
b
A Mi nlnt <= a <= Maxlnt}
WriteLn( 'lntroduci r dos enteros a y b: ');
ReadLn(a,b);
i f b = O then
P := 1
else
begin
P := a;
i := 1;
while i <= abs(b) - 1 do
{i nv: P = a; A 1 <= i <= b)
begin
P := P * a;
i := i + 1
end;
if b < O then
P := 1/P
end;
WriteLn('Potencia(a,b) =',P);
{post: entrada = vacio A salida = a b )
end. {Potencia)

Figura 4.14. Un programa que calcula abo

La precondición (a <> O v a = O Á b <> O) podría haberla comproba-


do explícitamente el programa Potenc; a, y emitido un mensaje de error
en el caso de ser violada. Sin embargo, esto no es así en el programa de la
Figura 4.14.
En general, debemos elegir entre dos extremos cuando tenemos que decidir
quién debe comprobar las precondiciones:
• Estrategia 1. Diseñar el programa sin comprobar ninguna de las precon-
diciones; construir el programa conforme a las precondiciones y poscon-
diciones, pero de forma que resuelva un problema más general (es decir,
de forma que haga algo incluso si alguien introduce entradas fuera del
rango de las precondiciones), en este caso, el usuario no se ofenderá si el
programa se utiliza para los propósitos especificados por las precondicio-
nes.
• Estrategia 2. Diseñar el programa de forma que compruebe todas aque-
llas precondiciones que sea posible comprobar, sin provocar una termi-
116 Computación l. Lógica, resolución de problemas, algoritmos y programas

nación anómala del mismo. Adoptar un convenio para devolver esta


información al usuario, si las entradas están en desacuerdo con las pre-
condiciones.
La estrategia 1 podria denominarse programación pasiva, mientras que la se-
gunda podría denominarse programación defensiva. En un curso de programa-
ción ¡lVanzado o de desarrollo profesional de software, siempre se prefiere la
estrategia 2 sobre la 1. Sin embargo, en un curso introductorio, donde se estu-
dian por primera vez otros conceptos fundamentales, suele aceptarse temporal-
mente la estrategia 1. Este punto es importante y trascendente. Lo analizare-
mos de nuevo en el Capítulo 6, cuando discutamos las diferencias entre la
noción de corrección de un programa, robustez y facilidad de uso.
No importa cuál de las dos estrategias decidamos seguir, siempre debemos
introducir las precondiciones y poscondiciones como comentarios al principio
y al final del programa, respectivamente. Es decir, el lector humano no debe
tener nunca dudas de lo que hace el programa, aunque la propia computadora
no esté totalmente informada de él.

4.4.2. Contar palabras de un texto

Los tipos numéricos integer y real se utilizan en casi todos los problemas
algorítmicos. Sin embargo, un gran número de problemas no se resolvería
adecuadamente con sólo esos dos tipos. Muchos problemas involucran a cade-
nas de caracteres ASCII, como datos, y su solución precisa la utilización de un
conjunto de operaciones características de este tipo de datos. La noción de
cadena en Pascal puede definirse de la forma siguiente:

Definición. Una cadena es una secuencia finita de cero o más caracteres


ASCII. Cuando se escribe una cadena en un programa Pascal, se encierra
entre comillas simples ( , ) para distinguirla de las instrucciones del progra-
ma que la rodean.

En la Figura 4.1 hemos visto ejemplos de cadenas. Por ejemplo, todos los
mensajes como:

'Introducir la lista de calificaciones'

son cadenas. Extendiendo el concepto, podemos declarar variables del tipo


cadena (s tri ng) * cuando queramos manipular valores de tipo cadena, en
lugar de valores numéricos. Acompañando a la idea de cadena de caracteres,
están una serie de funciones y procedimientos que sirven para manipularlas y
transformarlas. Esas funciones se resumen en la Figura 4.15.

• N. del T.: Algunas implementaciones de Pascal incluyen entre los tipos incorporados el
tipo string. Por ello, cuando nos refiramos al concepto seguiremos hablando de «cadenas" de
caracteres y utilizaremos la palabra «slring" cuando hablemos del tipo.
Problemas algorítmicos y su solución 117

Figura 4.15. Resumen de las operaciones sobre cadenas


y tipos involucrados*.

En el manual de laboratorio se realiza una discusión completa de estas


operaciones. Aquí sólo daremos unos pocos ejemplos que sirvan de ayuda para
comprender cómo se utilizan las cadenas de caracteres en la resolución de
algunos problemas algorítmicos de manipulación de textos.
Para declarar una variable cadena, por ejemplo s, basta con escribir la
declaración siguiente:

va .. s: st .. ing;

Ahora podemos asignar valores a la variable utilizando la asignación o los


procedimientos Read o ReadLn, de la misma forma en que lo hacíamos con
los enteros o los reales. Por tanto, podemos escribir instrucciones como:

5:= 'Hello World!'; o Read(s);

Para conseguir el efecto que se muestra en el centro de la Figura 4.16.


La Figura 4.16 muestra el efecto de la utilizacíón de varias de las operacio-
nes de las cadenas, tales como Lenght, Pos, Copy, Insert, Delete y
Wri te. Obsérvese que sus nombres son similares a los de las funciones que
manipulaban listas (véase Fig. 4.7). Más detalles pueden encontrarse en el
manual de laboratorio.
La longitud de una cadena (valor de la función Lengh t) es, simplemente,
el número de caracteres de que se compone, incluidos los blancos, signos de
puntuación y otros caracteres especiales del conjunto ASCII.

• N. del T.: En las Figuras 4.15 y 4.16 na se han traducido los nombres de las funciones.
pues son los mismos que suelen tener esas funciones en las implementaciones de Pascal. que
incluyen el tipo «string».
118 Computación ,. Lógica, resolución de problemas, algoritmos y programas

¡Input I 12
'Hello World!'
va .. s: St,;ng; 7

(
s := 'Hello World! ';
'Hello World!'
lnsert(', Ciao', s, 12)
1Output 1

Figura 4.16.
"
'Hello World, Ciao!'

Utilización de algunas operaciones de cadenas de caracteres.

La función Pos realiza una búsqueda en la cadena, de izquierda a derecha,


tratando de encontrar la primera ocurrencia de otra cadena. Así, si escribimos
Pos (s, I W' ) preguntamos por el índice o posición de la primera ocurrencia
empezando por la izquierda, de la cadena 'W'. Si la cadena no estuviera dentro
de s, la {unción devolverá el valor O.
La función Copy selecciona una subcadena de la cadena argumento, defini-
da por el valor de la posición inicial y la longitud. Así, Copy( s, 1,1) selec-
ciona la subcadena de s que comienza en la posición 1 y que tiene una longi-
tud 1. Como se trata de una función, Copy devuelve un resultado de tipo
string, aunque no altera para nada la cadena original s. La única forma en que
s podría quedar modificada, sería mediante una subsiguiente asignación a s,
del resultado de la función Copy, como ocurre en la instrucción siguiente:

s := Copy(', adiós' ,12);

Finalmente, la función Insert crea una cadena más larga mediante la


inserción o incrustración, de una cadena dentro de otra, a partir de una deter-
minada posición. Así, Inserte I , adi ós ,s, 12), literalmente indica que se
debe crear un valor nuevo de tipo string para la variable s incrustrando el
valor " adiós' en la cadena I Hello world! " que es el antiguo valor de s.
Para ilustrar estas ideas, considérese el problema de construir un programa
que tenga como entrada una cadena s, y que cuente el número de palabras de
la entrada.
Por simplicidad, supongamos que una palabra es una secuencia de caracte-
res distintos del blanco, tengan o no significado dentro de la lengua castel1ana
común. Los signos de puntuación, tales como puntos y comas, y así sucesiva-
Problem as algorítm icos y su solució n 119

mente, se consideran elementos de la palabr a a la que se añaden. Por


ello, cada
una de las cadenas siguientes son palabras:

esos
conoce r.
IMundo ! I

Podemo$ escribir las precondiciones y poscondiciones que describen


el proble-
ma, como sIgue:

{pre: entrad a ~ 'c[1J c[2J .,. c[nJ' /\ n >= O 1-. cada c[iJ
es
un caráct er ASCII}
{post: entrad a ~ vacia /\ sal ida = número de palabr as de
la entrad a}
Un progra ma que resuelve este problema, utilizando la mayoria de
las funcio-
nes de la Figura 4.16, se muestra en la Figura 4.17.

progra m Contad orPala bras;


(El program a cuenta el número de palabr as de una linea
de texto, y
muestr a el cómput o resul tanteo Se entien de por palabr
a cualqu ier
secuen cia de caract eres distin tos del blanco , que está
preced ido por
el comien zo del texto o un blanco , y que termin a con el
final del texto
o un blanco )
var s: string ;
i, npalab ras: intege r;
begin
{pre: entrad a = 'c[1J c[2J ... c[nJ' 1-. n >= O 1-. cada c[iJ
es
un caráct er ASCII}
{Paso 1. Obtene mos el texto de entrad a}
writln (' introd uci runa linea de texto seguid a de <RET>
'}
Read(s );
Writel n;
Insert (" ,s,Len ght<s) + 1);
(s = 'c[1J c[2J '" c[nJ c[n + 1J' 1-. c[n + 1J = " /\ n >~
O)
{Paso 2. Contam os las palabr as del texto}
npalab ras := O;
i := 1;
while i <+ Lengh( s) - 1 do
{inv: npala bras~ palab rasco ntada spara s[1... i-1JI-
.1<=i <=n+ 1)
begin
i f (Copy( s, i, 1) <>' ') and (Copy( s, i + 1,1) =' ') then
npalab ras :~ npalab ras + 1;
i:=i+ 1;
end;
(npala bras = palabr as contad as en s[1 ... n + 1J)
(Paso 3. Se muestr a el resulta do de la cuenta )
writel n('el número de palabr as es', npalab ras)
{post: entrad a = vacia 1-. sal ida = número de palabr as de
la entrad a}
end. (Conta dorPal abras)

Figura 4.17. Progra ma que cuenta el númer o de palabra s


de una cadena de texto.
120 Computación l. Lógica, resolución de problemas, algoritmos y programas

En el programa anterior, la variable npa Lab r a s indica el número de palabras


en el texto de entrada, mientras que n simboliza su longitud.
El corazón del programa es el bucle whi Le, donde se realiza una compro-
bación sobre el final del texto de s. Es decir, el final del texto será sólo cuando
el carácter i -ésimo sea un blanco, y el i + 1-ésimo también. Este convenio se
verifica para la palabra más a la derecha de s; es decir, la instrucción:
1

Insert<' " s, Length<s) + 1);

Introduce un carácter adicional a la derecha de s. Los casos en los que el valor


de entrada de s termine con un carácter distinto del blanco, son eliminados
previamente a la entrada del bucle que cuenta.
Consideremos la cadena de entrada:
'Whose woods these are 1 thi nk 1 know .•

Tras completarse el paso 1 del programa ContadorPa Labras, la variable s


queda con el valor:
5= 'Whose woods these are 1 think 1 know. '

Obsérvese que el blanco adicional se ha insertado dentro de la palabra I know I


de s. Ahora, el bucle de búsqueda del paso 2 comienza con la variable índice
i = 1:

s I Whose woods these are 1 thi nk 1 know.1


i

Utilizamos la expresión Copy(s, i, 1) para seleccionar el ;-ésimo ca-


rácter de s y Copy(s, i + 1, 1) para seleccionar el i + 1-ésimo. Así, la
expresión combinada:

<CoPY<s, i, 1) <>' ') and <copy<s, i + 1,1) =' ')

que busca si dentro de s el i -ésimo carácter es distinto de blanco, a la vez que


el i + 1-ésimo sí lo es. Obsérvese que esta condición es exactamente verdadera
cuando se alcanza el final de una palabra, como en el caso en que i = 5:

s IWhose woods these are 1 think 1 know.1


i

Puesto que el contador npaLabras se incrementa sólo al final de cada pala-


bra, registra apropiadamente el número de palabras de s en el devenir de i
desde la primera posición del texto hasta el siguiente carácter al final del tex-
to s. Esta posición se establece utilizando la expresión Lenght (s) - 1.
Problemas algorítmicos y su solución 121

Así, el ínvaríante del bucle se preserva. A la salida del bucle, el invariante se


convíerte en una expresíón sobre el valor final de npa labras, necesaría para
que se satisfaga la poscondicíón. ¡Níngún otro valor nos satisfaría!
Fínalmente, obsérvese la similitud entre las operaciones de las cadenas y las
correspondientes de las listas, viendo las Figuras 4.15 y 4.17, respectivamente.
Por ejemplo, la función de cadenas Lenght devuelve el número de caracteres
de Uljla cadena, mientras que la LenghtL; sta devuelve el número de elemen-
tos de una lista. Esta similitud es de gran ayuda cuando queremos recordar el
nombre de las funciones, cuando queremos resolver otros problemas algorítmi-
cos que utilizan listas y cadenas de caracteres.

4.4.3. Representación del problema de Las Tres-en-Raya

Aunque los tipos numéricos y cadena de caracteres son la base de gran canti-
dad de problemas algorítmicos, no sirven para caracterizar aquellos problemas
que implican la representación de información gráfica. Este tipo de problemas
incluye la visualización y análisis de imágenes de rayos X, fotografías desde
satélites, tableros de ajedrez y otros tipos de tableros de juego, y otros tipos de
gráficos utilizados habitualmente en gestión comercial y en el cálculo científico.
Para ilustrar este tipo de problemas, consideremos el conocido juego de las
tres-en-raya. Supongamos que se nos pide que construyamos un algoritmo que
muestre los movimientos individuales del juego de las tres en raya, reflejando
cada movimiento (X o O) en un entramado rectangular de la pantalla de la
computadora.
Para poder resolver este tipo de problemas necesitamos un nuevo tipo de
datos y el correspondiente conjunto de operaciones asociadas, que permitan a
un programa dibujar y realizar transformaciones sobre un array* rectangular,
denominado ent ramado de celdas individuales. Cada celda puede rellenarse
de una de las cuatro formas siguientes: No (sin rellenar); Sí (rellena en gris); X o
O. En la Figura 4.18 se muestra un entramado de 3 x 3 celdas, las cuales
tienen valores particulares en la que las filas y columnas del entramado se han
numerado de la forma convencional. La celda de la fila 1, columna 2 es una X;
la celda en la fila 2, columna 1 es Sí; la celda de la fila 3, columna 3 es O; y las
seis celdas restantes son No.
El número de filas y columnas, así como el tamaño de cada celda, se
establece en el momento en que el programa lo crea. El tamaño de la celda se
mide en pixels, que son unidades gráficas de la pantalla de la computadora y
que no son divisibles en unidades más pequeñas. Cada celda del entramado de
la Figura 4.18 es de 20 pixels de ancha y otros 20 de larga. El anclaje de un
entramado es la posición en la pantalla de su esquina superior izquierda,
cuando se visualiza el entramado. La localización viene dada por un par de

• N. del T.: Aparece aqui por primera vez la palabra inglesa ARRAY. Como ocurriera con
string, esta palabra se ha traducido de muchas formas, en mi opinión todas ellas insatisfactorias.
Por ello prefiero, tanto en este punto como en ocurrencias posteriores, respetar el término inglés.
122 Computación l. Lógica, resolución de problemas, algoritmos y programas

Anclaje
3

} Tamaño de la celda ~ 20 pixels


~~--i

o
Figura 4.18. Un entramado de 3 x 3 celdas y las situaciones de las mismas.

coordenadas xy, teniendo la esquina superior izquierda dc la pantalla las coor-


denadas (O, O).
La Figura 4.19 muestra las distintas operaciones que pueden utilizarse para
crcar entramados de varios tamaños y para modificar cl estado de sus celdas.
Si declaramos una-variable dc tipo Grid*, podemos utilizar esas opcraciones
para realizar transformaciones sobre la variable.

CellOn ~ole~
CellOff
CellX
Ce llO

TurnCellOn
TurnCellOff
TurnCellX
TurnCellO

Figura 4.19. Resumen de las operaciones más importantes.

• N. del T.: Seguiremos aquí el mísmo convenio entre tipo y objeto que utilizamos entre
cadena y slring.
Problem as algorítm icos y su solució n 123

Sin embargo, al contra rio de las listas y cadenas, necesitamos comen


zar los
progra mas que utilizan estas facilidades gráficas con la instrucción

StartG rids;

y finalizar el progra ma con la instrucción

StopG rids;

La primer a de ellas borra la pantal la y crea dos ventanas, una ventan


a gráfica a
la izquierda y una de texto a la derecha. Todos los entram ados que
se creen en
el progra ma aparec erán en la ventan a gráfica, mientras que los textos,
entrad as
y salidas lo harán en la ventan a de texto. Los detalles sobre
esas y otras
operaciones gráficas se explican extensivamente en el manual de labora
torio.
Al contra rio que las variables de tipo Lista o string, las variables
del tipo
Gr ; d se deben crear y dibujarse en la ventan a gráfica antes de asigna
r valores
a las celdas. Por ejemplo, si queremos crear el entram ado d para que
tenga 3
filas y columnas, con celdas de 20 pixels y anclad o en la posición (10,
10) de la
ventan a gráfica, escribiremos:

MakeG rid(d, 3,10, 10,20 );

Esta instrucción crea el entram ado d con la forma y tamaño idéntic


os al
entram ado de la Figura 4.18, pero teniendo todas sus celdas en
blanco. El
resultado se muestra en la Figura 4.20.
Si queremos cambia r de aspecto alguna celda de d, utilizaremos las
opera-
ciones TurnC eLLOn , TurnC ellX o TurnC eL LO. Por ejemplo,

TurnC ellX(d , 1, 2)

coloca una X en la celda de la fila 1, column a 2. El resultado de


esta acción
aparec erá inmedi atamen te en la ventan a gráfica. Si queremos extend
er lo ante-

(O, O) Ventana gráfica Ventana de texto

(10, ro;

(200,2~

Figura 4.20. Aspect o de la pantall a para salida gráfica .


124 Computación l. Lógica, resolución de problemas, algoritmos y programas

rior de forma que todas las celdas de la columna 2 contengan X, podemos


escribir el bucle siguiente:

for i := 1 to GridSize(d) do
TurnCeLLX(d, i, 2);

Donde se ha utilizado la variable integer i para recorrer todas las filas del
entrainado d, y se ha utilizado la función GridSize para obtener el número
total de filas y columnas del entramado d.
Finalmente, el grupo de operaciones CeLLOnCd, i, j), CeLLOffCd,
i, j), Ce LLX Cd, i, j) y Ce LLO Cd, i, j) pueden utilizarse para cono-
cer el estado de la celda de la fila i, columna j del entramado d. Por ejemplo,
si nuestro entramado d tuviera el aspecto del de la Figura 4.18, la función
CeLLOnCd, 1, 3) devolvería el valor faLse míentras que la operacíón
Ce LLX Cd, 1, 2) devolvería el valor t rue.
Con estas operaciones presentes podemos resolver el problema propuesto
al princípio de la sección. El programa de la Figura 4.21 controla el juego de
Las Tres-en-Raya, solicitando una alternancia de movímientos a dos jugado-
res, y redibuja la situación del juego en la parte gráfica de la pantalla.

program TresEnRaya;
uses Grids;

const ancLajex = 20; {coordenadas x e y}


ancLajey = 20; {deL tabLero en La pantaLLa}

var tabLero: Grid;


movimiento: integer; {movimiento que se debe mostrar}
i, j: integer; {fila y columna del movimiento}

begin
{pre: una serie de movimientos de las Tres en Raya}

{Paso 1. Inicializamos eL tabLero y eL juego}


StartGrids;
MakeGrid(tablero, 3, ancLajex, anclajey, 40)
movimiento :~ 1;

{Paso 2. Alternancia de movimientos entre x e y}


repeat
writeLn(' introduci r fila y columna del siguiente movimiento');
writeLn('(O O significa eL finaL deL juego): ');
read(i); read(j);
i f i > j then
begin
i f movi mi ento mod 2 ~ 1 then
TurnCeLLX(tabLero, i, j) {Los movimientos impares son de X}
else
TurnCe LLO( tabLero, i, j) {Los movimi entos pares son de O}
end;
movimiento := movimiento + 1;
unti l i = O;
Problemas algorítmicos V su solución 125

{post: entrada ~ vacía /\


sa l í da = una representaci 6n grá f i ca de estos movi mi entos sobre
un tablero de las Tres-en-Raya}
end. {TresEnRaya}

Figura 4.21. Programa que controla Las Tres-en-Raya.

En el programa, dos jugadores alternan sus movimientos. Cada movimien-


to viene dado por un número de fila ;, y uno en columna j, donde los
jugadores desean colocar una X o un O. La primera persona que juega es X. El
control del juego y la determinación del ganador se encuentra totalmente en
manos de los jugadores. Cuando un jugador introduce O O (dos ceros), el
programa terminará el juego. Un ejemplo de una secuencia de movimientos y
de los tableros resultantes se muestra en la Figura 4.22.
La variable mov; mi ento juega un papel trascendental en la simulación.
Almacena el número de jugada que está a punto de realizarse, en cada repeti-
ción del bucle del paso 2. Movimientos de números impares (siempre que
mov; mi ento mod 2 = 1) el programa coloca una X en la posición (;, j)
del tablero. Un número impar de movimiento coloca un O en la posición del
tablero.

Introducir fi la y columna del siguiente movimiento


(O O significa el final del juego):
12
Introducir fi la y columna del siguiente movimiento
(O O significa el final del juego):
1 3
Introducir fila y columna del siguiente movimiento
(O O significa el final del juego):
3 2

Figura 4.22. Serie de tres movimientos de Las Tres-en-Raya.

Aunque el programa anterior no sea una implementación excesivamente


satisfactoria del juego de las tres-en-raya, sirve para ilustrar alguna de las
operaciones gráficas básicas que acompañan al tipo Gr; d, Y su utilización
como ayuda en la visualización de la salida de cierto tipo de problemas algorit-
micos.
Existen varias razones por las que este programa no representa una simula-
ción totalmente satisfactoria del juego de las tres-en-raya. En primer lugar,
obsérvese que el programa permite a un jugador borrar el movimiento del
otro, sin más que especificar la misma casilla. Segundo, el programa no analiza
si un jugador accidentalmente introduce un número de fila o de columna
erróneo, tal como 4 ó 7. Tercero, el programa no tiene ningún conocimiento de
la situación del juego. Es decir, no puede reconocer un movimiento tras el que
126 Computación l. Lógica, resolución de problemas, algoritmos y programas

gane X o gane Y, o queden empatados (lo que es bastante habitual en este


juego). Cuarto, el programa no está preparado para ser uno de los dos jugado-
res, realizando movimientos inteligentes en respuesta a los que realice una
persona jugando con la pieza opuesta.
La primera y segunda de las carencias son fáciles de solucionar. Sin embar-
go, la tercera y la cuarta requieren la introducción de fragmentos de algoritmos
más sofisticados, que corresponderían al área de la informática conocida como
inteligencia artificial. Es decir, código que permitiera a la computadora simular
la inteligencia humana, aunque fuera de forma muy limitada.
Hemos investigado un pequeño número de algoritmos, y las herramientas
del lenguaje Pascal que sirven para su realización en una computadora. Hemos
utilizado varios tipos de datos, tales como el integer, real, string, lista y grids; y
varios tipos de instrucciones que incluyen la asignación, la selección condicio-
nal, los bucles y la E/S. Tras estudiar estas herramientas y ejemplos, y tras
haber trabajado con el manual de laboratorio, el lector habrá conseguido
desarrollar una cierta facilidad en el diseño y escritura de programas pequeños
en Pascal. Con los Ejemplos 4.11 a 4.27 conseguirá algo más de práctica.
Recuerde que el aprendizaje del diseño de programas, como el aprender a tocar
un instrumento, requiere no sólo estudio teórico, sino mucha práctica. Cuando
ejecute sus programas en una computadora, sea paciente e inténtelo de nuevo;
recuerde que pocos programas funcionan bien la primera vez. ¡Conseguirá
mayores éxitos si lo tiene en cuenta!

Ejercicios

4.11. Escribir un programa en Pascal que escriba S1, si la variable K es un


entero positivo impar (como 1, 3, 5, ...) Y NO en caso contrario.

4.12. Escribir un programa que asigne a m el valor absoluto de n, sin utilizar


la función de Pascal que da el valor absoluto.

4.13. Escribir un programa Pascal que escriba la secuencia de n números, en


la que cada término se obtiene de doblar el valor del anterior, siendo n
la entrada. (Es decir, cada número en la secuencia es el doble del ante-
rior.)

4.14. Repetir el Ejercicio 4.13, pero cambiando el programa para que escriba
los n números en orden inverso. (Es decir, cada número es la mitad del
anterior.)

4.15. Escribir un programa que lea 25 enteros y que cuente cuántos de ellos
son negativos.

4.16. Escribir un programa que lea 100 enteros y que determine y escriba
cuántos de ellos son divisibles por 5.
Problem as algorítm icos V su solució n 127

4.17. Escribir un fragmento de progra ma que realice lo siguiente:


Poner x a x + A si x es negativo;
Poner x a x + A + B si x es cero; o
Poner x a x + A + B + e si x es positivo.

,¡Cuidado!
4.18. ¿Qué hacen los fragmentos de progra ma siguientes? Es decir,
¿si in-
troduje ra y ejecutase en la compu tadora este progra ma, cuál seria
la
salida?

a) var
k, j, m: intege r;
n: real;
begin
k := 4;
j := 8;
m := 9;
n := m div j + k * 1.0 - m div j;
writel n ('n= " n)
end;

b) var
m, j: intege r;
begin
m :~ 4;
j := 1;
whi le (j < m) do
begin
j:=j+ 2;
m := m+ 1
end;
writel n ('m= ',m, Ij= " j)
end;

e) var
k, j: intege r;
begin
for k : = 1 to 10 do
begin
j := 10 - k;
i f (j <= 4) then
j := j - 1
end;
writel n ('j = " j, 'k= " k)
end;

ti) var
n, m: intege r;
begin
n := O;
m := 1;
128 Computación l. Lógica, resolución de problemas, algoritmos y programas

whi le (m <= 101) do


begin
n := n + 1;
m := m + 5
end;
writeln ('n= " n)
end;

e) var
j, k, m: integer;
begin
k := O;
for j : = 1 to 3 do
for m := 1 to 4 do
k :=k+1;
writeln ('k= " k)
end;

f) uses
Lista;
var
k: Lista;
j: integer;
begin
k[1] :~ 1;
for j := 2 to 4 do
k[j] :~k[j-1]+j;
wri teL i sta (k)
end;

g) uses
Lista;
var
m: Lista;
j: integer;
begin
j := 1;
while (j <= 2) do
begin
m[j + 2] :~ j;
m[3-j] :=j;
j :~j+1;
end
WriteL i sta (m)
end;

4.19. Trazar la ejecución del programa Po t ene i a para las entradas 5 y - 4,


escribiendo los valores de las variables a, b, P e i, antes de que se
ejecute cada iteración.

4.20. Escribir un programa que cambie a cero todos los valores de una lista
que ocupen una posición impar y que sean impares. Es decir, si llama-
mos L a la lista, el programa debe examinar L[1 J, y si contiene un
Problemas algoritmicos y su solución 129

valor impar, poner L [1] a O. Lo mismo debe hacer para L [3] ,


L[S], , mientras que deben descartarse los elementos L[2],
L[4], .

4.21. Escribir un programa que escriba los 11 primeros números de Fibonac-


ci. Los números de Fibonacci son 1, 1, 2, 3, 5, 8, 13, ...

4.22. Escribir un programa que calcule el factorial de un entero n (denotado


por ni). Recuérdese que la definición de n! es:

O! 1
n! n x (n - 1)1

4.23. Diseñar y escribir un programa en Pascal que lea un determinado texto,


y calcule y escriba el número de ocurrencias de la subcadena I and' .

4.24. Trazar la ejecución del programa ContadorPa Labras de la Figura


4.17, mostrando los valores de las variables i y npa Labras durante su
ejecución, con el texto de entrada siguiente:

'Able was 1 ere 1 saw Elba .•

4.25. Comprobar que el invariante del bucle del programa ContadorPa La-
bras es válido para la entrada del Ejercicio 4.24, analizándolo para
cada valor de i que provoca un cambio en npa Labras. Por ejemplo,
la primera interpretación del bucle ocurrirá cuando i = 6, Yse leería de
la forma siguiente:

{npalabras = 1 para 'Able' /\ 1 <= 6 <~ 26)

4.26. Escribir un programa en Pascal que lea un texto y lo invierta y escriba.


Es decir, si la entrada del programa es:

'mayo 1992'

la salida del programa debe ser:

'2991 oyam'

4.27. Refinar el programa de las Tres-en-Raya, de forma que prevea el


que un jugador coloque una X o una O en una casilla ya ocupada.
Cuando esto ocurra, el programa debe avisar al jugador, de forma cor-
tés, que debe hacer un movimiento alternativo y registrar un nuevo
movimiento.
130 Computación l. Lógica, resolución de problemas, algoritmos y programas

4.28. Escribir un programa que calcule el coste total del franqueo postal de
una serie de envíos. Debe contener un bucle que lea la cantidad y precio
de cada envío y lo acumule al total. Después de que se haya introducido
el último elemento, el programa mostrará el coste total de la operación
y el número total de envíos realizados. El coste total deberá reflejar un
5 por 100 de impuestos. Asegúrese de incluir en el propio programa las
pre y. poscondiciones.

4.5. RESUMEN

En este capítulo se han introducido las ideas de problema algoritmico y de su


solución correspondiente. Se ha hecho especial hincapié en la importancia de
especificar con precisión el problema en forma de precondición y poscondi-
ción. También se ha introducido la idea de invariante de un bucle y su utiliza-
ción en un programa. Se comenzó con algunos sencillos problemas matemáti-
cos, para seguir con problemas de procesamiento de textos y manipulación de
gráficos, proponiendo y discutiendo su solución en Pascal.
Este capítulo es una base importante para los Capítulos 5 y 6. En el Capí-
tulo 5 se introduce la metodología MAPS, que es una metodología sistemática
para la resolución de problemas algorítmicos, y que puede utilizarse en la
resolución de una amplia gama de problemas dentro de esas tres áreas. La
metodología MAPS incide especialmente en la utilización de las ideas funda-
mentales introducidas en el Capítulo 4 -precondiciones, poscondiciones, bu-
cles, invariantes, listas, cadenas, entramados y sus operaciones asociadas. En el
Capítulo 6 se introduce una técnica que permite asegurar la corrección de los
programas en Pascal que construimos al solucionar problemas algorítmicos.
CAPíTULO 5
RESOLUCiÓN
DE PROBLEMAS ALGORíTMICOS

El conocimiento puede ser de dos tipos. Conocemos algo por nosotros mismos,
o conocemos dónde podemos encontrar información sobre ello.
SAMUEL JOHNSON, 1775

En el Capítulo 4 nos concentramos en la construcción de programas relativa-


mente cortos, que resolvían problemas bastante sencillos. Enfatizamos en la
necesidad de realizar especificaciones claras, estableciendo los principales pa-
sos del algoritmo y trasladando esos pasos a instrucciones Pascal ejecutables.
En este proceso nos familiarizamos con los tipos de datos básicos (i nteger,
reaL y booLean) y con otros algo más complejos (Listas, strings y
gri ds), junto con sus operaciones asociadas. También desarrollamos algunos
programas sencillos para ilustrar el gran número de problemas con una solu-
ción algoritmica. Estos programas son una muestra de la denominada «pro-
gramación en pequeña escala».
Sin embargo, para poder resolver problemas algoritmicos más extensos y
complejos, necesitamos proveernos de un conjunto de técnicas de resolución de
problemas más robustas. Es decir, «la programación en gran escala» requiere
la utilización de una metodología unificada y robusta que pueda utilizarse de
manera fiable en una variada área de aplicaciones. Una Metodología de Reso-
lución de Problemas Algorítmicos *, MAPS, se desarrollará e ilustrará en este
capitulo.

5.1. NECESITAMOS UNA METODOLOGíA

Los problemas de la programacíón en gran escala difieren de los de la pequeña


escala en tres aspectos. Primero, un programa que representa una solución a

* N. del T.: En el texto se respelará el acrónimo inglés MAPS (Methodology for Algorith-
mic Problem Solvíng), debido a que el autor pretende enfatizar la semejanza de la metodología con
el término Map o Mapping, que podría traducirse por correspondencia y que se utiliza amplia-
mente en la terminologia informática. Debido a que al traducirlo es imposible capturar este
concepto, respetamos el acrónimo original.
132 Computación l. Lógica, resolución de problemas, algoritmos V programas

problemas grandes ocupa generalmente más de una página de código en Pas-


cal. Segundo, los programas largos tienen, además de su longitud, alguna otra
característica que los hace más complejos. Tercero, las soluciones para proble-
mas más complicados se expresa mejor si se utiliza una sistemática que si se
escribe directamente el código que se nos ocurre. Es necesario que los primeros
pasos describan el problema y que el algoritmo resultante sea fruto de un
diseño, fruto de una sistemática paso a paso. De hecho, en muchos casos, la
solución a un problema grande es el resultado del trabajo de un grupo de
trabajo numeroso más que de una sola persona.
Las soluciones a los problemas grandes comparten con las de los pequeños
tres caracteristicas: legibilidad, eficiencia y corrección. Legibilidad significa que
cualquiera que conozca el lenguaje Pascal y el dominio al que pertenece el
problema, es capaz de entender el problema y el programa sin más que leer
cuidadosamente el programa y los comentarios que le acompañan, indepen-
dientemente de lo largo que sea el programa o lo complejo que sea el pro-
blema. La eficiencia es deseable porque los recursos informáticos son rela-
tivamente costosos, y las aplicaciones deben intentar minimizarlos. La
corrección significa que todas las ejecuciones del programa deben conducir
a resultados válidos para cualquier entrada permitida por las especificaciones
del programa.
Estas tres características de los problemas complejos tienden a dotarlos de
una identidad dual. En un extremo, la legibilidad de los programas permite
que éstos constituyan una explicación de la solución a problemas algorítmicos
para un lector interesado. En el otro extremo, la eficiencia y fiabilidad de los
programas les permite ser un mecanismo a través del cual la computadora
puede ser utilizada para resolver problemas. En la construcción de programas
utilizando la metodología MAPS, siempre tendremos en mente esta doble
identidad de los programas. i El proceso de codificación de un programa en
Pascal es sólo una parte de la complicada tarea de resolver un problema
algoritmico!

5.1.1. Generalidades sobre el método MAPS

¿En qué consiste esta metodología de resolución de problemas algorítmicos?


La metodología MAPS puede resumírse como la sucesíón de las etapas si-
guientes:

• Etapa 1: El diálogo. Comprender el problema. Leer el enunciado del


problema. Hacer preguntas sobre las características de la entrada y la
salida y sus limitaciones, hasta tener completamente claro el problema.
• Etapa 2: Las especificaciones. Construir las especificaciones a partir de
nuestro conocimiento del enunciado del problema. Escribir pre y poscon-
diciones que sean completas y consistentes. Es decir, asegurarse de que
las precondiciones cubren todas (y sólo esas) las posibles entradas, que
las poscondiciones definen la salida para todas las posibles entradas, y
Resolu ción de problem as algorítm ícos 133

que tanto las precondiciones como las poscondiciones son interna y


mu-
tuamente consistentes.
• Etapa 3: La división. Subdividir, de forma sistemática, el proces
o en una
colección de pasos diferentes. Repetir este proceso para cada paso hasta
que la subdivisión no tenga sentido. Identificar las relaciones de contro
l
entre los distintos pasos. Es decir, ¿qué paso debe preceder a cuál?,
¿qué
pasos son parte de otro más complejo?, ¿qué pasos deben repetirse
den-
tro de un bucle?, y asi sucesivamente. Docum entar cada paso escribi
endo
una breve descripción de sus objetivos. Asignar un nombr e apropi ado
a
cada nueva variable que incorporemos en cada paso, en función de
cuál
es su cometido.
• Etapa 4: Definición de abstracciones. Determ inar cuáles de los
pasos que
hemos utilizado habian sido, a su vez, usados en otra situación, recolec
tar
las rutinas que se utilizaron en esa situación y adapta rlas para reutiliz
ar-
las en la situación presente. En muchos casos, esta tarea implica la cons-
trucción de una rutina nueva a partir de otras dos, o incluso constru
ir
una nueva para una utilización más especializada.
• Etapa 5: Codificación. Trasla dar a Pascal cada paso individual
de nuestra
solución, identificando rutinas nuevas y reutilizando aquellas que resul-
ten apropi adas para realizar cada paso individual. Conec tar cada
paso
utilizando la estructura de control apropi ada (bucles, invocaciones a
pro-
cedimientos, secuencias de instrucciones, selecciones condicionales,
etc.)
de una forma compatible con lo diseñado en el paso 3. Respetar como
comentarios toda la información generada durant e el paso 3 para
cada
paso individual y las variables correspondientes.
• Etapa 6: Prueba y verificación. Probar o validar de forma sistem
ática el
progra ma Pascal, haciéndolo ejecutarse para un conjun to de valores
de
entradas, que permita explorar todo el rango de valores permitido por
las
precondiciones. Para cada ejecución, compr obar que las salidas satisfac
en
las poscondiciones. De forma alternativa, verificar cuando sea apropi
ado
alguno o todos los pasos del programa, utilizando técnicas de demos
tra-
ción formal.
• Etapa 7: Presentación. Añadir un comentario al comienzo del
progra ma
para clarificar su propósito, escribir el nombre de los autore s y la fecha,
e
identificar la utilización de rutinas que fueron desarrolladas con
otro
propósito (e incluso por otros programadores). Prepar ar un listado
del
programa, junto con un listado de algunas ejecuciones de prueba.
En este capítulo se ilustran cada una de las etapas de la metodología
MAPS.
Definamos previamente la idea de rutina, tal y como se considera en
la resolu-
ción de problemas algorítmicos, junto con el método de Pascal para
empaque-
tarlas, de forma que éstas sean reutilizables convenientemente.
134 Computación l. Lógica, resolución de problemas, algoritmos y programas

5.2. CONSTRUCCiÓN DE SOFTWARE PARA


SU REUTILIZACiÓN: LA RUTINA

En el Capítulo 4, y en el manual de laboratorio, se han presentado algunos


procedimientos y programas cortos que realizan tareas especificas. Por ejem-
plo, un programa calcula el valor promedio de una lista de enteros, otro
calcula una potencia entera de un número, un tercero localiza el máximo de
una lista, y otros cuentan las palabras de un texto o visualizan una secuencia
de jugadas del juego de Las Tres-en-Raya.
Tuvimos que realizar un gran esfuerzo, porque desarrollamos esos proble-
mas partiendo de la nada. Es evidente que, para problemas más complejos,
tendremos que hacer un esfuerzo mayor si los resolvemos también partiendo
de la nada. ¿Existe alguna alternativa? Seguramente no queremos seguir resol-
viendo problemas si no podemos beneficiarnos de la experiencia adquirida al
resolver otros problemas.
Si encontramos un problema que para solucionarlo podemos utilizar partes
de otros previamente resueltos, el no hacerlo y desarrollar una solución desde
el principio seria un gran despilfarro de tiempo. Nuestro tiempo y energía
estarán utilizados de una forma más eficaz si somos capaces de reutilizar solu-
ciones ya conocidas cuando sean necesarias. La corrección de la solución nuc-
va será más fácil de establecer, puesto que la de uno de sus componentes ya lo
fue anteriormente.
Un algoritmo o parte de él, que parcialmente puede ser rcutilizado, recibe
el nombre de rutina. Una buena parte del tiempo empleado en resolver proble-
mas se utiliza en identificar rutinas y en reutilizarlas en forma apropiada.

Definición. Una rutina es una parte de un algoritmo que puede reutilizarse


en otros contextos de resolución de problemas algorítmicos.

Algunas rutinas son tan comunes que están incluidas en el lenguaje de progra-
mación Pascal para utilizarlas en los programas. Estas rutinas incluyen los
operadores numéricos (+, -, *, /, di v y mod); las expresiones aritméticas,
las estructuras de control (i f, wh i Le y f o r); los operadores de entrada,
salida y asignación (read, wri te y :=) y los operadores de cadenas (copy,
pos y Length). Se pucden añadir al lenguaje otras rutinas para manipular
listas (ReadL i s t a, Wri teL i s tal y entramados (Ma keGr i d, Gr i dS i ze y
TurnCeLLOn).
Existe una gran variedad de rutinas para diferentes dominios de la resolu-
ción de problemas. Varios de ellos se introducirán en este capitulo.

5.2.1. Encapsulamiento de rutinas para la reutilización.


Abstracción procedimental

¿Cómo podemos almacenar o encapsular rutinas de forma que puedan ser


reutilizadas con posterioridad en la resolución de otros problemas? Un método
Resolu ción de problem as algoritm icos 135

fundamental para construir rutinas en Pascal es la denominada abstrac


ción
procedimental. Siempre que hacemos que una parte de un progra ma
ya existen-
te se convierta en una rutina, primero encontramos (y definimos) su genera
liza-
ción más apropiada, y después la convertimos, de forma sistemática,
en un
procedimiento o función de Pascal. Estúdiese en el manual de labora
torio los
detalles de cómo utilizar y crear los procedimientos y funciones de Pascal
antes
de continuar con el capítulo.
La 'abstracción procedimental es un proceso que consta de tres etapas:
1. Nomb rar la abstracción. Asignar un nombre que identifique claram
en-
te el algoritmo que representará el algoritmo resultante.
2. Redefinir las precondiciones y poscondiciones del problema, de
forma
que su solución pueda implementarse como un procedimiento o función
.
a) Parametrizar la entrada y la salida. Reemplazar (partes de) «entra-
da =» y «salida =» en las especificaciones por el o los parámetros
apropiados, que jueguen el mismo papel que las especificaciones a
las que sustituyen en el problema original.
b) Generalizar los tipos. Examinar las entradas, las salidas y los
pa-
rámetros para determinar si es posible extenderlos o generalizarlos,
de forma que la solución construida pueda servir para un conjunto
más amplio de problemas.
3. Implementar la abstracción como un procedimiento o función
, bien
partiendo de cero, bien convirtiendo un progra ma existente para
el
problema original.
Los pasos 1 y 2 reciben el nombre de especificación de la rutina. Su resulta
do se
muestra en la forma encapsulada siguiente:

Nombr e(parám etros>


(pre: expres ión con las precon dicion es de la rutina )
(post: expres ión con las poscon dicion es de la rutina )

El paso 3 unifica las especificaciones con el propio procedimiento o función


de
Pascal, convirtiéndola en una unidad de progra ma reutilizable.
Para ilustrar lo anterior, considérese el problema de la CM que se resolvi
ó
en el Capítulo 4. Sus especificaciones se definieron, originalmente, en
la Figu-
ra 4.3 de la forma siguiente:

(pre: entrad a = (Notas" Notas " "."' Notas > 1\ n > O 1\


n
'Vi E {1, .""' n} :Notas ¡ E (O, "."' 4})
(post: entrad a =0/\ salida = Sum i E (1, •.. , n):No tasJn}

Supongamos ahora que este progra ma es lo suficientemente import ante


para
asignarle la categoría de rutina. Quizá porque estemos trabajando en
un pro-
blema más complejo que tiene por objetivo calcular qué mes del año
tiene el
mayor promedio de lluvia caída por día. La entrad a de este problema
podría
ser una serie de listas, una por cada uno de los 12 meses del año, que
conten-
gan la cantidad de lluvia caída cada día. Cuand o nos ponemos a resolve
r el
136 Computación l. Lógica, resolución de problemas, algoritmos y programas

problema observamos que parte de nuestro viejo programa CM puede utilizar-


se para encontrar la solución. ¡Nuestra solución de promediado acaba de
alcanzar el rango de rutina!
Pero, antes de reutilizar nuestro programa debemos convertirlo en un pro-
cedimiento o función equivalente; es decir, debemos aplicar los tres pasos de la
abstracción procedimental a su definición. Para el paso 1, la elección del nom-
bre Promed; al,; sta es una buena elección, pues indica con claridad lo que
hará la rutina. Para el paso 2a, parametrización de la entrada y la salida,
incluimos el parámetro l, en lugar de la entrada, para simbolizar la lista de
valores de la que deseamos calcular el promedio, y el parámetro resuL tado
en lugar de la salida, indicando así que el promedio resultante se devolverá al
contexto del programa en que se utilice, en lugar de visualizarse por la panta-
lla. Para el paso 3, generalización del tipo, cambiamos nuestra rutina de pro-
mediado para que trabaje sobre números en un intervalo apropiado, y no sólo
en el rango de O a 4 original.
Por tanto, los pasos 1 y 2 de la abstracción procedimental del problema
CM conducen a las especificaciones revisadas siguientes:

PromedioLista(L):
{pre: L= (e" e" """' en) 1\ n:i'a 1\ Vi E {1, """' n}:: e, es un número}
{post: n > a 1\ resultado = Sum i E {1, .""' n}: eJn
v n ~ a 1\ resul tado = a}

En el paso 3, en la abstracción procedimental, convertimos el programa CM


original en un procedimiento o función de Pascal que satisfaga las especifica-
ciones revisadas. La elección entre un procedimiento y una función dependerá
de la forma en que se vaya a utilizar la rutina, asi como de ciertas restricciones
que plantea el lenguaje Pascal en la codificación de procedimientos y funcio-
nes. Por ejemplo, ¿debe devolver la rutina un único resultado o debe devolver
varios? Si sólo tiene que devolver un resultado se suele preferir una función; si
los resultados a devolver son varios, entonces se utilizará un procedimiento. En
otras palabras, ¿invocaremos a la abstracción resultante como si de una ins-
trucción separada se tratase (lo que sugiere la codificación de un procedimien-
to) o, por el contrario, la invocaremos como una función utilizándola dentro
de una expresión aritmética más larga (lo que requiere la codificación de una
función)?
Consideremos la abstracción Promed; al; s ta que estamos construyen-
do. Si la vamos a invocar como una instrucción, debemos codificarla como un
procedimiento Pascal, con todos los parámetros de salida de la cabecera iden-
tificados con el prefijo varo (Para más detalles sobre la sintaxis de los procedi-
mientos de Pascal, y sobre los parámetros var, consúltese el manual de labora-
torio.) Por el contrario, si se va a invocar como una función Promed; ol; sta,
debe implementarse como una función de Pascal. En este caso, todos los pará-
metros deben incluirse en la cabecera de la definición, excepto el resultado, que
se identifica con el propio nombre de la función. En las Figuras 5.1 y 5.2 se
muestran ambas alternativas.
Resolución de problemas algorítmicos 137

p..ocedu.. e P.. omediolista (l: Lista; va .. resultado: reaL);


uses
Listas;
va ..
i, n: integer;
sum: rea l;
begin
(pre: l= (e" e" o e o ) A n;'O A Vi E (1, ... , ni:: e i es un núme .. o}
•• ,

n :~ lengh\;l i sta(L);
if n > O then
begin
Sum :~ O;
i := 1;
while i <= n do
begin
Sum := Sum + l[i];
i := i + 1
end;
..esul tado := Sum/n;
end {if}
else
..esul tado := O
(post: n>OA resultado~Sum; E {1, "0' nI: eJn
v n = O /\ resu l tado = O}
end; (P .. omediolista)

Figura 5.1. Abstracción de Promedi ol; sta como un procedimiento.

En ambas abstracciones hay que destacar algunas caracteristicas. Si con-


frontamos ambas con el programa original (véase Figura 4.6), a partir del cual
han sido construidas, podemos ver que todas las instrucciones de entrada y
salida han sido sustituidas por referencias a los parámetros l y resultado. Es
decir, la abstracción procedimental delega la tarea de hacer la entrada y la
salida al programa que utiliza la abstracción resultante. De hecho, el programa
debe suministrar los argumentos apropiados para que sirvan de entrada al
procedimiento o función, y debe interpretar de forma apropiada la información
de salida que éste le proporcione.
Podemos observar también que el fundamento lógico del programa origi-
nal se ha preservado. En el procedimiento Promediolista (Figura 5.1), el
resultado se asocia con un parámetro varo En el caso de la función Prome-
di ol i st a (Figura 5.2), el resultado se asocia con el propio nombre de la
función. Otras variables declaradas en el programa original (Sum, i y n)
adquieren la categoria de variables locales dentro de la abstracción, puesto que
ahora sólo tienen un interés local exclusivo para el cálculo del promedio.
En la Figura 5.2 se puede observar que el parámetro de salida resultado se
ha reemplazado por la asignación del promedio resultante al propio nombre
de la función. Esta es la forma en que Pascal permite especificar el resultado
que debe devolver una función. En otras palabras, el procedimiento Prome-
diolista de la Figura 5.1 y la función Promediolista de la Figura 5.2
son idénticos.
138 Computación l. Lógica, resolución de problemas, algoritmos y programas

function PromedioLista (L: Lista): real;


uses
Listas;
var
i, n: integer;
sum: rea l;
begin
• {pre: L ~ (e" e" •.. , en) /\ n '" O /\ Vi E {1, ••. , n}: /\ n '" 0/\
Vi E {1, ... , ni:: e, es un número}
n := LenghtLista(L);
i f n > O then
begin
Sum := O;
i := 1;
while i <= n do
begin
Sum:=Sum+L[i];
i := i + 1
end;
PromedioLista := Sum/n;
end {if}
else
PromedioLista := O
{post: n > O /\ resultado = SUII i E {1, .•. , ni: eJn
v n = O /\ resultado = O}
end; {PromediLista}

Figura 5.2. Abstracción de Promedi ol i sta como una función.

Comprobación de la integridad de una abstracción. Antes de utilizar una rutina


que ha sido abstraída en forma de procedimiento o función, debemos asegurar-
nos de que hemos realizado correctamente el proceso de abstracción. Como
mínimo, esto requiere reestructurar el programa original del que hemos parti-
do para realizar la abstracción, y comprobar que el programa sigue satisfacien-
do las especificaciones originales con la abstracción realizada. Para el programa
de la calificación media, esto puede realizarse como se muestra en la Figura 5.3.

prograll calculacM;
{esto es un programa que ca lcu la la CM de una seri e de una o más
calificaciones numéricas, cada una de ellas en el rango O.. 4,
que se introducirán por el teclado}
uses
Listas;
var
Notas: Listas;
CM: real;
function PromedioLista (L: Lista): real;
{Esta función calcula la media de los valores de una lista)
uses
Listas;
var
i, n: integer;
sum: real;
Resolución de problemas algorítmicos 139

begin
{pre: L = (e" e" ... , en) /\ n :;" O /\ Vi E {1, ••• , n}: /\ n :;" O /\
Vi E {1, ... , n}:: e; es un número}
n := LenghtL ista(L};
i1 n > O then
begin
Sum := O;
i := 1;
ilhile i <= n do
begin
Sum :~ Sum + L[i];
i := i + 1
end;
PromedioLista := Sum/n;
end {;t}
else
PromedioLista := O
{post: n > O /\ resu l tado = Su. i E {1, ... , n}: e;!n
v n = O /\ resu l tado = O}
end; {PromediLista}

begin
pre: entrada = (Notas" Notas" ..• , Notas n) /\ n > O /\
Vi E {1, ... , n} :Notas; E {O, ... , 4}}

{Paso 1 . Obtenemos la lista de ca l ;ti cac iones}


WriteLn ('Introducir la lista de calificaciones:');
ReadLista (Notas);
{Nota: Los pasos del 2 al 4 del programa CM original se realizan ahora
invocando a la función PromedioLista}
CM :~ PromedioLista (Notas);

{Paso 5. Se muestra CM}


WriteLn ('La CM de esas cal ificaciones es =', CM : 5 : 2)
end{if}
{post: entrada ~01\ salida ~ Sum i E {1, .. "' n}: Notas;!n}
end. {CalculaCM}

Figura 5.3. Revisión del programa CM utilizando la función Promedi ol i sta.

La revisión de la Figura 5.3 podrá perfectamente haber utilizado el proce-


dimiento en lugar de la función. En este caso, la instrucción

CM := PromedioLista (Notas);

debe sustituirse por la invocación equivalente al procedimiento

PromedioLista (Notas, CM)

Lo importante aqui no es si se utiliza un procedimiento o una función, sino la


estructura del programa resultante. En concreto, obsérvese que el programa ha
reducido su responsabilidad a la de obtener la lista de entrada Notas, y a
visualizar el promedio de notas (pasos 1 y 5, respectivamente, del diseño origi-
140 Computación ,. Lógica, resolución de problemas, algoritmos y programas

nal de la Sección 4.1). A su vez, el programa ha transferido la responsabilidad


de calcular el promedio a la rutina Promedi oL i s ta recién creada. Para
poder hacer esto, el programa necesita suministrar a la rutina una lista de
números (en este caso la variable Notas). El programa tiene que asignar el
resultado devuelto por Promedi oLi sta a la variable CM, de forma que su
responsabilidad frente a la salida esté cubierta. El proceso completo puede
observarse "en la Figura 4.5, donde se ha supuesto que la lista de entrada es
<32 1 3).

5.2.2. Identificación de rutinas nuevas y definición


de sus abstracciones

Una destreza importante que se debe adquirir al resolver problemas algoritmi-


cos es la de reconocer rutinas cuando aparecen en contextos de resolución de
problemas diferentes. Una vez identificada la rutina, puede ser abstraída con
facilidad en forma de procedimiento o función -¡lo ímportante es reconocer la
rutina, no su abstracción!
En el Capítulo 4 introdujimos algoritmos para resolver el problema CM,
para calcular ab , y para contar el número de palabras de una cadena de texto.
Trate de identificar las rutinas que incluyen. Algunas de ellas saltan a la vista.
Por ejemplo, consideremos el cálculo de la suma de los elementos de una lista
de enteros arbitraria, que está incluido dentro del programa CaLcuLaCM (y
también dentro de Promedi oL i sta). Calcular la suma de los elementos de
una lista es seguramente una rutina, puesto que puede ser útil en una gran
variedad de problemas, tales como el de calcular el balance de un libro de
contabilidad o totalizar la recaudación obtenida en un evento deportivo.
¿ Cómo sería el aspecto de una rutina que realice este proceso? Podríamos

Salida
Entrada 2.25
(32 1 3)

Figura 5.4. Utilización de la función Pramedi al i sta


en el programa CaLcuLaCM.
Resolu ción de problem as algorítm icos 141

llamarla Suma Lis t a y escribir sus especificaciones para una lista arbitra
ria L,
como las siguientes:

Suma Lis t a CLl :


{pre: L= Ce" e" ... , e,) /\ n;:,O /\ Vi E {1, ... , n):: e,
{post: result = Sum i E {1, ... , n): ed es un número )

Figuré 5.5. Especi ficació n de la abstrac ción que suma los elemen tos
de una lista.

El progra ma Pot ene i a es otro candid ato a convertirse en rutina,


y pode-
mos convertirlo de progra ma a función con facilidad. De las especif
icaciones
de la Sección 4.4.1, la abstracción procedimental de Poten e i a puede
definirse
como en la Figura 5.6:

Potenc iaCa, b):


{Pre: a y b son entero s /\ Ca '1' O v a = 0/\ b '1' O) /\ Minlnt
{Post: resulta do = a b ) '" a b '" Maxlnt )

Figura 5.6. Especi ficació n de la abstrac ción de la función Poten cia.

Considérese también la posibilidad de encaps ular como una rutina


el progra-
ma Cuen taPal abras . De las especificaciones del Capitu lo 4, la
abstracción
de la Figura 5.7 resulta evidente:

Cuenta Palabr asCs):


{pre: s ~ 'c[1] c[2] •.. c[n]' /\ n >= O /\ cada c[i] es un caráct
{post: resulta do = número de palabr as en s) er ASCII)

Figura 5.7. Especi ficacio nes de la abstrac ción Cuent aPaLa bras.

Existen otros muchos fragmentos de progra mas que tienen gran utilida
d y
que pueden ser convertidos con facilidad en rutinas. Otras cuatro
abstraccio-
nes simples pueden ser de utilidad en una gran variedad de problemas
algorit-
micos que procesan listas. Los dos primeros (Figura 5.8) encuen tran
el lugar
que ocupa (índice) el mayor y el menor elemento de los m primeros
elementos
de una lista L.

MaxLis taCL, m):


{pre: L = Ce" e" ... , e,) n;:' O /\ cada e es un número )
{pos t: n > O /\ Vi E {1, ... , n): e j ;:, e, /\ i resu l tado = j v
n = O /\
resul tado = O)

MinLis taCL, m):


{pre: L = (e 1 , el' .. _, en) n ~ O 1\ cada e, es un número }
{pos t: n > O /\ Vi E {1, ..• , n}: e j ' " e /\ resu l t ado = j v
i n ~ O /\
resul tado = O)

Figura 5.8. Especi ficació n para las abstrac ciones de MaxL i sta y Mi nL
i sta.
142 Computación ,. Lógica, resolución de problemas, algoritmos y programas

La tercera abstracción intercambia o permuta dos elementos de una lista L La


cuarta busca en una lista L y determina si un determinado valor x está o no
dentro de la lista, devolviendo su posición (si es Hue existe). Las especificacio-
nes para ambos se pueden ver en la Figura 5.9:

Intercambiar<L, j, k):
{pre: L = fe" e 2 , •• _, e j , • • _, e k , •• _, en) 1\ 1 ~ j, k ~ n}
{post: L = (e" e" ""., e., "."' e j , • • • , en)}

PosLista(x, Ll:
{pre: L = (e" e" ... , en) 1\ X es un número 1\ n ~ O}
{post: 3i E {1, .""' n}: X = e, 1\ resultado = i v Vi E {1, ... , n}:
X"" e, 1\ resul tado = O}
Figura 5.9. Especificación de las abstracciones Intercambi a r y PostL i sta.

Estas abstracciones precisan algunos comentarios. Consideremos la lista de


la Figura 5.10. Aquí podemos ver que L contiene 12 números, representando
cada uno un valor pluviométrico mensual expresado en litros por m 2 • Por
ejemplo, L[1] representa 2,5 litros por m 2 en el mes de enero. L[2] represen-
ta 4,4 litros por m 2 en el mes de febrero, y así sucesivamente.
Así, la rutina Mi n Lis t a ( L, 7) busca el valor pluviométrico mínímo de
los siete primeros meses del año, y devuelve el índíce 3. La rutina MaxL i s-
ta (L, 12) busca el valor máxímo de los doce de la lista L, devolviendo el
valor 9. PostLista(O.O, L) busca la existencia en L del valor específico
0.0, y devuelve el valor 10, que indica su posición en la lista. Finalmente, la
rutina Intercambiar(L, 2, 9) intercambia o permuta los elementos se-
gundo y noveno de la lista L, como se indica mediante los valores subrayados
en la parte baja de la Figura 5.10.
9

~Ci"''''
~10
( PosLista(O.O,Ll
12l

~'".b;''''' 2, 9l

L = (2.5 6.00.3 5.7 2.1 1.3 1.0 5.9 4.40.0 0.3 2.2)

Figura 5.10. Ilustración de las rutinas MaxL i sta, Mi nL i sta,


Intercambiar y PosLista.
Resolución de problemas algorítmicos 143

Veamos la implementación de una de esas rutinas, Ma xLi s t a ( L, m)


como un ejercicio adicional de la abstracción procedimental. La Figura 5.11
contiene algunas observaciones adicionales sobre una lista arbitraria:

al ir incrementando los valores del indice k entre 1 y m. Para una lista de


longitud 1, su valor máximo sólo puede ser el valor e,. Para k = 2, MaxL is-
ta (L, 2) es el índice del mayor de los valores e, o e 2 , Y si k = 3, MaxL i s-
ta(L, 3) es el índice del mayor entre e 3 Y eM.xList.(L, 2) --es decir, el índice
del mayor de e 3 Y (el mayor de e, Y e 2 ). Este razonamiento puede continuarse
hasta llegar finalmente a que Ma xLi s t a (L, m) es el índice del mayor de e m,
e m -" • • • , e" Y por tanto de toda la lista e m, e m _" • • • , e 2 Y e,. (Los
lectores observadores se habrán percatado de que esta expresión es un ejemplo
de relación de recurrencia, como las que se introdujeron en el Capítulo 2.)

MaxLista(L, k) =1 si k = 1
~k si k> 1 y e k > eMaxListaCL, k - 1)

= MaxL i sta(L, k - 1) si k> 1 y e k ::::;: eJIIIUListaCL, k - 1)

Figura 5.11. Valores de MaxLi sta (L, k) al variar el índice k.

Las consideraciones anteriores sugieren la estrategia de implementación


iterativa siguiente, para calcular el índice j del valor máximo de la sublista de
L que contiene m elementos:

j := 1;
fOI" k := 2 to m do
{invVi E {1, """' k-1}: e[j] >=e[i] /\2<=k<~m+1}
i f L[k] > L[j] then
j := k;
Max :=j;

Obsérvese que el invariante refleja nuestro razonamiento informal sobre cómo


debe calcularse el valor de MaxLista(L, i) para cada valor de i en el
intervalo i, ... , m. Cuando finaliza el bucle, el índice j señalará el elemento
mayor de la sublista L[1, ... , m]. Esto se deduce de la interpretación del
valor final del invariante

{inv: Vi E {1, ... , m}: e[j] >= e[i]}

puesto que k = m + 1 al terminar.


Hemos terminado prácticamente la codificación del cuerpo de la función
MaxL i sta, si exceptuamos la instrucción condicional, o guarda, que debe
144 Computación l. Lógica, resolución de problemas, algoritmos y programas

añadirse para comprobar que se satisfacen las precondiciones. Esto se muestra


en la Figura 5.12, donde se presenta una abstracción procedimental completa
de la función MaxL i s tao Obsérvese que la implementación elegida devuelve el
valor O siempre que no se satisface la precondición. Para esta función, este
valor es el equivalente a resultado <<indefinido».

5.2.3. Funciones recursivas: Una alternativa a la repetición

En el Capítulo 2 vimos que las funciones matemáticas pueden definirse utili-


zando una relación de recurrencia, o recursivamente. Esta idea ha sido trasla-
dada al lenguaje Pascal, de forma que éste nos permite definir funciones recur-
sivas. El hecho de que la función MaxL i sta fuera originalmente concebida de
forma recursiva no es, por tanto, ninguna traba para su traslación a una
función de Pascal. Es decir, podemos escribir una función Pascal que incluya la
relación de recurrencia original. Este tipo de funciones de Pascal reciben el
nombre de funciones recursivas. En la Figura 5.13 puede verse una implementa-
ción recursiva de la función MaxLista. La función refleja directamente la
relación de recurrencia de la Figura 5.11.
Podemos ver que la relación de recurrencia original se ha codificado en
Pascal utilizando una serie de instrucciones i f anidadas. El efecto de la itera-
ción se consigue en la tercera instrucción i f de la serie, donde los aspectos
recursivos de la función han sido subrayados. La función Ma xLi s ta se invoca
a sí misma de forma recurrente, con los argumentos L y m - 1. Así se activan
una serie completa de invocaciones a Ma xLi s t a, para poder determinar:

function MaxLista(L: Lista; m: integer): integer;


var j, k: integer;
begin
{pre: L = (e" ez, .. _, e lll , • • _ , en) 1\ n ~ O /\ cada e i es un número}
i f (O < n) and (m <= LengthLista(L» then
begin
j :~ 1;
for k := 2 to m do
{i nv: \li E {1, ... , k - 1 }: e [j] >= e [i] 1\ 2 <= k <~ m + 1 }
i f L[k] > L[j] then
j := k;
end
else
j := O;
MaxLi sta := j;
{pos t: n > O 1\ \1 i E {1, ... , n}: e j ? e, 1\ resu l tado = j v
n = O 1\ resul tado = O}
end;

Figura 5.12. Abstracción procedimental de MaxLi sta (L, m).


Resolución de problemas algorítmicos 145

el índice del elemento mayor de la sublista de L.

function MaxLista(L: Lista; m: integer): integer;


begin
{pre: L ~ (e" e" ... , e., ""., e o ) /\ n;¡, O /\ cada e, es un número}
i f (O<n) and (m<=LengthLista(L» then
i f m = 1 then
MaxLi sta := 1
else if L[m] > L[MaxLista(L, m - 1 )] then
MaxL i sta := m
else
MaxL i sta :~ MaxLi sta (L, m - 1)
else
MaxL i sta :~ O;
{post: n > O /\ Vi E {1, .""' n}: e j ;¡, e, /\ resul tado = j v
n = O /\ resul tado = O)
end;

Figura 5.13. Implementación recursiva de la función MaxLista(L, m).

Para ilustrar la semántica (el signífícado) de las funciones recursivas, su-


pongamos que se ínvoca a la funcíón con Ma xLi S t a ( L, 4), siendo L = (2 5
4 3 8 6). La secuencia de eventos que se producen en el proceso de cálculo
para calcular el resultado (que es el índíce 2), se muestra en la Tabla 5.1.

Tabla 5.1. Cálculo de MaxLista(L, 4)

Invocación Devuelve
Sublista Invocación Comparación
de em > MaxLista(L, m- 1) el re-
MaxL i sta m de L activa
sultado
1 4 (2 5 4 3) 2 3>MaxLista(L,3) 2
2 3 (2 5 4) 3 4> MaxL ista(L, 2) 2
3 2 (2 5) 4 5>MaxLista(L,1) 2
4 1 (2) ninguno ninguno 1

Cada ínvocación sucesiva a MaxL i sta, comenzando por la primera, reali-


za una comparación entre e m y el valor máxímo de la sublista que contiene un
elemento menos, hasta que se realíza una invocación para la que la longitud de
la lista es 1. (En este caso, esto ocurre en la cuarta invocación). En ese momen-
to se devuelve el resultado, que confirma que e, es siempre el máximo valor de
la lista de longitud 1. El resultado se pasa a la invocación 3, por lo que
MaxL i s ta compara los valores de e, Yez. Puesto que e z es mayor, la invoca-
ción 3 devuelve el índíce 2 a la llamada 2. La invocación 2 compara los valores
de e z Ye 3 , determinando que e z es mayor, por lo que pasa a la ínvocación 1 el
índíce 2. Aquí se realiza finalmente la comparación e 4 > ez' devolviéndose el
valor 2 al programa que ínvocó la función.
Una forma gráfica de ver el proceso de recursíón es dibujando una estruc-
146 Computación l. Lógica, resolución de problemas, algoritmos y programas

tura arborescente que muestre los caminos de invocación, argumentos pasados


y resultados devueltos. En la Figura 5.14 se muestra una estructura de este
tipo.

5.2.4. Aprendizaje de rutinas ya existentes: Bibliotecas


y características de los lenguajes

Muchas de las rutinas que nos sirven de ayuda en el proceso de resolución de


problemas, o están incluidas en el lenguaje de programación, o han sido desa-
rrolladas por otros programadores y son suministradas en forma de bibliotecas
y rutinas utilizables. Por ejemplo, hemos utilizado los tipos elementales; n t e-
ger y rea L de Pascal, junto con sus operaciones aritméticas y booleanas,
como +, -, <, =, y así sucesivamente. También hemos utilizado el tipo
string y algunas de las rutinas de utilidad (Length, Pos, Insert, De-
Lete, y así sucesivamente). Otras características del lenguaje, como Read o
ReadLn, tienen una utilidad más general, puesto que pueden aplicarse a un
gran variedad de tipos como el integer, reaL y string.
También hemos aprendido cosas sobre los tipos Li s ta y Gr id, conteni-
dos en bibliotecas, junto con algunas de sus operaciones (ReadLista,
L[i], MakeGrid, y así sucesivamente). Si no se nos hubieran suministrado
esas rutinas, tendríamos que haberlas construido de la nada, antes de ponernos
a resolver muchos problemas algorítmicos interesantes.

Figura 5.14. Estructura de la invocación recursiva de MaxLista(L, 4>-


El número de la invocación está dentro de un círculo; las flechas
descendentes simbolizan argumentos y las ascendentes resultados.
Resolución de problemas algorítmicos 147

En el manual de laboratorio puede encontrarse una discusión más detalla-


da sobre esos tipos y otros de Pascal igualmente útiles, junto con sus rutinas
relacionadas. Las definiciones siguientes de algunas de estas rutinas están escri-
tas en el ya familiar estilo de definición procedimental que se introdujo en la
Sección 5.2.1.

ReadLista(L):
{pre: entrada = (e" e u ..• , en) cualquier secuencia A n;. D}
{post: ent rada = cua lqui er secuenci a A L = (e" e u ... , en)}

L[ i] :
{pre: L= (e" e u •.. , en) A 1 ~i ~n}
{post: e, es un número A resul tado = e i v e, no es un número A
resul tado = D}

Writeln(s" Su •.• , sn):


{pre: entrada = cualquier secuencia A Vi E {1, .•• , ni: Si es un número
o una cadena}
{post: salida = cualquier secuencia s" s" .... , sn}
poses, t):
{pre: t = 't 1 , t z , • __ , t n l A S = '$" 52' •• _, Srw l / \ n ~ O}
{post: 5= t,t<+, ... t'...._1 (1 ~ i ~ i + m - 1 ~ n) es la ocurrencia más a la
izquierda de s en t A resultado = i v s no está en t A resultado = D}

Aquí, la noción cua lqui er secuenci a asociada a las rutinas Read-


Lis t a y Wr i te l n significan, literalmente, cualquier secuencia de valores de
entrada o salida existentes en el momento en que las rutinas son ejecutadas. En
el caso de ReadL i sta, la notación entrada = (e" el' ••. , en) cua l-
quier secuencia, significa que existe una lista al principio de la secuencia
de entrada, seguida de una secuencia arbitraria de otros posibles valores (que
puede no incluir ninguno). La utilización de cualquier secuencia en la especifi-
cación de wr i te l n, significa que coloca la salida a la cola de todas las salidas
que se hayan generado antes de la ejecución de la instrucción wr i te l n.

5.2.5. Selección y reutilización de tipos de datos y estructuras

Sabemos ya que es importante para su reutilización la identificación y encap-


sulamiento de rutinas. También debemos ejercitarnos en ser cuidadosos cuan-
do seleccionamos tipos de datos y estructuras para diferentes problemas con
los que estamos familiarizados.
Hemos trabajado ya con los tipos numéricos básicos (rea le i nteger), el
tipo string y con los tipos Lista y Array, que se utilizan para definir
estructuras lineales simples. Sabemos, por la experiencia adquirida, que las
operaciones para el tipo rea l también pueden aplicarse, aunque con algunas
restricciones, al i nteger. Sabemos también que rutinas con nombre similares
para los tipos Lis ta y s tri ng, tienen significados similares. Por ejemplo, la
rutina Pos (s, t), que devuelve la posición donde aparece por primera vez la
148 Computación l. Lógica, resolución de problemas, algoritmos V programas

cadena t en la s, es totalmente análoga PosL;sta(x, U, que localiza el


elemento x en L. De forma similar, la referencia L[; J, que selecciona el ele-
mento ; -ésimo de la lista L, se utiliza en la misma forma para seleccionar el
;-ésimo elemento de un array.
Sin embargo, existen sutiles diferencias entre esas alternativas. Por ejemplo,
no podemos aplicar operadores aritméticos a valores tipo s t r; ng, aunque
éstos teng;m connotaciones numéricas; instrucciones tales como:

5:=5+'1.5'

(donde s simboliza una variable cadena) son incorrectas. Del mismo modo,
podemos leer y almacenar en una lista una colección completa de números
reales, tanto de un archivo externo como desde el teclado, utilizando la opera-
ción ReadL; sta. Sin embargo, no podemos hacer lo mismo si queremos leer
y almacenar una colección de cadenas. Podemos utilizar un a r rayen lugar de
una lista para almacenar una colección de cadenas, pero carecemos de la
flexibilidad para leerlas y almacenarlas si no inventamos y construimos las
rutinas que tengan la capacidad funcional de ReadL; sta. Estas diferencias
están resumidas en la Figura 5.15, que muestra, en una especie de diagrama de
Venn, el solapamiento funcional que existe entre a r rays y Listas. El men-
saje básico de estos párrafos es que ninguna estructura de datos ofrece todas
las ventajas de otra sin acarrear algunas desventajas. Esta situación es típica en
la informática; los profesionales de la informática se refieren a ella como «el
compromiso y las consecuencias» de tomar una decisión.

Figura 5.15. Diferencias entre l; stas, arrays, cadenas y números.


Resolución de problemas algoritmicos 149

El tipo cadena es el más básico de los tipos construidos con valores elemen-
tales, puesto que puede contener cualquier número de caracteres separados por
blancos u otros caracteres ASCII no imprimibles (caracteres de control, tales
como saltos de línea y tabuladores). Así, cualquier carácter es una cadena, pero
no cualquier cadena es un carácter; por ejemplo, el carácter a es una cadena,
pero la cadena Pepe no es un carácter. Continuando con esta línea de razona-
miento, c'lda dígito numérico es un carácter, pero un carácter individual no
tiene por qué ser un dígito numérico. Por ejemplo, el dígito 3 es un carácter,
pero el carácter x no es dígito numérico. Igualmente, si colocamos un número
entre comillas simples tendremos una cadena, pero no toda cadena es un
número. Así, I 3 . 5 I es una cadena, pero I 1v á n I no es un número.

5.2.6. Arrays de cadenas de caracteres

Nos gustaría extender la potencia y utilidad del tipo st r; ng incorporándolo


dentro de la idea de l; sta. Esto nos permitiría aplicar todas las rutinas de
l; s ta s, que nos son tan familiares, a listas de cadena s de caracteres en la
misma forma en que lo hicimos en el Capítulo 4 con las listas de rea les.
Puesto que el tipo string es más general que el de integer o real, es
esperable que el tipo lista de s tri ng sea de utilidad en una gama de proble-
mas algorítmicos más amplia que el de las listas de rea les.
Desgraciadamente, no podemos extender la idea de lis ta de esta forma.
Tenemos que implementar las listas de cadenas utilizando arrays. El tipo
de elementos de un array puede ser establecido arbitrariamente por el pro-
gramador. Supongamos que escribimos una frase como una lista de palabras,
separadas entre sí por saltos de línea. La frase:

Iván caminó 3 ki Lómetros hasta eL coLegio a una veLocidad de


2,5 ki Lómetros por hora

Podría introducirse por el teclado en la forma siguiente:

Iván RET
cami nó RET
3 RET
ki Lómetros RET
hasta RET
eL RET
coLegio RET
a RET
una RET
veLocidad RET
de RET
2,5 RET
ki Lómetros RET
por RET
hora RET
RET
150 Computación l. Lógica, resolución de problemas, algoritmos y programas

Es posible declarar el tipo de array siguiente, y una variable del mismo que
permite almacenar cada palabra del texto:
type palabra = string[16];
ListaStrings = a ....ay[1 .. 100] of palab ..a;
va .. Frase: ListaSt .. ings;
LongitudFrase: integer;
Apala~ra: palab ..a;

La definición anterior permite manipular frases de 100 palabras o menos,


conteniendo cada palabra 16 caracteres o menos (lo que es una suposición
bastante razonable para el lenguaje cotidiano). La terminación de la frase se
señaliza con dos saltos de línea sucesivos (RET RET).
Si queremos leer una serie de cadenas en la variable f rase e identificar el
número de palabras con la variable Long; tudPa labra, debemos diseñar el
código que realice esta operación. Es decir, el código siguiente podría ser la
base de una abstracción procedimental:
LongitudFrase := o;
ReadLn(APalabra)
whi le APa lab ..a <> ' , do
begin
LongitudFrase := LongitudFrase + 1;
Frase[LongitudFrase] := APalabra;
ReadLn(APalabra)
end

De forma análoga, es posible implementar inserciones, eliminaciones y búsque-


das de palabras aisladas utilízando abstracciones procedurales. De esta forma,
podemos manipular listas de palabras de la misma forma que manipulamos
l; s tas de rea l es, posibilidad que es de gran utilidad en gran cantidad de
aplícaciones.

5.2.7. Tipificación fuerte y coerción


Es complícado cambiar de tipos dentro del lenguaje Pascal, porque el lenguaje
obliga a los programadores a hacer distinciones explícitas entre los valores del
tipo string, char, real e integer, dependiendo de la rutina concreta
(procedimiento o función) utilizada y del contexto sintáctico en el que se utili-
za. Esta característica de los lenguajes de programación recibe el nombre de
tipificación fuerte.

Definición. Un lenguaje de programaclOn es fuertemente-tipificado si el


tipo de cada variable y constante que interviene en cada instrucción indivi-
dual está restringida explícitamente al tipo de cálculo en el que se utiliza.

Los ejemplos de tipificación fuerte pueden encontrarse en los programas del


Capítulo 4:
Resolución de problemas algorítmicos 151

1. La instrucción i : = i + 1, del programa Po t en c i a de la Figura 4.14,


necesita que i sea una variable numérica --es decir, rea lo i nteger.
Si i hubiera sido declarada como un string, la idea de utilizar su
valor en una operación aritmética, como una suma, sería incorrecta,
incluso si su valor pudiera ser interpretable como un número (por
ejemplo, la cadena '2.2 ').
2. La expresión Copy(s, i, 1) <> ", del programa ContarPaLa-
bra s de la Figura 4.17, precisa que s sea una cadena y que i sea un
entero. La propia Copy debe devolver un valor de tipo s t ri ng, pues-
to que el resultado debe ser comparado (utilizando el operador <» con
la cadena ' '.
3. La instrucción Tu r n Ce LL( t a b Le ro, i, j), del programa de
las Tres-en-Raya de la Figura 4.21, necesita que sus argumentos
sean de los tipos grid, integer e integer, respectivamente.

Supongamos que queremos reinterpretar un valor de un tipo como valor


de otro diferente -por ejemplo, interpretar la cadena '2.5 I como el número
real 2,5--. Para hacer esto en un lenguaje fuertemente tipificado como Pascal,
necesitamos utilizar una rutina especifica que convierta el valor de tipo string
en uno de tipo real. Este es el papel de la rutina de Pascal Val (véase Sección
5.2.6). La utilización de una rutina de este tipo recibe el nombre de coerción. La
coerción en la dirección opuesta le permite la función s t r. Las rutinas siguien-
tes están definidas en Turbo Pascal para permitir la coerción entre reales y
cadenas:

valCs, r, código)
{pre: s es una cadena)
{post: r = valor real equivalente a s 1\ código = O si la coerción
se rea liza con éx ito
strCr, s);
{pre: r es un número real J
{post: s es la cadena equivalente a r)

Pascal realiza la coerción directa entre reales y enteros. Los enteros no


necesitan ser convertidos explícitamente a los reales equivalentes, puesto que se
pueden considerar un subconjunto de ellos. Por ejemplo, el entero 1 se inter-
preta siempre que sea necesario como el real 1 • O. Las rutinas siguientes con-
vierten un número real en entero, bien redondeándolo al entero más próximo,
bien truncándolo, respectivamente.

roundCx);
{pre: x es un número rea l )
{post: resultado=lx+O.5J}

trunc(x) :
{pre: x es un número rea l)
(post: resultado = lxJ}
152 Computación l. Lógica, resolución de problemas, algoritmos y programas

Por tanto, si queremos tratar el i-ésimo elemento de una lista L como un entero,
debemos convertirlo explícitamente utilizando una de las rutinas round o
trunco Por ejemplo, la instrucción siguiente asigna la parte entera (ignorando
la parte decimal) del i -ésimo elemento de L a la variable in t ege r j.

j :~ truncCL[i]);
,
¿Por qué son tan necesarias y útiles la tipificación fuerte y la coerción?
Existen varias razones. Primero, los lenguajes fuertemente tipificados suelen ser
más efectivos que los débilmente tipificados como vehículos de enseñanza de
los principios de la programación, por el simple hecho de que sus compiladores
detectan un rango de errores más amplio, y sugieren la forma de corregirlos
antes de que se produzca la ejecución del programa. En algunas ocasiones, las
listas incluyen elementos de distintos tipos, por lo que el programa debe identi-
ficar tanto los tipos como los valores de cada elemento. La lista siguiente
incluye algunos datos en los que cadenas y reales se alternan en una serie no
ordenada cronológicamente de valores pluviométricos de los meses del año.
Las listas como esta, en que cada valor numérico está precedido inmediata-
mente por un nombre descriptivo, reciben el nombre de listas con atributos.

L = (Enero 2,5
Septi embre 6,0
Febrero 4,4
Mayo 2,1
Junio 1,3
Novi embre 0,3
Diciembre 2,2
Julio 5,9
Marzo 0,3
Abril 5,7
Agosto 1,0
Octubre 0,0)

Ejercicios

5.1. Describir qué es lo que hacen los programas siguientes:


a) function nooz (L: lista) : integer;
val'
i: i nteger;
begin
nooz := O;
for i := 1 to LengthLista(L) do
i f L[i] =0,0 then
nooz := nooz + 1;
end;
Resolución de problemas algorítmicos 153

b) program fn;
var
k: integer;
function nd (m: integer>: integer;
var
n: integer;
begin
nd := O;
n := m;
while n >= 1 do
begin
n := n div 10;
nd := nd + 1
end
end;
begin
whi le not eof do
begin
readln (k>;
writeln ('ans = " nd(k»
end
end.

5.2. Implementar la rutina SumL; s ta, definida en la Figura 5.5, como una
función de Pascal. Probar que la función puede reutilizarse para conse-
guir una implementación alternativa del procedimiento Promed i 0-
Lis t a, diferente a la de la Figura 5.1.

5.3. Construir una función, a la que llamaremos MaxFactor, que calcule


el máximo factor de un entero n, que sea menor que el propio n (por
ejemplo, MaxFactor(100) = 50, MaxFactor(15) = 5, Y Max-
Factor(13) = 1).

5.4. Supongamos que A, B Y C representan la longitud de los lados de un


triángulo. Construir una función que devuelva t rue si A, B YC son los
lados de un triángulo rectángulo.

5.5. ¿En qué condiciones puede convertirse un procedimiento en una fun-


ción equivalente? ¿En qué condiciones puede realizarse la operación
inversa? Razonar la respuesta.

5.6. Convertir el programa factorial del Ejercicio 4.22 en una función.

5.7. Implementar como un procedimiento de Pascal la abstracción Con-


tarPalabraCs) de la Figura 5.7. Comprobar su integridad reutili-
zándola para resolver el programa original que contaba palabras del
Capítulo 4 (véase Figura 4.17).
154 Computación ,. Lógica, resolución de problemas, algoritmos y programas

5.8. Ilustrar el comportamiento de la implementación recursiva de Max-


Lista(L, m) (Figura 5.8), mostrando la secuencia de invocaciones y
resultados devueltos, cuando L = (6 5 4 3 2 1) Y m = 6. ¿Existe
alguna reducción en el número de invocaciones que son precisas cuando
el máximo valor ocupar el primer lugar de la lista? Razonarlo.

5.9. Reesdribir la función siguiente, pero reemplazando el bucle far por


uno whi Le equivalente; es decir, quc conduzca al mismo resultado.
Escribir pre y poscondiciones que describan el resultado. ¿En qué medi-
da es esta función similar a la operación Mi nL i sta (Figura 5.8)?

type ArrayLista = array[1 .. 255] of real;


function IndiceDelMenor<UnArray: ArrayLista; n: integer):
integer;
(devuelve el indice del menor de los elementos de un array de
longi tud n}
var IndiceDelMenorPorAhora: integer;
begin
Indi ceDelMenorPorAhora := 1;
for i ;= 2 to n do
i f UnArray[i] < UnArray[Indi ceDelMenorPorAhora] then
Indi ceDe lMenorPorAhora :~ i;
Indi ceDe lMenor := Indi ceDe lMenorPorAhora;
end;

5.10. Escribir una función recursiva SumL i sta (L, n) que devuelva la suma
de los n primeros elementos de la lista L.

5.11. a) Escribir la función recursiva Fib(n) que calcule para el entero n el


n-ésimo número de Fibonacci, que se define de la forma siguiente:

Fib(n) o para n O
1 para n 1
Fib(n - 1) + Fib(n 2) para n > 1

b) Dibujar la representación gráfica de la cadena de invocaciones


cuando se invoca con F i b (4).
e) Reescribir la función, pero sin utilizar recurrencias; es decir, utili-
zando un bucle en lugar de llamadas recursivas.

5.12. a) Describir en castellano qué hace la función siguiente.

function MyFun (n: integer): integer;


begin
i f n ~ O then
MyFun := O
else i f n mod 2 = O then
Resolución de problemas algorítmicos 155

MyFun := n + MyFun<n - 1)
else
MyFun :~ MyFun<n - 1)
end;

b) Escribir las pre y poscondiciones de la función My Fun.

5.13. Utillzando una linea de razonamiento similar a la utilizada para imple-


mentar MaxL i sta (L, m), desarrollar una implementación de la fun-
ción Mi n Lis t a definida en la Sección 5.2.2. Si la implementación con-
tiene un bucle, incluir la descripción del invariante del bucle.

5.14. Realizar la abstracción procedimental de la rutina PosLista(x, U,


definida en la Sección 5.2.2, y escribir en Pascal una función no recursi-
va de la abstracción.

5.15. La rutina PosL i sta (x, U es muy útil cuando queremos determinar
si una lista tiene elementos repetidos. Escribir un programa en Pascal
que, para una lista arbitraria de númcros L, utilice PosL i s ta para
ayudarnos a descubrir y escribir los números que aparezcan más de una
vez. Por ejemplo, la lista L = (1 2 3 5 4 1 5 1 ) hará que el programa
escriba las duplicidades siguientes:

1
5

Describir un problema algorítmico de la vida cotidiana en que sea de


utilidad una rutina que localice duplicidades.

5.16. Escribir una implementación recursiva de la función PosL i sta.

5.17. Escribir la función Primos Re La t i vos que tenga dos enteros n y m


como argumentos, y devuelva el valor booleano t rue, si y sólo si m y n
no tienen ningún divisor común más que 1. Por ejemplo,

PrimosRelativos<B, 9) = true
PrimosRelativos<B, 10) ~ false

puesto que 8 y 10 tiene un divisor común que es 2.

5.18. Escribir una función que devuelva el valor t r ue si el primer elemento


de una Lis ta es mayor que el resto de elementos. La función se llama-
rá EsMayor y tendrá un solo parámetro, que designará a la lista. Escri-
bir un programa de prueba que invoque a la función para establecer si
trabaja correctamente.
156 Computación l. Lógica, resolución de problemas, algoritmos y programas

5.19. Diseñar y escribir en Pascal una función que examine los n elementos
de una lista y devuelva t rue si los elementos están ordenados en orden
decreciente, y fa l se en caso contrario. La función se llamará Prueba
y tiene que tener un parámetro para el nombre de la lis ta, y otros
para el número de elementos n que deben examinarse. Escribir un pro-
grama de prueba que invoque a la función para establecer si trabaja
correctamente.

5.3. RESOLUCIÓN DE UN PROBLEMA UTILIZANDO


LA METODOLOGíA MAPS

Ya estamos en condiciones de utilizar nuestro método de resolución de proble-


mas (MAPS) y utilizarlo en un caso concreto, desde el principio hasta el final.
Durante todo el proceso enfatizaremos la importacia de identificar y reutilizar
rutinas en una forma creativa. Una de las características destacables de MAPS
es que no sólo sirve como guía en la resolución de problemas particulares, sino
que en este proceso se van creando rutinas que el programador podrá reutili-
zar en la resolución de otros problemas más complejos.

5.3.1. El diálogo

El problema que vamos a resolver es el de construír un algoritmo que tenga


como entrada una lista de valores pluviométricos, y escriba los meses cuyos
valores pluviométricos estén por debajo de la media, el valor pluviométrico
más alto y más bajo de cada mes, y el mes que dio el valor pluviométrico más
alto.
Nuestra primera reacción ante la explicación anterior es que es muy vaga.
Es decir, se han omitido muchos detalles. Necesitamos mucha más información
antes de pasar a la etapa siguiente en la resolución del problema. Esto es muy
típico en la resolución de problemas; no es buen proceder el admitir especifica-
ciones ambiguas y realizar preguntas que dejen sin clarificar algunos detalles.
Los resolutores de problemas inteligentes preguntan primero sobre las cuestio-
nes que necesiten ser clarificadas, antes de proceder a la búsqueda de una
solución. Asi, debemos tener un diálogo con la persona que nos presenta el
problema, hasta que consigamos tener un conocimiento preciso del mismo.
Para el problema que nos ocupa, algunas cosas que deberíamos preguntar
podrían ser las siguientes:
1. ¿Cómo se nos presentará la entrada? ¿Cronológicamente por meses?
¿Por valores pluviométricos? ¿Consistirá la entrada en una o dos lis-
tas? ¿Estarán los meses explícitamente representados en la entrada, o se
presentará las lecturas únicamente organizadas en orden cronológico?
2. ¿Cuál es un valor válido de pluviometría? Es decir, un valor pluviomé-
trico ¿puede ser cualquier número real no negativo?, ¿existe un valor
máximo para las lecturas?
Resolución de problemas algorítmicos 157

3. En la salida, ¿deben aparecer los meses con pluviometria inferior al


promedio, en orden cronológico, en orden de indices pluviométricos
relativos o, simplemente, en el mismo orden en que aparecieron las
lecturas en la entrada? ¿Es necesario escribir el valor medio del indice
pluviométrico?
4. ¿Puede ocurrir que más de un mes tengan el valor pluviométrico más
alto? En ese caso, ¿debe mostrar el algoritmo el nombre de todos esos
rbeses?
Tratando de clarificar lo anterior, suele ser conveniente dar una muestra de la
entrada y de la salida correspondiente del problema; ¡una imagen vale más que
mil palabras! Los ejemplos sirven para resolver de forma visual muchas de las
preguntas anteriores, e incluso nos proporcionan pistas sobre cómo debemos
resolver el problema. En la Figura 5.16, la entrada se presenta en una lista
sencilla, y el programa debe distinguir entre nombre de meses (entradas de la
lista con índices impares 1, 3, 5, ...) Yvalores pluviométricos (entradas de la lista
con índices pares 2, 4, 6, ...).

ENTRADA SALIDA

(Enero 2,5 ESTADISTICAS PLUVIOMETRICAS


Febrero 4,4 medi a = 2,64
Marzo 0,3 máxima = 6,0
Abril 5,7 minima = 0,0
Mayo 2,1 Meses por debajo de la media:
Junio 1,3 Enero
Julio 5,9 Marzo
Agosto 1,0 Mayo
Septiembre 6,0 Junio
Octubre 0,0 Agosto
Novi embre 0,3 Octubre
Di c i embre 2,2) Noviembre
Diciembre
Mes(es) máxima:
Septiembre

Figura 5.16. Ejemplo de entrada y salida para el problema


de la pluviometría.

Sin embargo, la entrada y salida para este problema pueden presentarse


perfectamente de otras maneras. Dos alternativas razonables se muestran en la
Figura 5.17. En una de las alternativas, la entrada se suministra en dos listas en
lugar de en una; en la otra, la entrada se presenta sin los nombres de los meses.
En este caso, el programa debe generar los nombres de los doce meses y
suponer que los valores de pluviometría están suministrados en orden cronoló-
gico --es decir, la primera entrada es el valor de enero, la segunda el de
febrero, y así sucesivamente.
158 Computación ,. Lógica, resolución de problemas, algoritmos y programas

CEnero, Febrero
Marzo, Abri L
Mayo, Junio
JuL i o, Agosto
Septiembre, Octubre
Noviembre, Diciembre)

2.5 ~.4 C2.5 4.4


0.3 5.7 0.3 5.7
2.1 1.3 2.1 1.3
5.9 1.0 5.9 1.0
6.0 0.0 6.0 0.0
0.3 2.2) 0.3 2.2)

a) b)
Figura 5.17. Dos representaciones alternativas de la entrada para el problema
de la pluviometría. al Dos listas de entrada. b) La lista de los valores pluviomé-
tricos como única entrada.

5.3.2. Las especificaciones

Supongamos que la Figura 5.17b muestra la organización de la entrada que


espera el programa, y la Figura 5.16 la de la salida. Podemos entonces cons-
truir de una forma más precisa las especificaciones del programa, es decir, las
precondiciones y postcondiciones. Estas especificaciones servirán para clarifi-
car los detalles de la solución al problema, que desarrollaremos en pasos si-
guientes. La precondición para el problema de la pluviometría puede expresar-
se de la forma siguíente:

{pre: entrada = Ce" e" .•. , e ,2 ) /\ Vi E {1, ... ,12) e i es eL vaLor


pLuviométrico deL i-ésimo mes deL año)

Así, si se suministrase al programa la entrada particular de la Figura 5.l7b, se


satisfaría esta precondición; el programa asignaría a e z el valor 4.4 Its/m 2 (el
valor pluviométríco de febrero), y así sucesivamente. Sin embargo, el lector se
percatará de que, si la entrada se suministrase en una de las otras formas
alternativas, no se satisfaria la precondición.
La poscondición para este problema puede definirse de la forma siguiente:

{post: entrada =0/\ saLida = ESTADIsnCAS PLUVIOMETRICAS


media = Sum i E {1, ,12): e i /12
má x i ma ~ Max i E {1, , 12): e i
minima = Min i E {1, ,12): e i
Meses por debajo de La media:
{m;: e; < Su.i E {1, ... ,12): e,l12)
MesCes) ma><ima:
[mi: e j = Max iE (1, ... ,12): e i /\ 1o;;jo;;12))
Resolución de problemas algoritmicos 159

En este caso, la especificación de entrada describe la forma general y la esencia


de la misma, y la especificación de salida la relación que deben guardar entra-
da y salida. Por ejemplo, podemos ver que son necesarias dos listas -una lista
(mi' ml' ..., miz) con los doce meses del año y una lista con los doce valores
pluviométricos. A la vista de esto, debe existir algún tipo de paralelismo entre
ambas listas. (Obsérvese que, aunque la lista de meses no se suministra como
entrada, deb~ ser generada de alguna forma por el programa en forma de un
array de doce posiciones). Es decir, el hecho de que m2 = Febrero y que e 2 =
4,4 significa que el valor pluviométrico de febrero es 4,4 lts/m 2 .
Por tanto, la solución al problema puede describirse en términos de aque-
llos meses cuyo valor pluviométrico guarda alguna relación con el máximo,
mínimo y valor pluviométrico medio. En este caso, la notación de la lógica es
especialmente útil, pues permite expresar las especificaciones de forma breve y
precisa. Una descripción en castellano de las mismas entradas y salidas podria
no estar escrita con la misma precisión y brevedad.

5.3.3. La partición

Muchos objetivos o pasos de este problema son evidentes, si nos ponemos


a pensar sobre el problema y sus especificaciones. Inicialmente, nuestra so-
lución debe generar la lista con los nombres de los meses y obtener de la
entrada la lista de valores pluviométricos. También nuestra solución necesita
calcular el valor medio, el máximo y mínimo de los valores pluviométricos.
Necesitará escribir Jos nombres de aquellos meses cuyo valor pluviométrico
esté por debajo de la media, así como el(los) mes(es) que tengan el máximo
de valor pluviométrico.
La solución a este problema puede ser dividida, inicialmente, en las cuatro
rutinas y cinco elementos de datos que se muestran en la Figura 5.18. Hemos
hecho dos cosas importantes en esta etapa inicial del proceso de partición;
hemos identificado las principales rutinas que serán necesarias, y hemos asig-
nado nombre a las listas principales (meses y pLuv;ometr;as) y a las
variables principales PLuv;ometr;aProm, MaxPLuv;ometr;a y M;n-
PLuv;ometria) que precisa la solución.
Recapacitemos ahora acerca de dos nuevos elementos del proceso de reso-
lución de problemas -la relación secuencial entre rutinas y la posibilidad de
utilizar rutinas ya conocidas como ayuda para la construcción de la solución al
problema que nos ocupa-o Podemos determinar informalmente el secuencia-
miento de los eventos descritos en la Figura 5.18 (simplemente pensando en
qué debe ocurrir primero, qué segundo, etc.), o formalmente (escribiendo pre
y poscondiciones para cada rutina que hayamos identificado, u ordenarlas
lógicamente según el contexto que imponen las especificaciones del proble-
ma principal). Un análisis informal conduce al orden inicial descrito en la
Figura 5.19, junto con las especificaciones del programa original, ocupando su
lugar.
160 Computación l. Lógica, resolución de problemas, algoritmos y programas

Rutinas

Inicializar Mostrar meses


lista de meses por debajo
y pluviometrías del promedio

Mostrar pluviometría Mostrar mes(es)


promedio. máxima con pluviometría
y mínima más alta

Elementos de datos

Meses Un array de doce nombre de meses.


Pluviometrias Una lista de doce lecturas de valores pluviométricos.
PluviometriaPorm El valor medio de las lecturas.
MaxPluviometria El valor máximo de pluviometría de los doce meses.
MinPluviometria El valor mínimo de pluviometría de los doce meses.

Figura 5.18. Partición en rutinas y elementos de datos para el problema


de la pluviometría.

{pre: entrada = (e" e" " .. , e,.) /\ Vi E {1, """' 12) e, es el valor
pluviométrico del i-ésimo mes del año}

Paso 1. Inicializar lista de meses y pluviometrías

Paso 2. Mostrar pluviometría promedio, máxima y mínima

Paso 3. Mostrar meses por debajo del promedio

Paso 4. Mostrar el(los) mes(es) con pluviometría más alta

{post: entrada =0/\ salida = ESTAOfSnCAS PLUVIOMIOTRICAS


media = Sum i E {1, , 12): e;l12
máxima = Max i E {1, , 12): e,
minima = Min i E {1, , 12): e,
Meses por debajo de la media:
{m j : e j < SUII i E {1, ... , 12): e ;l12}
Mes(es) máxima:
(m;: e; ~ Sum i E (1, """' 12): e;l12}

Figura 5.19. Orden inicial y especificaciones para el problema


de los valores pluviométricos.
Resolución de problemas algorítmicos 161

Podemos observar que las cuatro rutinas deben colocarse en este orden
para que la poscondición quede satisfecha. En concreto, la lectura de los valo-
res pluviométricos debe preceder a todos los demás pasos. Por si mismo, el
primer paso provoca la satisfacción de la especificación entrada = 0 de la
poscondición. Del mismo modo, a continuación debe calcularse y escribirse el
valor promedio por dos razones: Primero, el promedio es necesario como
entrada (calilla precondición) del paso 3, donde necesitamos calcular y visuali-
zar los meses cuya pluviométria está por debajo del promedio, y del paso 4,
donde encontramos y visualizamos el(los) mes(es) con máximo valor pluviomé-
trico. Segundo, la poscondición del problema exige que los valores promedio,
máximo y mínimo de pluviometría deben mostrarse antes de que se produzcan
las salidas de los pasos 3 y 4. Finalmente, el paso 3 debe preceder al 4, puesto
que sus salidas respectivas deben realizarse en este orden según las poscondi-
ciones del problema.

Ejercicios

5.20. Supóngase que se utiliza la metodología MAPS para diseñar un progra-


ma que lea un número expresado en cifras romanas, y que muestre su
valor en números arábigos. En las etapas de diálogo y especificación, se
ha establecido que la entrada será una cadena de letras mayúsculas del
conjunto (1, V, X, L, C, D, M). Recuerde que los valores de esos digitos
romanos son 1, S, 10, SO, 100, SOO, 1000, respectivamente. Se ha decidido
también que los digitos en la entrada estarán en orden no creciente de
valores; es decir, el número arábigo 4 se escribirá como el numeral UU
(en lugar de IV). Con estas hipótesis, escribir detalladamente las especifi-
caciones y realizar la fase de partición de la metodología MAPS.

5.21. Escribir la salida que aparecería si mezclamos los dos bucles for de los
pasos 3 y 4, del problema de la pluviometría, en un solo bucle for que
contenga las dos selecciones ; f, ante la entrada ejemplo de la Figu-
ra S.17. Es decir, suponer que se mezclan los pasos 3 y 4 de la forma
siguiente:

WriteLn('Meses por debajo de La media');


WriteLn( 'Mes(es) máxima:');
for i := 1 to LenghtLista(Pluviometrías) then
begin
i f Pluviometrias[i] < PLuvíometriaProm then
WriteLn(' " Meses[i]);
i f Pluvíometrías[i] = PluviometríaProm then
WriteLn(' " Meses[i]);
end;

5.22. Escribir un invariante para el bucle del paso 3 del problema de la


pluviometría.
162 Computación ,. Lógica, resolución de problemas, algoritmos y programas

5.4. DEFINICiÓN DE ABSTRACCIONES: UNIFICACiÓN


DE RUTINAS VIEJAS CON IDEAS NUEVAS
Hemos definido y desarrollado la solución al problema de la pluviometría,
hasta un punto en que es posible evaluar el esfuerzo necesario para solucionar
cada una de sus partes. Es decir, debemos decidir qué pasos deben ser subdivi-
didos en subpasQs. En nuestro ejemplo, los pasos 1 y 2 pueden ser subdivididos
nuevamente para clarificar sus objetivos individuales.
Estas nuevas subdivisiones pueden verse en la Figura 5.20. En la figura
podemos ver que se ha subdividido el paso 1 en dos subpasos 1.1 y 1.2, que
asignan la lista de nombre de meses y leen la lista de pluviometrías, respectiva-
mente. El paso 3 tiene tres componentes: el paso 2.1 calcula y escribe el valor
pluviométrico medio; el paso 2.2 calcula y escribe el valor pluviométrico máxi-
mo; y el paso 2.3 calcula y escribe el valor pluviométrico mínimo. Obsérvese
que los subpasos diseñados se han secuenciado apropiadamente, con objeto de
satisfacer el orden de las dos listas, y el que especifica la poscondición para las
salidas. Este cuidado, que puede parecer excesivo para un problema tan simple,
se hace más importante cuanto más complejo es el problema a resolver.

Paso 1. Inicializar lista de meses y pluviometría

I Paso 1.1. Asignar lista de meses I


L
Paso 1.2. Leer lista de valores pluviométricos

Paso 2. Mostrar pluviometría promedio, máxima y minima

Paso 2.1. Calcular y mostrar pluviometria media

Paso 2.2. Calcular y mostrar pluviometría máxima

Paso 2.3. Calcular y mostrar pluviometría mínima

Figura 5.20. Partición adicional de los pasos 1 y 2 del problema


de la pluviometría.

Hemos de continuar subdividiendo y secuenciando los pasos individuales


hasta que cada subpaso sea reconocible con claridad como una rutina, sea
resoluble mediante la combinación de otras existentes (conocidas), o pueda ser
resuelto directamente creando una rutina nueva. Podemos ver que es necesario
desarrollar una rutina nueva para el paso 1.1. Podemos utilizar la rutina
Resolución de problemas algorítmicos 163

ReadLista para el paso 1.2. Podemos combinar dos rutinas, Promed;o-


Lista y WriteLn para resolver el paso 2.1 (el cálculo del promedio y la
visualización del resultado son dos acciones separadas, y por tanto deben
realizarlas rutinas diferentes). De forma similar, podemos combinar las rutinas
MaxL; sta y Wri teLn para el paso 2.2, y las rutinas Mi nL i sta y Wr; teLn
para el paso 2.3.
Ni el paso 3 ni el 4 parecen resolubles mediante la aplicación directa ni la
combinación de rutinas. No hemos visto hasta ahora ninguna rutina que re-
suelva directamente problemas como éste, y tenemos que crear una solución
nueva para cada uno de los casos. Sin embargo, en la mayoría de las situaciones
de resolución de problemas algoritmicos, los diseñadores de software emplean
más tiempo combinando o reutilizando rutinas que creándolas nuevas desde el
principio. Es decir, el proceso de resolución de problemas algorítmicos tiende a
cumplir la regla «90 por 100 de transpiración y 10 por 100 de inspiración».
Apliquemos la <<transpiración» (Sección 5.4.1) y la inspiración (Sección 5.4.2).

5.4.1. Reutilización de rutinas


Como hemos dividido suficientemente el problema original, de forma que po-
demos tratar cada parte de forma independiente, debemos explorar la presen-
cia de rutinas conocidas o «viejas» entre los pasos individuales. Por cierto,
algunas rutinas que son conocidas para unos resolutores de problemas, son
desconocidas para otros. Por ello, no debe descorazonarse si desconoce algu-
nas rutinas con las que otros parecen estar muy familiarizados. Estamos apren-
diendo un lenguaje nuevo, y nuestro vocabulario crecerá con la experiencia.
Existen cuatro mecanismos que podemos utilizar para adaptar o reutilizar
rutinas ya existentes para propósitos nuevos: empalmándolas, anidándolas,
adaptándolas y mezclándolas *.

Empalmado. Es el método más sencillo de crear rutinas nuevas. Si Rl Y R2


son dos rutinas ya existentes, el empalmado de ambas crea una rutina nueva R,
que se compone de R 1 seguida secuencialmente de R2. Por ejemplo, en el
problema de la pluviometría. Las cuatro rutinas (pasos del 1 al 4) son empal-
madas para formar una rutina nueva como es la solución al problema. De la
misma forma, las rutinas PromedioLista y WriteLn se empalman para
construir la solución al paso 2.1, como se ilustra en la Figura 5.21.

Anidamiento. También se puede construir una rutina nueva anidando una


rutina R 1 ya existente de otra R2. El anidamiento se produce cuando utiliza-
mos una rutina como argumento de la invocación a un procedimiento, o
cuando la colocamos dentro de una instrucción condicional o dentro de un
bucle. Esto sc ilustra en la figura 5.22, donde el cálculo del promedio de
pluviometría, ha sido anidado dentro del propio proceso de visualización del

* E. SOLOWA v: {<Lcarning to program: Learning to construct rncchanisms and cxplana-


tions". Curnrnunicatians al the ACM (septiembre 1986), 29(9): 850.
164 Compu tación ,. Lógica, resoluc ión de problem as, algoritm os
y progra mas

mismo. Obsérvese que se pierde la asignación del valor prome dio a la


variable
PluviometríaProm, que se realizó en el empalmamiento mostra do en
la figu-
ra 2.21.

Rl Pluvio metria Prom: = Prome dioLis ta(Plu viome trias>


R
R2 WriteL n( 'media = " Pluvio metria Prom>

Figura 5.21. Creació n de una rutina nueva R empal mando


las rutinas R1 y R2.

R
R2 WriteL n( 'media = "

R11 Prome dioLis ta(Pluv iometr ias>!

Figura 5.22. Creació n de una rutina nueva R por anidam iento


de R1 y R2.

Adaptación. Es posible crear una rutina nueva R adaptando otra R1


ya exis-
tente. Es decir, el propósito original de Rl puede ser ligeramente modifi
cado,
generalizado o particularizado de forma que realice la función requer
ida por R.
Supongamos que adapta mos la rutina MaxL i s ta (que encuentra la
posición
que ocupa el elemento máximo de una lista) para utilizarla como una
rutina
Mi n Lis t a (que encuentra la posición que ocupa el elemento mínim
o de una
lista). ¿Qué necesitamos modificar en rutina MaxL i sta de forma que
cumpla
las especificaciones de Mi nL i sta? Es casi evidente que es necesario
realizar
unas modíficaciones mínimas, y el resultado se obtiene mucho más
rápída y
fácilmente que si la construyéramos de cero. Las modificacíones se resume
n en
la Figura 5.23.
Mezcla. Se puede constr uir una rutina nueva R mediante la mezcla
o entrela-
zamiento de los pasos individuales de otras Rl y R2 ya exístentes.
Esto se
muestra de forma ablitracta en la Figura 5.24, donde se han mezclado
los pasos
de la rutina MaxL i sta con los de la Mi nL i sta, para obtene r una
nueva que
obtiene el valor máximo y mínimo en una única pasada (bucle f o
r) por la
lista, en lugar de dos.
Obsérvese que, en la Figura 5.24, hemos alterado la sintaxís y renom
brado
algunas de las variables de las rutinas MaxL i sta y Mi nL i sta origina
les, con
objeto de que la nueva rutina MaxM i nL i s ta devuelva dos valores en
lugar de
uno. En una implementación completa, la cabecera podría ser:
Resolución de problemas algorítmicos 165

pl"ocedul"e MaxMinLista(L: Lista; val" Max, Min: reaL);

R
Rl if <O < m) and (m <= LengthL i sta(L» then
begin
j := 1
fOI" k := 2 to m do
i f L[k](2:)L[j] then
j := k;
end
else
j := O;
~Lista :y
~nLista ::J>
Figura 5.23. Creación de una rutina R por la adaptación de otra R1.

Donde los parámetros por referencia Max y Mi n se utilizan directamente en la


obtención de los resultados; la variable j utilizada para este propósito en la
Figura 5.23 no es necesaria en esta versión.

R Rl
i f (O < m) and (m <= LengthL i sta( L» then
begin
Max := 1 ; R2
IMin:=1;
for k :~ 2 to m do
begin
i f L[k] > L[Max] then
Max := k;

I if L~~~ ~~L~Min] then

end
end
else
begin
Max:=O;

I Min :=0
end;
I

Figura 5.24. Creación de una rutina nueva R por mezcla de otras dos R1 y R2.
166 Computación l. Lógica, resolución de problemas, algoritmos y programas

Obsérvese, en la Figura 5.24, que las instrucciones fa r e i f sirven tanto


para el caso de la función MaxL i sta que para el de Mi nL i sta. Esto es asi
porque la forma lógica de implementar ambas rutinas es la misma. En la
Figura 5.24 se han marcado esas instrucciones como originarias de la rutina
R l (en lugar de por R2), pero esto es indiferente en la ilustración de la mezcla.

5.4.2. Utilización de la creatividad para diseñar nuevas rutinas

En los casos en los que no existe ninguna rutina disponible, debemos emplear
nuestra creatividad para construir las abstracciones procedimentales apropia-
das. En el problema de la pluviometría tenemos que utilizar nuestra creativi-
dad para encontrar una solución a los pasos 3, que debe escribir los meses
cuya pluviometria está por debajo de la media, y el 4, que debe mostrar el(los)
mes(es) cuya pluviometria es la máxima.
Aparentemente, ambos pasos requieren utilizar el mismo tipo de estrategia
-una búsqueda dentro de la lista de pluviometrias para encontrar los valores
que cumplen una determinada propiedad-. El índice de la lista PLuv i ome-
tri a puede utilizarse para encontrar el nombre del mes correspondiente, en el
array Meses. Considerando el ejemplo de entrada de la Figura 5.17b, el pro-
medio de lecturas pluviométricas de esta entrada es de 2,64 lts/m 2 . El valor
pluviométrico para PLuviometria[1] es 2,5, que se encuentra por debajo
de la media. Por tanto, el paso 3 debe mostrar el valor de Me s e s [1] o Ene ro
como una de las salidas. Para encontrar todos los meses que satisfacen la
misma condición, es necesario explorar los doce valores pluviométricos. Esto
se consigue con el bucle siguiente:

{Paso 3. Mostrar meses que están por debajo deL promedio}


WriteLn('Meses por debajo de La media: '};
for i := 1 to LengthLista(PLuviometrias) do
i f PLuviometrias[i] < PLuviometriaProm then
WriteLn(' " Meses[i]);

Obsérvese que, a pesar de haber utilizado la creatividad, la solución al paso 3


combina algunas rutinas ya familiares mediante el empalmado y el anidamien-
to. Por ejemplo, una referencia a un elemento del array Meses se ha anidado
dentro de una rutina Wri teLn que, a su vez, está anidada dentro de una
rutina que incluye un bucle foro La propia rutina del for está, a su vez,
empalmada con otra rutina Wr i te Ln, para completar el paso 3.
Tras haber completado el paso 3, podemos intentar utilizar la misma estra-
tegia para resolver el 4, del que sospechamos que se puede resolver en la misma
forma. Obsérvese que el valor máximo de pluviometría puede darse para más
de un mes. En el caso peor, ¡los 12 meses pueden tener el mismo valor pluvio-
métrico, por lo que la salida debe mostrarlos todos como salida del paso 4!

{Paso 4. Muestra eL(Los) mes(es) con vaLor máximo de PLuviometria)


WriteLn( 'Mes(es) Máxima:'};
Resolu ción de problem as algorítm icos 167

for i := 1 to Length ListaC Pluvio metria s) do


i f PLuvi ometri as[i] = Maxpl uviome tria then
WriteL nC' " MesCe s)[i]);

Obsérvese que la solución se ha conseguido adapta ndo la solución del


paso 3.
Esta práctica es habitual en la resolución de problemas algorítmicos,
y debe
utilizarse siempre que parezca, intuitivamente, que dos pasos necesit
en el mis-
mo tipo de 'proceso. ¿Pueden mezclarse los pasos 3 y 4 en un bucle
for único
que resuelva el problema? No es el caso. Piense sobre lo que ocurrir
ía si un
único bucle f or tuviera las dos instrucciones ; f de los pasos 3 y
4 dentro
de él.

5.4.3. Utilización del conocimiento de dominio en el diseño


de rutinas nuevas

A lo largo de nuestra discusión sobre la resolución de problemas algorit


micos,
hemos supuesto que se posee un cierto conocimiento sobre el domin
io al que
pertenece el problema. En problemas sencillos, como los que hemos visto
hasta
el momento, el único conocimiento del dominio que se precisa es
el de la
familiaridad con los rudimentos del Álgebra y de la Lógica. Para poder
cons-
truir soluciones a problemas más avanzados, es necesario tener unos
conoci-
mientos más profundos dentro del campo de la alta Matemática, Inform
ática,
Ciencias Naturales, Ciencias Sociales, Lingüística, Economía o Comer
cio.
El Pascal estánd ar no incluye una rutina que calcule la raíz cuadra da
de un
número. Supongamos que estamos trabaja ndo con un problema que
requiere
esta rutina. Por ejemplo, necesitamos calcular, además del valor medio
de unas
lecturas pluviométricas, su desviación estándar. Este cálculo precisa
la utiliza-
ción de una rutina que calcule la raíz cuadra da de x, siendo x un
valor no
negativo. La construcción de esa rutina puede comenzarse con la especif
icación
siguiente:

sqrtCx ):
(pre: x>=O )
(post: result ~ y 1\ y2 ~ x)

Donde se ha utilizado el simbolo ~ para resaltar el hecho de que


la raiz
cuadra da de algunos números sólo se puede calcular de forma aproxi
mada.
Por ejemplo, la raíz cuadra da de 2 es aproximadamente, con una precisi
ón de
tres decimales, 1,414.
Un conocido método de las matemáticas para calcular raíces cuadra
das de
forma aproxi mada es el método de Newton. De hecho, el método de
Newton
es un método general para calcular las raíces de ecuaciones, entre
las que la
ecuación y = x 2 es tan sólo un caso particular. El método de Newto
n es un
método iterativo que trabaja de la forma siguiente. Comenzamos
con una
primera aproximación al valor esperado; por ejemplo, y = 1. La
elección
particular de este valor inicial es habitualmente indiferente para la efectiv
idad
168 Computación l. Lógica, resolución de problemas, algoritmos y programas

del método. A partir de y calculamos un nuevo valor «más aproximado», y',


mediante la fórmula:
y + x/y
y'
2
La apliqción reiterada de esta fórmula crea una serie de valores candidatos
que converge rápidamente hacia el valor de la raíz cuadrada de x. Por ejemplo,
en la Tabla 5.2 se muestran los valores obtenidos, suponiendo x = 2 Y asu-
miendo un valor inicial de y = 1. Así, utilizando el método de Newton, pode-
mos construir una rutina que calcule el valor aproximado de una raiz cuadra-
da, simplemente escribiendo un bucle que realice las aproximaciones sucesivas
de y' a y, y que termine cuando se haya alcanzado un grado de precisión
adecuado (por ejemplo, una diferencia de 0,0001).

Tabla 5.2. Primeros pasos del cálculo aproximado de )2 por el método


de Newton

Estimación (y) x/y Cálculo de y'

1 2/1 = 2 (1 + 2)/2 = 1,5


1,5 2/1,5 = 1,33333 (1,5 + 1,33333)/2 = 1,41667
1,41667 2/1,41667 = 1,41176 (1,41176 + 1,41667)/2 = 1,41422
1,41422

Existe una forma alternativa de calcular raíces cuadradas que nos permite
esquivar el necesario conocimiento del dominio que supone el método de New-
ton. Sin embargo, esta alternativa nos obliga a conocer el dominio de las ca-
racterísticas más avanzadas de las bibliotecas de funciones estándar de Pascal.
En concreto, Pascal incorpora las funciones exp(x) y ln(x) que calcu-
lan las funciones eX y el logaritmo neperiano de x, respectivamente. Combinan-
do esta información con los conocimientos adquiridos en el Capítulo 2, pode-
mos obtener:

b In (a)
a

Por lo que tenemos una forma simplificada de calcular la raiz cuadrada de x.


Recuérdese que es posible escribir la raíz cuadrada de un número como X 1 / 2 .
Por lo que utilizando logaritmos:

In (x 1/2) = 1/2 In x

Ahora, aplicando eX, que excribimos como exp (x), obtendremos:


1 2
exp (In (X / ) = exp (1/2 In x)
Resolu ción de problem as algorítm icos 169

o bien:
X'f2 = e1f2 In (xl

Que puede escribirse en Pascal como:


y :~ exp(O, 5 * ln(x»
Por lo que 1<1 solución que demos al problema algoritmico estará fuertem
ente
influida por nuestro conocimiento de la relación del mismo y sus discipl
relacionadas. inas

5.5. TERMINACiÓN DEL CASO DE ESTUDIO


Volviendo al caso de estudio, nos quedan por aplicar tres pasos de la
metod o-
logía MAPS propuesta: codíficación, verificación y prueba, y presen
tación.

5.5.1. Codificación
En pocas palabras, la codificación es la traslación de la solución de un
proble-
ma algorítmico a un programa, junto con su documentación. En la
resolución
de un problema algorítmico, la mayoría del código se obtiene durant
e las
etapas de partición y abstracción. Por ejemplo, hemos completado la
codifica-
ción de los pasos 3 y 4, del problema de la pluviometria, en la búsque
da de
nuevas rutinas que sean apropi adas para ellos. También los pasos 1
y 2 están
prácticamente codificados; sabemos que pueden implementarse reutiliz
ando y
combinando rutinas de otros problemas ya existentes.
El proceso de codificación implica el juntar las partes de la solució
n al
problema, asegurándonos de que el resultado conjun to es coherente,
inteligi-
ble, y satisface las pre y poscondiciones originales. Cuand o codific
amos en
Pascal suele ser útil utilizar una plantilla como la siguiente:
progra m <nomb re>;
{Breve comen tario que descri ba el proble ma, la estruc
tura genera l de
la soluci ón, el autor y la fecha}
uses <Bibli otecas de las que se tomará n alguna s rutina
s>;
<Decl aració n de las variab les identi ficada s en el proces
ción> o de parti-

begin
{pre: <Preco ndició n del problem a>}
{Paso 1. <Desc ripción del primer paso de la partici ón>}
{Paso 2. <Desc ripción del segund o paso de la partic ión}
{post: <Posco ndicio nes del problem a>}
end.

Figura 5.25. Plantill a de un progra ma escrito en Pascal.


170 Computación l. Lógica, resolución de problemas, algoritmos y programas

Podemos codificar el programa de la pluviometría que hemos desarrollado,


utilizando como guía la plantilla de la Figura 5.26. En este programa se ha
supuesto que las rutinas PromedioLista, MaxLista y MinLista están
almacenadas separadamente en la biblioteca denominada Herramientas-
Lis t a s, y otras operaciones diversas para manipulación de listas de reales en
la biblioteca Listas. Una vez que se ha construido el esqueleto del programa,
el código de lbS pasos individuales puede completarse mediante su inserción en
los lugares apropiados.

program Pluviometrias;
{El programa calcula algunas caracteristicas Pluviométricas
a partir de datos mensuales. Diseñador: Allen Tucker, 30 de marzo
de 1990. Reutiliza las rutinas MaxLista, MinLista y PromedioLista.}
uses Listas, Herrami entasL i stas;

type NombreMeses = array [1 •. 12] of string[10];

val' Meses: NombreMeses; {Los nombres de los 12 meses}


Pluviometrias: Lista; {Las 12 lecturas pluviométricas}
PluviometriaProm: real; {La medi a de las lecturas}
MaxPluviometria: real; {Valor máximo de las lecturas}
MinPluviometria: real; {Valor minimo de las lecturas}

begin
{pre: entrada = (e" e" .. "' e,,) /\ Vi E {1, .•. , 12} e, es el valor
pluviométrico del i-ésimo mes del año}

{Paso 1. Inicializar lista de meses y pluviometrias}

{Paso 2. Mostrar pluviometria promedio, máxima y minima}

{Paso 3. Mostrar meses por debajo del promedio}

{Paso 4. Mostrar mes(es) con pluviometria más alta.}

{post: entrada =0/\ salida = ESTADISTICAS PLUVIOMETRICAS


media = Sum i E {1, .. "' 12}: e,/12
má xi ma = Max i E {1, , 12}: e,
minima = Min i E {1, , 12}: e,
Meses por debajo de la media:
{m;: e J < Su. i E {1, ... , 12}: e,l12}
Mes(es) máxima:
{m;: e j ~ Max i E {1, ... , 12}: e, /\ 1,; j,; 12})
end.

Figura 5.26. Plantilla en Pascal para el problema de la pluviometria.

Para completar la codificación de los pasos de entrada de datos, es necesa-


rio que el programa emita los mensajes necesarios para que el usuario intro·
Resolu ción de problem as algoritm icos 171

duzca los datos correctamente. Lo que hay que hacer se refleja en


el esquema
del paso 1 siguiente:

{Paso 1. Inicia lizar lista de meses y pluvio metria s}


Inicial izarMe sesCM eses);
Writel nC'Int roduci r la lista de los 12 valore s pluvio
métric os men-
suales :' );
Readl ista(p luviom etrias) ;

Donde es necesario diseñar la rutina In; c; aL; za rMese s de forma


que el
array Meses se inicialice adecua damen te con los nombr e de los doce
meses del
año. Podem os añadir entrad as protegidas (para el caso en que las prccon
dicio-
nes deban ser compr obadas explícitamente por el programa). Por
ejemplo, en
vez de reducir el paso 1 a las instrucciones:

Writel nC'Int roduci r la lista de los 12 valore s pluvio


métric os
mensu ales:') ;
Readli staCPl uviom etrias) ;

Es posible compr obar que se introdu ce exacta mente una lista de doce
elemen-
tos con las instrucciones:

repeat
Write lnC'in troduc ir la lista de los 12 valore s pluvio
métric os
mensu ales: 1);
Readl istaCp luviom etrias) ;
until length listaC Pluvio metria s) = 12;

El código de los pasos 2 a 4 se ha comple tado en el progra ma


de la Figu-
ra 5.27. Obsérvese que se ha omitido el cuerpo del proced imient o In;
c; aL;-
za rMese s, que se deja como ejercicio.

5.5.2. Prueba y verificación

El objetivo de la prueba y la verificación es garant izar lo más posible


que la
solución diseñada es correc ta y comple ta en todos sus extremos.
Es decir, se
trata de poder asegur ar que la ejecución del progra ma, utilizando
unos datos
de entrad a permitidos por las precondiciones, produc irá un resulta
do consis-
tente con las poscondiciones. Debido a que la prueba y verificación
son funda-
mentales en el proceso de resolución de problemas algorítmicos, se
analiza rán
en profun didad en el Capítu lo 6.

5.5.3. Presentación

Cuand o la solución a un proble ma algorítmico ha sido desarro llada


completa-
mente, el progra ma debe ser suficientemente autodo cumen tado, para
cuando
172 Computación l. Lógica, resolución de problemas, algoritmos y programas

sea leído por alguien no familiarizado con la solución obtenida, pero sí con la
metodología y el dominio al que pertenece el problema. Es decir, el texto del
programa ---con su documentación, estructura paso a paso, y pre y poscondi-
ciones- debe ser fácilmente legible por un colega profesional, como lo sería un
artículo del ABe para cualquier persona bien educada.
La presentación de una solución completa de un problema algorítmico
debe incluir, además del propio texto del programa, los elementos siguientes:

1. Una introducción escrita en castellano identificando el(1os) autor(es) y


que descríba el problema, la solución y algún aspecto inusual o innova-
dor de la solución.
2. Un ejemplo de las entradas y las respectivas salidas obtenidas en una o
varias ejecuciones del programa.
3. Un resumen de los resultados de la verificación y/o prueba del progra-
ma, en los casos en que sea adecuada.

program Pluviometrias;
(El programa calcula algunas caracteristicas Pluviométricas
a partir de datos mensuales. Diseñador: Allen Tucker, 30 de marzo
de 1990. Reutiliza las rutinas MaxLista, MinLista y PromedioLista.)
uses Listas, HerramientasLi stas;

type NombreMeses = array[1 .. 12] of string[10];

var Meses: NombreMeses; (Los nombres de los 12 meses)


Pluviometrias: Lista; (Las 12 lecturas pluviométricas)
PluviometriaProm: real; {La media de las lecturas}
MaxPluviometria: real; (Valor máximo de las lecturas)
MinPluviometria: real; (Valor minimo de las lecturas)

begin
{pre: entrada = (e" e" .•• , en) /\ Vi E {1, .• ",12) e, es el valor
pluviométrico del i-ésimo mes del año)

{Paso 1. Inicializar lista de meses y pluviometrias)


InicializarMeses(Meses)
WriteLn('Introducir la lista de los 12 valores pluviométricos
mensuales:');
ReadLista(Pluviometrias);

(Paso 2. Mostrar pluviometria promedio, máxima y minima)


WriteLn('ESTADISTICAS PLUVIOMETRICAS');
PluviometriaProm := PromedioLista(Pluviometrias);
WriteLn(' media = " PluviometriaProm:5:2);
MaxPluviometria :=MaxLista(Pluviometrias, LengthLista(Pluviome-
trias»;
WriteLn(' máxima = " MaxPluviometria:5:2);
MinPluviometria :~ MinLista(Pluviometrias, LengthLista(Pluviome-
trias»;
WriteLn(' minima = " MinPluviometria:5:2);
Resolución de problemas algorítmicos 173

(Paso 3. Mostrar meses por debajo deL promedio)


WriteLn( 'Meses por debajo de La media:');
for i :~ 1 to LengthLista(PLuviometrias) do
if PLuviometrias[i] < PLuviometriaProm then
WriteLn('Meses[i]);

{Paso 4. Mostrar mes(es) con pLuviometria más aLta.)


WriteLn( 'Mes(es) máxima:');
fQr i := 1 to LengthL ista(PLuviometrias) do
if PLuviometrias[i] = MaxPLuviometria then
WriteLn(' " Meses[i]);

{post: entrada ~01\ saLida = ESTADISnCAS PLUVIOMETRICAS


media = Sum i E {1, , •. ,12): e,l12
máxima = "ax i E (1, .. ",12): e i
minima = "in i E (1, ." ,,12): e i
Meses por debajo de La media:
{mj: e j < Sum i E {1, .. "' 12): e,l 12 )
Mes(es) máxima:
(m j : e j ~ "ax i E (1, """,12): eiI\1~j~12))
end.

Figura 5.27. El programa Pascal PLuviometrías, compLeto.

En pocas palabras, la presentación de la solución a un problema debe hacese


de forma que un lector interesado sea capaz de comprenderla. El lector será,
por el momento, su profesor, pero en un futuro podría ser cualquier otro
resolutor de problemas profesional (programador o analista) al que se le asigne
la tarea de revisar o extender la solución al problema en cuestión.

5.6. RESUMEN
En el Capítulo 4 introdujimos las técnicas y herramientas básicas para cons-
truir programas sencillos en Pascal a partir de sus especificaciones. En este
capítulo hemos aprendido cómo analizar un problema y a desarrollar su solu-
ción, identificando elementos del programa más pequeños (rutinas) y combi-
nándolas todas en un programa completo.
Aprendimos algo del proceso de abstracción y de su realización en Pascal.
La creación de rutinas que puedan ser reutilizadas, es una de las tareas funda-
mentales del proceso de resolución de problemas algoritmicos. Podemos reuti-
lizar rutinas y recombinarlas en cuatro formas fundamentales --empalmándo-
las, anidándolas, adaptándolas y mezclándolas durante el proceso de creación
de una solución a un problema nuevo. Fuera de estos limites, es preciso utilizar
la creatividad y el conocimiento del dominio. Los otros aspectos de la resolu-
ción de problemas algorítmicos son evidentes en la metodología MAPS --el
diálogo, la especificación, la partición, definición de abstracciones, codificación,
verificación y prueba, y presentación.
Pero entender no es lo mismo que hacer. Antes de proseguir con el libro,
tómese algún tiempo para diseñar y escribir programas para los problemas
174 Computación l. Lógica, resolución de problemas, algoritmos y programas

propuestos en los ejercicios siguientes. Asegúrese de aplicar correctamente las


técnicas del método MAPS.

Ejercicios

5.23. Implementar una rutina del método de Newton para calcular raices
cuadradas en forma de función de Pascal, y que tenga Z + como domi-
nio y R como rango. Probar el resultado para diversos valores de x, y
compararlos con los resultados de utilizar la rutina de cálculo de raíces
cuadradas de la Sección 5.4.

5.24. Escribir el cuerpo del procedimiento In; e; al; za rMeses del progra-
ma de la Pluv;ometr;a.

5.25. Continuar con el diseño MAPS sobre los números romanos esbozado
en el Ejercicio 5.20; definir las abstracciones y codíficar el programa
completo. ¿Es posible utilizar abstracciones vistas previamente? ¿Qué
abstracciones nuevas es necesario introducir, y qué tipo de creatividad y
conocimiento del dominio es necesario utilizar para completarlo?

5.26. Diseñar e implementar un sistema de manipulación de información de


una pequeña compañía. El programa deberá leer un archivo del perso-
nal, en el que cada empleado tiene su número y sueldo anual. Diseñar
un programa que realice un informe. Diseñar el formato del informe
cuidadosamente; cada elemento debe estar etiquetado cuidadosamente.
Para poder probar el programa, suponer que la compañia tiene 25 em-
pleados y diseñar un archivo de empleados sencillo, consistente en núme-
ros de empleados y su sueldo anual. En el informe, mostrar lo siguiente:
a) La nómina anual de la empresa, los sueldos anuales máximo, mini-
mo y medio.
b) Un apartado de la misma información para los rangos de salarios
siguientes (en dólares):

< sueldo < 15.000


15.000 < sueldo < 20.000
20.000 < sueldo < 30.000
30.000 < sueldo < 45.000
45.000 < sue ldo

(Es decir, el número de trabajadores de cada intervalo, junto con los


sueldos máximo, mínimo y medio de cada intervalo.)

5.27. Desarrollar un diseño basado en la metodología MAPS, para un gestor


de apuestas múltiples en las carreras de caballos. Antes de cada carrera,
el usuario debe introducir la apuesta que realice, dando el número del
Resolución de problemas algorítmicos 175

caballo y el valor de la apuesta. Tras la campana de «última apuesta», el


programa debe calcular y visualizar la relación de las apuestas sobre
cada caballo, que se calcula dividiendo el valor total de las apuestas
sobre todos los caballos por la cantidad total apostada a cada caballo.
Por ejemplo, en una carrera con tres caballos, el total de apuestas para
el número 1 fueron 100 dólares; para el número 2, 150 dólares; y 250
dólares Pllra el número 3. Las relaciones para el ejemplo anterior serán
5 a 1 para el primero, 5 a 3,33 para cl segundo, y 2 a 1 para el tercero.
No es necesario implementar todo el programa, sino sólo el diseño
(pasos de MAPS del 1 al 4).
CAPíTULO 6
ROBUSTEZ Y PRUEBA
DE LOS ALGORITMOS

Todo lo que pueda funcionar mal, lo hará.


MURPHY

La disciplina de resolver problemas algorítmicos puede resultar cruel. El dise-


ñador no sólo debe cuidar que el programa funcione correctamente para las
entradas que se ajustan a las especificaciones, sino que debe cuidar que el
programa responda adecuadamente cuando se encuentre entradas incorrectas
o inesperadas. Asi, el resolutor de problemas se convierte rápidamente en un
escéptico sobre la infalibilidad de los programas que pretenden resolver un
problema. Invariablemente, siempre que oimos en el laboratorio «¡he encon-
trado el último error!», sabemos que después comprobará que sólo era el
penúltimo.
¿Es posible resolver problemas complejos y diseñar un software que tenga
una fiabilidad alta? Es decir, ¿cómo podemos asegurar que nuestros progra-
mas, que son el producto final de nuestra metodologia de resolución de proble-
mas algoritmicos (MAPS), detecten combinaciones de entradas inesperadas, y
aun así trabajen de forma correcta y predecible?
En este capítulo analizamos estas cuestiones de diferentes formas. Primero,
definimos las nociones complementarias de corrección, robustez y amigabili-
dad. Segundo, ilustramos la aplicación de estas ideas en dos situaciones dife-
rentes: una que involucra el procesamiento de textos, y otra que involucra el
tratamiento de gráficos. En ambos casos utilizamos la metodología de resolu-
ción de problemas MAPS, lo que nos servirá para afianzarnos en su conoci-
miento para utilizarla en otro tipo de problemas algorítmicos. Tercero, intro-
ducimos una estrategia para diseñar datos de prueba, y una metodología de
prueba sistemática que aporta un grado de confianza sobre la corrección y
robustez del programa mayor que si se utilizan pruebas aleatorias. Cuarto,
introducimos la noción complementaria de verificación formal y mostramos
que está estrechamente relacionada con el lenguaje de la lógica y los procesos
de demostración que fueron presentados en el Capitulo 3. Como veremos, las
técnicas de verificación y prueba pueden combinarse para crear una serie de
métodos que aseguren la calidad y fiabilidad del software.
178 Computación l. Lógica, resolución de problemas, algoritmos y programas

6.1. CORRECCiÓN V ROBUSTEZ

¿Qué significa que un programa sea correcto?

Definición. Un programa es correcto si, para cualquier entrada que satisfa-


ga la precondición, termina generando una salida que satisface sus poscon-
diciones. De forma análoga, un procedimiento (o función) es correcto si,
para todos los posibles valores de los parámetros de entrada que satisfacen
las precondiciones, termina y, además, los parámetros de salida (resultados)
satisfacen las poscondiciones.

Por ejemplo, el programa que calcula la calificación media CaLcuLaCM, del


Capitulo 4, es correcto en este sentido. Es decir, para todas las entradas del
tipo (Notas!' Notas 2 , .•. , Notas n), donde n > O y cada Notas, es un número en
el intervalo {O, ..., 4} el programa calcula y escribe la calificación media y
termina. A continuación, se demuestran algunas entradas y sus correspondien-
tes salidas, para diferentes ejecuciones del programa.

Entrada Salida

(3 1 2 4) 2,50
(2 ) 2,00
(2 3 3 3 3 3 3 2) 2,75

Un estudio cuidadoso del programa de la Figura 4.6 hace que nos plantee-
mos algunas dudas sobre lo que ocurre cuando las entradas no satisfacen las
especificaciones de las precondiciones. Por ejemplo, ¿qué ocurre si la entrada
es la lista vacia( )? ¿Qué ocurre si la lista de entrada contiene valores fuera del
intervalo {O, ..., 4}, como la lista de pluviometrías mensuales utilizadas en la
Figura 5.17b, utilizadas para un problema completamente distinto? ¿Qué ocu-
rre si la lista contiene algo distinto de números, como la lista de los doce
nombres de meses de la Figura 5.17a?
Una posible alternativa para responder a esas preguntas es correr el pro-
grama con varias entradas alternativas que no satisfacen las precondiciones, y
ver qué ocurre en cada caso. Observemos el cuerpo del programa Ca Lcu La CM
y analicemos cómo tratará cada uno de los distintos casos (véase Figura 6.1).
Este es un programa tan sencillo que resulta conveniente un análisis tan
directo. En el caso en que la lista de entrada esté vacia, no se satisface la
condición n > O, por lo que el programa no genera ninguna salida. Sin embar-
go, en el caso en que los valores de la entrada no estén en el intervalo {O, ..., 4},
el programa calcula sin problemas el promedio y escribe (erróneamente) el
valor de la CM, aunque algunas entradas numéricas no sean calificaciones
válidas, según las especificaciones.
Robustez y prueba de los algoritmos 179

begin
{pre: entrada ~ (Notas" Notas" . o., Notas,) 1\ n > O 1\
\;Ii E {1,
o ni: Notas, E {O, .•. , 4}}
•• ,

{Paso 1. Obtenemos la lista de calificaciones}


WriteLn( 'Introducir la lista de calificaciones:');
ReadLista(Notas);

{Pas6 2. Calcular n o número de calificaciones en la lista}


n:~ LengthLista(Notas);
i f n > O then
begin
{ent rada = 0 A n>O}

{Paso 3. Calculamos Sum = la suma de las calificaciones de la lista}


Sum := O;
i :~ 1;
while i <= n do
begin
Sum := Sum + Notas[i];
i := i + 1
end
end;

{Paso 4. Calculamos CM = Sum/n}


CM = Sum/n;
{Sum = Su. i E {1, ... , n}: Notas, 1\ CM ~ Sum/nj

{Paso 5. Se muestra CM}


WriteLn('La CM de esas calificaciones es ~', CM 5: 2)
end {if}
{pos t: ent rada = 0 1\
salida = SUII i E {1, o • • , n}: Notas,!n}
end. {CalculaCM}

Figura 6.1. Cuerpo del programa CalculaCM.

Finalmente, consideremos el caso en que la entrada no es completamente


numérica:

(31 hello 2)

Cómo trate el programa esta entrada dependerá de cómo manipule la rutina


ReadL i s ta las entradas no numéricas. Es decir, los valores obtenidos por
ReadLista<Notas) parai = 1,2y4sonlosnúmeros3,1 y2,respectiva-
mente. Para el caso de i = 3 (donde la entrada es Notas = heL Lo) ¿se
producirá un error?, ¿debe devolver la rutina algún valor especial, por ejemplo
0, cuando se introduzca un valor no numérico? Obsérvese que simplemente
analizando las pre y poscondiciones de la rutina ReadL i s ta (véase Apén-
dice C), no se contesta a esta pregunta, puesto que éstas están enunciadas para
entradas válidas. Sin embargo, ejecutando el programa podemos descubrir que
180 Computación l. Lógica, resolución de problemas, algoritmos V programas

°
la referencia a Notas[3] devuelve el valor para esta lista en concreto, por
lo que el valor promedio es, por tanto, 1,50.
Tenemos que destacar, por tanto, que el programa Ca Lcu LaCM es correcto
en el sentido de la definición anterior. Sin embargo, no es robusto.

Definición, Se dice que un programa es robusto si reúne las dos condicio-


nes siguientes:

1. Es correcto.
2. Para todas las entradas que no satisfacen las precondiciones, el
programa termina y produce una salida que refleja el hecho de que
ha ocurrido un error en la entrada.

Seguramente, el programa Ca Lcu LaCM no es robusto, aunque seguramente es


correcto. En general, la creación de software robusto es más complicada que la
creación de software correcto. Más aún, la robustez es una característica desea-
ble para el software de computadoras, sobre todo si va a ser manipulado por
gente no familiarizada con los detalles del diseño, o que son proclives a come-
ter ocasionalmente errores cuando escriben la entrada.
¿Cómo es posible robustecer el programa de la Figura 6.1? Es necesario
hacer dos cambios básicos. Primero, es preciso revisar el procedimiento
ReadL; s ta de forma que ponga L; s tError a t rue siempre que aparezca
un dato no numérico en la lista de entrada. El paso 1 del programa debe
explorar específicamente la corrección de cada calificación (es decir, que sea un
valor numérico dentro del conjunto {O, ..., 4}). Segundo, es necesario completar
la instrucción condiciorral que compruebe si n > 0, con una cláusula e Lse, de
forma que el programa escriba el mensaje de error apropiado cuando se pre-
sente como entrada una lista vacía. Podemos revisar el paso l de la forma
siguiente:

(Paso 1. Obtenemos la lista de calificaciones}


WriteLn( 'Introducir la lista de calificaciones:');
ReadLista(Notas);
n := LengthLista(Notas);
i f Li stError o .. (n = O) then
entradaválida := false
else
begin
entradavál ida :~ true;
for i : = 1 to n do
i f Notas[i] < O) o .. (Notas[i] > 4) then
entradaválida := false;
end;
i f not ent radavá l i da then
WriteLn('Entrada no válida. Programa terminado')
else begin
Robustez y prueba de los algoritmos 181

Donde la nueva variable booleana ent radavá L; da indica si la entrada se


ajusta o no a los requisitos de la precondición. El resto del programa quedaria
de la forma original, salvo que la comprobación de si n > O puede ser elimina-
da (puesto que se ha incorporado el paso 1 de esta nueva versión).
Una tercera caracteristica que es valorable en un programa es la de la
amigabilidad. Es decir, es deseable un programa que no sólo identifique los
errores de entrada, sino que, a ser posible, realice avisos constructivos al
usuario sobre cómo tiene que escribir ésta para que se ajuste a las especifica-
ciones.

Definición. Se dice que un programa es amigable si reúne los requisitos


siguientes:

1. Es correcto.
2. Para todas las entradas que no se ajusten a las precondiciones, el
programa indica el tipo de error de entrada y concede al usuario la
oportunidad de corregirlo y continuar.

Cómo podemos convertir el programa CaLcuLaCM en un programa amiga-


ble? Podemos revisar el paso 1 en la forma siguiente:

{Paso 1. Obtenemos la lista de calificaciones}


repeat
WriteLn('Introducir La lista de calificaciones:');
ReadLista(Notas);
n := LengthLista(Notas);
i f ListError 01' (n = O) then
entradavál ida ;= false
else
begin
entradavál ida := true;
for i : ~ 1 to n do
if Notas[i] <O) 01' (Notas[i] > 4) then
entradaválida := false;
end;
until entradaválida;

Esta entrada es más constructiva que la anterior, porque permite al usuario


múltiples oportunidades de reintroducir la lista de calificaciones. Es decir, el
programa continúa en el bucle del paso 1 hasta que se introducen las califica-
ciones apropiadas, tras lo cual pueden ser ejecutados los pasos del 2 al 5.
Asegurarse de que un programa es correcto, robusto y amigable es una
tarea dura. Ello implica la aplicación cuidadosa de técnicas de prueba de
programas intrínsecas a la metodología MAPS. Puesto que esta actividad es
tan crucial en la resolución de problemas algorítmicos, hacemos hincapié en
ella en las Secciones 6.2 y 6.3, donde resolvemos dos problemas muy diferentes
utilizando la metodología MAPS.
182 Computación l. Lógica, resolución de problemas, algoritmos y programas

Ejercicios

6.1. ¿Es correcto el programa de las Tres-en-Raya de la Figura 4.21? ¿Es


robusto? Justificar la respuesta.

6.2. Mostrar cómo puede modificarse el problema de las Tres-en-Raya


para que sea má~ amigable.

6.3. a) Revisar la precondición del programa de las Tres-en-Raya, de


forma que describa exactamente cuál movimiento es legal y cuál no.
b) Considerando esta revisión, ¿qué cambios adicionales pueden reali-
zarse para hacer el programa más robusto?

6.4. ¿Es correcto el programa ContarPalabras de la Figura 4.17? ¿Es


robusto? Justificarlo.

6.5. Mostrar la forma en que se puede alterar el programa ContarPala-


bras para hacerlo amigable.

6.2. RESOLUCiÓN DE PROBLEMAS DE PROCESAMIENTO


DE TEXTO UTILIZANDO MAPS: CRIPTOGRAFíA

Las computadoras se han utilizado desde la Segunda Guerra Mundial para


ayudarnos en la resolución de problemas criptográficos, que requieren descu-
brir el esquema de codificación, o cifrado, que subyace en un mensaje codifica-
do. En el Capítulo 2 presentamos un ejemplo del conocido método César de
cifrado que convirtió el mensaje

SERGEANT PEPPERS LONEL y HEARTS CLUB BANO

en el mensaje codificado

VHUJHOQW SHSSHUV ORQHOB KHOUWV FOXE EOQG

Aquí, el esquema de codificación subyacente consiste en reemplazar cada letra


por la que ocupa tres posiciones posteriores en el alfabeto. En la práctica,
se utilizan otros esquemas de codificación más elaborados, y el descubrimiento
tanto del cifrado como del mensaje original puede ser una tarea bastante
complicada.
En nuestro caso, el problema es mucho más sencillo: dada una serie de
mensajes codificados según el método César, descodificar cada uno y escribir el
texto del mensaje ya descodificado. Por tanto, para el mensaje codificado que
se muestra arriba, la salida debería ser SERGEANT PEPPERS LONEL y
HEARTS CLUB BAND.
Robustez V prueba de 105 algoritmos 183

Etapa 1: El diálogo. Existen tan pocas dudas acerca de este problema, que
podemos estar tentados de abordar directamente el diseño. Sin embargo, es
necesario apuntar y resolver algunas cuestiones de detalle. Por ejemplo, ¿puede
contener el texto tanto letras mayúsculas como minúsculas, e incluso caracte-
res no alfabéticos? Presumiblemente la respuesta es si, y tales caracteres deben
aparecer en el mensaje descodificado tal y como aparecían en el mensaje origi-
nal. Por ejemplo, el blanco del mensaje codificado permanece igual en el men-
saje descodificado.

Etapa 2: Las especificaciones. Las especificaciones para este problema pueden


establecerse de la forma siguiente:

[pre: entrada = una serie de mensajes, siendo cada uno una secuencia
de caracteres c" ... , c n representando una codi fi cación uti Lizando
eL método de César
"post: saLida = una serie de textos descodificados de La forma
d" ... , d n en La que cada d, satisface La reLación c, ~ César(d,) /\c,
es un carácter aLfabético v para todo i en {1, ... , n): c, = di}

Donde Césa r (di) representa la codificacíón César del; -ésimo carácter di


en el texto de salida. Esta codificación se describe en la Tabla 6.1.

Tabla 6.1. El cifrado de César

Letra (1) César (1) Letra (1) César (1)

a d A D
b e B E

x a X A
Y b Y B
z e Z C

Etapa 3: La partición. El programa necesita ejecutar tres pasos principales


para cada mensaje que descodifica. En el paso dc entrada se obtendrá el men-
saje descodificado, a continuación se hará un paso de descodificación y, final-
mente, se realizará el paso de salida que escribe el programa ya descodificado.
Estos pasos se resumen de la forma siguiente:

repeat
{Paso 1. Obtener eL mensaje. J

{Paso 2. Descodificar eL mensaje.)


{Paso 3. Escribir eL mensaje descodificado.)
until no existan más mensajes para descodificar
184 Computación ,. Lógica, resolución de problemas, algoritmos y programas

Si acordamos que la longitud máxima de un mensaje está limitada a la


longitud máxima de un string de Pascal (255 caracteres en la mayoría de las
implementaciones), entonces existe una pequeña dificultad al implementar los
pasos de entrada y salida para este programa. Sin embargo, si pretendemos
más generalidad, es necesario realizar algunas cuidadosas consideraciones so-
bre el almacenamiento y los requerimientos de codificación de cada mensaje.
En esta solución vamos a atenernos a la versión más simple.
Así, el programa tendrá dos variables del tipo s tri n9 principales, la va-
riable mensaje y la variable deseodi fi cado. La propia descodificación
puede hacerse de dos maneras. Una forma es la de almacenar las letras del
alfabeto en un array, por ejemplo el array César, de forma que la descodifica-
ción de una letra concreta Césa r [i] puede obtenerse directamente buscando
en César[i-3]. Si utilizamos esta estrategia, tendremos que tener cuidado
con las tres primeras letras del alfabeto. (¿Por qué?)
Otra forma de descodificar las letras del alfabeto es la de hacer operar
sobre el ordinal que corresponde a cada carácter en la tabla ASCII (véase el
Apéndice A). Por ejemplo, el ordinal de la letra A es el 65, que se diferencia del
ordinal de la letra D (68) en tres unidades. Así, si e es una letra del mensaje de
entrada, la función de Pascal ehr(ord(e)-3) devolverá la letra codificada,
suponiendo de nuevo que e no es una de las tres primeras letras del alfabeto.
(¿Por qué?)

Etapa 4: Definición de abstracciones. El paso 2 de este programa, la descodifi-


cación del mensaje, puede considerarse como el anidamiento de dos rutinas. Es
decir, la función que calcula la inversa de la función de cifra de César (véase
Tabla 6.1) para una sola letra del alfabeto, puede anidarse dentro de otra que
analice uno a uno los caracteres que componen el mensaje codificado. La
última puede implementarse como un bucle for, mientras que la primera
puede implementarse como una función de Pascal a la que denominaremos
Césarlnversa(e), que toma un solo carácter como argumento y devuelve
como resultado el carácter descodificado. El anidamiento debe tener lugar,
puesto que esta operación se realiza para todos los caracteres del mensaje.
Podemos definir las especificaciones de esas dos rutinas en la forma siguiente:

Descodificar(mensaje)
{pre: mensaje = C 1 , e 2 , •• _, en}
{post: resultado = d" d" •. "' dn 1\ para todo i in (1, "."' n):
di = Césarlnversa(c»)

Césarlnversa(c)
(pre: c es un carácter)
(post: resultado = d, Y bien d es alfabético 1\ CésarCd) = c or d no
es alfabético 1\ d = c)

Etapa 5. Codificación. Las especificaciones anteriores pueden codificarse


como rutinas Pascal de la forma siguiente:
Robustez y prueba de los algoritmos 185

function Descodificar (mensaje: string): string;


yar
i: integer;
descodificado: string;
begin
{pre: mensaje = C 1 , C 2 , •• "' en}
descodificado := mensaje;
for i := 1 to Length(mensaje) do
descodifi cado[i] := CésarInyersa (mensa j e[i]);
Descodif i ca r : = descodi f i cado
{post: resultado=d" d" ... , do 1\ para todo i in {1, ... , ni:
di = CésarInversa(c i )}
end;

function César Inversa (c: char): char;


yar
d: char;
j: integer;
alpha: string;
begin
{pre: c es un carácter}
alpha := concat('abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
j := pos(c, alpha); {busca c en el al fabeto}
case j of
O:
d := c; {no es alfabético}
1 .. 3:
d := alpha[23 + n; {descodifica a, b, c}
4 .. 26:
d := alpha[j - 3]; {descodifica d, e, .. "' z}
27 .. 29:
d := alpha[23 + n; (descodifica A, B, C)
30" .52:
d :~ alpha[j - 3J; {descodifica D, E, ... , Z}
end;
César Inversa := d;
(post: resultado = d, Y bien d es alfabético /\ César(d) = c
or d no es al fabético 1\ d = c}
end;

Teniendo todo lo anterior en mente, podemos escribir el esbozo de programa


siguiente:

program Criptografia;
{El programa descodifica una serie de mensajes y escribe cada uno
descodi fi cado; se supone que cada mensaje se ha codi fi cado
util izando la cifra de César.}

function CésarInversa

function Descodificar
Yar
mensaje, descodificado: string;
186 Computación l. Lógica, resolución de problemas, algoritmos y programas

begin
{pre: entrada ~ una serie de lineas, conteniendo cada una un mensaje
codifi cado}

repeat
{Paso 1. Obtener el mensaje}
WriteLn('Introducir en una sola linea un mensaje codificado, y');
WriteLn,( 'pulsar <RET> para terminar:');
ReadLn(mensaje);

{Paso 2. Descodificar el mensaje.}


descodificado:~ Descodificar(mensaje);

{Paso 3. Escribir el mensaje descodificado.)


WriteLn('El mensaje descodificado es:');
WriteLn(descodificado);
unti l mensaje = "

{post: entrada ~ vacia 1\ salida ~ el correspondiente mensaje


descodificado}

Etapa 6: Prueba. Es útil probar este tipo de programas utilizando como entra-
da un texto almacenado en un archivo separado. De esta forma, no es necesa-
rio reescribir de forma tediosa el mensaje de entrada cada vez que se ejecute el
programa. Una sencilla alternativa al programa anterior, que permita utilizar
una variable archivo (tipo file), en el que se escribiría la entrada, y que condi-
ciona la terminacíón del programa a que se lea la marca de fin de fichero, se
obtendría con las modificaciones siguíentes. Previas al paso 1 habría que escri-
bir:

var Archi voTexto: text

reset(ArchivoTexto, 'codificado,txt');

El paso 1 debería alterarse para que tome la entrada del archivo, en lugar de
tomarla desde el teclado, en la forma siguiente:

ReadLn(ArchivoTexto, mensaje);
WriteLn(mensaje);

Finalmente, la condición de terminación del bucle repea t debe explorar el


final del archivo de entrada en la forma siguiente:

until not eof(Archi voTexto);

Es evidente que, separadamente, se debe preparar en la forma adecuada el


archivo cod i f i cado. t x t para que incluya los textos codificados antes de
ejecutar esta nueva versión del programa.
Robustez y prueba de los algoritmos 187

Etapa 7: Presentación. La presentación de la solución a este problema requie-


re la realización de varias ejecuciones del programa, imprimiendo la salida
correspondiente a cada ejecución. La combinación de estas salidas con el texto
del programa, una breve exposición escrita con el propósito del mismo, y una
explicación con alguna característica inusual (si la tiene), completarán la pre-
sentación.

6.3. RESOLUCiÓN DE PROBLEMAS GRÁFICOS UTILIZANDO


MAPS: EL JUEGO DE LA VIDA

El Juego de la Vída lo inventó John H. Conway en 1970. Su propósito es


proporcionar una simulación gráfica sencilla de la variación de población a lo
largo de varias generaciones. El juego se juega en un entramado rectangular
que representa «el mundo» para una especie particular de organismo viviente
(personas, amebas, pollos u otros). En el entramado, cada celda sombreada
representa un organismo vivo, y la coleccíón completa de celdas sombreadas la
población viva en una generación concreta. Por ejemplo, la Figura 6.2 muestra
una población de seis pollos vivos en un mundo que puede albergar un total
de 64 en una generación.

Figura 6.2. Una generación de seis pollos en el Juego de la Vida.

Cada nueva generación de pollos se obtiene de la anterior siguiendo cuatro


reglas muy simples que regulan la supervivencia, muerte o nacimiento de cada
individuo. Estas reglas están relacionadas solamente con el entorno fisico en
que vive el pollo -es decir, las condiciones de superpoblación o la baja densi-
dad de población en la que se incluye el pollo-o Se define el entorno de una
celda del entramado, como el conjunto de celdas contiguas a ella. Las cuatro
reglas mencionadas son:
1. Supervivencia. Un pollo sobrevive hasta la siguiente generación, si tie-
ne dos o tres vecinos vivos en la generación actual.
2. Nacimiento. Un pollo nacerá en la generación siguiente, si: a) no existe
en la generación actual; y b) tiene exactamente tres vecinos vivos en la
generación actual.
188 Computación l. Lógica, resolución de problemas, algoritmos y programas

3. Muerte por soledad. Un pollo morirá en la generación siguiente, si en


la actual tiene cero o un solo vecino.
4. Muerte por superpoblación. Un pollo morirá en la siguiente genera-
ción, si en la actual tiene 4 o más vecinos vivos.
Asi, si extendemos el ejemplo de la Figura 6.2 hasta la generación siguiente,
nuestra población de ppllos se transformará en la de la Figura 6.3.
Observamos, en la Figura 6.3b, que el pollo de abajo, a la izquierda, ha
muerto por soledad, mientras que dos pollos en la «cruz» han muerto por
superpoblación. Los otros tres pollos de la cruz han sobrevivido porque tenían
tres vecinos en la generación anterior. Finalmente, nacieron tres nuevos pollos
en celdas que estaban sin ocupar, y tenian exactamente tres vecinos en la
generación anterior.

a) b)

Figura 6.3. Dos primeras generaciones en el Juego de la Vida.


a) Seis pollos en la primera generación. b) Seis en la segunda.

Después de algunas pruebas, podemos concluir que la configuración de po-


llos en cada generación puede diferir de la precedente. El Juego de la Vida y
algunas derivaciones que presentan pequeñas diferencías, han fascinado a mate-
máticos y científicos que los han estudiado detenidamente. Los científicos han
estudiado la forma de aplicar este tipo de simulacíones o técnicas de modeliza-
ción, como ayuda para comprender los cambios de población en la vida real.
Sin embargo, el problema al que nos enfrentamos aquí es simplemente el de
construir un programa que, para una configuración inicial sobre un entramado
de 8 x 8 celdas, muestre la secuencia de generaciones que siguen las reglas
sobre supervivencia, vida y muerte anteriores.

Etapa 1: El diálogo. Seguramente, nuestro programa debe proporcionar al


usuario una forma sencilla de describir la configuración inicial de los pollos en
el entramado. Mientras no sea dificil generalizar el programa para que pueda
manipular entramados de cualquier tamaño, esto no parece ser importante
para el programa, por lo que lo ignoraremos por el momento. Sin embargo, es
necesario incorporar algún mecanismo por el cual el usuario pueda controlar
el paso de las generaciones, e incluso parar el proceso.
Robustez y prueba de los algoritmos 189

Podemos considerar la entrada en dos etapas. Primero, el usuario introdu-


ce las filas y columnas de cada celda que estarán ocupadas por la primera
generación de pollos. Segundo, el usuario introducirá una serie de órdenes que
indicarán al programa si debe avanzar a la siguiente generación, o bien termi-
nar el juego.
La salida la constituyen una sucesión de gráficos como los de la Figura 6.3.
La configuración inicial se compone de un entramado, colocado a la izquierda,
con la distribución inicial, y otro entramado vacío a la derecha. La segunda
imagen, que se visualiza ante la entrada por el usuario de la orden de progre-
sar, estará constituida por un entramado a la izquierda con la configuración
inicial, y el de la derecha con la distribución obtenida en la segunda genera-
ción. La tercera imagen, si se ordena, presentará a la segunda generación a la
izquierda, y la tercera a la derecha, y asi sucesivamente.

Etapa 2: Las especificaciones. Después del diálogo, podemos escribir las si-
guientes especificaciones para este problema:

{pre: entrada =n, X 1 , y" XZ' Y2' •• _, Xn , Yn , C" e Z' ••• , Cm /\ Cm = I q ' /\
n > O 1\ m > O pa ra todo i in {1, ... , n}: 1 ,;; Xii y; ,;; 8}

{post: salida = una serie de m- 1 imágenes del Juego de la Vida,


donde la pri mera imagen está determi nada por las n celdas
(x" y,>, (x" y,> ••• , (X n, Yn>' y cada imagen sucesiva presenta
la i-ésima generación que se obtiene de la (i -1)-ésima al aplicar
las reglas de nacimiento, supervivencia y muerte. El final del
juego lo provoca el usuario pulsando' 'q" (de' 'quit' '>')

Donde las pulsaciones de teclas C" Cl' ••• , Cm-' sirve para proporcionar al
usuario el control de las transiciones entre una generación y la siguiente. Es
decir, el carácter (incluida la barra espaciadora) que se simboliza por c; indica
al programa que tiene que calcular la i -ésima generación, a partir de la
(i -1)-ésima y dibujarlas una alIado de la otra. Por tanto, en cada momento, el
programa debe mostrar las dos últimas generaciones calculadas, siendo la de la
derecha la más reciente. La primera generación, dada por las coordenadas x"
y" x 2 ' Yl' ••• , x n, Yn' se identifica con el nombre de generación 1 de la
secuenCia.
La Figura 6.3 muestra una generación 1 inicial (en el entramado del lado
izquierdo) y la generación 2, que se obtendría si se pulsa la tecla de continua-
ción (lee las celdas sombreadas, fila a fila, suponiendo que las filas y las colum-
nas estén numeradas del 1 al 8):

6364546475456

Etapa 3: La partición. Inmediatamente nos vienen a la cabeza varias ideas para


resolver este problema. Primero, será útil usar las facilidades de la abstracción
Gri d. Segundo, será útil declarar dos variables del tipo Gri d; por ejemplo,
Gen1 (para el entramado de la izquierda) y Gen2 (para el de la derecha).
190 Computación l. Lógica, resolución de problemas, algoritmos V programas

El programa deberá incorporar tres pasos principales para simular el Juego


de la Vida. El primer paso debe inicializar los dos entramados Gen1 y Gen2,
leyendo la primera parte de la entrada, y sombreando las celdas de Gen1
apropiadas. El paso 2, simplemente leerá del teclado la orden de si se debe
continuar a la siguiente generación o terminar el juego. El paso 3 se encargará
de calcular la generación actual a partir de la anterior, y de mostrar el re-
sultado.
El secuenciamiento de esos tres pasos puede ser gobernado por la estructu-
ra iterativa que se muestra más adelante. En ella, una instrucción wh i Le
controla el bucle, y cada repetición de éste provoca el cálculo de una nueva
generación que resulta visualizada.

{Paso 1. Inicializar Gen1 y Gen2}

{Paso 2. Leer una orden de cont ro l}

while control <> 'q' do


begin
{Paso 3. Obtener y mostrar la generación siguiente}

{Paso 4. Leer una orden de control}


end¡

Donde la variable de control se utiliza para almacenar el carácter que el


usuario pulsa en el teclado para indicar si se debe calcular y mostrar la próxi-
ma generación. Por esta razón, el paso 4 es una repetición del paso 2; es decir,
se necesita una orden distinta para iniciar el proceso y para cada repetición
sucesIva.
El paso 1 primero crea los dos entramados con un tamaño de celda apro-
piado, por ejemplo, 10 pixels, y coloca los entramados Gen1 y Gen2 en las
coordenadas apropiadas para que aparezcan uno al lado del otro. Este paso
también necesita inicializar la variable entera n seguida de n pares de enteros i
y j, en el rango 1 al 8 que designe la posición inicial de la población de la
generación 1. A diferencia de la primera parte del paso 1, esta segunda parte
necesitará ser abstraida como un procedimiento -que podríamos llamar
Ini ciaL izar.
El paso 2 es prácticamente inmediato. Puede construirse simplemente in-
cluyendo una instrucción Read que asigne el carácter de la tecla pulsada a la
variable control. El paso 3 es el más complejo del programa, por lo que se debe
subdividir en varias partes. Es decir, para el cálculo y visualización de una
nueva generación, primero debemos borrar el entramado que contendrá la
generación siguiente (Gen2 o Gen1, dependiendo de cuál de los dos contiene
la generación actual), y a continuación determinar para cada celda en esa
generación cuál está «viva» y cuál no. El acto de borrar un entramado puede
abstraerse bajo el nombre de Bor ra rG r i d. La determinación de la configura-
ción de la próxima generación puede abstraerse bajo el nombre Si gui ente-
Generación.
Robustez y prueba de los algoritmos 191

El procedimiento SiguienteGeneración puede subdividirse a su vez


en partes, puesto que para cada celda debe contarse el número de vecinos vivos
que tiene. Abstraeremos esa tarea con la función Vec i nos. El procedure
NuevaGene ra c ión puede construirse anidando la función Vec i nos dentro
de un bucle que examine por separado cada celda de la generación actual.
Puede utilizarse una instrucción case (véase el manual de laboratorio para
más detalles sobre esta función) para discriminar entre si las cuatro reglas del
nacimiento, supervivencia y muerte. Por tanto, el paso 3 tendrá la estructura
de control siguiente, suponiendo que Gen1 es la generación actual, y Gen2 la
siguiente:

{Paso 3. Obtener y mostrar la generación siguiente}


BorrarGrid<Gen2);
SiguienteGeneración<Gen1, Gen2);

El procedimiento S; gui enteGenerac i ón tiene la siguiente estructura


cuando se aplica al cálculo de Gen 2 a partir de Gen1:

for i := 1 to 8 do
for j : = 1 to 8 do
case Vecinos<Gen1, i, j) of
{sobrevivir: poner a on la celda i, j de Gen2}
{nacimiento: poner a on la celda i, j de Gen2}
{muerte: poner a off la celda i, j de Gen2}
end

Donde se han establecido informalmente las distintas alternativas para evitar


detalles innecesarios para este paso.
Obsérvese que los papeles de Gen1 y Gen2 serán los opuestos cuando se
produzca el cambio de población siguiente de la generación 2 a la 3. Es decir,
para la generación 3 habrá que borrar el entramado Gen1 y recalcular sus
celdas a partir de los valores actuales de Gen2. Por tanto, para valores impa-
res del número de generación (1, 3, 5, Yasi sucesivamente), el entramado Gen1
mostrará la nueva generación, y para valores pares (2, 4, 6, ..., etc.). Este papel
lo jugará Gen2. Debido a esta alternancia de papeles, Gen1 y Gen2 deben ser
parámetros en la implementación de las rutinas BorrarGri d, Si gui ente-
Generación y Vecinos.

Paso 4: Definición de abstracciones. A continuación, resumimos las abstrac-


ciones nuevas más importantes que son necesarias para este programa:

procedure Inicializar<var G: Grid);


{pre: entrada = n, x" y" X 2 , Y2' •• -, xn , Yn }
{post: todas las celdas de G que corresponden a los pares de enteros
Xi' Yi se ponen a on, para todo i in 1, .. "' n, A entrada = vacia}

procedure BorrarGrid<var A: Grid);


{post: todas las celdas del Grid A se ponen a off}
192 Computación l. Lógica, resolución de problemas, algoritmos y programas

function Vecinos(G: Grid: i, j: integer): integer;


{post: resultado ~ el número de vecinos vivos de la celda i, j
del grid G}

procedure SiguienteGeneración(A: Grid; var B: Grid);


{post: entramado B = nueva generación de celdas vivas, calculadas
del entramado A uti l izando las cuatro reglas de nacimiento,
supervivenci a,Y muerte}

El resto de las abstracciones pueden implementarse directamente a partir de


los procedimientos y funciones generales disponibles para los entramados.

Etapa 5: Codificación. El código de este programa se muestra en varios frag-


mentos. El cuerpo principal del programa puede verse en la Figura 6.4. Es
bastante autoexplicativo, puesto que se ha obtenido a partir de las etapas
previas de la metodología MAPS.
program vi da;
uses
Grids;

var
Gen1, Gen2: Grid;
control: char;
Gennumero: integer;
begin
{pre: entrada = n, X 1 , y" x2 ' Y2' ••• , x n, Yn, C 1 , e 2 , .... , Cm 1\ C lII = Iql A
n > O 1\ m> O pa ra todo i in {1, ••. , n}: 1 <:; x;, y; <:; 8}

{Paso 1. Inicializar Gen1 y Gen2}


WriteLn( 'El juego de la Vida');
StarsGrids;
SetUpGrid(Gen1, 8,10,10, 10};
SetUpGrid(Gen2, 8, 120, 10, 10};
Inicializar(Gen1);

{Paso 2. Leer una orden de control}


gennúmero:= 1; {lleva el control del número de generación}
WriteLn( 'pulsar <RET> para ver la generación', gennumero + 1 :3);
WriteLn( 'pulsar "q" para terminar');
Read(control);
while control <> 'q' do
begin

{Paso 3. Calcula y visualiza la generación siguiente}


i f gennumero IIOd 2 = 1 then
begin
BorrarGrid(Gen2); {las generaciones pares en el grid derecho}
SiguienteGeneración(Gen1, Gen2);
end
else
begin
BorrarGrid (Gen1 ); {las generaciones impares en el gri d i zqui erdo}
SiguienteGeneración(Gen2, Gen1);
end;
Robustez y prueba de los algoritmos 193

{Paso 4. Leer una orden de cont ro l )


gennúmero + 1;
WriteLn( 'pulsar <RET> para ver la generación', gennumero + 1 :3);
WriteLn( 'pulsar' 'q" para terminar');
Read(controL> ;
end

{post: salida = una serie de m-1 imágenes del Juego de la Vida,


donde la primera imagen está determinada por las n celdas
(x" y,), (x" y,) ••• , (x n , Yn)' y cada imagen sucesiva presenta
la i-ésima generación que se obtiene de la (i - 1)-ésima al aplicar
las reglas de nacimiento, supervivencia y muerte. El final del
juego lo provoca el usuario pulsando 'q' (de 'quit').)
end

Figura 6.4. Cuerpo del programa principal del programa Vi da.

Obsérvese que el paso 3 tiene dos partes alternativas. Una que se ejecuta al
calcular una generación par, y la otra al calcular una impar. Esto se obtiene
directamente de la idea de los «papeles invertibles» de los entramados Gen1 y
Gen2. La variable gennumero lleva la cuenta de la generación que se está
calculando y visualizando.
Las rutinas nuevas que se utilizan en este programa se muestran a conti-
nuación. La primera es la rutina 1 ni e; aL; za r, que se muestra en la Figu-
ra 6.5. Simplemente pone a on cada celda del entramado G que se indica por la
fila y columna.

procedure Ini c i a l i zar (val' G: Gr i d);


val'
k, n, i, j: integer;
begin
{pre: entrada =0, x" y" X Z, Y2' a •• , Xn , Yn }
Writeln('Introducir el número n de celdas inicialmente vivas:');
Read(n);
Writeln(' Introducir una serie n de pares de enteros');
Writeln('indicando cada uno las coordenadas x, y de una celda
vi va. ' );
for k :~ 1 to n do
begin
repeat
read(i, P;
until (1 <= i ) and (i <= GridSize(G» and
(1 <= j) and (j <= GridSize(G»;
TurnCellOn(G, i, j);
end;
{post: todas las celdas de G que corresponden a los pares de enteros
Xi' Yi se ponen a on para todo i in 1, ..• , n /\ entrada = vacía)

Figura 6.5. La ruti na 1n i e i a l iza r.


194 Compu tación l. Lógica, resoluc ión de problem as, algoritm os
y progra mas

Además, obsérvese que el procedimiento Inici aLiza r lee reitera


damen te
pares de enteros i, j hasta que el par represente una celda válida
del entra-
mado G. Es decir, si i o j estuvieran fuera de rango de filas y column
as de G, el
par se vuelve a leer. Esto es un ejemplo de la construcción de
progra mas
robustos y amigables. '
La segund a rutina, 80 r ra rG r id, realiza la sencilla tarea de poner
a off
todas y cada u¡na de las celdas del entram ado G. Esto se muestr a
en la Figu-
ra 6.6.

p ..ocedu ..e Bo.... a .. Gr-id (va .. A: Grid);


(post: todas las celdas del Gr-id A se ponen a off}
va"
i,j:in tege .. ;
begin
fa .. i := 1 to Gr-idS ize(A) do
fo .. j := 1 to GridSi ze(A) do
Tu .. nCellO fHA, i, j)
end;

Figura 6.6. La rutina Borra rGrid.

La rutina Vec i nos, de la Figura 6.7, cuenta el númer o de vecinos


vivos
para la celda i, j del entram ado G. Obsérvese que se considera
vecina a
cualqu ier celda viva a la que se acceda directamente desde la celda
i, j por
uno de sus cuatro lados, o por una de sus cuatro esquinas. Por
tanto, cada
celda puede tener como máxim o ocho vecinas. Sin embarg o, las celdas
que se
encuen tran en los bordes del entram ado G tienen menos de ocho vecinos
. Por
ejemplo, la celda 1,1 no tiene más que tres vecinos, y la celda 1,2
no tiene
más que cinco. Para asegurarnos de que la rutina calcula valores
correctos
para esos casos, debemos saber que la rutina Ce LLOn (k, L>, que
se suminis-
tra con el tipo Grid, devuelve el valor faLse siempre que el par
k, L no se
corres ponda con una celda de G. Por ejemplo, si k = O o L = O Ce
LLOn (k,
L) = fa Ls e. Podem os cercior arnos de esto viendo
la especificación de la
rutina CeL LOn (véase Apéndice D).

functi on Vecino s(G: grid; i, j: intege r): intege r;


var
k, l, con t: i nt ege r;
begin
cont := O;
for k : = i - 1 to i + 1 do
for l := j - 1 to j + 1 do
if CellOn (G, k, l) and not ((k ~ i) and (L = j» then
cont := cont + 1;
Vecino s := cont
: post: resul tado = eL número de vecino s vivos de la celda
i, j del grid G:

Figura 6.7. La rutina Vecin os.


Robustez V prueba de los algoritmos 195

Finalmente, la rutina Si gui enteGeneraci ón (véase Fig. 6.8) calcula las


celdas vivas para la siguiente generación del entramado B, a partir de la gene-
ración actual del entramado A. Incluye una interpretación detallada de las
cuatro reglas de supervivencia, nacimiento y muerte.

Etapa 6: Prueba. Para probar este programa suficientemente, necesitamos


diseñar una serie de éasos de prueba, que no sólo prueben el Juego de la Vida
para unas configuraciones iniciales inusuales, sino también otras que suminis-
tren datos de entrada no válidos. Queremos asegurar que nuestro programa es
tan robusto como correcto. Un paso inicial es ejecutar el programa para una
entrada conocida, tal como la de la Figura 6.2. ¿Producirá el programa la
salida correcta para la generación 2?, ¿y para la 3 y sucesivas?
A continuación, debemos probar el programa con otras entradas, especial-
mente aquellas que representan poblaciones con una evolución fácil de prede-
cir, tal como la que se muestra en la Figura 6.9. Esta configuración es intere-
sante porque prueba la corrección del programa cuando las celdas vivas
ocupan los bordes.

procedure SiguienteGeneración (A: Grid; var B: Grid);


var
i, j, cont: integer;
begin
for i := 1 to GridSize(A) do
for j :~ 1 to GridSize(A) do
case Vecinos(A, i, j) of
{RegLa 1. Supervivencia}
2, 3:
if CeLLOn(A, i, j) then
TurnCeLLOn(B, i, j)
{RegLa 2. Nacimiento)
else i f (Nacimiento(A, i, j) = 3) and
(CeLlOff(A, i, j))
then TurnCellOn(B, i, j);
{Regla 3. Muerte por soLedad)
O, 1 :
i f CellOn(A, i, j) then
TurnCellOff<B, i, j);
{Regla 4. Muerte por superpobLación}
4,5,6,7,8:
i f CeLLOn(A, i, j) then
TurnCeLLOff(B, i, j);
end
{post: entramado B =nueva generación de ceLdas vivas, caLcuLadas
del entramado A utiLizando Las cuatro regLas de nacimiento,
supervi venci a y muerte}
end;
Figura 6.8. La rutina SiguienteGeneración.

Etapa 7: Presentación. La presentación del programa está completa cuando se


ha preparado un esbozo de su propósito general, junto con un listado del
196 Computación l. Lógica, resolución de problemas, algoritmos y programas

propio programa y con las salidas de algunas ejecuciones como ejemplo. El


esbozo debe contener no sólo un resumen de lo que hace el programa, sino
también de cómo trata las situaciones excepcionales, junto con otras limitacio-
nes que el usuario se pueda encontrar.

a) b)

Figura 6.9. Un caso de prueba diferente para el Juego de la Vida.


a) Primera generación. b) Segunda generación.

Ejercicio

6.6. Obtener a mano las generaciones 2 y 3 para las generaciones iniciales


siguientes.

a) b) e)

6.4. GARANTíA DE LA ROBUSTEZ: DISEÑO DE CASOS


DE PRUEBA

La metodología de resolución de problemas MAPS proporciona una estructu-


ra que, si se manipula cuidadosamente, puede conducirnos a la obtención de
programas robustos -programas que no sólo funcionan bíen para entradas
correctas, sino que también se comportan «elegantemente» para un amplio
Robustez y prueba de los algoritmos 197

rango de entradas incorrectas-o Una etapa fundamental de la metodología


MAPS es la etapa 6, o etapa de prueba y verificación. Esta es una etapa que, en
nuestro camino, puede que sea la más critica de todo el proceso. Si un progra-
ma resulta no ser correcto como resultado de la aplicación sistemática de
métodos de prueba y/o verificación, todas las etapas anteriores de la metodolo-
gía MAPS habrán sido inútiles. En las Secciones 6.4 y 6.5 introduciremos
métodos esp¡::cificos para llevar a cabo en esta importante etapa de prueba y
verificación. La discusión de estos temas concluye con una evaluación compa-
rativa de la utilización de ambos métodos en la construcción del software.
Un conjunto ya clásico de técnicas para asegurar la corrección, robustez y
amigabilidad de los programas, se incluye bajo el nombre genérico de prueba.
Las técnicas de prueba han sido desarrolladas por diseñadores e ingenieros del
software durante las últimas décadas.

Definición. La prueba de un programa es la aplicación sistemática de casos


de prueba al programa, procedimiento o función, con el objetivo de detec-
tar y corregir los errores (o «fallos») del programa. Los casos de prueba
consisten en distintos conjuntos de valores de entrada, elegidos intenciona-
damente para analizar el comportamiento del programa bajo unas condi-
ciones que pueden o no haber sido sugeridas por el diseñador del progra-
ma. Conjuntamente, todos los datos que están incluidos en esas pruebas
reciben el nombre de juego de pruebas.

El resultado de la prueba ofrece menores garantías que la de la verificación. Es


decir, la prueba, por su naturaleza limitada, no asegura la ausencia de errores
-sólo puede poner de manifiesto los errores presentes.
Para que sea eficaz, la prueba debe realizarse por una persona distinta a la
que construyó el programa. Esto es importante, porque una persona ajena
puede ser bastante más objetiva sobre el comportamiento del programa que la
persona que lo ha desarrollado.
Existen varias formas de diseñar juegos de prueba y de aplicarlas a un
programa o a un procedimiento concreto. Son necesarias estrategias de prueba
más largas cuando se trata de probar programas construidos con extensos
conjuntos de instrucciones, que cuando tratamos de probar casos de estudio.
En esta sección nos concentraremos en el diseño y aplicación de métodos de
prueba rigurosos para programas relativamente pequeños, dejando para el
Volumen II de esta serie los métodos de prueba del software de gran escala.

6.4.1. Ejemplo: Prueba de un procedimiento o función


completos

Cuando desarrollamos una abstracción procedimental para un problema bien


definido, o para una rutina, es necesario probarla ejecutando el procedimiento
suministrándole una amplia gama de valores a los parámetros de entrada, y
198 Computación l. Lógica, resolución de problemas, algoritmos V programas

asegurándonos de que, en cada caso. el resultado del procedimicnto es consis-


tente con sus poscondiciones.
Puesto que es imposible probar (odos los posibles valores de entrada. ten-
dremos que seleccionar un subconjunto de ellos que sea significativo, y ejecutar
el programa e imprimir el resultado con cada uno de los elementos de ese
subconjunto. U na forma bastante útil de realizar este proceso es construir un
programa cO/lductor. Se denomina así porque, literalmente. «guia» la ejecución
e impresión de resultados del procedimiento o función con los valores seleccio-
nados para la prueba.
Ilustremos estas ideas construyendo un juego de pruebas para la función
MaxL i sta (véase Figura 5.12). ¿Cuál seria un conjunto de pruebas apropiado
para esta función, y cómo podemos diseñar un conductor para estudiar el
comportamiento de MaxL i sta frente a estos datos?
Los juegos de prueba pueden crecer rápidamente. Consideremos el número
total de posibles entradas válidas que podríamos construir para la función
MaxL i sta, utilizando O ó más elementos. Supongamos que cada elemento es
un entcro en el rango - 2':; al 2':; - 1, habrán 2\6 = 65.536 listas diferentes
de un solo elemento. El número de posibles listas diferentes de /l elementos
resulta astronómico --seguramente excesivo para incluirlas en un juego de
pruebas.
Pensemos un momento sobre algunas posibles listas que pueden ser pasa-
das a MaxLista. y a lo que esperamos que MaxLista produzca como res-
puesta. En la Tabla 6.2 se ofrecen algunas ejemplos obvios.

Tabla 6.2

Parámetros de entrada L m Resultado esperado

(3 1 2 8) 4 4

(8 1 2 3) 4

(3 8 1 2) 4 2
(-1 ·2··3 -8) 4

(3 1 2 8) 3

(3 1 2 8) 2
(3 2 8)

Los dos primeros datos de pruebas son importantes porque el valor máximo
ocupa la última y primera posición de la lista. respectivamente. El tercer caso
también es importante. pues representa el caso típico que puede ocurrir---es
Robustez y prueba de 105 algoritmos 199

decir, el valor máximo ocupa una posición cualquiera entre el primero y el


último elemento de la lista--. El cuarto comprueba si MaxL i s ta trabaja
correctamente con valores negativos. Los tres últimos son importantes, puesto
que restringen el valor de m forzando a la función a buscar el máximo en una
sublista.
Pero, ¿qué otras cosas deben introducirse en un juego de pruebas~ Eviden-
temente, debem9s asegurarnos de que la lista vacía ( ) devuelva el valor O.
¿Qué hacer con los otros incontables casos que se nos pueden presentar?
¿Cómo presentar a MaxL i sta un subconjunto de esos casos que nos asegure
que el procedimiento hace lo que debe? Así mismo, ¿cómo podcmos probar la
robustez y amigabilidad de Ma xLi s ta? Es decir, ¿cómo se comportará Ma x-
Lista con entradas como la lista (-1, -2, hoLa, -8)? ¿ü ante un valor
de L, como I hoLa I o I asrtfadesr I o 3332123442 que no sean una
lista? Debemos añadir todos esos casos a nuestro juego de pruebas para deter-
minar cómo responde MaxL i sta.
Por tanto, debemos ser muy selectivos al diseñar los juegos de prueba
para un procedimiento. Sólo debemos incluir en los juegos de pruebas aquellas
entradas que representen a un gran número de casos posibles. Es conveniente
almacenar aparte nuestro juego de pruebas en un archivo de disco, de forma
que no necesitemos reescribirlo cada vez que queramos probar el procedi-
miento.
En la Figura 6.10 se muestra la estructura general dc un conductor de
pruebas. En la notación utilizada <p (x, y) > simboliza a un procedimiento p
con parámetros de entrada x y parámetros de salida y; <archivo juegos
prueba> simboliza al archivo del que se pueden obtener los juegos de prueba.
Esta estructura es fácilmente adaptable para el caso en que lo que deseemos
sca probar una función f (x) en lugar de un procedimiento p (x, y); en este
caso la variable y se identifica con la salida de la función, la instrucción <p (x,
y» se sustituye por y := f(x) en el conductor de la Figura 6.10, y los
comentarios se modifican para indicar que se prueba una función en lugar de
un procedimiento.

program conductor;
(Este programa conductor prueba eL procedimiento <p(x, y»}
{ut i Lizando un juego de pruebas como datos de ent rada}
(deL archivo <archivo juego pruebas>}

procedure <p(x, y»;


begin

end;

var <variabLes correspondientes a Los parámetros>;


<archivo juego pruebas>: text;
begin
WriteLn(' Comi enza La prueba deL procedimi ento <p(x, y»');
read«archivo juego pruebas», <x»;
while not eof«archivo juego pruebas» do
200 Computación l. Lógica, resolución de problemas, algoritmos y programas

beg;n
WrHeLn('Parámetros de entrada = " <x»;
<p(x, y»;
Wr; teLn(' Resul tado de p(x, y) =' , <y»;
read«arch;vo juego pruebas>, <x»;
end;
WrHeLn( 'F;nal de la prueba del proced;m;ento <p(x, y»')
end.

Figura 6.10. Estructura general de un conductor de pruebas.

Supongamos que se quiere probar la función MaxL; sta utilizando un


programa conductor y el archivo de juegopruebasmax que contiene las
listas siguientes:

(3 1 2 8) 4
(8 1 2 3) 4
(3 8 1 2) 4
(-1 -2 -3 -8) 4
(3 1 2 8) 3
(3 1 2 8) 2
(3 1 2 8) 1
(-1 -2 hello -8) 4
() O

Los enteros que aparecen a la derecha no están almacenados en el archivo,


sino que son escritos por el usuario cuando el conductor se lo solicita. El
programa conductor siguiente podría ser eficaz:

program Conductor;
{Este prog rama conductor prueba la fune; ón MaxU sta ut H ; zando un
juego de pruebas como datos de entrada del arch;vo juegopruebasmax}

function MaxUsta(L: Usta; m: integer): integer;


val' j, k: ;nteger;
begin
{pre: L = (e 1 , e 2 , •• _, e m, •• _, en) A n ~ O 1\ cada e i es un número}
i f (O < n) and (m <= LengthUsta(L» then
begin
j := 1;
for k := 2 to m do
{; nv: v; E {1, ... , k - 1 }: e [j] >= e [;] /\ 2 <= k <= m + 1 }
if L[k] > L[j] then
j := k;
end
else
j := O;
MaxUsta := j;
( pos t: n > O /\ v; E {1, ... , n}: e j ;;O e, /\ re su l tado = j v
n = 0/\ resul tado = O}
end;
Robust ez y prueba de los algoritm os 201

varL: Lista;
m, j: intege r;
juegop ruebas max: text;
begin
WriteL n( 'Comie nza la prueba de la funció n MaxLi sta(L,
m)');
WriteL n('Intr oducir el nombre del archiv o del juego de
prueba s: ');
ReadL ista(U ;
while not eof(jue goprue basma x) do
begin
WriteL n( 'Lista de entrad a L ='); Write Lista( U;
Write (Intro ducir valor de m: '); ReadLn (m);
j := MaxLi sta(L, m);
WriteL n('Res ultado de MaxLi sta(L, m) ~ " j);
ReadL ista(U ;
end;
WriteL n('Fin al de la prueba de la funció n MaxLi sta(L,
m)
end.

La salida que produce este conduc tor cuando se le suministra el archivo


j ue-
gopru ebasm ax se muestra en la Figura 6.11.

Comien za la prueba de la funció n MaxLi sta(L, m)


Introd ucir el nombre del archiv o del juego de prueba s:
milist a
Lista de entrad a L =
(3 1 2 8)
Introd ucir valor de m: 4
Result ado de MaxLi sta(L, m) = 4
Lista de entrad a L =
(8 1 2 3)
Introd ucir valor de m: 4
Resul tado de MaxLi sta(L, ro) = 1

i.. i sta de entrad a L =


O
Int roduci r va lar de ro: O
Result ado de MaxLi sta(L, ro) = O
Final de la prueba de la funció n MaxLi sta(L, ro)

Figura 6.11. Result ado de ejecuta r el conduc tor de prueba


s para
la función MaxL ista, con el archivo de entrada juegop ruebas
max.

Como puede verse, la construcción y utilización de un conduc tor de


prue-
bas es casi un proceso mecánico. Sin embargo, cuando se utiliza con
cuidado,
no sólo nos permite descubrir errores ocultos, sino que nos sugiere
formas de
conseguir que el progra ma sea más robust o y amigable. j Utilizar el
procesa-
miento con un juego de datos desconocido puede conducirnos a
resuÍtados
sorprendentes! Sin embargo, es preferible realizar esos descubrimien
tos en el
momento de la prueba, que no después, cuando se ha entregado el
procedi-
miento a una persona desconocida para cualquier utilización.
202 Computación l. Lógica, resolución de problemas, algoritmos y programas

6.4.2. Ejemplo: Prueba de un programa completo

Debemos probar los programas «de abajo a arriba». Es decir, debemos probar
todos los procedimientos y funciones antes de probar el programa que los
utiliza. Después de realizar esto, podemos proceder a la prueba del programa
completo en la misma forma en que probamos un procedimiento. Para cllo,
convertimos e' programa en una especie de autoconductor de si mismo, aña-
diéndole un bucle externo que lea sistemáticamente datos de entrada alternati-
vos desde un archivo externo que contenga un juego de pruebas, y después
prosiga con su ejecución normal, produciendo las salidas a todas las entradas
del juego de pruebas, en lugar de la salida de un único juego de datos.
Por ejemplo, considérese el programa Ca Lcu La CM, de la Figura 4.6, el cual
modificamos para ser más amigable (véase Sección 6.1). Para probar este pro-
grama, podemos convertirlo en el autoconductor que se muestra más abajo, y
ejecutarlo con los datos de el archivo que contiene varias listas de calificacio-
nes alternativas, en lugar de sólo una. El resultado de convertir este programa
en amigable y en un autoconductor, se muestra en la Figura 6.12. Para conver-
tirlo en autoconductor, basta con añadirle un bucle repea t abarcando las pre
y poscondiciones del programa. Esta modificación hace que el programa lea y
procese entradas hasta que lea una lista vacia 0, lo que señala el fin de las
ejecuciones de prueba.

program caLcuLaCM;
uses
Listas;
var
Notas: Listas;
i, n: integer;
Sum, CM: reaL;
entradaváLida: BooLean;
beyin
repeat
{pre: entrada = (Notas" Notas" ... , Notas") 1\ n> O
1\ \1'i E {1, .""' n}: Notas, E (O, .. "' 4}}
repeat
WriteLn ('Introducir La Lista de caLificaciones:');
ReadLista(Notas);
n :~ LongLista(Notas);
entradaváL ida :~ true;
for i : ~ 1 to n do
if not isNumeric(Notas[i] or Notas[i] < O) or (Notas[i] > 4) then
entradaváL ida := faLse;
until entradaváLida;
i f n > O then
beyin
Sum :~ O:
i := 1;
whi le i <= n do
begin
Sum := Sum + Notas[i];
Robustez y prueba de los algoritmos 203

i := i + 1
end
CM = Sum/n;
WriteLn('La CM de esas caLificaciones es=', CM: 5: 2)
end {i f}
{post entrada =0"
saLida=Sumi E {1, ... ,n}:Notas;ln}
untiL n ~ O;
end.

Figura 6.12. Un autoconductor para la versión amigable del CaLcuLaCM.

6.5. GARANTíA DE CORRECCiÓN: VERIFICACiÓN


DE PROGRAMAS

Como metodología general, la veríficacíón de programas tíene como objetívo


la aplícacíón de la lógíca matemática, para demostrar formalmente que el
programa incluido entre la precondicíón y la poscondición cumple esas especi-
ficaciones bajo cualquier circunstancía de ejecución posible. En este sentido, la
verificacíón formal es un logro relativamente nuevo en el área del software.
Pienso que es importante tener unos conocimientos básicos de la verificación
formal, sus métodos y sus objetivos. De todas formas, esta materia está en un
«punto de corte» de la investigacíón en informática. La verificación de progra-
mas proporciona a los lectores un punto de vista alternativo sobre la correc-
ción de los programas, que pone de manifiesto uno de los mayores defectos de
las técnicas de prueba clásicas: éstas nunca garantizan la corrección.
El estilo de verificación que utilizaremos es informal, resaltando los funda-
mentos más que explicando el proceso de demostracíón con mucho detalle.
Partimos de la hipótesis de que las propiedades algebraicas de los números,
con las que estamos familiarizados, se preservan cuando se utilizan los núme-
ros en programación. Sabemos que es una restricción bastante fuerte. Sabe-
mos, por ejemplo, que en los números -reales o enteros- no tiene una repre-
sentación exacta en los programas Pascal. Sabemos también que, en
matemáticas, las operaciones entre reales y enteros siempre producen resulta-
dos exactos, mientras que en computación se introducen a veces pequeños
errores. Para el propósito de esta introducción, ignoraremos estas pequeñas
desviacíones, con objeto de simplificar la presentación de la verificación y
centrarnos en los principios y en la metodología.

6.5.1. Tableau de demostración

Para verificar un programa, desarrollamos una prueba del tableau. Un comien-


zo de prueba del tableau, o «tableau vacío», es un listado del programa con sus
precondiciones, poscondiciones y comentarios vacios antes y después de cada
204 Computación l. Lógica, resolución de problemas, algoritmos y programas

instrucción, tal y como se muestra en la Figura 6.13. El objetivo de la verifica-


ción es rellenar este tableau con asertos válidos. Cada instrucción del progra-
ma debe preceder y seguir a un aserto, y debemos demostrar, siguiendo una
línea sistemática de razonamiento, que la instrucción conduce de una situación
que satisface el aserto que le precede al que le sigue, partiendo de otros cuya
validez ya ha sido demostrada. Así, la verificación de un programa es un
proceso en el que se desarrollan una serie de asertos (y razonamientos que les
acompañan) en una forma similar al de las demostraciones de la lógica (como
vimos en el Capítulo 3). El resultado de este proceso se denomina demostración
de la corrección del programa, o simplemente demostración.
El proceso por el que descubrimos sistemáticamente la validez de los aser-
tos que componen una prueba, se le denomina razonamiento sobre programas.
Podemos definir esas ideas más formalmente como sigue:

Definición. Una demostración es un conjunto de asertos P1 , Pu ... ,


Pr>+1' que, cuando se insertan sucesivamente entre las instrucciones 51' Su
••• , sn en un tableau vacío, dan origen a la siguiente prueba de tableau:

begin
(precondiciones}
{P,}, 5,; {P 2 }, 52; ••• ; {Pn}' Sn; {P n+,}
(poscondiciones}
end

Donde los asertos siguientes son válidos (es decir, tautologías):

1. precondi ciones =<> P,


2. {P;}, s,, (P '+1} para todo i en (1, ••. , n}
3. P0+' =<>poscondi ciones

La notación {P;} s; {P H1} quiere decir que, respecto de la ; -ésima ins-


trucción del programa, si P; es válido antes de la ejecución de S;, el aserto
P H1 será válido después de la ejecución de s;. En otras palabras, Pi y P H1
son, respectivamente, precondición y poscondición de la instrucción s;.

Supongamos que tenemos el programa y el tableau vacío de la Figura 6.14. Es


decir, queremos demostrar la corrección del programa que se ha diseñado para
calcular y escribir el resultado del producto de dos números cualquiera n 1 Yn 2 .
Para completar la demostración, necesitamos descubrir y justificar los aser-
tos p 1 al P4 del tableau, uno a uno, hasta rellenarlo. El resultado se muestra en
la Figura 6.15. Las justificaciones que aparecen en la columna de la derecha de
la Figura 6.15 son el resultado de aplicar equivalencias lógicas y reglas de
inferencia a la secuencia de instrucciones de Pascal. Algunas de esas justifica-
ciones (por ejemplo, aritméticas) se basan en las hipótesis comunes que se
suelen realizar en la aritmética. Otras (por ejemplo, A -introducción) se han
tomado de las reglas de inferencia de la lógica (véase Capítulo 3). Otras (por
Robustez y prueba de los algoritmos 205

begin
(pre: precondición)

5,;

(post: poscondición);

Figura 6.13. Aspecto inicial de una prueba de tableau: el tableau vacío.

ejemplo, la regla de la asignación) reflejan las propiedades de algunas instruc-


ciones de Pascal (véanse Secciones 6.5.2 a 6.5.5), y las estudiaremos en este
capítulo.
La demostración completa del tableau refleja el estilo de las demostraciones
de la lógica, tal como se vio en el Capítulo 3.

begin
{pre: entrada = n1, n2 /\ salida =0l
( @)
Read (x, y ) ; - - -
{ @
z:=x*y; ~
{ @Y
Write (z) - - - -
( )
{post: entrada =0/\ salida = n1 * n2
end.

Figura 6.14. Tableau vacío para un sencillo programa de tres instrucciones.


206 Computación l. Lógica, resolución de problemas, algoritmos y programas

6.5.2. La regla de inferencia de la asignación

Al igual que otros métodos de demostración, la verificación de programas


utiliza los axiomas y las reglas de inferencia. Las reglas de inferencia son
generalizaciones sobre el comportamiento de determinados tipos especificos de
instrucciones de los programas en Pascal. Estas reglas aportan métodos con
los que derivar asertos nuevos, a partir de otros existentes, para algunas ins-
trucciones específicas.

begin
{pre: entrada ~ n1, n2 /\ salida =0} Jus! ¡Cica! iuus
~{entrada~n1, n2 } {A-eLiminación
QY Read (x, y);.. @
[entrada =0/\ x = n1 /\ y = n2 /\ x*y = n1*n2) i regla de la asignación,
® z:~x*y;.. @ aritmética,
/\-introducción}
{regla de la asignación,
{x = n1 /\ y = n2 /\ z = n1 *n2 /\ sa l i da = 0
® Write (z) .. @ A-eliminación,
/\-introducción}
{z = n1 *n2 /\ sa l i da = n1 *n2 } {regla de la asignación,

® {post: entrada =0/\ salida ~ n1 * n2 }


/\-el iminación}
{/\-eliminación,
/\-introducción}

Figura 6.15. Prueba del tableau completa


para un programa sencillo.

En la verificación formal de programas, el sistema de inferencias, y concre-


tamente las reglas de inferencia, son mucho más complicadas que las que se
han presentado aquí. Consideremos cuatro reglas de inferencia básicas para los
programas: la regla de la asignación, la regla de los procedimientos, la regla de
las condicionales y la regla de los bucles.

Definición. La regla de inferencia de la asignación.

a) {P(e)} v := e { }
{P(e)} v := e {P(v)}

b) {entrada ~ n} Read(v) { }
[entrada ~ n} Read(v) {entrada = 0 /\ V = n}

e) [salida=0/\e=n} Write(e):}
[salida ~0 /\ e = n) Write(e) {salida = e}
Robustez y prueba de los algoritmos 207

El apartado a) de esta regla dice que si una proposición es válida para el


estado de una expresión e (la satisface) antes de que la instrucción de
asignación v : = e sea ejecutada. Podemos inferir que la misma proposi-
ción es válida para la variable y después de la ejecución de la asignación.
Los apartados b) y e) simplemente destacan el hecho de que las instruc-
ciones Read y Wr; te producen el mismo efecto que la asignación. En la
instrucción .Read, el origen del valor para la asignación es el flujo de
entrada, y el receptor la variable v. En la instrucción Wr; te, el origen del
valor es la expresión e, y el destino el propio flujo de salida. Más concreta-
mente, el apartado b) significa que la instrucción Read retira un valor n del
flujo de entrada, y se lo asigna a la variable v. El apartado e) indica que la
instrucción Wri te(e) añade una copia del valor n, que actualmente es el
resultado de evaluar la expresión e, al flujo de salida. Esto no es más que
una formalización directa de lo que ya conociamos de la actuación de
Read y Wr; te en nuestros programas.

Por ejemplo, en el programa de la Figura 6.14, supongamos que lo único


que conocemos antes de la ejecución de la instrucción de asignación z : = x * y,
es que los valores de x e y son dos números n1 y n2. Por las reglas de la
aritmética, también sabemos que x * y = n1 * n2. Podemos utilizar estos
conocimientos en la forma siguiente:

{x*y~nl*n2}
z:=x*y;
{z=n1 *n2} {regLa de La asignación}

Lo único que hemos hecho, al realizar esta inferencia, ha sido poner z en todas
las ocurrencias de x * y en la proposición ya conocida P (x * y) para poder
obtener P (z >. Obsérvese que se ha marcado esta inferencia de la demostra-
ción, con una justificación situada a la derecha, para clarificar las razones de la
inferencia realizada. Esto es consistente con el estilo de las demostraciones de
la lógica.
Está claro que esta regla es fácilmente extensible para instrucciones Read y
Wr; te, con más de una variable, para las instrucciones ReadLn, Wr; tLn,
ReadL; sta y Wr; teL; sta, y así sucesivamente. Por simplicidad, no re-
cargaremos esta regla tratando de formalizarla para todas esas situaciones,
aunque la utilizaremos en nuestras demostraciones como si lo hubiéramos
hecho.
Como ejemplo adicional, consideremos la primera instrucción del progra-
ma de la Figura 6.14, para la que se cumple lo siguiente cuando razonamos
sobre ella:

{ent rada ~ n1, n2)


Read(x, y);
{ }
20S Computación l. Lógica, resolución de problemas, algoritmos y programas

La parte b) de la regla de la asignación nos permite escribir la inferencia


siguiente:

(entrada = n1, n2]


Read(x, y);
{entrada =0/\ x = n1 /\ y ~ n2} {regla de la asignación}

Lo que justifica formalmente que se les asignan valores a x e y, utilizando la


instrucción Read, y los valores son eliminados, simultáneamente, del flujo de
entrada.
De forma similar, podemos razonar sobre la instrucción Wr i te, de la
Figura 6.14, utilizando el apartado e) de la regla de asignación. Comencemos
con:

{z = n1 * n2 /\ sal ida =0}


Wr ite (z);
{

Lo más importante de esta inferencia es que z debe tener el valor n1 * n2,


para que dicho valor pueda aparecer en el flujo de salida. Podemos escribir
esta conclusión formalmente utilizando la regla de la asignación de la forma
siguiente:

{z = n1 * n2 /\ sal ida =0}


Write(z);
{salida=n1 *n2} {regla de la asignación}

El razonamiento sobre programas siempre requiere que utilicemos activa-


mente nuestros conocimientos sobre aritmética, aunque no reflejemos los deta-
Hes de este conocimiento durante el proceso de la demostración. Aqui presen-
tamos otro ejemplo. Supongamos que sabemos que el valor de la variable i es
no negativo, inmediatamente antes de que se ejecute la asignación i := i + 1
en un programa. ¿Cómo nos puede ayudar la regla de la asignación a razonar
acerca de esto? Formalmente comenzamos con:

{i ~ O]
i:=i+1;
{i > Ol {Regla de la asignación, aritmética}

Si analizamos detenidamente los detaHes de esta asignación, descubriremos


que si i ?> O, antes de la asignación, entonces ; + 1 ?> 1. Así, para utílizar la
regla de la asignación identificamos las variables v e i + 1 con la expresión e,
y el aserto P (e) con i + 1 ?> 1. Por todo ello, podemos inferir P (v) o i ?> O.
Aplicando de nuevo las reglas de la aritmética, podemos reescribir el aserto
sólo en términos de la variable i, lo que nos conduce a i > O.
Robustez y prueba de los algoritmos 209

6.5.3. Reutilización de las reglas de inferencia de la lógica

Cuando escribimos la demostración de un programa, además de las reglas que


definen el comportamiento del programa y la aritmética, utilizamos las reglas
de inferencia de la lógica que estudiamos en el Capitulo 3. Esas reglas nos
permiten «mover» los asertos a lo largo del programa, lo que permite utilizar-
los para escribir nuevas inferencias en otros puntos del programa. Por tanto,
podemos conseguir una línea de razonamiento coherente que incluya a todo el
programa, desde el principio hasta el final.
Para ilustrarlo, consideremos el programa de la Figura 6.15. Obsérvese
cómo se utiliza la regla de la asignación para construir ciertas partes de los
asertos del P1 al P4' Pero, ¿qué ocurre con sus interconexiones? Por ejemplo,
¿cómo justificar la migración del aserto ent rada = 0 desde P2 , donde se
origina, saltándose P3 y P4' Yapareciendo finalmente en la poscondición donde
se necesita para que la prueba esté completa? Aunque la regla de la asignación
no nos permite realizar esta migración, las reglas de inferencia de la lógica, sí.
El primer paso de la demostración de la Figura 6.15 contiene el aserto P1

{ent rada = n1, n2}

que se infiere de la precondición, utilizando la /\ -eliminación

{ent rada = n1, n2 " sa l i da = 0}


La derivación de P2 es el resultado de la combinación de tres pasos diferentes:
la regla de la asignación, las propiedades de la aritmética, y la regla de in-
ferencia de la 1\ -introducción. En concreto, la proposición x*y = n1 *n2
aparece en P2 como preparación para la derivación de los asertos P3 de P2
y S2'
Las reglas de la /\ -introducción y de la /\ -eliminación se utilizan también
a lo largo de la prueba. Sin embargo, esas reglas de inferencia deben aplicarse
con cuidado cuando el predicado que se va a añadir viene de una posición
distante del programa. Por ejemplo, considérese la reintroducción del aserto
sa l i da = 0 en la proposición P3 después de haber desaparecido de las propo-
siciones P 1 y P2 . Esto es posible hacerlo si en el programa no se han ejecutado
instrucciones que invaliden este predicado: es decir, alguna instrucción Wr i te
(o, de forma equivalente, alguna aplicación del apartado e) de la regla de
asignación) entre la última posición en que el predicado era válido y la instruc-
ción donde se reintroduce.

6.5.4. Reglas para las condicionales

Los programas incluyen otros tipos de instrucciones, aparte de las que asignan
valores a las variables. Cuando se pretenden escribir demostraciones sobre los
programas, es necesario conocer axiomas para poder razonar sobre selecciones
210 Computación l. Lógica, resolución de problemas, algoritmos V programas

condicionales (instrucciones i f), invocaciones a procedimientos y funciones


sobre bucles. Existen reglas de inferencia que son aplicables a estas instruccio-
nes.

Definición. La regla de inferencia de la selección condicional.

a) ;P 1\ 6} 5 {Q) b) {P 1\ 6) 5, {Q }
PI\-6o>Q {P 1\ -6) 5, {Q )
{Pi i f 6 then 5 {Q) {Pi i f 6 then 5, el5e 5, {Q}

El apartado a) permite inferir la validez de Q tras la ejecución de la selec-


ción condicional, si podemos inferirla independientemente de cuál alterna-
tiva se haya seguido -en una tanto, P como B son verdaderas, y se ejecuta
la instrucción s; y en la otra, B es falso, y P Q es también válido-o El =
apartado b) es simplemente una extensión del a), en la que se establece la
validez de q, independientemente de cuál de los dos caminos alternativos se
haya seguido -ejecución de S1 (cuando B es ve rdade ro), o la de S2
(cuando B es fa Lso).

Consideremos las asignaciones y selección condicional siguientes, que se


han diseñado para asignar a la variable z el mayor de los valores x o y:

z := x;
i f x <~ y then
z :~ y

Para verificar esto, tenemos que establecer la validez del aserto Q:

Q= {z = X 1\ X > y v z ~ y 1\ X <~ y)

Que es una manera más formal de describir la salida que deseamos para z. Las
inslrucciones de asignación y la selección condicional están en secuencia, por
lo que necesitamos utilizar, en el proceso de verificación, tanto la regla de la
asignación como la de la selección condicional. Por tanto, podemos comenzar
con lo siguiente:

I 1
, J
Z := x;
{z = x}
if x <~ y then
z := y
{x > y 1\ Z = X v X <= Y 1\ Z = y)

Utilizando la regla de la asignación, podemos identificar P como z = x (la


validez de P se deduce de la linea anterior a él y de la regla de la asignación), y
B como x <= y. Por tanto, tendremos que demostrar ahora la validez del
Robustez y prueba de los algoritmos 211

aserto Q para completar la demostración. Sustituyendo por B, P Y Q sus


valores en la regla de la selección, obtendremos:

{z ~ X /\ X <= y} z : = y (z ~ X /\ X > y v z = y /\ X <= y) /\


{z = X /\ ~(x <= y) "'" z ~ X /\ X > y v z = y /\ X <= YJ

Para demostrar la validez del aserto anterior, consideramos los dos casos
x <= y Y~(x <= y) que, como sabemos por aritmética, son los únicos posibles.
Cuando x <= y la linea 1 es válida, puesto que se realizará la asignación
x : = y, y la regla de la asignación garantiza que z = y. La regla de la
/\ -introducción nos permite inferir z = y /\ X <= y, Yfinalmente, la regla de la
v -introducción nos permite inferir z = x /\ x > y v z = Y /\ X <= y. La línea 2
también es trivialmente válida, puesto que ~(x <= y) es fa Lso y fa Lso =o> p
es siempre válido para cualquier proposición p.
En el segundo caso, x > y, la segunda línea de la disyunción de arriba es
válida. Es decir, tanto ~(x <= y) (o equivalentemente, x> y) y z = x son
válidos, por lo que lo es su conjunción por la /\ -introducción. Pero

z =X /\ ~(x := y)

es equivalente a

z=x/\x>y

por lo que la línea 2 completa tiene la forma de un aserto de tipo p =o> P V q,


que es válido por la v -introducción. La línea 1 también es trivialmente válida,
puesto que x <= y es fa Lso. Esto completa la justificación para el aserto Q.
Considérese la alternativa siguiente para asignar a z el mayor de los valo-
res de x e y:

if x > y then
z := x
else
z := y

Para demostrar esto, es necesario establecer de nuevo la validez del aserto Q:

Q= {z = X /\ X > y v z = y /\ X <= y}

Utilizaremos de nuevo la regla de inferencia de la selección condicional, junto


con otras reglas de inferencia apropiadas, la aritmética e identidades. Comen-
zamos con un tableau de prueba parcial:

{verdadero}
i f x ) y then
z := x
else
z :~ y
: z = x /\ x > y v z ~ y /\ X <= y}
212 Computación l. Lógica, resolución de problemas, algoritmos V programas

Identificamos B como x> y, 5, como z := x, 52 como z := y, y P como


verdadero (esto es lo mismo que decir que no nos importa cuál era el valor
que tenian esas variables antes de ejecutar la selección condicional). Asi, la
parte b) de la regla de la selección tomaria la forma siguiente, de la que debe-
mos demostrar su validez para establecer la propia validez de Q:

{verdadero A x ~ y} z := x {z = X A X > y v z = y A X <= y} A


{verdadero A ~(X > y)} z := y{ z = X A X > y v z = y A X <= y}

Analicemos por separado cada una de las lineas de la disyunción, como hici-
mos en el ejemplo anterior. La línea 1 se corresponde con el caso en que x > y.
Utilizando la regla de la asignación, la propiedad de la identidad para el valor
verdadero y la 1\ -introducción, podemos poner lo siguiente:

{X>Y} z :~X(Z=XAX>Y}

Por lo que, utilizando la v -introducción, es posible validar la línea I con la


hipótesis de que x > y. La linea 2 cubre el caso alternativo en el que supone-
mos que x <= y. Es posible establecer la validez de la línea 2 siguiendo
razonamientos similares a los que se utilizaron con la 1.

6.5.5. Verificación de bucles

Para verificar un bucle de un programa es necesario realizar dos operaciones


independientes: debemos encontrar y justificar sus asertos asociados, y debe-
mos verificar por inducción su invariante.
Recuérdese, del Capítulo 4, que es posible explicar lo que hace un bucle
«desplegándolo». Es decir, si reescribimos el bucle como una secuencia de
ocurrencias del cuerpo del bucle, podemos deducir con exactitud qué clase de
generalización representa el bucle. Considérese el bucle wh i l e de la Figu-
ra 6.16, que suma los enteros del 1 al 5.

sum := o;
i :~ 1;
while i <= 5 do
begin
5um := sum + i;
i:=i+1;
end

Figura 6.16. Un bucle wh i Le sencillo.

Las cinco ejecuciones del cuerpo del bucle pueden ser desplegadas de la mane-
ra siguiente:

sum : = sum + i;
i:=i+1;
Robustez y prueba de los algoritmos 213

sum : = sum + i;
i:=i+1;
suro : = sum + i;
i:=i+1;
sum : = sum + i;
i:=i+1;
sum := sum + i;
i:=i+1;
,
Recuérdese también de! Capitulo 4, que el invariante del bucle es un aserto
que es válido antes y después de cada repetición del bucle, incluidas la primera
y la última. El invariante para e! bucle de arriba es:

{inv: sum=Su. j E {1, •.. , i -1}: j 1\ 1 <= i <=6)

Es posible comprobar que este invariante es verdadero, tanto antes como


después de cada repetición del bucle wh; Le. En particular, el bucle explica
cada uno de los pasos individuales de la expansión del bucle siguiente:

{sum = O 1\ i = 1 }
sum := sum + i;
i:=i+1;
{sum = O + 1 1\ i = 2}
sum := sum + i;
i:=i+1;
{sum = O + 1 + 2 1\ i = 3}
sum : = sum + i;
i:=i+1;
{sum = O + 1 + 2 + 3 1\ i = 4}
sum := sum + i;
i:=i+1;
{sum = O + 1 + 2 + 3+4 1\ i ~ 5}
sum := sum + i;
i:=i+1;
( sum = O + 1 + 2 + 3+ 4+5 1\ i = 6}

Otro aspecto importante de la verificación de bucles es demostrar que termi-


nan. Es decir, estamos interesados sólo en bucles controlados, bucles que termi-
nan después de un número de pasos finito, independientemente del estado de
las variables en el momento en que empieza su ejecución. Existen tres reglas de
inferencia diferentes para describir la condición de terminación de un bucle,
una por cada tipo de bucle existente. Cada una de estas variantes utiliza el
invariante y la presunción de terminación de forma diferente.

Definición. La regla de inferencia de los bucles.

a) { i nv 1\ e} s {i nv}
{inv} while e do s {inv 1\ -e}
214 Computación l. Lógica, resolución de problemas, algoritmos y programas

b) (inv) s {inv}
{inv) repeat s until B {inv A B)

e) (inv A 1 ~ i ~ n} s {inv}
[ i nv) for i : = 1 to n do s {i nv A i = n + 1)

La parte a) de la regla dice que si {i nv !\ B} s {i nv} es válido para una


única ejecución del cuerpo del bucle s, entonces {i nv} whi le B do s
{; nv !\ ~B} es válido para el bucle completo. Obsérvese que la condición
de terminación del bucle (~B) está incluida en la expresión de forma
explicita. Los apartados b) y e) son similares, aunque se aplican a los otros
dos tipos de bucles de Pascal.

Reconsideremos el bucle whi le de la Figura 6.16. La regla del bucle nos


permite inferir los asertos siguientes, en los que B es la expresión i <= 5.

5Uro := o;
i :~ 1;
while i <= 5 do
{i nv: sum = Sum i E (1, ... , i - 1 ): i A 1 <~ i <= 6)
begin
sum := suro + i;
j:~i+1;
end
{sum = Sum i E {1, ... , i - 1 ): i A 1 <~ i <= 6 A -( i <= 5) )

Este último aserto puede simplificarse a

{sum = Sum i E {1, o o ., 5}: i A i = 6)

utilizando reglas elementales de la aritmética.


Observando las formas de la regla de los apartados b) y e), podemos ver
que la relación entre el invariante y la condición de terminación son ligeramen-
te diferentes, y reflejan con exactitud el significado de los bucles repeat y
foro Por ejemplo, si reescribimos el bucle de la Figura 6.16 en forma de bucle
repeat, la regla del bucle nos dará lo siguiente:

sum :~ O;
i := 1;
repeat
(inv: sum= Sumi E [1, o •• , i -1): i A 1 <= i <~6}
sum := suro + i;
i:~i+1;
until i > 5
{sum = SUII j E {1, .. o, i - 1 }: j A 1 <~ i <= 6 A i > 5}

Donde la condición B es ahora; > 5, que especifica la condición para que el


bucle termine (en lugar de la condición para que continúe, como pasaba en el
Robustez V prueba de los algoritmos 215

bucle wh; Le). De nuevo, podemos simplificar utilizando las reglas de la arit-
mética:

{ s um = SUI! j E {1, ... , S}: j /\ ; = 6}

Finalmente, reescribiremos el bucle de la Figura 6.16 como un bucle for.


Podemos ver de nuevo que la regla para este bucle alternativo genera los
asertos siguientes:

sum :~o;
for ; := 1 to 5 do
{ ; nv: sum ~ SumjE {1, ... , ; - 1 }: j /\ 1 <~ ; <= 6}
sum := sum + i;
{sum = Su. j E {1, •.. , ; - 1 }: j /\ 1 <= ; <= 6 /\ ; = 6}

donde el último aserto se simplifica de nuevo a

{ sum = Sum j E {1, ... , S}: j /\ ; = 6}

Utilización de la inducción en la verificación del invariante. Hasta ahora, he-


mos admitido la validez del invariante del bucle muy informalmente. Es decir,
hemos supuesto que el invariante es, sin ninguna duda, válido para el bucle
que describe. Sin embargo, cuando verificamos un programa, debemos escrutar
la corrección de todos los invariantes del bucle. ¿Cómo podemos hacer esto?
Puesto que cada iteración del bucle sigue a una iteración anterior, podemos
utilizar el método de la inducción que se introdujo en el Capitulo 3.
Aqui, la base de la inducción es el número ;, de la iteración del bucle
que se va a ejecutar. Concretamente, es necesario demostrar: 1) Que el in-
variante se satisface antes de la primera interación; y 2) Que la satisfacción
del bucle después de las ; - 1 primeras iteraciones (es decir, antes de la
; -ésima iteración) garantizan la satisfacción del invariante después de la ite-
ración; -ésima (es decir, antes de la iteración ; + 1). Consideremos de nuevo
el bucle siguiente:

sum := o;
for ; := 1 to 5 do
{; nv: SUm = Su", j E {1, ... , ; - 1}: j /\ 1 <= ; <= 6}
sum : ~ sum + 1;

Antes de la primera iteración del bucle; = 1 Y sum = O, por lo que se ve


fácilmente que el invariante se satisface en este caso. Supongamos ahora que el
invariante se satisface después de ; - 1 iteraciones, para algún ; = 2, 3,
... , 6. Es decir, supongamos que:

sum ~ Sum j E {1, ... , ; - 1 }: j /\ 1 <= ; <= 6


216 Computación l. Lógica, resolución de problemas, algoritmos y programas

Ahora tendremos que demostrar por, inducción, que tras una ejecución de
todo el cuerpo del bucle, se garantiza que:

sum = SUII j E (1, ... , ;' - 1 ): j 1\ 1 <= ; , <= 6

donde ; , = ; + 1.
Podemos hacer esto examinando el efecto de las instrucciones sobre el
invariante original. Es decir, una simple ejecución de la instrucción sum :=
sum + 1 conduce a

sum = ; + Sum j E (1, ... , ; - 1 ): j


= Sum j E (1, , ;): j
= Sum j E (1, , (; + 1 ) - 1 } :
= SUII j E (1, , ; , - 1 ): j

por reglas algebraicas sencillas. La instrucción; :=; +1 conduce a

1 <= ; + 1 <= 6 '" 1 <= ;' <~ 6

puesto que el valor limitador de ; = 5 en el bucle for limita a 6 el valor de


; , . Luego, por inducción, se deduce que el invariante del bucle es válido, lo
que completa la verificación del bucle.

6.5.6. Verificación formal frente a verificación informal


de programas

Las técnicas que se han introducido en este capítulo reciben habitualmente el


nombre de técnicas de verificación formal. Como es fácil imaginar, la aplicación
de las técnicas de verificación formal a un programa razonablemente largo se
convierte rápidamente en algo inabarcable. Adicionalmente, existe una amplia
gama de problemas de programación para los que no se han desarrollado
suficientemente las técnicas de verificación. El proceso de verificación formal
de programas está todavía en la infancia -hace falta hacer mucho todavía
para que pueda utilizarse como una herramienta que garantice la robustez de
los programas.
Sin embargo, existen diferentes niveles de granularidad de programas, en
los que se puede aplicar las técnicas de verificación de programas. El proceso
de la verificación i'!formal parece aportar medios de argumentar, de forma
convincente, sobre la corrección de los programas, evitando muchos de los
inconvenientes de la demostración línea-a-linea de los métodos formales. Este
proceso es similar al que se sigue en Matemáticas, en la demostración de un
teorema, o en una simplificación algebraica, tal como se vio en el Capítulo 3.
Es decir, cuando simplificamos una expresión algebraica, no solemos enumerar
todos los pasos que se siguen ni su justificación formal en términos de propie-
Robustez y prueba de los algoritmos 217

dades algebraicas básicas (asociatividad, conmutatividd, etc.). En su lugar, sole-


mos saltarnos los pasos que son evidentes para el lector, concentrándonos sólo
en aquellos esenciales y dificiles de comprender.
Este es el caso de la verificación informal. En lugar de construir una demos-
tración completa mediante una prueba tableau, nos centramos en los aspectos
más complejos del programa, y argumentamos en castellano sobre la forma en
que esas partes complejas satisfacen las especificaciones. Así, las nociones de
precondición, poscondición e invariante juegan aún un papel esencial en este
proceso, pero las argumentaciones sobre la corrección del programa se realizan
más informalmente.

6.6. RESUMEN

En este capítulo se han presentado las nociones de correCClOn, robustez y


amigabilidad de los programas. En el capítulo se ha incidido sobre la utiliza-
ción de la estrategia de resolución de problemas MAPS, ilustrando su utiliza-
ción en la resolución de un problema de tratamiento de textos, y otro grá-
fico.
Hemos presentado y desarrollado dos metodologías complementarias para
asegurar la corrección y robustez de nuestros programas: prueba y verificación.
Éstas permiten desarrollar buenos programas, aun a los más escépticos, si
tienen una mentalidad positiva. Es evidente que tendremos que realizar un
análisis activo de nuestros propios diseños y los programas que resultan a
medida que sean más complejos los problemas que queremos resolver.
El método de prueba que se ha estudiado en este capítulo aporta herra-
mientas que nos permiten adquirir una mayor confianza sobre la corrección de
los programas. Aunque el proceso de prueba no garantiza la inexistencia de
errores, nos ayuda a adquirir confianza en la validez de nuestras soluciones. La
prueba es uno de los fundamentos pragmáticos en el campo de la garantiza-
ción de la calidad del software.
Cuando es posible aplicar los métodos de verificación estudiados en este
capitulo, garantizan la ausencia de errores. A su vez, inciden en la importancia
de la lógica para la informática. Es decir, los principios de la lógica y las
demostraciones que se introdujeron en el Capítulo 3, son la base notacional y
metodológica para la combinación de precondiciones y poscondiciones en la
especificación de problemas, con las reglas de inferencia en la verificación de
programas. Sin embargo, la verificación tiene limitaciones prácticas, y es nece-
sario que las conozcamos bien cuando tengamos que decidirnos entre prueba o
verificación de programas. En el Volumen JI de esta serie, veremos técnicas de
prueba y verificación adicionales.
218 Computación l. Lógica, resolución de problemas, algoritmos y programas

Ejercicios

6.7. Verificar los siguientes grupos de instrucciones, rellenando la precondi-


ción o poscondición que falta, utilizando la regla de inferencia de la
asignación:

a) { } e) { }
i := i + 1 Write(x>
{i > O} {saLida ~ 12)

b) {i ~ O} J) {i ~ 10}
i :~ i + 1 j :~ 25
{ } { }

e) {i+j=O} g) { }
i := i + 1; s :~ s + t ~ 1
j :~ j ~ 1 {O <= s)
{ }

d) {entrada ~ 4 7 5)
Read(x>
{ }

6.8. Verificar el bucle siguiente, utilizando la regla de inferencia para bucles,


incluyendo una demostración (por inducción) del invariante.

{L~ (e[1], e[2], .. " e[m], .•• , e[n]>)


j := 1;
k := 2;
whi le k <= m do
{inv: para todo i in {1, ... , k ~ 1): e[j] <= e[i] /\ 2 <~ k <= m + 1}
if L[k] < L[j] then
j := k;
Min := j
}

6.9. Escribir un bucle controlado utilizando una instrucción for o una


repeat y que sea equivalente al bucle del Ejercicio 6.8. Encontrar el
invariante y verificar el bucle resultante.

6.10. En cada una de las instrucciones siguientes, escribir el aserto que falta
(utilizando la regla de inferencia de la selección condicional) de forma
que éste sea válido.

a) { }
if a = 1 then b :~ a else b := a + 1
{b ~ 1}
Robustez y prueba de los algoritmos 219

b) {i=nAj=m}
H i = O then j := O else j := 1
{ }
e) {i = n A j = m}
i f i = O then j :~ O
{ }

6.11. Confrontar los procesos de prueba y verificación como garantes de la


corrección, robustez y amigabilidad de los programas. ¿Qué ventajas
tiene cada uno? ¿Cuáles son las desventajas? Para el problema de la
Pluviometria del Capítulo 5, ¿cuál de los dos procesos parece ser
más adecuado y por qué?

6.12. Verificar el invariante del segmento de programa siguiente, suponiendo


que s = • R2D2 I . Escribir los valores de las variables Count y Loe en
cada iteración de bucle.

Count := o;
Loe := O;
whi le Loe < Length(S) do
{inv: O <= Loe <= Length(S) A
Coun t = Num i E {1, ... , Lo e}: 'O' <= S[ i] <= '9')
begin
Loe :~Loe+1;
i f (S[Loe] >= 'O') and (S[Loe] <~ '9') then
Count := Count + 1
end¡

6.13. Encontrar el invariante del bucle de la función siguiente:

function EstaOrdenada (A: Lista): Soolean;


var i: i nteger
begin
EstaOrdenada := true;
for i := 1 to LengthLista(A) -1 do
i f A[ i] > A[i + 1] t hen
EstaOrdenada := false¡
end¡

También podría gustarte