Está en la página 1de 215

Aporte de Victor Caballero para www.admycontuna.mforos.

com

o m
Notas para los cursos de

.c
os
Computación y Programación
con el lenguaje Pascal r
fo
.m
na

Néstor E. Aguilera
tu

2003
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Índice general

1. Preliminares 1
1.1. ¿Qué es esto? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Cómo usar este libro . . . . . . . . . . . . . . . . . . . . . . . . . 2

m
1.3. Organización y convenciones usadas . . . . . . . . . . . . . . . . 3

o
.c
2. El primer contacto 5

os
2.1. Un poco, muy poco, sobre cómo funciona la computadora . . . . 5
2.2. El puntapié inicial . . . . . . . . . . . . . . . . . . . . . . . . . . 6
r
fo
3. Tipos de datos elementales 9
.m

3.1. Tipos, variables e identificadores . . . . . . . . . . . . . . . . . . 9


na

3.2. Tipos numéricos: entero y real . . . . . . . . . . . . . . . . . . . . 11


3.2.1. “Readln” . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
tu

3.2.2. Funciones numéricas . . . . . . . . . . . . . . . . . . . . . 13


on

3.2.3. La codificación de enteros y reales . . . . . . . . . . . . . 14


3.3. Variables lógicas . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
yc

3.4. Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
dm

4. Tomando control 21
4.1. “If” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
.a

4.2. “While” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
w

4.3. “Repeat” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
w

4.4. “For” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
w

4.5. “Eoln” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

5. Aplicaciones 35
5.1. Cálculo numérico elemental . . . . . . . . . . . . . . . . . . . . . 35
5.1.1. Mezclando números grandes y pequeños . . . . . . . . . . 35
5.1.2. Métodos iterativos: puntos fijos . . . . . . . . . . . . . . . 37
5.1.3. El método babilónico . . . . . . . . . . . . . . . . . . . . . 40
5.2. Números enteros y divisibilidad . . . . . . . . . . . . . . . . . . . 42
5.2.1. Ecuaciones diofánticas . . . . . . . . . . . . . . . . . . . . 42
5.2.2. Máximo común divisor y el algoritmo de Euclides . . . . . 43
5.2.3. Números primos . . . . . . . . . . . . . . . . . . . . . . . 45
5.3. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 47

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. ii Índice general

6. Arreglos 49
6.1. Dimensionamiento de arreglos . . . . . . . . . . . . . . . . . . . . 49
6.2. Búsqueda Lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.3. Cribas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.4. Polinomios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.5. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 56

7. Funciones y Procedimientos 59
7.1. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2. El método de la bisección . . . . . . . . . . . . . . . . . . . . . . 63
7.3. Procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.4. Pasando por valor o por referencia . . . . . . . . . . . . . . . . . 68

8. Todos juntos: arreglos, funciones y procedimientos 73


8.1. Definiendo nuestros propios tipos de datos . . . . . . . . . . . . . 73

m
8.2. Ingreso e impresión de arreglos . . . . . . . . . . . . . . . . . . . 74

o
8.3. La caja de herramientas . . . . . . . . . . . . . . . . . . . . . . . 76

.c
8.4. Arreglos multidimensionales . . . . . . . . . . . . . . . . . . . . . 77

os
8.5. “Strings” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
8.6. Manejo elemental de archivos de texto . . . . . . . . . . . . . . . 78
8.7. Problemas Adicionales . . . . . . . . . . . . . . . . . r . . . . . . . 81
fo
.m

9. Números Aleatorios y Simulación 85


9.1. Números aleatorios . . . . . . . . . . . . . . . . . . . . . . . . . . 85
na

9.2. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.3. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 88
tu

9.4. Una función para números aleatorios . . . . . . . . . . . . . . . . 90


on

9.4.1. Procedimiento “randinic” . . . . . . . . . . . . . . . . . . 91


9.4.2. Función “random” . . . . . . . . . . . . . . . . . . . . . . 91
yc

9.4.3. Instrucciones para su uso . . . . . . . . . . . . . . . . . . 92


dm

10.Búsqueda y clasificación 93
10.1. Búsqueda lineal con centinela . . . . . . . . . . . . . . . . . . . . 93
.a

10.2. Búsqueda binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . 95


w

10.3. Métodos elementales de clasificación . . . . . . . . . . . . . . . . 98


w

10.4. Registros (Records) . . . . . . . . . . . . . . . . . . . . . . . . . . 102


w

10.5. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 104

11.Recursión 109
11.1. Funciones y procedimientos definidas recursivamente . . . . . . . 109
11.2. Los Grandes Clásicos de la Recursión . . . . . . . . . . . . . . . . 112
11.3. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 115

12.Generando objetos combinatorios 117


12.1. Un aparte: pilas y colas . . . . . . . . . . . . . . . . . . . . . . . 117
12.2. Generando Subconjuntos . . . . . . . . . . . . . . . . . . . . . . . 119
12.3. Generando permutaciones . . . . . . . . . . . . . . . . . . . . . . 122
12.4. Árboles binarios ordenados . . . . . . . . . . . . . . . . . . . . . 124
12.5. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 127

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Índice general Pág. iii

13.Grafos y árboles 129


13.1. Representación de grafos en la computadora . . . . . . . . . . . . 132
13.2. Recorriendo un grafo . . . . . . . . . . . . . . . . . . . . . . . . . 134
13.3. Recorrido a lo ancho y en profundidad . . . . . . . . . . . . . . . 135
13.4. Camino más corto: Dijkstra . . . . . . . . . . . . . . . . . . . . . 137
13.5. Mı́nimo árbol generador: Prim . . . . . . . . . . . . . . . . . . . 140
13.6. Problemas Adicionales: Kruskal . . . . . . . . . . . . . . . . . . . 143

14.Punteros y listas encadenadas 151


14.1. Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
14.2. Listas encadenadas . . . . . . . . . . . . . . . . . . . . . . . . . . 153
14.3. Otras estructuras dinámicas . . . . . . . . . . . . . . . . . . . . . 156
14.4. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 157

A. Programas mencionados 159

m
Problema 2.2: holamundo . . . . . . . . . . . . . . . . . . . . . . . . . 159
Problema 3.2: sumardos . . . . . . . . . . . . . . . . . . . . . . . . . . 159

o
Problema 3.3: leerdato . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

.c
Problema 3.4: raiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

os
Problema 3.7: enteroareal . . . . . . . . . . . . . . . . . . . . . . . . . 160
Problema 3.13: segundos . . . . . . . . . . . . . . . . . . . . . . . . . . 161
r
fo
Problema 3.16: positivo . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Problema 3.18: caracteres1 . . . . . . . . . . . . . . . . . . . . . . . . . 161
.m

Problema 4.1: valorabsoluto . . . . . . . . . . . . . . . . . . . . . . . . 162


na

Problema 4.2: comparar . . . . . . . . . . . . . . . . . . . . . . . . . . 162


Problema 4.3: caracteres2 . . . . . . . . . . . . . . . . . . . . . . . . . 162
tu

Problema 4.9: resto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163


Problema 4.10: tablaseno . . . . . . . . . . . . . . . . . . . . . . . . . . 163
on

Problema 4.11: gauss . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164


yc

Problema 4.13: cifras . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165


Problema 4.14: epsmin . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
dm

Problema 4.15: potencia . . . . . . . . . . . . . . . . . . . . . . . . . . 166


Problema 4.18: eolnprueba . . . . . . . . . . . . . . . . . . . . . . . . . 166
.a

Problema 4.19: sumardatos . . . . . . . . . . . . . . . . . . . . . . . . . 167


Problema 5.9: babilonico . . . . . . . . . . . . . . . . . . . . . . . . . . 167
w

Problema 5.13: euclides . . . . . . . . . . . . . . . . . . . . . . . . . . . 168


w

Problema 5.18: factoresprimos . . . . . . . . . . . . . . . . . . . . . . . 169


w

Problema 6.1: unidades . . . . . . . . . . . . . . . . . . . . . . . . . . . 170


Problema 6.2: busquedalineal . . . . . . . . . . . . . . . . . . . . . . . . 170
Problema 6.3: eratostenes . . . . . . . . . . . . . . . . . . . . . . . . . 172
Problema 6.5: flaviojosefo1 . . . . . . . . . . . . . . . . . . . . . . . . . 172
Problema 7.1: potencias2 . . . . . . . . . . . . . . . . . . . . . . . . . . 173
Problema 7.2: potencias3 . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Problema 7.3: biseccion . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
Problema 7.6: tablaseno2 . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Problema 7.7: swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Problema 8.2: maximo . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Problema 8.8: deconsolaaarchivo . . . . . . . . . . . . . . . . . . . . . . 180
Problema 8.8: dearchivoaconsola . . . . . . . . . . . . . . . . . . . . . . 180
Problema 9.1: dado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. iv Índice general

Problema 9.2: dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182


Problema 12.6: arbolbinario . . . . . . . . . . . . . . . . . . . . . . . . 182
Problema 13.4: anchoprimero . . . . . . . . . . . . . . . . . . . . . . . 185
Problema 13.8: dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Problema 14.1: listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Problema 14.9: grafos . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

B. Breve referencia de Pascal 201


B.1. Resumen de operadores . . . . . . . . . . . . . . . . . . . . . . . 201
B.1.1. Operadores aritméticos . . . . . . . . . . . . . . . . . . . 201
B.1.2. Operadores relacionales . . . . . . . . . . . . . . . . . . . 201
B.1.3. Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . 201
B.2. Identificadores estándares . . . . . . . . . . . . . . . . . . . . . . 201
B.3. Nombres Reservados . . . . . . . . . . . . . . . . . . . . . . . . . 202

m
C. Algunas notaciones y sı́mbolos usados 203
C.1. Lógica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203

o
C.2. Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203

.c
C.3. Números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204

os
C.4. Números importantes en programación . . . . . . . . . . . . . . . 205
C.5. Generales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
r
fo
D. Sobre los compiladores Pascal 207
.m

Bibliografı́a 209
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 1

Preliminares

m
1.1. ¿Qué es esto?

o
.c
Algunos se enloquecen por la cerveza, otros prefieren vino, y también están

os
los abstemios. Pero todos estamos de acuerdo en que la computadora ocupa, y
cada vez más, un lugar importante en nuestras vidas.
r
fo
Decimos esto pensando en internet, el acceso a la información, la cuenta
del banco y los impuestos, multimedios como gráficos, sonidos, pelı́culas y jue-
.m

gos (videos), y siguiendo con una larga lista que, sin embargo, difı́cilmente in-
cluirá resolución de problemas matemáticos.
na

Y nuestras creencias están de acuerdo a nuestras experiencias. Los prime-


tu

ros encuentros con la computadora y sus aplicaciones tienen que ver con hacer
“click” con el “ratón” para acceder a juegos, “browsers” y el correo electrónico.
on

Después aprendemos algo del funcionamiento del sistema operativo, procesado-


res de texto y planillas de cálculo, y el software que se use en nuestro trabajo.
yc

No es ningún misterio que estas aplicaciones están diseñadas y realizadas


dm

por personas, ingenieros y técnicos de sistemas, y programadores. Consecuen-


temente, el primer curso de programación usualmente está orientado hacia las
aplicaciones de sistemas informáticos, por ejemplo hacia el manejo de archivos
.a

con vistas a bases de datos, lo cual es muy comprensible.


w

La propuesta aquı́ es un tanto diferente: hacer el primer curso de progra-


w

mación pensando en las aplicaciones matemáticas. Después de todo, el nombre


w

“computadora” tiene que ver más con matemáticas que con “informática”.
Tratamos de ubicarnos entonces entre dos extremos. Por un lado el uso de
la computadora como un fin y no como un medio, donde lo importante es la
computadora relegando a un plano secundario, o aún ignorando, las matemáti-
cas. Por otro lado, el aprendizaje de sistemas avanzados con los que “apretando
botones” obtenemos resultados, muchas veces sin saber de dónde salen ni cuán
confiables son (o de qué estamos hablando).
Y seguramente que queremos apartarnos lo más posible de la peor de to-
das las propuestas, lamentablemente muy difundida: el aprendizaje rutinario de
técnicas de dudoso valor que son rápidamente olvidadas.
El núcleo del libro es la resolución de problemas para desarrollar el pensa-
miento matemático y las conexiones con el pensamiento algorı́tmico, pasando
de abstracciones al resultado concreto de un programa para la computadora.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 2 Preliminares

Esta relación entre abstracto y concreto se realimenta al usar la computadora


como banco experimental para encontrar patrones, llegar a conjeturas y a su
eventual demostración matemática.
En el libro se va entrelazando la introducción de los elementos de programa-
ción estructurada con la resolución de problemas de análisis y cálculo numérico,
teorı́a de números, combinatoria y grafos, sirviendo tanto de introducción de
algunos temas como de repaso y fortalecimiento de otros.
Quedan un poco aparte temas de álgebra lineal. Esto es ası́ puesto que la
resolución numérica de problemas lineales requiere un estudio cuidadoso de los
errores numéricos, que está fuera del propósito introductorio del libro.
Interpuestos más que nada como comentarios, se tocan temas de eficien-
cia y complejidad de algoritmos, pero no se incluyen temas de corrección de
programas.
Una caracterı́stica del libro, además de los temas que se presentan, es la
inclusión de muchos programas acabados, pues en programación —mucho más

m
que en matemáticas— se aprende mirando lo que han hecho otros, y es inclusive

o
deseable el “reuso” de partes ya hechas. Sin embargo los programas presenta-

.c
dos difı́cilmente puedan considerarse “estado del arte”, y se ha sacrificado la
eficiencia y la brevedad tratando de hacer una presentación clara.

os
También sólo se usa seudo-código en los capı́tulos más avanzados (y en mı́ni-

r
ma medida), a contra corriente de otros cursos introductorios, con la convicción
fo
de que es muy difı́cil entender —e imposible concretar— qué se está diciendo en
.m
seudo-código. Es como tratar de aprender una sı́ntesis de los idiomas antes de
aprender a hablar en alguno (y podemos recordar el fracaso de la “matemática
na

moderna”).
En el libro usamos el lenguaje Pascal. Considerado “obsoleto” por distin-
tu

tas razones, tiene la gran ventaja de haber sido diseñado especialmente para la
on

enseñanza de programación por uno de los grandes autores, N. Wirth, y brin-


da la posibilidad de una clara formulación de los programas y —mucho más
yc

importante— una sólida base para el futuro.


Habiendo elegido Pascal, hemos tratado de ceñirnos lo más posible a su
dm

estándar pues debe conocerse y apreciarse la importancia de la portabilidad y


consistencia entre diferentes sistemas operativos.
.a

Los capı́tulos de 2 a 11 forman la parte básica de un primer curso de pro-


w

gramación. En el capı́tulo 12 trabajamos con objetos combinatorios, como sub-


conjuntos, permutaciones y árboles binarios, donde aparece verdaderamente la
w

fuerza de recursión.
w

Los dos últimos capı́tulos toman rumbos distintos y son bastante indepen-
dientes entre sı́. El capı́tulo 13 está pensado para aquéllos que ya hayan visto
un curso de matemática discreta o teorı́a de grafos, implementando (en forma
elemental) los primeros algoritmos de grafos. En cambio, el capı́tulo 14 muestra
las posibilidades de las listas encadenadas, liberándonos de algunas ataduras de
los arreglos.

1.2. Cómo usar este libro


Uno puede darse una idea de cómo cocinar mirando un libro de cocina, pero
ciertamente la experiencia es mucho más completa y satisfactoria si se tienen
los ingredientes, las cacerolas y la misma cocina. Del mismo modo, se puede

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

1.3. Organización y convenciones usadas Pág. 3

apreciar qué es la programación mirando un libro, pero nosotros supondremos


que el lector dispone además de una computadora y un compilador Pascal.
Ası́ como el aprendiz aprende observando al artesano, en programación es
muy importante copiar (y tomar ideas y estructuras de) programas ya hechos,
aunque a veces no se entienda exactamente del porqué de tal o cual sintaxis.
Idealmente quien trabaje con los contenidos de este libro copiará (preferente-
mente de un diskette o de internet) los programas presentados, y hará variaciones
o copiará partes de uno o más de ellos para resolver los distintos problemas.
Muchos principiantes se ponen ansiosamente a tipear un programa sin tener
un plan especı́fico, quizás por su experiencia previa con la computadora. Una
buena parte de los problemas que planteamos no son sencillos de resolver, y
seguramente no se resuelven probando con distintos “botones”. Más aún, varios
de los problemas no necesitan de la computadora.
Quien quiera sacar provecho de este libro deberá hacer uso del lápiz y papel
para trazarse un plan de resolución antes de copiar o modificar los programas,

m
pero para eso debe entender primero qué es lo que hacen las distintas partes y

o
—por sobre todo— a dónde se quiere llegar.

.c
La adquisición de conocimientos no es gratuita y requiere esfuerzo y tiempo,
y las equivocaciones forman parte inevitable de este proceso: ¡habrá que estar

os
dispuesto a pasar un buen rato en la silla!

r
Aún cuando no podamos resolver un problema en programación o en ma-
fo
temáticas, es poco útil que alguien nos cuente la solución si antes no hemos
.m
hecho un trabajo propio para apreciarla, e inclusive criticarla.
En algunos problemas se incluyen “sugerencias” para la resolución, que están
na

puestas como orientación cuando se está “perdido”. La recomendación es “ta-


par” la sugerencia, y recurrir a ella en segunda instancia o cuando ya se ha
tu

resuelto el problema para ver si hay otras posibilidades de resolución.


on

Esto nos trae al tema de que tanto en programación como en matemáticas,


no hay una única forma de hacer los programas o resolver los problemas. Lo
yc

presentado es sólo una posibilidad.


A algunos les parecerá que las sugerencias son oscuras o escasas, a otros
dm

les parecerá que el material presentado es excesivo, y habrá otros que querrán
resolver más problemas: para éstos se incluye una sección de “Problemas Adi-
.a

cionales” en los capı́tulos más avanzados.


w

En fin, habrá algunos que querrán seguir avanzando aún más, quizás después
w

de terminar el libro, y una posibilidad muy interesante es tomar problemas de las


competencias estudiantiles de la ACM (Association for Computing Machinery)1 ,
w

en las que los representantes de nuestro paı́s vienen participando exitosamente


desde hace varios años.

1.3. Organización y convenciones usadas


En los capı́tulos 2 a 14 se presentan los temas y problemas, agrupados en
secciones y a veces subsecciones. Los problemas están numerados comenzando
con 1 en cada capı́tulo, de modo que el “problema 4.5” se refiere al problema 5
del capı́tulo 4. De modo similar, la “sección 3.2” se refiere a la sección 2 del
capı́tulo 3.
1 http://icpc.baylor.edu/icpc/ (la página está en inglés).

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 4 Preliminares

En muchos de los problemas se mencionan programas completos, que se han


puesto juntos en el apéndice A, al que le sigue una breve referencia de Pascal
(apéndice B), notaciones usadas en el libro (apéndice C) e información sobre
compiladores Pascal (apéndice D).
A veces intercalaremos texto entre los problemas, por lo que para indicar el
fin del enunciado de un problema colocamos el signo ✄, que puede leerse como
“la cortamos acá”.
Intercalados entre texto y enunciados de problemas, hay algunas notas y
comentarios, en tipo de letra más chico. En los comentarios, en itálica, se hacen
referencias históricas, orientadoras, curiosidades, etc. Son de la forma
Esto es un comentario.
Por otra parte, las notas son en general de ı́ndole más técnica, y son de la
forma
Nota: Esto es una nota.

m
Usamos otros tipos de letra para indicar los nombres de los programas, ası́,
mientras que lo que escribiremos en la computadora está indicado en “mono-

o
tipo”, ası́, algunas veces entre comillas dobles, para recalcar algún fragmento,

.c
“ como éste ”, reservando las comillas simples para caracteres, como ‘ a ’.

os
Siguiendo la tradición norteamericana, la computadora expresa los núme-
ros poniendo un “punto” decimal en vez de la “coma”, de modo que para no
r
fo
confundirnos seguiremos esa práctica en todo el texto. Ası́, 1.589 es un número
entre 1 y 2, mientras que 1589 es un número entero, mayor que mil.
.m

También se hace complicado trabajar con acentos o tildes (como en “á” o


na

“ñ”) al escribir los programas o mostrar resultados por pantalla, de modo que
en la escritura de códigos los obviaremos (escribiendo “a” o “ni”).
tu

Finalmente, en el texto indicamos con <retorno> el pulsado de la tecla


“Retorno” (o “Return” en teclados ingleses) para iniciar un nuevo renglón. De-
on

pendiendo del sistema operativo, es posible que deba pulsarse en cambio la tecla
yc

“Intro” (o “Enter” en inglés).


dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 2

El primer contacto

m
2.1. Un poco, muy poco, sobre cómo funciona

o
la computadora

.c
os
Conceptualmente, la computadora es una máquina que toma o accede a
datos, los procesa y devuelve resultados. Los datos o entradas y los resultados
r
fo
o salidas pueden ser simples como números o letras, o mucho más complicados
como una matriz o una base de datos.
.m

En el modelo de computadora con el que trabajaremos (o de von Neuman),


pensaremos que el procesamiento está a cargo de una única unidad, llamada
na

CPU por “Central Processing Unit” o Unidad Central de Procesamiento, que


tu

accede los datos y retorna los resultados secuencialmente, es decir, de a uno por
vez, y los datos a los que accede se guardan en una lugar denominado memoria.
on

En los programas que haremos normalmente nos comunicaremos con la com-


putadora entrando los datos con el teclado y recibiendo los resultados en la pan-
yc

talla, refiriéndonos en general como terminal o consola al conjunto combinado


dm

de teclado y pantalla. Estos datos que entramos o recibimos no son directamente


procesados por la CPU, sino que son transferidos a o desde la memoria mediante
la misma CPU u otro procesador dedicado.
.a

Nos imaginaremos que la memoria, en donde se almacenan los datos, es-


w

tá constituida por muchas cajitas pequeñas llamadas bits por “Binary Digit”
w

o dı́gito binario, en cada una de las cuales sólo se puede guardar un 0 o un


w

1. Puesto que esta caja es demasiado pequeña para guardar información más
complicada que “sı́/no” o “blanco/negro”, los bits se agrupan en cajas un poco
más grandes llamadas bytes, que generalmente tienen 8 bits, conceptualmente
alineados, puesto que queremos que 00001111 sea distinto de 11110000.

Problema 2.1. Suponiendo que un byte tenga 8 bits,


a) ¿cuántas “ristras” distintas de 0’s y 1’s puede tener? Sugerencia: hacer la
cuenta primero para un byte de 1 bit, luego para un byte de 2 bits, luego
para un byte de 3 bits,. . .
b) Si no importara el orden de los bits que forman el byte, y entonces 00001111,
11110000, 10100101 fueran indistinguibles entre sı́, ¿cuántos elementos dis-
tintos podrı́a contener un byte? Sugerencia: si el byte tiene 8 bits puede ser
que hayan 8 ceros y ningún uno, o 7 ceros y 1 uno, o. . . ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 6 El primer contacto

A su vez, para las computadoras más recientes, estas unidades resultan dema-
siado pequeñas para alimentar a la CPU, por lo que los bits o bytes se agrupan
formando cajas de, por ejemplo, 32, 64 o 128 bits (usualmente potencias de 2),
siempre conceptualmente alineadas.
Los mismos programas pueden considerarse como un tipo especial de datos,
con “instrucciones” que dicen a la CPU qué hacer. En particular, el sistema
operativo de la computadora es un programa que alimenta constantemente a la
CPU, y le va a indicar, por ejemplo, que ejecute nuestro programa, leyendo las
instrucciones que contiene.

2.2. El puntapié inicial


Los lenguajes de programación son “abstracciones” que nos permiten escribir
instrucciones que un ser humano puede entender más fácilmente que ristras de

m
ceros y unos. Las instrucciones para la máquina se escriben como sentencias, de

o
acuerdo a la sintaxis del lenguaje, en lo que se llama programa fuente y deben

.c
ser traducidas a instrucciones que la CPU pueda entender. Este proceso se llama
compilado, dando por resultado un programa ejecutable.

os
El ciclo usual es entonces: escribir nuestro programa con un editor de textos,

r
eventualmente guardándolo en el disco rı́gido o diskette, y luego compilarlo y
fo
ejecutarlo.
.m

Nota: Esta descripción basta para nuestros propósitos, en realidad todo el pro-
ceso requiere otros pasos intermedios que en nuestro caso serán hechos au-
na

tomáticamente.
Nota: Algunos lenguajes son interpretados, es decir no existe la compilación y
tu

no se crea el ejecutable.
on

Aún cuando estemos trabajando con Pascal, los detalles de cómo realizar
el ciclo de edición-compilación-ejecución dependen del sistema operativo y el
yc

compilador (la marca del compilador) que estemos usando.


dm

En Pascal, todos los programas se dividen en dos partes o cuerpos. En la


primera, a veces llamada de declaraciones, se coloca el nombre del programa
mediante “ program nombre(input,output) ” y otras sentencias que iremos
.a

viendo1 . La segunda parte, a veces llamada principal, empieza con “ begin ” y


w

termina con “ end. ” (punto “ . ” incluido), y entre ellos se ponen sentencias


w

para realizar “acciones”.


w

En ambas partes, de declaraciones y principal, las sentencias se separan


mediante “ ; ”. En cualquier lugar se pueden agregar comentarios, encerrados
entre “ (* ” y “ *) ” que nos ayudan a entender lo que hicimos cuando volvemos
a mirar después de un par de semanas.
Nota: Para muchos principiantes resulta confuso el uso de “ ; ” en Pascal, pero
debe tenerse en mente que se usa como “ , ” o “ ; ” se usan al construir una
oración en castellano: para separar sentencias.
Del mismo modo, “ . ” tiene el mismo en Pascal que el punto final en
castellano.
En cambio, la “ , ” se usa en Pascal en forma distinta al castellano. Por
ejemplo, para separar argumentos de funciones como se hace en matemáticas.
1 Siguiendo el estándar Pascal. Muchos compiladores aceptan, sin embargo, la omisión de

“ (input, output) ”.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

2.2. El puntapié inicial Pág. 7

Nota: También se pueden encerrar comentarios entre “ { ” y “ } ”, pero no los


usaremos a fin de seguir una sintaxis más parecida a otros lenguajes como “C”
o Mathematica.
Para evitar confusiones, normalmente se guarda el programa fuente en un
archivo con el mismo nombre que en la sentencia “ program nombre ... ”, y
con extensión “ .p ” o “ .pas ”. Ası́, generalmente guardaremos el programa
fuente pepe en el archivo pepe.p o pepe.pas. Al compilarlo, el ejecutable cambia
la extensión a “ .exe ”2 , de modo que obtenemos pepe.exe.
La prueba de fuego es editar, compilar y ejecutar el primer programa:

Problema 2.2 (Hola Mundo). Copiar, compilar y ejecutar el programa ho-


lamundo (pág. 159).
Nota: Muchos compiladores, entre ellos Turbo Pascal, hacen que al ejecutar
un programa como holamundo se abra una ventana distinta que se cierra au-
tomáticamente al finalizar la ejecución del programa. El proceso puede ser tan

m
rápido que apenas nos damos cuenta de que ha sucedido algo. En estos casos
es conveniente agregar un renglón con las instrucciones

o
.c
writeln(’<retorno> para fin’); readln

os
al terminar el programa, antes de “ end. ” y agregando un “ ; ” al fin del renglón
anterior.

r
fo
a) Observar con cuidado los signos de puntuación y qué hace cada una de las
instrucciones:
.m

• El ya mencionado renglón inicial que comienza con “ program... ”, y


na

que termina en “ ; ”. En este ejemplo es la única sentencia de la parte


declarativa.
tu

• El comentario inmediatamente después de “ program... ”, explicando al


on

que lee el programa fuente qué hace el programa.


• El cuerpo principal que empieza con “ begin ” y termina en “ end. ”.
yc

• Hay tres sentencias en la parte principal, separadas por dos “ ; ”.


dm

• “ writeln ” escribe un renglón en la pantalla, y el texto a escribir se


encierra entre “ ’ ” (comillas simples). Si no tiene argumentos, “ writeln ”
escribe un renglón “vacı́o”.
.a

b) Eliminar, repetir o cambiar las instrucciones. Por ejemplo:


w

i) eliminar el segundo “ writeln ”,


w

ii) y después también el tercero,


w

iii) cambiar “ writeln ” por “ WRITELN ”, y después por “ Writeln ”,


iv ) cambiar “ ’Hola Mundo!’ ” por “ ’HOLA MUNDO!’ ”,
v ) modificar el programa para que se escriba “ bye, bye ” en vez de “ y
Chau! ”.
c) En general, al escribir el programa usamos indentación o sangrı́as, i.e. es-
pacios al comienzo de algunos de los renglones, y a veces renglones enteros
en blanco, para resaltar la estructura del programa. Esto no es necesario, e
inclusive podrı́a escribirse el programa en un único renglón, y un espacio o
varios no hacen diferencia:
i) Eliminar o agregar espacios al comienzo, en el medio y/o al final de
2 La extensión depende del sistema operativo.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 8 El primer contacto

algunos renglones, compilar el programa y verificar que se obtiene el


mismo resultado.
ii) Agregar renglones en blanco o poner dos (o todos los que se quiera)
renglones en uno solo, y verificar que se obtiene el mismo resultado (y
recordar que “ ; ” se usa en Pascal como en castellano: para separar
sentencias).
Nota: A los fines del programa fuente, los espacios, tabulaciones (tecla tab
o similar) y renglones son intercambiables (mientras no estén encerrados
entre comillas simples). ✄

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 3

Tipos de datos elementales

m
Recordemos que la información, incluyendo el programa, se guarda en un
lugar de memoria, como ristras de ceros y unos. Como números y caracteres se

o
representan de esta forma, la computadora al tomar un dato debe saber si se

.c
trata de uno u otro. Esto da lugar a distintos tipos de datos, como estudiamos

os
en este capı́tulo.

r
fo
3.1. Tipos, variables e identificadores
.m

Supongamos que guardamos las letras siguiendo el orden del abecedario, ‘ a ’


na

como 0, ‘ b ’ como 1, ‘ c ’ como 10, y ası́ sucesivamente. Vemos que no podrı́amos


tu

distinguir entre el par de letras “ ba ” y la única letra “ c ”. Para evitar esta


confusión, se decide que todas las letras ocupen siempre el mismo espacio, por
on

ejemplo, un byte de 8 bits. De esta forma tendremos (ver problema 2.1) 28 = 256
posibilidades para los caracteres, lo cual es suficiente para guardar las letras de
yc

nuestro alfabeto (pero no el kanji).


dm

Habiendo decidido esto, nos toca ahora guardar números. Como antes, es
conveniente guardar a todos los números en la misma cantidad de bits. Si usára-
mos 8 bits como hicimos para las letras, tendrı́amos sólo 256 números disponi-
.a

bles, lo cual es bien pobre. Es conveniente que las “cajas” sean más grandes.
w

Cuando la máquina lea estas cajas que guardan letras o números, tiene que
w

saber cuántos bits juntos tiene que leer, e interpretar la ristra de bits según sea
w

una letra o un número. Surgen ası́ cuatro tipos elementales de datos, cada uno
con distinta codificación interna:

• boolean para guardar los valores “ true ” (verdadero) o “ false ” (falso),

• char para guardar caracteres, i.e. letras, signos de puntuación y otros que
veremos más adelante,

• integer para guardar números enteros, como 1, 0, −5, y

• real para guardar números reales, i.e. números como 123.456.

Ahora tenemos el problema de cómo acceder a esas cajas que guardan carac-
teres o números. Esto es sencillo: les ponemos nombres para identificarlas. Las

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 10 Tipos de datos elementales

cajas ası́ nombradas se llaman variables pues podremos colocar en ellas datos
distintos o (¡ejem!) variables y los nombres que reciben se llaman identificadores.
Cuando redactamos el programa, debemos indicar al compilador los nom-
bres y tipos de variables que usaremos, procedimiento llamado declaración de
variables. En el programa se pone una lista de variables y tipos después de la
palabra clave “ var ”.
Una cosa trae a la otra, y tenemos el problema de que no podemos poner
cualquier nombre. Por ejemplo, no es conveniente usar “ writeln ” o “ write ”,
que son usados por Pascal para instrucciones del lenguaje. Además, en Pascal
hay nombres reservados, que no podemos usar como identificadores1 . Aparte
de estas palabras prohibidas, los identificadores en Pascal pueden ser cualquier
sucesión de letras mayúsculas o minúsculas y dı́gitos, pero

• siempre tienen que empezar con una letra,


• no pueden tener espacios entre medio,

m
• ni pueden tener caracteres como $, , +, etc.,

o
.c
• y por supuesto, dos variables distintas no pueden tener el mismo nombre.

os
A diferencia de otros lenguajes (como “C” o Mathematica), según el estándar
r
fo
Pascal no se hace diferencia entre mayúsculas o minúsculas tanto en los identi-
ficadores como en palabras claves. Ası́, podemos poner sin problemas en alguna
.m

parte “ writeln ” y en otra “ WriteLn ” (como hemos hecho en el problema 2.2).


na

Nota: Sin embargo, algunos compiladores sı́ diferencian entre mayúsculas y


minúsculas en identificadores y palabras reservadas.
tu

Problema 3.1. Decidir cuáles de los siguientes son identificadores válidos en


on

Pascal:
a) mn32xy, b) 32xymn, c) mn32 xy, d ) M32nxY, e) mn 32. ✄
yc

También tenemos otro problema: cómo colocar los datos en estas cajas o
dm

variables, proceso que se llama asignación.


Una forma de hacerlo es mediante “ := ” (sin espacios intermedios). Ası́, si
.a

queremos guardar un 2 en la variable n que es de tipo entero ponemos “ n := 2 ”,


w

y si queremos copiar los contenidos de la caja o variable a en la caja o variable


w

b, ponemos “ b := a ”. Hay que tener un poco de cuidado en este último caso,


pues siempre se pueden hacer asignaciones entre variables del mismo tipo, pero
w

no es posible hacer asignaciones arbitrarias entre distintos tipos de variables, lo


que veremos con algún detalle en el problema 3.11.
Nota: Aunque no debe dejarse espacio entremedio en “ := ”, los espacios a cada
lado no son necesarios. Ası́, “ n:=2 ” o “ n := 2 ” tienen el mismo efecto.
Nota: Otros lenguajes usan otros sı́mbolos para la asignación, en vez de “ := ”.
Al escribir en “seudo código”, muchas veces se usa el sı́mbolo ←, que parece
mucho más apropiado y evita confusiones con el sı́mbolo =.
Otra forma de colocar datos en las respectivas variables es mediante la lec-
tura de datos, por ejemplo, si a está declarado como “ real ”, la instrucción
“ readln(a) ” lee el número que se ingresa por terminal y lo guarda en la va-
riable a.
1 Una lista completa de los nombres reservados está en la sección B.3, pág. 202.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

3.2. Tipos numéricos: entero y real Pág. 11

Desde ya que

¡nunca hay que usar el valor de una variable sin an-


tes haber hecho una asignación a la variable!

aunque hay compiladores que automáticamente ponen algún valor al hacer las
declaraciones.
Para tratar de entender esta jeringoza, pasemos a estudiar algunos ejemplos
comenzando con los tipos numéricos “ integer ” y “ real ”, para luego conside-
rar los tipos “ boolean ” y “ char ”.

3.2. Tipos numéricos: entero y real

o m
La vida serı́a un tanto aburrida si sólo pudiéramos cambiar valores de lugar,

.c
es más interesante si podemos realizar operaciones entre estas variables. Ası́,

os
suponiendo que las variables a, b y c son del tipo adecuado, una instrucción
como “ a := b + c ” hace que la CPU tome los datos que están en las cajas o
r
fo
variables b y c, las sume, y coloque el resultado en la caja o variable a.
.m

Problema 3.2. Compilar y ejecutar el programa sumardos (pág. 159) analizan-


do la sintaxis y qué hacen las distintas instrucciones. Por ejemplo:
na

a) ¿Cuántos comentarios tiene el programa fuente?, ¿qué pasa si se eliminan


tu

los comentarios del programa fuente?


on

Nota: Nosotros pondremos siempre un primer comentario en el programa


fuente para indicar qué hace el programa. A su vez, al ejecutar el programa
yc

trataremos de imprimir un cartel similar para que el usuario sepa qué es


lo que se pretende hacer.
dm

b) La parte declarativa del programa tiene dos sentencias. En la primera está el


nombre del programa y en la segunda se declaran a y b como de tipo entero,
.a

separándo los identificadores con “ , ”.


w

¿Qué ocurre si se cambia “ , ” por “ ; ” en el renglón


w

var a, b: integer;?
w

c) ¿Qué pasa si cambiamos el nombre de a por sumardos?


Nota: El resultado puede depender del compilador. En general no es acon-
sejable usar como nombre del programa una de las palabras reservadas
de Pascal o de una de las variables que aparecen en el mismo programa
fuente, aún cuando el compilador lo acepte.
d ) ¿Cuáles son las instrucciones en el cuerpo principal?
e) Observar el uso de la asignación “ := ”, como en “ a := 1 ” donde se guarda
el valor 1 en a. Cambiar el valor de a para que sea −1 y volver a ejecutar
el programa.
f ) “ write ” escribe su/s argumento/s sin terminar un renglón, a diferencia de
“ writeln ”. En cualquier caso, los argumentos (si hay más de uno) están
separados por “ , ”.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 12 Tipos de datos elementales

i) Cambiar todas las ocurrencias de “ write ” por “ writeln ”, y observar


los cambios en la salida del programa.
ii) Cambiar los renglones
write(’La suma de ’, a);
write(’ y ’, b);
writeln(’ es ’, a + b);
por el único renglón
writeln(’La suma de ’, a,’ y ’, b, ’ es ’, a + b);
¿Hay alguna diferencia en la salida?
g) La última lı́nea, “ writeln; writeln(’** Fin **’) ” es sólo para avisar
al usuario que el programa ha terminado, y el programa ya no realizará ac-
ciones. Eliminarla y verificar el comportamiento del programa (recordando
la nota al principio del problema 2.2).

m
h) Cambiar las sentencias de modo de obtener la suma de números reales, i.e.

o
poner “ real ” en vez de “ integer ” en la declaración de variables. Compilar

.c
y ejecutar el programa, observando los cambios en la salida.

os
i) Agregar una variable c, de tipo entero o real según corresponda, hacer la
asignación “ c := a + b ” e imprimir c en vez de a + b.
r
fo
j ) Modificarlo de modo de escribir también la resta (“ - ”), producto (“ * ”) y
.m
división (“ div ” para enteros y “ / ” para reales), tanto en el caso de enteros
como de reales.
na

k ) ¿Qué pasa cuando se divide por 0? ✄


tu

Conceptualmente hay que distinguir en Pascal entre la variable, el identifi-


on

cador y el valor, i.e. entre la caja, su nombre y lo que contiene.


A fin de no ser demasiado latosos, es común tanto en Pascal como en ma-
yc

temáticas o la vida real, no distinguir entre identificador, valor y variable, uso


que nosotros seguiremos salvo cuando lleve a confusión: cuando decimos “José es
dm

simpático”, en general queremos decir “la persona cuyo nombre es José tiene la
cualidad de ser simpática”, y no que el nombre en sı́ es simpático, en cuyo caso
.a

dirı́amos “el nombre José es simpático”.


w
w

3.2.1. “Readln”
w

Es un poco tedioso estar cambiando los valores en el programa fuente para


hacer los cálculos. Mucho más razonable es ingresar los datos a nuestro antojo:

Problema 3.3. El programa leerdato (pág. 159) lee un entero ingresado por
terminal y lo imprime. Observar la sintaxis e instrucciones, similares al programa
sumardos, la única novedad es el uso de “ readln ”.
a) Compilar y ejecutar el programa, comprobando su funcionamiento.
b) Ingresar, en ejecuciones sucesivas del programa, los siguientes datos (sin las
comillas) seguidos de <retorno>, conservando los espacios intermedios
cuando corresponda:
i) “ 23 ”; ii) “ 2 ”; iii) “ 2 3 ”;
iv ) “ 2a ”; v ) “ a2 ”; vi) “ <retorno><retorno>2”.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

3.2. Tipos numéricos: entero y real Pág. 13

c) Si “ readln ” no tiene argumentos, se lee el renglón entrado —lo escrito


hasta <retorno>— y lo escrito no se guarda (como la instrucción en la
nota recuadrada de la pág. 7).
Agregar el renglón
readln;
antes de “ write(’Entrar un ... ”.
¿Qué pasa si se ingresa “ <retorno> ” y luego “ 1<retorno> ”?, ¿y si se
ingresa “ 1<retorno> ” y luego “ 2<retorno> ”?
d ) Combinando los programas sumardos y leerdato, hacer un programa para
ingresar por terminal dos números enteros e imprimir su suma.
Nota: Además de “ readln ” existe la función “ read ”, pero postergaremos su
uso para más adelante. Basta decir por ahora que la instrucción “ readln(a) ”
es equivalente a las instrucciones “ read(a); readln ”.
Ası́ como con “ writeln ” y “ write ” podemos escribir más de un argumen-

m
to, con “ readln ” (y “ read ”) podemos leer varios argumentos a la vez, pero

o
no usaremos esta facilidad para evitar confusiones.

.c
Los comportamientos de “ write ” y “ writeln ” para leer, y de “ read ”
y “ readln ” son prácticamente simétricos, aunque existen algunas diferencias
< >
os
importantes. Por ejemplo, en el tratamiento de los retorno ’s o finales de
lı́nea, como hemos visto en el inciso b.vi): “ readln(a) ” los ignora mientras no
r ✄
fo
aparezca el dato a, pero “ writeln(a) ” escribe un único renglón por vez.
.m
Cuando se programa profesionalmente, es muy importante que el programa
funcione aún cuando los datos ingresados sean erróneos, por ejemplo si se ingresa
na

una letra en vez de un número, o el número 0 como divisor de un cociente.


Posiblemente se dedique más tiempo a esta fase, y a la interacción con el usuario,
tu

que a hacer un programa que funcione cuando las entradas son correctas.
on
yc

Nosotros supondremos que el usuario siempre in-


gresa datos apropiados, y no haremos (salvo excep-
dm

cionalmente) detección de errores. Y mucho menos


nos preocuparemos por ofrecer una interfase estéti-
.a

camente agradable.
w

En cambio, trataremos de dejar claro mediante car-


w

teles qué hace el programa y qué tipo de datos han


w

de ingresarse, y nos concentraremos en el funciona-


miento del programa.

3.2.2. Funciones numéricas


Pero volvamos a los tipos numéricos.
No sólo podemos sumar o multiplicar números sino que, al igual que en las
calculadoras, hay ya funciones predeterminadas como la raı́z cuadrada, que en
Pascal se indica por “ sqrt ”.
Problema 3.4. El programa raiz (pág. 160) calcula la raı́z cuadrada de un
número no negativo entrado por terminal.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 14 Tipos de datos elementales

a) Compilar y ejecutar el programa, analizando qué hacen las distintas instruc-


ciones. Probar el programa ingresando números reales, enteros, positivos y
negativos.

b) A fin de escribir en pantalla x, no es necesario hacer la asignación previa
“ y := x ”:
i) Reemplazar “ y ” por “ sqrt(x) ” en la sentencia “ writeln ”, compilar
y ejecutar el programa, verificando que el resultado es el mismo.
ii) Eliminar la variable y del programa por completo (eliminando inclusive
su declaración).
c) ¿Qué pasa si se ingresa un dato menor que 0?
d ) Muchas veces se confunde “ sqrt ” con “ sqr ”, que encuentra el cuadrado de
un número, y no su raı́z cuadrada. Cambiar “ sqrt ” por “ sqr ” y verificar
el comportamiento del programa. ✄

m
Nota: Una lista de las funciones predefinidas en Pascal está en el apéndice B
(pág. 201).

o
.c
3.2.3. La codificación de enteros y reales

os
En Pascal, los números ocupan un número fijo de bits, por ejemplo 16 bits
r
para el tipo “ integer ” y 32 bits para el tipo “ real ”2 , por lo que en la máquina
fo
.m

sólo pueden representarse un número finito de números,


na

ya sean enteros o reales. Ası́, hay un máximo entero representable, que en Pascal
se llama maxint, y hay un menor número positivo representable que llamaremos
tu

εmin y del cual hablaremos en el problema 4.14.


on

Problema 3.5.
yc

a) Suponiendo que el tipo “ integer ” tenga asignados 16 bits, ¿cuántos núme-


ros enteros pueden representarse?
dm

b) Análogamente, si el tipo “ real ” tiene asignados 32 bits, ¿cuántos números


reales pueden representarse en la máquina? ✄
.a

Problema 3.6. Hacer un programa para imprimir el valor de maxint corres-


w

pondiente al compilador que se usa.


w

Sugerencia: no declarar variables y poner el renglón


w

writeln(’ El entero máximo es: ’, maxint)

Nota: En los compiladores más viejos, maxint = 32767. ✄


Sea que las variables de tipo “ integer ” y “ real ” ocupen el mismo lugar o
no, su codificación como cadenas de bits es bien distinta. Los enteros se represen-
tan consecutivamente (del menor al mayor), mientras que para representar los
números reales se dividen los bits en dos grupos, uno representando la mantisa y
otro el exponente, como se hace en la notación “cientı́fica” al escribir 0.123×1045
(.123 es la mantisa y 45 el exponente en base 10, pero la computadora trabaja
en base 2).
2 La cantidad de bits en cada caso depende del compilador. Incluso ambos tipos podrı́an

tener asignados la misma cantidad de bits.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

3.2. Tipos numéricos: entero y real Pág. 15

La representación de números reales mediante mantisa y exponente hace que


—a diferencia de lo que sucede con los números enteros— la distancia entre un
número real que se puede representar y el próximo vaya aumentando a medida
que sus valores absolutos aumentan. Esta propiedad de densidad variable trae
inconvenientes para el cálculo aproximado, como veremos en el problema 4.14
y en la sección 5.1.
Como todo número entero es en particular un número real, es convenien-
te poder pasar de la representación como tipo “ integer ” a la representación
como tipo “ real ”. Esto se hace automáticamente en Pascal: si a es de tipo
“ integer ” y x es de tipo “ real ”, la asignación
x := a
hace que automáticamente se guarde el valor que tiene a (un entero) en x , de
modo que ahora el valor se codifica como real.
Nota: Y también sucede al ingresar datos: si x es de tipo real, y tenemos la

m
instrucción “ readln(x) ”, el ingreso de 1 como dato —un entero— hace que
se guarde codificado como real en x .

o
.c
Problema 3.7. Compilar y ejecutar el programa enteroareal (pág. 160). Obser-

os
var la declaración de variables de distinto tipo en el mismo programa. ✄

r
Problema 3.8. Decir por qué las siguientes declaraciones de variables son in-
fo
correctas:
.m

i) var a, b, c: integer; c, d, e: real;


ii)
na

var a, b, c: integer; 2c, d, e: real;


iii) var: a, b, c: integer; d, e: real;
tu

iv ) var a, b, c: integer; var d, e: real; ✄


on

Ası́ como escribimos un entero como real, es posible que querramos pasar de
un real a un entero:
yc

Problema 3.9. Modificar el programa enteroareal, de modo de leer x , hacer la


dm

asignación “ a := x ” e imprimr a: ¿cuál es el resultado? ✄


.a

Como puede observarse, no es posible cambiar de real a entero directamente:


debe eliminarse primero la parte fraccionaria del real.
w
w

Problema 3.10. Las funciones de Pascal “ trunc ”, correspondiente a truncar,


w

y “ round ”, correspondiente a redondear, cuando aplicadas a un número real


dan números enteros, se aplican en la forma “ trunc(x) ” o “ round(x) ”.
a) Hacer un programa para averiguar el comportamiento de estas funciones,
tomando como entradas ±3, ±3.1, ±3.5, ±3.7.
b) ¿Cómo se relacionan estas funciones y las funciones piso, bxc, y techo, dxe?
Nota: Las definiciones de piso y techo están en la pág. 204. ✄
En el próximo problema abundamos sobre las asignaciones entre variables
numéricas de distinto tipo:
Problema 3.11 (Asignaciones entre distintos tipos). Supongamos que
hemos declarado
var n: integer; x: real;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 16 Tipos de datos elementales

En los siguientes, decir cuál es el valor de la última variable asignada o si se


produce un error (sugerencia: hacer un programa, compilarlo y ejecutarlo):
a) x := 3.7; n := x;
b) x := 3.7; n := trunc(x);
c) x := 3.5; n := trunc(x);
d ) x := 3.7; n := trunc(-x);
e) x := 3.5; n := trunc(-x);
f ) x := 3.7; n := round(x);
g) x := 3.5; n := round(x);
h) x := 3.7; n := round(-x);
i) x := 3.5; n := round(-x);
j ) n := maxint div 2; n := n * (n+1) / 2;

m
k ) n := maxint div 2; n := n * (n+1) div 2;

o
l ) n := maxint div 2; x := n * (n+1) / 2; ✄

.c
También es posible hacer operaciones mezclando variables numéricas de dis-

os
tintos tipos:

r
fo
Problema 3.12. Hacer un programa donde se declara a de tipo entero y b de
tipo real, se lean a y b, y se escriba en pantalla los resultados de: a + b, a ∗ b,
.m

a/b, b/a. ¿De qué tipo son los resultados en cada caso? Sugerencia: modificar

na

la declaración de variables en el programa sumardos.


tu
on

De aquı́ en más supondremos que el lector compi-


yc

lará y ejecutará cada programa mencionado en los


problemas, probándolo con distintos datos. En lo su-
dm

cesivo, generalmente omitiremos esta instrucción.


.a
w

Problema 3.13. El programa segundos (pág. 161) permite pasar de segundos


w

a horas, minutos y segundos, usando la función “ mod ”: “ a mod b ” da esencial-


w

mente el resto de la división de a por b.


a) Agregarle instrucciones de modo de poder verificar si el resultado es correc-
to, pasando de horas, minutos y segundos a segundos.
b) ¿Qué pasa si se coloca el renglón “ writeln(hs, ’ hs, ’,... ” inmedia-
tamente después del renglón “ writeln(segs, ’ segundos... ” y antes de
“ mins := segs div ... ”? ✄

Problema 3.14.
a) Hacer un programa para averiguar el comportamiento de “ mod ”, tomando
como entradas a = ±7 y b = ±3.
b) Comprobar si el valor de “ (a div b) * b + (a mod b) ” coincide con el
de a. ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

3.3. Variables lógicas Pág. 17

3.3. Variables lógicas


Estamos acostumbrados a valores numéricos, y no es sorpresa que por ejem-
plo las variables del tipo “ integer ” o entero, admitan valores como 1 o −987.
Sin embargo, cuesta acostumbrarse a las variables de tipo lógico o “ boolean ”,
que sólo admiten valores verdadero o “ true ”, y falso o “ false ”. Antes de
trabajar con estas variables en programación, practiquemos un poco:

Problema 3.15. En cada caso, decidir si la expresión es verdadera o falsa:


i) 1 = 2. ii) 1 > 2.
iii) 1 ≤ 2. iv ) 4 × 5 > 34 .
v) 1 < 2 < 3. vi) 1 < 2 < 0.
vii) 1 < 2 o 2 < 0. viii) 1 = cos 1.
ix ) {a, b, c} = {b, a, c}. x) {0, 1, 2, 3} ⊂ N. ✄

m
Habiendo estirado un poco los músculos, tiene sentido preguntarle a la com-

o
putadora si un número es positivo o no, y que guarde el resultado en una variable

.c
lógica:

os
Problema 3.16. Compilar y ejecutar el programa positivo (pág. 161), anali-
zando qué hacen las distintas instrucciones.
r
fo
Agregar una variable pos, declarada como lógica (“ boolean ”), incluir en el
.m
cuerpo del programa la asignación “ pos := a > 0 ” e imprimir pos, en vez de
“ a > 0 ”. ✄
na

Debemos destacar que al comparar números, Pascal usa a veces una sintaxis
tu

ligeramente diferente a la usual:


on

Matemáticas Pascal
yc

= =
6= <>
dm

> >
≥ >=
.a

< <

w

<=
w

debiendo tener cuidado en no confundir “ := ”, que indica asignación, con “ = ”,


w

que pregunta si los valores a ambos lados coinciden:

• “ a := a + 1 ” significa tomar el valor de a, agregarle 1 y guardar el


resultado en a,

• mientras que “ a = a + 1 ” es una proposición lógica con valor “falso”, y


no se modifica el valor de a.

Ası́ como para números tenemos la suma, el producto o el inverso aditivo,


para variables lógicas tenemos la conjunción y, a veces simbolizada con ∧, la
disyunción o, simbolizada con ∨, y la negación no, a veces simbolizada con ¬.
En Pascal, usamos respectivamente “ and ”, “ or ” y “ not ”.
Por ejemplo si a y b son de tipo entero, pondrı́amos

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 18 Tipos de datos elementales

Matemáticas Pascal
(a > 0) ∧ (b > 0) (a > 0) and (b > 0)
(a > 0) ∨ (b > 0) (a > 0) or (b > 0)
¬(a > 0) not (a > 0)

pero la expresión matemática a < b < c debe escribirse en Pascal como “ (a <
b) and (b < c) ”. También observamos que para preguntar si a 6= b en Pascal
puede ponerse tanto “ a <> b ” o como “ not (a = b) ”, y de modo similar para
las desigualdades.

Problema 3.17. Modificar el programa positivo, agregando una variable b de


tipo entero y leyendo tanto a como b, de modo que:
a) Para cada uno de los casos siguientes se imprima si son verdaderas o falsas
las siguientes proposiciones:

m
i) a < b, ii) 0 < a y 0 < b, iii) 0 < a o 0 < b.

o
b) Una de las leyes de De Morgan establece que si p y q son proposiciones

.c
(lógicas), entonces
¬(p ∧ q) = (¬p) ∨ (¬q).

os
Modificar el programa de modo de verificar esta ley cuando p = (a > 0)
r
fo
y q = (b > 0), i.e. evaluar las expresiones a cada lado en la ley de De Morgan

.m
y compararlas.
na

3.4. Caracteres
tu

Ası́ como la computadora guarda internamente los números como ristras de


on

ceros y unos en la computadora, también las letras se guardan como ristras de


ceros y unos. La forma en que se hace esta codificación depende del sistema
yc

operativo, pero un código particularmente usado es el ASCII con el que se con-


dm

vierten a números entre 0 y 127 algunos caracteres: letras como ‘ a ’, ‘ b ’,. . . ,‘ z ’


o ‘ A ’,. . . ,‘ Z ’, números (dı́gitos) como 0,1,. . . ,9, y sı́mbolos como ‘ + ’, ‘ $ ’, etc.
.a

En ASCII, los caracteres imprimibles como letras y sı́mbolos empiezan a par-


tir de 32, pero no todas las letras están representadas, como la ‘ ~ n ’ o las vocales
w

acentuadas del castellano. A fin de codificar también estos caracteres, hay ex-
w

tensiones para cubrir caracteres con números hasta 255, pero estas extensiones
w

no son del estándar ASCII y dependen en general del sistema operativo.


En Pascal, dado un carácter podemos encontrar su número de orden me-
diante “ ord ” y recı́procamente, dado un entero podemos ver qué carácter le
corresponde mediante la función “ chr ”3 . El estándar Pascal establece que:

• Los caracteres ‘ 0 ’,‘ 1 ’,. . . ,‘ 9 ’ están numéricamente ordenados y son con-


secutivos, pero

• las letras minúsculas, ‘ a ’,‘ b ’,. . . ,‘ z ’, si bien están ordenadas, ¡no son
necesariamente consecutivas!

• y lo mismo para las letras mayúsculas.


3 ¡No confundir “ char ” con “ chr ”!

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

3.4. Caracteres Pág. 19

Afortunadamente, el código ASCII —con el que trabajaremos— satisface


estos requisitos, y más aún, las letras minúsculas ‘ a ’,. . . ,‘ z ’ son consecutivas y
lo mismo para las mayúsculas.
Desafortunadamente, si bien para nosotros ‘ ~ n ’ está entre ‘ n ’ y ‘ o ’, esto no es
ası́ en el código ASCII, pues la letra ‘ ~
n ’ ni siquiera está codificada. Ni qué hablar
de ch o ll, que se consideran (en cada caso) como dos caracteres distintos. Todo
esto puede traer problemas al clasificar u ordenar alfabéticamente.
Problema 3.18 (Ordinales y caracteres). El programa caracteres1 (pág.
161) toma un carácter como entrada, retorna su número de orden y verifica la
corrección.
a) ¿Qué pasa si cambiamos el nombre del programa de “ caracteres1 ” a
“ char ” ?, ¿y a “ char1 ” ?
Nota: Recordar lo dicho sobre identificadores al principio del capı́tulo y en
el problema 3.2.c).

m
b) ¿Qué pasa si se escribe como entrada “ tonto ” ?

o
c) ¿Cuál es el ordinal correspondiente a los caracteres ‘ a ’, ‘ A ’, ‘ ’ (espacio),

.c
y tabulación (tecla “tab”)?

os
d ) Hacer un programa que, inversamente, dado un número retorne el carácter
correspondiente.
r
fo
e) Averiguar si hay caracteres correspondientes a números mayores que 127 (el
.m
resultado dependerá de la máquina). ¿Y a números mayores que 255?
f ) ¿Cuál es el carácter correspondiente al número 7 (en ASCII)? ✄
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 4

Tomando control

m
Con las operaciones que hemos visto, no podemos hacer mucho más que lo
que hace una calculadora sencilla. Las cosas empiezan a ponerse interesantes

o
cuando disponemos de estructuras de control de flujo, esto es, instrucciones que

.c
nos permiten tomar decisiones sobre si realizar o no determinadas instrucciones

os
o realizarlas repetidas veces.
Al contar con estructuras de control, podremos verdaderamente comenzar a
r
fo
describir algoritmos, es decir, instrucciones (no necesariamente en un lenguaje
de programación) que nos permiten llegar a determinado resultado, y encarar
.m

el principal objetivo de este curso: pensar en los algoritmos y cómo traducirlos


na

a un lenguaje de programación.
Los programas irán aumentando en longitud, lo que nos obligará a encarer
tu

de forma diferente su confección, debiendo pensar con más cuidado primero en


qué queremos hacer, luego en cómo lo vamos a hacer, y finalmente pasar a los
on

detalles. Pascal, y casi todos los lenguajes de programación, son poco “natu-
rales” en este sentido, exigiendo —por ejemplo— la declaración de variables al
yc

principio del programa fuente, mientras que nosotros pensaremos el programa


dm

de “abajo hacia arriba” (o casi), y recién al final miraremos cómo declarar las
variables necesarias.
.a

En Pascal, las estructuras fundamentales de control son “ if ”, “ while ”,


“ repeat ” y “ for ”, que pasamos a estudiar.
w

Nota: En Pascal hay otras estructuras de control, como “ cases ”, que no ve-
w

remos. En otros lenguajes de programación existen otras estructuras, pero casi


w

siempre están los equivalentes de “ if ” y “ while ”, con las que se pueden


simular las otras.

4.1. “If”
Supongamos que al cocinar decidimos bajar el fuego si el agua hierve, es decir
realizar cierta acción si se cumplen ciertos requisitos. Podrı́amos esquematizar
esta decisión con la sentencia:

si el agua hierve entonces bajar el fuego.

A veces queremos realizar una acción si se cumplen ciertos requisitos y


además realizar una acción alternativa si no se cumplen. Por ejemplo, si pa-

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 22 Tomando control

ra ir al trabajo podemos tomar el colectivo o un taxi —que es más rápido pero


más caro que el colectivo—, dependiendo del tiempo que tengamos decidirı́amos
tomar uno u otro, que podrı́amos esquematizar como:

si es temprano entonces tomar el colectivo en otro caso tomar el taxi.

En Pascal podemos tomar este tipo de decisiones, usando la construcción


“ if...then... ” para el esquema “si . . . entonces . . . ”, mientras que para la
variante “si . . . entonces . . . en otro caso . . . ” usamos “ if...then...else... ”
Por supuesto, entre “ if ” y “ then ” tenemos que poner una condición (una
expresión lógica) que pueda evaluarse como verdadera o falsa.

Problema 4.1. El valor absoluto para x ∈ R se define como


(
x si x ≥ 0,
|x| =
−x en otro caso.

o m
El programa valorabsoluto (pág. 162) realiza este cálculo. Observar qué rea-

.c
lizan las distintas instrucciones. ¿Qué pasa si se eliminan los paréntesis después

os
del “ if ” ?, ¿y si se cambia “ >= ” por “ > ” ?, y si se agrega “ ; ” antes de “ else ”?
La función “ abs ” de Pascal hace el cálculo del valor absoluto. Incorporarla
r ✄
fo
al programa y verificar que se obtienen los mismos resultados.
.m
A veces es conveniente encadenar varios “ if ”:
na

Problema 4.2. El programa comparar (pág. 162) toma como datos los números
enteros a y b y determina cuál es mayor o si son iguales.
tu

a) ¿Qué pasa si se escriben las lı́neas que empiezan con “ if ” y terminan en


on

“ ; ” en un solo renglón?
b) ¿Qué pasa si se incluyen “ ; ” al final de cada uno de los renglones?
yc

c) ¿Qué pasa si se ingresan números reales o letras en vez de enteros?


dm

d ) Modificar el programa para que siempre escriba en la salida el número a


primero (i.e. si a = −5 y b = 3 escriba “ -5 es menor que 3 ” en vez de “ 3
.a

es mayor que -5 ”).


w

e) Modificarlo para que sólo decida si a = b o a 6= b.


w

f ) Modificarlo para que sólo decida si a > b o a ≤ b. ✄


w

Problema 4.3 (Comparación de caracteres). El programa caracteres2 (pág.


162) decide si un carácter entrado por terminal es una letra minúscula o una
mayúscula o no es una letra mediante comparación1. Modificarlo de modo de
hacer un programa para clasificar un carácter ingresado como uno de los si-
guientes:
a) una letra minúscula vocal, b) una letra minúscula consonante,
c) una letra mayúscula vocal, d ) una letra mayúscula consonante,
e) un dı́gito (0, 1, . . . , 9), f ) ni letra ni dı́gito. ✄

Problema 4.4. El Gobierno ha decidido establecer impuestos a las ganan-


cias en forma escalonada: los ciudadanos con ingresos hasta $10000 no pagarán
1 Recordar lo dicho al principio de la sección 3.4.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

4.1. “If ” Pág. 23

impuestos; aquéllos con ingresos superiores a $10000 pero que no sobrepasen


$30000, deberán pagar 10 % de impuestos; aquéllos cuyos ingresos sobrepasen
$30000 pero no sean superiores a $50000 deberán pagar 20 % de impuestos, y
los que tengan ingresos superiores a $50000 deberán pagar 35 % de impuestos.
a) Hacer un programa para calcular el impuesto dado el monto de la ganancia.
b) Modificarlo para determinar también la ganancia neta (una vez deducidos
los impuestos) del ciudadano.
c) Modificarlo de modo que los impuestos y ganancia neta se impriman hasta
el centavo (y no más). ✄
Problema 4.5 (Años bisiestos). Desarrollar un programa para decidir si un
año dado es o no bisiesto.
Julio César (101 o 100–44 a. de C.) cambió el calendario egipcio, que estaba
basado en un año de exactamente 365 dı́as, a un nuevo calendario, el julia-
no, con años bisiestos cada 4 años aproximando mejor la verdadera longitud

m
del año. Sin embargo, cálculos posteriores mostraron que la longitud del año

o
es aproximadamente 365.2422 dı́as. Con el paso de los siglos, las diferencias

.c
anuales de 0.0078 dı́as en promedio se fueron acumulando, y hacia el año 1582
el Papa Gregorio comenzó un nuevo calendario (el gregoriano). Por un lado,

os
se agregaron 10 dı́as a la fecha, de modo que el 5 de octubre de 1582 pasó a ser
el 15 de octubre (y nunca existieron los dı́as entre el 6 y el 14 de octubre de
r
fo
ese año). Por otro lado, se decidió que los años bisiestos serı́an precisamente
los divisibles por 4, excepto que aquéllos que fueran divisibles por 100 serı́an
.m

bisiestos sólo cuando divisibles por 400 (ası́ el año 1700 no es bisiesto pero
sı́ el 2000). Con esta convención, el año promedio tiene 365.2425 dı́as. No to-
na

dos los paı́ses del mundo adoptaron este calendario inmediatamente. En Gran
Bretaña y lo que es ahora Estados Unidos de Norteamérica, se adoptó recién
tu

en 1752, por lo que se debieron agregar 11 dı́as más; Japón cambió en 1873,
on

Rusia en 1917 y Grecia en 1923. Aún hoy algunas iglesias ortodoxas mantienen
el calendario juliano, especialmente para determinar la fecha de las Pascuas.
yc

Nota: A veces con un pequeño esfuerzo podemos hacer el cálculo más eficiente.
Un esquema para resolver el problema anterior, si anio es el año ingresado, es
dm

write(anio);
if (anio mod 400 = 0) then writeln(’ es bisiesto’)
.a

else if (anio mod 100 = 0) then writeln(’ no es bisiesto’)


w

else if (anio mod 4 = 0) then writeln(’ es bisiesto’)


else writeln(’ no es bisiesto’)
w
w

Sin embargo, el esquema


write(anio);
if (anio mod 4 <> 0) then writeln(’ no es bisiesto’)
else if (anio mod 100 <> 0) then writeln(’ es bisiesto’)
else if (anio mod 400 <> 0) then writeln(’ no es bisiesto’)
else writeln(’ es bisiesto’)
es más eficiente, pues siendo que la mayorı́a de los números no son múltiplos
de 4, en la mayorı́a de los casos haremos sólo una pregunta con el segundo
esquema pero tres con el primero.
Nosotros no vamos a preocuparnos por la eficiencia del programa, pero
haremos comentarios (¡como éste!) de tanto en tanto. ✄
Problema 4.6. Desarrollar un programa que, tomando como entrada un núme-
ro natural entre 1 y 3000, lo escriba en romano (e.g. entrando 2999 se debe

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 24 Tomando control

obtener MMCMXCIX). Sugerencia: primero hacer un programa para tratar el


caso entre 1 y 30, y después ir agregando posibilidades.
Nota: Las letras usadas para números romanos son I, V, X, L, C, D, M, co-
rrespondientes respectivamente a 1, 5, 10, 50, 100, 500, 1000. ✄
Nota: El problema anterior es ciertamente tedioso para escribir, pero es tı́pico
de muchas aplicaciones, en las que no hay atajos.
A veces es conveniente y aún necesario agrupar instrucciones, lo que en
Pascal se hace mediante “ begin...end ”, como en la parte principal de todo
programa2. Este “agrupar” es similar al uso de paréntesis en expresiones ma-
temáticas, “ ( ” correspondiendo a “ begin ” y “ ) ” correspondiendo a “ end ”.
Como “ begin...end ” es similar a los paréntesis, “ begin ” y “ end ” no son
sentencias en sı́, por lo tanto

es incorrecto poner un “ ; ” inmediatamente antes de un “ end ”.

o m
Veamos un ejemplo donde podrı́amos usar “ begin...end ”:

.c
Problema 4.7. Sea f : R → R la función definida como

os
(
x2 si x > 0,
r
fo
f (x) =
0 si x ≤ 0.
.m

a) Hacer un esquema del gráfico de la función.


na

b) Hacer un programa para que, dando x como entrada, se imprima f (x).


tu

c) Supongamos que queremos imprimir además un cartel diciendo si el valor


entrado es positivo (y por lo tanto f (x) = x2 ) o si no lo es (y por lo tanto
on

f (x) = 0). Una posibilidad es, habiendo declarado a x y y como del tipo
yc

“ real ”, poner dos “ ifs ” consecutivos, por ejemplo:


dm

if (x > 0) then writeln( x, ’ es positivo’)


else writeln( x, ’ no es positivo’);
if (x > 0) then y := x*x else y := 0;
.a

writeln(’El valor de la funcion es ’, y);


w

Incluir en el programa del inciso anterior estas modificaciones.


w

d ) Otra posibilidad, que puede parecer más coherente, es poner un único “ if ”,


w

agrupando las instrucciones en cada caso:


if (x > 0) then begin
writeln( x, ’ es positivo’);
y := x*x
end
else begin
writeln( x, ’ no es positivo’);
y := 0
end;
writeln(’El valor de la funcion es ’, y);
2 En este caso a “ end ” le sigue un “ . ”, pero el punto sólo debe aparecer al final del

programa fuente.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

4.2. “While” Pág. 25

Modificar el programa del inciso b) incorporando esta variante. ¿Qué pa-


sa si se elimina el “ end ” antes del “ else ”?, ¿y si se pone “ ; ” entre “ end ”
y “ else ”? ✄

Otras veces el uso de “ begin...end ” evita confusiones:

Problema 4.8. Supongamos que hemos declarado


var a, b, n, z: integer;
y que tenemos el renglón
if (n > 0) then if (a > b) then z := a else z := b;

a) ¿Qué tiene de confuso este renglón?


b) Decir cuál es el valor de z inmediatamente después de ejecutar el renglón
original cuando los valores de a, b, n y z son:

m
i) a = 1, b = 2, n = 1, z = 0;

o
ii) a = 1, b = 2, n = −1, z = 0;

.c
iii) a = 2, b = 1, n = 1, z = 0;

os
iv ) a = 2, b = 1, n = −1, z = 0;
c) Decir si el renglón es equivalente a:
r
fo
i) if (n > 0) then begin
.m

if (a > b) then z := a else z := b end;


ii) if (n > 0) then begin
na

if (a > b) then z := a end else z := b; ✄


tu
on

4.2. “While”
yc

La estructura “ while ” es una estructura para realizar repetidas veces una


misma tarea. Junto con “ repeat ” y “ for ” —que veremos más adelante— reci-
dm

ben el nombre común de lazos o bucles para indicar esta capacidad de repetición.
Supongamos que voy al supermercado con cierto dinero para comprar la
.a

mayor cantidad posible de botellas de cerveza. Podrı́amos ir calculando el di-


w

nero que nos va quedando a medida que vamos poniendo botellas en el carrito:
w

cuando no alcance para más botellas, vamos a la caja. Una forma de poner
esquemáticamente esta acción como
w

mientras me alcanza el dinero, poner botellas en el carrito.

En Pascal, este esquema se realiza con la construcción “ while...do... ”,


donde “ do ” reemplaza a la “ , ” en la sentencia anterior, y entre “ while ” y
“ do ” debe ponerse una expresión lógica.
Observamos desde ya que:

• Si la condición no es cierta al comienzo, nunca se realiza la acción: si el


dinero inicial no me alcanza, no pongo ninguna botella en el carrito.

• En cambio, si la condición es cierta al principio, debe modificarse con


alguna acción posterior, ya que en otro caso llegamos a un lazo “infinito”

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 26 Tomando control

que nunca termina. Por ejemplo, si tomamos un número positivo y lo


dividimos sucesivamente por 2 mientras el resultado sea positivo3 .

Por cierto, en el ejemplo de las botellas en el supermercado podrı́amos reali-


zar directamente el cociente entre el dinero disponible y el precio de cada botella,
en vez de realizar el lazo mientras. Es lo que vemos en el próximo problema:

Problema 4.9. El programa resto (pág. 163) calcula el resto de la división de


a ∈ N por b ∈ N mediante restas sucesivas.
a) Antes de compilar y ejecutar el programa, hacemos una “prueba de escri-
torio”. Por ejemplo, si ingresamos a = 10 y b = 3, podrı́amos poner
Paso acción a b r
0 (antes de empezar) sin valor sin valor sin valor
1 leer a 10
2 leer b 3

m
3 r := b 10

o
4 r ≥ b: verdadero

.c
5 r := r - b 7

os
6 r ≥ b: verdadero
7 r := r - b 4
r ≥ b: verdadero r
8
fo
9 r := r - b 1
.m

10 r ≥ b: falso
11 imprimir r
na

donde indicamos los pasos sucesivos que se van realizando (excepto la impre-
tu

sión de carteles sin variables), y los valores de las variables a, b y r (el último
on

valor que aparece es el que tiene antes de ejecutarse el paso correspondien-


te). Podemos comprobar entonces que los valores de a y b al terminar el
yc

programa son los valores entrados, mientras que r se va modificando.


Nota: El lector puede hacer la tabla de la “prueba de escritorio” como le
dm

parezca más clara. La presentada es sólo una posibilidad.


Las “pruebas de escritorio” sirven para entender el comportamiento
.a

del programa y detectar algunos errores (pero no todos). Son útiles al


comenzar a programar, en programas ni demasiado sencillos, como los que
w

hemos visto hasta ahora, ni demasiado complicados como varios de los que
w

veremos en los capı́tulos siguientes.


w

Otra forma —bastante más primitiva— de entender el comportamiento


de un programa y eventualmente encontrar errores, es probarlo con dis-
tintas entradas, como hemos hecho desde el compienzo, por ejemplo en el
problema 2.2 y prácticamente en todo el capı́tulo 3.
Alentamos al lector para que haga las “pruebas de escritorio” de los
restantes problemas de éste y el siguiente capı́tulos.
b) Hacer una “prueba de escritorio” con otros valores de a y b, por ejemplo
con 0 < a < b (en cuyo caso la instrucción dentro del lazo “ while ” no se
realiza).
c) Compilar y ejecutar el programa, verificando que coinciden los resultados
del programa y de las “pruebas de escritorio”.
3 En teorı́a. Como veremos en el problema 4.14, la máquina tiene un comportamiento

diferente.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

4.2. “While” Pág. 27

d ) Observar que es importante que a y b sean positivos: dar ejemplos de a y b


donde el lazo “ while ” no termina nunca (¡sin correr el programa!).
e) Modificar el programa de modo de contar el número de veces que se realiza
el lazo “ while ”.
f ) Modificar el programa para comparar el valor obtenido con el resultado de
la operación “ a mod b ”.
g) Modificarlo para que el programa también calcule el cociente, c, y com-
parar con el resultado de “ a div b ”. Sugerencia: al calcular el cociente,
¿qué diferencia hay con el inciso e)?
h) ¿Conoces algún ejemplo donde tenga sentido declarar las variables a, b y
r como de tipo real en vez de entero? Sugerencia: pensar en radianes y
b = 2π. ✄
Problema 4.10. El programa tablaseno (pág. 163) produce una tabla del seno
para ángulos expresados en grados. Tanto el ángulo inicial, el ángulo final como

m
el incremento, son entrados por el usuario.

o
a) Observar el uso de “ const ” para dar un valor aproximado de π, anterior

.c
a la declaración de variables. Observar también que no se hace una asig-

os
nación a pi , sino que se usa el signo “ = ”. El uso de const hace que no se
puedan hacer asignaciones a pi pues no es una variable: tratar de hacer una
r
fo
asignación a pi y observar qué pasa.
.m
b) Antes o después de ejecutar el programa, verificar que se obtienen los mismos
resultados de una “prueba de escritorio” (como en el problema 4.9, ahora
na

con las variables inicial , final , incremento, grados y radianes).


c) Algunos compiladores Pascal tienen pre-definida la constante pi , pero no es
tu

estándar. Verificar si el compilador usado está en esta categorı́a comentando


on

el renglón donde se define pi .


d ) En vez de poner manualmente un valor aproximado de π, es común usar la
yc

igualdad π = 4 arctan 1.
dm

i) Cambiar el valor “ 3.14159265 ” dado a pi en el programa original,


reemplazándolo por “ 4 * arctan(1) ”. En general los compiladores
.a

no aceptan este cambio, pues el estándar Pascal no lo permite.


ii) Eliminar el renglón donde se define a pi como una constante y definir
w

en cambio pi como una variable de tipo real, y en el cuerpo del pro-


w

grama hacer la asignación de la igualdad anterior. Comparar con los


w

resultados obtenidos originalmente.


Nota: Podrı́a usarse otro valor similar para π como 2 arc cos 0 o 2 arcsin 1,
pero el arco tangente es la única función trigonométrica inversa definida
en el estándar Pascal.
e) ¿Qué pasa si el valor de incremento es cero? ¿Y si el valor de final es menor
que el de inicial y incremento es positivo?
f ) Modificar el programa tablaseno para producir en cambio una tabla de loga-
ritmos en base 10, donde los valores inicial , final e incremento son de tipo
real. Sugerencia: en Pascal está definido ln x = loge x, y para cualquier a, b
y x razonables, loga x = logb x/ logb a. ✄
Otro uso muy común de los lazos (“ while ”, “ repeat ” o “ for ”) es el cálculo
de sumas o productos de muchos términos. Si el resultado se va a guardar en

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 28 Tomando control

la variable resultado, ésta se inicializa a 0 en el caso de la suma o a 1 en el


caso del producto antes del lazo, y en éste se van sumando o multiplicando los
términos deseados modificando resultado. Ilustramos esta técnica en el siguiente
problema:

Problema 4.11 (Suma de Gauss). Para n ∈ N consideremos la suma

X
n
sn = 1 + 2 + 3 + · · · + n = i,
i=1

que, según la fórmula de Gauss, es

n × (n + 1)
sn = .
2
Según se dice, Karl Friederich Gauss (1777–1855) tenı́a 8 años cuando el

m
maestro de la escuela le dio como tarea sumar los números de 1 a 100 para

o
mantenerlo ocupado (y que no molestara). Sin embargo, hizo la suma muy
rápidamente al observar que la suma era 50 × 101.

.c
El programa gauss (pág. 164) calcula sn sumando los términos (y no usando

os
la fórmula de Gauss).

r
fo
a) ¿Cuál es el valor de n al final del lazo? Sugerencia: hacer una “prueba de
escritorio”.
.m

b) ¿Qué pasa si se ingresa n ≤ 0?


na

c) La variable suma está declarada como de tipo real. Si se declarara como de


tipo entero, ¿cuál serı́a el máximo n para el cual se podrı́a calcular sn (i.e.
tu

para el cual sn ≤ maxint )?


on

d ) ¿Cuántas asignaciones se realizan (en términos de n) dentro del lazo?


e) Modificar el programa de modo que a la salida exprese también el valor
yc

original de n (algo como “ La suma de 1 a 100 es 5050 ” cuando n =


dm

100).
f ) Modificar el lazo “ while ” para que se sume “al derecho”, i.e. primero los

.a

términos más chicos.


w

Problema 4.12. Modificar el programa gauss para obtener, dado n ∈ N, su


w

factorial,
n! = 1 × 2 × · · · × n.
w

a) ¿Hay alguna diferencia entre los resultados multiplicando “al derecho” (de
menor a mayor) y “al revés” (de mayor a menor)?
Nota: es posible que sı́ cuando n es grande, debido a errores numéricos.
b) Determinar cuántos ceros hay al final de la expresión decimal de 30!, y com-
parar con la cantidad de ceros que aparecen en el resultado del programa.
Sugerencia: contar las veces que 2 y 5 son factores de 30!.
Nota: 30! tiene 33 cifras decimales. ✄

4.3. “Repeat”
Si queremos ir a la facultad caminando, podrı́amos poner

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

4.3. “Repeat” Pág. 29

repetir caminar hasta llegar.

esquema que en Pascal se pone como “ repeat...until... ”


La estructura “ repeat ” es muy similar a la de “ while ”. Observamos que
en “ while ” la condición de control va al comienzo, mientras que en “ repeat ”
va al final. Ası́, la principal diferencia es que la acción se realiza siempre al
menos una vez con “ repeat ”, mientras con “ while ” puede no realizarse si la
condición de control es falsa.
Otra diferencia, pero de carácter formal, es que para ejecutar varias instruc-
ciones en un lazo “ while ” debemos agruparlas con “ begin...end ”, mientras
que en un lazo “ repeat ” no es necesario usar “ begin...end ” pues ya están
agrupadas entre “ repeat ” y “ until ”.
Problema 4.13. El programa cifras (pág. 165) calcula la cantidad de cifras en
base 10 de un número entero, sin tener en cuenta el signo, pero tomando que el
número 0 tiene una cifra.

m
a) ¿Para qué está el renglón “ if (a < 0) then b := -a else b := a; ”?

o
.c
b) Cambiar el lazo “ repeat ” por otro “ while ” de modo que ahora se consi-
dere que el número 0 tiene 0 cifras. ✄

os
Los errores numéricos al trabajar con el tipo “ real ” están relacionados
r
fo
con la representación interna de este tipo, como ya observamos al mencionar la
densidad variable en el capı́tulo 3 (pág. 14).
.m

En el próximo problema calculamos dos indicadores importantes de esta


propiedad: εmin , el epsilon mı́nimo, que es el menor número real —potencia de
na

2— positivo que se puede representar; y εmaq , el epsilon de máquina, que es el


tu

menor número positivo —potencia de 2— que sumado a 1 da mayor que 1.


on

Problema 4.14. El programa epsmin (pág. 165) calcula εmin y εmaq . Debemos
destacar que en principio los resultados exhibidos son sólo aproximaciones según
yc

la máquina a potencias de 2, puesto que en su cálculo o en su impresión pueden


intervenir errores numéricos.
dm

a) Observar que εmin  εmaq . Esto permite que en el cálculo de εmaq podamos
.a

multiplicar por 2, pero no para calcular εmin: ver qué sucede si para calcular
εmin tomamos
w

eps := 1;
w

repeat eps := eps/2 until (eps = 0);


w

eps := 2 * eps;
writeln(’epsmin es: ’, eps);

b) ¿Qué pasa si se calcula εmaq mediante


eps := 1;
repeat eps := eps/2 until (1 + eps = 1);
eps := 2 * eps;
writeln(’epsmaq es: ’, eps);
?
Nota: Es posible que los valores sean distintos, debido a la forma de trabajo
interna de la máquina.
c) Cambiar los lazos “ repeat ” por lazos “ while ” haciendo las modificaciones
necesarias. ¿Cuál es más natural, “ repeat ” o “ while ”? ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 30 Tomando control

Para calcular εmaq y εmin hemos violado justamente la regla de oro:

¡Nunca deben compararse números reales por igual-


dad sino por diferencias suficientemente pequeñas!

4.4. “For”
Hay veces que queremos repetir una acción un número fijo de veces. Por
ejemplo, al subir una escalera con 10 escalones harı́amos algo como

hacer 10 veces: subir un escalón.

m
En Pascal no existe una estructura que traduzca esta acción directamente4 .

o
En cambio, cuenta con una estructura un poco más flexible que nos permite

.c
imitarla: si contamos los escalones con el “contador” e, que variará entre 1 y

os
10, podrı́amos poner el esquema

r
fo
para e desde 1 a 10 hacer subir el e-ésimo escalón.
.m

Este esquema se reproduce en Pascal (habiendo declarando e como de tipo


entero) con la estructura
na

for e := 1 to 10 do subir el e-ésimo escalón


tu

que ciertamente podrı́a cambiarse por un lazo “ while ”:


on

e := 1;
yc

while (e <= 10) do begin


subir el e-ésimo escalón;
dm

e := e + 1
end
.a

En este caso resulta más cómodo el lazo “ for ”, no sólo porque es más
w

conciso, sino también porque evita errores como el de olvidarse incrementar e


w

dando lugar a un lazo infinito.


w

Ası́, una de las diferencias entre “ while ” (o “ repeat ”) y “ for ” es que


no aparece explı́citamente una condición lógica. Otra de las diferencias es que,
según el estándar Pascal, el valor de la variable que indica el paso, llamada
variable de control (en el ejemplo e) no está definido al finalizar el lazo “ for ”,
aunque muchos compiladores no respetan esta convención.
Un ejemplo de uso de “ for ” en matemáticas es el cálculo de la potencia xn
cuando x ∈ R y n ∈ N, donde debemos multiplicar x por sı́ mismo n veces:

Problema 4.15. El programa potencia (pág. 166) calcula la potencia n-ésima


del real x.
a) Compilarlo y ejecutarlo, verificando su comportamiento.
4 Pero sı́ en Modula, el sucesor de Pascal también desarrollado por N. Wirth.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

4.4. “For” Pág. 31

b) No es posible tomar bases y exponentes muy grandes sin que haya overflow
o underflow, i.e. resultados que no se pueden representar en la máquina por
ser muy grandes o muy pequeños. Probar con distintos valores hasta obtener
este comportamiento, e.g. 1010 , 100100, (1/10)10 , (1/100)100, etc.
c) Modificar el programa de modo de imprimir el valor de i al finalizar el lazo.
Nota: Como mencionáramos, según el estándar el valor no está definido,
pero muchos compiladores no respetan esta convención y el valor obtenido
puede depender del compilador: no debe usarse esta variable después del
lazo “ for ” sin antes re-asignarla.
d ) Agregar sentencias para considerar también el caso de n negativo. Sugeren-
cia: ab = 1/a−b , usar la función “ abs ” y condicional “ if ”.
e) Algunos compiladores admiten la operación “ x ** y ”, que no es del están-
dar Pascal, para el cálculo de la potencia xy (x ∈ R+ , y ∈ R). Verificar si
el compilador usado admite esta sentencia y, en caso afirmativo, comparar
con el resultado anterior cuando y ∈ N.

m
f ) Otra forma de calcular la potencia en general es usar xy = ey×ln x . Incorpo-

o
rar esta fórmula al programa para comparar con los resultados anteriores.

.c
Nota: Es posible que difieran debido a errores numéricos. ✄

r os
Muchos de los problemas que hemos visto usando “ while ” o “ repeat ”
fo
pueden hacerse con “ for ” con modificaciones sencillas, pero no todos:
.m

Problema 4.16.
na

a) Modificar los programas tablaseno (pág. 163) y gauss (pág. 164) cambiando
tu

el lazo “ while ” por uno “ for ”.


on

b) ¿Podrı́a cambiarse el lazo “ while ” por uno “ for ” en el programa resto


(problema 4.9)?, ¿y el lazo “ repeat ” por uno “ for ” en el programa epsmin
yc

(problema 4.14)? ✄
dm

Un problema muy común es entrar datos por terminal para realizar alguna
acción, como sumarlos:
.a
w

Problema 4.17.
w
w

a) Hacer un programa que calcule —usando un lazo “ for ”— la suma de cierta


cantidad de enteros entrados por terminal y luego los imprima. El usuario
debe ingresar primero el número de datos y después cada uno de los datos
a sumar.
b) Cuando entramos datos, muchas veces es molesto contar la cantidad de
datos antes de entrarlos: es mucho más cómodo ingresarlos y dar una señal
al programa de que la entrada de datos ha terminado. Una posibilidad es
usar un “dato imposible” como señal, por ejemplo si se ingresa −1 cuando
los datos a sumar son positivos.
Haciendo esta suposición (i.e. el usuario sólo ingresa los datos a sumar
que son positivos, y la señal de fin de datos que es −1), modificar el pro-
grama del inciso anterior cambiando el lazo “ for ” por uno “ while ” o
“ repeat ”. ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 32 Tomando control

4.5. “Eoln”
Ciertamente es molesto el “dato imposible −1” del problema 4.17, ya que
tendremos que modificar el programa si en algún momento queremos usarlo para
sumar datos que pueden ser negativos. Más cómodo serı́a ingresar como “dato
imposible” alguna señal que no imponga limitaciones sobre los números a entrar
(salvo que sean de tipo entero o real).
Una posibilidad es poner como “dato imposible” un “dato vacı́o”, es decir,
nada, o más correctamente un <retorno> sin entrada de datos, usando la
función de Pascal “ eoln ”.
Pero antes de describir esta función, será conveniente repasar un poco lo di-
cho —y hecho— al introducir la función “ readln ” en la sección 3.2.1 (pág. 12).
Cuando ingresamos un dato, éste no es leı́do por el programa hasta que
ingresemos <retorno>, dándonos la oportunidad de cambiarlo5. Si hemos de-
clarado la variable a (de algún tipo), la sentencia “ readln(a) ” hace que el

m
programa lea el primer dato que no sea justamente <retorno> y lo guarde

o
en a si el dato es correcto (si es incorrecto, puede pasar cualquier cosa: ver el

.c
problema 3.3.b)).

os
Si en el programa fuente tenemos la instrucción “ readln ”, sin argumentos,
entonces se lee el renglón, y si hemos entrado otros datos antes de <retorno>,
éstos son ignorados (ver el inciso c) del problema citado).
r
fo
Al ingresar nosotros <retorno>, no sólo estamos indicando que terminamos
.m
de ingresar nuestros datos, sino que se emite una señal especial que llamaremos
fin de lı́nea 6 . Cuando ponemos “ readln(a) ” lo que hacemos efectivamente es
na

“leer” la señal de fin de lı́nea además del dato a.


La función de Pascal “ eoln ” (una abreviatura de “end of line” o fin de lı́nea),
tu

sin argumentos, devuelve un valor lógico, i.e. verdadero o falso. Como “ readln ”,
on

“ eoln ” se queda a la espera de ingreso de datos, que necesariamente deben


incluir un <retorno>. Si hay otros datos además de <retorno>, “ eoln ”
yc

retorna falso y si no, verdadero. Pero a diferencia de “ readln ”, no “lee” el fin


de lı́nea, y debemos realizar esta “lectura” con “ readln ” sin argumentos.
dm

Nota: Sin embargo, después de evaluarse “ eoln ”, un nuevo “ readln ” con


< >
argumentos hará que se ignoren los retorno ’s que hayamos introducido
.a

aunque no se hayan “leı́do”, como ya hemos verificado en el problema 3.3.


w
w

Problema 4.18. El programa eolnprueba (pág. 166) es una prueba del funcio-
namiento de “ eoln ”, en el que se usa la variable a para entender el flujo de las
w

instrucciones.
a) Compilar y ejecutar el programa, observando que luego de imprimir el valor
de a antes del lazo, queda a la espera del ingreso de datos:
i) Ingresar “ <retorno> ”, sin otros caracteres, y observar el valor final
de a. ¿Cuáles de las instrucciones en “ if ” se ejecutaron?, ¿qué valor
ha dado “ eoln ” y por qué?
ii) Si en vez de ingresar sólo “ <retorno> ”, se ingresa “ <retorno> ”
(con un espacio antes), ¿cuál será el valor de a que se imprime al
final? Verificar la respuesta ejecutando el programa con esa entrada.
5 Al menos para compiladores “razonables”.
6 La señal de “fin de lı́nea” depende del sistema operativo.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

4.5. “Eoln” Pág. 33

b) Después del renglón “ writeln(’Ahora el valor... ” agregar los renglo-


nes
write(’Entrar un nuevo valor de a: ’);
readln(a);
writeln(’El valor final de a es ’, a);

i) Ejecutar el programa e ingresar “ <retorno> ” y “ 3<retorno> ” (con


un 3 antes), ¿cuál es el último valor de a?, ¿por qué?
ii) Si en la nueva variante ingresáramos “ 4<retorno> ” (con un 4 antes)
y luego “ 5<retorno> ” (con un 5 antes), ¿cuál es el último valor de
a?, ¿por qué? Ejecutar el programa y verificar la respuesta. ✄
Problema 4.19. El programa sumardatos (pág. 167) calcula la suma de núme-
ros reales entrados por terminal, donde para finalizar la entrada de datos se
ingresa <retorno> sin dato.

m
a) Observar el uso de “ eoln ” para detectar que se ha entrado un <retorno>

o
sin dato, y el uso de “ readln ” para “leer” el fin de lı́nea que ha quedado.

.c
¿Qué pasa si se elimina este “ readln ”?

os
b) Modificar el programa para que a la salida indique también la cantidad de
datos ingresados.
r
fo
c) Modificar el programa para que también indique el promedio de los datos
.m
entrados. ✄
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 5

Aplicaciones

m
En este capı́tulo mostramos que se puede hacer bastante matemáticas con
los recursos de programación que hemos visto.

o
.c
os
5.1. Cálculo numérico elemental
r
fo
Ciertamente una de las aplicaciones más importantes de la computadora —y
a la cual debe su nombre— es la obtención de resultados numéricos. A veces es
.m

sencillo obtener los resultados deseados pero a veces —debido a lo complicado


del problema o a los errores numéricos— es sumamente complicado, lo que ha
na

dado lugar a toda un área de matemáticas llamada “cálculo numérico”.


tu
on

5.1.1. Mezclando números grandes y pequeños


Comenzamos viendo que puede haber inconvenientes al sumar un número
yc

grande a uno pequeño, y de modo similar que al restar números grandes y


dm

parecidos, el resultado puede ser 0 aunque los números no sean iguales. Sor-
prendentemente, al trabajar con números de tipo “ real ” pueden pasar cosas
curiosas como:
.a
w

• que la suma no sea conmutativa,


w

• o que la diferencia sea 0 aún cuando los números son distintos.


w

Por supuesto, ya hemos visto este tipo de comportamiento al calcular εmaq


(problema 4.14), debido fundamentalmente a la densidad variable de números
de tipo “ real ” mencionada en el capı́tulo 3 (pág. 14).

Problema 5.1.
a) Encontrar x de tipo real tal que, para la máquina, ¡x = x + 1!.
Sugerencia: buscar una potencia de 2, usando algo como
x := 1; repeat x := 2 * x; y := x + 1 until (x = y)

b) ¿Qué relación hay entre x y 1/εmaq (e.g. si x × εmaq es una potencia de


2)? ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 36 Aplicaciones

Problema 5.2 (Números armónicos). Para n ∈ N, el n-ésimo número


armónico se definen como
1 X1
n
1 1
Hn = 1 + + + ···+ = .
2 3 n i=1
i

Ası́, H1 = 1, H2 = 3/2, H3 = 11/6, H4 = 25/12, H5 = 137/60.


a) Desarrollar un programa (siguiendo las ideas del problema 4.11 sobre suma
de Gauss), para calcular Hn aproximadamente.
b) En este caso particular, puede demostrarse que es más preciso hacer la
suma “de atrás hacia adelante” que “de adelante hacia atrás”. Comprobar
que para n grande difieren los resultados realizando las sumas en estas dos
formas.
c) En los cursos de análisis o cálculo matemático se ve que a medida que n
crece, Hn crece indefinidamente como ln n.

m
Ver que efectivamente la diferencia Hn − ln n es aproximadamente cons-

o
tante para valores grandes de n (esta constante es la constante de Euler

.c
γ = 0.5772156649 . . .), por ejemplo calculando las diferencias para n =

os
100, 1000, 10000. ✄

r
El siguiente problema muestra algunos de los inconvenientes al restar canti-
fo
dades grandes y similares.
.m

Problema 5.3 (Problemas numéricos en la ecuación cuadrática). Como


es sabido, la ecuación cuadrática
na

ax2 + bx + c = 0 (5.1)
tu

donde a, b, c ∈ R son datos con a 6= 0, tiene soluciones reales si d = b2 − 4ac no


on

es negativo, y están dadas por


√ √
yc

−b + d −b − d
x1 = y x2 = . (5.2)
dm

2a 2a
a) Hacer un programa que, dados a, b y c, verifique si a 6= 0 y d ≥ 0, poniendo
.a

un aviso en caso contrario, y en caso afirmativo calcule x1 y x2 usando las


ecuaciones (5.2), y también las cantidades ax2i + bxi + c, i = 1, 2, viendo
w

cuán cerca están de 0.


w

b) Cuando d ≈ |b|, i.e. si |4ac|  b2 , pueden surgir inconvenientes numéricos.


w

Por ejemplo, calcular las raı́ces usando el programa del inciso anterior,
cuando a = 1, b = 10000 y c = 1, verificando si se satisface la ecuación (5.1)
en cada caso.
Nota: Las soluciones con varios dı́gitos son:

x1 = −0.00010000000100000 . . . y x2 = −9999.99989999999899999 . . .

c) Uno de los problemas en el ejemplo del inciso anterior surge de restar núme-
ros grandes que son comparables. El siguiente fragmento de programa alivia
un poco la situación:
if (b > 0) then x1 := -(b + sqrt(d))/(2 * a)
else x1 := (sqrt(d) - b)/(2 * a);
x2 := c / (x * a);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

5.1. Cálculo numérico elemental Pág. 37

i) Justificar esta construcción. Sugerencia: recordar que x1 + x2 = −b y


x1 x2 = c.
ii) Hacer un programa con estas modificaciones y comparar con los resul-
tados en b). ✄

5.1.2. Métodos iterativos: puntos fijos


Una de las herramientas más poderosas en matemáticas, tanto para aplica-
ciones teóricas como prácticas —en este caso gracias a la capacidad de repetición
de la computadora— son los métodos iterativos. Casi todas las funciones ma-
temáticas (salvo las elementales) como cos, sen, ln, etc., son calculadas por la
computadora mediante estos métodos.
Pero, ¿qué es iterar ?: repetir una serie de pasos. Por ejemplo, muchas calcu-
ladoras elementales pueden calcular la raı́z cuadrada del número que aparece en
el visor. En una calculadora con esta posibilidad, ingresando cualquier número

m
(positivo), y apretando varias veces la tecla de “raı́z cuadrada” puede observarse
que rápidamente se converge al mismo número, independientemente del valor

o
.c
ingresado inicialmente.
Como a lo mejor no tenemos disponible la calculadora, pero sı́ la computa-

os
dora y Pascal, hagamos el trabajo:
Problema 5.4 (Punto fijo de la raı́z cuadrada).
r
fo
a) Utilizando la construcción
.m

y := x; for i := 1 to n do y := sqrt(y)
na

hacer un programa que ingresando x ∈ R+ y n ∈ N, calcule


sr
tu

q
√ n
on

··· x (= x1/2 ).
| {z }
yc

n raı́ces

b) Ejecutar el programa para distintos valores de x (positivo) y n (más o menos


dm

grande dependiendo de x). ¿Qué se observa? ✄


.a

Como el lector habrá comprobado en el problema anterior, a medida que


aumentamos las iteraciones, i.e. el valor de n, nos aproximamos cada vez más a
w

1. Por supuesto que si empezamos


√ con x = 1, obtendremos siempre el mismo 1
w

como resultado, ya que 1 = 1.


w

En general, si tenemos una función f , un punto x en el rango de f se dice


punto fijo de f si
f (x) = x,

de modo que 1 es un punto fijo de la función f (x) = x.
Problema 5.5. ¿Hay algún otro punto fijo de la raı́z cuadrada, además de
x = 1? ✄
En lo que resta de la sección trabajaremos con funciones continuas, es decir,
funciones que “pueden dibujarse sin levantar el lápiz del papel”.√Ejemplos de
funciones continuas son: cualquier polinomio P (x), |x|, cos x, y x (para x >
0) que acabamos de ver. Para nosotros será suficiente esta idea intuitiva: la
definición rigurosa de función continua se da en los cursos de análisis o cálculo
matemático.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 38 Aplicaciones

Nota: Por supuesto, hay funciones continuas que “no se pueden dibujar” como
1/x para x > 0, pues cerca de x = 0 se nos acaba el papel,
sen 1/x para x > 0, pues cerca de x = 0 oscila demasiado,
(
x sen 1/x si x 6= 0,
f (x) = pues cerca de x = 0 oscila demasiado.
0 si x = 0,

y hay funciones que no son continuas como

signo(x) para x ∈ R, pues pega un “salto” en x = 0,


(
1 si x ∈ Q,
f (x) = una función imposible de visualizar.
0 si x ∈ R \ Q,

Otra forma de describir que una función es continua es decir que a cambios
“pequeños” en x corresponden cambios “pequeños” en f (x), aunque la “pe-

queñez” relativa sea muy distinta (por ejemplo, f (x) = 3 x es continua, pero

m
si cambiamos de 0 a 0.001 en x, hay sólo un cambio de 0.1 de f (0) a f (0.001)).

o
Tener en cuenta también que puede ser que a pequeños cambios de f (x)

.c
pueden no corresponder pequeños cambios en x. Por ejemplo una función cons-
tante es continua.

os
Muchas funciones de interés interactúan de manera interesante con sus pun-

r
tos fijos como en el caso de la raı́z cuadrada: supongamos que x0 es un punto
fo
dado y definimos
.m

x1 = f (x0 ), x2 = f (x1 ), . . . , xn = f (xn−1 ), . . .


na

y supongamos que tenemos la suerte que xn se va aproximando a ` a medida


que n crece, i.e.
tu

xn ≈ ` cuando n es muy grande.


on

Puede demostrarse entonces que, si f es continua, ` es un punto fijo de f .


yc

Nota: Veamos esta afirmación.


Si xn se va aproximando a ` a medida que n crece, cuando n es muy grande
dm

n + 1 también es muy grande y xn+1 se parece a `, de modo que tendremos

xn ≈ ` y xn+1 = f (xn ) ≈ ` cuando n es muy grande.


.a
w

Como suponemos que a medida que n crece la aproximación de xn a ` es


cada vez mejor, por la continuidad de f debe ser f (xn ) cada vez más parecido
w

a f (`), y dado que xn+1 = f (xn ) tendremos


w

f (`) = `,

es decir, ` es un punto fijo de f .


Nota: A veces las iteraciones sucesivas “se disparan al infinito”, otras veces
pueden tener comportamientos cı́clicos, o en fin, como mostramos en el pro-
blem 5.9.f ), pueden oscilar sin ninguna ley aparente.
Por ejemplo, supongamos que queremos encontrar x tal que cos x = x. Mi-
rando el gráfico de la figura 5.1, vemos que efectivamente hay un punto fijo de
f (x) = cos x, i.e. un punto x∗ tal que f (x∗ ) = x∗ . Podemos apreciar en el gráfi-
co que el punto buscado está entre 0.5 y 1, y probamos la técnica mencionada:
dado x0 ∈ R definimos

xn+1 = cos xn para n = 0, 1, 2, . . .,

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

5.1. Cálculo numérico elemental Pág. 39

y
y=x
1

0.5 y = cos x

x
0.5 1 1.5

Figura 5.1: gráficos de y = cos x y y = x.

y tratamos de ver si xn se aproxima a algún punto cuando n crece.


En la figura 5.2, vemos cómo a partir de x0 = 0, nos vamos aproximando al

m
punto fijo, donde los trazos horizontales van desde puntos en el gráfico de f a
la diagonal y = x y los verticales vuelven al gráfico de f :

o
.c
y

os
1

r
fo
0.5
.m
na

x
0.5 1 1.5
tu
on

Figura 5.2: aproximándose al punto fijo de cos x.


yc
dm

Problema 5.6 (Punto fijo de f (x) = cos x). Con las notaciones anteriores
para f y xi :
.a

a) Hacer un programa que dados x0 y n calcule xn , y observar el comporta-


w

miento para distintos valores de x0 y n. Sugerencia: usar un lazo “ for ”


como en el problema 5.4.
w

b) Modificar el programa para que también imprima cos xn y comprobar que


w

para n más o menos grande se tiene xn ≈ cos xn .


c) Modificar el programa para hacer 200 iteraciones, mostrando los resultados
intermedios cada 10. Observar que después de cierta cantidad de iteraciones,
los valores de xk no varı́an.
Nota: El valor de x200 es una buena aproximación al único punto fijo de
f (x) = cos x, aún cuando puede ser que x200 6= cos x200 (= x201 ) debido a
errores numéricos. El “verdadero” punto fijo es 0.7390851332 . . ..
d ) Modificar el programa de modo que no se hagan más iteraciones si

|xk+1 − xk | < ε,

donde ε > 0 es un parámetro entrado por el usuario (e.g. ε = 0.00001). ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 40 Aplicaciones

La importancia de los puntos fijos es que al encontrarlos estamos resolviendo


la ecuación f (x) − x = 0. Ası́, si nos dan la función g y nos piden encontrar una
raı́z de la ecuación g(x) = 0, podemos definir f (x) = g(x) + x o f (x) = x − g(x)
y tratar de encontrar un punto fijo para f .
Por ejemplo, π es una raı́z de la ecuación tan x = 0 (tan x es la tangente
de x), y para obtener un valor aproximado de π podemos tomar g(x) = tan x,
f (x) = x − tan x, y usar la técnica anterior, como hacemos en el siguiente
problema:
Problema 5.7.
a) Encontrar los puntos fijos de f (x) = x − tan x, i.e. los x’s para los que
f (x) = x, en términos de π. Ver que π es uno de los infinitos puntos fijos.
b) Hacer un programa para calcular π iterando f , dando como entradas un
punto inicial y la cantidad de iteraciones (en Pascal no está definida la
función tan, habrá que usar tan = sen / cos).

m
c) Verificar que con 3 o 4 iteraciones se obtiene una muy buena aproximación

o
de π comenzando desde x0 = 3.

.c
d ) Sin embargo, si empezamos desde x0 = 1, nos aproximamos a 0, otro punto

os
fijo de f .
e) f (π/2) no está definida, y es previsible encontrar problemas cerca de este
r
fo
punto. Como π/2 ≈ 1.5708, hacer una tabla de los valores obtenidos después
de 10 iteraciones, comenzando desde los puntos 1.5, 1.51, . . . , 1.6 (desde 1.5
.m

hasta 1.6 en incrementos de 0.01) para verificar el comportamiento de la


na

función.
f ) Si en vez de usar f (x) = x − tan x usáramos f (x) = x + tan x, los resultados
tu

del inciso a) no varı́an. Hacer los incisos b) y c) con esta variante y verificar

on

si se obtienen resultados similares.


yc

5.1.3. El método babilónico


dm

El problema 5.7 nos muestra que aun cuando un método iterativo nos lleve a
encontrar un punto fijo, no siempre es el que buscamos, salvo que empecemos con
.a

un valor cercano “razonable”. Por otra parte, nos


√ queda la duda de cómo hace la
máquina para calcular funciones como “tan” o x. En general, estas funciones se
w

calculan, con suficiente aproximación, mediante variantes del método de Newton-


w

Raphson que se estudia en los cursos de Cálculo Numérico.


w

La técnica de punto fijo para encontrar raı́ces de ecuaciones no surgió con


las computadoras. Por ejemplo, el método que estudiaremos a continuación es
una versión actualizada de una técnica usada por los babilónicos hace miles de
años para aproximar a la raı́z cuadrada:
Problema 5.8 (Método babilónico I). Este método para aproximar la raı́z
cuadrada de a ∈ R+ , consiste en aplicar sucesivamente las iteraciones
1 a 
xn+1 = xn + para n = 1, 2, . . . , (5.3)
2 xn
a partir de un valor inicial x0 dado (x0 > 0). Es decir xn+1 = fa (xn ), donde
fa : R+ → R+ está definida como
1 a
fa (x) = x+ .
2 x

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

5.1. Cálculo numérico elemental Pág. 41

El método era conocido por los babilonios (entre 2000 y 200 años a. de C.),
quienes usaron ideas geométricas para aproximar la media geométrica de los
números x y a/x, r
a
x× ,
x

que es el número a buscado, por su media aritmética (o promedio),
1 a
x+ .
2 x
Mucho más tarde, Isaac Newton (1642–1727) desarrolló un método basado
en el análisis matemático para encontrar raı́ces de funciones mucho más gene-
rales, que tiene como caso particular al método babilónico. El método de Newton
también es conocido como de Newton-Raphson, pues J. Raphson (1648–1715)
publicó este resultado unos 50 años antes que se publicara el de Newton.
a) Probar que si a > 0 y x > 0, entonces
√ fa (x) > 0, y si fa (x) = x (x es un
punto fijo de fa ) entonces x = a.

m
b) En particular, si x0 > 0 y definimos√xn como en la ecuación (5.3), entonces

o
xn > 0 para √todo n ∈ N, y si xn = a para algún n, entonces xn = xn−1 =

.c
· · · = x0 = a. Por lo tanto, si suponemos precisión
√ “infinita” el método

os
nunca da la respuesta exacta salvo que x0 = a.
c) Bosquejar un gráfico similar al segundo del problema 5.6, cuando a = 2 y
r
fo
x0 = 1.
.m
d ) ¿Qué pasa si a = 0? ¿Y si x0 = 0?
e) ¿Qué pasa si x0 < 0 y a > 0?
na

f ) ¿Qué pasa si a < 0 y x0 ∈ R? ✄


tu

Problema 5.9 (Método babilónico II). El programa babilonico (pág. 167) es


on

una versión para aplicar el método babilónico estudiado en el problema anterior.


a) Correr el programa para distintos valores de a, y luego modificarlo para
yc

comparar el resultado con el valor dado por “ sqrt(a) ”.


dm

b) En el programa presentado, el valor inicial x0 , el número máximo de itera-


ciones y la tolerancia están dados como constantes. Modificar el programa
.a

para que estos datos puedan ser entrados interactivamente.


c) Uno de los “criterios de parada” usados en el programa es que la diferencia
w

de dos valores consecutivos “en x” sea suficientemente pequeña. Agregar


w

también un criterio para parar si la diferencia “en y”, |x2 − a|, es suficien-
w

temente pequeña, con tolerancia eventualmente distinta a la anterior.


d ) Modificar el programa para que imprima el criterio que se ha usado para
terminar. Ver cuál es este criterio cuando x0 = 1 y ambas tolerancias son
10−5 , para a = .0001, .01, 100, 10000.
e) En el programa se √consideran errores absolutos √en vez de relativos. Por
ejemplo, aproximar 2 es equivalente a aproximar 200, sólo hay que correr
en un lugar la coma decimal en la solución, pero en el programa hemos
tomado la misma tolerancia para las diferencias.
i) Verificar el comportamiento del programa para los valores a = 2 y
a = 200, tomando el mismo valor inicial y tolerancias.
ii) Modificar el programa de modo que internamente se aproximen las
raı́ces cuadradas sólo de números entre 1 y 100, pero que después el

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 42 Aplicaciones

resultado escrito como salida sea el correspondiente al número original


(suponiendo siempre a > 0).
Nota: Este procedimiento de normalización o escalado es esencial en cálcu-
lo numérico: trabajar con papas y manzanas y no con átomos y planetas1 .
Cuando se comparan papas con manzanas, se tiene en cuenta el valor de
εmaq .
f ) Verificar el comportamiento del programa en los casos de los incisos d ), e)
y f ) del problema 5.8.
Nota: En el caso a < 0 se observa un comportamiento caótico (que se
puede definir matemáticamente). Si convergiera a algún número ` ∈ R,
éste serı́a la raı́z cuadrada de un número negativo, lo cual es absurdo. ✄

5.2. Números enteros y divisibilidad

m
No sólo podemos obtener resultados numéricos con la computadora, sino que
podemos inferir resultados teóricos experimentando:

o
.c
Problema 5.10.

os
a) Hacer un programa para calcular la suma de los primeros n números impares
positivos (n ∈ N),
r
fo
X
n
.m

tn = 1 + 3 + · · · + (2n − 1) = (2k − 1).


k=1
na

b) Obtener el valor para distintos valores de n, y conjeturar una fórmula similar


tu

a la de Gauss, sn = n×(n+1)
2 (ver el problema 4.11).
on

c) Tratar de demostrar la fórmula. Sugerencia: usar la fórmula de Gauss o, si


se conoce el método, usar inducción. ✄
yc
dm

5.2.1. Ecuaciones diofánticas


Problema 5.11. Geri y Guille compraron botellas de vino para la reunión.
.a

Ambos querı́an quedar bien y Guille gastó $5 por botella, mientras que Geri,
w

que es más sibarita, gastó $8 por botella. Si entre los dos gastaron $81, ¿cuántas
w

botellas compró cada uno?


w

a) Hacer un programa para resolver el problema. Sugerencia: se trata de re-


solver la ecuación 5x + 8y = 81, con x, y ∈ Z, x, y ≥ 0. Por lo tanto,
0 ≤ x ≤ b 81
5 c. Recorrer los valores de x posibles, x = 0, 1, . . . , 16, buscando
y ∈ Z, y ≥ 0.
Nota: Hay dos soluciones posibles.
Nota: En este caso es más eficiente recorrer los valores de y (desde 0 hasta
b 81
8
c = 10) pues hay menos valores a considerar.
b) Generalizar el programa para resolver ecuaciones de la forma ax + by = c,
donde a, b, c ∈ N son dados por el usuario y x, y son incógnitas enteras no
negativas. El programa debe exhibir todas los pares (x, y) de soluciones o
decir que no hay soluciones posibles.
1 O, más cientı́ficamente, con magnitudes del mismo orden.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

5.2. Números enteros y divisibilidad Pág. 43

Nota: La estrategia de resolución es recorrer todas las posibilidades, pero hay


algoritmos más eficientes para este caso basados en que el máximo común
divisor entre a y b, mcd(a, b), puede escribirse como mcd(a, b) = Aa + Bb para
A, B ∈ Z, y que valores apropiados de A y B pueden obtenerse mediante el
algoritmo de Euclides, que veremos luego. Los detalles se ven en cursos de
matemática discreta o teorı́a elemental de números. ✄
Diofanto de Alejandrı́a (alrededor de 250 d. de C.) fue el primero en es-
tudiar sistemáticamente problemas de ecuaciones con soluciones enteras. En
su honor, las ecuaciones donde las incógnitas son enteros, como en el proble-
ma 5.11, se llaman diofánticas o diofantinas.
Otras ecuaciones diofánticas bien conocidas son las de la forma xn + y n =
z , donde las incógnitas son x, y, z ∈ N y n ∈ N está dado. Cuando n = 2, las
n

soluciones (x, y, z) forman una terna pitagórica, y cuando n > 2, tenemos la


célebre ecuación de Fermat-Wiles, que no tiene soluciones.
Problema 5.12. Hacer un programa para que dado n ∈ N determine si existen

m
enteros no-negativos x, y tales que x2 + y 2 = n, exhibiendo en caso positivo
un par de valores de x, y posibles, y en caso contrario imprimiendo un cartel

o
adecuado. ✄

.c
os
5.2.2. Máximo común divisor y el algoritmo de Euclides
r
Dados a, b ∈ N, el máximo común divisor entre a y b, mcd(a, b), se define2
fo
como el máximo elemento del conjunto de divisores comunes de a y b:
.m

mcd(a, b) = máx D,
na

donde
tu

D = {d ∈ Z : d | a y d | b}.
on

Nota: D = 6 ∅, pues 1 ∈ D. Además, D está acotado superiormente —por


mı́n{a, b}—, por lo que mcd(a, b) está bien definido.
yc

Cuando a, b ∈ Z, definimos
dm

mcd(a, b) = mcd(|a|, |b|)


y
.a

mcd(0, z) = mcd(z, 0) = |z| para todo z ∈ Z, z 6= 0.


w

No tiene mucho sentido mcd(0, 0), pero por comodidad ponemos


w

mcd(0, 0) = 0.
w

Cuando mcd(a, b) = 1, es usual decir que los enteros a y b son primos entre
sı́ o coprimos.
En la escuela elemental a veces se enseña a calcular el máximo común divisor
efectuando primeramente la descomposición en primos. Sin embargo, la facto-
rización en primos es computacionalmente difı́cil3 y en general bastante menos
eficiente que el algoritmo de Euclides, que aún después de 2000 años, es el más
indicado (con pocas variantes) para calcular el máximo común divisor.
Euclides (alrededor de 300 a. de C.) escribió una serie de libros de enorme
influencia en las matemáticas, aún las actuales. En la proposición 1 del libro VII,
se enuncia:
2 ¡Como su nombre lo indica!
3 Ver también las notas en la pág. 46.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 44 Aplicaciones

Dados dos números distintos, y restando sucesiva y contı́nuamente


el menor del mayor, si el número que queda nunca mide el anterior a
él hasta que queda una unidad, los números originales serán primos
entre sı́.

Y en la proposición 2,

Dados dos números y no primos entre sı́, encontrar su máxima me-


dida común.

procediendo a construirlo. En lenguaje moderno, nos dice que para encontrar


el máximo común divisor, “la medida común”, entre a y b, debemos restar el
menor del mayor hasta que los dos sean iguales.
Nota: Por supuesto que Euclides con “números” se refiere a lo que nosotros
llamamos números naturales. En los tiempos de Euclides se pensaba en números

m
como longitudes de segmentos, y la multiplicación de números es un área. Un
poco
√ antes de Euclides, con Pitágoras y el descubrimiento de la irracionalidad

o
de 2, surgió el problema de la conmensurabilidad de segmentos, es decir si

.c
dados dos segmentos de longitudes a y b existe otro de longitud c tal que √a y

os
b son múltiplos enteros de c, i.e. c = mcd(a, b). Si a es irracional, como 2, y
b = 1, entonces no existe c, y el algoritmo de Euclides no termina.
r
fo
Problema 5.13 (Algoritmo de Euclides). El programa euclides (pág. 168)
.m
es una versión de este algoritmo para encontrar mcd(a, b) cuando a, b ∈ Z, cam-
biando las restas sucesivas por el cálculo del resto mediante la función “ mod ”.
na

Nota: En el problema 4.9 hemos hecho el proceso inverso: para calcular el resto
hicimos restas sucesivas.
tu

a) Teniendo en cuenta la descripción original del algoritmo, ¿serı́a correcto


on

cambiar el lazo principal por


yc

(* lazo principal *)
repeat
dm

if (a > b) then a := a - b else b := b - a


until (a = b); ?
.a

¿Y por
w

(* lazo principal *)
w

repeat
w

if (a > b) then a := a - b
else if (a < b) then b := b - a
until (a = b); ?
Explicar en cada caso.
b) Ver que el programa termina en un número finito de pasos, por ejemplo en
no más de |a| pasos.
Nota: Observar que en el método babilónico (sección 5.1.3), hemos debido
poner “criterios de parada”, pero no aquı́.
c) Modificar el programa de modo que a la salida escriba la cantidad de veces
que realizó el lazo “ while ”.
d ) ¿Qué pasa si se cambia la instrucción “ while (b <> 0) do ” por “ while
(b > 0) do ”?

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

5.2. Números enteros y divisibilidad Pág. 45

e) ¿Qué pasa si se eliminan los renglones donde se consideran los casos a < 0
y b < 0?
f ) ¿Y si se hacen los cambios d ) y e) simultáneamente?
g) Modificar el programa de modo que a la salida escriba también los valores
originales de a y b, e.g. “ El máximo común divisor entre 12 y 8 es 4 ”
si las entradas son a = 12 y b = 8.
h) Una de las primeras aplicaciones del mcd es “simplificar” números raciona-
les, e.g. escribir 12 3
8 como 2 . Hacer un programa que dados los enteros p y q,
con q 6= 0, encuentre m ∈ Z y n ∈ N de modo que pq = m n y mcd(m, n) = 1.
Nota: ¡Atención con los signos de p y q! ✄
Problema 5.14. Pablito y su papá caminan juntos tomados de la mano. Pa-
blito camina 2 metros en exactamente 5 pasos, mientras que su padre lo hace en
exactamente 3 pasos. Si empiezan a caminar juntos, ¿cuántos metros recorrerán
hasta marcar nuevamente el paso juntos?, ¿y si el padre caminara 2 21 metros

m
en 3 pasos? Aclaración: Se pregunta si habiendo en algún momento apoyado si-

o
multáneamente los pies izquierdos, cuántos metros después volverán a apoyarlos

.c
simultáneamente.

os
Nota: Recordando el tema de la conmensurabilidad mencionado al introducir
el algoritmo de Euclides, no siempre el problema
√ tiene solución. Por ejemplo,
r ✄
fo
si Pablito hace 1 metro cada 2 pasos y el papá 2 metros cada 2 pasos.
Problema 5.15. El mı́nimo común múltiplo de a, b ∈ N, mcm(a, b), se define
.m

en forma análoga al máximo común divisor: es el menor entero del conjunto


na

{k ∈ N : a | k y b | k}.
a) Demostrar que si a, b ∈ N entonces mcm(a, b) × mcd(a, b) = a × b.
tu

b) Escribir un programa para calcular mcm(a, b) para a, b ∈ N.


on

c) ¿Cómo podrı́a extenderse la definición de mcm para a, b ∈ Z? ¿Cuál serı́a



yc

el valor de mcm(0, z)?


Problema 5.16.
dm

a) ¿Qué relación hay entre el máximo común divisor o el mı́nimo común múlti-
.a

plo con el problema 5.14 (de Pablito y su papá)? Sugerencia: recordando


que el problema no siempre tiene solución, volver a mirar la descripción
w

dada por Euclides de su algoritmo pensando en números cualesquiera, res-


w

tando el mayor del menor hasta llegar a una “medida común”, y quizás el
w

inciso h) del problema 4.9 (pág. 27).


b) Hacer un programa para resolver ese problema, donde las entradas son el
número de pasos y la cantidad de metros recorridos tanto para Pablito como
para su papá.
Nota: Como para la computadora todos los números son racionales, el
problema siempre tiene solución. ✄

5.2.3. Números primos


Un número n ∈ N es primo si n > 1 y sus únicos divisores (positivos) son 1
y él mismo. Los primeros primos son 2, 3, 5, 7 y 11.
Nota: Algunos autores consideran que −2, −3, etc., también son primos, pero
nosostros los consideraremos siempre mayores que 1.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 46 Aplicaciones

También en sus libros Euclides demuestra muchos teoremas importantes so-


bre números primos: en el Libro VII, Proposiciónes 31 y 32, que todo número
se puede escribir como producto de primos, y en el Libro IX, Proposición 20
—con una de las demostraciones más hermosas de las matemáticas— que hay
infinitos primos.
Nota: Recordemos esta demostración “por el absurdo”: supongamos que hay
un número finito de primos, p1 , p2 , . . . , pk . Entonces el entero

a = p1 × p2 × · · · × pk

debe tener algún factor primo que lo divida, digamos pi (para algún i entre 1
y k) y tendremos

pi | a y pi | ap1 × p2 × · · · × pk ,

por lo que pi divide a la diferencia, i.e. pi | 1, lo que es un absurdo.

m
Problema 5.17.

o
a) Probar que si el entero a > 1 no es√primo, entonces existen enteros b y c,

.c
√ que 1, tales que b ≤ a y a = b × c. Sugerencia: si a = b × c
ambos mayores
y 1 < b, c < a, entonces b × c < . . .

os
b) Usando el resultado anterior, hacer un programa que determine si el en-
r
fo
tero a > 1√entrado por terminal es primo o no, viendo si los números
2, 3, 4, . . . , b ac lo dividen o no. ✄
.m

Problema 5.18. El programa factoresprimos (pág. 169) es una elaboración del


na

programa en el problema 5.17, e imprime todos los factores primos del número
a > 1 entrado por terminal.
tu

a) Ver que efectivamente el programa determina todos los factores primos cuan-
on

do a > 1.
yc

b) Sin embargo, cuando a = 1 o 0 el programa retorna a, que no es primo.


c) ¿Qué pasa cuando a < 0?
dm

d ) Como el programa busca factores primos y el único primo par es 2, modificar


el programa de modo de primero eliminar los factores 2, y luego a partir de
.a

b = 3, incrementar b de a dos en vez de a uno.


w

e) Modificar el programa de modo que no imprima repetidas veces el mismo


w

factor primo, sino que imprima el primo seguido de su multiplicidad. Por


ejemplo, si a = 456 = 23 × 3 × 19, se imprima
w

Factor Multiplicidad
2 3
3 1
19 1

Nota: Como ya mencionamos, el problema de encontrar los factores primos de


un entero es un problema muy difı́cil. Esta dificultad es aprovechada por la
criptografı́a, por ejemplo para enviar los códigos de banco por internet, como
se ve en cursos de teorı́a elemental de números y sus aplicaciones.
Curiosamente, el problema de decidir si un número es primo o no es mucho
más sencillo: en 2002, M. Agrawal, N. Kayal y N. Saxena probaron que existe
un algoritmo polinomial para este problema.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

5.3. Problemas Adicionales Pág. 47

Problema 5.19 (La función φ de Euler). Para n ∈ N, se define la función


φ de Euler como la cantidad de coprimos menores que n (y positivos), es decir,

φ(n) = #({m ∈ N : 1 ≤ m ≤ n y mcd(m, n) = 1}).

a) Probar que n ∈ N es primo ⇔ φ(n) = n − 1.


b) Desarrollar un programa que dado n ∈ N calcule φ(n).
c) Calcular φ(n) para varios valores de n, y después conjeturar y demostrar
para cuáles valores de n φ(n) es par. Sugerencia: para la demostración,
mcd(n, k) = 1 ⇔ mcd(n − k, n) = 1. ✄

5.3. Problemas Adicionales


Problema 5.20. Consideremos la sucesión
 1 n

m
an = 1 + , para n ∈ N.

o
n

.c
a) Hacer un programa para generar an dado n, y usarlo para varios valores de n.

os
En base a la experiencia, ¿podrı́as decir que an es creciente (i.e. si an < an+1
para todo n ∈ N) o decreciente?, ¿podrı́as demostrar esta propiedad?
r
fo
b) En los cursos de Análisis Matemático se demuestra que a medida que n
.m
aumenta, an se parece cada vez más a e = 2.718281828459 . . .. Modificar el
programa del inciso anterior para ver este comportamiento, donde el usuario
na

entra la tolerancia ε deseada, e.g. ε = .001, y el programa dice el primer


valor de n para el cual |an − e| < ε.
tu

Nota: Para valores de ε pequeños, es muy posible que n supere maxint,


on

por lo que hay que tener cuidado en la implementación. ✄


Problema 5.21. En los partidos de rugby se consiguen tantos mediante tries
yc

(5 tantos cada try), tries convertidos (7 tantos cada uno) y penales convertidos
dm

(3 tantos cada uno).


Hacer un programa que ingresando la cantidad total de puntos que obtuvo
.a

un equipo al final de un partido, determine las formas posibles de obtener ese


resultado.
w

Por ejemplo, si un equipo obtuvo 21 tantos, las formas posibles son:


w

Posibilidad Tries Tries convertidos Penales


w

1 0 0 7
2 0 3 0
3 1 1 3
4 3 0 2 ✄

Problema 5.22 (Visibilidad en el plano). Consideremos el reticulado C


definido por los puntos del plano con ambas coordenadas enteras. Diremos que
el punto P ∈ C es visible (desde el origen O) si el segmento que une P y O no
contiene otros puntos de C distintos de P y O.
a) Si a, b ∈ N, entonces (a, b) es visible ⇔ mcd(a, b) = 1.
b) Desarrollar un programa que dado n ∈ N calcule sn , la cantidad de puntos
visibles en el cuadrado 1 ≤ a, b ≤ n.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 48 Aplicaciones

c) Se puede demostrar que a medida que n crece, pn = sn /n2 se aproxima


a p = 6/π 2 = 0.6079 . . . . Calcular pn para distintos valores de n para ver
que este es el caso. Sugerencia: empezar con n pequeño e ir aumentando
paulatinamente su valor. ✄

Problema 5.23 (El juego de Euclides). En un tablero, cuyas casillas su-


ponemos identificadas como (x, y), con x, y enteros no negativos, se coloca una
ficha en la posición (a, b) con a, b 6= 0.
Dos jugadores juegan alternativamente, moviendo la ficha desde la posición
(x, y), a una casilla de la forma (x − ty, y) o (x, y − tx), según si x ≥ y o y ≥ x,
donde t ∈ N es elegido por el jugador pero es tal que las nuevas coordenadas
son ambas no negativas.
Gana el jugador que primero llegue a una posición donde en la que alguna
de las coordenadas es 0.
a) Ver que cualquier sucesión de movimientos que empieza en (a, b) debe ter-

m
minar en el par (0, mcd(a)b) (o su simétrico).

o
b) Si y < x ≤ y(1 + 5)/2, hay un único movimento √ posible desde (x, y).

.c
Además, si se llega a (z, y), debe ser y > z(1 + 5)/2.

os
c) Ver que si un juego empieza en (a, b) y a ≥ b,√el primer jugador tiene una
estrategia ganadora si a = b o si a > b(1 + 5)/2. En caso contrario, el
r
segundo jugador tiene una estrategia ganadora.
fo
d ) Desarrollar un programa para jugar el juego de Euclides en el que:
.m

i) La posición inicial es entrada por el jugador humano.


na

ii) Luego juegan alternativamente la computadora (óptimamente) y el


usuario.
tu

iii) La computadora muestra su movimiento por pantalla y el usuario in-


on

gresa su movida por teclado.


iv ) La computadora verifica que el movimiento propuesto por el usuario
yc

es válido, y vuelve a requerir una entrada válida en caso contrario. ✄


dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 6

Arreglos

m
A veces queremos tener muchas variables del mismo tipo, por ejemplo una
lista de los 100 primeros números primos. Escribir un nombre para cada una de

o
las variables ya serı́a engorroso, pero ¿qué pasa si ahora el problema requiere una

.c
lista de 1000 en vez de 100 datos? Para estos casos es conveniente usar una es-

os
tructura similar a la de vector, que podrı́amos pensar como v = (v1 , v2 , . . . , vn ),
para guardar los datos. En programación, esta estructura se llama arreglo (uni-
r
fo
dimensional).
.m

6.1. Dimensionamiento de arreglos


na

En presencia de un arreglo, la máquina guarda lugares consecutivos de me-


tu

moria, todos del mismo tipo, a los cuales se accede mediante ı́ndices (como
on

un vector) poniendo “ v[i] ” en vez de vi en el programa fuente. Puesto que


ocupan un lugar de memoria, y este lugar depende del tipo de sus elementos
yc

y de la cantidad o dimensión, debemos hacer una declaración apropiada en el


encabezamiento del programa.
dm

Si queremos usar un arreglo de enteros v, de dimensión 100, hacemos la


declaración
.a

v: array[1..100] of integer;
w

La parte “ 1..100 ” indica el rango donde se moverán los ı́ndices. A diferencia


w

de los vectores de matemática o fı́sica, cuyos ı́ndices siempre empiezan con


w

1, podemos hacer que los ı́ndices sean negativos, poniendo el rango adecuado
(por ejemplo “ -30..20 ”), aunque nosotros casi siempre usaremos arreglos con
ı́ndices que empiezan en 1 o 0.
Problema 6.1. En el programa 6.1 (pág. 170) entramos números enteros y al
terminar el ingreso de datos se imprime la cantidad de datos que terminaron en
0, 1, . . . , 9. Observemos que:
• En vez de declarar un contador para cada dı́gito 0, 1, . . . , 9, declaramos el
arreglo terminaen que nos provee de los 10 contadores necesarios, con ı́ndices
entre 0 y 9.
• El uso del arreglo no sólo facilita la declaración, sino que después es mucho
más sencillo incrementar el contador pues no tenemos que hacer una serie de
if’s para determinar el contador adecuado.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 50 Arreglos

• Al comenzar el programa los valores del arreglo no están definidos, y hay que
inicializarlos.
• El ingreso de datos está tomado del programa sumardatos (pág. 167), excepto
que al entrar un dato se determinar el dı́gito de las unidades del dato, d, y se
incrementa el contador correspondiente.
¿Por qué no se pone directamente “ digito := dato mod 10 ” en vez de
“ digito := abs(dato) mod 10 ” para determinar el dı́gito? ✄

6.2. Búsqueda Lineal


Muchas veces necesitaremos un arreglo con, digamos, n elementos, pero no
conocemos n de antemano, y es muy fácil cometer errores en la programación
porque podrı́amos tratar de usar una componente que no existe. Por ejemplo si
declaramos “ a: array[1..10] of integer ”, serı́a un error poner “ a[0] :=

m
1 ” o “ a[20] := 0 ”.

o
Nota: Algunos compiladores pueden agregar instrucciones de modo de compro-

.c
bar que cuando se corra el programa se verifique que los ı́ndices están dentro

os
del rango permitido. Por supuesto, esto hace que la ejecución del programa sea
más lenta.

r
En estos casos tratamos de hacer la declaración para guardar d elementos,
fo
con d prudentemente más grande que el rango con los que pensamos trabajar.
.m

Claro que si hemos declarado el arreglo con d = 100 elementos y usamos sólo
n = 20 lugares, estamos desperdiciando lugar.
na

Como el cambio de dimensión es frecuente, y sobre todo cuando hay muchos


arreglos con la misma dimensión, es usual declarar una constante, mediante
tu

“ const ”, para indicar las dimensiones y mantenerlas en un lugar destacado del


on

programa. Ası́, es conveniente hacer las declaraciones


yc

const MAXN = 20;


.
.
dm

.
var
.
.
.a

.
a: array[1..MAXN] of integer;
w

Si queremos usar un arreglo mayor, bastará cambiar el valor de MAXN .


w

Nota: Algunos compiladores viejos no admiten arreglos demasiado grandes. Por


w

otra parte, lenguajes más modernos que Pascal admiten el dimensionamiento


dinámico, evitando recompilar el programa.

Problema 6.2 (Búsqueda lineal). Una de las primeras aplicaciones de arre-


glos es ver si cierto elemento aparece o no en el arreglo. Más concretamente,
dados un arreglo a = (a1 , a2 , . . . , an ) y un objeto x (del mismo tipo que los
elementos de a), queremos ver si existe i, 1 ≤ i ≤ n, tal que x = ai .
El programa busquedalineal (pág. 170) muestra una posibilidad, donde se
ingresa un arreglo de ndatos enteros y el número x a buscar. El programa
decide con un lazo “ repeat ” si x es o no un elemento del arreglo recorriendo el
arreglo linealmente, es decir comparando sucesivamente a1 , a2 , . . . , andatos con x,
terminando cuando se encuentra coincidencia o cuando se han analizado todos
los elementos.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

6.2. Búsqueda Lineal Pág. 51

a) Observar que se leen datos como en el programa sumardatos (pág. 167),


donde el fin de la entrada de datos se señala con <retorno>. Como el
número de dato a ingresar es uno más que el número de datos ya ingresado,
se mantiene ndatos “adelantado” en 1, y al terminar se lo retrocede.
Sin embargo, al ingresar datos no se verifica si el número de datos leı́do
es ya MAXN . Modificar el lazo de modo de que esa constante sea tenida en
cuenta (impidiendo que se entren más de MAXN datos).
b) Manteniendo la modificación anterior, cambiar el programa para que en vez
de ingresar un arreglo de longitud máxima 20, se pueda ingresar un arreglo
de longitud máxima 50. Sugerencia: sólo habrá que cambiar un número.
c) Observar que en la impresión del arreglo inicial se usa la función “ mod ”
para imprimir un máximo de 5 datos por renglón.
i) ¿Por qué se pone el renglón
if ((ndatos mod 5) <> 0) then writeln;?

o m
ii) El 5 que indica la cantidad de datos por renglón se usa en dos lugares,

.c
de modo que si se cambia el 5 por otro número, hay que acordarse de
cambiar ambos.

os
Definir una constante, maxrenglon al principio del programa para
eliminar este problema.
r
fo
iii) Ahora modificar el programa de modo de imprimir 8 datos por renglón
.m
como máximo, cambiando el programa en un único lugar.
d ) Para buscar x en el arreglo, se usa un lazo “ repeat ”. Cambiarlo por uno
na

“ while ” (¡y que el programa siga funcionando correctamente!).


tu

e) Cambiar el lazo principal de modo que, de estar x en el arreglo, se obtenga


el último ı́ndice i tal que ai = x, en vez del primero como hace el programa.
on

Sugerencia: empezar recorriendo desde atrás.


yc

f ) ¿Podrı́a cambiarse la última parte del programa original por


dm

(* lazo principal *)
i := ndatos;
while ((a[i] <> x) and (i > 1)) do i := i - 1;
.a

(* resultados *)
w

if (x <> a[i]) then writeln(’no se encontro’)


w

else writeln(’se encontro en el lugar ’, i:1) ?


w

En caso negativo, decir por qué no, y en caso afirmativo, qué ventajas y
desventajas tendrı́a. Sugerencia: ¿cuál es el último valor de i si x no está en
el arreglo?
g) Modificar el lazo principal de modo que al terminar el programa diga cuántas
veces aparece x en el arreglo, y los ı́ndices correspondientes (i.e. los i para
los cuales ai = x).
Nota: Observar que el lazo principal cambia sustancialmente.
Sugerencia: una posibilidad es ir imprimiendo i a medida que aparece. Otra
posibilidad es agregar un arreglo para guardar los ı́ndices i, por ejemplo:
(* lazo principal *)
veces := 0;
for i := 1 to ndatos do

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 52 Arreglos

if (a[i] = x) then begin


veces := veces + 1;
indice[veces] := i
end;
y luego imprimir los resultados (entre 1 y veces si veces > 0). Observar que
en esta variante no es necesaria la variable seencontro. ✄

6.3. Cribas
Cuando debemos seleccionar en un arreglo todos los elementos que satisfacen
cierto criterio, y el criterio para cada elemento depende de los elementos que le
precedieron, es conveniente usar una criba (o cedazo o tamiz). Quizás la más
conocida de las cribas sea la atribuı́da a Eratóstenes para encontrar los números
primos entre 1 y n (n ∈ N), donde el criterio para decidir si un número k es

m
primo o no es la divisibilidad por los números que le precedieron.

o
Eratóstenes (contemporáneo de Arquı́medes y posterior a Euclides) nació en

.c
Cirene (en Libia de hoy) alrededor del 276 a. de C.. Vivió gran parte de su

os
vida en Alejandrı́a (Egipto), donde murió alrededor de 194 a. de C., después de
haber sido bibliotecario de la famosa biblioteca. Hizo importantes contribuciones

r
en matemáticas, astronomı́a, geografı́a y filosofı́a. Por ejemplo, fue el primero
fo
en medir correctamente la circunferencia de la Tierra (y pensar que Colón
.m
usaba un huevo, ¡1700 años después!).

Problema 6.3 (Criba de Eratóstenes). Supongamos que queremos encon-


na

trar todos los primos menores o iguales que un dado n ∈ N. Una posibilidad es
tu

verificar si cada uno de los números 2, 3, 4, . . . es primo, usando la técnica del


problema 5.17. Sin embargo, una forma mucho más eficiente es hacer la criba
on

de Eratóstenes.
La idea es empezar con la lista
yc

2 3 4 . . . n.
dm

Recuadramos 2, y tachamos los restantes múltiplos de 2 de la lista, quedando


.a

2 3 64 5 66 . . .
w
w

Ahora miramos al primer número que no está marcado (no tiene recuadro
w

ni está tachado): 3. Lo recuadramos, y tachamos de la lista todos los múltiplos


de 3 que quedan sin marcar. La lista ahora es

2 3 64 5 66 7 68 6 9 16 0 11 16 2 . . .

y seguimos de esta forma, recuadrando y tachando múltiplos hasta agotar la


lista.
a) Ver que el algoritmo es correcto, i.e. los números recuadrados que quedan
finalmente son todos los primos que no superan n.
b) Ver que el programa eratostenes (pág. 172) es una implementación de esta
idea.
c) ¿Qué pasa si en el lazo principal se elimina la variable i reemplazándola
directamente por p, poniendo “ for p := 2 to MAXP do ”, etc.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

6.3. Cribas Pág. 53

d ) Podemos empezar directamente diciendo que 2 es primo1 , y considerar sólo


la lista de impares para recuadrar y tachar, reduciendo el tamaño de la
lista inicial esencialmente a la mitad. Introducir esta modificación en el
programa eratostenes, cambiando o agregando las siguientes sentencias en
lugares apropiados:
• const MAXN = 4999; (* = (MAXP-1) div 2,
= tama~
no del arreglo esprimo *)
• esprimo: array[1..MAXN] of boolean;
• (* inicializacion *)
for i := 1 to MAXN do esprimo[i] := true;
cuenta := 1; (* el primer primo es 2 *)
• for i := 1 to MAXN do
if (esprimo[i]) then begin

m
(* p = 2i + 1 es primo *)
cuenta := cuenta + 1; p := i + i + 1;

o
(* ahora eliminar multiplos impares de p *)

.c
k := i + p; (* = (3p - 1)/2 = (p - 1)/2 + p,

os
de modo que 2k + 1 = 3p *)
while (k <= MAXN) do begin
r
esprimo[k] := false;
fo
k := k + p (* 2k+1 se incrementa en 2p *)
.m

end
na

end;
y verificar que el nuevo procedimiento es correcto.
tu

e) Una vez √que hemos “recuadrado” o “tachado” todos los números que no
on

superan n (donde n es el último de la lista), los números que han quedado


sin tachar serán recuadrados y no se tacharán nuevos números2 , por lo que
yc

podemos mejorar el programa poniendo:


dm

i := 1; p := 3; (* p = 2i+1 *)
while (p * p <= MAXP) do begin
.a

if (esprimo[i]) then begin


cuenta := cuenta + 1; (* p es primo *)
w

k := p + i; (* 2k+1 = 3p *)
w

while (k <= MAXN) do begin


w

esprimo[k] := false;
k := k + p (* 2k+1 se incrementa en 2p *)
end
end; (* if esprimo *)
i := i + 1; p := p + 2
end; (* while p * p *)
while (i <= MAXN) do begin
if (esprimo[i]) then cuenta := cuenta + 1;
i := i + 1
end;
1 Recordar el problema 5.18.d).
2 Recordar el ya citado problema 5.17. Ver también el ejemplo dado más arriba, tomando
n = 12.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 54 Arreglos

Verificar que la nueva versión es correcta, y hacer un programa con esta


modificación, agregando un contador para contar cuántas veces se hace la
asignación “ esprimo[k] := false ” en ambas versiones.
f ) Modificar el programa de modo de imprimir los primos entre m y n, m ≤ n,
ingresados por terminal.
g) El teorema de los números primos dice que, a medida que n crece, el número
de primos menores o iguales que n se aproxima a n/ ln n. Comprobarlo
usando eratostenes con MAXP = 100, 1000, 10000. ✄
Problema 6.4. En la cárcel habı́a n celdas numeradas de 1 a n, cada una ocu-
pada por un único prisionero. Cada celda podı́a cambiarse de abierta a cerrada
o de cerrada a abierta dando media vuelta a una llave. Para celebrar el Cente-
nario de la Creación de la República, se resolvió dar una amnistı́a parcial. El
Presidente envió un oficial a la cárcel con la instrucción:

m
for i := 1 to n do girar media vuelta la llave de las celdas i, 2i, 3i,...

o
comenzando con todas las celdas cerradas. Un prisionero era liberado si al final

.c
de este proceso su puerta estaba abierta. ¿Qué prisioneros fueron liberados?

os
Sugerencia: ¡no pensar, hacer el programa! ✄

r
Problema 6.5 (El problema de Flavio Josefo I). Durante la rebelión judı́a
fo
contra Roma (unos 70 años d. de C.), 40 judı́os quedaron atrapados en una
.m

cueva. Prefiriendo la muerte antes que la captura, decidieron formar un cı́rculo,


matando cada 3 de los que quedaran, hasta que quedara uno solo, quien se
na

suicidarı́a. Conocemos esta historia por Flavio Josefo (historiador famoso), quien
siendo el último de los sobrevivientes del cı́rculo, no se suicidó. El problema es
tu

ver en qué posición debió colocarse Flavio Josefo dentro del cı́rculo para quedar
on

como último sobreviviente.


En general, supondremos un cı́rculo de n personas, numeradas de 1 a n,
yc

y vamos eliminando la m-ésima comenzando desde la primera, recorriendo los


cı́rculos—cada vez más reducidos—siempre en el mismo sentido. Por ejemplo si
dm

n = 5 y m = 3, se eliminarán sucesivamente las numeradas 3, 1, 5, 2, sobrevi-


viendo 4 (¡hacer un dibujo!).
.a

El programa flaviojosefo1 (pág. 172) implementa una criba para resolver el


w

problema. El usuario entra n y m, y el programa escribe la permutación de


w

Flavio Josefo, es decir la secuencia en que van siendo eliminados incluyendo el


sobreviviente al final (3, 1, 5, 2, 4 en el ejemplo).
w

a) ¿Qué posición ocupaba en el cı́rculo inicial Flavio Josefo (en la versión


original del problema)?
b) ¿Cuál es el sobreviviente si n = 1234 y m = 3? Sugerencia: no imprimir
todos los valores, y tener cuidado con el dimensionamiento.
c) Modificar el programa de modo que responda cuántas vueltas sobrevivió la
persona que estaba inicialmente en el lugar s (el usuario entrará n, m y s).
d ) Modificar el programa de modo de imprimir una tabla, dando la posición
inicial y el orden de ejecución, algo como
Posición en el cı́rculo Orden de ejecución
1 2
2 4

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

6.4. Polinomios Pág. 55

3 1
4 5
5 3
cuando n = 5 y m = 3.
Sugerencia: eliminar el arreglo flavio y usar otro, digamos orden, de mo-
do que “ orden[i] ” indicará en qué vuelta fue eliminado i. Si se inicia-
liza “ orden[i] := n ” para todo i, y consideramos que en cada vuelta
“ orden[i] = n ” indica que vive, tampoco es necesario el arreglo vive ni el
lazo para encontrar el sobreviviente: será el único con “ orden[i] = n ” al
terminar.
e) ¿Cómo podrı́a modificarse el programa, de modo que siempre se trabaje
con un arreglo (o sub-arreglo) de longitud (por ejemplo) vivos, de modo de
no tener que considerar los que ya no viven? Sugerencia: ir “corriendo” las
posiciones en el arreglo a medida que “se cierra” el cı́rculo.

m
Nota: En el capı́tulo 14 veremos que las listas encadenadas se adaptan
mejor que los arreglos para esta variante.

o
.c
f ) El lazo “ repeat...until (cuenta = m) ” en el programa original es muy
primitivo. Por ejemplo, si n = 1000, m = 100, cuando quedan 10 sobrevi-

os
vientes debemos pasar unas 10 veces por cada uno de los 1000 elementos.

r
¿Podrı́a mejorarse el lazo usando “ mod ”? Sugerencia: recordar el programa
fo
resto (pág. 163). ✄
.m
na

6.4. Polinomios
tu

¿Para qué estudiar polinomios? ¿No basta con estudiar las funciones lineales
on

(y = ax + b) y a lo sumo las cuadráticas (y = ax2 + bx + c)?


¡Humm! Por un lado, los polinomios son las funciones más sencillas que po-
yc

demos considerar, para su cálculo sólo se necesitan sumas y productos. Además


los usamos diariamente, por ejemplo un número en base 10 es en realidad un
dm

polinomio evaluado en 10.


Pero también los polinomios sirven para aproximar tanto como se desee a
.a

casi cualquier función, lo que constituye un tema central de las matemáticas,


w

y se estudia tanto en los cursos teóricos de Análisis Matemático como en los


aplicados de Análisis Numérico. A modo de ejemplo visual, en el problema 8.11
w

mostramos cómo se podrı́a aproximar a la función seno mediante los denomina-


w

dos polinomios interpoladores de Lagrange.


Nuestra primer tarea será evaluar un polinomio dado:

Problema 6.6 (Evaluación de Polinomios). Hacer un programa que dada


una lista de coeficientes (reales) de un polinomio, (a0 , a1 , a2 , . . . , an ) y un núme-
ro x ∈ R, entrados por terminal, evalúe el polinomio an xn + an−1 xn−1 +
· · · + a 1 x + a0 .
Hacerlo de tres formas:
a) Calculando la suma de términos como se indica, calculando xk como en el
problema 4.15.
b) Como el anterior, pero las potencias en la forma xk+1 = xk × x, guardando
xk en cada paso.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 56 Arreglos

c) Usando la regla de Horner

((· · · ((an x + an−1 ) x + an−2 ) + · · · ) x + a1 ) x + a0 .

Nota: En las dos primeras versiones se hacen n sumas, n productos y se calculan


n−1 potencias, que en la primera versión representan otros 1+2+· · ·+(n−1) =
n(n − 1)/2 productos, mientras que en la segunda, los productos provenientes
de las potencias se reducen a n − 1. Finalmente, en la regla de Horner, tenemos
sólo n sumas y n productos. ✄

Problema 6.7 (Escritura en base entera). La idea de la regla de Horner


se usa también en el caso de escritura en base b (entero > 1). Si n ∈ Z es
no-negativo y

X
k
n= ai b i , donde ai ∈ Z y 0 ≤ ai < b, (6.1)

m
i=0

entonces a0 se obtiene como resto de la división de n por b, por lo que b | (n−a0 ),

o
.c
y n1 = (a− a0 )/b es un entero que tiene resto a1 cuando dividido por b, entonces
b | (n1 − a1 ), etc.

os
Un esquema para encontrar los coeficientes a0 , a1 , . . . , ak de n en base b es
m := n; j := -1;
r
fo
repeat
.m

j := j + 1; a[j] := m mod b; m := m div b


until m = 0
na

y para encontrar n dados los coeficientes en base b (si ak es el último coeficiente


tu

no nulo)
on

j := k; n := a[j];
while (j > 1) do begin j := j-1; n := n * b + a[j] end
yc

a) Implementar dos programas para que dados la base b y n, encontrar los coe-
dm

ficientes ai , y recı́procamente, dados la base b y los coeficientes ai , calcular


n usando la regla de Horner.
.a

b) En la ecuación (6.1), ¿cómo se relacionan k y logb n (si ak 6= 0)? ✄


w
w

6.5. Problemas Adicionales


w

Problema 6.8 (Potencias binarias). Otra variante de la idea de la regla de


Horner (problema 6.6) es el cálculo de una potencia “pura”. Si x ∈ R y n ∈ N,
podemos calcular xn escribiendo n en base 2:

X
k
n= ai 2 i ,
i=0

y haciendo
2
a2 +···+2k−1 ak−1 +2k ak
xn = xa0 +2a1 +2
k−1 k
= xa0 · (x2 )a1 · (x4 )a2 · · · (x2 )ak−1 · (x2 )ak .

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

6.5. Problemas Adicionales Pág. 57

a) Implementar un programa para el cálculo de potencias “puras” con estas


ideas.
Nota: Si los coeficientes se conocen, un esquema es
if (a[0] = 1) then producto := x else producto := 1;
potencia := x;
for j := 1 to k do begin
potencia := potencia * potencia; (* = x**{2**j} *)
if (a[j] = 1) then producto := producto * potencia
end
mientras que si los coeficientes no se conocen (encontrando aj en cada
paso):
m := n; potencia := x;
if (m mod 2 = 1) then producto := x else producto := 1;
while (m > 0) do begin
m := m div 2; potencia := potencia * potencia;

m
if (m mod 2 = 1) then producto := producto * potencia

o
end

.c
b) Comparando con el problema 4.15, donde se usaba un lazo “ for ”: ¿cuántos

os
operaciones aritméticas (productos y divisiones) se realizan en cada caso?
(Recordar el problema 6.7.b))
r ✄
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 7

Funciones y Procedimientos

m
Nuestros programas se han hecho cada vez más largos, y a medida que avan-
cemos lo serán aún más. La longitud y complejidad de los programas profesio-

o
nales es tal que una sola persona no puede siquiera escribirlos completamente.

.c
A fin de abordar esta dificultad, se han desarrollado una serie de técnicas, y

os
en este capı́tulo daremos los primeros pasos hacia una de ellas, la modularización,
para lo cual Pascal cuenta con dos mecanismos: funciones y procedimientos.
r
fo
Aunque la ventaja de usar funciones o procedimientos irá quedando más
clara con los ejemplos que veremos en éste y otros capı́tulos, podemos decir que
.m

en general son convenientes para:


na

• Poner en un único lugar cálculos idénticos que se realizan en distintas


tu

partes del programa,


on

• o poner por separado alguna acción permitiendo su fácil reemplazo (y con


menor posibilidad de error),
yc

• y no menos importante, haciendo el programa más fácil de entender, de-


dm

jando una visión más global y no tan detallada en cada parte (viendo el
bosque y no el árbol).
.a

El lector seguramente recordará el uso que con el mismo espı́ritu hemos dado
w

a “ const ”, por ejemplo en el problema 6.2, para hacer un único cambio que
w

afecta a varias partes del programa.


w

7.1. Funciones
Como hemos visto, Pascal cuenta con una serie de funciones pre-definidas,
como “cos” o “ln”, de una variable, o la suma, “+”, que se aplica a dos o más
variables. Pero —necesariamente— no puede tener todas las funciones y sólo
algunas se han incluido. Por ejemplo, el cálculo de la potencia xn cuando x ∈ R
y n ∈ N no está incluida en Pascal, pero lo hemos hecho en el problema 4.15.
Supongamos que queremos hacer un programa para comparar los valores xn
y y , donde x, y ∈ R y n, m ∈ N son datos ingresados por el usuario. Nuestro
m

programa tendrı́a los siguientes pasos:


1. Poner carteles iniciales.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 60 Funciones y Procedimientos

2. Leer los datos x y n, y luego los datos y y m.


3. Calcular xn y y m .
4. Comparar el resultado, imprimiendo la decisión.
5. Imprimir un cartel final.
Cada una de las acciones mencionadas no es difı́cil. Por ejemplo para calcular
xn y y m podrı́amos poner
xn := 1; for k := 1 to n do xn := xn * x;
ym := 1; for k := 1 to m do ym := ym * y;

Problema 7.1. En el programa potencias2 (pág. 173) hemos implementado


estas ideas. Leerlo, reconociendo la estructura mencionada, y ejecutarlo a fin de
verificar su funcionamiento. ✄

Claro que si contáramos con una función de Pascal para calcular xn para

m
x ∈ R y n ∈ N, digamos “ potencia(x,n) ”, podrı́amos reemplazar los dos

o
renglones anteriores por

.c
xn := potencia(x,n); ym := potencia(y,m);

os
Aunque Pascal no cuenta con una función que realice este cálculo, nos da un
mecanismo para definirla nosotros.
r
fo
En Pascal, las funciones y procedimientos (que veremos en un rato) se de-
.m

claran en la parte declarativa1 del programa y tienen la misma estructura que


los programas. Se comienza poniendo el nombre, cambiando “ program ” por
na

“ function ” para funciones o “ procedure ” para procedimientos, luego una


parte de declaraciones propias, y después un cuerpo principal encerrado entre
tu

“ begin ” y “ end; ” (con “ ; ” en vez de “ . ”).


on

Ası́, para definir la función “ potencia ” pondrı́amos


yc

function potencia(a: real; b: integer): real;


var k: integer; z: real;
dm

begin
z := 1;
.a

for k := 1 to b do z := z * a;
w

potencia := z
end;
w
w

Observamos que

• Las funciones en Pascal calculan un valor de alguno de los tipos elementales


boolean, char, integer o real2, y por lo tanto el valor debe ser declarado.
En nuestro ejemplo, el resultado es de tipo “ real ”.

• Los argumentos de la función también deben ser declarados. En nuestro


ejemplo hay dos argumentos, a, de tipo “ real ”, y b, de tipo “ integer ”.
Por supuesto, el orden de los argumentos tiene importancia al calcular
“ potencia(a,b) ”: 23 6= 32 .
1 ¿Podrı́a ser de otro modo?
2 O puntero, tipo que veremos en el capı́tulo 14. Según el estándar no deben ser de otro
tipo, como arreglos, aunque muchos compiladores lo permiten.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

7.1. Funciones Pág. 61

• El nombre de la función, dentro del bloque correspondiente, debe ser asig-


nado para poder “retornar” el resultado a otras partes del programa, aun-
que no es una variable.
Nota: No es importante el lugar de esta asignación dentro de la función,
y es posible poner varias asignaciones, pero al menos debe haber una
que se ejecute antes de que termine la función.

• Además de los argumentos y el valor “a retornar”, la función puede tener


otras variables propias. En este caso, k de tipo “ integer ” y z de tipo
“ real ”. Todas estas variables propias, en nuestro ejemplo a, b, k y z , se
llaman locales, y se desconocen fuera de la función.
Problema 7.2. En el programa potencias3 (pág. 174) hemos incorporado la
función “ potencia ” y los cambios mencionados. Leerlo, estudiando las distintas
instrucciones, y ejecutarlo.

m
a) Observar que la variable k ya no está definida al comienzo del programa,
sino sólo en la función “ potencia ”: es local a la función pero desconocida

o
para el resto del programa.

.c
i) Incluir el renglón

os
writeln(’ El valor de k es: ’, k:1);
r
fo
inmediatamente antes del cartel final del programa. El compilador se-
.m
guramente rechazará esta acción pues k no está declarada.
ii) Manteniendo el renglón problemático, incluir la declaración de k como
na

estaba en el programa potencias2, y hacer la asignación “ k := 1 ”


antes del renglón del inciso anterior. Compilar y ejecutar nuevamente
tu

el programa, observando que ahora no hay inconvenientes.


on

iii) Repetir los pasos anteriores cambiando k por z , comprobando que z


“vive” sólo dentro de la función.
yc

b) Observar que al declarar la función, los argumentos a y b se separan con


dm

“ ; ”, pero que al llamar la función se invoca con “ , ” separando los argu-


mentos: “ potencia(x,n) ”.
.a

i) Cambiar “ ; ” por “ , ” en la declaración de la función, i.e. poner


w

function potencia(a: real, b: integer): real;


w

y comprobar que el compilador no acepta el cambio.


w

ii) De modo similar, cambiar “ xn := potencia(x,n); ” por


xn := potencia(x;n);
y comprobar que este cambio tampoco es aceptado.
Nota: Sin embargo, si dos o más argumentos consecutivos son del mis-
mo tipo, podemos separarlos con una coma. Para ejemplificar, si a y
b son de tipo “ real ”, podemos poner tanto “ function f(a: real; b:
real):... ” como “ function f(a, b: real):... ”.
c) Las variables que son argumentos de la función también son locales a ella,
y es posible que coincidan con los nombres de otras variables del programa
(llamadas globales).
i) Cambiar la definición de la función a

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 62 Funciones y Procedimientos

function potencia(x: real; n: integer): real;


var k: integer; z: real;
begin
z := 1;
for k := 1 to n do z := z * x;
potencia := z
end;
y comprobar (compilando y ejecutando el programa) que el compor-
tamiento es el mismo.
ii) La localidad de los argumentos también se traduce en que podemos
modificarlos dentro de la función, sin modificar otras variables con el
mismo identificador del programa.
Por ejemplo, cambiar la declaración de la función poniendo
function potencia(x: real; n: integer): real;

m
var z: real;

o
begin

.c
z := x;

os
while (n > 1) do begin
z := x * z; n := n - 1 end;
potencia := z
r
fo
end;
.m

Observar que el valor final de z es el mismo en uno u otro caso,


pero el valor de la variable n (local a la función) se va modificando
na

con la nueva definición.


tu

Ejecutar el programa, y verificar que sin embargo, el valor de n


que se imprime al final es el mismo que el ingresado inicialmente:
on

corresponde a la variable global n, y no a la variable n local a la


función.
yc

Nota: Si bien podemos poner cualquier identificador a los argumen-


dm

tos, no deben ser los mismos que las variables locales a la función.
Por ejemplo no debemos poner
.a

function potencia(x: real; n: integer): real;


w

var k: integer; x: real;


.
w

.
.
w

d ) Ya hemos mencionado que el nombre de la función debe ser asignado antes


de terminar la función, pero no es una variable: ¿qué pasa si se pone
function potencia(x: real; n: integer): real;
begin
potencia := x;
while (n > 1) do begin
potencia := x * potencia; n := n - 1 end
end; ?
e) Cambiar la función potencia de modo de que el cálculo se haga mediante la
fórmula
xn = exp(n ln x). ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

7.2. El método de la bisección Pág. 63

7.2. El método de la bisección


Supongamos que tenemos una función continua f definida sobre el intervalo
[a, b] a valores reales3, y que f (a) y f (b) tienen distinto signo, como en la figu-
ra 7.1. Cuando la dibujamos desde el punto (a, f (a)) hasta (b, f (b)), vemos que
en algún momento cruzamos el eje x, y allı́ encontramos una raı́z de f , i.e. un
valor de x tal que f (x) = 0.

a = a0 c0 = a1 = a2 c1 = b2 b = b0 = b1

c2

o m
.c
os
r
Figura 7.1: una función continua con distintos signos en los extremos.
fo
.m
En el método de la bisección se comienza tomando a0 = a y b0 = b, y para
i = 0, 1, 2, . . . se va dividiendo sucesivamente en dos el intervalo [ai , bi ] tomando
na

el punto medio ci , y considerando como nuevo intervalo [ai+1 , bi+1 ] al intervalo


[ai , ci ] o [ci , bi ], manteniendo la propiedad que en los extremos los signos de f
tu

son opuestos (que podemos expresar como f (ai )f (bi ) < 0). Se finaliza cuando
se obtiene un valor de x tal que |f (x)| es suficientemente chico, |f (x)| < εy , o
on

se han realizado un máximo de iteraciones.


yc

Nota: Recordando la filosofı́a de comparar papas con manzanas, el valor εy a


poner dependerá del problema que se trate de resolver.
dm

Nota: También en este sentido, observamos que 210 = 1024 ≈ 103 y 220 =
1048576 ≈ 106 , por lo que el intervalo inicial se divide aproximadamente en
.a

1000 después de 10 iteraciones y en 1000000 = 106 después de 20 iteraciones. Es


w

decir, después de 10 iteraciones el intervalo mide el 0.1 % del intervalo original,


y después de 20 iteraciones mide el 0.0001 % del intervalo original.
w
w

Problema 7.3 (Método de la bisección para encontrar raı́ces). El pro-


grama biseccion (pág. 175) utiliza el método de bisección para encontrar raı́ces
en el caso particular

f (x) = x(x + 1)(x + 2)(x − 4/3).

a) Observar la declaración de la función f en Pascal, y la estructura del cuerpo


principal:
1. Carteles iniciales.
2. Entrada de los extremos del intervalo inicial: la cota inferior se llama
poco y la superior mucho.
3 Recordar los comentarios en la pág. 37.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 64 Funciones y Procedimientos

3. En la inicialización, se calculan los valores de f en ambos extremos,


llamados ypoco y ymucho, y se inicializa a 0 el contador de iteraciones,
iter .
Dado que se sigue con el método si la función toma signos distintos
en los extremos, se define la variable lógica seguir que indica si la
condición es cierta o no.
4. En el lazo principal:
• Se incrementa el contador iter , se calcula el punto medio del inter-
valo, medio, y el valor de f en este punto, ymedio.
• Si el valor absoluto de ymedio es suficientemente chico, paramos.
Esto se indica poniendo seguir en falso.
• También paramos si ya hemos llegado al máximo de iteraciones.
• Si no, calculamos el nuevo intervalo, cuidando de que los signos en

m
los extremos sean distintos.
Observar que sólo necesitamos el signo de ypoco para determinar

o
.c
el nuevo intervalo, de modo que no hemos conservado el valor de
ymucho.

os
5. En la salida tenemos en cuenta las distintas posibilidades por las cuales
r
seguir es falsa:
fo
.m
• Si la condición de distinto signo en los extremos no se satisfacı́a
inicialmente, no se han realizado iteraciones (por lo que el valor de
na

iter es 0), y ponemos un cartel apropiado.


• En otro caso señalamos los valores obtenidos, poniendo un cartel
tu

especial si el error no es suficientemente pequeño (y por lo tanto el


on

número de iteraciones es máximo).


yc

6. Terminamos poniendo un cartel de “Fin”.


dm

b) Dada la forma de f , en este caso conocemos exactamente las raı́ces. Bos-


quejar el gráfico de f en el intervalo [−3, 2], y dar valores iniciales de poco
.a

y mucho para obtener aproximaciones a cada una de ellas ejecutando el


programa.
w

c) En caso de que haya más de una raı́z en el intervalo inicial, la solución elegida
w

depende de los datos iniciales. Verificar este comportamiento ejecutando


w

el programa sucesivamente con los valores .8, 1 y 1.2 para mucho, pero
tomando poco = −3 en todos estos casos.
d ) ¿Por qué si ponemos poco = −3 y mucho = 1 obtenemos la raı́z x = −1 en
una iteración?
Nota: En general, nunca obtendremos el valor exacto de la raı́z: recordar
que en la computadora sólo existen racionales (¡y pocos!).
e) x = 0 es raı́z, pero ¿qué pasa si ponemos poco = 0 y mucho = 1? Modificar
el programa de modo que si f (poco) o f (mucho) sean en valor absoluto
suficientemente pequeños, entonces se imprima la correspondiente solución
y no se realice el lazo de bisección.
f ) Agregar también la impresión de carteles apropiados cuando f (poco) ×
f (mucho) ≥ 0 y no se está en las condiciones del inciso anterior.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

7.2. El método de la bisección Pág. 65

g) El programa no verifica si poco < mucho, y podrı́a suceder que poco >
mucho. ¿Tiene esto importancia?
h) Teniendo en cuenta las notas al principio de la sección (pág. 63), ¿tendrı́a
sentido agregar al programa un criterio de modo de parar si los extremos
del intervalo están suficientemente cerca? Si la nueva tolerancia fuera εx ,
¿cuántas iteraciones deben realizarse para alcanzarla, en términos de εx y
los valores originales de mucho y poco?
i) Modificar el programa de modo que en vez de considerar hasta un máximo
de iteraciones, el programa termine cuando o bien se ha encontrado x tal que
|f (x)| < εy o bien se llega a poco y mucho de modo que |poco − mucho| <
εx . ✄
El método de la bisección es bien general y permite encontrar las raı́ces de
muchas funciones. Al programarlo, hemos separado la declaración de la función
f de modo de poder cambiarla fácilmente según la aplicación, sin necesidad de

m
recorrer todo el programa buscando las apariciones de f (habrá, sı́, que cambiar

o
también los carteles iniciales).

.c
Problema 7.4. Cambiar la definición de la función en el programa biseccion,

os
para responder a los siguientes ejemplos (hacer primero un bosquejo del gráfico
para estimar valores iniciales de poco y mucho):
r
fo
a) Resolver aproximadamente las ecuaciones:
.m

x2 − 5x + 2 = 0 y x3 − x2 − 2x + 2 = 0.
na

Resolver también estas ecuaciones en forma exacta y comparar los resultados


obtenidos.
tu

Nota: La primera ecuación tiene 2 raı́ces y la segunda 3.


on

b) Encontrar una solución aproximada de cos x = x y comparar con los resul-


yc

tados del problema 5.6.


c) Obtener una solución aproximada de cada una de las ecuaciones
dm

2 − ln x = x y x3 sen x + 1 = 0.
.a

Nota: La primera ecuación tiene una raı́z, y la segunda tiene infinitas. ✄


w

Problema 7.5 (Interés sobre saldo). Consideremos el siguiente problema:


w
w

Un artı́culo cuesta $100 de lista. Puedo comprarlo al contado con un


10 % de descuento, o con un plan de pagos de anticipo y 9 cuotas
mensuales de $10 c/u, ¿cuál es la tasa de interés (nominal anual)
que están cobrando?

Resulta que hay muchas formas de calcularla4. En este problema estudiamos


el llamado interés sobre saldo, que da como respuesta a la pregunta anterior una
tasa de 29.07 %.
Supongamos que pedimos prestada una cantidad a (en $) con un tasa de
interés anual (nominal) r (en %), que pagaremos en cuotas mensuales fijas de p
(en $) comenzando al final del primer mes, y que el préstamo tiene las carac-
terı́stica de que el interés se considera sobre el saldo, esto es, si am es el saldo
4 Y el contador dirá “¿cuánto querés que te de?”

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 66 Funciones y Procedimientos

adeudado a fin del mes m, justo antes de pagar la cuota m-ésima, y dm es el


saldo inmediatamente después de pagar esa cuota, poniendo t = 1+r/(100×12),
tendremos:

a1 = ta y dm = am − p, am+1 = tdm para m ≥ 1.

Inclusive podrı́amos poner d0 = a.


a) Programar una función saldo(a, r, p, m) que dados el monto inicial a, la
tasa r, y el pago mensual p, calcule dm = saldo(a, r, p, m) para m = 1, 2, . . .
Aclaración: no se pide encontrar una fórmula, sólo escribir la función Pascal.
b) Verificar la corrección de la función anterior (dentro de un programa),
usando los datos de la pregunta al principio del problema (se financian
$90 − $10 = $80 en 9 cuotas de $10, a una tasa de 29.07 %; el saldo corres-
pondiente debe ser aproximadamente 0).

m
c) Considerando que a y r están fijos, ¿existe un valor de p de modo que el

o
saldo sea siempre el mismo, i.e. dm+1 = dm para m = 1, 2, . . . ?, ¿cuál?

.c
d ) Para a, r e p fijos, la función saldo(a, r, p, m) es decreciente con m si p es

os
mayor que el monto calculado en el inciso c). ¿Cómo es el comportamiento
cuando sólo varı́a a?, ¿y si sólo varı́a r?
r
fo
e) Hacer un programa de modo que dados r, a y p (p mayor que el monto en c))
calcule el número total de cuotas n. Aclaración: todas las cuotas deben ser
.m

iguales, excepto tal vez la última que puede ser menor.


na

f ) Hacer un programa que usando el método de bisección, y dados a, r y


el número total de cuotas n, calcule p de modo que la deuda se cancele
tu

exactamente con n cuotas fijas, es decir dn = 0. ¿Podrı́amos usar poco = 0


on

y mucho = a?
g) El resultado anterior, p, en general no podrá pagarse en pesos y centavos
yc

(habrá más de dos decimales). Si los pagos se harán en pesos y centavos,


¿qué tolerancias en p pondremos? Modificar (si es necesario) el programa
dm

anterior para incorporar esta tolerancia.


h) Modificar el programa para que dados r (la tasa que me cobran), p (la cuota
.a

que estoy dispuesto a pagar) y n (la cantidad de meses), calcule el monto a


w

(al que podrı́a acceder).


w

Nota: En calculadoras financieras y “planillas de cálculo” están implementadas


w

funciones similares.
Por ejemplo, en (la versión inglesa de) Excel están las funciones:

PMT: Para calcular p dados n, r y a: “ PMT(r/12,n,a) ”.


PV: Para calcular a dados n, r y p: “ PV(r/12,n,p) ” (p negativo
pues es un pago).
RATE: Para calcular r dados n, a y p: “ RATE(n,p,a)*12 ” (p
negativo pues es un pago).
NPER: Calcular n dados r, a y p: “ NPER(r/12,p,a) ” (p nega-
tivo pues es un pago).

Ası́, para calcular el pago mensual si r = 8 %, n = 10 y a = $10000,


ponemos “ PMT(8 %/12,10,10000) ” para obtener −1037.03. ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

7.3. Procedimientos Pág. 67

7.3. Procedimientos
Los procedimientos y funciones son muy parecidos entre sı́, y a veces se
los engloba bajo el nombre común de rutinas 5 . De hecho, en lenguajes como
“C” no hay distinción entre ellos. En Pascal, la diferencia más obvia es que los
procedimientos no tienen “un resultado” visible.
Pascal nos permite mezclar funciones y procedimientos, con la única restric-
ción de que se deben declarar en el orden en que son usados: una función o
procedimiento no puede llamar a una función o procedimiento que no ha sido
aún declarada.
Nota: Una posibilidad intermedia el uso de “ forward ”, que no veremos.
Más aún, como veremos en el próximo problema, es posible poner una función
o procedimiento dentro de otra función o procedimiento.
Volvamos al problema 4.10 (pág. 27) donde hicimos una tabla del seno usan-
do el programa tablaseno (pág. 163). Podemos esquematizar ese programa por

m
medio de los siguientes pasos:

o
1. Poner carteles.

.c
2. Leer los datos, en este caso inicial , final e incremento.

os
3. Hacer e imprimir la tabla.
4. Señalar el fin del programa.
r
fo
Pascal nos permite poner cada uno de estos pasos como procedimiento, po-
.m
niendo en el cuerpo principal del programa6:
na

begin
cartelesiniciales;
tu

leerdatos;
imprimirtabla;
on

cartelfinal
end.
yc

donde cartelesiniciales , leerdatos, imprimirtabla y cartelfinal son procedimientos


dm

que realizarán las acciones correspondientes7 .


La ventaja de hacerlo es que podemos preocuparnos por cada uno de los
.a

procedimientos —y si las hubiera, funciones— por separado. En nuestro ejem-


w

plo de la tabla del seno, usamos sólo procedimientos puesto que no tenemos
asignaciones explı́citas.
w

Ası́, podrı́amos definir el procedimiento cartelesiniciales como


w

procedure cartelesiniciales;
begin
writeln(’Hacer una tabla del seno dando valores’);
writeln(’inicial, final, y del incremento (en grados).’);
writeln
end;

Problema 7.6. En el programa tablaseno2 (pág. 177) hemos reescrito el pro-


grama tablaseno, con las modificaciones mencionadas.
5 Aunque en algunos lenguajes, ¡las rutinas son nuestros procedimientos!
6 Claro que es una exageración. Lo hacemos sólo a modo de ejemplo.
7 Usaremos estetipodeletra para señalar que se trata de una función o procedimiento.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 68 Funciones y Procedimientos

a) Comparar ambos programas, observando cómo se han declarado los pro-


cedimientos en tablaseno2 y cómo se corresponden con las sentencias de
tablaseno.
Observar que las variables inicial , final e incremento se han declarado
como globales, puesto que se usan en distintas partes, mientras que grados
y radianes son locales al procedimiento imprimirtabla, pues es el único que
las usa.
b) Compilar y ejecutar tablaseno2 verificando su comportamiento.
c) En el problema 4.10 nos preocupamos por cómo dar un valor aproximado de
π, teniendo las alternativas de definirlo “manualmente”, o usar una fórmula
como π = 4 × arctan 1, a la que podrı́amos agregar el uso de la técnica de
punto fijo del problema 5.7.
Eliminar la declaración de pi como constante en el programa tablaseno2,
y declararlo en cambio como variable real, e incluir el procedimiento

m
procedure calculodepi;

o
begin pi := 4 * arctan(1) end;

.c
como primer procedimiento, incluyendo la sentencia “ calculodepi ” en el

os
cuerpo principal del programa. Compilar y ejecutar el programa con estas
modificaciones.
r
fo
d ) En realidad, el valor de pi se usa sólo para pasar de grados a radianes. Eli-
minar las declaraciones de pi , calculodepi y la sentencia “ calculodepi ” del
.m

programa, y en cambio colocarlas dentro del procedimiento imprimirtabla .


na

Nota: Deberı́a quedar algo como


procedure imprimirtabla;
tu

var
on

grados: integer;
radianes, pi: real;
yc

procedure calculodepi;
dm

begin pi := 4 * arctan(1) end;

begin
.a

calculodepi;
w

writeln(’Angulo Seno’);
w

grados := inicial;
while (grados <= final) do begin
w

radianes := grados * pi/180;


writeln(grados:5, sin(radianes):15:5);
grados := grados + incremento
end
end;
mientras que el cuerpo principal y las variables globales son como el en la
versión original de tablaseno2. ✄

7.4. Pasando por valor o por referencia


Tanto los procedimientos como las funciones tienen en general uno o más
parámetros que son los argumentos para evaluarlos. Tenemos que distinguir

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

7.4. Pasando por valor o por referencia Pág. 69

entre los parámetros que están en la descripción de la función o procedimiento,


llamados parámetros formales y los que se especifican en cada llamada de la
función o procedimiento, llamados parámetros reales.
Los parámetros formales se utilizan solamente dentro del cuerpo de la fun-
ción o procedimiento y son locales a él, es decir, son desconocidos fuera de la
función o procedimiento. Por supuesto, los parámetros reales deben tener el
mismo tipo que los formales. De alguna forma, podemos pensar que la función
o procedimiento tiene sus propias “cajas” (los parámetros formales) en las que
alojar las variables que son pasadas (los parámetros reales).

Problema 7.7 (Swap de variables). Supongamos que queremos intercam-


biar los valores de las variables u y v, es decir poner en u lo que está en v y
recı́procamente, proceso al que llamaremos swap, conservando el inglés. No po-
demos poner sencillamente “ u := v; v := u ” pues quedarı́a u = v 8 . La forma
usual de hacer el intercambio es usando una variable intermedia o temporal t,

m
siguiendo un esquema como “ x := t; t := y; y := x ”.
Si queremos implementar este intercambio como procedimiento, el primer

o
impulso es poner (suponiendo variables enteras) un procedimiento como en el

.c
programa swap (pág. 178).

os
a) Observar la declaración de los argumentos en swapincorrecto: cuando hay
r
dos (o más) consecutivos del mismo tipo, en este caso “ integer ”, hemos
fo
colocado
.m

x, y: integer
na

separando con “ , ”. Esto es completamente equivalente a


tu

x: integer; y: integer
on

donde los argumentos están separados por “ ; ”, y el tipo se explicita para


cada uno de ellos.
yc

Nota: Es decir, el uso es similar a las “ , ” que se usan al declarar variables


con “ var ”.
dm

b) Ejecutar el programa, y comprobar que los valores de a y b no han cambiado.


c) Agregar en swap una impresión de x, y antes y después del intercambio.
.a

d ) Cambiar el procedimiento swapincorrecto del programa anterior por el si-


w

guiente:
w

procedure swapcorrecto(var x, y: integer);


w

(* x, y pasados por referencia *)


var t: integer;
begin t := x; x := y; y := t end;
Probarlo y comprobar que los valores finales de a y b son ahora los correc-
tos. ✄

Como hemos visto en el problema anterior, en funciones y procedimientos


podemos indicar si queremos trabajar con una copia de los parámetros reales,
o directamente con éstos. Este mecanismo se llama de “sustitución” y es usual
distinguir las siguientes clases de sustitución de parámetros9:
8 Ante la duda, recomendamos hacer “pruebas de escritorio” en este problema.
9 La siguientes descripciones y ejemplos están tomados de [5, pág. 99].

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 70 Funciones y Procedimientos

Sustitución por valor: El parámetro real se evalúa, y el valor resul-


tante sustituye al correspondiente parámetro formal.
Sustitución por referencia: El parámetro real es una variable; los po-
sibles ı́ndices se evalúan, y la variable ası́ identificada sustituye a su
correspondiente parámetro formal. Es utilizada si el parámetro repre-
senta el resultado de un procedimiento.
El siguiente ejemplo está escrito como Pascal sólo con fines ilustrativos.
Supongamos que tenemos
var i: integer; a: array[1..2] of integer;
procedure P(x: integer);
begin i := i + 1; x := x + 2 end;
begin (* cuerpo principal *)
a[1] := 10; a[2] := 20; i := 1; P(a[i])
end.

m
y nos preguntamos el valor final del arreglo a.

o
Sustitución por valor : La variable x en el procedimiento P tiene valor

.c
inicial 10 = a1 . El valor final de a es (10, 20).

os
Sustitución por referencia: En el procedimiento P , x = a1 . La sentencia

r
“ x := x + 2 ” significa “ a[1] := a[1] + 2 ”. El valor final de a es
fo
(12, 20).
.m

Nota: Hay una tercera forma de sustitución, llamada por nombre, que en Pascal
no está implementada. En este tipo de sustitución, el parámetro real sustituye
na

literalmente al parámetro formal y no hay evaluación. En nuestro ejemplo,


tendrı́amos que en el procedimiento P , x = ai ; la sentencia “ x := x + 2 ”
tu

significa “ a[i] := a[i] + 2 ”; y el valor final de a es (10, 22).


on

Recordar entonces que


yc

• En Pascal la sustitución por valor es la usual, si queremos sustitución por


referencia anteponemos la palabra “ var ” al parámetro formal.
dm

• No podemos poner como parámetro real una constante cuando el pará-


metro se pasa por referencia. Por ejemplo las declaraciones:
.a
w

.
.
.
w

procedure P(var x: integer):


w

.
.
.
begin (* parte principal *)
.
.
.
P(1);
.
.
.
end.

son incorrectas.

• Si hay varios parámetros, algunos pueden pasarse por valor y otros por
referencia (con “ var ”). Si hay una lista de parámetros separados por “ , ”,
como en “ proc (var a, b: integer; c, d: integer) ”, entonces los

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

7.4. Pasando por valor o por referencia Pág. 71

parámetros a y b se pasan por referencia mientras que los parámetros c


y d se pasan por valor (aunque es conveniente evitar estas confusiones,
poniendo explı́citamente la palabra “ var ” delante de cada parámetro que
se pasará por referencia).

Problema 7.8. En los siguientes programas determinar los valores de los pará-
metros de las sentencias “ writeln ” y luego comprobar las respuestas eje-
cutándolos (agregando encabezado):
a) var a, b, c: integer;

procedure P(x, y: integer; var z: integer);


begin z := x + y + z; writeln(x, y, z) end;

begin a:= 5; b := 8; c := 3;
P(a, b, c); P(7, a + b + c, a); P( a * b, a div b, c)

m
end.

o
b) var i, j, k: integer;

.c
os
procedure P(var i: integer);
begin i := i + 1; writeln(i, j, k) end;
r
fo
procedure Q( h: integer; var j: integer);
.m
var i: integer;
na

procedure R;
begin i := i + 1 end;
tu
on

begin i := j;
if (h = 0) then P(j) else if h = 1 then P(i) else R;
yc

writeln( i, j, k)
end;
dm

begin i := 0; j := 1; k := 2; Q(0,k); Q(1,i); Q(2,j)


.a

end.
w

Nota: Observar que en b) el procedimiento “ R ” es local al procedimiento “ Q ”,


w

y desconocido globalmente. ✄
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 8

Todos juntos: arreglos,


funciones y procedimientos

o m
.c
8.1. Definiendo nuestros propios tipos de datos

os
Hemos visto cuatro tipos de datos elementales —“ boolean ”, “ char ”, “ in-
r
fo
teger ” y “ real ”— pero Pascal también nos permite crear nuevos tipos me-
diante la palabra “ type ”. Esto es especialmente conveniente cuando trabajamos
.m

con varios arreglos de las mismas caracterı́sticas (y como veremos más tarde,
también para otras estructuras).
na

Supongamos por ejemplo que queremos trabajar con dos arreglos, a y b,


tu

declarados como
on

var a, b: array[1..100] of integer


Puesto que tienen las mismas caracterı́sticas, podrı́amos definir un tipo para
yc

estos arreglos, por ejemplo


dm

type arregloentero = array[1..100] of integer


después de la declaración de constantes y antes de la de variables. Observar que
.a

usamos el signo “ = ”, como en las constantes, y no “ : ” que usamos al declarar


w

variables.
w

Luego podemos declarar (en la parte de “ var ”)


w

a, b: arregloentero
lo que nos permite hacer asignaciones de la forma “ a := b ”, y no tener que
hacer un lazo para la asignación1. Más importante, es altamente conveniente y
recomendable usar como argumentos de funciones o procedimientos únicamente
parámetros de los tipos elementales o declarados con “ type ”, a fin de evitar
errores.
La estructura de un programa Pascal —a la que ya no haremos modifica-
ciones— toma entonces la forma del cuadro 8.1, y la estructura de funciones
y procedimientos es similar (pueden tener constantes, tipos, etc. propios), sólo
que terminan con “ end; ” en vez de “ end. ”.
1 No obstante, no es posible hacer la comparación “ a = b ”. En este caso habrá que usar

un lazo.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 74 Todos juntos: arreglos, funciones y procedimientos

1. program nombre (input, output);


2. const si hubiera que definir constantes.
3. type si hubiera definidos tipos propios.
4. var si hay variables.
5. Si las hubiera, funciones (con “ function ”) y procedimien-
tos (con “ procedure ”). Deben declararse teniendo en cuen-
ta el orden en que serán usadas.
6. begin
7. Instrucciones.
8. end.
Cuadro 8.1: estructura de un programa Pascal.

8.2. Ingreso e impresión de arreglos

m
Ahora que contamos con funciones, es un buen momento para repasar la

o
entrada de datos del programa busquedalineal del problema 6.2.

.c
Problema 8.1 (Ingreso de arreglos). Supongamos que queremos ingresar

os
un arreglo a = (a1 , a2 , . . . , an ) de datos (de algún tipo), ingresando un dato

r
por renglón, poniendo los carteles en cada paso, y terminando la entrada con
fo
“doble <retorno>”. Siguiendo el esquema del programa busquedalineal, e in-
.m
corporando el control del número de datos, de ahora en más vamos a usar un
procedimiento para englobar esta acción.
na

Si declaramos “ const MAXN = 100; ” y los tipos


tu

tipodato = integer; (* real, char,... *);


tipoarreglo = array[1..MAXN] of tipodato;
on

podemos leer arreglos del tipo “ tipoarreglo ” mediante el siguiente procedi-


yc

miento:
dm

procedure leerarreglo(var a: tipoarreglo; var n: integer);


var findatos: boolean;
begin
.a

write(’Entrar numeros enteros,’); (* o reales o ... *)


w

writeln(’ uno por renglon y no mas de ’, MAXN:1, ’.’);


w

writeln(’ Fin con retorno sin entrada de datos.’);


w

n := 1; findatos := false;
repeat
if (n > MAXN) then findatos := true
else begin
write(’Entrar el dato ’, n:2);
write(’ (fin = <retorno>): ’);
if (eoln) then begin findatos := true; readln end
else begin readln(a[n]); n := n + 1 end
end
until findatos;
n := n - 1
end;
Podemos también copiar la impresión de arreglos del mismo programa, con

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

8.2. Ingreso e impresión de arreglos Pág. 75

las modificaciones del problema 6.2:


procedure escribirarreglo(a: tipoarreglo; n: integer);
const maxrenglon = 10; (* maxima cantidad por renglon *)
var i: integer;
begin
for i := 1 to n do begin
write(a[i]:6); (* cambiar formato para reales,... *)
if ((i mod maxrenglon) = 0) then writeln
end;
if ((n mod maxrenglon) > 0) then writeln
end;
Hacer un programa con los procedimientos leerarreglo y escribirarreglo (que
acabamos de describir), para leer y luego imprimir un arreglo de enteros, de
modo que el cuerpo principal sea

m
begin

o
writeln(’** Prueba de lectura e impresion de arreglos’);

.c
writeln;

os
leerarreglo( arreglo, ndatos);
writeln(’El arreglo leido es:’);
escribirarreglo( arreglo, ndatos);
r
fo
writeln; writeln(’** Fin **’)
.m

end.
na

Cuando esté funcionando correctamente, cambiar el tipo de datos ingresado


de entero a real. ✄
tu

Problema 8.2. En el programa maximo (pág. 178) se ingresa un arreglo de


on

caracteres (un renglón), lo reproduce y encuentra su máximo.


Observar la declaración del tipo tiporenglon con “ type ”.
yc

Nota: Según el estándar Pascal, son incorrectas la declaraciones del tipo


dm

var s: array[1..10] of char


como hacemos en el programa maximo, y deberı́an declararse como
.a

var s: packed array[1..10] of char


w
w

Sin embargo, hemos preferido no adherirnos al estándar en este caso, para


no introducir el concepto de “packed”. Si el compilador no lo admite, habrá que
w

hacer la declaración según el estándar.


Observar también que el procedimiento para leer el arreglo es ahora diferente
al procedimiento leerarreglo del problema 8.1, pues no queremos entrar una letra
por renglón.
a) max es un procedimiento y no una función, ¿por qué?
b) Observar que el argumento t en max es del tipo “ tiporenglon ” y se pasa
por referencia (mediante “ var ”).
Nota: En otro caso, habrı́a que hacer una copia del arreglo (local a la
función), ocupando lugar y tiempo. En los ejemplos con los que estamos
trabajando, esto no tiene mayor importancia, debido al tamaño pequeño,
y en realidad serı́a más apropiado usar la copia local, ya que el arreglo no
se modifica dentro de la función.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 76 Todos juntos: arreglos, funciones y procedimientos

c) max supone que long > 0. Dar un ejemplo donde es 0 y modificar el pro-
grama para considerar este caso.
d ) Modificarlo de modo de encontrar también el mı́nimo, usando un nuevo
procedimiento.
e) Modificarlo de modo que se intercambien las posiciones del máximo y el
último elemento, imprimiendo el nuevo renglón. ✄
Problema 8.3. Desarrollar un programa que, dado un arreglo de enteros en-
trado como en el problema 8.1, y sin usar otro arreglo adicional, lo invierta, i.e.
si el arreglo inicialmente es (a1 , a2 , . . . , an ), el arreglo final es (an , . . . , a2 , a1 ).
Sugerencia: usar “ swap ” para intercambiar a1 ↔ an , a2 ↔ an−1 ,. . . ✄
Problema 8.4. Desarrollar un programa que leyendo un renglón (como en el
programa maximo), lo escriba al revés, e.g. si la entrada es
“ dabale arroz a la zorra el abad ”

m
el programa escriba

o
“ daba le arroz al a zorra elabad ”

.c
Sugerencia: ver el problema anterior. ✄

r os
fo
8.3. La caja de herramientas
.m

Iremos presentando cada vez menos programas completos, bajo el entendi-


miento de que cuestiones “comunes” ya han sido vistas, como leer o imprimir
na

arreglos.
tu

Es una buena idea armar una “caja de herramientas” formada por archivos
de texto en el disco, con las funciones o procedimientos que nos parecen más
on

usados, o quizás más difı́ciles de reproducir. De esta forma, cuando necesitemos


alguno, podemos simplemente hacer una copia en el programa, haciendo quizás
yc

pequeños cambios, sin necesidad de escribirlos —mucho menos pensarlos— cada


dm

vez.
Nota: Los sistemas de programación más avanzados incluyen en bibliotecas es-
.a

tas funciones o procedimientos, muchas veces en forma binaria difı́cil de mo-


dificar. Aunque similar, el concepto es ligeramente diferente del de la “caja de
w

herramientas”.
w

Por ejemplo, los procedimientos leerarreglo y escribirarreglo del problema 8.1


w

nos dan una “plantilla” (o “template”) para leer o imprimir arreglos de números
eventualmente modificando el ı́ndice inicial, e.g. “ array[1..MAXN] ”, o el tipo
de dato del arreglo, e.g “ real ” en vez de “ integer ”.
Problema 8.5 (La Caja de Herramientas). Copiar en sendos archivos de
texto2 , los procedimientos leerarreglo y escribirarreglo del problema 8.1, y prac-
ticar incorporarlos en un programa para leer y escribir un arreglo de enteros
(como en el problema 8.1). ✄
Seguramente habrá muchos procedimientos o funciones que querrás incorpo-
rar a la caja de herramientas, algunos que ya hemos visto como el método de la
bisección (sección 7.3), y otros que veremos más adelante, como alguno de los
métodos de búsqueda y clasificación del capı́tulo 10.
2 En algunos sistemas operativos, es conveniente que tengan la extensión “ .txt ”.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

8.4. Arreglos multidimensionales Pág. 77

8.4. Arreglos multidimensionales


Los arreglos unidimensionales no son apropiados para guardar la informa-
ción contenida en una tabla (como la del seno o del logaritmo). En estos casos
es más conveniente usar un “arreglo de arreglos”, también llamado arreglo mul-
tidimensional.
Por ejemplo, si queremos guardar una matriz de reales de 2 × 3, podemos
pensar que necesitamos un arreglo de dimensión 2 (cantidad de filas), cuyos
elementos individuales son arreglos de dimensión 3 (cantidad de columnas).
Hacemos entonces la declaración
m: array[1..2] of array[1..3] of real;
o equivalentemente,
m: array[1..2,1..3] of real;

m
y accedemos al elemento (i, j) usando indistintamente “ m[i][j] ” o “ m[i,j] ”.
Las matrices son un ejemplo de estructura de dos dimensiones, pero no hay

o
inconvenientes en considerar estructuras con cualquier número de dimensiones.

.c
Si en vez de números guardamos caracteres, las filas pueden pensarse como

os
renglones, como hacemos en el siguiente problema.

r
fo
Problema 8.6. En este problema consideraremos un texto como una serie de
renglones o lı́neas no vacı́as (cada una tiene al menos un carácter). Declaramos
.m

const
na

MAXC = 255; (* maxima cantidad de caracteres por renglon *)


MAXR = 20; (* maximo numero de renglones *)
tu
on

type
tiporenglon = array[1..MAXC] of char;
yc

tipotexto = array[1..MAXR] of tiporenglon;


caracteresenrenglon = array[1..MAXR] of integer;
dm

var
.a

nr, (* numero de renglones *)


w

nc (* numero de caracteres en renglon *)


: integer;
w

texto: tipotexto;
w

cenr: caracteresenrenglon;
eventualmente recordando la Nota en la pág. 75.
a) Desarrollar un procedimiento o función para leer no más de MAXR renglo-
nes, con no más de MAXC caracteres por renglón, dando como señal de fin
de entrada un “renglón vacı́o”.
Sugerencia: copiar las ideas del programa maximo.
Sugerencia si la anterior no alcanza:
nr := 0; (* numero de renglon leido, al ppo. ninguno *)
while (not eoln) do begin
(* mientras el renglon a leer no sea vacio... *)
nr := nr + 1; (* hay un renglon mas *)
nc := 0; (* que todavia no tiene caracteres leidos *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 78 Todos juntos: arreglos, funciones y procedimientos

repeat (* leemos los caracteres *)


nc := nc + 1; (* y los guardamos en el *)
read(texto[nr][nc]) (* lugar correspondiente *)
until (eoln); (* hasta llegar al fin del renglon *)
readln; (* leemos el fin de linea *)
cenr[nr] := nc (* nro. de caracteres en renglon nr *)
end;
b) Incorporar un procedimiento para escribir renglones, y verificar que el pro-
ceso de lectura desarrollada en el inciso anterior es correcto.
Nota: A veces arreglos como texto y cenr se dicen paralelos, pues la infor-
mación se va actualizando simultáneamente. Cuando veamos registros, en la
sección 10.4, veremos otra forma de guardar información de este tipo. ✄

8.5. “Strings”

o m
Problema 8.7. El tipo “ string ” no es estándar en Pascal, pero existe en casi

.c
todos los compiladores, en particular en Turbo Pascal. Probar el comportamien-

os
to del compilador con un programa que lee un renglón entrado por terminal, vı́a
“ readln(s) ”, donde se declara s como “ string[100] ”, indicando su longitud,
y/o simplemente como “ string ”.
r
fo
En caso de aceptar alguna de estas variantes, agregar al programa las ins-
.m
trucciones:
na

a) “ writeln(s) ”, para escribir s,


b) “ length(s) ”, para averiguar su longitud, y
tu

c) “ for i := 1 to length(s) do writeln( i:3, ’: ’, s[i]) ”, para es-


on

cribirlo carácter por carácter, indicando el ı́ndice.


d ) Modificar el programa del problema 8.6, cambiando el tipo “ renglon ” por
yc

el tipo “ string ”.
dm

Nota: Según el estándar Pascal, el tipo “string” es una denominación genéri-


ca para el tipo char y para arreglos empaquetados de caracteres, como los

.a

mencionados en la nota del problema 8.2.


w
w

8.6. Manejo elemental de archivos de texto


w

A veces resulta útil guardar los resultados obtenidos por un programa, para
después leerlos, imprimirlos o utilizarlos en otros programas. Dado el tamaño
de las “salidas” de los programas con los que trabajamos, será suficiente para
nosotros trabajar con archivos de texto, es decir, archivos que podemos abrir
(para leer o modificar) con un procesador de texto e imprimir directamente.
El lenguaje Pascal no es especialmente apto para leer y escribir archivos, y
es por ello que se han realizado muchas extensiones (no estándar) tratando de
mejorarlo, pero trataremos de ceñirnos al estándar. En él se establece otro tipo
elemental de datos, además de los que ya hemos visto: el tipo “ text ”, o archivo
de texto.
Dos estructuras fundamentales para el manejo de archivos son: copiar de
consola a archivo, y copiar de archivo a consola, que mostramos en los programas

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

8.6. Manejo elemental de archivos de texto Pág. 79

deconsolaaarchivo (pág. 180) y dearchivoaconsola (pág. 180) respectivamente, y


que pasamos a comentar.

• Al leer los programas, observamos que el el archivo a leer o escribir tiene


una variable asignada, curiosamente llamada archivo, declarada como de
tipo “ text ”. Una de las primeras cosas que hemos de hacer es relacionar
este identificador, interno al programa, con el nombre que el archivo tiene
o tendrá en el disco. Para eso, primeramente el usuario ingresa el nombre
tal como aparece o aparecerá en el disco.
Nota: Nos hemos permitido usar “ string ” para la variable correspon-
diente, aunque no es estándar y habrá que hacer modificaciones si el
compilador no lo acepta.

• Luego el nombre interno y el externo se relacionan mediante “ rewrite ”


si el archivo se escribirá, o mediante “ reset ” si el archivo se leerá.

m
Ha de tenerse cuidado con “ rewrite ”, pues si el archivo no existe, se crea

o
uno nuevo, pero si ya existe, sus contenidos son borrados.

.c
Nota: En compiladores que siguen el modelo de Turbo Pascal para en-

os
trada/salida, hay que modificar los programas, ya que no aceptan el
estándar.
r
fo
Para escribir un archivo, hay que cambiar el renglón
.m
rewrite(archivo, nombre);
por
na

assign(archivo, nombre); rewrite(archivo);


tu

en el programa deconsolaaarchivo. Del mismo modo, para leer desde un


on

archivo, hay que modificar el renglón


reset(archivo, nombre);
yc

por
dm

assign( archivo, nombre); reset(archivo);


en el programa dearchivoaconsola.
.a

• Luego de leer el nombre externo y relacionarlo con el interno, debemos


w

leer de consola e ir escribiendo el archivo en un caso, y recı́procamente,


w

leer del archivo y escribir en consola en el otro.


w

En el primer caso vemos una estructura similar a la lectura de renglones


del problema 8.6, excepto que al escribir no usamos “ writeln(c) ” sino
“ writeln(archivo, c) ” para escribir c en el archivo y no la consola, de-
biendo incoporar el nombre del archivo. Del mismo modo, “ writeln(ar-
chivo) ” escribe un “fin de lı́nea” terminando el renglón en el archivo.
En el segundo caso, cuando leemos del archivo en dearchivoaconsola e im-
primimos en la terminal, volvemos a encontrar una estructura conocida,
excepto que ahora el “fin de datos” que anteriormente señalabamos con
“<retorno> vacı́o”, ahora se señala de un modo especial para archivos de
textos: el “fin de archivo”. De modo similar a “ eoln ”, “ eof(archivo) ”
pregunta si ya hemos llegado a esta señal en el archivo que estamos leyen-
do.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 80 Todos juntos: arreglos, funciones y procedimientos

Nota: Hay que tener cuidado pues no debe llamarse a “ eoln ” si se ha


llegado al final del archivo: primero siempre debe llamarse a “ eof ”.

También, el usual “ readln ” se cambia por “ readln(archivo,c) ” para


leer c desde el archivo y no la consola.

• Al terminar de leer o escribir el archivo, usamos “ close(archivo) ”, lo


que hace que ya no se relacionen el nombre del archivo en el disco y la
variable nombre, y —en el caso de estar escribiendo— coloca en el archivo
la señal de “fin de archivo”.

Problema 8.8 (Archivos de texto).


Nota: Recordar los cambios a realizar si se sigue el modelo de Turbo Pascal,
como se mencionó en la Nota de la pág. 79.
a) Compilar y ejecutar el programa deconsoloaarchivo, y verificar su compor-
tamiento abriendo el archivo creado con un editor de textos.

m
Nota: El “directorio” en el cual el programa creará o buscará el archivo

o
depende de la instalación del compilador. Las posibilidades son: dar una

.c
ubicación “absoluta”, indicando el “camino” completo; o crear un archivo

os
y encontrar dónde se instaló, a veces el mismo compilador permite deter-
minar el directorio donde se trabajará.

r
fo
Nota: En algunos sistemas operativos es conveniente poner la extensión
“ .txt ” a los archivos de texto.
.m

b) Del mismo modo, compilar y ejecutar el programa dearchivoaconsola.


na

c) Tomando las partes necesarias de los programas deconsolaaarchivo y dearchi-


voaconsola, y tal vez del problema 8.6, hacer un programa dearchivoaarchivo
tu

para copiar un archivo en otro (nuevo) archivo, y verificar con un editor de


textos que el nuevo archivo es correcto. Sugerencia: dar distintos nombres
on

a las variables para el archivo de entrada y el de salida. ✄


yc

Problema 8.9.
dm

a) Hacer un programa para crear un archivo con la lista de los 1000 prime-
ros primos, esencialmente modificando la salida del programa eratostenes
.a

(pág. 172), escribiendo 10 primos por renglón.


w

b) Hacer un programa para que leyendo el archivo creado en el inciso anterior,


determine cuántos primos terminan respectivamente en 1, 3, 7 y 9.
w

Sugerencia: tomando el programa dearchivoaconsola como base, poner en el


w

lugar adecuado algo como


while (not eof(archivo)) do
if (eoln(archivo)) then readln(archivo)
else begin
read(primo); unidades := primo mod 10;
if (unidades = 1) then cuenta1 := cuenta1 + 1
else if ...
haciendo las declaraciones e inicializaciones correspondientes.
Nota: Observar que, al menos en el rango considerado, la distribución entre
los que terminan en 1, 3, 7 o 9 es muy pareja.
El matemático francés Lejeune Dirichlet (1805–1859) demostró en 1837
que toda progresión aritmética a + bk, k = 1, 2, . . . , contiene infinitos primos

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

8.7. Problemas Adicionales Pág. 81

si mcd(a, b) = 1 (lo que implica el resultado de Euclides sobre que hay infinitos
primos). En el inciso b), si pensamos que b = 10, y a es cualquier número que
no tenga como factores a 2 ni a 5, el teorema de Dirichlet dice que hay infinitos
primos que terminan en 1, infinitos que terminan en 3, etc. ✄

8.7. Problemas Adicionales


Problema 8.10. Hacer un programa que dado un arreglo de n enteros, lo
rote en m posiciones hacia la izquierda, sin usar un arreglo auxiliar. E.g. si
n = 10 y m = 3, el arreglo (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) debe transformarse en
(3, 4, 5, 6, 7, 8, 9, 0, 1, 2).
Sugerencia: el problema consiste en transformar el vector de dos bloques AB
en BA. Si AR es la operación de invertir el arreglo A (ver problema 8.3),
(AR B R )R = BA. Implementar la inversión como función o procedimiento. ✄

m
Problema 8.11 (Polinomios interpoladores de Lagrange). Un polinomio

o
P (x) = an xn + an−1 xn−1 + · · · + a1 x + a0 , de grado a lo sumo n, está determi-

.c
nado por los n + 1 coeficientes. Supongamos que no conocemos los coeficientes,

os
pero podemos conocer los valores de P (x) en algunos puntos, ¿cuántos puntos
necesitaremos para determinar los coeficientes? Como hay n + 1 coeficientes,
r
fo
es natural pensar que quedarán determinados por n + 1 ecuaciones, i.e. que
bastarán n + 1 puntos.
.m

Este resultado es cierto, y se puede demostrar planteando un sistema de n+1


na

ecuaciones lineales, y viendo que su determinante (de Van der Monde que se
estudia en Álgebra Lineal) es no nulo.
tu

Dados (xi , yi ), i = 1, . . . , n + 1, el polinomio interpolador de Lagrange se


define como
on

X
n+1 Y x − xj
yc

P (x) = yi . (8.1)
xi − xj
dm

i=1 j6=i

Nota: El polinomio en general resulta de grado ≤ n, y no necesariamente n.


.a

Pensar, por ejemplo, en 3 puntos sobre una recta: determinan un polinomio de


grado 1 y no 2.
w

Nota: En Cálculo Numérico se ven formas más eficientes (esencialmente exten-


w

siones de la regla de Horner) para el cómputo de estos polinomios interpolado-


w

res.
a) Ver que efectivamente, P (x) definido por la ecuación (8.1) satisface P (xi ) =
yi para i = 1, . . . , n + 1.

0.5

π π π
6 4 2
π

Figura 8.1: aproximación de sen x (en trazo discontinuo) mediante un polinomio


de grado 3.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 82 Todos juntos: arreglos, funciones y procedimientos

b) Desarrollar un procedimiento para evaluar P (x) dado por la ecuación (8.1),


donde los datos son (xi , yi ), 1 ≤ i ≤ n + 1, y x. Aclaración: sólo se pide una
traducción “literal” de la ecuación (8.1).
c) Utilizarlo en un programa que calcule los coeficientes del polinomio de grado
a lo sumo 3 que pasa por los puntos (−1, 0), (0, 1), (1, 0), (2, 2), en el punto
x = −2.
d ) Ampliar el programa para calcular una aproximación de sen π/4, usando los
valores del seno para 0, π/6, π/2 y π.
Nota: Es usual poner π = 4 × arctan 1, aunque en este problema en par-
ticular no se necesita un valor especı́fico de π (considerar la función sen πx
en vez de sen x).
La figura 8.1 muestra cómo se parecen las gráficas de sen x y el polinomio
en el intervalo [0, π]. ✄

Problema 8.12 (El Juego de Nim3 ). Propongamos el siguiente juego:

o m
Dos jugadores, A y B, colocan un número arbitrario de fósforos sobre

.c
una mesa, separados en filas o grupos. El número de filas y el número

os
de fósforos en cada fila también son arbitrarios. El primer jugador,
A, toma cualquier número de fósforos de un fila, pudiendo tomar
r
uno, dos o hasta toda la fila, pero sólo debe modificar una fila. El
fo
jugador B juega de manera similar con los fósforos que quedan, y
.m

los jugadores van alternándose en sus jugadas. Gana el jugador que


saca el último fósforo.
na

Veremos que, dependiendo de la posición inicial, uno u otro jugador tiene


tu

siempre una estrategia ganadora. Para esto, digamos que una disposición de los
on

fósforos es una posición ganadora para el jugador X, si dejando X los fósforos


en esa posición, entonces no importa cual sea la jugada del oponente, X puede
yc

jugar de forma tal de ganar el juego. Por ejemplo, la posición en la cual hay dos
filas con dos fósforos cada una, es ganadora: si A deja esta posición a B, B debe
dm

tomar uno o dos fósforos de una fila. Si B toma 2, A toma los dos restantes. Si
B toma uno, A toma uno de la otra fila. En cualquier caso, A gana.
.a

a) Ver que la posición en donde hay 3 filas con 1, 2 y 3 fósforos respectivamente,


w

es ganadora.
w

Para encontrar una estrategia ganadora, formamos una tabla expresando el


w

número de fósforos de cada fila en binario, uno bajo el otro, poniendo en una
fila final “ P ” si la suma total de la columna correspondiente es par, e “ I ” en
otro caso. Por ejemplo, en las posiciones anteriores harı́amos:

0 1
1 0
1 0
1 0 y
1 1
P P
P P
En el último ejemplo, es conveniente poner 01 en vez de sólo 1 en la primer
fila a fin de tener todas las filas con igual longitud.
3 Tomado de G. H. Hardy y E. M. Wright, An Introduction to the Theory of Numbers, 4.a

ed., (Oxford University Press, 1975), pág. 117 y siguientes.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

8.7. Problemas Adicionales Pág. 83

Si la suma de cada columna es par decimos que la posición es correcta, y


en cualquier otro caso decimos que la posición es incorrecta. Por ejemplo, la
posición donde hay 1, 3 y 4 fósforos es incorrecta:

| = 0 0 1
| | | = 0 1 1
| | | | = 1 0 0
I I P
b) En este inciso, veremos que una posición en Nim es ganadora si y sólo si es
correcta.
i) Si ninguna fila tiene más de un fósforo, la posición es ganadora ⇔ hay
en total un número par de fósforos ⇔ la posición es correcta.
ii) Si un número a ∈ N, expresado en binario por (an , an−1 , . . . , a1 , a0 )
(permitiendo an = 0) es reemplado por otro menor b (entero ≥ 0),

m
expresado en binario como (bn , bn−1 , . . . , b1 , b0 ), entonces para algún
i, 0 ≤ i ≤ n, las paridades de ai y bi son distintas.

o
iii) Por lo tanto, si el jugador X recibe una posición correcta, necesaria-

.c
mente la transforma en incorrecta.

os
iv ) Supongamos que estamos en una posición incorrecta, es decir, al menos

r
la suma de una columna es impar. Para fijar ideas supongamos que las
fo
paridades de las columnas son
.m

P P I P I P
na

Entonces hay al menos un 1 en la tercera columna (la primera con


suma impar). Supongamos, otra vez para fijar ideas, que una fila en la
tu

cual esto pasa es (en binario)


on

- -
0 1 1 1 0 1
yc

donde marcamos con “ - ” que los números debajo están en columnas


dm

de suma impar. Cambiando 0’s y 1’s por 1’s y 0’s en las posiciones
marcadas, obtenemos el número (menor que el original, expresado en
.a

binario):
w

- -
w

0 1 0 1 1 1
w

Está claro que este cambio corresponde a un movimiento permitido,


haciendo la suma de cada columna par, y que el argumento es general.
v ) Por lo tanto, si el jugador X recibe una posición incorrecta, puede
mover de modo de dejar una posición correcta.
vi) Si A deja una posición correcta, B necesariamente la convierte en
incorrecta y A puede jugar dejando una posición correcta. Este proceso
continuará hasta que cada fila quede vacı́a o contenga un único fósforo
(el caso i)).
c) En base a los incisos anteriores, y suponiendo que A y B siempre juegan de
modo óptimo, ¿quién ganará?
d ) Ver que la “jugada ganadora” del inciso b.iv ) no siempre es única.
e) Desarrollar un programa que, ingresada una posición inicial en la forma de

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 84 Todos juntos: arreglos, funciones y procedimientos

una lista con el número de fósforos en cada fila, decida si la posición es


correcta o incorrecta, y en este caso encuentre una jugada ganadora.
f ) ¿cuál es la “estrategia” si el que saca el último fósforo es el que pierde? ✄

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 9

Números Aleatorios y
Simulación

o m
.c
Muchas veces se piensa que en matemáticas las respuestas son “siempre exac-

os
tas”, olvidando que las probabilidades forman parte de ella y que son muchas
las aplicaciones de esta teorı́a.
r
fo
Una de estas aplicaciones es la simulación, técnica muy usada por fı́sicos,
ingenieros y economistas cuando es difı́cil llegar a una fórmula que describa el
.m

sistema o proceso. Ası́, simulación es usada para cosas tan diversas como el
estudio de las colisiones de partı́culas en fı́sica nuclear y el estudio de cuántos
na

cajeros poner en el supermercado para que el tiempo de espera de los clientes


tu

en las colas no sea excesivo.


La simulación mediante el uso de la computadora es tan difundida, que hay
on

lenguajes de programación (en vez del Pascal o “C”) especialmente destinados


a este propósito.
yc
dm

9.1. Números aleatorios


.a

Cuando en la simulación interviene el azar o la probabilidad, se usan núme-


w

ros generados por la computadora que reciben el nombre de “aleatorios” (o, más
w

correctamente, “seudo-aleatorios”). No es nuestra intención aquı́ hacer una des-


w

cripción de qué son estos números o cómo se obtienen: basta con la idea intuitiva
de que la computadora nos da un número en cierto rango, y cualquier número
del rango tiene la misma probabilidad de ser elegido por la computadora.
En general, los números aleatorios se obtienen a partir de un valor inicial o
semilla (“seed” en inglés); de modo que si no se indica lo contrario —cambiando
la semilla— siempre obtenemos la misma sucesión de números aleatorios. Un
método eficaz para obtener “números verdaderamente aleatorios”, es cambiar
la semilla de acuerdo a la hora que indica el reloj de la computadora.
Lamentablemente, en el estándar Pascal no está definida una función para
generar números aleatorios. Aún cuando un compilador tenga implementada
una tal función, el nombre con la que se llama y aún el tipo de resultado —por
ejemplo si son números reales entre 0 y 1 o enteros entre 0 y maxint —, dependen
también del compilador.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 86 Números Aleatorios y Simulación

Dado que muchos compiladores siguen el modelo de Turbo Pascal, nosotros


seguiremos la notación de este compilador para no complicar más de lo necesa-
rio, apartándonos del estándar. Para los lectores cuyo compilador no sigan ese
modelo para números aleatorios, hemos incluido instrucciones al final de este
capı́tulo, en la sección 9.4.
Turbo Pascal cuenta con las funciones “ random ” y “ randomize ”, y la va-
riable randseed . “ random ” sin argumentos da un número aleatorio entre 0 y 1;
y cuando incluimos un argumento n ∈ N, “ random(n) ” da un número aleatorio
entero entre 0 y n − 1. La semilla es la variable “ randseed ”, que puede cam-
biarse de acuerdo al tiempo interno de la computadora mediante la instrucción
“ randomize ”.

9.2. Aplicaciones

m
Problema 9.1. El programa dado (pág. 181) hace una simulación de tirar un
dado mediante números aleatorios obtenidos con la sentencia “ random ”.

o
.c
a) La sentencia “ randomize ” sirve para comenzar una nueva serie de números
aleatorios. Eliminarla, comentándola, ejecutar repetidas veces el programa

os
y comprobar que siempre se obtienen los mismos resultados (o sea no es
muy al azar).
r
fo
Nota: Salvo para programas que tienen un tiempo de ejecución grande, no
.m
debe hacerse más de una llamada a “ randomize ”.
b) Modificar el programa para simular tirar una moneda con resultados “cara”
na

o ”ceca”. ✄
tu

Problema 9.2. El programa dados (pág. 182) hace una simulación para en-
on

contrar la cantidad de veces que se necesita tirar un dado hasta que aparezca un
número prefijado, entrado por el usuario. Gracias a la sentencia “ randomize ”, el
yc

resultado en general será distinto con cada ejecución. Observar que si el usuario
entra un número menor que 1 o mayor que 6, el programa no termina nunca.
dm

a) Ejecutar el programa repetidas veces, para tener una idea de cuánto tarda
en aparecer un número.
.a

b) En vez de correr varias veces el programa, modificarlo de modo de realizar


w

n simulaciones (n entrado por el usuario), mostrando como resultado el


w

promedio de los tiros en que tardó en aparecer el número predeterminado.


w

Sugerencia: encerrar el lazo “ repeat ” dentro de un lazo “ for ”.


c) Modificar el programa original a fin de simular que se tiran simultáneamente
dos dados, y contar el número de tiros necesarios hasta obtener un resultado
ingresado por el usuario (entre 2 y 12). ✄

Problema 9.3. Tomando como base el problema anterior y el programa dados,


hacer un programa que diga cuántas veces debió tirarse un dado hasta que
aparecieron k seis consecutivos, donde k es ingresado por el usuario. Sugerencia:
poner un contador c inicialmente en 0, y dentro de un lazo “ repeat ” (donde
se hace la llamada a “ random ”) el contador se incrementa en 1 si salı́o un seis
y si no se vuelve a 0.
Nota: En éste y en el problema 9.2, surge la duda de si el programa termi-
nará alguna vez, dado que existe la posibilidad de que nunca salga el número

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

9.2. Aplicaciones Pág. 87

prefijado, o que nunca salga k veces consecutivas. Sin embargo, se puede demos-
trar matemáticamente que la probabilidad de que esto suceda es 0 (suponiendo
que el generador de números aleatorios sea correcto). ✄
Problema 9.4. El compilador de Pascal de Ana tiene la sentencia “ aleat ”,
que obtiene números aletorios r ∈ R con 0 ≤ r < 1, i.e. como la sentencia
“ random ” de Turbo Pascal sin argumentos.
a) ¿Qué instrucción debe poner Ana en vez de “ random(n) ” (que no es re-
conocida por su compilador) para hacer los problemas anteriores sobre los
dados? Sugerencia: multiplicar y usar “ trunc ”.
b) El profesor le ha dado un problema donde tiene que obtener 1 o −1 con
igual probabilidad. ¿Qué sentencias deberı́a usar?
c) ¿Y si necesitara números reales entre a y b (que pueden ser a pero no b),
donde a < b? ✄

m
Existen muchos métodos, que reciben el nombre de métodos de Monte-Carlo,
para aproximar cantidades determinı́sticas, i.e. no aleatorias, mediante proba-

o
bilidades. El próximo problema es un ejemplo de la técnica.

.c
os
Problema 9.5. Hacer un programa para calcular π tomando n pares de núme-
ros aleatorios (a, b), con a y b entre −1 y 1, contar cuántos de ellos están dentro
r
del cı́rculo unidad, i.e. a2 + b2 < 1. El cociente entre este número y n (ingresado
fo
por el usuario), es aproximadamente la proporción entre las áreas del cı́rculo de
.m

radio 1 y el cuadrado de lado 2. ✄


na

Problema 9.6.
a) Desarrollar un programa para hacer una lista de r números enteros, elegidos
tu

“aleatoria y uniformemente” entre 0 y s − 1, donde r, s ∈ N son entrados


on

por el usuario.
b) Modificar el programa de modo que, recorriendo linealmente la lista genera-
yc

da en el inciso anterior, al terminar imprima la cantidad de veces que cada


elemento se repite cuando r  s. Sugerencia: agregar un segundo arreglo
dm

para contar las apariciones.


.a

Nota: La cantidad de apariciones deberı́an ser muy similares, aproxima-


damente r/s cada uno. ✄
w

Problema 9.7 (Dos con el mismo cumpleaños). Mucha gente suele sor-
w

prenderse cuando en un grupo de personas hay dos con el mismo dı́a de cum-
w

pleaños: la probabilidad de que esto suceda es bastante más alta de lo que se


cree normalmente.
Supongamos que en una sala hay n (n ∈ N) personas y supongamos, para
simplificar, que no hay años bisiestos (no existe el 29 de febrero), de modo que
podemos numerar los posibles dı́as de cumpleaños 1, 2, . . . , 365.
a) ¿Para qué valores de n se garantiza que haya al menos dos personas que
cumplen años el mismo dı́a? Sugerencia: recordar el principio del casillero,
también conocido como del palomar o de Dirichlet.
Nota: El principio de Dirichlet dice que si hay n + 1 objetos repartidos en
n casillas, hay al menos una casilla con 2 o más objetos.
b) Si la sala es un cine al cual van entrando de a una las personas, ¿cuántas
personas, en promedio, entrarán hasta que dos de ellas tengan el mismo

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 88 Números Aleatorios y Simulación

dı́a de cumpleaños? Responder esta pregunta escribiendo un programa que


genere aleatoriamente dı́as de cumpleaños (números entre 1 y 365) hasta que
haya dos que coincidan, retornando la cantidad de “personas” necesarias.
Hacer varias “corridas” para tener una idea más acabada.
Si en el programa se usan arreglos, ¿cuál será la dimensión, de acuerdo
al inciso anterior?
c) Basado en el punto anterior, si en tu curso hay 30 compañeros, ¿apostarı́as
que hay dos que cumplen años el mismo dı́a? ✄

9.3. Problemas Adicionales


Problema 9.8 (Aproximando e con dados). Hacer un programa para si-
mular una máquina que emite números al azar (uniformemente distribuidos) en
el intervalo (0, 1) uno tras otro hasta que su suma excede 1. Comprobar que al

m
usar la máquina muchas veces, la cantidad promedio de números emitidos es

o
aproximadamente e = 2.71828 . . ..

.c
Nota: Éste es otro ejemplo del método de Monte-Carlo, esta vez para aproximar
e. ✄

os
Problema 9.9 (El Show de Televisión). Un problema que causó gran re-
r
fo
vuelo hacia 1990 en Estados Unidos es el siguiente:
.m

En un show televisivo el locutor anuncia al participante que detrás


na

de una de las tres puertas que ve hay un auto 0 km, no habiendo nada
detrás de las otras dos. El locutor le pide al participante que elija
tu

una de las puertas. Sin abrir la puerta elegida por el participante, el


locutor abre otra puerta mostrándole que no hay nada detrás de ésta,
on

y le da la opción al participante de cambiar su elección (pudiendo


yc

optar entre mantener su primera elección o elegir la tercer puerta).


El participante gana el auto si éste está detrás de la puerta que elige
dm

finalmente.
.a

a) Intuitivamente y sin dar una justificación rigurosa, ¿es más conveniente para
el participante mantener su primera elección, cambiarla o es indiferente?
w

b) Hacer un programa que simule el juego para tener mejor idea: i) el programa
w

elige una de las puertas donde estará el auto (sin que nosotros sepamos
w

el resultado); ii) nosotros hagamos una elección; iii) que en base a esta
elección, el “locutor” elija una segunda puerta detrás de la cual no está el
auto, al azar si hay dos posibilidades; iv ) que nosotros mantengamos o
cambiemos la elección; y v ) que diga si ganamos o no el auto.
c) Modificar el programa anterior de modo de hacer automáticamente n jue-
gos en los que siempre elegimos una puerta al azar y luego cambiamos,
retornando el número de veces que ganamos. Correrlo para n = 1000 (¡pri-
mero hacerlo para n = 5 para ver si está funcionando bien!). Comparar el
resultado con la respuesta en a). ✄

Problema 9.10 (Aguja de Buffon). Una aguja fina (teóricamente un seg-


mento) de longitud l ≤ 1, es arrojada sobre un tablero dividido por lı́neas
paralelas donde la distancia entre dos consecutivas es 1. Mostrar, mediante un

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

9.3. Problemas Adicionales Pág. 89

programa, que el promedio de veces que la aguja cruzará una de las lı́neas es
aproximadamente 2l/π.
Georges Louis Leclerc, Conde de Buffon (1707–1788) planteó este problema
en 1733, dando vigor a la teorı́a de probabilidad geométrica. El problema es
más que nada de interés teórico e introductorio al tema, pues las aproximacio-
nes experimentales de π no son buenas.

Problema 9.11. Hacer un programa para simular el juego de la generala en el


que se tiran 5 dados, y se tiene “full” cuando hay 3 dados con un mismo número
y los otros 2 con otro número, “póker” cuando hay 4 dados iguales, “escalera”
cuando hay 5 números consecutivos, y “generala” cuando los 5 dados son iguales.
Sugerencia: una vez “tirados” los dados, clasificarlos para determinar si se ha
obtenido alguno de los juegos. ✄
Problema 9.12. Queremos resolver el problema de encontrar una “mano” de
los números enteros entre 1 y n, es decir, queremos “mezclar” un mazo de n

m
cartas de un mismo palo (matemáticamente, obtener una permutación aleatoria

o
de los números entre 1 y n).

.c
Presentamos varias posibilidades:

os
a) Imaginando que empezamos con la cartas numeradas de 1 a n, ordenadas
crecientemente, hacemos el siguiente procedimiento: sacamos una carta al
r
fo
azar entre 1 y n y la separamos, de las restantes sacamos una al azar (entre
1 y n − 1) y la separamos, y ası́ sucesivamente. Sin embargo, computacio-
.m

nalmente esta tarea no es sencilla, debido al manejo que debemos hacer de


listas.
na

b) Una variante es: al elegir la primera carta la colocamos adelante y ponemos


tu

la primera carta en el lugar de la elegida. Luego elegimos al azar alguna de


las cartas que están en las posiciones entre 2 y n y la intercambiamos con la
on

segunda, y ası́ sucesivamente. Implementar esta idea computacionalmente.


yc

c) En el algoritmo anterior, podrı́amos reemplazar el intercambiar la carta


elegida con la primera, después con la segunda, etc., por intercambiar con
dm

la última, después con la ante-última, etc. Implementar también esta idea


computacionalmente.
.a

Nota: Aunque el problema de las “manos” es muy sencillo de plantear, y se usa


w

con mucha frecuencia, encontrar algoritmos correctos y eficientes no es sencillo.


w

La corrección de un algoritmo se refiere a que 1) todas las permutaciones de


1, . . . , n se pueden generar de esta forma, y 2) con igual probabilidad. Otro
w

problema es el de decidir su eficiencia: cuánto tiempo e intercambios realiza en


términos de n. Ninguna de éstos es sencillo, y no lo haremos aquı́.
Nota: El algoritmo en b) está presentado en [4, Vol. 2, pág. 126], donde se
hacen los análisis de corrección y eficiencia. Es atribuı́do independientemente
a L. E. Moses y R. V. Oakford (1963) y R. Durstenfeld (1964).
Nota: Existen muchos otros algoritmos relacionados con este problema. Los
interesados pueden consultar el libro de Knuth ya mencionado. Otro algoritmo
interesante, debido a R. Floyd, está descripto en [1]. ✄
Problema 9.13. Usando alguno de los algoritmos del problema anterior, hacer
un programa para simular una “mano” de m cartas de un total de n. Por
ejemplo, para un jugador en el truco elegirı́amos m = 3 y n = 40.
Nota: ¡No es necesario generar toda la permutación! ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 90 Números Aleatorios y Simulación

Problema 9.14. Al estudiar los datos numéricos (a1 , a2 , . . . , an ) estadı́stica-


mente, se consideran (entre otros) tres tipos de “medidas”:
1 X
n
La media o promedio: ai .
n i=1
La mediana: Intuitivamente es un valor a` tal que la mitad de los datos
son menores o iguales que a` y la otra mitad son mayores o iguales que
a` . Para obtenerla, se ordenan los datos y la mediana es el elemento
del medio si n es impar y es el promedio de los dos datos en el medio
si hay un número par de datos.
Nota: En realidad no es necesario ordenar todos los datos para ob-
tener la mediana. Por otra parte, observar que la mediana puede no
ser un dato en el caso de n par.
La moda: Es un valor a` con frecuencia máxima, y puede haber más de
una moda.

m
Por ejemplo, si los datos son 8, 0, 4, 6, 7, 8, 3, 2, 7, 4, la media es 4.9, la mediana

o
es 5, y las modas son 4, 7 y 8.

.c
Hacer un programa para generar aleatoriamente un arreglo de longitud n

os
de números entre 0 y 9, y luego encontrar la media, mediana y moda/s (n es
ingresado por el usuario). ✄
r
fo
Problema 9.15. Desarrollar un procedimiento que, dado un arreglo de núme-
.m
ros reales, encuentre el arreglo de sumas parciales o acumuladas, i.e. si el arreglo
inicial es a = (a1 , a2 , . . . , an ), encuentre (a1 , a1 + a2 , a1 + a2 + a3 , . . . ). ✄
na

Problema 9.16.
tu

a) Hacer un programa que elija aleatoriamente el número 1 aproximadamente


on

el 45 % de las veces, el número 2 el 35 % de las veces, el 3 el 15 % de las


veces y el 4 el 5 % de las veces. Sugerencia: considerar las sumas parciales
yc

.45, .45 + .35, . . .


b) Generalizar al caso en que en vez de la lista (1, 2, 3, 4) se dé una lista
dm

(a1 , a2 , . . . , an ) y que en vez


Pn de las frecuencias (.45, .35, .15, .5) se dé una
lista (f1 , f2 , . . . , fn ), con i=1 fi = 1. Probarlo para distintos valores y
.a

verificar que las frecuencias son similares a las deseadas. ✄


w
w

9.4. Una función para números aleatorios


w

Si no se dispone de una rutina para generar números aleatorios, es necesario


instalar una propia. Sin embargo, obtener números aleatorios con la computa-
dora no es tan sencillo como tirar dados, y hay mucha teorı́a matemática detrás
de los “buenos” generadores: el lector interesado puede consultar el excelente
libro de Knuth [4, vol. 2].
Nos contentaremos con presentar un generador que aparece como ejemplo
en el mismo estándar de Pascal extendido (ISO 10206) adaptado al estándar
estándar de Pascal sin extensiones que es el que usamos. El ejemplo en el
estándar está tomado, a su vez, del artı́culo de Wichmann and Hill, Building a
Random-Number Generator, Byte, marzo 1987, págs. 127–128.
Muchos de los generadores usan congruencias lineales, y éste no es excepción.
La diferencia es que, cuando tenemos el lı́mite de maxint = 32767, como en

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

9.4. Una función para números aleatorios Pág. 91

muchos compiladores viejos, trabajar con una congruencia no es satisfactorio,


por lo que el generador que presentamos usa tres, necesitando sı́ que maxint ≥
30323.
Para la implementación, hacemos uso del procedimiento randinic y la fun-
ción random. Por cierto que no es necesario entender qué hacen, sólo hay que
copiarlos y usarlos. Primero damos la descripción y luego instrucciones para su
uso.

9.4.1. Procedimiento “randinic”


procedure randinic;
begin
randsemilla1 := 1;
randsemilla2 := 10000;
randsemilla3 := 3000;

m
end;

o
.c
9.4.2. Función “random”

r os
function random: real;
fo
const
.m
p1 = 30269; m1 = 171;
p2 = 30307; m2 = 172;
na

p3 = 30323; m3 = 170;
tu

var
on

x1, x2, x3: integer;


temp: real;
yc

begin
dm

(* ajustar semillas *)
randsemilla1 := randsemilla1 mod p1;
.a

if (randsemilla1 < 1) then randsemilla1 := 1;


w

randsemilla2 := randsemilla2 mod p2;


w

if (randsemilla2 < 1) then randsemilla2 := 10000;


w

randsemilla3 := randsemilla3 mod p3;


if (randsemilla3 < 1) then randsemilla3 := 3000;

(* primer generador *)
x1 := m1*(randsemilla1 mod 177) - 2*(randsemilla1 div 177);
if x1 < 0 then x1 := x1 + p1;

(* segundo generador *)
x2 := m2*(randsemilla2 mod 176) - 35*(randsemilla2 div 176);
if x2 < 0 then x2 := x2 + p2;

(* tercer generador *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 92 Números Aleatorios y Simulación

x3 := m3*(randsemilla3 mod 178) - 63*(randsemilla3 div 178);


if x3 < 0 then x3 := x3 + p3;

(* formar nuevas semillas y resultado *)


randsemilla1 := x1;
randsemilla2 := x2;
randsemilla3 := x3;
temp := randsemilla1/p1 + randsemilla2/p2 + randsemilla3/p3;
random := temp-trunc(temp)
end;

9.4.3. Instrucciones para su uso


• randinic y random deben colocarse antes que otras funciones o procedi-
mientos, a fin de no generar conflictos.

m
• randsemilla1 , randsemilla2 y randsemilla3 deben declararse como varia-

o
bles globales de tipo “ integer ”.

.c
• La llamada a randinic debe ir al principio del cuerpo principal, también a

os
fin de evitar problemas.

r
fo
• random da un número aleatorio r, con 0 ≤ r < 1. Si se desea imitar la
llamada “ random(n) ” de Turbo Pascal, habrá que cambiar el encabezado
.m

de random, poniendo
na

function random(n: integer): integer;


tu

y reemplazar el renglón final de random por


on

random := trunc((temp-trunc(temp)) * n)
yc

• No es posible cambiar alguna de las semillas con el reloj de la computa-


dm

dora, i.e. no podemos imitar “ randomize ” de Turbo Pascal, y el cambio


tendrá que ser manual. Para ello, bastará cambiar sólo una de las semillas,
.a

digamos la primera, poniendo un procedimiento como


w

procedure cambiarsemilla;
w

begin
w

write(’** El valor actual de la semilla es ’);


writeln(randsemilla1);
write(’ Entrar el nuevo valor (entero): ’);
readln(randsemilla1)
end;

haciendo la llamada en el lugar apropiado del cuerpo principal (en nuestros


ejemplos de programas, en vez de “ randomize ”).

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 10

Búsqueda y clasificación

m
Siempre estamos buscando algo y es mucho más fácil encontrarlo si los datos
están clasificados u ordenados. No es sorpresa que búsqueda y clasificación sean

o
.c
temas centrales en informática y que haya una enorme cantidad de material
escrito al respecto. Por ejemplo, en sus clásicos libros [4] Knuth dedica al tema

os
todo el Volumen 3 de (que por supuesto, usa material de los volúmenes anterio-
res). Acá hacemos una introducción al tema siguiendo, en mı́nima proporción,
r
fo
la presentación de Wirth en [6].
.m
na

10.1. Búsqueda lineal con centinela


tu

Empecemos recordando lo hecho en el problema 6.2 y el programa busquedali-


on

neal en el que recorrı́amos “linealmente” el arreglo a = (a1 , a2 , . . . , an ) buscando


x.
yc

Problema 10.1. Al buscar x en el arreglo (a1 , a2 , . . . , an ), el programa bus-


dm

quedalineal (pág. 170) usa un lazo “ repeat ”:


i := 0;
.a

repeat i := i + 1; seencontro := (a[i] = x)


w

until (seencontro or (i = n));


w

if (seencontro) then ... (* se encontro en la posicion i *)


w

¿Con cuáles de las siguientes alternativas se podrı́a reemplazar este lazo?:


a) i := 0;
repeat i := i + 1 until ((i = n) or (x = a[i]));
if (x = a[i]) then ... (* se encontro en la posicion i *)
b) i := 1;
while ((i <= n) and (x <> a[i])) do i := i + 1;
if (i <= n) then ... (* se encontro en la posicion i *)
c) i := 0; seencontro := false;
while ((not seencontro) and (i < n)) do begin
i := i + 1; seencontro := (x = a[i])
end;
if (seencontro) ... (* se encontro en la posicion i *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 94 Búsqueda y clasificación

d ) i := n;
while ((a[i] <> x) and (i > 1)) do i := i - 1;
if (x = a[i]) then ... (* se encontro en la posicion i *) ✄

En el lazo “ while ” o “ repeat ” de los esquemas en el problema anterior para


búsqueda lineal se hacen dos comparaciones, pero podemos mejorarlo haciendo
una sola colocando a x como “centinela” en alguna posición de modo que siempre
lo encontremos.
Por ejemplo, una modificación del esquema del inciso d ) del problema an-
terior —que no es correcto— suponiendo que el arreglo a está dimensionado
adecuadamente, es:
(* a debe ser declarado de modo de admitir a[0] *)
a[0] := x; i := n;
while (x <> a[i]) do i := i - 1;
if (i > 0) then ... (* se encontro en la posicion i *)

m
Observamos que siempre termina, ya sea porque x es un elemento del arreglo,

o
.c
en cuyo caso i es el lugar que ocupa, o bien porque no se encontró, en cuyo caso
i = 0.

os
Nota: No hay nada misterioso en ir desde atrás hacia adelante, podrı́amos haber

r
puesto el “centinela” en la posición n + 1 y recorrer de adelante hacia atrás.
fo
Problema 10.2 (Búsqueda lineal con centinela). Hacer una implementa-
.m

ción con ambas variantes (con y sin centinela) como procedimientos, incluyendo
na

un contador para la cantidad de comparaciones en cada una, y probar el com-


portamiento en distintos ejemplos (entrar el arreglo como en el procedimiento
tu

leerarreglo, pág. 74). ✄


on

Problema 10.3. En este problema queremos hacer un procedimiento para eli-


minar elementos repetidos de la lista de enteros a = (a1 , a2 , . . . , an ).
yc

a) Si a está ordenada de menor a mayor, e.g. a = (1, 2, 2, 5, 6, 6, 9), queremos


dm

que al fin del procedimiento sea a = (1, 2, 5, 6, 9). En este caso podemos
poner algo como
.a

procedure purgarordenado(var a: arreglo; var n: integer);


w

(* sacar elementos repetidos del arreglo


ordenado a de longitud n. *)
w

var i, m: integer;
w

begin
m := 1; (* a[1],...,a[m] son los elementos sin repetir *)
for i := 2 to n do
(* incluir a[i] si no es a[m] *)
if (a[m] < a[i]) then begin
m := m + 1; a[m] := a[i] end;
n := m
end;
Hacer un programa para verificar el comportamiento de este procedi-
miento.
b) Hacer un procedimiento (sin usar un arreglo auxiliar) para el caso general,
preservando en la salida el orden en que aparecen los elementos originalmen-

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.2. Búsqueda binaria Pág. 95

te. E.g. si inicialmente a = (3, 5, 2, 6, 2, 1, 3, 2), al final del procedimiento es


a = (3, 5, 2, 6, 1). ✄

Problema 10.4 (Fusión o mezcla de arreglos ordenados). Supongamos


que tenemos dos arreglos a = (a1 , a2 , . . . , an ) y b = (b1 , b2 , . . . , bm ) ordena-
dos de menor a mayor (con n, m ≥ 1), y queremos construir un arreglo c =
(c1 , c2 , . . . , ck ) que esté también ordenado y de modo que tenga los elemen-
tos de a y b. Por ejemplo, si a = (1, 2, 5, 5) y b = (2, 3, 4), deberá ser c =
(1, 2, 2, 3, 4, 5, 5).
Hacer un procedimiento para obtener c, usando un esquema como
i := 1; j := 1; k := 0;
repeat
k := k + 1;
if (a[i] <= b[j]) then begin c[k] := a[i]; i := i + 1 end
else begin c[k] := b[j]; j := j + 1 end

m
until ((i > n) or (j > m));

o
while (i <= n) do begin (* copiar el resto de a *)

.c
k := k + 1; c[k] := a[i]; i := i + 1 end;

os
while (j <= m) do begin (* copiar el resto de b *)
k := k + 1; c[k] := b[j]; j := j + 1 end
r
fo
Nota: El esquema se podrı́a mejorar usando centinelas, evitando algunas com-
.m
paraciones. ✄
na

10.2. Búsqueda binaria


tu
on

Cuando a = (a1 , a2 , . . . , an ) está ordenado, la búsqueda de x en a se facilita


enormemente, por ejemplo, al buscar en un diccionario o en una tabla.
yc

Quizás el método más eficiente para la búsqueda en un arreglo ordenado


es el de búsqueda binaria, que es el equivalente al método de la bisección para
dm

encontrar raı́ces de funciones, visto en el problema 7.3, sólo que en un entorno


“discreto” y no “continuo”: sucesivamente dividir en dos y quedarse con una de
.a

las mitades.
w

En lo que resta de esta sección, suponemos que a está ordenado no-decre-


cientemente, i.e. a1 ≤ a2 ≤ · · · ≤ an (y 1 ≤ n).
w
w

Problema 10.5 (Búsqueda Binaria).


a) Una primer idea es
poco := 1; mucho := n;
while (poco < mucho) do begin
medio := (poco + mucho) div 2;
if (a[medio] < x) then poco := medio
else mucho := medio
end;
(* a continuación comparar x con a[mucho] *)
Ver que esta idea no es del todo correcta. Sugerencia: considerar a =
(1, 3) y x = 2.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 96 Búsqueda y clasificación

b) El problema con el lazo anterior es que cuando

mucho = poco + 1, (10.1)

obtenemos

medio = poco. (10.2)


i) Verificar que (10.1) implica (10.2).
ii) Verificar que si en cambio, mucho ≥ poco +2, entonces poco < medio <
mucho.
iii) Si se cambiara el esquema del inciso a) a
poco := 1; mucho := n;
while (poco + 1 < mucho) do begin
medio := (poco + mucho) div 2;
if (a[medio] < x) then poco := medio

m
else mucho := medio

o
end;

.c
(* a continuación comparar x

os
con a[mucho] y con a[poco] *)
¿serı́a ahora correcto?
r
fo
c) Si en vez de considerar poco + 1 en la condición del lazo del inciso anterior,
.m

lo incorporamos a la asignación, llegamos a:


na

poco := 1; mucho := n;
while (poco < mucho) do begin
tu

medio := (poco + mucho) div 2;


if (a[medio] < x) then poco := medio + 1
on

else mucho := medio


yc

end
(* a continuación comparar x con a[mucho] *)
dm

En este inciso veremos que el nuevo esquema es correcto:


i) Verificar que en todo momento, poco ≤ mucho, la distancia entre ellos
.a

se acorta en cada “vuelta” del lazo, y al terminar es poco = mucho (y


w

no puede ser poco > mucho). Sugerencia: recordar el inciso b.ii).


w

ii) Si x ≤ a1 , poco queda fijo, y al final se compara x con a1 .


w

iii) Si x > an , mucho queda fijo, y al final se compara x con an (y obten-


dremos que x ∈ / a).
iv ) Si apoco < x ≤ amucho , en el próximo paso acortamos el arreglo donde
buscar x y puede ser que tengamos x ≤ apoco (habiendo incrementado
poco), reduciéndose al caso ii), o que siga siendo apoco < x ≤ amucho .
Continuando con el procedimiento llegamos siempre al caso ii), con
un arreglo de longitud 1, ya que al final poco = mucho.
d ) Hacer un procedimiento implementando búsqueda binaria con el esquema
del inciso anterior, y aplicarlo en un programa para encontrar un elemento
en un arreglo (¡ordenado no decrecientemente!), entrados por terminal. ✄
Problema 10.6 (Variantes de Búsqueda Binaria). Decidir cuáles de los
siguientes esquemas para búsqueda binaria son correctos, haciendo un análisis

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.2. Búsqueda binaria Pág. 97

similar al hecho con el esquema presentado. Suponemos que los datos son el
arreglo a = (a1 , a2 , . . . , an ) ordenado no-decrecientemente y x, y que al terminar
se hace la comparación entre ak y x.
a) i := 1; j := n;
repeat
k := (i + j) div 2;
if (a[k] < x) then i := k else j := k
until ((a[k] = x) or (i >= j))
b) i := 1; j := n;
repeat
k := (i + j) div 2;
if (x <= a[k]) then j := k - 1;
if (a[k] <= x) then i := k + 1
until (i > j)

m
c) i := 1; j := n;

o
repeat

.c
k := (i + j) div 2;
if (x < a[k]) then j := k else i := k + 1

os
until (i >= j)

r
d ) Muchos autores presentan un algoritmo un poco más ineficiente que el del
fo
problema 10.5.c), comparando x con amedio en cada paso de modo que hay
.m
dos comparaciones por “vuelta” de lazo:
na

poco := 1; mucho := n; encontrado := false;


while ((poco <= mucho) and (not encontrado)) do begin
tu

medio := (poco + mucho) div 2;


if (x = a[medio]) then begin
on

i := medio; encontrado := true end


else if (x > a[medio]) then poco := medio + 1
yc

else mucho := medio - 1


dm

end;
Ver que el esquema es correcto. ✄
.a

Problema 10.7. Se ha roto un cable maestro de electricidad en algún punto


w

de su recorrido subterráneo de 50 cuadras. La compañı́a local de electricidad


w

puede hacer un pozo en cualquier lugar para comprobar si hasta allı́ el cable
w

está sano, y bastará con detectar el lugar de la falla con una precisión de 5m.
Por supuesto, una posibilidad es ir haciendo pozos cada 5m, pero el encar-
gado no está muy entusiasmado con la idea de hacer tantos pozos, porque hacer
(y después tapar) los pozos cuesta tiempo y dinero, y los vecinos siempre se
quejan por el tránsito, que no tienen luz, etc.
¿Qué le podrı́as sugerir al encargado? ✄

Problema 10.8 (El regalo en las cajas). Propongamos el siguiente juego:

Se esconde un “regalo” en una de diez cajas alineadas de izquierda


a derecha, y nos dan cuatro oportunidades para acertar. Después de
cada intento nuestro, nos dicen si ganamos o si el regalo está hacia
la derecha o izquierda.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 98 Búsqueda y clasificación

a) Ver que siempre se puede ganar si nos dan cuatro oportunidades.


b) Simular este juego en la computadora: entrada una elección del usuario,
el programa responde si el regalo está en la caja elegida (y termina), a la
derecha o a la izquierda, orientando la búsqueda. Si después de cuatro opor-
tunidades no se acertó la ubicación, el programa termina dando el número
de caja donde estaba el regalo.
c) Cambiar el programa de cuatro a tres oportunidades, y ver que no siempre
se puede ganar.
d ) ¿Cuántas oportunidades oportunidades habrá que dar para n cajas, supo-
niendo una estrategia de búsqueda binaria (la respuesta involucra las fun-
ciones techo y log2 )? ✄
Problema 10.9. Hacer un programa para un juego como el del regalo en las
cajas (problema 10.8), pero ahora jugando en un tablero de m × n. El jugador
da las coordenadas (i, j), 1 ≤ i ≤ m, 1 ≤ j ≤ n, de la casilla que elige y la

m
búsqueda se orienta dando direcciones hacia donde está el regalo: N, NE, E, SE,

o
S, SO, O, NO.

.c
Recordando el problema del regalo en las cajas, ¿cuántas oportunidades
oportunidades habrá que dar en el caso de las m × n casillas? ✄

r os
fo
10.3. Métodos elementales de clasificación
.m

Nuestra próxima tarea consistirá en ver cómo clasificar u ordenar un arre-


na

glo a = (a1 , a2 , . . . , an ) de elementos no necesariamente distintos, poniendo el


resultado en el mismo arreglo a —tratando de evitar usar otro arreglo—, pero
tu

con sus elementos ordenados de menor a mayor. Para ello veremos tres méto-
dos elementales para clasificar que podemos relacionar con la forma con que
on

generalmente cada uno ordena las cartas de un juego:


yc

Inserción directa: Levantamos una carta, buscamos su posición de iz-


quierda a derecha (o al revés) en el abanico que tenemos en la mano,
dm

la colocamos en el lugar correspondiente y continuamos levantando la


próxima carta. Podemos escribir el algoritmo resultante como:
.a

for i := 2 to n do begin
w

x := a[i]; a[0] := x; j := i;
w

while (x < a[j-1]) do begin


w

a[j] := a[j-1]; j := j-1


end;
a[j] := x
end
Nota: Observar el uso de centinela: a debe ser declarado de modo
de admitir a0 .
Selección directa: Levantamos las cartas al mismo tiempo (todas juntas),
las abrimos en abanico, y comenzando desde la izquierda (o derecha)
buscamos la menor y la colocamos al principio, luego buscamos la se-
gunda menor y la ubicamos en la segunda posición, etc. Acá podemos
poner:
for i := 1 to n-1 do begin
k := i; x := a[i];

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.3. Métodos elementales de clasificación Pág. 99

for j := i+1 to n do
if (x > a[j]) then begin k := j; x := a[k] end;
a[k] := a[i]; a[i] := x
end
Nota: Comparar con el inciso e) del problema 8.2.
Intercambio directo o burbujeo: Aquı́ también levantamos las cartas al
mismo tiempo y las abrimos en abanico, pero vamos mirando de iz-
quierda a derecha (o de derecha a izquierda) buscando un par de
cartas consecutivas fuera de orden y cuando lo encontramos lo inver-
timos. Seguimos recorriendo con la mirada el abanico hasta que no
haya ningún par fuera de orden. Un posible esquema es:
for i := 2 to n do
for j := n downto i do
if (a[j-1] > a[j]) then begin (* swap *)

m
x := a[j-1]; a[j-1] := a[j]; a[j] := x
end

o
.c
Nota: Observar la similitud entre la clasificación por selección directa o inser-
ción directa con la obtención de “manos” aleatorias, como en el problema 9.12,

os
incisos b) y a) respectivamente. Esto permite estudiar estos métodos teórica-
mente.
r
fo
Problema 10.10 (Métodos elementales de clasificación). Implementar
.m

los algoritmos anteriores como procedimientos en un único programa. Agregar


en cada uno contadores para el número de asignaciones y comparaciones (entre
na

datos como x o ak y no entre ı́ndices como i, j o k) hechas en cada caso, y


comparar estas cantidades cuando:
tu
on

a = (1, 2, 3, 4, 5, 6), a = (6, 5, 4, 3, 2, 1), a = (2, 6, 8, 7, 4, 5, 1, 3, 6, 4). ✄


yc

En el cuadro 10.1 mostramos una comparación entre los tres métodos y el


“mergesort” —un método avanzado que explicamos en el problema 10.19— con
dm

arreglos de 10 000 enteros construidos de tres formas: ordenado, ordenado al


revés, y aleatorio. Los tiempos en el cuadro son para una máquina particular
.a

(y no demasiado rápida para los estándares cuando se realizaron las pruebas),


y debe considerarse sólo la proporción entre ellos (la cifra 0.00 para el tiempo
w

indica que la fracción de segundo es menor a 1/60 = 0.0166 . . .). Se contaron las
w

comparaciones y asignaciones de arreglos (y no cuando se comparan o asignan


w

ı́ndices).
Observamos en la tabla que tanto selección directa como intercambio directo
realizan siempre el mismo número de comparaciones, n(n − 1)/2, y que aún
cuando no se hacen asignaciones (como en el caso de intercambio directo, cuando
el arreglo está ordenado), la enorme cantidad de comparaciones lleva tiempo.
En cuanto a las asignaciones, hemos de destacar que hemos tomado arreglos
de enteros, por lo que individualmente no llevan demasiado tiempo, y en el
caso de inserción directa con un arreglo aleatorio, hay muchas asignaciones pero
—comparando con los otros métodos elementales— no llevan tanto tiempo. Serı́a
distinto si los elementos fueran arreglos (o “strings”) al clasificar palabras).
Algunos autores usan búsqueda binaria en vez de búsqueda lineal en inser-
ción directa, disminuyendo en principio el número de comparaciones. Pero las
ventajas son marginales y aún pueden empeorar el algoritmo (ver [6, pág. 87]).

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 100 Búsqueda y clasificación

Arreglo ya ordenado
mergesort selección intercambio inserción
comparaciones 71 712 49 995 000 49 995 000 9 999
asignaciones 140 000 29 997 0 29 997
tiempo (segs.) 0.00 1.10 1.43 0.00

Arreglo ordenado al revés


mergesort selección intercambio inserción
comparaciones 64 608 49 995 000 49 995 000 50 004 999
asignaciones 140 000 25 029 997 149 985 000 50 024 997
tiempo (segs.) 0.00 1.27 1.87 1.45

Arreglo aleatorio
mergesort selección intercambio inserción

m
comparaciones 123 582 49 995 000 49 995 000 25 023 757

o
asignaciones 140 000 103 894 75 041 274 25 043 755

.c
tiempo (segs.) 0.00 1.10 1.75 0.72

r os
Cuadro 10.1: comparación de algoritmos de clasificación.
fo
.m

Muchos libros de programación insisten en presentar el método de intercam-


na

bio directo o burbujeo —y a veces ningún otro— que es claramente inferior a


cualquiera de los otros: aún en el caso del arreglo ordenado, se hace la misma
tu

cantidad de comparaciones que selección directa, y ninguna asignación, pero ¡el


tiempo es superior! Hemos incluido este método sólo para que el lector sepa
on

que existe y que es bastante malo: nosotros casi siempre elegiremos el de se-
yc

lección directa o inserción directa: selección directa hace relativamente pocas


asignaciones, inserción directa relativamente pocas comparaciones.
dm

Problema 10.11 (Clasificación por conteo). La clasificación es muy sencilla


si sabemos de antemano que el rango de los elementos de a = (a1 , a2 , . . . , an ) es
.a

pequeño. Por ejemplo, supongamos que los elementos de a son enteros entre 1 y
w

m, y pensemos que tenemos n bolitas alineadas representando el arreglo a, la k-


w

ésima pintada con el número ak : armamos m cajas numeradas de 1 a m, y vamos


colocando cada bolita en la caja que tiene su número. Luego será cuestión de
w

vaciar los contenidos de las cajas ordenadamente, empezando desde la primera,


alineando las bolitas a medida que las sacamos. En el algoritmo, en vez de
“colocar cada bolita en su caja”, contamos las veces que apareció:
for k := 1 to m do cuenta[k] := 0;
for i := 1 to n do cuenta[a[i]] := cuenta[a[i]] + 1;
i := 0;
for k := 1 to m do
while (cuenta[k] > 0) do begin
i := i + 1; a[i] := k; cuenta[k] := cuenta[k] - 1 end

a) Ver que el algoritmo es correcto, i.e. al finalizar el arreglo a está ordenado.


b) Implementar el algoritmo y comparar su eficiencia con los otros métodos,

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.3. Métodos elementales de clasificación Pág. 101

poniendo contadores para el número de asignaciones y comparaciones.


c) ¿A lo sumo cuántas asignaciones de arreglos se hacen en este algoritmo (la
respuesta involucra n y m)? ¿Cuándo lo usarı́as? ✄

Problema 10.12 (Arreglos como conjuntos). Cuando queremos tratar a


un arreglo a = (a1 , a2 , . . . , an ) como un conjunto, es decir no nos interesan los
elementos repetidos ni el orden, es conveniente primero ordenarlo (de menor a
mayor) y quitar sus elementos repetidos, por ejemplo con el procedimiento del
problema 10.3.a, para facilitar las operaciones entre conjuntos.
a) Hacer un procedimiento, que a su vez usa un procedimiento de clasifi-
car y otro de purgar, para “pasar el arreglo a a conjunto” ordenándolo
y “purgándolo”.
Nota: Aunque se puede hacer un único procedimiento modificando un pro-
cedimiento de clasificación elemental de modo de eliminar elementos re-

m
petidos, es más sencillo, y desde el punto de vista de la modularidad más
razonable, el llamar a dos procedimientos separados, uno para clasificar y

o
otro para “purgar”. Ası́, se puede cambiar, por ejemplo, el procedimien-

.c
to de clasificación por uno más conveniente sin tener que revisar todo el

os
procedimiento de este inciso.
Por otro lado, con métodos más avanzados es más eficiente primero
clasificar y luego “purgar” que al revés.
r
fo
b) Dados dos arreglos a = (a1 , a2 , . . . , an ) y b = (b1 , b2 , . . . , bm ), considerados
.m

como conjuntos, hacer un procedimiento para construir el “conjunto inter-


sección”, i.e. un arreglo ordenado c = (c1 , c2 , . . . , cr ) con todos los elementos
na

que están en a y b simultáneamente (pero aparecen en c una sola vez).


tu

Sugerencia: suponiendo que a = (a1 , a2 , . . . , an ) y b = (b1 , b2 , . . . , bm ) ya


están ordenados y purgados, una posibilidad es considerar el esquema
on

i := 1; j := 1; r := 0;
yc

repeat
if (a[i] > b[j]) then j := j + 1
dm

else if (a[i] < b[j]) then i := i + 1


else begin (* a[i] = b[j]: ponerlo en c *)
.a

r := r + 1;
c[r] := a[i];
w

i := i + 1;
w

j := j + 1
end
w

until ((i > n) or (j > m))

Cuando r = 0, los arreglos originales no tienen elementos comunes.


c) Del mismo modo, hacer un procedimiento para construir el “conjunto u-
nión”, i.e. un arreglo ordenado c = (c1 , c2 , . . . , cs ) con todos los elementos
que están en a o b (pero aparecen en c una sola vez).
Sugerencia: podrı́a ponerse un arreglo a continuación del otro, haciendo
for i := 1 to n do c[i] := a[i];
for i := 1 to m do c[i+n] := b[i];
y luego “purgar” el arreglo c que tiene longitud m + n.
Otra posibilidad es, si a y b ya están ordenados y clasificados, usar la
“fusión” o “mezcla” del problema 10.4. ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 102 Búsqueda y clasificación

10.4. Registros (Records)


En aplicaciones informáticas, y aún algunas matemáticas como veremos, es
conveniente agrupar la información que tenemos sobre un objeto. Por ejemplo,
para una persona podrı́amos tener su apellido, nombres, número de identidad,
etc. Una forma de hacerlo es mediante arreglos paralelos, e.g. un arreglo para
los apellidos, otro para los nombres, etc., donde una persona tendria el mismo
ı́ndice para cada uno de los arreglos. Otra forma más razonable es el uso de
registros.
Nota: Partes del siguiente texto están extraı́das de [2, Cap. 7].
Un registro es una estructura que consiste en un número fijo de componentes
llamadas campos. A diferencia del arreglo, las componentes de un registro pueden
ser de diferentes tipos, pero no pueden ser indexados.
Al definir un tipo de registro, se especifica el tipo de cada componente y su
identificador. Por ejemplo, si que queremos trabajar con números complejos de

m
la forma a + bi, con a, b ∈ R, podemos poner:

o
.c
type complejo = record re, im: real end;

os
var z: complejo;

Nota: Observar que la declaración del registro termina con “ end; ”, pero no
r
fo
hay un “ begin ”.
.m
Para acceder a una componente del registro, el nombre del registro es se-
guido por un punto (“ . ”) y el correspondiente identificador del campo. Ası́, si
na

quisiéramos asignar a z el valor 3 + 5i, pondrı́amos


tu

z.re := 3; z.im := 5;
on

Si z 0 es otro número complejo, podemos hacer la asignación


yc

zp := z;
mientras que la suma z 00 = z + z 0 puede expresarse como:
dm

zpp.re := z.re + zp.re; zpp.im := z.im + zp.im;


.a
w

Las dos operaciones válidas para variables de registro (comple-


w

tas) son la selección de componentes y la asignación.


w

Es importante destacar la localidad de los nombres de los campos de un


registro. La declaración
var a: array[2..8] of integer;
a: real;
es incorrecta, pero la declaración
var a: array[2..8] of integer;
b: record
a: real;
b: boolean
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.4. Registros (Records) Pág. 103

es correcta, pues “ b.a ” indica la componente “ a ” de “ b ”, mientras que “ a ”


indica un arreglo.
Cuando trabajamos con un registro en Pascal, es posible acceder directa-
mente a sus componentes mediante la sentencia “ with ” (en castellano, con).
Por ejemplo, para asignar a z el valor 3 + 5i, podrı́amos poner:
with z do begin re := 3; im := 5 end;

Problema 10.13. Suponiendo que declaramos el tipo “ complejo ” como más


arriba, hacer un programa con procedimientos para leer y escribir un complejo
por terminal, y calcular la suma y el producto de dos complejos. ✄
En los dos siguientes problemas, suponemos que el compilador soporta el
tipo “ string ”.
Problema 10.14. Supongamos que queremos trabajar con un arreglo de regis-

m
tros, en cada uno de los cuales se guarda un nombre y un número de identifica-
ción. Declaramos

o
.c
const MAXN = 20;

os
type

r
tipoinfo = record nombre: string; nroid: integer end;
fo
tipoarreglo = array[0..MAXN] of tipoinfo;
.m

Una variante del procedimiento leerarreglo (capı́tulo 8, pág. 74), para leer
datos es:
na

procedure leerdatos(var a: tipoarreglo; var n: integer);


tu
on

function nuevodato: boolean;


begin
yc

if (n > MAXN) then nuevodato := false


else begin
dm

writeln;
write(’Entrar el dato ’, n:2);
.a

writeln(’ (fin = <retorno>): ’);


write(’ Entrar el nombre: ’);
w

if (eoln) then begin readln; nuevodato := false end


w

else begin
w

with a[n] do begin


readln(nombre);
write(’ Entrar el numero de id: ’);
readln(nroid)
end;
nuevodato := true
end
end
end;

begin
writeln(’** Entrada de datos’);
n := 1;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 104 Búsqueda y clasificación

while (nuevodato) do n := n + 1;
n := n - 1
end;
donde la variable findatos es de tipo “ boolean ”.
Hacer un procedimiento para escribir arreglos de este tipo, y hacer un pro-
grama incorporando ambos procedimientos para probarlos. ✄
Problema 10.15 (Clasificación de arreglos de registros). Supongamos
que hemos hecho las declaraciones del problema 10.14, y queremos clasificar el
arreglo a de tipo “ tipoarreglo ”, que puede ser según nombre o según nroid .
La componente por la cual se clasifica se denomina llave o clave (en inglés
“key”). Por ejemplo si tenemos el registro x con el nombre “ Geri ” y el número
de identidad “ 5 ”, y el registro y con el nombre “ Ana ” y número “ 10 ”, or-
denándolos (de menor a mayor) por nombre vendrá primero y antes que x, pero
si los ordenamos por nroid vendrá primero x.

m
Nota: La comparación de strings se hace como la comparación entre números

o
(en Pascal), e.g. “ s1 <= s2 ”, obteniendo un valor booleano (verdadero o falso).

.c
a) Hacer una función “ mayor(x, y, llave) ” que retorna un valor lógico, y

os
donde x, y son del tipo “ info ” y llave es del tipo “ integer ”, tomando los
valores ‘ 1 ’ o ‘ 2 ’, de modo que el resultado de mayor sea el valor de “ x.z
r
fo
> y.z ” donde z puede ser nombre o nroid según el valor de llave.
b) Modificar alguno de los métodos de clasificación vistos, de modo de poder
.m

clasificar un arreglo de registros, cambiando la comparación entre datos de


na

la forma x > y por “ mayor(x, y, llave) ”.


c) Hacer un programa para ingresar el arreglo a y clasificarlo según nombre o
tu

nroid a elección del usuario, escribiendo por pantalla el resultado. ✄


on

10.5. Problemas Adicionales


yc
dm

Problema 10.16. Una variante del juego de las cajas (problema 10.8) es la
siguiente: Dado un número (entero) entre 1 y 100 (generado aleatoriamente),
tratar de encontrarlo mediante “encajonamiento”, o sea vamos dando números a
.a

y b (a ≤ b) y la computadora nos va respondiendo si el número está a la izquierda


w

de a, “atrapado” entre a y b, o a la derecha de b. El número será encontrado


w

cuando a y b coincidan con el número a acertar, terminando el juego. Hacer


w

un programa para este juego, y pensar una estrategia para encontrar el número
en “pocas” jugadas. ¿Podrı́as relacionar una estrategia para este juego con el
método de la bisección (problema 7.3)? ✄
Problema
√ Dado n ∈ N, queremos encontrar el mayor m ∈ N tal que
10.17. √
m ≤ n, i.e. m = b nc, usando sólo operaciones entre enteros.
a) Ver que el siguiente esquema, siguiendo las ideas de búsqueda binaria, en-
cuentra m:
poco := 2; if (n = 1) then mucho := 2 else mucho := n;
while (poco < mucho) do begin
medio := (poco + mucho) div 2;
if ((medio * medio) <= n) then poco := medio + 1
else mucho := medio

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.5. Problemas Adicionales Pág. 105

end;
m := mucho - 1;
Sugerencia: realizar un análisis como en el hecho para Búsqueda Binaria,
viendo que i) el algoritmo
√ termina, ii) que al terminar poco = mucho, y iii)
siempre es poco − 1 ≤ b nc < mucho.
b) Hacer un procedimiento y programa implementándolo. Comprobar la co-
rrección comparando el valor obtenido con “ trunc(sqrt(n)) ”.

c) ¿Qué pasa si b(n + 1)/2c > maxint en el programa anterior?, ¿por qué?,
¿podrı́a arreglarse el esquema en a)?, ¿podrı́a usarse un escalamiento como
en el problema 5.9 sobre el método babilónico, para evitar que mucho ×
mucho > maxint?
Nota: El método babilónico también hace sólo operaciones aritméticas elemen-
tales y “acorta al menos en dos la distancia” en cada paso. El esquema

m
m := 1;
repeat m := (m + (n div m)) div 2 until ((m * m) <= n)

o
es correcto, excepto si m2 > maxint en algún momento. Es decir, valen las

.c
mismas
√ objeciones hechas en c) al esquema en a): es correcto si n + 1 ≤

os
2 maxint. ✄

r
Problema 10.18 (Estabilidad para clasificación). Un método de clasifica-
fo
ción se llama estable si el orden relativo de elementos con llaves iguales perma-
.m
nece inalterado en el proceso de clasificación.
na

a) Decidir si el método de clasificación usado en el programa en el proble-


ma 10.15 es estable o no, e.g. si ordenando primero por nroid y después
tu

por nombre, personas con el mismo nombre aparecen en orden creciente de


nroid .
on

b) Decidir cuáles de los métodos elementales de clasificación que hemos visto


yc

(inserción directa, selección directa, intercambio directo) son estables. ✄


dm

Problema 10.19 (Mergesort). Los métodos elementales usan del orden de


n2 comparaciones y/o asignaciones para clasificar un arreglo de longitud n. En
contraposición, los métodos más avanzados usan del orden de n × log2 n opera-
.a

ciones. Entre los más “populares” de este tipo podemos mencionar al heapsort
w

o clasificación por montón, quicksort o clasificación rápida, y mergesort o clasi-


w

ficación por fusión o mezcla.


w

No es difı́cil explicar este último método, cuya idea es dividir el arreglo


de a pares, y luego ir fusionando —como en el problema 10.4— los subarreglos:
primero juntar pares consecutivos, luego cuaternas consecutivas, etc. hasta llegar
obtener el arreglo completo. Por ejemplo, el método realiza los siguientes pasos
al clasificar (3, 6, 7, 5, 4, 1, 2):

1. 3675412 se juntan de a pares (o lo que se pueda),


2. 3657142 se ordena cada par (i.e. se “fusiona” cada grupo),
3. 3657142 se agrupan de a cuatro (o lo que se pueda),
4. 3567124 se “fusiona” cada grupo,
5. 3567124 se agrupan de a ocho (o lo que se pueda),

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 106 Búsqueda y clasificación

6. 1234567 y se fusionan.
A fin de implementar este algoritmo, usamos el procedimiento clasificar que
llama, a su vez, al fusionar :
procedure fusionar(
var a, b: arreglo; (* entra a, sale b *)
inic, medio, fin: integer (* partes a fusionar *)
);
var
i, j, k: integer;
begin
i := inic; j := medio + 1; k := inic - 1;
repeat
k := k + 1;
if (a[i] <= a[j]) then begin

m
b[k] := a[i]; i := i + 1 end

o
else begin

.c
b[k] := a[j]; j := j + 1 end
until ((i > medio) or (j > fin));

os
(* copiar lo que falta *)
while (j <= fin) do begin
r
fo
k := k + 1; b[k] := a[j]; j := j + 1 end;
.m
while (i <= medio) do begin
k := k + 1; b[k] := a[i]; i := i + 1 end
na

end;
tu

procedure clasificar(var a: arreglo; n: integer);


on

var
i, m, f, k, k2, veces, j: integer;
yc

a2: arreglo;
begin
dm

k := 1; (* tamanio de subarreglos a fusionar *)


veces := 0; (* las veces que paso por el lazo *)
.a

while (k < n) do begin


veces := veces + 1;
w

k2 := 2 * k;
w

i := 1; m := k; f := k2;
w

if ((veces mod 2) = 0) then begin


while (f <= n) do begin
fusionar(a2, a, i, m, f);
i := i + k2; m := m + k2; f := f + k2
end;
if (m < n) then fusionar(a2, a, i, m, n)
else for j := i to n do a[i] := a2[i]
end
else begin
while (f <= n) do begin
fusionar(a, a2, i, m, f);
i := i + k2; m := m + k2; f := f + k2
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

10.5. Problemas Adicionales Pág. 107

if (m < n) then fusionar(a, a2, i, m, n)


else for j := i to n do a2[i] := a[i]
end;
k := k2
end;
if ((veces mod 2) = 1) then
for i := 1 to n do a[i] := a2[i]
end;
donde arreglo es un arreglo de elementos de tipo simple (entero, real, carácter
o lógico). Si se desea clasificar otros tipos, e.g. registros, habrá que modificar el
procedimiento fusionar adecuadamente.
Usamos un arreglo auxiliar. La primera vuelta, y las siguientes vueltas im-
pares, se fusionan subarreglos de a y se escriben los resultados en a2 . En las
vueltas impares se fusionan subarreglos de a2 y se escriben los resultados en a.
Si al terminar hubo un número impar de vueltas, debe copiarse a2 en a.

m
La versión de mergesort presentada no es muy “pulida” a fin de resaltar el

o
algoritmo. Entre otras cosas, usamos un arreglo auxiliar, violando una de las

.c
condiciones que habı́amos impuesto. Aunque nuestra versión no es demasiado

os
“competitiva” con otros algoritmos avanzados y otras implementaciones de mer-
gesort, es bastante mejor que otros métodos elementales, como puede verse en
el cuadro 10.1 (pág. 100). r
fo
.m
a) ¿Cuál es el valor de k, en términos de n, al terminar el procedimiento
clasificar ? Sugerencia: comparar con log2 n.
na

b) Observar en el cuadro 10.1 que la cantidad de asignaciones de “mergesort” es


siempre la misma (pero en los otros algoritmos cambia). ¿Cuál es el número
tu

de asignaciones, en términos de n? Sugerencia: usar el inciso anterior.


on

c) Agregar los procedimientos presentados a un programa para clasificar, veri-


ficar que su comportamiento es correcto, y cotejar el número de asignaciones
yc

(entre arreglos) y comparaciones con los otros algoritmos elementales a fin


de obtener un cuadro similar al 10.1 (sin los tiempos, y para arreglos más
dm

chicos). ✄
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 11

Recursión

m
Supongamos que queremos encontrar todas las sumas

o
s1 = 1, s2 = 1 + 2, s3 = 1 + 2 + 3, ... sn = 1 + 2 + · · · + n, ...

.c
os
Podrı́amos calcular cada una de ellas separadamente, por ejemplo usando la
fórmula de Gauss an = n × (n + 1)/2, pero también podrı́amos poner
r
fo
s1 = 1, s2 = s1 + 2, s3 = s2 + 3, ... sn = sn−1 + n, ...
.m

Esto es tı́pico de inducción o, en programación, de recursión: dar un valor


na

inicial y una “fórmula” para calcular los valores subsiguientes.


Ya hemos visto un esquema similar muchas veces, como al calcular sumas
tu

de Gauss donde si cambiáramos la variable suma por un arreglo tendrı́amos


on

suma[0] := 0; for i := 1 to n do suma[i] := suma[i-1] + i


yc

calculando en cada paso de lazo “ for ” la cantidad si .


Inclusive la función saldo en el problema 7.5 (en el que se consideraba interés
dm

sobre saldo), puede ponerse en un esquema parecido:

dm = t(dm−1 − p) para m > 0.


.a

d0 = a y
w

En los problemas subsiguientes vamos a encontrar varios ejemplos que ya


w

hemos visto en los que usábamos un lazo “ for ” o similar.


w

11.1. Funciones y procedimientos definidas re-


cursivamente
Problema 11.1 (Factorial recursivo). En el problema 4.12 definimos para
n ∈ N su factorial como n! = 1 × 2 × · · · n, pero podemos ver que es equivalente
a definirlo por

1! = 1 y n! = n × (n − 1)! para n ∈ N, n > 1.

El siguiente esquema muestra una función recursiva, i.e. que se llama a


sı́ misma, para calcular n!:

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 110 Recursión

function factorial(n: integer): integer;


begin
if (n > 1) then factorial := n * factorial(n - 1)
else factorial := 1
end;

Nota: Observar que la variable “sobre la que se hace la recursión”, en este caso
n, no tiene antepuesta la palabra “ var ” en la definición de la función. No tiene
sentido hacerlo, pues cuando llamamos factorial (n − 1), n − 1 no tiene asignado
un lugar (aunque lo tenga n): n − 1 tiene que pasarse por valor y no referencia.
Nota: Ahora podemos apreciar un poco más por qué la función (o procedimien-
to) no es una variable local a la función.
a) Hacer un programa para calcular n! usando la función anterior.
b) Obtener el máximo valor de n para el cual se puede calcular n! (i.e. n! ≤
maxint ) en la versión del inciso a).

m
Nota: En vista de este resultado, es conveniente cambiar el tipo de los

o
valores de factorial de “ integer ” a “ real ” en la función en a), como

.c
hemos hecho en los problemas 4.11 y 4.12.

os
c) La fórmula de Stirling establece que cuando n es bastante grande,

n! ≈ nn e−n 2πn.
r
fo
Hacer un programa para calcular esta aproximación, y probarla con n =
.m

10, 100, 1000. Comparar con los resultados obtenidos en a) (habiendo hecho

na

las modificaciones indicadas en la nota del inciso b)).


Nota: Recordar que generalmente se toma π = 4 × arctan(1), aunque en
tu

algunos compiladores pi está predefinido como constante. ✄


on

Expliquemos un poco cómo funciona recursión. Una función o procedimiento


que no usa recursión (no se llama a sı́ misma), ocupa un lugar en la memoria al
yc

momento de correr el programa. Ese lugar contiene las “cajas” correspondientes


dm

a sus variables locales, y también el código (las instrucciones) que debe seguir.
Cuando la función o procedimiento se llama a sı́ misma, podemos pensar que
el programa automáticamente genera copias de la función (tantas como llama-
.a

das se hagan), con sus propios lugares para código y variables locales. Cuando
w

la función o procedimiento que ha sido llamada por la recursión termina su


w

tarea, el espacio es liberado. Este espacio de memoria usado por recursión no


está reservado (la palabra técnica es alocado) por el programa en el momento
w

de compilar (como sucede con los arreglos), pues no puede saber de antemano
cuántas veces se usará la recursión. Por ejemplo, para calcular n!, se necesitan
unas n copias de la función: cambiando n cambiamos el número de copias ne-
cesarias. Este espacio de memoria especial se llama “stack” o “pila”, estructura
que veremos también en el problema 11.6 y un poco más formalmente en la
sección 12.1.
Ası́ como hemos usado recursión para una función con un único argumento,
no hay problemas en usar recursión con procedimientos o con varios argumentos,
como en los siguientes dos problemas.
Problema 11.2. Usando recursión, reescribir el algoritmo de Euclides (sec-
ción 5.2.2) para encontrar el máximo común divisor entre dos enteros positivos.
Sugerencia: mcd(a, b) = mcd(a − b, b) si a > b. ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

11.1. Funciones y procedimientos definidas recursivamente Pág. 111

Problema 11.3. Supongamos que queremos encontrar los factores primos de


n ∈ N usando recursión. Podemos encontrar “el menor factor”, empezando desde
un valor inicial mediante
procedure factorizar(n, inic: integer);
var x: real;
begin
x := sqrt(n);
while ((inic <= x) and ((n mod inic) <> 0)) do
inic := inic + 1;
if (inic > x) then writeln(n:10) (* n es primo *)
else begin
write(inic:10);
factorizar(n div inic, inic)
end
end;

m
Hacer un programa para escribir los factores de n ∈ N, usando este procedi-

o
.c
miento y comenzando con la llamada factorizar(n, 2).
Nota: Refiriéndonos a comentarios anteriores, observar que en el procedimiento

os
n y inic son llamados por valor y no referencia.
Nota: Comparar con los problemas 5.17 y 5.18.
r ✄
fo
.m
Problema 11.4. Hacer un programa implementando la clasificación por selec-
ción directa (pág. 98) como un procedimiento recursivo. Sugerencia: declarar
na

el procedimiento como “ seleccion(var a: arreglo, n: integer) ” y hacer


recursión en n, colocando el mayor elemento en la posición final antes de llamar
tu

al procedimiento con n − 1. ✄
on

Problema 11.5. Para m, n ∈ N, consideremos una cuadrı́cula rectangular de


dimensiones m × n (4 × 3 en la figura 11.1), e imaginémosnos que se trata de un
yc

mapa, donde los segmentos son calles con direcciones oeste–este o sur–norte, y
dm

los puntos remarcados son las intersecciones.

1 4 10 20 35
.a
w
w

1 3 6 10 15
w

1 2 3 4 5

1 1 1 1

Figura 11.1: contando la cantidad de caminos posibles.

Nos preguntamos de cuántas maneras podremos ir desde la esquina más


hacia el sudoeste, de coordenadas (0, 0), a la esquina más hacia el noreste, de
coordenadas (m, n), si estamos limitados a recorrer las calles únicamente en
sentido oeste–este o sur–norte, según corresponda.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 112 Recursión

Para resolver el problema, podemos pensar que para llegar a una intersección
hay que hacerlo desde el oeste o desde el sur (salvo cuando la intersección está en
el borde oeste o sur), y por lo tanto la cantidad de caminos para llegar a la
intersección es la suma de la cantidad de caminos llegando desde el oeste (si
se puede) más la cantidad de caminos llegando desde el sur (si se puede). Los
números en la figura 11.1 indican, para cada intersección, la cantidad de caminos
para llegar allı́ desde (0, 0) mediante movimientos permitidos.
a) Hacer un programa para calcular la cantidad de caminos para llegar desde
(0, 0) a (m, n), donde m y n son ingresados por el usuario.
b) En cursos de matemática discreta se demuestra que el número de caminos
es
(m + n)! (m + n) × (m + n − 1) × · · · × (m + 1)
c(m, n) = = .
m! n! n × (n − 1) × · · · × 1

m
Incorporar al programa del inciso anterior una función con el cálculo de
c(m, n) (por ejemplo, con un lazo) y comparar con el obtenido anteriormen-

o
te.

.c
c) Modificar el programa del inciso a) de modo de calcular la cantidad de

os
caminos cuando la intersección (r, s) está bloqueada y no se puede pasar

r
por allı́, donde r y s son ingresados por el usuario (0 < r < m y 0 < s < n).
fo
Sugerencia: poner c(r, s) = 0.
.m
d ) Supongamos ahora que, al revés del inciso anterior, para ir de (0, 0) a (m, n)
tenemos que pasar por (r, s) (por ejemplo, para llevar a (m, n) la pizza
na

que compramos en la esquina (r, s)). Hacer un programa para esta nueva
posibilidad. Sugerencia: puedo armar un camino de (0, 0) a (m, n) tomando
tu

cualquier camino de (0, 0) a (r, s) y después cualquier camino de (r, s) a


on

(m, n).
e) De acuerdo al inciso b), la cantidad de caminos en el inciso d ) es c(r, s) ×
yc

c(m − r, n − s). Verificar que esto es cierto.


dm

f ) Análogamente, la cantidad de caminos del inciso c) es c(m, n) − c(r, s) ×


c(m − r, n − s). Verificar que esto es cierto en el mismo programa. ✄
.a
w

11.2. Los Grandes Clásicos de la Recursión


w

En todo curso de programación que se precie de tal, entre los ejemplos de


w

recursión se encuentran el factorial, que ya hemos visto y las torres de Hanoi y


los números de Fibonacci.
Problema 11.6 (Las torres de Hanoi). Según la leyenda, en un templo se-
creto de Hanoi hay 3 agujas y 64 discos de diámetro creciente y los monjes
pasan los discos de una aguja a la otra mediante movimientos permitidos. Los
discos tienen agujeros en sus centros de modo de encajar en las agujas e inicial-
mente los discos estaban todos en la primera aguja con el menor en la cima, el
siguiente menor debajo, y ası́ sucesivamente, con el mayor debajo de todos. Un
movimiento permitido es la transferencia del disco en la cima desde una aguja a
cualquier otra siempre que no se ubique sobre uno de diámetro menor. Cuando
los monjes terminen de transferir todos los discos a la segunda aguja, será el fin
del mundo.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

11.2. Los Grandes Clásicos de la Recursión Pág. 113

Nuestra intención es1 hacer un programa para realizar esta transferencia,


mostrando los pasos realizados.
Supongamos que tenemos n discos, “pintados” de 1 a n, de menor a ma-
yor. Supongamos también que llamamos a las agujas a, b y c, y que tienen,
respectivamente, na , nb , y nc discos cada una, donde inicialmente na = n y
nb = nc = 0.
Si queremos conservar un inventario de los contenidos de cada aguja, podrı́-
amos definir
type aguja = array[0..10] of integer
y declarar, por ejemplo, var a, b, c: aguja. Usamos la posición 0 para guar-
dar la longitud (e.g a0 = na ), y las siguientes posiciones, (a1 , a2 , . . . , ana ), para
indicar que en la posición 1 de la aguja a está el disco a1 , en la 2 el disco a2 ,
etc. Inicialmente será a = (n, n, n − 1, . . . , 2, 1), b = (0, . . .) y c = (0, . . .).
Si hay n discos en la aguja a, y ninguno en la agujas b y c, para poner el

m
disco más grande en la aguja b, habrá que poner los n − 1 restantes en la aguja
c primero, después colocar la n-ésima en la aguja b, y volver a poner los n − 1

o
.c
en la aguja b. Para mover los n − 1 de la aguja a a la aguja c, habrá que mover
n − 2 a la aguja b, pasar el n − 1 a la aguja c,. . .

os
a) Definir una función pasar (k, x, y, z) que pase k discos (con movimientos
r
fo
permitidos2 ) de la aguja x a la y usando la z, imprimiendo los discos en
cada aguja al terminar.
.m

Sugerencia: para k = 1 sólo hay que mover la aguja que está en la cima de
x hacia y, y para k > 1, podemos poner pasar (k, x, y, z) como consistente
na

de los siguientes pasos:


tu

pasar(k-1,x,z,y); pasar(1,x,y,z); pasar(k-1,z,y,x)


on

Sugerencia si la anterior no alcanza:


yc

procedure pasar( k: integer; var x, y, z: aguja);


(* pasar k discos de x a y, usando z *)
dm

begin
if (k > 1) then begin
.a

pasar(k-1, x, z, y);
pasar(1, x, y, z);
w

pasar(k-1, z, y, x)
w

end
else begin (* pasar una de x a y *)
w

y[0] := y[0] + 1; y[y[0]] := x[x[0]]; x[0] := x[0] - 1;


imprimir
end
end;
donde hemos supuesto un procedimiento imprimir que imprime los elemen-
tos que tiene cada arreglo (hasta la longitud correspondiente). Observar que
k se pasa por valor, pero x, y y z se pasan por referencia: los arreglos pueden
modificarse en el procedimiento.
b) Hacer un programa que, usando recursión, dé una sucesión de movimientos
1 ¡Quién lo duda!
2 Recordar que el disco a pasar de una aguja a otra debe ser menor que cualquier otro
disco en esas agujas.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 114 Recursión

a realizar para transferir n discos inicialmente en una aguja a otra. Verificar


el programa para n = 1, 2, 3, 4, 5.
c) Agregar un contador para contar la cantidad de veces que se transfiere un
disco de una aguja a otra (en pasar ), e imprimirlo al terminar el progra-
ma. En base a este resultado (para n = 1, 2, 3, 4, 5) conjeturar la cantidad
de movimientos necesarios para transferir n discos de una aguja a otra, y
demostrarlo.
Sugerencia: 2n − 1 = 1 + 2 + 4 + · · · + 2n−1 = 1 + 2(1 + 2 + · · · + 2n−2 ) =
1 + 2(2n−1 − 1).
d ) Suponiendo que transfieren un disco por segundo, ¿cuánto tardarán los mon-
jes en transferir los 64 discos? ¿Cuántos años tardarı́a una computadora en
calcular la solución para n = 64, suponiendo que tarda un nanosegundo por
movimiento3 (nano = dividir por mil millones)? Bajo la misma suposición
sobre la velocidad de la computadora, ¿cuál es el valor máximo de n para
calcular los movimientos en 1 minuto?

m
Nota: La suposición que lo que tarda la computadora por movimiento es

o
independiente de n es una aproximación muy buena. Si es posible, compro-

.c
barla (tomando por ejemplo n = 19, 20, . . . ), sin imprimir los movimientos,

os
y estimar lo que tarda la computadora por movimiento en cada caso (del
orden de micro segundo, micro = dividir por un millón).

r
fo
Nota: En el problema 11.8 se da una variante para resolver el problema de
las torres de Hanoi sin usar arreglos.
.m

Nota: La estructura que estamos usando para los arreglos, agregando atrás y
na

sacando también desde atrás, es un ejemplo de pila (como la de platos), sobre


la que hablaremos más en la sección 12.1.
tu
on

Hay muchas variantes del problema de las torres de Hanoi, por ejemplo:
¿qué pasa si los discos no están inicialmente todos sobre una misma aguja
(pero respetan la distribución de mayor a menor)?, ¿qué pasa si hay más de
yc

tres agujas? ✄
dm

Problema 11.7 (Números de Fibonacci). En 1202 Fibonacci propuso el


siguiente problema en su libro Liber Abaci:
.a
w

¿Cuántos pares de conejos se producen a partir de una única pareja


w

en un año si cada mes cada par de conejos da nacimiento a un nuevo


par, el que después del segundo mes se reproduce, y no hay muertes?
w

a) Resolver el problema.
Los números que aparecen en la solución de este problema se conocen como
números de Fibonacci, definidos recursivamente como:

f1 = 1, f2 = 1, y fn = fn−1 + fn−2 para n ≥ 3.

b) Hacer un programa para calcular los números de Fibonacci:


i) Usando recursión.
ii) Sin usar recursión. Sugerencia: un esquema posible como función es

3 ¡Y que no hay cortes de luz!

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

11.3. Problemas Adicionales Pág. 115

a := 1; b := 1;
for i := 3 to n do begin c := a + b; b := a; a := c end;
fibonacci := a

c) La fórmula de Euler-Binet establece que


√ n √ n
(1 + 5) − (1 − 5)
fn = √ .
2n 5
de modo que, créase o no, el miembro derecho es un número entero (¿podrı́as
decir por qué, si no supieras el resultado?).
Implementar el miembro derecho de esta igualdad como función y com-
parar con los resultados del inciso anterior.
d ) Según la fórmula de Euler-Binet, fn crece exponencialmente, por lo que
rápidamente toma valores muy grandes, y al implementarse como función
debe retornar valores de tipo “ real ” (como en el caso de n!). Encontrar el

m
máximo n tal que fn ≤ maxint . ✄

o
Leonardo Bigollo es el verdadero nombre de Leonardo de Pisa (1180–1250),

.c
también conocido como Fibonacci (contracción de las palabras “hijo de Bonac-

os
ci”).
Jacques Binet (1786–1856) publicó la fórmula para los números de Fibo-
r
nacci en 1843, pero ya habı́a sido publicada por Leonhard Euler (1707–1783)
fo
en 1765.
.m
Mucho después de Fibonacci, se observó que los números fn aparecen en
muy diversos contextos, algunos insospechados como en la forma de las flores
na

del girasol, y son de importancia tanto en las aplicaciones prácticas como teóri-
cas aún en la actualidad. Por ejemplo, han sido usados para resolver problemas
tu

prácticos de confiabilidad de comunicaciones.


En cuanto a aplicaciones teóricas, vale la pena mencionar que en el Con-
on

greso Internacional de Matemáticas de 1900, David Hilbert propuso una serie


de problemas que consideró de importancia para resolver durante el siglo XX.
yc

Entre ellos, el décimo pide encontrar un algoritmo para determinar si un poli-


dm

nomio con coeficientes enteros, arbitrariamente prescripto, tiene raı́ces enteras


(resolver la ecuación diofántica asociada). Recién en 1970 pudo obtenerse una
respuesta al problema, cuando el matemático ruso Yuri Matijasevich (quien
.a

tenı́a 22 años) demostró que el problema es irresoluble, es decir, no existe al-


w

goritmo que pueda determinar si una ecuación diofántica polinomial arbitraria


tiene soluciones enteras. En su demostración, Matijasevich usa la tasa de cre-
w

cimiento de los números de Fibonacci.


w

11.3. Problemas Adicionales


Problema 11.8 (Torres de Hanoi sin arreglos). Flor, que siempre le lleva
la contra al profe y está compitiendo con él, hizo un programa para resolver el
problema de las torres de Hanoi sin usar arreglos. Más aún, ¡cambió arreglos
por caracteres!:
program hanoisinarreglos(input, output);
(*
Solucion recursiva de las torres de Hanoi,
sin usar arreglos.
*)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 116 Recursión

type aguja = char;

var n: integer;

procedure pasar( n: integer; x, y, z: aguja);


(* pasar n discos de x a y usando z *)
begin
if (n > 1) then begin
pasar(n-1, x, z, y);
write(’pasar el disco ’, n:1);
writeln(’ de "’, x, ’" a "’, y,’"’);
pasar(n-1, z, y, x)
end
else

m
write(’pasar el disco 1’);

o
writeln(’ de "’, x, ’" a "’, y,’"’)

.c
end;

os
begin

r
writeln(’** Solucion recursiva de las torres de Hanoi:’);
fo
writeln(’ Pasar n discos de la aguja "a" a la "b"’);
.m
write(’ usando la "c", mediante movimientos ’);
writeln(’ permitidos.’);
na

writeln;
tu

write(’ Entrar el numero de discos: ’); readln(n);


on

writeln;
yc

pasar(n, ’a’, ’b’, ’c’);


dm

writeln; writeln(’** Fin **’)


end.
.a

¿Es correcto el programa de Flor? ¿Por qué? ✄


w

Problema 11.9. Recordando la fórmula de Euler-Binet (problema 11.7.c)),


w

comparar el número de Fibonacci fn con el redondeo (“ round ”) de


w

√ n
(1 + 5)
√ .
2n 5
¿A partir de qué n son iguales?, ¿podrı́as decir por qué? ✄
Problema 11.10. Un resultado de computación teórica dice que toda función o
procedimiento recursiva puede reescribirse con lazos “ while ” y arreglos sin usar
recursión (que se usan como “pilas” para ir guardando los datos intermedios).
De hecho, hemos visto que muchos de los problemas de este capı́tulo se
pueden reescribir sin recursión con lazos “ for ”, como el factorial, el algoritmo
de Euclides, factorizar, selección directa o números de Fibonacci. ¿Podrı́as hacer
el problema 12.1, para generar las cadenas de bits, sin usar recursión (para n
variable) usando lazos “ while ”, “ repeat ” y/o “ for ”? ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 12

Generando objetos
combinatorios

o m
.c
Muchos de los problemas de “conteo” que pueden resolverse usando recursión

os
en programación, también pueden resolverse mediante el uso de lazos como
“ for ” o “ while ”, por ejemplo calcular 2n o n!, y nos quedamos con la impresión
r
de que recursión no es muy útil. Otro problema muy distinto al de contar objetos
fo
es generarlos, por ejemplo para encontrar alguno o todos los que satisfacen cierto
.m
criterio, y aquı́ es donde recursión muestra toda su potencia.
Puesto que el número de objetos a generar puede ser muy grande, como
na

2n o n!, es conveniente no tener —por ejemplo— un arreglo para cada uno de


ellos, sino tener uno solo que se va modificando a medida que vamos creando
tu

los objetos. Una estructura particularmente útil para esta modificación es la de


on

“pila”, con la que comenzamos.


yc

12.1. Un aparte: pilas y colas


dm

Las pilas, como las de platos, y las colas, como las de supermercados, son dos
.a

estructuras familiares que trataremos de formalizar un poco aquı́, más que nada
para poner en perspectiva lo que estamos haciendo y no siendo estrictamente
w

rigurosos.
w

Tanto en pilas como en colas podemos pensar que tenemos una serie de
w

objetos —todos del mismo tipo— esperando en lı́nea a ser tratados. En el caso
de la pila, o cola lifo, por “last in first out”, el último en llegar es el primero en
ser tratado. En cambio en lo que denominamos comunmente cola (sin aditivos),
o más formalmente cola fifo, por “first in first out”, el primero en llegar es el
primero en ser tratado.
No es difı́cil imaginar distintas acciones comunes:
Crear o inicializar la cola, construyendo la cola “vacı́a”.
Agregar un elemento, ubicándolo al final y actualizando la cola.
Quitar un elemento, tomando, según corresponda, el primero o el último
y actualizando la cola.
Destruir la cola, cuando no la necesitamos más, limpiando lo que ensu-
ciamos.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 118 Generando objetos combinatorios

En fin, podrı́amos agregar otras, como averiguar el estado de la cola (si


está vacı́a o llena).
Es tradicional en computación llamar con distintos nombres a estas acciones,
según se trate de una pila o cola. Por ejemplo, agregar el elemento a en una pila
se llama push y en la cola put. En cambio, el quitar un elemento se llama pop
para pilas y get para colas.
No es casualidad que put y get sean nombres en Pascal de funciones o pro-
cedimientos para entrada o salida (que no cubriremos, siendo más “primitivas”
que read y write). Efectivamente, la entrada y salida en la computadora se
implementan como colas fifo: los datos se leen o escriben en el orden en que
van surgiendo. Dado que put y get tienen significados especiales en Pascal, es
conveniente evitar estos nombres.
El concepto de cola, que tiene muchı́simas generalizaciones, constituye un
tipo de datos abstracto (TDA): no importa tanto cómo se va a implementar o
representar en la máquina, sino saber que contamos con las operaciones de crear

m
o destruir la cola, agregarle o quitarle un elemento, etc.
Sin embargo, la implementación de una cola como “fifo” o “lifo” puede te-

o
.c
ner consecuencias decisivas en los algoritmos, como veremos en el capı́tulo 13.
Además, en lenguajes no tan abstractos como Pascal, debemos tener cuidado en

os
la implementación de TDA’s. En el capı́tulo 14 introduciremos punteros y listas

r
encadenadas de Pascal, que son más apropiados para implementar colas, pero a
fo
fin de no complicar la presentación, por ahora “nos arreglaremos con arreglos”.
.m
Como sabemos, si queremos trabajar con un arreglo tenemos que declararlo
al comienzo, dando su dimensión y el tipo de elementos. Por lo tanto, si vamos a
na

representar una cola con un arreglo, no podemos “crearlo de la nada”, y tenemos


que prever su existencia y dar una dimensión adecuada al problema. Del mismo
tu

modo, no podemos “destruirlo”, y nos limitaremos a ignorarlo cuando no lo


on

necesitemos más.
Suponiendo que hemos declarado el tipo “ dato ”, que puede ser un registro
yc

u otro arreglo, podemos implementar una pila declarando


pila: array[1..MAXP] of dato
dm

donde MAXP es una constante apropiada. Si npila es la cantidad de elementos,


.a

declarado con la misma localidad que pila, podemos hacer:


w

Crear la pila:
w

procedure inicializar;
w

begin npila := 0 end;


Agregar d :
procedure push(d: dato);
begin
if (npila < MAXP) then begin
npila := npila + 1; pila[npila] := d
end
end;

Nota: Siguiendo con la filosofı́a de no poner en general mensajes de


aviso, tratando de ver el bosque y no el árbol, no hemos agregado
acciones para el caso en que npila = MAXP antes de incorporar el
dato.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

12.2. Generando Subconjuntos Pág. 119

Quitar un elemento:
function pop: dato;
begin
if (npila > 0) then begin
pop := pila[npila]; npila := npila - 1
end
end;

Nota: Valen los mismos comentarios anteriores: no hemos previsto


acciones para cuando se quiera sacar un elemento de una cola vacı́a.
En general, preguntaremos si hay elementos antes de sacar.
Observar la simetrı́a entre las acciones de push y pop: una realiza exacta-
mente los pasos inversos de la otra. Esta simetrı́a se traslada a que push es un
procedimiento con el dato como parámetro, mientras que pop es una función
que devuelve el dato (y por lo tanto debe hacerse alguna asignación o similar).

m
Para colas (fifo), las cosas son ligeramente diferentes. En vez de tener un ı́ndi-

o
ce como en el caso de la pila, mantenemos dos: uno, digamos ppocola, señalando

.c
el principio de la cola en el arreglo, y otro, digamos fincola, señalando el final.

os
fincola se incrementa al agregar un dato, mientras que ppocola aumenta cuando
se extrae un dato, de modo que los elementos “vivos” en principio estarán entre
ppcola y fincola (inclusivo en ambos casos). r
fo
Claro que si la cola está definida como un arreglo de MAXC elementos,
.m

las cosas se complican cuando agregamos más de MAXC elementos a la cola


(aún cuando hayamos quitado algunos), y necesitamos usar aritmética módu-
na

lo MAXC . No entramos en detalles porque en los ejemplos que daremos en


los capı́tulos posteriores supondremos que MAXC es lo suficientemente grande
tu

como para evitar este problema.


on

Volviendo a las pilas, no podemos dejar de observar que los procedimientos


inicializar y push son viejos conocidos que usamos al leer los datos de un arreglo,
yc

por ejemplo en el programa maximo (pág. 178). También hemos visto un atisbo
dm

del uso de pilas en el problema 11.6 de las torres de Hanoi, donde las agujas
eran pilas en sı́.
En la próxima sección veremos que la técnica de mezclar recursión con pilas
.a

puede usarse para generar distintos objetos combinatorios, aunque las pilas no
w

aparecerán explı́citamente.
w
w

12.2. Generando Subconjuntos


Problema 12.1. Supongamos que queremos imprimir todas las cadenas de bits
(arreglos de 0’s y 1’s) de longitud n. Dado que las cadenas de bits de longitud
n se obtienen agregando un 0 o un 1 a las cadenas de longitud n − 1, podrı́amos
declarar un arreglo global a como
a: array[1..10] of integer,
donde iremos construyendo las cadenas, y hacer la llamada en el cuerpo principal
a “ cadena(1) ”, donde cadena está dada por:
procedure cadena(k: integer);
var i, j: integer;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 120 Generando objetos combinatorios

begin
for i := 0 to 1 do begin
a[k] := i; (* poner 0 o 1 en el lugar k *)
if (k < n) then cadena(k+1) else begin
(* imprimir la cadena cuando k = n *)
write(’ ’);
for i := 1 to n do write(a[i]:1);
writeln
end (* if *)
end (* for *)
end;
Observar que usamos al arreglo a como una pila. Para k fijo, el elemento
en la posición k tiene un valor de 0 cuando i = 0, que se mantiene para va-
lores superiores a k pero luego es cambiado para i = 1, y obviamente cambia
varias veces cuando es llamado desde valores inferiores a k. Sin embargo, k es

m
el parámetro del procedimiento y no una variable global.

o
.c
a) Hacer una prueba de escritorio del procedimiento cuando n = 3.
b) Hacer un programa que dado n ∈ N imprima todas las cadenas de bits de

os
longitud n, siguiendo las indicaciones anteriores.

r
c) Agregar un contador (global) para contar las cadenas de longitud n, y ver
fo
que la cantidad de cadenas es 2n .
.m

d ) En el procedimiento cadena propuesto, se va “hacia adelante”, llamando


a “ cadena(1) ” en el cuerpo principal y se va aumentando el valor del
na

parámetro k en cada llamada del procedimiento hasta llegar a k = n, ya


tu

que n es global. Esto contrasta con el uso de recursión de, por ejemplo, el
factorial, donde vamos “hacia atrás” disminuyendo el valor del parámetro en
on

cada llamada. ¿Cómo podrı́a redefinirse el procedimiento de modo de hacer


la llamada “ cadena(n) ” en el cuerpo principal, y que en vez comparar k
yc

con n se compare con 0 o 1?


dm

Nota: En el problema 12.7 vemos otra forma de encontrar las cadenas de bits,
sin usar recursión. ✄
.a

Problema 12.2 (Subconjuntos). En el problema anterior esencialmente se


w

construyen todos los subconjuntos de {1, 2, . . . , n}, ya que las cadenas de bits de
w

longitud n se pueden considerar como vectores caracterı́sticos. Dado un conjunto


A ⊂ {1, 2, . . . , n} definimos su vector caracterı́stico, b(A) = (b1 , b2 , . . . , bn ),
w

mediante (
1 si i ∈ A,
bi =
0 si no.
Es claro que dos conjuntos distintos tienen vectores caracterı́sticos distintos,
y que por otro lado, dada una cadena de bits podemos encontrar un conjunto
A tal que b(A) sea esa cadena. Por lo tanto, hay una correspondencia biunı́voca
entre cadenas de bits de longitud n y subconjuntos de {1, 2, . . . , n}.
Modificar el programa del problema 12.1 para que en vez de imprimir cadenas
de bits, imprima el subconjunto correspondiente en {1, 2, . . . , n}, representando
al conjunto vacı́o con una raya “ - ”.
Por ejemplo, si n = 2 la salida deberı́a ser algo como:

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

12.2. Generando Subconjuntos Pág. 121

- 1 2 1 2 ✄

Una vez que sabemos cómo generar una familia de objetos, es más senci-
llo generar o contar objetos de la familia con caracterı́sticas particulares. Por
ejemplo:
Problema 12.3. Supongamos que queremos contar la cantidad c(n) de cadenas
de bits que no contienen dos 0’s sucesivos:
a) Hacer un programa para calcular c(n) para n ∈ N, usando el programa
del problema 12.1 y una variante de búsqueda lineal para encontrar dos
0’s consecutivos en cada cadena construida, verificando si se trata de una
cadena válida antes de aumentar el contador.
b) Comparar el número c(n) obtenido anteriormente con los números de Fibo-
nacci, y hacer un nuevo programa para calcular c(n) directamente. ✄

m
Problema 12.4. En este problema imprimiremos todos los caminos que hemos
contado en el problema 11.5. Para ello consideramos una arreglo global camino

o
en el que guardaremos las “intersecciones” o “esquinas” por las que hay que

.c
pasar. Como éstas tienen dos coordenadas, digamos x y y, declaramos

os
type esquina = record x, y: integer end;

r
fo
y
.m
camino: array[1..MAXK] of esquina;
donde MAXK es una constante para la máxima longitud del camino. En el pro-
na

blema original todos los caminos tienen longitud m+n, pero podrı́an ser distintas
tu

si, por ejemplo, también consideráramos la posibilidad de ir en diagonales de


suroeste a noreste. Para nuestros ejemplos, 10 o 20 son valores razonables para
on

MAXK .
También consideramos a los datos m y n como variables globales, y agrega-
yc

mos la variable global k que indicará la longitud del camino construido. Inicial-
dm

mente tendremos k = 0, puesto que no tenemos camino.


El trabajo lo hará el procedimiento llegardesde, haciendo la única llamada
“ llegardesde(0,0) ” en el cuerpo principal:
.a

procedure llegardesde(i, j: integer);


w

var r: integer;
w

begin
w

k := k + 1;
with camino[k] do begin x := i; y := j end;
if (i < m) then llegardesde(i+1,j);
if (j < n) then llegardesde(i,j+1);
if ((i = m) and (j = n)) then (* llegamos *)
begin
(* imprimir *)
write(’ ’);
for r := 1 to k do
with camino[r] do
write(’ (’, x:1, ’,’, y:1, ’)’);
writeln
end; (* if i = m and j = n *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 122 Generando objetos combinatorios

k := k - 1
end;
Estudiemos este procedimiento, haciendo una “prueba de escritorio” en todo
caso:
• k , inicialmente en 0, se incrementa en 1 al entrar al procedimiento, y se
incorpora la intersección (i, j) al camino en la (nueva) posición k .
Es decir, k indica la cantidad de objetos en la pila camino. Al comienzo,
la pila está vacı́a, y se van incorporando elementos atrás.
A diferencia con el problema 12.1, k es global y no el parámetro del
procedimiento.
• Si i < m o j < n, se puede continuar (hacia la derecha o hacia arriba), por
lo que llamamos al procedimiento incrementando los valores respectivos.
Observar que no hay un “ else ”: queremos que cada caso contribuya con
un camino distinto.

m
• Si i = m y j = n, hemos llegado a la esquina deseada, e imprimimos el

o
camino.

.c
• Una vez recorridos los caminos que siguen hacia el este, o el norte, o impre-

os
so el camino, debemos retornar, borrando nuestras huellas para permitir
que la posición k pueda ser ocupada por otra esquina. Por eso se pone la
r
instrucción “ k := k - 1 ” al terminar el procedimiento.
fo
En otras palabras, al terminar quitamos de la pila camino el elemento
.m

que habı́amos agregado al comienzo.


na

a) Hacer un programa con estas instrucciones, probando con valores pequeños,


tu

e.g. m = 3 y n = 2 mientras se van corrigiendo los errores.


on

b) ¿Qué pasa si se elimina la instrucción “ k := k - 1 ” en el procedimiento


llegardesde ?
yc

c) Agregar un contador, de manera de ir contando la cantidad de caminos


encontrados, y de modo que la salida tenga una forma como
dm

1: (0,0) (1,0) (2,0) (2,1)


2: (0,0) (1,0) (1,1) (2,1)
.a

3: (0,0) (0,1) (1,1) (2,1)


w
w

Hay 3 caminos
w

cuando m = 2 y n = 1. ✄

12.3. Generando permutaciones


Otros objetos combinatorios de interés son las permutaciones: una permuta-
ción de un conjunto (finito) es un ordenamiento particular de ese conjunto. Por
ejemplo, las permutaciones del conjunto {a, b, c} son 6: (a, b, c), (a, c, b), (b, a, c),
(b, c, a), (c, a, b) y (c, b, a). Indicamos las permutaciones entre paréntesis, “( )”,
pues el orden —obviamente— es importante. En muchos cursos se demuestra,
y por lo tanto no lo haremos aquı́, que si el conjunto tiene n elementos entonces
hay n! permutaciones posibles.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

12.3. Generando permutaciones Pág. 123

Problema 12.5 (Generación de permutaciones). En este problema, co-


piando las ideas de los problemas anteriores, generamos todas las permutaciones
de {1, 2, . . . , n} usando el procedimiento poner :
procedure poner(k: integer);
(* poner en la posicion k los que faltan *)
var j: integer;
begin
if (k < n) then
(* buscar y agregar los que faltan *)
for j := 1 to n do begin
if (falta[j]) then begin
falta[j] := false;
a[k] := j;
poner(k+1); (* ir a proxima posicion *)
falta[j] := true

m
end (* if *)

o
end (* for *)

.c
else (* k = n: nueva permutacion *)

os
begin
(* buscar y agregar el que falta *)
j := 0;
r
fo
repeat j := j + 1 until falta[j];
.m

a[k] := j;
(* imprimir *)
na

write(’ ’);
for j := 1 to n do write(a[j]:3);
tu

writeln
on

end
end;
yc

donde a es un arreglo global en el que guardamos la permutación que estamos


dm

construyendo, y falta es un arreglo cuyos elementos son de tipo “ boolean ” (y


tiene la misma longitud de a), de modo que falta i es falso o verdadero de acuerdo
.a

a si el elemento i ya ha sido colocado o no (respectivamente) en la permutación.


Observamos que
w

• Los arreglos a y falta deben tener una longitud adecuada. 10 es suficiente


w

para nosotros.
w

• n debe ser una variable global. En cambio, k es el parámetro del procedi-


miento, como en el problema 12.1.
• Inicialmente “faltan” todos los elementos en la permutación, i.e. falta i es
verdadero para todo i = 1, . . . , n.
• En cualquier paso, cuando se agrega el elemento j en la posición k (k < n),
se pone “ falta[j] := false ”, se consideran las posiciones siguientes
(incrementando k) y al volver se vuelve a poner “ falta[j] := true ”. Es-
te “borrado de huellas” es similar al hecho en el procedimiento llegardesde
del problema 12.4 cuando primero se incrementaba k, se hacı́an cosas, y
luego se disminuı́a al terminar.
• a tiene la estructura de pila, aunque no borramos explı́citamente, como en
el problema 12.1. Podrı́amos pensar que falta también es una pila, en la que

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 124 Generando objetos combinatorios

ponemos valores “verdadero” o “falso”, pero esto no es ası́ pues cambiamos


los valores en lugares arbitrarios y en cada momento son importantes todos
los valores, no sólo los primeros.
• Se hace la única llamada “ poner(1) ” en el cuerpo principal.

a) Usando las ideas anteriores, hacer un programa para generar todas las per-
mutaciones de n elementos.
b) En el procedimiento poner , al buscar los elementos que faltan se pone
if (k < n) then
for j := 1 to n do begin
if (falta[j]) then begin
.
.
.
end (* if *)
end (* for *)

m
¿Qué función cumple el par begin–end que encierra a “ if ” en este

o
.c
caso?, ¿es redundante?
¿Serı́a equivalente poner

os
if (k < n) then begin
for j := 1 to n do
r
fo
if (falta[j]) then begin
.m
.
.
.
na

end (* if *)
end (* if *) ?
tu

c) Eliminar (comentándolo) al renglón “ falta[j] := true ” del procedimien-


on

to poner , y comprobar el comportamiento.


yc

d ) Agregar un contador, como en los problemas anteriores, a fin de ir contando


las permutaciones obtenidas, imprimiendo cada permutación con su número
dm

de orden.
Nota: Hay varios algoritmos para generar las permutaciones de {1, . . . , n}, algu-
.a

nos bastante diferentes al que presentamos, y otros más eficientes. Observar que
w

con el presentado, las permutaciones obtenidas están ordenadas “lexicográfica-


mente”, cosa que no ocurre en otros algoritmos. ✄
w
w

12.4. Árboles binarios ordenados


En una figura como la 12.1, tenemos puntos y segmentos que los unen. Los
puntos están en distintos “pisos” o niveles, marcados por las rectas punteadas1 .
Si tuviéreamos nodos hasta un nivel k, vemos que para ir desde el punto superior
a cualquier otro usando los segmentos, necesitamos a lo sumo k pasos. Por lo
tanto, podemos ir desde cualquier punto o nodo a otro cualquiera en a lo sumo
2k pasos, aún cuando puede haber hasta 1 + 2 + 4 + · · · + 2k = 2k+1 − 1 nodos.
Esta observación es muy importante si guardamos y buscamos datos en los
nodos. Por ejemplo, si tuviéramos n = 2k+1 − 1 datos, y los guardáramos en
“una lı́nea”, como en la figura 12.2, para ir de un dato a otro necesitarı́amos en
1 Sı́, a medida que bajamos, el nivel aumenta.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

12.4. Árboles binarios ordenados Pág. 125

nivel 0

nivel 1

nivel 2

nivel 3

Figura 12.1: un árbol binario ordenado.

Figura 12.2: una estructura “lineal”.

o m
.c
el peor caso n − 1 pasos. La diferencia entre estas dos estructuras, la de árbol

os
binario ordenado por un lado y la lineal, es la misma que entre búsqueda binaria
y búsqueda lineal.
r
Una estructura como la de la figura 12.1 se llama árbol binario ordenado, y
fo
el nodo de más arriba (en el nivel 0) se llama raı́z. Cada nodo está conectado
.m

hacia abajo con 0, 1 o 2 hijos, a la izquierda o a la derecha. Como vimos, un


árbol binario ordenado con k niveles puede tener hasta 2k+1 − 1 nodos. En
na

contrapartida, si tiene exactamente k niveles, no puede tener menos de k + 1


nodos.
tu

Nota: El nombre de árbol binario ordenado parece un poco largo. Informal-


on

mente, se llama árbol por la forma de “ramas”, aunque la raı́z se representa


arriba, binario porque cada rama se divide en a lo sumo 2, y ordenado porque
yc

distinguimos entre el hijo a la izquierda y a la derecha.


dm

Problema 12.6. El programa arbolbinario (pág. 182), que toma ideas de [6,
pág. 210] y de [3, pág. 153], muestra la construcción de un árbol binario ordenado
.a

usando arreglos.
w

En los nodos se guarda información, que en nuestro ejemplo es llave y cuenta.


En llave guardamos un entero, pero podrı́a ser una palabra de un texto, o
w

un apellido, etc., mientras que en cuenta guardamos el número de veces que


w

apareció ese dato. En el árbol, un nodo tiene a su izquierda un subárbol cuyos


nodos tienen menor llave, y a su derecha nodos con mayor llave.
A la salida se imprimen los datos ordenados por orden creciente de llave
y las veces que apareció el dato, como se muestra en el ejemplo de salida al
finalizar el listado.
Estudiando el programa, observamos que

• Cada nodo guardará cuatro datos: la llave, la cantidad de veces que apa-
reció, y cómo encontrar a sus hijos. Por lo tanto necesitamos un registro
con 4 campos.

• Los nodos con la información se guardan en un arreglo de nodos llamado


(¡curiosamente!) arbol .

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 126 Generando objetos combinatorios

• Hay una función booleana entrardatos, modificando un poco lo hecho en


el procedimiento leerarreglo del problema 8.1.

• El árbol se va construyendo en el arreglo arbol como pila con narbol ele-


mentos: a medida que llega nueva información se aumenta narbol y se
incorpora al final.

• Pero la información de la estructura de árbol —bien diferente a la de


pila— se guarda en los registros: en izquierda (o derecha) colocamos la
posición en el arreglo del hijo a la izquierda (o derecha). Para indicar que
no hay un hijo (a izquierda o derecha) introducimos la constante nada,
que podrı́a tomar cualquier valor menor que el menor ı́ndice del arreglo
arbol y que hemos puesto en 0.

• Para procesar los datos ingresados, usamos el procedimiento recursivo

m
binario que primero se fija si se está en una posición “vacı́a”, i.e. sin
datos, en cuyo caso se agrega un nodo al árbol, se guarda el dato, se

o
.c
coloca cuenta en 1, y se inicializan los hijos a nada pues están vacı́os.

os
Si la posición no está vacı́a, se pregunta si el dato es el mismo que se
guarda en ese nodo, en cuyo caso se incrementa cuenta, o si debe ir a la
izquierda en caso que sea menor o a la derecha.
r
fo
.m

• El procedimiento enorden imprime los datos en (¡ejem!) orden con la can-


tidad de apariciones en forma recursiva: para mantener el orden, deben
na

imprimirse primero los descendientes a la izquierda, luego el nodo mismo,


y finalmente los descendientes a derecha. La condición “de parada” para
tu

la recursión es encontrar el valor nada.


on

a) Cambiar el procedimiento enorden para imprimir también el nivel en que se


yc

ha alojado la información. Sugerencia: agregar un argumento para el nivel,


dm

formando parte de la recursión, o, alternativamente, redefinir los nodos de


modo de incluir el nivel cuando se construyen.
.a

b) Agregar al programa la impresión de la profundidad (= máximo nivel) del


árbol al terminar de ingresar los datos.
w

c) Cambiar la impresión en orden por post orden (primero se imprimen los


w

subárboles y después el nodo) y por pre orden (primero el nodo y después


w

los subárboles).

Créase o no, ya hemos visto y recorrido árboles binarios ordenados con an-
terioridad, aunque tenı́an una estructura particular que nos permitı́a ahorrar
espacio.
Por ejemplo, en el problema 12.1 construı́amos todas las cadenas de bits
de longitud n o, equivalentemente, los subconjuntos de {1, . . . , n}. Dada una
cadena c de bits de longitud k, podemos considerar el “hijo a izquierda” como
la cadena c0 de longitud k + 1 que se obtiene de c agregando a la derecha un 0, y
de la misma forma considerar el “hijo a derecha” como la cadena c1 de longitud
k + 1 que se obtiene agregando un 1. En la función recursiva del problema 12.1
el nivel que estábamos construyendo era precisamente k, y cuando llegábamos
a k = n, imprimı́amos la cadena.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

12.5. Problemas Adicionales Pág. 127

Nota: También hay “árboles” en los problemas 12.4 y 12.5, sólo que los nodos
pueden tener más de dos hijos. A diferencia del problema de los subconjun-
tos o cadenas de bits, el “recorrido” del árbol es más complicado porque la
descripción de los nodos lo es.
La variable k del procedimiento llegardesde o en el procedimiento poner
indica también el nivel que estamos recorriendo. Las instrucciones “ k := k
- 1 ” y “ falta[j] := true ”, respectivamente, hacen que vayamos “un nivel
hacia arriba” antes de descender nuevamente, y por eso la técnica de “borrar las
huellas” que usamos en ambos casos es conocida como backtracking o “rastreo
inverso”.

12.5. Problemas Adicionales


Problema 12.7. Resolver el problema 12.1 sin usar recursión, usando que las
cadenas de bits de longitud n pueden pensarse como los coeficientes en base 2
de los números entre 0 y 2n − 1. Sugerencia: para k = 0, . . . , 2n − 1, construir

m
la lista de coeficientes e imprimirla. ✄

o
.c
Problema 12.8 (Problema del Viajante). Supongamos que un viajante

os
tiene que recorrer n ciudades (exactamente) volviendo a la de partida, sin repetir
su visita a ninguna (salvo la inicial), y que el costo de viajar desde la ciudad i
r
a la ciudad j es cij ≥ 0. El problema del viajante es encontrar una permutación
fo
(a1 , a2 , . . . , an ) de (1, 2, . . . , n) de modo que el costo total del recorrido, ca1 a2 +
.m
ca2 a3 + · · · + can−1 an + can a1 , sea mı́nimo. Como se recorre un ciclo, es suficiente
tomar a1 = 1.
na

a) Usando una variante del programa del problema 12.5, hacer un programa
tu

para encontrar una solución al problema cuando el costo cij es:


on

i) i + j,ii) |i − j|,iii) i × j,iv ) dado como entero aleatorio entre 1 y 10.


Sugerencia: definir una variable min de tipo “ real ” y un arreglo opt donde
yc

se guardarán el costo más chico obtenido y la permutación correspondiente,


dm

y
Pponer
P inicialmente min a un valor muy grande e inalcanzable como 1 +
i j cij . A medida que se van recorriendo las permutaciones, calcular
el costo de la permutación y compararlo con min, cambiando min y opt
.a

acordemente. Tomar n pequeño, 4 o 5 al principio, mientras se prueba el


w

programa, y no mayor que 10–12 en cualquier caso.


w

b) Ver que en el caso i) del inciso anterior, el costo es el mismo para cualquier
w

permutación.
c) ¿Podrı́as decir cuál es el óptimo en el caso ii) para cualquier n? Sugerencia:
podemos pensar que los puntos 1, . . . , n están sobre una recta y los costos
son las distancias.
Nota: En los ejemplos i–iii) los costos son simétricos, i.e. cij = cji . Como
recorrer un ciclo en uno u otro sentido no cambia el costo, estamos trabajando
(al menos) el doble de lo necesario con la propuesta.
El problema del viajante y otros similares son sumamente importantes y
se aplican a problemas de recorridos en general. Por ejemplo, para “ruteo” de
vehı́culos, máquinas para perforar o atornillar, circuitos impresos y “chips”,
etc., donde el costo puede medirse en dinero, tiempo, longitud, etc. Conse-
cuentemente, hay toda una área de las matemáticas y computación dedicada al
estudio de su resolución eficiente. Hasta el momento no se conocen algoritmos

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 128 Generando objetos combinatorios

verdaderamente buenos, resolviéndose exactamente el caso de unos pocos miles


de “ciudades” y costos arbitrarios. Aunque se sospecha que no hay algoritmos
“eficientes”, aún no se ha demostrado que no los hay (para esto, hay que desa-
rrollar toda una teorı́a matemática de qué significa “eficiente”). El algoritmo
propuesto acá es quizás el menos eficiente, pues analiza todas las soluciones
posibles, y por eso se lo llama de búsqueda exhaustiva. ✄

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 13

Grafos y árboles

m
Si tomamos un conjunto de ciudades y las carreteras que las unen, y re-
presentamos gráficamente a las ciudades como puntos y a las carreteras como

o
segmentos o curvas uniendo esos puntos, obtenemos una figura similar a la fi-

.c
gura 11.5, en la cual pensábamos que los segmentos eran calles y los puntos las

os
intersecciones.
La idea subyacente en ambos casos es la de grafo, un conjunto de nodos o
r
fo
vértices (las ciudades o intersecciones) que indicaremos por V , y un conjunto
.m
de aristas (las rutas o calles), E, cada una de las cuales queda definida por dos
nodos.
na

No sólo los grafos están relacionados con calles o rutas. En comunicaciones


también podemos pensar que los nodos son computadoras y las aristas represen-
tu

tan la posibilidad de que dos computadoras se conecten entre sı́. O, saliendo de


on

las comunicaciones, podemos pensar en un árbol genealógico, donde los nodos


son las personas y las aristas relacionan padres con hijos. En fin, los ejemplos
yc

de aplicaciones de grafos son muy variadas, y muchas veces sorprendentes.


Euler (1707–1783) fue uno de los más grandes y prolı́ficos matemáticos,
dm

y ya hemos mencionado su nombre en conexión con análisis (la constante de


Euler), teorı́a de números (la función φ y la fórmula de Euler-Binet), aunque
.a

éstas son comparativamente contribuciones menores suyas.


Euler también fue el que originó el estudio de teorı́a de grafos y la topo-
w

logı́a al resolver en 1736 el famoso problema de los puentes de Königsberg (hoy


w

Kaliningrad, en Rusia), donde el rı́o Pregel se bifurcaba dejando dos islas (y


w

dos costas), y las islas y las costas se unı́an por siete puentes. Euler resolvió el
problema, demostrando que no se podı́an recorrer todos los puentes pasando
una única vez por ellos, demostrando el teorema que hoy llamamos “de grafos
eulerianos” y que mencionamos en el problema 13.7.
En este capı́tulo veremos algunas propiedades básicas y algunos algoritmos
elementales para grafos. Nos contentaremos con dar una simple, y seguramen-
te demasiado breve, descripción de los términos y propiedades que usaremos,
dejando para los cursos de matemática discreta las definiciones rigurosas y las
demostraciones.
Nota: Lamentablemente no hay una nomenclatura ni notación uniforme de los
muchos conceptos asociados a grafos, de modo que las nuestras pueden diferir
de las de otros autores.
A fin de distinguir los nodos entre sı́ es conveniente darles nombres, pero

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 130 Grafos y árboles

para el uso computacional supondremos que si hay n nodos, éstos se denominan


1, 2, . . . , n, i.e. V = {1, 2, . . . , n}. Más aún, casi siempre usaremos el nombre n
para el cardinal de V . Las aristas están formadas por un par de nodos, y como
el orden no importa, indicaremos la arista que une el nodo a con el nodo b por
{a, b}. Claro que decir que {a, b} ∈ E es lo mismo que decir que {b, a} ∈ E.
Si e = {a, b} ∈ E, diremos que a y b son vecinos o adyacentes, que a y b son
los extremos de e, o que e incide sobre a (y b). En fin, ası́ como n es el “nombre
oficial” de |V |, el “nombre oficial” para |E| es m. A veces, un nodo no tiene
vecinos —no hay aristas que inciden sobre él— y entonces decimos que es un
nodo aislado.
En la figura 13.1, mostramos un ejemplo de grafo donde n = 6,

E = {{1, 2}, {1, 3}, {2, 3}, {2, 6}, {3, 4}, {3, 6}, {4, 6}}.

y por lo tanto m = 7, y el nodo 5 es aislado.

m
2 1

o
.c
r os
fo
3 6
.m
na
tu

4 5
on
yc

Figura 13.1: un grafo con n = 6 nodos y m = 7 aristas, el nodo 5 es aislado.


dm

Siguiendo con la analogı́a de calles y rutas, es común hablar de caminos,


.a

una sucesión de nodos (el orden es importante) de la forma (v1 , v2 , . . . , vk ),


donde {vi , vi+1 } ∈ E para i = 1, . . . , k − 1. Un camino en principio puede tener
w

nodos repetidos, y si se cierra sobre sı́ mismo de modo que v1 = vk , decimos


w

que se trata de un camino cerrado, mientras que si no tiene nodos repetidos


w

decimos que es un camino simple. Por ejemplo, en la figura 13.1, {3, 2, 6, 3, 4}


es un camino, {1, 2, 3} es un camino simple y {4, 3, 2, 1, 3, 6, 4} es un camino
cerrado. Claro que podemos describir un camino tanto por los vértices como por
las aristas intermedias, y en vez de poner (v1 , v2 , . . . , vk ) podrı́amos considerar
({v1 , v2 }, {v2 , v3 }, . . . , {vk−1 , vk }).
Un ciclo es un camino cerrado sin aristas repetidas (pero puede tener varios
nodos repetidos). Por ejemplo, {4, 3, 2, 1, 3, 6, 4} es un ciclo en el grafo de la
figura 13.1.
De fundamental importancia es reconocer si un grafo es conexo, es decir, si
existe un camino desde cualquier nodo a cualquier otro nodo. Por otra parte, si
un grafo es conexo pero no tiene ciclos, decimos que es un árbol.
Por ejemplo, el grafo de la figura 13.1 no es conexo, pues tiene un nodo
aislado. En la figura 13.2.a) mostramos otro grafo no conexo, y en b) un árbol.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 131

a) grafo no conexo con 9 nodos y 9 aristas. b) árbol con 7 nodos.

Figura 13.2: un grafo no conexo y un árbol.

Sólo consideraremos grafos simples, para los que no hay una arista de un
nodo en sı́ mismo, ni aristas paralelas uniendo los mismos nodos. En este caso,

m
podemos relacionar n = |V | y m = |E|: si hay n elementos, hay c(n, 2) =
n(n − 1)/2 subconjuntos de 2 elementos, de modo que m ≤ c(n, 2).

o
.c
Si además el grafo es conexo (y simple), como se puede unir un nodo con los
n − 1 restantes, debe haber al menos n − 1 aristas. De modo que para un grafo

os
(simple) conexo, m tiene que estar básicamente entre n y n2 .
r
A veces se consideran grafos dirigidos o digrafos, en los que las aristas están
fo
orientadas, y por lo tanto se indican como (a, b), y se distingue entre (a, b) y
.m
(b, a). Nosotros no estudiaremos este tipo de grafos, aunque en realidad hemos
trabajado con el grafo de la figura 11.5 como si fuera un digrafo: las calles sólo
na

iban de sur a norte o de oeste a este.


Dada su estructura, es más sencillo trabajar con árboles que con grafos.
tu

Como hemos dicho, un árbol es un grafo (simple) conexo y sin ciclos, pero hay
on

muchas formas equivalentes de describirlo, algunas de las cuales enunciamos


como teorema (que por supuesto creeremos):
yc

Teorema 13.1 (Caracterizaciones de árboles). Un grafo simple G = (V, E) con


dm

|V | = n es un árbol si y sólo si se cumple alguna de las siguientes condiciones:


a) Para cualquier a, b ∈ V existe un único camino que los une.
.a

b) G es conexo y |E| = n − 1.
w

c) G no tiene ciclos y |E| = n − 1.


w

d) G es conexo, y si se agrega una arista entre dos vértices cualesquiera, se


w

crea un único ciclo.


e) G es conexo, y si se quita cualquier arista —pero no los nodos extremos—
queda no conexo.

A veces en un árbol consideramos un nodo particular como raı́z, y miramos


a los otros nodos como “descendientes” de la raı́z: los que se conectan mediante
una arista a la raı́z son los “hijos”, los que se conectan con un camino de 2
aristas son los “nietos” y ası́ sucesivamente. Dado que hay un único camino de
la raı́z a cualquier otro nodo, podemos clasificar a los nodos según niveles: la
raı́z tiene nivel 0, los hijos nivel 1, los nietos nivel 2, etc1 .
1 Ya hemos visto este tipo de descripción cuando estudiamos árboles binarios ordenados,

en el problema 12.6.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 132 Grafos y árboles

Por supuesto, podemos pensar que los nietos son hijos de los hijos, los hijos
padres de los nietos, etc., de modo que —en un árbol con raı́z— hablaremos
de padres, hijos, ascendientes y descendientes de un nodo. La raı́z será el único
nodo sin ascendientes, mientras que habrá uno o más nodos sin descendientes.
También es común referirse al conjunto de descendientes de un nodo (aunque
el nodo no sea la raı́z) como una rama del árbol.

13.1. Representación de grafos en la computa-


dora
Antes de meternos de lleno con los algoritmos, nos falta una “cosita” más:
¿cómo guardar la información de un grafo en la computadora? Ya sabemos que
los nodos son 1, 2, . . . , n, pero ¿y las aristas?
Hay muchas formas de representar un grafo en general, y nosotros en este

m
capı́tulo nos limitaremos a dos: dar la lista de aristas, y dar la matriz de adya-

o
cencias, una matriz cuyas entradas son sólo 0 o 1 y de modo que la entrada ij

.c
es 1 si y sólo si {i, j} ∈ E.

os
La representación mediante matriz de adyacencias es cómoda, y relativa-
mente fácil de entender. Quizás serı́a más eficiente —para los algoritmos que
r
veremos— dar para cada nodo una lista de sus vecinos. Esta tercera forma
fo
puede implementarse mediante arreglos, pero es mucho más natural usar listas
.m
encadenadas que veremos en el próximo capı́tulo, especialmente en el proble-
ma 14.9.
na

En lo que resta de este capı́tulo, supondremos las declaraciones:


tu

const
MAXN = 20; (* maximo numero de nodos *)
on

MAXM = 100; (* maximo numero de aristas *)


yc

type
dm

arreglodevertices = array[1..MAXN] of integer;


tipoarista = record i, j: integer end;
.a

arreglodearistas = array[1..MAXM] of tipoarista;


matrizNN = array[1..MAXN,1..MAXN] of integer;
w
w

var
w

ngrafo, mgrafo: integer;


aristasgrafo: arreglodearistas;
adyacencias: matrizNN;
usando para las aristas una representación como arreglo de registros (pudiendo
haber declaraciones adicionales, por supuesto).
Usualmente el ingreso de datos es más sencillo mediante la lista de aristas,
pues la matriz de adyacencias tiene n2 elementos y en general m  n2 (y siempre
m ≤ n(n − 1)/2 para grafos simples). De cualquier modo, es conveniente tener
a mano procedimientos para la lectura y pasar de una a otra representación:

Problema 13.2. En nuestros programas supondremos que ngrafo, el número


de nodos del grafo, es entrado explı́citamente (recordando que los nodos serán
entonces 1, 2, . . . , ngrafo).

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.1. Representación de grafos en la computadora Pág. 133

a) Hacer un procedimiento para leer las aristas ingresadas por terminal, donde
cada arista consta de dos números enteros, formando un arreglo de longitud
mgrafo.
Nota: Como es usual, supondremos que el usuario ingresa correctamente
los datos: los vértices de las aristas son enteros entre 1 y ngrafo, no hay
aristas repetidas o de la forma {i, i}, y no hay más de MAXM aristas.
b) Considerando el procedimiento dearistasaadyacencias ,
procedure dearistasaadyacencias;
var i, j, k: integer;
begin
for i := 1 to ngrafo do
for j := 1 to ngrafo do
adyacencias[i,j] := 0;
for k := 1 to mgrafo do
with aristasgrafo[k] do begin

m
adyacencias[i,j] := 1;

o
adyacencias[j,i] := 1

.c
end

os
end;
hacer un programa que lea el número de nodos, las aristas (como en el inciso
r
fo
anterior), calcule la matriz de adyacencias, e imprima para cada nodo, los
nodos que son adyacentes. Por ejemplo, si la entrada son las aristas {1, 5},
.m

{2, 5} y {4, 2}, deberá imprimir


na

Nodo Vecinos
1 5
tu

2 4 5
on

3
4 2
yc

5 1 2
dm

c) Hacer un procedimiento que, dada la matriz de adyacencias, construya el


arreglo de aristas (a fin de evitar repeticiones, es conveniente construir sólo
aristas de la forma {i, j} con i < j), y verificarlo incorporándolo al programa
.a

anterior. ✄
w
w

Problema 13.3 (Grado de vértices). Dado un grafo G = (V, E), para cada
nodo v ∈ V se define su grado o valencia, δ(v), como la cantidad de aristas que
w

inciden en v, o equivalentemente, la cantidad de vecinos de v (excluyendo al


mismo v).
Por ejemplo, en el grafo de la figura 13.1, los grados son δ(1) = 2, δ(2) =
3, δ(3) = 4, δ(4) = 2, δ(5) = 0, δ(6) = 3.
Tomando como base el problema anterior:
a) Hacer un programa que ingrese un grafo dado por sus aristas y calcule δ(v)
para todo v ∈ V .
b) Uno de los primeros teoremas que se ven en teorı́a de grafos dice que si U
es el conjunto de nodos de grado impar, entonces
X
δ(v) es par.
v∈U

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 134 Grafos y árboles

Incorporar al inciso anterior un procedimiento para hacer este cálculo y


verificar el teorema. ✄

13.2. Recorriendo un grafo


Cuando consideramos grafos, es de interés tener algoritmos para recorrerlo,
i.e. “visitar” los nodos en forma ordenada, evitando visitar nodos ya visitados,
y siempre “caminando” por las aristas del grafo. Es lo que hemos hecho con el
árbol binario ordenado del problema 12.6 al recorrerlo “en orden”.
Claro que si el grafo no es conexo, no será posible hacerlo. Por suerte, con
un pequeño esfuerzo extra, los algoritmos que veremos también nos dirán si el
grafo es conexo o no.
Estos algoritmos se encuadran en la estructura del algoritmo visitar, que
mostramos en “seudo-código” en el cuadro 13.1, donde con “←” denotamos la

m
asignación y con la indentación implı́citamente señalamos un par “begin-end” o

o
“comienzo-fin” para agrupar instrucciones.

.c
os
Algoritmo visitar

r
fo
Entrada: un grafo G = (V, E), y un nodo i0 ∈ V .
.m

Salida: el mismo grafo con los nodos que se pueden alcanzar des-
de i0 “visitados”.
na

comienzo
Q ← {i0 };
tu

mientras Q 6= ∅ hacer
on

sea i ∈ Q;
sacar i de Q;
yc

“visitar” i;
para todo j adyacente a i hacer
dm

si j no está “visitado” y j ∈
/ Q entonces agregar j a Q
fin
.a
w

Cuadro 13.1: esquema del algoritmo visitar para recorrer todos los nodos a los
w

que se puede llegar desde i0 .


w

En el algoritmo se guardan en la cola Q los vértices que pueden visitarse, esto


es, son adyacentes a algún nodo ya visitado pero aún no han sido visitados. Ası́,
en todo momento del algoritmo habrá tres clases de nodos: los ya visitados, los
que están en la cola (y todavı́a no se han visitado), y los que no fueron visitados
ni están en la cola (porque todavı́a no visitamos ninguno de sus vecinos).
Como no queremos agregar a la cola un nodo ya agregado, debemos guardar
información que nos diga si un nodo ha estado en la cola o no. Para los algoritmos
que haremos será conveniente usar un arreglo padre, inicialmente en 0, y al
incluir el nodo j en la cola porque es vecino del nodo i que estamos visitando,
pondremos padre j = i (> 0). Ası́, la condición padre i > 0 indicará que i se ha
puesto alguna vez en la cola, aunque ya no esté.
A medida que recorremos el grafo, las aristas que usamos y los nodos que

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.3. Recorrido a lo ancho y en profundidad Pág. 135

visitamos van formando un árbol2 que (cuando el grafo es conexo) se llama


generador o de expansión del grafo, pues contiene a todos los nodos del grafo
original y las aristas del árbol son también aristas del grafo original (pero pueden
no ser todas).
Por supuesto que si el grafo que queremos recorrer es ya un árbol, no se
formará uno nuevo. Pero sı́ que podemos tomar al nodo i0 , con el que empezamos
el recorrido, como raı́z, y el arreglo padre justamente nos dirá quién es el padre
de cada nodo en el árbol que se forma.
Pondremos padre i0 = i0 para indicar que i0 es justamente la raı́z y no po-
demos seguir “más arriba”, de modo que también para i = i0 la condición
padre i > 0 es equivalente a que el vértice i se ha incoporado alguna vez a la
cola.

13.3. Recorrido a lo ancho y en profundidad

m
En el recorrido “a lo ancho” o “ancho primero”, visitamos primero el nodo

o
i0 , luego sus vecinos, luego los vecinos de los vecinos, etc., sin volver a visitar a

.c
alguien ya visitado. O sea que, pensando en el árbol que se formará, visitaremos

os
primero la raı́z, después todos sus hijos, después todos sus nietos, etc., en el mis-
mo orden en que los vamos encontrando. Es entonces conveniente implementar
r
fo
la cola Q como cola fifo (como en la sección 12.1): el que llegó antes sale antes.
.m
Problema 13.4 (Recorrido a lo ancho). Implementamos el algoritmo en el
programa anchoprimero (pág. 185), y observamos que:
na

• Además de las variables mencionadas en la pág. 132, incorporamos narbol


tu

y marbol que nos indicarán la cantidad de nodos y aristas en el árbol.


Por supuesto, al finalizar tendremos narbol = ngrafo si y sólo si hemos
on

recorrido todos los nodos, o equivalentemente, si el grafo original es conexo.


yc

También agregamos el arreglo aristasarbol , donde guardaremos las aristas


del árbol construido, y el arreglo de vértices orden, donde guardaremos el
dm

orden en que han sido visitados los nodos.


Es posible que sólo querramos saber si el grafo es conexo. En este caso los
.a

arreglos aristasarbol y orden no son necesarios, y sólo habrá que mantener


w

narbol (o marbol , pues marbol = narbol - 1), y comparar con el valor de


w

ngrafo.
w

• El procedimiento ancho es el que realiza el trabajo. Hemos tomado como


nodo inicial a 1, guardando la cola (fifo) con los nodos a visitar en el
arreglo avisitar , entre ppo y fin.
Cuando visitamos un nodo i, guardamos el número correlativo de visi-
ta en orden. Luego recorremos los vecinos, reconocidos por la condición
adyacencias ij > 0. Si un vecino j de i tiene padre j = 0, quiere decir que
no ha sido agregado a la cola, y entonces se lo coloca en la cola y actualiza
el valor de padre j .
• A fin de reconocer las aristas del árbol, usamos el arreglo padre en el
procedimiento hacerarbol .
2 Esto irá quedando más claro con los ejemplos. Es una propiedad que no demostraremos,

¡como tantas otras!

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 136 Grafos y árboles

a) Ejecutar el programa, tomando como entrada el grafo de la figura 13.1,


agregando las aristas {3, 5} y {4, 5}. Los nodos se recorren en el orden
1, 2, 3, 6, 4, 5.
b) Cambiar el programa de modo de que la raı́z sea un nodo arbitrario r (en
vez de 1), ingresado por el usuario.
Nota: Una fuente de ineficiencia en la implementación es el uso de la matriz de
adyacencias para reconocer a los vecinos. Esto hace que nuestro programa haga
del orden de n2 pasos para recorrer el grafo. Si m  n2 , es más conveniente
usar directamente una lista de vecinos para cada nodo, puesto que entonces el
algoritmo hará del orden de m pasos.
Nota: El orden en que se recorren los nodos —tanto en ancho primero como
en profundidad primero que veremos luego— está determinado también por
el orden que se dan a los vecinos. Acá, al usar la matriz de adyacencias para
buscar los vecinos, seguimos el orden de los naturales, pero podrı́a ser otro. En
las aplicaciones, el orden de los vecinos no es importante. ✄

m
En general, aún cuando el grafo sea un árbol binario ordenado (y con raı́z

o
.c
1), el recorrido a lo ancho es distinto de los recorridos “en orden”, “pre orden” o
“post orden” que hemos visto en el problema 12.6, pues ahora visitamos los veci-

os
nos de la raı́z, luego los vecinos de los vecinos, etc., en otras palabras, visitamos
el árbol por niveles.
r
fo
Problema 13.5. Agregar instrucciones al programa arbolbinario de modo de
.m

imprimir también el árbol construido por niveles: primero la información del


na

nodo en el nivel 0, luego la de los nodos en el nivel 1, etc., hasta la profundi-


dad. ✄
tu

Si en el algoritmo visitar, en vez de implementar la cola como fifo la imple-


on

mentamos como lifo (pila), visitaremos primero los nodos que se han incorpo-
rando más recientemente a la pila. Si el grafo fuera ya un árbol, resultará que
yc

primero visitaremos toda una rama hasta el fin antes de recorrer otra3 , lo que
dm

hace que este tipo de recorrido se llame “en profundidad” o de “profundidad


primero”.
.a

Problema 13.6 (Recorrido en profundidad). Cambiar apropiadamente el


w

procedimiento ancho del programa anchoprimero de modo de implementar una


w

pila (cola lifo) en vez de una cola fifo, para realizar el recorrido en profundidad
primero. Si la entrada es la misma que en el problema 13.4 (agregando las aristas
w

{3, 5} y {4, 5}), y se sigue el orden de los naturales (al listar los vecinos de un
nodo), la salida debe ser 1, 2, 6, 4, 5, 3. ✄

Problema 13.7. Un célebre teorema de Euler dice que un grafo tiene un ciclo
que pasa por todas las aristas exactamente una vez, llamado ciclo de Euler,
si y sólo si el grafo es conexo y el grado de cada vértice es par (recordar el
problema 13.3).
Modificar el programa anchoprimero para que a la salida determine también
si el grafo tiene o no un ciclo de Euler usando el teorema.
Nota: Un problema muy distinto es encontrar un ciclo de Euler en caso de
existir. Esto es lo que hacemos en el problema 14.10. ✄
3 Bah, que nos vamos por las ramas.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.4. Camino más corto: Dijkstra Pág. 137

13.4. Camino más corto: Dijkstra


Algunas veces la información interesante de un grafo está en los nodos, y las
aristas nos dicen que, por alguna razón, dos nodos están relacionados y nada
más. Tal es el caso del árbol binario del problema 12.6, donde la conexión estaba
asociada al orden entre los datos guardados en los nodos.
Otras veces, las aristas tienen información adicional. Por ejemplo si las aristas
indican rutas entre ciudades, podrı́a ser la distancia entre las ciudades. Cuando
cada arista e ∈ E de un grafo tiene un costo o peso asociado, we , decimos que
se trata de un grafo con pesos o pesado (nosotros sólo consideraremos el caso
we > 0 para todo e ∈ E).
En la figura 13.3 damos un ejemplo de grafo con pesos, donde los pesos sobre
las aristas están recuadrados, pero por comodidad reproducimos los datos en el
cuadro a su costado.

m
2 3 Arista e peso we
{1,2} 2

o
{1,4}

.c
2 1
3
1 {1,5} 8

os
2
1 {2,3} 2
{2,4}
1 1 6
r 1
fo
{3,4} 1
{3,5}
.m
2
3 3 {3,6} 1
na

{4,6} 1
8
4 5 {5,6} 3
tu
on

Figura 13.3: un grafo con pesos en las aristas.


yc

Si tuviéramos que ir de una ciudad a otra, teniendo distintas rutas alterna-


tivas para elegir, es razonable preguntarse cuál de ellas será la más corta (o la
dm

más barata). Éste es el problema del camino más corto: en un grafo pesado, y
dados dos nodos s, t ∈ V , encontrar un camino (v0 = s, v1 , . . . , vk = t) con peso
.a

total mı́nimo, donde el peso total de un camino se define como


w

X
k
w

w{v0 ,v1 } + w{v1 ,v2 } + · · · + w{vk−1 ,vk } = w{vi−1 ,vi } .


w

i=1

Observar que el valor de k no está fijo: no nos interesa si tenemos que usar
una ruta o cien, sólo nos interesa que la distancia total para ir de s a t sea
mı́nima.
Por ejemplo, en el grafo de la figura 13.3 podemos usar varios caminos para
ir del vértice 1 al 5: el camino (1, 5) usa una única arista (k = 1) y tiene peso 8,
el camino (1, 2, 3, 5) usa 3 aristas y tiene costo total 2 + 2 + 1 = 5, y en realidad
no hay otro con menor costo.
Tal vez el algoritmo más conocido para resolver este problema sea el de
Dijkstra, que sigue la estructura del algoritmo visitar : se comienza desde un
nodo, en este caso s, se lo coloca en una cola, y se visitan los nodos de la cola.
E. W. Dijkstra (1930–2002) nació y murió en Holanda. Fue uno de los más
grandes intelectos que contribuyeron a la lógica matemática subyacente en los

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 138 Grafos y árboles

programas de computación y sistemas operativos. Entre sus muchas y destaca-


das contribuciones está el algoritmo para el camino más corto que presentamos,
publicado en 1959.
Nuevamente habrá tres clases de nodos: los visitados, los que están en la
cola y no han sido aún visitados, y los que nunca estuvieron en la cola. Como
novedad, para cada i ∈ V consideraremos el valor di de la distancia más corta
de s a i mediante caminos de la forma (s, v1 , v2 , . . . , vk , i) donde todos los nodos
excepto i ya han sido visitados. A fin de indicar que no existe tal camino,
pondremos di = ∞ (siendo el valor inicial para todo i).
A diferencia del recorrido a lo ancho, donde se visita primero a los nodos
que llegaron primero a la cola, en el algoritmo de Dijkstra elegimos para visitar
el nodo de la cola con menor distancia di . El algoritmo termina cuando t es el
próximo nodo a visitar (y entonces dt es la distancia del menor camino de s a
t), o cuando la cola es vacı́a, en cuyo caso no existe un camino desde s hacia t
(y necesariamente el grafo no es conexo).

m
Al visitar un nodo i y examinar un nodo vecino j, verificamos si

o
di + w{i,j} < dj . (13.1)

.c
os
Si esta desigualdad es válida, quiere decir que el camino más corto para ir desde
s a j (sólo por nodos ya visitados) es ir desde s a i con el camino para i, y luego
r
usar la arista {i, j}. Por lo tanto, actualizamos dj poniendo dj = di + w{i,j} .
fo
También agregaremos j a la cola si no se ha agregado aún.
.m

Es una propiedad del algoritmo, que no demostraremos, que si j es un vecino


de i —el nodo que se está visitando— y j ya se ha visitado, la desigualdad (13.1)
na

no puede darse, por lo que, a diferencia del algoritmo visitar no es necesario


verificar si j ya a sido visitado.
tu

Nota: Para demostrar esta propiedad —y que el algoritmo es correcto— se


on

usa de forma esencial que we > 0 para todo e ∈ E. Nosotros dejaremos estas
propiedades para cursos de matemática discreta o teorı́a de grafos.
yc

Podemos esquematizar el algoritmo en seudo-código como:


dm

para todo i ∈ V hacer di ← ∞;


ds ← 0; Q ← {s};
.a

repetir
sea i ∈ Q tal que di = mı́nj∈Q dj ;
w

si i 6= t entonces
w

sacar i de Q;
w

para todo j adyacente a i hacer


si di + w{i,j} < dj entonces
dj ← di + w{i,j} ;
si j ∈
/ Q entonces agregar j a Q
hasta que i = t o Q = ∅
Una simplificación puede hacerse si convenimos que w{i,j} = ∞ cuando
{i, j} ∈
/ E. En este caso, los nodos j adyacentes a i se caracterizan por w{i,j} <
∞, y como los nodos j que no han ingresado en la cola son los que satisfacen
dj = ∞, el último lazo interno puede simplificarse a
para todo j ∈ V hacer
si di + w{i,j} < dj entonces
si dj = ∞ entonces agregar j a Q;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.4. Camino más corto: Dijkstra Pág. 139

dj ← di + w{i,j} ;

Problema 13.8 (Algoritmo de Dijkstra para el camino más corto). En


el programa dijkstra (pág. 188) implementamos el algoritmo y observamos que:

• Las aristas se guardan, como antes, en registros que ahora tienen un campo
más, w , para guardar el peso correspondiente.

• Claro que no podemos usar ∞ en la computadora, pero en el algoritmo


será suficiente tomar un valor mayor que el correspondiente a cualquier
camino (simple). Nosotros hemos tomado el valor de infinito como
X
infinito = 1 + we ,
e∈E

calculando su valor a medida que se ingresan las aristas.

o m
• La distancia desde s al nodo i, usando como nodos intermedios sólo nodos

.c
ya visitados, se indica por dist i .

os
• Usaremos una matriz costos, similar a la de adyacencias y que se construye
en forma análoga, poniendo
r
fo
(
w{i,j} si {i, j} ∈ E,
.m

costos ij =
∞ en otro caso.
na

• El arreglo padre es similar al visto en el programa anchoprimero, y nos


tu

servirá para reconstruir el camino de s a t. Puede eliminarse si no se desea


on

conocer este camino, pues para reconocer si un nodo ha ingresado alguna


vez a la cola usamos dist .
yc

• El mayor trabajo se realiza en el procedimiento mascorto:


dm

1. Se inicializan dist y padre, poniendo


.a

(
0 si i = s,
w

dist i =
∞ en otro caso,
w
w

y (
s si i = s,
padre i =
0 en otro caso.
2. La cola de nodos a visitar se guarda en el arreglo avisitar , que inicial-
mente sólo contiene al nodo s.
3. En el lazo principal, se busca en la cola y se coloca al final el vértice
con menor valor de dist , que será el próximo nodo a visitar. El pro-
cedimiento para encontrar el mı́nimo y modificar la cola es similar al
proceso de selección directa (pág. 98, ver también el problema 8.2.e)).
4. Una vez que determinamos el nodo a visitar, i, lo comparamos con t,
el nodo al cual queremos llegar. Si i = t, hemos llegado y se termina
el procedimiento.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 140 Grafos y árboles

5. En cambio, si i 6= t, recorremos los vecinos de i, indicados con j,


actualizando dist , padre y la cola si fuera necesario, de acuerdo a la
desigualdad (13.1). Antes de cambiar dist j , sin embargo, preguntamos
si j ha estado en la cola comparando dist j con infinito.

• Para construir e imprimir un camino mı́nimo, usamos el arreglo padre en


el procedimiento hacercamino: formamos un arreglo recorriendo los ante-
cesores de t hasta llegar a s. El problema es que, como vamos agregando
atrás, nos queda el camino (t, padre t , . . . , s), en orden inverso.
Ésto puede no ser problema, pero es fácil imprimirlo de atrás para ade-
lante de modo de mostrarlo “al derecho”, como hemos hecho. Si fuera
necesario, se puede invertir el arreglo usando un procedimiento similar al
del problema 8.3.

a) Ejecutar el programa, tomando como entrada el grafo pesado de la figu-

m
ra 13.3, nodo de partida 1 y nodo de llegada 4.

o
b) Cambiar el programa de modo que calcule las distancias de s a todos los

.c
otros nodos del grafo.

os
c) Modificarlo de modo que calcule todas las distancias de i a j, para 1 ≤ i <
j < ngrafo.
r
fo
Nota: Técnicamente el tipo de cola que usamos en los programas dijkstra y prim
.m
—en la próxima sección— se llama cola de prioridad, en vez de “lifo” o “fifo”:
no se elige ni el primero ni el último sino que se usa otro criterio “de valor” o
na

“prioridad” en la elección.
Nuestra implementación de este tipo de colas es un tanto rudimentaria a
tu

fin de mantener la claridad de los programas. El uso de la variante de selección


directa tarda del orden de |V | pasos cada vez que se elige el próximo nodo
on

a visitar. En cambio, el uso de variantes de algoritmos de clasificación más


avanzados hace que se tarde a lo sumo del orden de log2 (|V |) pasos para esa
yc

elección.
dm

Nota: Otra fuente de ineficiencia es el uso de la matriz de adyacencias (o costos)


si m  n2 , como hemos observado en el problema 13.4.
Nota: Hemos tratado de mantener la presentación de los algoritmos y progra-
.a

mas de este capı́tulo — algoritmo visitar y los programas anchoprimero, dijkstra


w

y prim— lo más parecidas entre sı́ a fin de resaltar las semejanzas, lo que tam-
w

bién afecta (un poquito) a la eficiencia. Es muy posible que el lector los vea
con un “disfraz” bastante distinto en otras referencias. ✄
w

13.5. Mı́nimo árbol generador: Prim


Si tuviéramos una cierta cantidad de ciudades y las distancias entre ellas,
un problema interesante es ver cómo construir carreteras de modo de que todas
las ciudades puedan unirse entre sı́ mediante estas nuevas rutas. Dado que no
nos interesan ciclos, pero el grafo formado por las carreteras debe ser conexo,
estamos buscando un árbol generador.
El teorema 13.1 nos dice que cuando tenemos un grafo conexo que no es
un árbol, tendremos más de un árbol generador: será cuestión de tomar un
ciclo en el grafo original, sacar cualquiera de las aristas del ciclo, y repetir el
procedimiento hasta que no haya más ciclos, manteniendo la conexión. Como

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.5. Mı́nimo árbol generador: Prim Pág. 141

las aristas que sacamos son bastante arbitrarias, en general hay muchos árboles
generadores de un mismo grafo.
Cuando hay pesos (o costos) asociados a las aristas, como en el caso de la
construcción de las carreteras, nos interesa encontrar entre todos los árboles ge-
neradores uno que minimice la suma de los pesos de las aristas que lo componen,
X
we ,
e en el árbol

llamado árbol generador mı́nimo (es posible que haya más de un árbol con esta
propiedad, por ejemplo si los costos de todas las aristas son 1).
Por ejemplo, volviendo al grafo de la figura 13.3, podemos formar un árbol
generador con las aristas {1, 2}, {1, 4}, {2, 3}, {3, 6}, {6, 5} (siempre un árbol ge-
nerador debe tener n − 1 aristas), con peso total 2 + 3 + 2 + 1 + 3 = 11, y si
reemplazamos cambiamos la arista {5, 6} por la arista {3, 5}, reducimos el costo

m
en 1.
Hay varios algoritmos para encontrar un árbol generador mı́nimo, y nosotros

o
.c
veremos aquı́ el debido a Prim, pues sigue la estructura del algoritmo visitar,
siendo por lo tanto muy similar al recorrido a lo ancho o al algoritmo de Dijkstra

os
para el camino más corto. Otro algoritmo, más eficiente en la mayorı́a de los

r
casos prácticos, es el de Kruskal, que dejamos para la sección de Problemas
fo
Adicionales pues su implementación es más elaborada.
.m
El algoritmo de Kruskal fue publicado en 1956, mientras que el de Prim fue
publicado en 1959. Kruskal también obtuvo en forma independiente el algoritmo
na

de Prim y lo publicó en 1959 en el mismo trabajo donde presenenta su algoritmo


para el camino más corto, lo que no es sorpresa por la similitud.
tu

Recordemos que en el algoritmo visitar mantenı́amos una cola con los nodos
on

a visitar, y se formaban tres clases de nodos: los visitados, los que estaban en
la cola, y los que nunca habı́an ingresado en la cola.
yc

En el algoritmo de Prim se sigue la misma idea, sólo que un nodo en la cola


se visita cuando su “distancia” a los nodos ya visitados es la menor entre los
dm

nodos de la cola. De modo que hay que hacer pocas modificaciones al algoritmo
de Dijkstra: cambiar la definición de la distancia y, en vez de construir el cami-
.a

no, construir el árbol correspondiente. A diferencia del algoritmo de Dijkstra,


w

tenemos que mantener información sobre los nodos visitados. Como el arreglo
w

padre se irá modificando en los sucesivos pasos (en el Dijkstra también, pero
no en el recorrido a lo ancho), introducimos un nuevo vector con valores lógicos
w

visitado.
Nota: Como con el algoritmo de Dijkstra, se necesita que we > 0 para todo
e ∈ E. Asimismo, dejamos la demostración de que el algoritmo da efectivamente
un árbol generador mı́nimo para los cursos de matemática discreta o teorı́a de
grafos.

Problema 13.9 (Algoritmo de Prim para el mı́nimo árbol generador).


A fin de implementar el algoritmo de Prim, observamos las similitudes y dife-
rencias con el de Dijkstra:

• La matriz de costos y el valor infinito son idénticos.

• En Prim “no existen” s y t, ni por supuesto el camino que los une.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 142 Grafos y árboles

• Mientras que en el programa dijkstra tomamos a s como “raı́z”, en el


programa prim el nodo 1 es el primero en ingresar a la cola, como en el
programa anchoprimero.

• Guardamos información sobre si un nodo ha sido visitado o no en el arreglo


visitado. La distancia se actualiza comparando dist j con w{i,j} en vez de
con w{i,j} + dist i . Por lo tanto, el procedimiento ancho del programa
dijkstra se puede cambiar a

procedure arbolminimo;
var
i, j, k, kmin, hay: integer;
d, dmin: costo;
avisitar: arreglodevertices;
visitado: array[1..MAXN] of boolean;

o m
begin

.c
(* inicializacion *)
dearistasacostos;

os
for i := 1 to ngrafo do begin
padre[i] := 0;
r
fo
dist[i] := infinito;
.m
visitado[i] := false
end;
na

(* 1 es la "raiz" *)
tu

padre[1] := 1; dist[1] := 0; hay := 1;


on

avisitar[1] := 1;
yc

repeat (* aca hay > 0 *)


(* nuevo i: el de minima distancia en la cola *)
dm

kmin := hay; i := avisitar[hay]; dmin := dist[i];


for k := 1 to hay - 1 do begin
.a

j := avisitar[k]; d := dist[j];
if (d < dmin) then begin
w

kmin := k; i := j; dmin := d end


w

end;
w

(* poner i al final de la cola y sacarlo *)


if (kmin < hay) then
avisitar[kmin] := avisitar[hay];
hay := hay - 1;

(* la distancia de i ya no debe modificarse *)


visitado[i] := true;

(* examinar vecinos de i *)
for j := 1 to ngrafo do
if ((not visitado[j]) and
(costos[i,j] < dist[j])) then begin
(* si j no se agrego, agregarlo a la cola *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.6. Problemas Adicionales: Kruskal Pág. 143

if (dist[j] = infinito) then begin


hay := hay + 1; avisitar[hay] := j end;
(* actualizar dist y padre *)
dist[j] := costos[i,j];
padre[j] := i
end
until (hay = 0)
end;

• En dijkstra buscábamos un camino, y ahora debemos construir un árbol,


i.e. encontrar las aristas correspondientes. Esto lo podremos hacer usando
nuevamente padre, agregando los pesos mediante dist . Ası́, el procedi-
miento hacercamino en dijkstra puede reemplazarse por una variante del
procedimiento hacerarbol del programa anchoprimero, observando que el
peso w de la arista {k, padre k } es dist k .

m
• A la salida, debemos imprimir el árbol encontrado y un cartel en caso que

o
el grafo original no sea conexo. Esto puede hacerse modificando las partes

.c
correspondientes de los programas anchoprimero o dijkstra.

os
a) Hacer un programa para implementar el algoritmo de Prim usando las ideas
r
fo
expuestas. En el grafo de la figura 13.3, se obtiene el árbol
b) Incluir también instrucciones a fin de imprimir el peso total del árbol resul-
.m

tante.
na

c) El algoritmo expuesto empieza con el nodo 1. Cambiarlo de modo de poder


comenzar con un nodo arbitrario r ingresado por el usuario. Observar que
tu

se pueden obtener distintos árboles de mı́nimo costo (aunque siempre con


on

el mismo costo total).


Nota: Dado la similitud entre nuestras implementaciones de los algoritmos de
yc

“ancho primero”, Dijkstra y Prim, no es sorprendente que valgan las mismas


observaciones hechas al final de los problemas 13.4 y 13.8. ✄
dm
.a

13.6. Problemas Adicionales: Kruskal


w

El algoritmo de Kruskal para encontrar un árbol generador de mı́nimo peso


w

se aparta un poco del esquema del algoritmo visitar, y más bien se acerca al
w

denominado algoritmo del codicioso pues busca incorporar la mejor arista de las
que quedan.
Antes de describir el algoritmo es conveniente introducir el concepto de com-
ponente conexa (o simplemente componente cuando quede claro que es la co-
nexa): dado un grafo G = (V, E), vamos a dividir, o más correctamente, par-
ticionar, el conjunto de nodos V en subconjuntos, digamos A1 , A2 , . . . , Ac , que
llamaremos componentes conexas de G, de modo que dos nodos u, v ∈ V están
en la misma componente si (y sólo si) existe un camino de u a v. De esta forma
resulta que las componentes A1 , A2 , . . . , Ac satisfacen:

1. V = ∪1≤i≤c Ai .

2. Si c > 1, Ai ∩ Aj = ∅ para 1 ≤ i < j ≤ c.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 144 Grafos y árboles

Dado u ∈ V , podemos encontrar todos los nodos que se conectan con él
mediante el algoritmo visitar (pág. 134), encontrando ası́ la componente a la
cual pertenece. Claro que si el grafo G es conexo, hay una única componente
(c = 1), y recı́procamente.
Nota: A fin de describir el algoritmo de Kruskal, consideramos que las compo-
nentes conexas son conjuntos de nodos. En otras variantes de la definición de
componente conexa, también se incluyen las aristas que unen a los nodos.
En el algoritmo de Kruskal, que esquematizamos en el cuadro 13.2, dado el
grafo G = (V, E) vamos construyendo por etapas un grafo (V, F ), con F ⊂ E,
examinando ordenadamente cada arista de E para decidir si la agregaremos o
no a F .
En el algoritmo, conservamos en cada etapa las componentes conexas de
(V, F ) en C, e indicamos mediante mediante Ci a la componente conexa que tiene
al nodo i. Inicialmente no habrá aristas, es decir, tendremos F = ∅, Ci = {i}
para i ∈ V , y C = {Ci }i∈V .

m
Todas las aristas en el grafo original se ponen en una cola, Q, y en cada paso

o
extraemos de Q la arista de menor peso, digamos e = {i, j}. Si i y j están en

.c
la misma componente conexa (hasta ese momento), i.e. si Ci = Cj , pasamos a
la próxima arista. En cambio, si Ci 6= Cj , podemos unir las componentes Ci y

os
Cj mediante e formando una nueva componente conexa, y guardamos e en el
conjunto F .
r
fo
En principio terminaremos cuando no haya más aristas en la cola Q, pero si
.m

en algún momento llegamos a una única componente conexa, o sea si |C| = 1,


entonces sabemos que ya no agregaremos más aristas (pues Ci = Cj para todo
na

i, j ∈ V ) y podemos terminar aún cuando Q 6= ∅.


Al finalizar el algoritmo, las componentes conexas de H = (V, F ) y de G =
tu

(V, F ) serán las mismas, C, y H será el grafo de menor peso con esta propiedad.
on

Claro que si hay una única componente conexa (de G o de H porque coinciden),
H será un árbol generador mı́nimo de G.
yc

Nota: Como en los caso anteriores, “creeremos” que el algoritmo es correcto.


dm

La mayor dificultad para implementar el algoritmo de Kruskal es mantener la


información sobre los conjuntos Ci a medida que van cambiando, y se ha ideado
.a

una estructura eficiente para ello que —conservando el inglés— llamaremos


union-find (por “unión-encontrar”) y que pasamos a describir.
w

En cada etapa del algoritmo, vamos a considerar para cada componente


w

C ∈ C un nodo r ∈ C representante de C, y una función f : V → V tal que f (i)


w

es el representante de la componente en la que está i, Ci . En particular, si r es


el representante de C, tendremos f (r) = r.
f irá cambiando en cada etapa, pues se van fusionando las componentes
conexas, pero como inicialmente tenemos Ci = {i} para todo i ∈ V , inicialmente
tendremos f (i) = i.
Cuando en alguna etapa se unen C y C 0 —con representantes r y r0 respecti-
vamente— para formar C 00 , por simplicidad elegiremos a uno de ellos, digamos
r, como representante de C 00 . Es decir que al finalizar esa etapa f (r) sigue siendo
r pero f (r0 ) = r, y en realidad f se ha modificado en todos los nodos de C 0 .
Hacer la redefinición de f explı́citamente en cada etapa para todos los nodos
es muy costosa, por lo que se considera otra función auxiliar, g, que inicialmente
toma los mismos valores de f , i.e. inicialmente g(i) = i para todo i ∈ V .
g también se va modificando en cada etapa, pero lo haremos únicamente

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.6. Problemas Adicionales: Kruskal Pág. 145

Algoritmo de Kruskal

Entrada: un grafo G con nodos V , aristas E y pesos {we }e∈E .


Salida: las componentes conexas (nodos) de G, C, y un conjunto
F ⊂ E tal que H = (V, F ) tiene las mismas componentes
conexas que G, y con el mı́nimo peso entre todos los
grafos con esta propiedad. En particular si G es conexo,
H es un árbol generador de mı́nimo peso.
comienzo
para todo i ∈ V hacer Ci ← {i};
C ← {Ci }i∈V ;
F ← ∅;
Q ← E;
mientras (E 6= ∅) y (|C| > 1)hacer

m
sea e = {i, j} una arista de menor peso en Q;

o
sacar e de Q;
si Ci 6= Cj entonces

.c
C ← Ci ∪ Cj ;

os
eliminar Ci y Cj de C y agregar C a C;
Ci ← C; Cj ← C;
r
fo
F ← F ∪ {e}
.m
fin
na

Cuadro 13.2: esquema del algoritmo de Kruskal.


tu
on

sobre el representante que ha dejado de serlo. Con las notaciones anteriores de


C, C 0 , r y r0 , y habiendo elegido a r como representante de C ∪ C 0 , pondrı́amos
yc

g(r0 ) = r. Observemos que una vez que un nodo i deja de ser representante
dm

de la clase Ci , el valor g(i) no se modifica (g sólo se modifica sobre algunos


representantes). Es decir, o bien g(i) no se modifica nunca, o bien se modifica
una única vez.
.a

Para encontrar f (i) en cualquier momento, preguntamos si g(i) = i. En caso


w

afirmativo, quiere decir que, o bien la componente Ci no se unió con ningu-


w

na otra, o bien cada vez que se unió con otra resultó que elegimos a i como
w

representante de la nueva componente, y debe ser f (i) = i.


Por otro lado, si g(i) = j 6= i, quiere decir que en algún momento Ci se
unió con Cj y tomamos como representante a j en vez de i, y por lo tanto Ci = Cj
desde ese momento en adelante. Podrı́a ser que g(j) 6= j, en cuyo caso repeti-
mos el argumento, considerando los valores sucesivos g(i), g(g(i)), g(g(g(i))), . . .
hasta llegar a algún valor k para el cual g(k) = k, que debe ser el representante
de la clase, por lo que f (i) = k.
Nota: El uso de g es similar al uso del arreglo padre en el procedimiento
hacercamino del programa dijkstra. De hecho, estamos pensando a cada com-
ponente conexa como árbol con raı́z, que se reconoce por g(r) = r y las aristas
son de la forma {i, g(i)} con g(i) 6= i. Sin embargo, las aristas {i, g(i)} no tienen
por qué estar en el conjunto de aristas originales E.
En la implementación, llamaremos find (por “encontrar”) a este proceso

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 146 Grafos y árboles

iterativo para calcular la función f , guardaremos los valores de g en el arreglo


repre (por “representante”, claro), que por simplicidad supondremos definido
globalmente, y pondremos
function find(i: integer): integer;
var r: integer;
begin
r := i;
while (r <> repre[r]) do r := repre[r];
find := r
end;
Ahora tenemos que resolver cómo elegir el representante cuando se unen
las componentes C y C 0 con representantes r y r0 respectivamente, o en otras
palabras, cómo actualizar g (o el arreglo repre). En principio podrı́amos elegir
cualquiera de g(r) = r0 o g(r0 ) = r, pero podemos hacer la elección eficiente-

m
mente si tenemos en cuenta que g se usará para buscar, dado i, al representante
de Ci con el procedimiento iterativo de find , y nos interesa mantener el número

o
.c
de iteraciones lo más bajo posible.
Si una componente C tiene como representante a r, llamemos `(r) al número

os
máximo de iteraciones (del lazo “ while ” en el procedimiento find) para cual-

r
quier nodo de esa componente C. Inicialmente tendremos `(r) = 0 para todo r,
fo
pero este valor se irá modificando a medida que fusionemos componentes.
Cuando se juntan las componentes C y C 0 con representantes r y r0 , y
.m

elegimos como representante de la nueva clase a (por ejemplo) r, el número de


na

iteraciones máximas para la nueva componente será:


(
tu

`(r0 ) + 1 para los nodos cuyo representante era r0 ,


on

`(r) para los nodos cuyo representante era r.


yc

de modo que el nuevo valor de `(r) será máx{`(r0 ) + 1, `(r)}.


Si queremos que los valores de ` aumenten lo menos posible, podemos elegir
dm

r o r0 como nuevo representante comparando máx{`(r0 ) + 1, `(r)} y máx{`(r) +


1, `(r0 )}, o equivalentemente, `(r) y `(r0 ):
.a

(
g(r0 ) = g(r) si `(r0 ) < `(r),
w

g(r) = g(r0 ) en otro caso.


w
w

Nota: Esta elección hace que el procedimiento find no haga más de dlog 2 |V |e
iteraciones.
Manteniendo el arreglo long (definido globalmente por simplicidad) para
guardar los valores de `(r), podemos escribir el procedimiento para juntar dos
componentes como:
procedure union(r, rp: integer);
var l, lp: integer;
begin
l := long[r]; lp := long[rp];
if (lp < l) then begin
repre[rp] := r;
if (l < lp + 1) then long[r] := lp + 1

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.6. Problemas Adicionales: Kruskal Pág. 147

end
else begin
repre[r] := rp;
if (lp < l + 1) then long[rp] := l + 1
end
end;
Por completitud, incluimos la inicialización de la estructura “union-find”:
procedure inicializaruf;
var i: integer;
begin
for i := 1 to ngrafo do begin
repre[i] := i; long[i] := 0 end
end;
En resumen, para implementar “union-find” necesitamos tres funciones o

m
procedimientos, inicializaruf , find y union, y dos arreglos, repre y long, que

o
por simplicidad hemos considerado como globales.

.c
Ya estamos en condiciones de implementar el algoritmo, con las siguientes

os
observaciones

• Tomamos como variables globales a r


fo
.m

ngrafo, mgrafo, marbol, nconexas: integer;


long, repre: arreglodevertices;
na

aristasgrafo, aristasarbol: arreglodearistas;


tu

La novedad es que no necesitamos una matriz de adyacencias o costos, y


on

en particular no necesitaremos una variable infinito. Pero usaremos una


nueva variable nconexas para indicar la cantidad de componentes conexas.
yc

• Se pueden ingresar los datos de las aristas como en el programa dijkstra,


dm

pero no necesitamos el valor infinito.


.a

• Para la salida necesitaremos un procedimiento para imprimir las aristas


w

elegidas, similar al de escribirarbol en el programa anchoprimero.


w

• Luego habrá que declarar las funciones de “union-find”, como hemos des-
w

cripto anteriormente.

• Podemos hacer el trabajo principal en el procedimiento arbolminimo, don-


de elegimos la arista de menor peso haciendo parcialmente selección directa
(como hicimos en dijkstra al elegir la menor distancia entre los que estaban
en la cola).
No es necesario mantener una cola explı́cita para las aristas si se usa el
mismo arreglo de aristas originales (en todo caso, si es necesario mantener
la lista original, se puede hacer una copia, o directamente pasar el arreglo
de aristas como argumento con lo que la copia se hace automáticamente).
En esta implementación, no “se pierden” las aristas originales, sino que
cambian de lugar (quedando parcialmente ordenadas de mayor a menor).

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 148 Grafos y árboles

procedure arbolminimo;
var
hay, ri, rj, k, kmin: integer;
wmin : costo;
e: tipoarista;

begin
(* inicializacion *)
inicializaruf; (* inicializar union-find *)
nconexas := ngrafo; (* numero de componentes *)
marbol := 0; (* en el bosque no hay aristas *)
hay := mgrafo; (* cantidad de aristas a examinar *)

while ((nconexas > 1) and (hay > 0)) do begin


(* tomar la arista de menor peso

m
y sacarla de la pila *)

o
kmin := hay;

.c
e := aristasgrafo[hay];
wmin := e.w;

os
for k := 1 to hay - 1 do
with aristasgrafo[k] do
r
fo
if (w < wmin) then begin
.m
kmin := k; wmin := w end;
if (kmin < hay) then begin
na

e := aristasgrafo[kmin];
aristasgrafo[kmin] := aristasgrafo[hay]
tu

end;
on

hay := hay - 1;
yc

(* miramos si los extremos estan


en la misma componente *)
dm

with e do begin ri := find(i); rj := find(j) end;


if (ri <> rj) then begin
.a

(* distintas componentes *)
(* agregar arista a arbol *)
w

marbol := marbol + 1;
w

aristasarbol[marbol] := e;
w

(* juntar componentes conexas *)


union(ri, rj); nconexas := nconexas - 1
end (* if ri <> rj *)
end (* while nconexas y hay *)
end;

• Al finalizar, además de las aristas, podemos imprimir la cantidad de com-


ponentes conexas, nconexas, donde sabemos que |nconexas| = 1 ⇔ el grafo
es conexo y las aristas elegidas forman un árbol.

Problema 13.10 (Algoritmo de Kruskal para el mı́nimo árbol gene-


rador). Hacer un programa con las indicaciones dadas, y verificarlo con las
mismas entradas que en el problema 13.9 (figura 13.3). Es posible que la solu-

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

13.6. Problemas Adicionales: Kruskal Pág. 149

ción sea distinta. ✄


Nota: En nuestra implementación de Dijkstra, la fuente de mayor ineficiencia
está en la selección del mayor elemento. En nuestra variante se tarda alrededor
de m pasos en ubicarlo, mientras que puede hacerse en el orden de log2 m
pasos. Tal cual está planteado, el algoritmo puede tardar del orden de m2
pasos por el ordenamiento, pero en variantes más eficientes puede tardar del
orden de m log2 m pasos. Aún con nuestra implementación, el comportamiento
para “grafos tı́picos” es mejor que el de Prim.

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Capı́tulo 14

Punteros y listas
encadenadas

o m
.c
La lista encadenada surge de la necesidad de evitar la rigidez de la estructura

os
de arreglo. Recordemos que los arreglos tienen una dimensión predefinida (en
el programa fuente) que a veces resultará excesiva o escasa, dependiendo de la
r
aplicación. También es muy difı́cil eliminar o agregar un elemento intermedio,
fo
pues debemos hacer un corrimiento de varios valores del arreglo (por ejemplo,
.m

en el método de clasificación por inserción directa).


En contraste, veremos que la lista encadenada es una estructura dinámica,
na

que se va construyendo y modificando de acuerdo a los datos que se ingresan.


tu

Para construir una lista encadenada, necesitamos la noción de puntero o


apuntador, que introducimos en la próxima sección.
on
yc

14.1. Punteros
dm

Recordemos que las variables que hemos usado hasta ahora ocupan un lugar
de memoria, que será más grande o más chico dependiendo de su tipo, y que
.a

pensamos como “cajas”. Para reconocer una variable le hemos dado un nombre,
w

su identificador, que tienen sentido para nosotros, pero para la máquina —una
w

vez compilado el programa— los nombres desaparecen y en cambio los identifica


w

por la dirección de memoria en la que empieza la caja y su contenido (y por lo


tanto su longitud).
Podemos pensar que un puntero es una variable donde nosotros guardaremos
direcciones de memoria donde empiezan cajas. Aunque todas las direcciones
(para una máquina y sistema operativo dados) ocupan el mismo espacio, en
Pascal debemos agregar en la declaración de un puntero el tipo al cual “apunta”
o “referencia” (esto es una propiedad del lenguaje, protegiéndonos de posibles
errores).
Por ejemplo, si queremos alojar en el puntero p una dirección de memoria
en la que se guardará un entero, declaramos
var p: ^integer
En este caso, al comienzo del programa p no tiene asignado valor alguno.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 152 Punteros y listas encadenadas

Para indicar que p no tiene una caja relacionada, es muy conveniente y


aconsejable (para evitar problemas) asignarle una dirección “imposible” que en
Pascal se denomina nil :

p := nil
Podemos interpretar a nil como “un cable a tierra”, que indica que no hay
“nada”. Recordemos que justamente hemos usado con el mismo sentido a la
variable nada en el programa arbolbinario (pág. 182).
A pesar de que, habiéndolo declarado, p ocupa un lugar de memoria, ese
lugar es sólo para guardar direcciones, y puede no coincidir con la cantidad de
espacio necesaria para guardar, por ejemplo, un entero. Si además queremos
reservar un lugar para guardar un entero, usamos “ new ”:

new(p)

m
Esta instrucción hace que se reserve un lugar en memoria —la famosa “ca-
ja”— para alojar un dato de tipo entero (en este caso), y guarda en p la dirección

o
de ese lugar, dejando a la caja disponible pero sin valor asignado.

.c
os
Nota: Es posible que no haya más memoria para reservar, cosa improbable con
las computadoras actuales y los programas que nosotros usaremos, por lo que

r
no nos preocuparemos por esta eventualidad. En programación más “seria”
fo
también debemos preocuparnos por dejar libres los lugares obtenidos mediante
.m
“ new ”, que en Pascal se hace mediante “ dispose ” (que no veremos).
La variable (entera) a la que ahora “apunta” p se indica por pˆ. Ası́, si
na

queremos poner el número 3 allı́, hacemos


tu

p^ := 3;
on

Es un error hacer una asignación a pˆ sin haber hecho antes


yc

una reserva de espacio con “ new ”.


dm

En (estándar) Pascal no es posible obtener la dirección de memoria de una


.a

variable. Por ejemplo si n es una variable entera, no podemos asignar a p la


dirección de n. Por supuesto que no podemos hacer
w
w

p := n (* incorrecto! *)
w

pero si p y q son punteros del mismo tipo (declarado mediante “ type ”), es legal
hacer

q := p
aún cuando p o q no referencien dato alguno, ya que estamos copiando en q la
dirección guardada en p. Por otro lado, supuesto que p y q sean del mismo tipo
y que ambos se hayan “inicializado” con new , podemos copiar los contenidos de
una caja a la otra mediante

q^ := p^
Nuestro interés en este tipo de variables es en la creación de listas encade-
nadas.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

14.2. Listas encadenadas Pág. 153

14.2. Listas encadenadas


Una lista encadenada, o simplemente lista 1 , consiste en una serie de elemen-
tos, llamados nodos 2 unidos entre sı́. Los nodos son registros (records), uno de
cuyos campos es un puntero que señala al próximo elemento de la lista. Una
declaración tı́pica para los nodos es:
type
ptrnodo = ^nodo;
nodo = record
info: integer; (* puede haber mas de un
campo de este estilo *)
siguiente: ptrnodo
end;
Observar que la definición de los nodos es de alguna forma recursiva, ya que

m
el tipo “ ptrnodo ” sólo puede definirse si está definido el tipo “ nodo ”, que a
su vez sólo puede definirse si está definido el tipo “ ptrnodo ”. Sin embargo esta

o
recursión es un tanto aparente pues las direcciones de memoria siempre ocupan

.c
el mismo lugar (que dependerá de la computadora y sistema operativo). Ası́, la

os
reserva de espacio para “ ptrnodo ” es siempre la misma, independientemente
del espacio que deba reservarse para una variable de tipo “ nodo ”.
r
fo
Las listas encadenadas tienen asociados un puntero donde se guarda la di-
rección donde comienza la lista. En nuestra implementación consideraremos que
.m

todos los nodos de la lista tienen información3 , por lo que la lista “vacı́a” tiene
na

el puntero asociado en nil (como nada en el programa arbolbinario). Del mismo


modo, si la lista no es vacı́a, su último nodo apunta a nil como sucesor.
tu

En forma similar a la descripción que hicimos de pilas o colas en general en


la sección 12.1 (pág. 117), las operaciones fundamentales con listas son: inicia-
on

lización, agregar nodos a la lista, sacar nodos y recorrer la lista.


yc

Nota: Para una descripción más detallada, ver [6, pág. 194].
Suponiendo que lista es la variable declarada como “ ptrnodo ” que guarda
dm

el comienzo de la lista, tenemos:


.a

Inicialización de lista. Al comienzo la lista no tiene nodos, y ponemos


w

lista := nil
w
w

Agregar un nodo a la lista. Si queremos agregar el nodo p de tipo ptrnodo


“adelante” en la lista, ponemos

p^.siguiente := lista; lista := p;

pero puede ser que querramos construir la lista de otra forma, por ejemplo
si queremos que esté ordenada.
Si queremos agregar siempre atrás, es conveniente guardar también la di-
rección del último nodo de la lista, y debe preverse en la inicialización.
1 A veces, para confundir, también hablamos de listas cuando nos referimos a arreglos.
2 Como en grafos. En realidad, las listas encadenadas se pueden pensar como grafos diri-
gidos.
3 Nuestra presentación difiere de otras donde siempre hay al menos un nodo.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 154 Punteros y listas encadenadas

Agregar un nodo antes o después de otro. Supongamos que p y q son del


tipo “ ptrnodo ”, que pˆ es un elemento de la lista y qˆ debe agregarse
después de pˆ. El esquema simple es:

q^.siguiente := p^.siguiente; p^.siguiente := q;

Si se desea insertar qˆ antes que pˆ, y no conocemos el antecesor de pˆ,


podemos insertar qˆ después de pˆ y luego intercambiar las datos corres-
pondientes (“ swap ”):

q^.siguiente := p^.siguiente; p^.siguiente := q;


temp := p^.info; p^.info := q^.info; q^.info := temp;

Eliminar un elemento. Para sacar el nodo siguiente a pˆ, podemos hacer:

q := p^.siguiente; p^.siguiente := q^.siguiente;

o m
No podemos eliminar el nodo pˆ si no conocemos su antecesor (salvo que

.c
sea el primero de la lista). En este caso habrá que recorrer la lista hasta
encontrar pˆ, guardando información sobre los antecesores.

os
Recorrer la lista. Es bastante sencillo en nuestro caso (donde no hay nodos
r
fo
especiales para el principio o fin de lista):
.m

p := lista;
while (p <> nil) do begin
na

haceralgo; (* haceralgo con p o p^ *);


tu

p := p^.siguiente
end
on
yc

Problema 14.1. En el programa listas (pág. 192) se ven algunas posibilidades


para construir listas, tanto agregando nodos adelante como en el medio para
dm

formar una lista ordenada por id , y el recorrido de una lista, para buscar una
información o imprimir. Al final del listado del programa damos un ejemplo de
.a

salida (pág. 195).


w

a) Estudiar cuidadosamente los distintos tipos de listas y la salida.


w

b) Agregar un procedimiento o función para contar los elementos de una lista,


imprimiendo luego el número. ✄
w

Problema 14.2 (Listas recursivas). La definición de alguna forma recursiva


del tipo “ ptrnodo ” nos hace pensar que podemos usar recursión para varias de
las tareas.
a) Por ejemplo, para imprimir una lista podrı́amos usar:
procedure enorden (p: ptrnodo);
begin
if (p <> nil) then begin
with p^.info do writeln(nombre:tamanio, id:10);
enorden(p^.siguiente)
end
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

14.2. Listas encadenadas Pág. 155

haciendo la llamada a “ enorden(lista) ” (eventualmente imprimiendo un


cartel antes si la lista es vacı́a). Agregar este procedimiento para comprobar
su comportamiento.
b) Cambiar el procedimiento enorden por enordeninverso, en el que primero
se hace la llamada recursiva y luego se imprime info, y verificar su compor-
tamiento. ✄

Problema 14.3. Hacer un procedimiento para construir una copia de una lista
encadenada, es decir, una lista con tantos nodos como la original, y en la cual
la información se guarda en el mismo orden que la lista original.
Nota: Observar que si a y b son arreglos del mismo tipo (que se ha declarado
con “ type ”), para copiar a en b se puede hacer simplemente la asignación “ b
:= a ”, pero con listas encadenadas tenemos que recorrer todos los nodos. ✄

Problema 14.4. Hacer un procedimiento para “invertir” una lista de modo de

m
que el nodo que estaba en primer lugar esté al final en la nueva lista y viceversa,

o
etc., sin crear nuevos nodos (i.e. sin usar “ new ”).

.c
Sugerencia: ir formando una nueva lista en la que se incorpora el primer nodo

os
de la vieja, borrándolo de la vieja, y continuar hasta terminar la lista original.
Sugerencia si la anterior no alcanza: usar el esquema
r
fo
procedure darvuelta(var lista: ptrnodo);
.m
var p, q: ptrnodo;
begin
na

q := lista; lista := nil;


while (q <> nil) do begin
tu

p := q; q := q^.siguiente;
p^.siguiente := lista; lista := p
on

end
end;
yc

Nota: El problema 8.3 hacı́a lo mismo para un vector, pero la técnica es muy
dm

diferente. En cambio, el esquema propuesto usa la idea del procedimiento


“ enordeninverso ” del problema 14.2, agregando el nodo adelante en vez de

.a

imprimir.
w

Problema 14.5 (El problema de Flavio Josefo II). Implementar una solu-
w

ción a este problema (problema 6.5) usando en vez de arreglos una lista circular,
w

i.e. el “último” nodo se encadena al “primero”, donde cada nodo representa una
persona, y la eliminación se traduce en eliminar el nodo de la lista. El programa
debe imprimir la permutación de Flavio Josefo.
Sugerencia: crear una lista quedan de los que “quedan”, inicialmente con los va-
lores 1, . . . , n, donde el último nodo apunta al primero en vez de a nil . A medida
que se eliminan nodos de quedan, ir formando la permutación de Flavio Josefo.
En cada paso de “eliminación”, guardar información sobre el nodo anterior al
eliminado, para eliminar el nodo y poder contar m de los que quedan a partir
de él. ✄

Problema 14.6. Siguiendo las ideas del problema 14.4 y del procedimiento
ordenada en el programa listas (pág. 192), hacer un procedimiento para ordenar
(por alguna llave) una lista ya ingresada sin generar nuevos nodos. ✄

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 156 Punteros y listas encadenadas

Problema 14.7. Si una lista se modifica sólo agregando adelante o quitando


el primer elemento, tenemos la estructura de “pila” que hemos usado para re-
solver el problema de las torres de Hanoi (problema 11.6). Hacer un programa
resolviendo ese problema usando listas encadenadas en vez de arreglos. ✄

14.3. Otras estructuras dinámicas


En la construcción de listas, hemos colocado un puntero en los registros
(“records”) para apuntar al siguiente elemento en la lista terminando con nil
o volviendo a apuntar al principio de la lista para formar una lista “circular”.
También podemos pensar en agregar en cada registro un puntero para señalar
al elemento anterior (formando una lista doblemente encadenada), pero siempre
podemos visualizar estas listas como “lineales” o “unidimensionales”.
Podemos pensar, en cambio, en agregar punteros en los registros de modo

m
de señalar a otras listas, que pueden tener el mismo tipo de registros o no.
Si por ejemplo en el programa listas en vez de tener los nodos un campo

o
para punteros, tenemos dos, y llamamos al primero “izquierdo” y al segundo

.c
“derecho”, obtenemos otra implementación de los árboles binarios ordenados

os
que vimos en la sección 12.6, reemplazando la “pila” del arreglo por listas en-
cadenadas.
r
fo
Problema 14.8.
.m

a) Declarando
na

type
tu

ptrnodo = ^nodo;
nodo = record
on

llave: integer; (* numero leido *)


cuenta: integer; (* veces que aparecio *)
yc

izquierda: ptrnodo; (* hijo a la izquierda *)


dm

derecha: ptrnodo (* hijo a la derecha *)


end;
.a

rehacer el programa arbolbinario (pág. 182) reemplazando los arreglos por


listas encadenadas, y luego realizar los incisos del problema 12.6.
w
w

b) Ver que si los datos se ingresan ya ordenados, el árbol correspondiente se


reduce básicamente a un arreglo unidimensional.
w

Nota: Para eliminar este problema, se han diseñado una serie de variantes,
como árboles balanceados, árboles “B”, etc. Observar que sacar nodos de
un árbol es un proceso bastante más complicado que sacar nodos de una
lista (ver [6, Cap. 4]). ✄

Recordemos que en el problema 8.6 estudiamos un arreglo bidimensional


donde el primer ı́ndice indicaba el número de renglón, mientras que el segundo
ı́ndice indicaba el número de carácter dentro de ese renglón. Al tener la estruc-
tura de arreglo una dimensión fija, estamos desperdiciando mucho espacio si
queremos tener renglones de tamaños muy diferentes, o si no tenemos mucha
idea de cuántos renglones habrá en el texto completo. Es mucho más apropiado
tomar una lista encadenada, representando cada renglón, y donde sus nodos
apuntan no sólo al siguiente renglón sino también a una lista con el texto de ese

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

14.4. Problemas Adicionales Pág. 157

renglón. Los editores de texto trabajan con una estructura similar (escribiendo
luego secuencialmente en el disco cuando guardamos el archivo).
Un problema matemático donde se puede usar una idea similar es en grafos,
al tener la descripción por lista de aristas o por nodos adyacentes a un nodo
dado.
Problema 14.9. Como hemos visto en el capı́tulo anterior, un grafo G consiste
de un conjunto de nodos o vértices V = {1, 2, . . . , n}, y un conjunto de aristas
E, donde cada arista es de la forma {u, v} con u, v ∈ V , u 6= v (y supondremos
que no hay aristas repetidas).
Es usual representar los nodos mediante puntos y las aristas con segmentos
o curvas que unen los nodos, como se muestra en la figura 13.1 (pág. 130), en
donde n = 6 y E = {{1, 2}, {1, 3}, {2, 3}, {2, 6}, {3, 4}, {3, 6}, {4, 6}}.
Por otra parte, en vez de describir G mediante E (y V ), podrı́amos hacerlo
a partir de las listas de adyacencias o vecinos: para cada u ∈ V el conjunto de
nodos {v ∈ V : {u, v} ∈ E}. En el ejemplo anterior, podrı́amos poner:

o m
1 → {2, 3}, 2 → {1, 3, 6}, 3 → {1, 2, 4, 6}, 4 → {3, 6}, 5 → ∅, 6 → {2, 3, 4}.

.c
Para representar el grafo en la computadora usando listas encadenadas4,

os
podemos recurrir a las estructuras:
r
fo
Para lista de aristas: Una lista de aristas, donde cada arista se re-
presenta mediante
.m

ptrarista = ^arista;
na

arista = record u, v: integer; s: ptrarista end;


tu

Para lista de adyacencias: Una lista de vértices, donde cada vértice


tiene un puntero a una lista de vecinos:
on

ptrvecino = ^vecino;
yc

vecino = record id: integer; s: ptrvecino end;


dm

ptrvertice = ^vertice;
vertice = record
.a

id: integer;
w

s: ptrvertice;
vecinos: ptrvecino
w

end;
w

El programa grafos (pág. 195) imprime la listas de adyacencias si el grafo


es ingresado dando n y E. Estudiarlo y modificarlo de modo de realizar la
operación inversa, i.e. imprimir los aristas cuando el grafo se ingresa dando n y
las listas de adyacencias. ✄

14.4. Problemas Adicionales


Problema 14.10 (Ciclo de Euler). En el problema 13.7 nos hemos referido
a la existencia de ciclos de Euler, y nos preocuparemos ahora por encontrar
efectivamente uno.
4 En el capı́tulo 13 hemos considerado algunas representaciones con arreglos y matrices.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 158 Punteros y listas encadenadas

Para demostrar que un grafo conexo con todos los nodos de grado par tiene
un ciclo de Euler, se comienza a partir de un nodo y se van recorriendo aristas y
borrándolas hasta que volvamos al nodo, formando un ciclo. Esto debe suceder
porque todos los nodos tienen grado par. Puede ser que el ciclo no cubra a
todas las aristas, pero como el grafo es conexo, debe haber un vértice en el ciclo
construido que tenga una arista (aún no eliminada) incidente en él, a partir del
cual podemos formar un nuevo ciclo, agregarlo al anterior y continuar con el
procedimiento hasta haber eliminado recorrido todas las aristas.
Esta demostración es bien constructiva, y podemos implementar los ciclos
como listas encadenadas. La implementación de aristas, etc. puede hacerse con
arreglos o matrices.
Hacer un programa para decidir si un grafo entrado por n, la cantidad de
vértices, y en caso de ser conexo y con todos los vértices de grado par, imprimir
un ciclo de Euler. ✄

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Apéndice A

Programas mencionados

m
Problema 2.2: holamundo (pág. 7)

o
program holamundo(input, output);

.c
(* primer programa *)

os
begin
r
fo
writeln(’Hola Mundo!’);
writeln;
.m

writeln(’y Chau!’)
na

end.
tu

Problema 3.2: sumardos (pág. 11)


on

program sumardos(input, output);


yc

(* sumar dos numeros enteros *)


dm

var a, b: integer;
.a

begin
writeln(’** Programa para sumar dos numeros enteros’);
w

writeln;
w

a := 1;
w

b := 2;
write(’La suma de ’, a);
write(’ y ’, b);
writeln(’ es ’, a + b);
writeln; writeln(’** Fin **’)
end.

Problema 3.3: leerdato (pág. 12)


program leerdato(input, output);
(* Leer un entero entrado por terminal *)

var a: integer;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 160 Programas mencionados

begin
writeln(’** Programa para leer un dato entero’);
writeln;
write(’Entrar un entero: ’); readln(a);
writeln;
writeln(’El entero leido es ’, a);
writeln; writeln(’** Fin **’)
end.

Problema 3.4: raiz (pág. 13)

program raiz(input, output);


(*

m
Obtener la raiz cuadrada de un numero real x.

o
Se supone que x no es negativo.

.c
*)

os
var x, y: real;

r
fo
begin
.m
writeln(’** Calcular la raiz cuadrada de x **’);
writeln;
na

write(’Entrar x (> 0): ’); readln(x);


y := sqrt(x);
tu

writeln;
on

writeln(’La raiz cuadrada de ’, x, ’ es ’, y);


writeln; writeln(’** Fin **’)
yc

end.
dm

Problema 3.7: enteroareal (pág. 15)


.a

program enteroareal(input, output);


w

(* Asignar un entero a un real *)


w
w

var
a: integer;
x: real;

begin
writeln(’** Asignar un entero a un real’);
writeln;
write(’Entrar el valor del entero: ’); readln(a);
x := a;
writeln;
writeln( a, ’ cambiado a real es: ’, x);
writeln; writeln(’** Fin **’)
end.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

segundos (Problema 3.13) Pág. 161

Problema 3.13: segundos (pág. 16)


program segundos(input, output);
(* Pasar de segundos a horas, minutos y segundos *)

var hs, mins, segs: integer;

begin
write(’** Programa para pasar de segundos’);
writeln(’ a horas, minutos y segundos **’);
writeln;
write(’Entrar la cantidad de segundos: ’); readln(segs);
writeln;
writeln(segs, ’ segundos son equivalentes a ’);
mins := segs div 60; segs := segs mod 60;

m
hs := mins div 60; mins := mins mod 60;
writeln(hs, ’ hs, ’, mins, ’ mins, ’, segs, ’ segs.’);

o
writeln; writeln(’** Fin **’)

.c
end.

r os
Problema 3.16: positivo (pág. 17)
fo
.m
program positivo(input, output);
(* dice si un numero entero es positivo *)
na

var a: integer;
tu
on

begin
writeln(’Ver si un numero entero es positivo’);
yc

writeln;
write(’Entrar un numero: ’); readln(a);
dm

writeln;
writeln(’es positivo?: ’, a > 0);
.a

writeln; writeln(’** Fin **’)


end.
w
w

Problema 3.18: caracteres1 (pág. 19)


w

program caracteres1(input, output);


(* pasar de caracter a numero y viceversa *)

var c: char; i: integer;

begin
writeln(’ ** Pasar de caracter a numero y viceversa **’);
write(’ Entrar un caracter: ’); readln(c);
writeln;
i := ord(c);
writeln(’ El numero de orden de ’, c, ’ es ’, i:1);
(* i:1 indica que escribira i en un solo espacio,

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 162 Programas mencionados

o los que sean necesarios *)


write(’ Verificacion:’);
write(’ el caracter correspondiente a ’, i:1);
writeln(’ es ’, chr(i));
writeln; writeln(’** Fin **’)
end.

Problema 4.1: valorabsoluto (pág. 22)


program valorabsoluto(input, output);
(* encuentra el valor absoluto del numero real x *)

var x, y: real;

begin

m
writeln(’** Encontrar el valor absoluto de un numero real’);

o
writeln;

.c
write(’Entrar el numero: ’); readln(x);

os
if (x >= 0) then y := x else y := -x;
writeln;
writeln(’El valor absoluto de ’, x, ’ es ’, y);
r
fo
writeln; writeln(’** Fin **’)
.m
end.
na

Problema 4.2: comparar (pág. 22)


tu

program comparar(input, output);


on

(* decidir cual numero entero es mayor o si son iguales *)


yc

var a, b: integer;
dm

begin
.a

writeln(’** Comparar dos numeros enteros’);


writeln;
w

write(’Entrar un numero entero: ’); readln(a);


w

write(’Entrar otro numero entero: ’); readln(b);


w

writeln;
if (a > b) then
writeln(a, ’ es mayor que ’, b)
else if (a < b) then
writeln(b, ’ es mayor que ’, a)
else
writeln(a, ’ es igual a ’, b);
writeln; writeln(’** Fin **’)
end.

Problema 4.3: caracteres2 (pág. 22)


program caracteres2(input, output);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

resto (Problema 4.9) Pág. 163

(* decidir si un caracter es una letra mayuscula o


minuscula, o no es letra, comparando caracteres *)

var c: char;

begin
write(’** Decide si un caracter es una letra ’);
writeln(’mayuscula o minuscula o no es letra **’);
write(’ Entrar un caracter: ’); readln(c); writeln;
write(c);
if (’a’ <= c) and (c <= ’z’) then
writeln(’ es una letra minuscula’)
else if (’A’ <= c) and (c <= ’Z’) then
writeln(’ es una letra mayuscula’)
else

m
writeln(’ no es una letra’);

o
writeln; writeln(’** Fin **’)

.c
end.

os
Problema 4.9: resto (pág. 26)
r
fo
program resto(input, output);
.m

(* Hallar el resto de la division entre dos numeros


enteros positivos mediante restas sucesivas. *)
na
tu

var a, b, r: integer;
on

begin
writeln(’** Hallar el resto de la division entre a y b,’);
yc

writeln(’(enteros positivos) mediante restas sucesivas **’);


dm

writeln;

write(’Entrar el valor de a: ’); readln(a);


.a

write(’Entrar el valor de b: ’); readln(b);


w
w

r := a;
w

while (r >= b) do r := r - b;

writeln;
write(’El resto de dividir ’, a:1);
writeln(’ por ’, b:1, ’ es ’, r:1);

writeln; writeln(’** Fin **’)


end.

Problema 4.10: tablaseno (pág. 27)


program tablaseno(input, output);
(* hacer una tabla del seno donde los angulos

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 164 Programas mencionados

estan dados en grados *)

const pi = 3.14159265;

var
inicial, final, incremento, grados: integer;
radianes: real;

begin
writeln(’Hacer una tabla del seno dando valores’);
writeln(’inicial, final, y del incremento (en grados).’);
writeln;

write(’Entrar el valor inicial (en grados): ’);


readln(inicial);

m
write(’Entrar el valor final (en grados): ’);

o
readln(final);

.c
write(’Entrar el valor de incremento (en grados): ’);
readln(incremento);

os
writeln;

r
fo
writeln(’Angulo Seno’);
.m
grados := inicial;
while (grados <= final) do begin
na

radianes := grados * pi/180;


writeln(grados:5, sin(radianes):15:5);
tu

grados := grados + incremento


on

end;
yc

writeln; writeln(’** Fin **’)


end.
dm

Problema 4.11: gauss (pág. 28)


.a

program gauss(input, output);


w

(* sumar 1 + 2 +...+ n *)
w
w

var
n: integer;
suma: real; (* ponemos real por si es muy grande *)

begin
writeln(’** Sumar 1 + 2 +...+ n **’);
writeln;
write(’ Entrar n (maxint = ’, maxint, ’): ’); readln(n);

suma := 0;

(* lazo while, al reves *)


while (n > 0) do begin suma := suma + n; n := n-1 end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

cifras (Problema 4.13) Pág. 165

writeln;
writeln(’ suma con while al reves: ’, suma:10:0);
(* 10:0 indica que el numero se escribe en
al menos 10 espacios, sin decimales *)

writeln; writeln(’** Fin **’)


end.

Problema 4.13: cifras (pág. 29)


program cifras(input, output);
(* determinar la cantidad de cifras significativas
(en base 10, ignorando el signo) de un entero
entrado por terminal.

m
E.g. 246 -> 3, -908 -> 3, pero 0 -> 1 *)

o
.c
var a, b, c: integer;

os
begin
r
fo
writeln(’Determina la cantidad de cifras de un entero’);
writeln(’(sin tener en cuenta el signo)’);
.m

writeln;
na

write(’Entrar el numero: ’); readln(a); writeln;


tu

if (a < 0) then b := -a else b := a;


on

c := 0;
yc

repeat
dm

c := c + 1;
b := b div 10
until b = 0;
.a
w

writeln(’La cantidad de cifras de ’, a:1, ’ es ’, c:1);


w
w

writeln; writeln(’** Fin **’)


end.

Problema 4.14: epsmin (pág. 29)


program epsmin(input, output);
(*
epsmin = menor numero real mayor que 0.
epsmaq = menor numero real que sumado a 1 da mayor que 1.
*)

var eps, x: real;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 166 Programas mencionados

begin
writeln(’** Determinacion de epsmin y epsmaq **’); writeln;

x := 1;
repeat eps := x; x := x / 2 until (x = 0);
writeln(’epsmin es: ’, eps);

eps := 1;
while (1 + eps > 1) do eps := eps/2;
eps := 2 * eps;
writeln(’epsmaq es: ’, eps);

writeln; writeln(’** Fin **’)


end.

m
Problema 4.15: potencia (pág. 30)

o
.c
program potencia(input, output);

os
(* calculo de potencias con exponente natural *)

r
var
fo
x, pot: real;
.m

n, i: integer;
na

begin
tu

writeln(’** Calculo de la potencia x a la n’);


writeln;
on

write(’Entrar x (real): ’); readln(x);


yc

write(’Entrar n (entero positivo): ’); readln(n);


dm

pot := 1;
for i := 1 to n do pot := pot * x;
.a
w

writeln;
w

writeln(x, ’ a la ’, n:1, ’ es ’, pot);


w

writeln; writeln(’** Fin **’)


end.

Problema 4.18: eolnprueba (pág. 32)


program eolnprueba(input, output);
(* prueba del funcionamiento de eoln *)

var a: integer;

begin
writeln(’** Prueba del funcionamiento de eoln’);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

sumardatos (Problema 4.19) Pág. 167

writeln;

a := 1; writeln(’El valor de a es ’, a:1);


if (eoln) then a := 2 else a := 3;
writeln(’Ahora el valor de a es ’, a:1);

writeln; writeln(’** Fin **’)


end.

Problema 4.19: sumardatos (pág. 33)


program sumardatos(input, output);
(* Calcular la suma de datos entrados por terminal, donde
el fin de datos se indica con <retorno> sin datos *)

m
var

o
s, x: real;

.c
findatos: boolean;

os
begin
r
fo
writeln(’** Calcular la suma de los datos entrados’);
writeln(’indicando el fin de datos con doble <retorno>’);
.m

writeln;
na

s := 0; findatos := false; (* inicializacion *)


tu

repeat
write(’Entrar un dato (fin = <retorno>): ’);
on

if (eoln) then findatos := true


else begin readln(x); s := s + x end
yc

until (findatos);
dm

writeln;
writeln(’La suma de los datos es ’, s);
.a
w

writeln; writeln(’** Fin **’)


w

end.
w

Problema 5.9: babilonico (pág. 41)


program babilonico(input, output);
(*
Metodo babilonico o de Newton para aproximar
la raiz cuadrada de un real positivo
*)

const
itmax = 10;
tol = 10e-5;
x0 = 1.0;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 168 Programas mencionados

var
it: integer;
a, x, y: real;

begin
(* carteles *)
writeln(’** Metodo babilonico o de Newton para’);
writeln(’ aproximar la raiz cuadrada de un numero’);
writeln(’ real positivo "a" **’); writeln;

(* datos de entrada *)
write(’ Entrar a: ’); readln(a); writeln;

(* inicializacion *)

m
it := 1; x := x0; y := (x + a/x)/2;

o
.c
(* lazo principal *)
while ((it <= itmax) and (abs(x - y) > tol)) do

os
begin x := y; y := (x + a/x)/2; it := it + 1 end;

r
fo
(* salida *)
.m
writeln(’Iteraciones realizadas: ’, it);
if (it > itmax) then
na

writeln(’** Iteraciones maximas excedidas **’);


writeln(’Solucion aproximada obtenida: ’, y:20:10);
tu
on

(* fin *)
writeln; writeln(’** Fin **’)
yc

end.
dm

Problema 5.13: euclides (pág. 44)


.a

program euclides(input, output);


(*
w

Algoritmo de Euclides, usando restos


w

en vez de restas sucesivas


w

*)

var a, b, r: integer;

begin
(* cartel de info *)
writeln(’** Algoritmo de Euclides para encontrar el ’);
write(’ maximo comun divisor entre dos enteros, ’);
writeln(’mcd(a,b) **’); writeln;
writeln(’Nota: mcd(0,0) definido como 0’); writeln;

(* datos de entrada *)
write(’ Entrar a: ’); readln(a);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

factoresprimos (Problema 5.18) Pág. 169

write(’ Entrar b: ’); readln(b); writeln;

(* inicializacion: trabajar con no negativos *)


if (a < 0) then a := -a;
if (b < 0) then b := -b;

(* lazo principal *)
while (b <> 0) do begin r := a mod b; a := b; b := r end;

(* salida *)
writeln(’El maximo comun divisor es: ’, a:2);

(* fin *)
writeln; writeln(’** Fin **’)
end.

o m
Problema 5.18: factoresprimos (pág. 46)

.c
os
program factoresprimos(input, output);

r
(* Encontrar factores primos de un entero positivo *)
fo
.m
var
a, b, s, t: integer;
na

tesprimo: boolean;
tu

begin
on

(* carteles *)
writeln(’Encontrar los factores primos del entero a > 1’);
yc

writeln;
dm

(* datos de entrada *)
write(’Entrar a (a > 1): ’); readln(a); writeln;
.a

(* inicializacion, lazo e impresion *)


w

t := a; tesprimo := false; b := 2;
w

repeat
w

s := trunc(sqrt(t));
while (((t mod b) <> 0) and (b <= s)) do b := b+1;
if (b > s) then tesprimo := true
else begin (* b es factor de t y de a *)
write(b:1, ’, ’);
t := t div b
end;
until tesprimo;
writeln(t:1);

(* fin *)
writeln; writeln(’** Fin **’)
end.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 170 Programas mencionados

Problema 6.1: unidades (pág. 49)


program unidades(input, output);
(*
contar cuantos de los numeros ingresados
terminan en 0,1,...,9
*)

var
dato, digito: integer;
terminaen: array[0..9] of integer;

begin
writeln(’** Contar cuantos de los numeros ingresados’);
writeln(’ terminan en 0,1,...,9’);

m
writeln;

o
(* inicializar el arreglo *)

.c
for digito := 0 to 9 do terminaen[digito] := 0;

os
(* entrar datos *)
r
fo
write(’Entrar un entero (fin = <retorno>): ’);
while (not eoln) do begin
.m

readln(dato);
na

digito := abs(dato) mod 10;


terminaen[digito] := terminaen[digito] + 1;
tu

write(’Entrar un entero (fin = <retorno>): ’)


end;
on

readln;
yc

(* salida *)
dm

writeln;
writeln(’ Digito numero de datos’);
.a

for digito := 0 to 9 do
writeln( digito:4, terminaen[digito]:20);
w
w

writeln; writeln(’** Fin **’)


w

end.

Problema 6.2: busquedalineal (pág. 50)


program busquedalineal(input, output);
(*
Dado un arreglo a1, a2,... y un elemento x, ver
si x = ai para algun i.
*)

const MAXN = 20;

var

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

busquedalineal (Problema 6.2) Pág. 171

i, ndatos, x: integer;
findatos, seencontro: boolean;
a: array[1..MAXN] of integer;

begin
(* carteles *)
writeln(’** Dado un arreglo a1, a2,...’);
writeln(’ ver si x = ai para algun i **’);
writeln;

(* leer el arreglo *)
write(’Entrar enteros, senialando fin’);
writeln(’ con <retorno> sin datos.’);
ndatos := 1; findatos := false;
repeat

m
write(’Entrar el dato ’, ndatos:1);

o
write(’ (fin = <retorno>): ’);

.c
if (eoln) then begin (* no hay mas datos *)
findatos := true; readln end

os
else begin (* nuevo dato *)

r
readln(a[ndatos]); (* incorporarlo al arreglo *)
fo
ndatos := ndatos + 1 (* para el proximo *)
.m
end
until (findatos);
na

ndatos := ndatos - 1;
tu

(* imprimir el arreglo *)
on

writeln(’El arreglo es: ’); writeln;


for i := 1 to ndatos do begin
yc

write(a[i]:10);
if ((i mod 5) = 0) then writeln
dm

end;
if ((ndatos mod 5) <> 0) then writeln;
.a

writeln;
w

(* elemento a buscar *)
w

write(’Entrar el entero x a buscar: ’); readln(x);


w

writeln;

(* lazo principal *)
i := 0;
repeat
i := i + 1;
seencontro := (a[i] = x)
until (seencontro or (i = ndatos));

(* resultados y fin *)
if (seencontro)
then writeln(’Se encontro en la posicion ’, i:1)
else

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 172 Programas mencionados

writeln(’No se encontro’);

writeln; writeln(’** Fin **’)


end.

Problema 6.3: eratostenes (pág. 52)


program eratostenes(input, output);
(* Criba de Eratostenes para encontrar primos *)

const MAXP = 10000; (* maximo primo *)

var
p, i, cuenta, k: integer;
esprimo: array[2..MAXP] of boolean;

o m
begin

.c
writeln(’** Criba de Eratostenes para encontrar’);

os
writeln(’ los primos entre 1 y ’, MAXP:1);
writeln;
r
fo
(* inicializacion *)
.m

for i := 2 to MAXP do esprimo[i] := true;


cuenta := 0;
na
tu

(* lazo principal *)
for i := 2 to MAXP do
on

if (esprimo[i]) then begin (* nuevo primo *)


p := i; cuenta := cuenta + 1;
yc

(* ahora eliminar multiplos de p *)


dm

k := p + p;
while (k <= MAXP) do begin
esprimo[k] := false;
.a

k := k + p
w

end
w

end;
w

(* salida y fin *)
write(’ Se encontraron ’, cuenta:1);
writeln(’ primos que no superan ’, MAXP:2);
writeln; writeln(’** Fin **’)
end.

Problema 6.5: flaviojosefo1 (pág. 54)


program flaviojosefo1(input, output);
(* Problema de Flavio Josefo con arreglos *)

const MAXN = 100;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

potencias2 (Problema 7.1) Pág. 173

var
m, n, i, vuelta, cuenta: integer;
vive: array[1..MAXN] of boolean;
flavio: array[1..MAXN] of integer;

begin
(* carteles *)
writeln(’** Problema de Flavio Josefo **’); writeln;
writeln(’En un circulo de n, se van eliminando de a m’);
writeln;

(* datos *)
write(’Entrar n: ’); readln(n);
write(’Entrar m: ’); readln(m); writeln;

o m
(* inicializacion *)

.c
for i := 1 to n do vive[i] := true; (* aun vive *)
i := 0;

r os
(* parte principal *)
fo
for vuelta := 1 to n-1 do begin
.m
(* en cada vuelta, i es el ultimo eliminado *)
cuenta := 0; (* contamos m sobrevivientes desde i *)
na

repeat
if (i <> n) then i := i + 1 else i := 1;
tu

if (vive[i]) then cuenta := cuenta + 1


on

until (cuenta = m);


vive[i] := false; (* lo eliminamos *)
yc

flavio[vuelta] := i
end;
dm

(* encontrar el sobreviviente *)
.a

i := 0; repeat i := i + 1 until vive[i];


flavio[n] := i;
w
w

(* salida y fin *)
w

writeln(’ La permutacion de Flavio Josefo es’); writeln;


for i := 1 to n do begin
write(flavio[i]:5);
if ((i mod 10) = 0) then writeln
end;
if ((n mod 10) <> 0) then writeln;
writeln; writeln(’** Fin **’)
end.

Problema 7.1: potencias2 (pág. 60)


program potencias2(input, output);
(* Comparar x a la n con y a la m. *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 174 Programas mencionados

var
n, m, k: integer;
x, y, xn, ym: real;

begin
(* carteles iniciales *)
writeln(’** Comparar x a la n con y a la m’);
writeln;

(* entrar datos *)
write(’Entrar x (real): ’); readln(x);
write(’Entrar n (entero): ’); readln(n);
write(’Entrar y (real): ’); readln(y);
write(’Entrar m (entero): ’); readln(m);

o m
(* calcular x a la n, y a la m *)

.c
xn := 1; for k := 1 to n do xn := xn * x;
ym := 1; for k := 1 to m do ym := ym * y;

r os
(* comparar e imprimir *)
fo
write(x:10:5, ’ a la ’, n:1);
.m
if (xn < ym) then write(’ es menor que ’)
else if (xn > ym) then write(’ es mayor que ’)
na

else write(’ es igual a ’);


writeln(y:10:5, ’ a la ’, m:1);
tu
on

(* cartel final *)
writeln; writeln(’** Fin **’)
yc

end.
dm

Problema 7.2: potencias3 (pág. 61)


.a

program potencias3(input, output);


(*
w

Primer ejemplo de uso de funciones,


w

definiendo potencia(x,n) para comparar


w

x a la n con y a la m.
*)

var
n, m: integer;
x, y, xn, ym: real;

function potencia(a: real; b: integer): real;


var k: integer; z: real;
begin
z := 1;
for k := 1 to b do z := z * a;
potencia := z

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

biseccion (Problema 7.3) Pág. 175

end;

begin
(* carteles iniciales *)
writeln(’** Comparar x a la n con y a la m’);
writeln;

(* entrar datos *)
write(’Entrar x (real): ’); readln(x);
write(’Entrar n (entero): ’); readln(n);
write(’Entrar y (real): ’); readln(y);
write(’Entrar m (entero): ’); readln(m);

(* calcular x a la n, y a la m *)
xn := potencia(x,n); ym := potencia(y,m);

o m
(* comparar e imprimir *)

.c
write(x:10:5, ’ a la ’, n:1);
if (xn < ym) then write(’ es menor que ’)

os
else if (xn > ym) then write(’ es mayor que ’)
else write(’ es igual a ’);
r
fo
writeln(y:10:5, ’ a la ’, m:1);
.m

(* cartel final *)
na

writeln; writeln(’** Fin **’)


end.
tu
on

Problema 7.3: biseccion (pág. 63)


yc

program biseccion(input, output);


(*
dm

Metodo de biseccion para encontrar raices de funciones


reales y continuas.
.a

Cambiar la funcion cuando se investigue otra ecuacion.


w
w

Version simplificada que se va mejorando


w

en los trabajos practicos.


*)

const
dify = 1.0e-6; (* tolerancia en y *)
maxiter = 30; (* maximo numero de iteraciones *)

var
iter: integer;
poco, mucho, x, (* valores en x *)
ypoco, ymucho, y (* correspondientes valores en y *)
: real;
seguir (* indica si seguir iterando *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 176 Programas mencionados

: boolean;

(* funcion para la que se quiere encontrar una raiz *)


function f(x: real): real;
begin
f := x * (x + 1) * (x + 2) * (x - 4/3)
end;

begin
(* carteles *)
writeln(’* Metodo de biseccion para encontrar raices *’);
writeln;
write(’Implementacion para ’);
writeln(’f(x) = x (x + 1) (x + 2) (x - 4/3)’);
writeln;

o m
(* datos de entrada *)

.c
write(’ Entrar cota inferior para x: ’); readln(poco);
write(’ Entrar cota superior para x: ’); readln(mucho);

os
writeln;

r
fo
(* inicializacion *)
.m
ypoco := f(poco); ymucho := f(mucho);
seguir := (ypoco * ymucho < 0);
na

iter := 0;
tu

(* lazo principal *)
on

while (seguir) do begin


iter := iter + 1;
yc

x := (poco + mucho) / 2;
y := f(x);
dm

if (abs(y) < dify) then seguir := false


else if (iter = maxiter) then seguir := false
.a

else if (y * ypoco < 0) then mucho := x


else begin poco := x; ypoco := y end
w

end;
w
w

(* salida *)
if (iter > 0) then (* hay una solucion, aceptable o no *)
begin
writeln(’ Solucion obtenida: ’, x);
writeln(’ Valor de f: ’, y);
writeln(’ Iteraciones realizadas: ’, iter:1);
if (abs(y) > dify) then begin
writeln;
write(’* La respuesta puede no estar ’);
writeln(’cerca de una raiz *’)
end
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

tablaseno2 (Problema 7.6) Pág. 177

(* fin *)
writeln; writeln(’** Fin **’)
end.

Problema 7.6: tablaseno2 (pág. 67)


program tablaseno2(input, output);
(* hacer una tabla del seno donde los angulos
estan dados en grados *)

const pi = 3.14159265;

var inicial, final, incremento: integer;

procedure cartelesiniciales;

m
begin

o
writeln(’Hacer una tabla del seno dando valores’);

.c
writeln(’inicial, final, y del incremento (en grados).’);
writeln

os
end;

r
fo
procedure leerdatos;
.m
begin
write(’Entrar el valor inicial (en grados): ’);
na

readln(inicial);
write(’Entrar el valor final (en grados): ’);
tu

readln(final);
on

write(’Entrar el valor de incremento (en grados): ’);


readln(incremento);
yc

writeln
end;
dm

procedure imprimirtabla;
.a

var
grados: integer;
w

radianes: real;
w

begin
w

writeln(’Angulo Seno’);
grados := inicial;
while (grados <= final) do begin
radianes := grados * pi/180;
writeln(grados:5, sin(radianes):15:5);
grados := grados + incremento
end
end;

procedure cartelfinal;
begin writeln; writeln(’** Fin **’) end;

begin

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 178 Programas mencionados

cartelesiniciales;
leerdatos;
imprimirtabla;
cartelfinal
end.

Problema 7.7: swap (pág. 69)


program swap(input, output);
(* prueba de swap *)

var a, b: integer;

procedure swapincorrecto(x, y: integer);


(* x, y pasados por valor *)

m
var t: integer;

o
begin t := x; x := y; y := t end;

.c
os
begin
a := 5; b := 2;
r
fo
writeln(’Inicialmente: a = ’, a:2, ’, b = ’, b:2);
swapincorrecto(a,b);
.m

writeln(’Despues de swap: a = ’, a:2, ’, b = ’, b:2);


writeln; writeln(’** Fin **’)
na

end.
tu
on

Problema 8.2: maximo (pág. 75)


yc

program maximo(input, output);


(* encontrar el maximo en un renglon entrado por terminal *)
dm

const MAXC = 255; (* maximo tamanio de arreglo *)


.a

type tiporenglon = array[1..MAXC] of char;


w
w

var
w

p, (* posicion en el renglon *)
n (* numero de caracteres en el renglon *)
: integer;
t (* el caracter de maximo ordinal *)
: char;
renglon (* el renglon a procesar *)
: tiporenglon;

procedure leerrenglon( var s: tiporenglon; var n: integer);


(* aqui suponemos renglon: array[1..MAXC] of char *)
begin
write(’Escribir algo ’);
writeln(’(no mas de ’, MAXC:1, ’ caracteres.):’);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

maximo (Problema 8.2) Pág. 179

n := 0;
while ((not eoln) and (n < MAXC)) do begin
n := n + 1; read(s[n]) (* aca es read y no readln *)
end;
readln (* para leer el ultimo fin de linea *)
end;

procedure escribirrenglon( s: tiporenglon; n: integer);


var i: integer;
begin
for i := 1 to n do write(s[i]);
writeln
end;

procedure max(

m
var t: tiporenglon;

o
long: integer;

.c
var tmax: char;
var indice: integer);

os
var i: integer;
begin
r
(* suponemos long positivo *)
fo
tmax := t[1]; indice := 1;
.m
for i := 2 to long do
if (t[i] > tmax) then begin
na

tmax := t[i]; indice := i end


end; (* fin de procedure max *)
tu
on

begin
(* titulo *)
yc

write(’** Programa para encontrar el maximo en un ’);


write(’arreglo de caracteres entrado por terminal’);
dm

writeln;
.a

(* entrada *)
leerrenglon(renglon, n);
w
w

(* verificacion *)
w

writeln; writeln(’El renglon entrado es: ’);


writeln;
escribirrenglon(renglon, n);

(* procesamiento *)
max(renglon, n, t, p);

(* salida y fin *)
writeln; write(’El maximo es "’, t, ’",’);
writeln(’ alcanzado en la posicion ’, p:1);

writeln; writeln(’** Fin **’)


end.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 180 Programas mencionados

Problema 8.8: deconsolaaarchivo (pág. 80)


program deconsolaaarchivo(input, output);
(*
Copiar de consola a archivo

En la entrada no deben haber renglones "vacios"


*)

var
archivo: text;
c: char;
nombre: string;

begin

m
(* carteles *)
writeln(’** Copiar de consola a archivo **’);

o
writeln;

.c
os
(* nombre de archivo a escribir *)
writeln(’ Entrar el nombre del archivo a escribir’);
r
fo
writeln(’ Atencion: si ya existe se borra!’);
write(’ Archivo: ’); readln(nombre);
.m

rewrite(archivo, nombre);
na

(* copiar de consola a archivo *)


tu

writeln;
write(’Entrar datos en ’, nombre);
on

writeln(’ (Fin con retorno "vacio")’);


yc

while (not eoln) do begin


repeat read(c); write(archivo, c)
dm

until eoln;
readln; writeln(archivo)
.a

end;
w

close(archivo);
w
w

(* Fin *)
writeln; writeln(’** Fin **’)
end.

Problema 8.8: dearchivoaconsola (pág. 80)


program dearchivoaconsola(input, output);
(* Copiar de archivo a consola *)

var
archivo: text;
c: char;
nombre: string;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

dado (Problema 9.1) Pág. 181

begin
(* carteles *)
writeln(’** Copiar de archivo a consola **’);
writeln;

(* nombre de archivo a leer *)


write(’ Entrar el nombre del archivo a leer: ’);
readln(nombre);
reset(archivo, nombre);
writeln;

(* copiar datos en pantalla *)


writeln(’Estos son los datos en ’, nombre, ’:’);
writeln;

m
while not eof(archivo) do

o
if (eoln(archivo)) then begin

.c
readln(archivo); writeln end
else begin

os
read(archivo, c); write(c) end;

r
fo
close(archivo);
.m

(* Fin *)
na

writeln; writeln(’** Fin **’)


end.
tu
on

Problema 9.1: dado (pág. 86)


yc

program dado(input, output);


dm

(*
Simular tirar un dado.
.a

Usa la sentencia "random" de Turbo Pascal.


w

*)
w
w

var d: integer;

begin
writeln(’** Simular tirar un dado **’);
writeln;

randomize;
d := 1 + random(6);
writeln(’El dado que salio es ’, d:1);

writeln;
writeln(’** <retorno> para Fin **’); readln
end.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 182 Programas mencionados

Problema 9.2: dados (pág. 86)


program dados(input, output);
(*
Contar las veces que se tira un dado
hasta que aparece un numero prefijado.

Usa la sentencia "random" de Turbo Pascal.


*)

var numero, tiros: integer;

begin
writeln(’** Contar las veces que se tira un dado’);
writeln(’ hasta que aparece un numero prefijado **’);

m
writeln;

o
write(’Entrar el valor que debe aparecer ’);

.c
write(’(entre 1 y 6): ’);

os
readln(numero);

r
fo
randomize;
tiros := 0;
.m

repeat tiros := tiros + 1


na

until (1 + random(6) = numero);


tu

writeln;
write(’El numero ’, numero:1);
on

writeln(’ tardo ’, tiros, ’ tiros hasta aparecer.’);


yc

writeln;
dm

writeln(’** <retorno> para Fin **’); readln


end.
.a

Problema 12.6: arbolbinario (pág. 125)


w
w

program arbolbinario(input, output);


w

(*
Arbol binario parcialmente ordenado guardando los
nodos en un arreglo. Variante de las versiones con
punteros de Wirth87, p. 210, y K&R, p. 153.

Aplicacion al problema de hacer un listado


ordenado de los numeros entrados por terminal,
contando las apariciones.
*)

const
MAXN = 100; (* maximo numero de datos a guardar *)
nada = 0; (* para cuando no hay hijos *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

arbolbinario (Problema 12.6) Pág. 183

type
indice = integer; (* para senialar los arreglos *)
tipodato = integer; (* el tipo de datos a ingresar *)
nodo = record
llave: tipodato; (* dato *)
cuenta: integer; (* veces que aparecio *)
izquierda: indice; (* hijo a la izquierda *)
derecha: indice (* hijo a la derecha *)
end;
tipoarbol = array[0..MAXN] of nodo;

var
narbol: integer; (* numero de nodos en el arbol *)
arbol: tipoarbol; (* el arbol! *)

m
raiz: indice; (* donde empieza el arbol *)

o
dato: tipodato; (* dato leido *)

.c
function entrardatos(var n: tipodato): boolean;

os
begin

r
write(’Entrar el numero (fin = <retorno>): ’);
fo
if (eoln) then
.m
begin entrardatos := false; readln end
else
na

begin entrardatos := true; readln(n) end


end;
tu
on

procedure binario(x: integer; var p: indice);


var mayormenor: integer;
yc

begin
if (p = nada) then begin (* nueva entrada *)
dm

narbol := narbol + 1;
p := narbol;
.a

with arbol[p] do begin


llave := x; cuenta := 1;
w

izquierda := nada; derecha := nada


w

end
w

end
else begin
mayormenor := x - arbol[p].llave;
if (mayormenor = 0) then (* dato existente *)
arbol[p].cuenta := arbol[p].cuenta + 1
else if (mayormenor < 0) then (* ir a la izquierda *)
binario(x, arbol[p].izquierda)
else (* ir a la derecha *)
binario(x, arbol[p].derecha)
end
end;

procedure enorden(w: integer);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 184 Programas mencionados

begin
if (w <> nada) then
with arbol[w] do begin
enorden(izquierda);
writeln(llave:10, cuenta:20);
enorden(derecha)
end
end;

begin
(* carteles *)
writeln(’** Arbol binario **’); writeln;
write(’Entrar numeros enteros y contar’);
writeln(’ las veces que aparecieron’);
writeln;

o m
(* inicializacion *)

.c
narbol := 0;
raiz := nada;

r os
(* lectura de datos y construccion del arbol *)
fo
while (entrardatos(dato)) do binario(dato, raiz);
.m

(* salida y fin *)
na

writeln; writeln(’Impresion en orden:’);


writeln;
tu

writeln(’ Numero Cantidad de Apariciones’);


on

enorden(raiz);
yc

writeln; writeln(’** Fin **’)


end.
dm

Ejemplo de Salida
.a

** Arbol binario **
w
w

Entrar numeros enteros y contar las veces que aparecieron


w

Entrar el numero (fin = <retorno>): 3


Entrar el numero (fin = <retorno>): 2
Entrar el numero (fin = <retorno>): 1
Entrar el numero (fin = <retorno>): 1
Entrar el numero (fin = <retorno>): 2
Entrar el numero (fin = <retorno>):

Impresion en orden:

Numero Cantidad de Apariciones


1 2
2 2

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

anchoprimero (Problema 13.4) Pág. 185

3 1

** Fin **

Problema 13.4: anchoprimero (pág. 135)


program anchoprimero(input, output);
(*
Recorrer un grafo a lo ancho para
determinar un arbol de expansion.

Entrada: un grafo dado por lista de aristas y el


numero de vertices, n.
Los vertices son todos los enteros positivos
entre 1 y n. Si alguno no aparece en ninguna

m
arista, es un vertice aislado.

o
.c
Salida: las aristas del arbol y el orden en que
fueron visitados los nodos.

os
Si el arbol no es conexo, se imprime un mensaje.

r
fo
Implementacion con una cola fifo de nodos a visitar.
.m
*)
na

const
MAXN = 20; (* maximo numero de nodos *)
tu

MAXM = 100; (* maximo numero de aristas *)


on

type
yc

arreglodevertices = array[1..MAXN] of integer;


tipoarista = record i, j: integer end;
dm

arreglodearistas = array[1..MAXM] of tipoarista;


matrizNN = array[1..MAXN,1..MAXN] of integer;
.a

var
w

ngrafo, mgrafo, narbol, marbol: integer;


w

padre, orden: arreglodevertices;


w

aristasgrafo, aristasarbol: arreglodearistas;


adyacencias: matrizNN;

function nuevodato: boolean;


begin
writeln;
write(’ Entrar la arista ’, mgrafo:2);
writeln(’ (fin = <retorno>):’);
write(’ Entrar el primer vertice: ’);
if (not eoln) then begin
nuevodato := true;
with aristasgrafo[mgrafo] do begin
read(i);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 186 Programas mencionados

write(’ Entrar el segundo vertice: ’);


readln(j)
end (* with *)
end (* if not eoln *)
else begin nuevodato := false; readln end
end;

procedure entrardatos;
begin
(* nodos *)
writeln;
write(’Entrar el numero de nodos: ’); readln(ngrafo);
(* aristas *)
writeln; writeln(’Entrar las aristas:’);
mgrafo := 1;

m
while (nuevodato) do mgrafo := mgrafo + 1;

o
mgrafo := mgrafo - 1

.c
end;

os
procedure escribiraristas( a: arreglodearistas; m: integer);

r
const maxrenglon = 10; (* maxima cantidad por renglon *)
fo
var k: integer;
.m
begin
for k := 1 to m do begin
na

write(’ (’, a[k].i:1, ’,’, a[k].j:1, ’)’);


if ((k mod maxrenglon) = 0) then writeln
tu

end;
on

if ((m mod maxrenglon) > 0) then writeln


end;
yc

procedure dearistasaadyacencias;
dm

var i, j, k: integer;
begin
.a

for i := 1 to ngrafo do
for j := 1 to ngrafo do
w

adyacencias[i,j] := 0;
w

for k := 1 to mgrafo do
w

with aristasgrafo[k] do begin


adyacencias[i,j] := 1; adyacencias[j,i] := 1
end
end;

procedure ancho;
var
i, j, ppo, fin: integer;
avisitar: arreglodevertices;

begin
(* inicializacion *)
dearistasaadyacencias;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

anchoprimero (Problema 13.4) Pág. 187

for i := 1 to ngrafo do padre[i] := 0;


narbol := 0;

(* al principio solo esta 1 *)


padre[1] := 1;
ppo := 1; fin := 1; (* los extremos de la cola *)
avisitar[1] := 1;

while (ppo <= fin) do (* mientras la cola no es vacia *)


begin
i := avisitar[ppo]; (* tomar un nodo no visitado *)
ppo := ppo + 1; (* sacarlo de la cola *)
(* y visitarlo *)
narbol := narbol + 1;
orden[narbol] := i;

m
(* examinar vecinos, preservando el orden *)

o
for j := 1 to ngrafo do

.c
if ((adyacencias[i,j] > 0) and (padre[j] = 0)) then
begin (* agregar j a la cola *)

os
padre[j] := i;
fin := fin + 1;
r
fo
avisitar[fin] := j;
.m
end (* if adyacencias > 0 *)
end (* while *)
na

end;
tu

procedure hacerarbol;
on

(* Construir el arbol generado usando padre *)


var k: integer;
yc

begin
marbol := 0;
dm

for k := 2 to ngrafo do (* 1 es "raiz" *)


if (padre[k] > 0) then begin
.a

marbol := marbol + 1;
with aristasarbol[marbol] do begin
w

i := k; j := padre[k] end
w

end
w

end;

procedure escribirorden;
const maxrenglon = 10; (* maxima cantidad por renglon *)
var i: integer;
begin
for i := 1 to narbol do begin
write(orden[i]:5);
if ((i mod maxrenglon) = 0) then writeln
end;
if ((narbol mod maxrenglon) <> 0) then writeln
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 188 Programas mencionados

begin
(* Carteles *)
writeln(’** Recorrer un grafo a lo ancho para determinar’);
writeln(’ un arbol de expansion entrando las aristas.’);
writeln(’ La busqueda se hace en el orden de los enteros,’);
writeln(’ tomando como raiz a 1.’); writeln;

(* Datos e inicializacion *)
entrardatos;
writeln; writeln(’ Las aristas originales son:’);
escribiraristas( aristasgrafo, mgrafo);

(* Procesamiento y salida *)
ancho;
hacerarbol;

m
writeln; writeln(’ Las aristas del arbol resultante son:’);

o
escribiraristas(aristasarbol, marbol);

.c
writeln;
writeln(’Los nodos se visitaron en el siguiente orden:’);

os
escribirorden;
if (narbol < ngrafo) then begin
r
fo
writeln; writeln(’** El grafo no es conexo **’) end;
.m

(* Fin *)
na

writeln; writeln(’** Fin **’)


end.
tu
on

Problema 13.8: dijkstra (pág. 139)


yc

program dijkstra(input, output);


(*
dm

Implementacion del algoritmo de Dijkstra para


encontrar un camino mas corto entre dos nodos
.a

en un grafo pesado.
w

Entrada: un grafo pesado dado por lista de aristas,


w

el numero de vertices, n, y los nodos a unir, s y t.


w

Los vertices son todos los enteros positivos


entre 1 y n. Si alguno no aparece en ninguna
arista, es un vertice aislado.
Los pesos de cada arista son enteros positivos.

Salida: la longitud de un camino mas corto, y el


camino que lo realiza como lista de vertices. Si
los nodos no se pueden conectar, se imprime un
mensaje.
*)

const
MAXN = 20; (* maximo numero de nodos *)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

dijkstra (Problema 13.8) Pág. 189

MAXM = 100; (* maximo numero de aristas *)

type
costo = integer; (* podria ser real *)
arreglodevertices = array[1..MAXN] of integer;
tipoarista = record
i, j: integer; (* los extremos *)
w: costo (* el costo *)
end;
arreglodearistas = array[1..MAXM] of tipoarista;
matrizNN = array[1..MAXN,1..MAXN] of costo;

var
ngrafo, mgrafo, s, t: integer;
infinito: costo;

m
padre: arreglodevertices;

o
dist: array[1..MAXN] of costo;

.c
aristasgrafo: arreglodearistas;
costos: matrizNN;

r os
function nuevodato: boolean;
fo
begin
.m
writeln;
write(’ Entrar la arista ’, mgrafo:2);
na

writeln(’ (fin = <retorno>): ’);


write(’ Entrar el primer vertice: ’);
tu

if (not eoln) then begin


on

nuevodato := true;
with aristasgrafo[mgrafo] do begin
yc

read(i);
write(’ Entrar el segundo vertice: ’);
dm

read(j);
write(’ Entrar el costo (entero > 0): ’);
.a

readln(w);
infinito := infinito + w
w

end (* with *)
w

end (* if not eoln *)


w

else begin nuevodato := false; readln end


end;

procedure entrardatos;
begin
(* nodos *)
writeln;
write(’Entrar el numero de nodos: ’);
readln(ngrafo);
(* aristas *)
writeln;
writeln(’Entrar las aristas:’);
mgrafo := 1; infinito := 1;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 190 Programas mencionados

while (nuevodato) do mgrafo := mgrafo + 1;


mgrafo := mgrafo - 1;
(* nodos a unir *)
writeln;
write(’Entrar el nodo de partida: ’); readln(s);
write(’Entrar el nodo de llegada: ’); readln(t)
end;

procedure escribiraristas(a: arreglodearistas; m: integer);


var k: integer;
begin
writeln;
writeln(’ Vertices Costo’);
for k := 1 to m do
with a[k] do writeln(i:5, j:6, w:9)

m
end;

o
.c
procedure dearistasacostos;
var i, j, k: integer;

os
begin
for i := 1 to ngrafo do
r
fo
for j := 1 to ngrafo do
.m
costos[i,j] := infinito;
for k := 1 to mgrafo do
na

with aristasgrafo[k] do begin


costos[i,j] := w; costos[j,i] := w
tu

end
on

end;
yc

procedure mascorto;
var
dm

i, j, k, kmin, hay: integer;


d, dmin: costo;
.a

avisitar: arreglodevertices;
w

begin
w

(* inicializacion *)
w

dearistasacostos;
for i := 1 to ngrafo do begin
padre[i] := 0; dist[i] := infinito end;

(* s es la "raiz" y el unico en la cola al comenzar *)


padre[s] := s; dist[s] := 0; hay := 1; avisitar[1] := s;

repeat (* aca hay > 0 *)


(* nuevo i: el de minima distancia en la cola *)
kmin := hay; i := avisitar[hay]; dmin := dist[i];
for k := 1 to hay - 1 do begin
j := avisitar[k]; d := dist[j];
if (d < dmin) then begin

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

dijkstra (Problema 13.8) Pág. 191

kmin := k; i := j; dmin := d end


end;
(* ahora tenemos el nuevo i *)
if (i <> t) then begin
(* poner i al fin de la cola y sacarlo *)
if (kmin < hay) then avisitar[kmin] := avisitar[hay];
hay := hay - 1;

(* examinar vecinos de i *)
for j := 1 to ngrafo do
if (dist[i] + costos[i,j] < dist[j]) then begin
(* si j no se agrego, agregarlo a la cola *)
if (dist[j] = infinito) then begin
hay := hay + 1; avisitar[hay] := j end;
(* actualizar dist y padre *)

m
dist[j] := dist[i] + costos[i,j];

o
padre[j] := i

.c
end
end (* if i <> t *)

os
until ((i = t) or (hay = 0))
end;
r
fo
.m
procedure hacercamino;
(* Construir e imprimir el camino mas corto usando padre. *)
na

const maxrenglon = 10; (* maxima cantidad / renglon *)


var
tu

i, ncamino: integer;
on

camino: arreglodevertices;
begin
yc

ncamino := 1; camino[1] := t; i := t;
repeat
dm

i := padre[i];
ncamino := ncamino + 1;
.a

camino[ncamino] := i
until (i = s);
w

(* imprimir "dando vuelta" el arreglo *);


w

writeln(’ Los vertices en el camino son:’);


w

for i := ncamino downto 1 do begin


write(camino[i]:5);
if ((i mod maxrenglon) = 0) then writeln
end;
if ((ncamino mod maxrenglon) <> 0) then writeln
end;

begin
(* Carteles *)
writeln(’** Busqueda del camino mas corto entre dos’);
writeln(’ vertices con el algoritmo de Dijkstra.’);
writeln(’ El grafo se entra mediante lista de aristas.’);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 192 Programas mencionados

(* Datos e inicializacion *)
entrardatos;
writeln; writeln(’Las aristas del grafo son:’);
escribiraristas(aristasgrafo, mgrafo);
writeln; writeln(’Los nodos a unir son:’);
writeln(’ partida: ’, s:1);
writeln(’ llegada: ’, t:1);

(* Procesamiento y salida *)
mascorto;
writeln;
if (padre[t] > 0) then begin
writeln(’ La longitud del camino es ’, dist[t]:1);
hacercamino
end

m
else begin

o
writeln;

.c
writeln(’*** ’, s:1, ’ y ’ , t:1, ’ no se conectan ***’)
end;

r os
(* Fin *)
fo
writeln; writeln(’** Fin **’)
.m
end.
na

Problema 14.1: listas (pág. 154)


tu

program listas(input, output);


on

(*
Listas encadenadas con punteros: con los datos ingresados
yc

formamos dos listas, una agregando adelante y otra ordenada.


dm

El fin de lista con "nil", pero podria ser otro nodo.


Tambien podria haber un nodo al principio sin datos.
.a

listaadelante: se agrega nueva info adelante.


w
w

listaordenada: lista ordenada por "id". Recorremos toda la


w

lista buscando donde ubicar la nueva info, que se inserta


"despues" del elemento que ya estaba si coinciden las
llaves de comparacion, para insertar "antes" cambiar
"<=" por "<".
*)

const tamanio = 40; (* longitud para imprimir strings *)

type
info = record
nombre: string;
id: integer
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

listas (Problema 14.1) Pág. 193

ptrnodo = ^nodo;
nodo = record
info: info;
siguiente: ptrnodo
end;

var
nuevainfo: info;
listaadelante, listaordenada: ptrnodo;

function entrardatos(var datos: info): boolean;


begin
write(’Entrar el nombre (fin = <retorno>): ’);
if (not eoln) then begin
entrardatos := true;

m
with datos do begin

o
readln(nombre);

.c
write(’Entrar numero de id para "’, nombre, ’": ’);
readln(id)

os
end
end
r
fo
else begin entrardatos := false; readln end
.m
end;
na

procedure adelante (var lista: ptrnodo; nuevainfo: info);


(*
tu

agregar nueva info adelante en lista,


on

lista puede ser "nil"


*)
yc

var p: ptrnodo;
begin
dm

new(p);
p^.info := nuevainfo; p^.siguiente := lista;
.a

lista := p
end;
w
w

procedure insertaradelante( p, q: ptrnodo);


w

(* insertar q delante de p, ninguno debe ser "nil" *)


var temp: info;
begin
q^.siguiente := p^.siguiente; p^.siguiente := q;
temp := p^.info; p^.info := q^.info; q^.info := temp
end;

procedure insertardetras( p, q: ptrnodo);


(* insertar q detras de p, ninguno debe ser "nil" *)
begin
q^.siguiente := p^.siguiente; p^.siguiente := q
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 194 Programas mencionados

procedure ordenada (var lista: ptrnodo; nuevainfo: info);


(* agregar elementos a una lista ordenada por id *)
var p, r: ptrnodo;
begin
(* nuevo nodo con nueva info *)
new(r); r^.info := nuevainfo;

(* buscar donde ubicar r *)


if (lista = nil) then begin
(* r es el primer nodo *)
lista := r; r^.siguiente := nil end
else begin (* recorrer la lista *)
p := lista;
while ((p^.siguiente <> nil) and
(p^.info.id <= nuevainfo.id))

m
(* cambiar a < para antes *)

o
do p := p^.siguiente;

.c
if (p^.info.id > nuevainfo.id)
(* cambiar a >= para antes *)

os
then insertaradelante(p, r)
else (* p es el ultimo de la lista *)
r
fo
insertardetras(p, r)
.m
end
end;
na

procedure imprimir (lista: ptrnodo);


tu

var p: ptrnodo;
on

begin
if (lista = nil) then writeln(’ Lista vacia’);
yc

p := lista;
while (p <> nil) do begin
dm

with p^.info do writeln(nombre:tamanio, id:10);


p := p^.siguiente
.a

end
end;
w
w

begin
w

(* carteles *)
writeln(’** Prueba de listas encadenadas **’);
writeln;

(* inicializacion de listas *)
listaadelante := nil;
listaordenada := nil;

(* entrada de datos y construccion de listas *)


while (entrardatos(nuevainfo)) do begin
adelante( listaadelante, nuevainfo);
ordenada( listaordenada, nuevainfo)
end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

grafos (Problema 14.9) Pág. 195

(* imprimir listas *)
writeln; writeln(’Las listas son:’); writeln;

writeln(’ Lista agregando adelante:’);


imprimir(listaadelante); writeln;

writeln(’ Lista ordenada por id:’);


imprimir(listaordenada);

(* Fin *)
writeln; writeln(’** Fin **’)
end.

Ejemplo de Salida

m
** Prueba de listas encadenadas **

o
.c
Entrar el nombre (fin = <retorno>): Geri

os
Entrar numero de id para "Geri": 2
Entrar el nombre (fin = <retorno>): Pablito
r
fo
Entrar numero de id para "Pablito": 1
Entrar el nombre (fin = <retorno>): Guille
.m

Entrar numero de id para "Guille": 2


na

Entrar el nombre (fin = <retorno>): Robin Hood


Entrar numero de id para "Robin Hood": 3
tu

Entrar el nombre (fin = <retorno>):


on

Las listas son:


yc

Lista agregando adelante:


dm

Robin Hood 3
Guille 2
.a

Pablito 1
Geri 2
w
w

Lista ordenada por id:


w

Pablito 1
Geri 2
Guille 2
Robin Hood 3

** Fin **

Problema 14.9: grafos (pág. 157)


program grafos(input, output);
(*
Representaciones de grafos dirigidos donde
V = (1,...,n) por lista de aristas o por

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 196 Programas mencionados

listas de vecinos.

Siempre n es dato.

Las listas terminan en "nil".


*)

type
ptrarista = ^arista;
arista = record u, v: integer; s: ptrarista end;

ptrvecino = ^vecino;
vecino = record id: integer; s: ptrvecino end;

ptrvertice = ^vertice;

m
vertice = record

o
id: integer;

.c
s: ptrvertice;
vecinos: ptrvecino

os
end;

r
fo
var
.m
ngrafo: integer;
aristas: ptrarista;
na

vertices: ptrvertice;
tu

(***************************************
on

Aristas
***************************************)
yc

function nuevaarista(var x, y: integer): boolean;


dm

begin
writeln;
.a

writeln(’Entrar una arista (fin = <retorno>): ’);


write(’ Entrar el primer vertice: ’);
w

if (not eoln) then begin


w

nuevaarista := true;
w

read(x);
write(’ Entrar el segundo vertice: ’);
readln(y)
end (* if not eoln *)
else begin nuevaarista := false; readln end
end;

procedure agregararista(x, y: integer);


(* agregamos arista adelante *)
var parista: ptrarista;
begin
new(parista);
with parista^ do begin u := x; v := y; s := aristas end;

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

grafos (Problema 14.9) Pág. 197

aristas := parista
end;

procedure entrararistas;
(* inicializar e ingresar aristas *)
var x, y: integer;
begin
aristas := nil;
while nuevaarista(x,y) do agregararista(x,y)
end;

procedure escribiraristas;
const maxrenglon = 10; (* maxima cantidad por renglon *)
var
k: integer;

m
p: ptrarista;

o
begin

.c
if (aristas = nil) then writeln(’** lista vacia’)
else begin

os
p := aristas; k := 0;
repeat
r
fo
k := k + 1;
.m
with p^ do write(’ (’, u:1, ’,’, v:1, ’)’);
if ((k mod maxrenglon) = 0) then writeln;
na

p := p^.s
until (p = nil)
tu

end;
on

if ((k mod maxrenglon) > 0) then writeln


end;
yc

(***************************************
dm

Vertices y sus vecinos


***************************************)
.a

procedure inicializarvecinos;
w

var
w

k: integer;
w

p: ptrvertice;
begin
vertices := nil;
(* la construimos "al reves" para que quede ordenada *)
for k := ngrafo downto 1 do begin
new(p);
with p^ do begin
id := k; s := vertices; vecinos := nil end;
vertices := p
end (* for k *)
end;

procedure agregarvecino(x, y: integer);

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 198 Programas mencionados

(* agregar y a los vecinos de x *)


var
pvertice: ptrvertice;
pvecino: ptrvecino;
begin
(* buscar x entre los vertices *)
pvertice := vertices; (* aca debe ser vertices <> nil *)
while (pvertice^.id <> x) do pvertice := pvertice^.s;

(* actualizar vecinos de "x", agregando "y" adelante *)


new(pvecino);
with pvecino^ do begin s := pvertice^.vecinos; id := y end;
pvertice^.vecinos := pvecino
end;

m
procedure escribirvecinos;

o
var

.c
pvertice: ptrvertice;
pvecino: ptrvecino;

os
begin
(* adyacencias *)
r
fo
pvertice := vertices;
.m
while (pvertice <> nil) do begin
write(pvertice^.id:5, ’ -> ’);
na

pvecino := pvertice^.vecinos;
if (pvecino = nil) then write(’ {}’)
tu

else
on

repeat
write(pvecino^.id:3);
yc

pvecino := pvecino^.s
until (pvecino = nil);
dm

writeln;
pvertice := pvertice^.s
.a

end
end;
w
w

(***************************************
w

De aristas a vertices
***************************************)

procedure dearistasaadyacencias;
(* construir listas de adyacencias
recorriendo la lista de aristas *)
var parista: ptrarista;
begin
parista := aristas;
while parista <> nil do begin
with parista^ do begin
agregarvecino(u, v);
agregarvecino(v, u)

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

grafos (Problema 14.9) Pág. 199

end;
(* proxima arista *)
parista := parista^.s
end
end;

(**************************************
Parte principal
**************************************)

begin
(* carteles *)
writeln(’** Pasar de aristas a adyacencias usando’);
writeln(’ listas encadenadas.’); writeln;

m
(* Inicializacion y entrada de datos *)

o
(* vertices *)

.c
write(’ Entrar n (la cantidad de vertices): ’);
readln(ngrafo);

os
inicializarvecinos; writeln;

r
fo
(* aristas *)
.m
entrararistas;
na

(* construccion de listas de adyacencias *)


dearistasaadyacencias;
tu
on

(* imprimir resultados *)
writeln; writeln(’** Resultados **’); writeln;
yc

writeln(’ Lista de aristas:’);


escribiraristas; writeln;
dm

writeln(’ Listas de vecinos:’);


escribirvecinos;
.a

(* fin *)
w

writeln; writeln(’** Fin **’)


w

end.
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Apéndice B

Breve referencia de Pascal

m
B.1. Resumen de operadores

o
.c
B.1.1. Operadores aritméticos

os
Operador operación operando resultado
+ (unitario) identidad
r
entero o real mismo que operando
fo
- (unitario) inversión de signo entero o real mismo que operando
.m
+ (binario) suma entero o real entero o real
- (binario) resta entero o real entero o real
na

* producto entero o real entero o real


div división entera entero entero
tu

/ división entero o real real


on

mod resto entero entero


yc

B.1.2. Operadores relacionales


dm

Operador operación operando resultado


= igualdad simple, string o puntero lógico
.a

<> desigualdad simple, string o puntero lógico


w

< menor simple o string lógico


w

> mayor simple o string lógico


<= menor o igual simple o string lógico
w

>= mayor o igual simple o string lógico

B.1.3. Operadores lógicos


Operador operación operando resultado
not negación lógico lógico
or disyunción lógico lógico
and conjunción lógico lógico

B.2. Identificadores estándares


Constantes

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 202 Breve referencia de Pascal

false maxint true


Tipos
boolean char integer real text
Variables
input output
Funciones con argumentos numéricos

Función operación argumento resultado


abs valor absoluto entero o real entero o real
arctan arco tangente entero o real real
chr carácter en orden entero carácter
exp exponencial (ex ) entero o real real
ln logaritmo (base e) entero o real real

m
odd (∗) si impar entero lógico

o
round (†) redondeo real entero

.c
sin seno real o entero real

os
sqr cuadrado real o entero real o entero
sqrt raı́z cuadrada real o entero (≥ 0) real
trunc (‡) r
truncar
fo
real entero
.m
(∗)
odd (x) es verdadero si x es impar.
na

(†)
round (x) = trunc(x + 0.5).
(
bxc si x ≥ 0,
tu

(‡)
trunc(x ) =
dxe si x < 0.
on

Nota: Las funciones trigonométricas cos y sin tienen sus argumentos


yc

en radianes (y no grados). Del mismo modo la función trigonométrica


inversa arctan retorna un ángulo en radianes. Para pasar entre grados
y radianes, usar que 180◦ = π radianes. La constante π no está definida
dm

en Pascal, pero puede usarse que π = 4 arctan 1.


.a

Otras funciones
w

eof eoln ord pred succ


w

Procedimientos
w

dispose get new pack page put


read readln reset rewrite unpack write
writeln

B.3. Nombres Reservados


and array begin case const div
downto do else end file for
function goto if in label mod
nil not of or packed procedure
program record repeat set then to
type until var while with

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Apéndice C

Algunas notaciones y
sı́mbolos usados

o m
.c
Ponemos aquı́ algunas notaciones, abreviaturas y expresiones usadas (que

os
pueden diferir de algunas ya conocidas), sólo como referencia: el lector deberı́a
mirarlo rápidamente y volver cuando surja alguna duda.
r
fo
C.1. Lógica
.m


na

⇒ implica o entonces. x > 0 ⇒ x = x2 puede leerse como “si x es


positivo, entonces. . . ”.
tu

⇔ si y sólo si. Significa que las condiciones a ambos lados son equivalentes.
on

Por ejemplo x ≥ 0 ⇔ |x| = x se lee “x es positivo si y sólo si. . . ”.


yc

∃ existe. ∃ k ∈ Z tal que. . . se lee “existe k entero tal que. . . ”.



dm

∀ para todo. ∀ x > 0, x = x2 se lee “para todo x positivo,. . . ”.

¬ La negación lógica no. Si p es una proposición lógica, ¬p se lee “no p”.


.a

¬p es verdadera ⇔ p es falsa.
w
w

∧ La conjunción lógica y. Si p y q son proposiciones lógicas, p ∧ q es


verdadera ⇔ tanto p como q son verdaderas.
w

∨ La disyunción lógica o. Si p y q son proposiciones lógicas, p ∨ q es


verdadera ⇔ o bien p es verdadera o bien q es verdadera.

C.2. Conjuntos
∈ pertenece. x ∈ A significa que x es un elemento de A.

∪ unión de conjuntos, A ∪ B = {x : x ∈ A o x ∈ B}.

∩ intersección de conjuntos, A ∩ B = {x : x ∈ A y x ∈ B}.

\ diferencia de conjuntos, A \ B = {x : x ∈ A y x ∈
/ B}.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 204 Algunas notaciones y sı́mbolos usados

| |,
# cardinal, |A|, o a veces #(A), es la cantidad de elementos en el conjunto
A.
∅ El conjunto vacı́o, #(∅) = 0.

C.3. Números
N El conjunto de números naturales, N = {1, 2, 3, . . . }. Observar que para
nosotros 0 ∈
/ N.

Z Los enteros, Z = {0, 1, −1, 2, −2, . . . }.


Q Los racionales p/q, donde p, q ∈ Z, q 6= 0.

R Los reales. Son todos los racionales más números como 2, π, etc., que

m
no tienen una expresión decimal periódica.

o
R+ Los reales positivos, R+ = {x ∈ R : x > 0}.

.c
C √ z = a + bi, con a, b ∈ R y donde i es la

os
Los complejos, de la forma
unidad imaginaria, “ i = −1 ”.
r
fo
| Para m, n ∈ Z, m | n se lee m divide a n, o n es múltiplo de m, y significa
que existe k ∈ Z tal que n = km.
.m

≈ aproximadamente. x ≈ y se lee “x es aproximadamente igual a y”.


na

 mucho menor. x  y se lee “x es mucho menor que y”.


tu

 mucho mayor. x  y se lee “x es mucho mayor que y”.


on

|x| √ absoluto o módulo del número x. Si z = a+ bi ∈ C con a, b ∈ R,


El valor
yc

|z| = a2 + b2 .
dm

bxc El piso de x, x ∈ R. Es el mayor entero que no supera a x, por lo que


bxc ≤ x < bxc + 1: bπc = 3, b−πc = −4, bzc = z ∀ z ∈ Z.
.a

[x] La parte entera de x, x ∈ R. [x] = bxc. Nosotros usaremos la notación


w

bxc, siguiendo la costumbre en las áreas relacionadas con la computa-


w

ción.
w

dxe El techo de x, x ∈ R. Es el primer entero que no es menor que x, por


lo que dxe − 1 < x ≤ dxe: dπe = 4, d−πe = −3, dze = z ∀ z ∈ Z.
ex ,
exp(x) La función exponencial de base e = 2.718281828459 . . .
logb x El logaritmo de x ∈ R, x > 0, en base b. y = logb x ⇔ by = x.

ln x El logaritmo natural de x ∈ R, x > 0, o logaritmo en base e, ln x =


loge x. Es la inversa de la exponencial, y = ln x ⇔ ey = x.
Nota: Para no confundir con los logaritmos en base 10, evitaremos la
notación log x: si nos referimos a la base e usaremos ln x, y en otras
bases logb x.

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

C.4. Números importantes en programación Pág. 205

sen x,
sin x Las función trigonométrica seno, definida para x ∈ R.

cos x Las función trigonométrica coseno, definida para x ∈ R.


sgn(x),
signo(x) Las función signo, definida para x ∈ R por


1 si x > 0,
signo(x) = 0 si x = 0,


−1 si x < 0.

Nota: Algunos autores consideran que signo(0) no está definido.


P Pn
Indica suma, a i = a 1 + a 2 + · · · + an .
i=1
Q Qn
Indica producto, i=1 ai = a1 × a2 × · · · × an .

o m
C.4. Números importantes en programación

.c
os
maxint El máximo entero que admite el compilador.

r
εmin
fo
El menor número positivo que admite el compilador.
.m
εmaq El menor número positivo que sumado a 1 da mayor que 1. Su valor
depende del compilador.
na
tu

C.5. Generales
on

i.e. es decir o esto es, del latı́n “id est”.


yc

e.g. por ejemplo, del latı́n “exempli gratia”.


dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Apéndice D

Sobre los compiladores


Pascal

o m
.c
Suponiendo que se tenga acceso a una computadora, el próximo problema es

os
usar un compilador Pascal. Si bien los compiladores comerciales tienen menos
problemas y tienen documentación adecuada, afortunadamente hay varias posi-
r
bilidades de compiladores disponibles gratis de internet, y pasamos a presentar
fo
algunas1 :
.m

• Aunque no se adhiere estrictamente al estándar Pascal, el compilador


na

“Turbo Pascal” de Borland (para DOS) es muy “robusto” (no tiene proble-
mas), y su popularidad ha hecho que muchos otros compiladores copiaran
tu

su sintaxis.
on

La versión 5.5 de este compilador se puede conseguir gratis para uso per-
sonal en Borland Museum2 .
yc

Versiones más recientes de Turbo Pascal o Delphi (como Turbo Pascal


dm

pero para Windows), también para uso personal pero en francés, pueden
conseguirse en Borland Francia3 .
.a

• Free Pascal4, es un compilador gratuito para varias plataformas, incluyen-


w

do DOS, Win32 y Linux, entre otras. La página principal de Free Pascal5


w

tiene más datos sobre Free Pascal y Pascal en general.


w

• Thefreecountry.com6 presenta varias posibilidades de compiladores gratis,


incluyendo Turbo Pascal y Delphi.

• Para Macintosh, puede consultarse la página de Pascal Central7 para dis-


tintas posibilidades, incluyendo programas hechos.
1 La información está actualizada a febrero de 2003.
2 http://community.borland.com/museum/ (la página está en inglés).
3 http://www.inprise.fr/download/compilateurs/
4 http://www.freepascal.org/download.html (la página está en inglés).
5 http://www.freepascal.org/
6 http://www.thefreecountry.com/developercity/pascal.shtml (la página está en in-

glés).
7 http://www.pascal-central.com/ (la página está en inglés).

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Pág. 208 Sobre los compiladores Pascal

Claro que una vez obtenido el compilador, debe instalarse en la computadora


siguiendo las instrucciones para cada caso.

o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com


Aporte de Victor Caballero para www.admycontuna.mforos.com

Bibliografı́a

[1] J. L. Bentley: More Programming Pearls, Addison-Wesley, 1988.

[2] K. Jensen y N. Wirth: Pascal—User Manual and Report, Springer Ver-


lag, 1985.

m
[3] B. W. Kernighan y D. M. Ritchie: El lenguaje de programación C

o
(2.a ed.), Prentice-Hall Hispanoamericana, 1991.

.c
[4] D. E. Knuth: The Art of Computer Programming, Addison-Wesley. Vol. 1,

os
3.a ed., 1997; vol. 2, 3.a ed., 1997; vol. 3, 2.a ed., 1997.

r
fo
[5] N. Wirth: Introducción a la Programación Sistemática, El Ateneo, 1984.
.m

[6] N. Wirth: Algoritmos y Estructuras de Datos, Prentice-Hall Hispanoame-


ricana, 1987.
na
tu
on
yc
dm
.a
w
w
w

Aporte de Victor Caballero para www.admycontuna.mforos.com

También podría gustarte