You are on page 1of 185

Notas para los cursos de

Computacin y Programacin
con el lenguaje Pascal
Nstor Aguilera
Ao 2007

ndice general
ndice de figuras

ndice de cuadros

1.

2.

3.

4.

5.

Preliminares
1.1. Temas que vemos . . . . . .
1.2. Organizacin y convenciones
1.3. Ideas y consejos sueltos . . .
1.4. Por qu Pascal . . . . . . .
1.5. Sobre la versin 2007 . . . .
El
2.1.
2.2.
2.3.
2.4.

. . . . . . .
que usamos
. . . . . . .
. . . . . . .
. . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

primer contacto
Un poco muy poco sobre cmo funciona la
Programas: edicin, compilacin, ejecucin . . .
El puntapi inicial . . . . . . . . . . . . . . . .
Comentarios Bibliogrficos . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

1
1
1
2
3
4

computadora
. . . . . . . .
. . . . . . . .
. . . . . . . .

.
.
.
.

.
.
.
.

5
5
6
7
10

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

Tipos de datos elementales


3.1. Tipos, variables e identificadores .
3.2. Tipos numricos: entero y real . . .
3.3. Readln . . . . . . . . . . . . . . . .
3.4. Funciones numricas . . . . . . . .
3.5. La codificacin de enteros y reales .
3.6. Variables lgicas . . . . . . . . . .
3.7. Caracteres . . . . . . . . . . . . . .
3.8. Comentarios Bibliogrficos . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

11
11
13
15
16
17
20
22
23

Tomando control
4.1. If . . . . . . . . . . . . . . . . .
4.2. Begin-end . . . . . . . . . . . .
4.3. While . . . . . . . . . . . . . .
4.4. Repeat . . . . . . . . . . . . . .
4.5. For . . . . . . . . . . . . . . . .
4.6. Ingresando muchos datos: eoln .
4.7. Read . . . . . . . . . . . . . . .
4.8. Comentarios Bibliogrficos . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

24
25
27
29
33
34
36
39
40

Aplicaciones
5.1. Clculo numrico elemental . . . . . . . . .
5.1.1. Mezclando nmeros grandes y pequeos
5.1.2. Mtodos iterativos: puntos fijos . . . .
5.1.3. El mtodo babilnico . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

41
41
41
43
46

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

Pg. ii
5.2. Nmeros enteros . . . . .
5.2.1. Algoritmo de Euclides
5.2.2. Ecuaciones diofnticas
5.2.3. Nmeros de Fibonacci
5.3. Comentarios Bibliogrficos

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

49
49
52
53
54

6.

Arreglos
55
6.1. Dimensionamiento de arreglos . . . . . . . . . . . . . . . . . . . . 55
6.2. Bsqueda Lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.3. Polinomios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

7.

Funciones y Procedimientos
7.1. Funciones . . . . . . . . . . . . . .
7.2. El mtodo de la biseccin . . . . .
7.3. Procedimientos . . . . . . . . . . .
7.4. Pasando por valor o por referencia
7.5. Comentarios Bibliogrficos . . . . .

8.

9.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

62
62
66
70
72
75

Todos juntos: arreglos, funciones y procedimientos


8.1. Definiendo nuestros propios tipos de datos: type . . . .
8.2. Ingreso e impresin de arreglos . . . . . . . . . . . . .
8.3. La caja de herramientas . . . . . . . . . . . . . . . . .
8.4. Arreglos multidimensionales . . . . . . . . . . . . . . .
8.5. Strings . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.6. Manejo elemental de archivos de texto . . . . . . . . .
8.7. Comentarios Bibliogrficos . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

76
76
77
80
80
81
82
84

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

Nmeros Aleatorios y Simulacin


85
9.1. Nmeros aleatorios . . . . . . . . . . . . . . . . . . . . . . . . . . 85
9.2. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

10. Bsqueda y clasificacin


10.1. Bsqueda lineal con centinela . . . .
10.2. Bsqueda binaria . . . . . . . . . . .
10.3. Mtodos elementales de clasificacin
10.4. Registros (records) . . . . . . . . . .
10.5. Comentarios Bibliogrficos . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

89
89
90
92
95
98

11. Recursin
99
11.1. Funciones y procedimientos definidos recursivamente . . . . . . . 100
11.2. Los Grandes Clsicos de la Recursin . . . . . . . . . . . . . . . . 103
11.3. Comentarios Bibliogrficos . . . . . . . . . . . . . . . . . . . . . . 106
12. Objetos combinatorios
12.1. Pilas y colas . . . . . . . . . . . . .
12.2. Generando Subconjuntos . . . . . .
12.3. Caminante, no hay caminos... . . .
12.4. Generando permutaciones . . . . .
12.5. Objetos combinatorios generados al

. . .
. . .
. . .
. . .
azar

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

107
107
109
111
113
114

13. rboles binarios ordenados


117
13.1. Comentarios Bibliogrficos . . . . . . . . . . . . . . . . . . . . . . 121

ndice general

Pg. iii

14. Grafos
14.1. Representacin de grafos en la computadora
14.2. Recorriendo un grafo . . . . . . . . . . . . .
14.3. Recorrido en profundidad y a lo ancho . . .
14.4. Grafos con pesos . . . . . . . . . . . . . . .
14.5. Camino ms corto: Dkstra . . . . . . . . .
14.6. Mnimo rbol generador: Prim . . . . . . . .
14.7. Comentarios Bibliogrficos . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

122
124
127
129
133
136
140
143

A. Programas mencionados
Problema 2.2: holamundo . . . .
Problema 3.2: sumardos . . . . .
Problema 3.4: leerentero . . . .
Problema 3.5: raiz . . . . . . . .
Problema 3.6: segundos . . . . .
Problema 3.10: enteroareal . . .
Problema 3.17: positivo . . . . .
Problema 3.19: caracteres1 . . .
Problema 4.2: valorabsoluto . . .
Problema 4.4: comparar . . . . .
Problema 4.5: caracteres2 . . . .
Problema 4.11: resto . . . . . .
Problema 4.12: tablaseno1 . . .
Problema 4.13: gauss . . . . . .
Problema 4.16: cifras . . . . . .
Problema 4.17: epsmin . . . . .
Problema 4.18: potencia . . . . .
Problema 4.23: eolnprueba . . .
Problema 4.24: eco . . . . . . .
Problema 4.25: sumardatos . . .
Problema 4.27: palabras . . . . .
Problema 5.11: babilonico . . . .
Problema 5.13: euclides . . . . .
Problema 6.1: unidades . . . . .
Problema 6.2: renglon . . . . . .
Problema 6.3: busquedalineal . .
Problema 7.2: potencias . . . . .
Problema 7.3: biseccion . . . . .
Problema 7.6: tablaseno2 . . . .
Problema 7.7: intercambio . . .
Problema 8.8: deconsolaaarchivo
Problema 8.8: dearchivoaconsola
Problema 9.1: dado . . . . . . .
Problema 9.2: dados . . . . . . .
Problema 13.1: arbolbinario . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

144
144
144
144
145
145
145
146
146
147
147
147
148
148
149
149
150
150
151
151
152
152
153
154
154
155
156
157
158
159
160
161
162
162
163
163

B. Breve referencia de Pascal


B.1. Operadores . . . . . . . .
B.1.1. Aritmticos . . . . . .
B.1.2. Relacionales . . . . .
B.1.3. Lgicos . . . . . . . .
B.1.4. Precedencia . . . . . .
B.2. Identificadores estndares

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

166
166
166
166
166
167
167

Pg. iv
B.3. Nombres reservados . . . . . . . . . . . . . . . . . . . . . . . . . . 168
C. Algunas notaciones y smbolos usados
C.1. Lgica . . . . . . . . . . . . . . . . . . . .
C.2. Conjuntos . . . . . . . . . . . . . . . . . .
C.3. Nmeros: conjuntos, relaciones, funciones
C.4. Nmeros importantes en programacin . .
C.5. Generales . . . . . . . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

169
169
169
170
171
171

Bibliografa

172

ndice alfabtico

173

ndice de figuras
2.1.
2.2.
2.3.
2.4.

Esquema de transferencia de datos en la computadora.


Bits y byte. . . . . . . . . . . . . . . . . . . . . . . . .
Esquema del desarrollo de un programa. . . . . . . . .
Esquema del programa ejecutable en la memoria. . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

6
6
7
8

3.1. Datos de tipos elementales en la memoria. . . . . . . . . . . . . .


3.2. Los datos con sus identificadores. . . . . . . . . . . . . . . . . . .
3.3. Esquema de la densidad variable. . . . . . . . . . . . . . . . . . .

12
12
19

5.1. Grficos de y = cos x y y = x. . . . . . . . . . . . . . . . . . . . .


5.2. Aproximndose al punto fijo de cos x. . . . . . . . . . . . . . . .

45
45

6.1. Esquema del arreglo v guardado en memoria. . . . . . . . . . . .


6.2. Aproximacin de sen x mediante un polinomio. . . . . . . . . . .

56
59

7.1. Una funcin continua con distintos signos en los extremos. . . . .


7.2. Programa, funciones y procedimientos en la memoria. . . . . . .
7.3. Intercambio de los valores de u y v. . . . . . . . . . . . . . . . . .

66
70
73

10.1. Ordenando por conteo. . . . . . . . . . . . . . . . . . . . . . . . .


10.2. Esquema del registro de tipo complejo en memoria. . . . . . . .

94
96

11.1. Contando la cantidad de caminos posibles. . . . . . . . . . . . . . 102


11.2. Las torres de Hanoi. . . . . . . . . . . . . . . . . . . . . . . . . . 105
13.1. Una estructura lineal. . . . . . . . . . . . . . . . . . . . . . .
13.2. Un rbol binario ordenado. . . . . . . . . . . . . . . . . . . . .
13.3. Disposicin del arreglo de registros luego de ingresar los datos.
13.4. Disposicin del rbol binario luego de ingresar los datos. . . . .
13.5. Los registros de la figura 13.4 proyectados. . . . . . . . . . .
13.6. Los registros con ndices para la estructura de rbol binario. . .

.
.
.
.
.
.

117
117
118
119
119
119

14.1. Un grafo con 6 vrtices y 7 aristas. . . . . . . . . . . . . . . . . . 123


14.2. Un grafo no conexo y un rbol. . . . . . . . . . . . . . . . . . . . 123
14.3. Un grafo con pesos en las aristas. . . . . . . . . . . . . . . . . . . 133

ndice de cuadros
4.1. Prueba de escritorio . . . . . . . . . . . . . . . . . . . . . . . . .

30

8.1. Estructura de un programa Pascal. . . . . . . . . . . . . . . . . .


8.2. Diferencias entre el estndar y Turbo Pascal. . . . . . . . . . . .

77
83

10.1. Comparacin de algoritmos de clasificacin. . . . . . . . . . . . .

94

14.1. Esquema del algoritmo recorrer. . . . . . . . . . . . . . . . . . . . 128


14.2. Esquema del algoritmo de Dkstra. . . . . . . . . . . . . . . . . . 137
14.3. Esquema del algoritmo de Prim. . . . . . . . . . . . . . . . . . . 141

Captulo 1

Preliminares
En este curso se van entrelazando la introduccin de los elementos de programacin estructurada con la resolucin de problemas de matemticas, incluyendo temas de anlisis y clculo numrico, teora de nmeros, combinatoria
y grafos, sirviendo tanto de introduccin de algunos temas como de repaso y
fortalecimiento de otros. Por el contrario, se cubre muy poco de las aplicaciones
informticas como bases de datos.

1.1. Temas que vemos


En los captulos 2, 3, 4, 6, 7, 8, 10 y 11 cubrimos temas como tipos elementales
de datos, estructuras de control, arreglos, funciones, archivos de texto, bsqueda,
clasificacin elemental y recursin. Estos temas son ms o menos comunes a
todos los cursos de programacin, aunque ac el nfasis est repartido entre las
matemticas y la programacin.
En cambio, los captulos 5 y 9 cubren temas de matemticas (tericas o
aplicadas) que normalmente no se dan, como clculo numrico, nmeros enteros
o uso de nmeros aleatorios, claro que puestos a un nivel muy elemental.
Pilas y colas es un tema normalmente incluido en cursos de programacin,
pero la perspectiva que damos en los captulos 12 y 13 es bastante distinta.
All trabajamos con objetos combinatorios, como subconjuntos, permutaciones
y rboles binarios, donde aparece verdaderamente la fuerza de recursin.
El ltimo captulo tampoco se cubre en cursos tradicionales. Se incluye para
cumplir con un requisito de las carreras ingenieriles, y para entenderlo a pleno
es conveniente haber tenido algn contacto previo con la teora de grafos, a
diferencia de los anteriores que no necesitan de mayores prerrequisitos (aunque
s cierta madurez matemtica).

1.2. Organizacin y convenciones que usamos


En los captulos 2 a 14 se presentan los temas y problemas, agrupados en
secciones y a veces subsecciones. Los problemas estn numerados comenzando
con 1 en cada captulo, de modo que el problema 4.5 se refiere al problema 5
del captulo 4. De modo similar, la seccin 3.2 se refiere a la seccin 2 del
captulo 3.
En varios de los problemas se mencionan programas completos, puestos juntos en el apndice A. Tambin entre los apndices incluimos una breve referencia
de Pascal (apndice B) y notaciones usadas en el libro (apndice C).

Pg. 2

Preliminares
A veces hay texto intercalado entre los problemas, por lo que para indicar
el fin del enunciado de un problema est 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 ms chico para no distraer demasiado del texto
principal, y puede omitirse su lectura.
En los comentarios, en itlica, se hacen referencias histricas, orientadoras,
curiosidades, etc. Son de la forma
Esto es un comentario.

Por otra parte, las notas son en general de ndole ms tcnica, y son de la
forma
- Esto es una nota.

Los nombres de los programas aparecen con otro tipo de letra, as, mientras
que lo que escribiremos en la computadora est indicado en monotipo, as,
algunas veces entre comillas dobles, para recalcar algn fragmento o tratando
de evitar confusiones, como ste, reservando las comillas simples para caracteres, como a , que imitando la usanza en Pascal. Tambin algunas veces los
espacios en blanco de un texto se ponen de esta forma para distinguirlos.
Siguiendo la tradicin norteamericana, la computadora expresa los nmeros
poniendo un punto decimal en vez de la coma, y para no confundirnos
seguimos esa prctica. As, 1.589 es un nmero entre 1 y 2, mientras que 1589
es un nmero entero, mayor que mil. A veces dejamos pequeos espacios entre
las cifras para leer mejor los nmeros, como en 123 456.789.
Tambin se hace complicado trabajar con tildes (como en ) o virgulillas
(como en ) al escribir los programas o mostrar resultados por pantalla, de
modo que en la escritura de cdigos los obviaremos (escribiendo a o ni, y
paragero quedar como paraguero).
En fin, en el texto indicamos con <retorno> el pulsado de la tecla retorno
(o return en teclados ingleses) para iniciar un nuevo rengln. Dependiendo del
sistema operativo, es posible que debas pulsar en cambio la tecla intro (o
enter en ingls).

1.3. Ideas y consejos sueltos


Uno puede darse una idea de cmo cocinar mirando un libro de cocina, pero
seguramente la experiencia es mucho ms completa y satisfactoria si se tienen
los ingredientes, las cacerolas y la misma cocina. Del mismo modo, se puede
apreciar qu es la programacin mirando un libro, pero vas a sacar mucho ms
provecho si dispons adems de una computadora y un compilador Pascal con
los que trabajar.
En programacin es muy importante copiar (y tomar ideas y estructuras
de) programas ya hechos. Idealmente cuando trabajes con los contenidos de
este libro copiars (preferentemente de un diskette o de internet) los programas
presentados, y hars variaciones o copiars partes de uno o ms de ellos para
resolver problemas.
Con ese propsito inclu muchos programas completos que sirven como base.
Unos cuantos no estn completos (pero funcionan), y tendrs que completar o
cambiar partes para llegar a algo satisfactorio. En todos los casos trat de poner
programas claros (que se entiendan) antes que eficientes.
Muchos principiantes se ponen ansiosamente a escribir o copiar un programa
sin tener un plan especfico, quizs por su experiencia previa con la computado-

1.4. Por qu Pascal


ra. Una buena parte de los problemas que aparecen no son sencillos de resolver,
y seguramente no se resuelven probando con distintos botones (no hay menes!). Inclusive hay varios problemas que no necesitan de la computadora.
Al principio el curso parece (y es) fcil y muchos estudiantes se dejan estar,
pero ya hacia el final del captulo 3 las cosas se ponen ms movidas y es difcil
de recuperar el tiempo perdido: es importante que mantengas el ritmo del curso
que, un poco ms o un poco menos, es de un captulo por semana.
Si quers sacar provecho del curso, muchas veces vas a tener que usar lpiz
y papel para trazarte un plan de resolucin antes de copiar o modificar los programas, pero para eso debs entender primero qu es lo que hacen las distintas
partes y por sobre todo a dnde se quiere llegar.
La adquisicin de conocimientos no es gratuita y requiere esfuerzo y tiempo,
y las equivocaciones forman parte inevitable de este proceso: habr que estar
dispuesto a pasar un buen rato en la silla!
An cuando no podamos resolver un problema en programacin o en matemticas, es poco til que alguien nos cuente la solucin si antes no hemos hecho
un trabajo propio: de este modo podremos apreciar, e inclusive criticar, la que
nos proponen.
En algunos problemas se incluyen sugerencias para la resolucin, que estn
puestas como orientacin cuando ests perdido. La recomendacin es tapar la
sugerencia, y recurrir a ella en segunda instancia o cuando ya hayas resuelto el
problema para ver si hay otras posibilidades de resolucin.
Esto nos trae al tema de que tanto en programacin como en matemticas,
no hay una nica forma de hacer los programas o resolver los problemas. Lo
presentado es slo una posibilidad.
A algunos les parecer que las sugerencias son oscuras o escasas, a otros
les parecer que el material presentado es excesivo, y habr otros que querrn resolver ms problemas. A todos les recomendamos los libros de Engel [3],
Wirth [11, 12] autor del lenguaje que usamos, Pascal y Jensen y Wirth [4]
para temas especficos de Pascal, que forman la base sobre la cual est hecho
este libro.
Finalmente, cuando ests perdido con alguna definicin, consult el apndice B para referencias de Pascal, o el apndice C para notaciones y definiciones
de matemticas, o el ndice alfabtico al final.(1)

1.4. Por qu Pascal


Desde cierto punto de vista, Pascal es un lenguaje obsoleto. Fue popular en
la dcada de 1980 con el auge de la computadoras personales, pero prcticamente no se usa profesionalmente en la actualidad. No tiene en su estndar
facilidades grficas, las rutinas de entrada y salida no son muy flexibles, e inclusive no tiene incorporadas rutinas de nmeros aleatorios como usaremos en
el curso. En fin, muchas veces su sintaxis hace que debamos hacer rodeos para
expresar algo.
Los lenguajes de moda en estos das como Java ofrecen un paradigma
ms alejado de los algoritmos de matemticas, estando ms bien dirigidos a las
aplicaciones informticas.
Pascal fue creado como lenguaje para la enseanza de la programacin estructurada por N. Wirth, siguiendo ideas del lenguaje Algol, nombre derivado
de ALGOrithmic Language, que expresa bien el propsito con el que fue creado.
(1)

Donde nunca se encuentra lo que uno busca, como en las guas telefnicas.

Pg. 3

Pg. 4

Preliminares
El lenguaje C tambin deriva del Algol, y es mucho ms popular ahora que
Pascal. Sin embargo, para hacer un mnimo programa ya necesitamos mencionar qu son las bibliotecas, para leer un dato tenemos que saber qu es pasar
por valor o referencia, y su sintaxis puede ser francamente crptica para los no
iniciados. En sntesis, requiere de un esfuerzo extra que dejara menos tiempo
para lo que verdaderamente importa: aprender algoritmos y resolver problemas
de matemticas.
El aprendizaje de Pascal da una slida base para programar en otros lenguajes, como Fortran o C, y sistemas, como Matlab o Maple, que s son usados
profesionalmente.
No est dems comentar que este curso comenz a dictarse en el ao 1995 basado en uno de los sistemas que ms se usan actualmente en aplicaciones cientficas. Como resultado vimos que no se aprenda una disciplina de programacin,
y se confundan conceptos bsicos como que nmeros enteros y fraccionarios son
bien distintos para la computadora.
En http://math.unl.edu.ar/~aguilera/compiladores_pascal.html hay
indicaciones de cmo conseguir compiladores Pascal gratis de internet. Especialmente recomendado es el compilador GNU Pascal (gpc). Otras recomendaciones
son Free Pascal (fpc) y Turbo Pascal. gpc y fpc no tienen un editor de textos
integrado (que se puede agregar) pero estn disponibles para todos los sistemas
operativos preponderantes. Son mucho ms modernos que Turbo Pascal, que s
viene con un editor integrado, pero usa el sistema operativo MS-DOS y tiene
serias limitaciones en el tamao de las variables.

1.5. Sobre la versin 2007


Estas notas se van modificando en base a la experiencia que se gana en el
dictado del curso, de modo que a travs de los aos hay temas que se cambian,
otros se eliminan, otros se agregan, y otros... reaparecen!
En esta ocasin reaparecieron la generacin de conjuntos y permutaciones
aleatorias (al final del captulo 12), lo que empuj al tema de rboles binarios
a un captulo separado. Tambin reescrib gran parte del captulo de grafos, que
sigue ofreciendo dificultades a los alumnos (y a m para ensearlo).
Agradezco a Jorge DEla, Egle Haye, Alberto Marchi y Marcela Morvidone
quienes me ayudaron a corregir errores (tipogrficos o conceptuales) en notas de
aos anteriores. Especialmente agradezco a Luis Bianculli, cuyos comentarios y
observaciones me hicieron cambiar radicalmente la presentacin de arreglos de
la versin de 2003 a la de 2004.

Captulo 2

El primer contacto
2.1. Un poco muy poco sobre cmo funciona
la computadora
Conceptualmente, la computadora es una mquina que toma o accede a
datos, los procesa y devuelve resultados. Los datos o entradas y los resultados
o salidas pueden ser simples como nmeros o letras, o mucho ms complicados
como una matriz o una base de datos,(1) que podemos esquematizar como
entrada

procesamiento

salida

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


pensaremos que el procesamiento est a cargo de una nica unidad, llamada
CPU por Central Processing Unit o Unidad Central de Procesamiento, que
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.
John von Neumann (19031957) se interes inicialmente en lgica, teora
de conjuntos, de la medida, y mecnica cuntica, tocando luego temas de anlisis funcional, teora ergdica, siendo fundador de la teora de juegos. En sus
ltimos aos tambin tuvo influencia decisiva en ecuaciones en derivadas parciales y en teora de autmatas, en la que sintetiz sus conocimientos e ideas
de lgica y grandes computadoras electrnicas.

En los programas que haremos normalmente nos comunicaremos con la


computadora entrando los datos con el teclado y recibiendo los resultados en
la pantalla, refirindonos en general como terminal o consola al conjunto combinado 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.
Un esquema del movimiento de datos entre perifricos (consola, discos, impresora, etc.), memoria y CPU est indicado en la figura 2.1.
Nos imaginaremos que la memoria, en donde se almacenan los datos, est
constituida por muchas cajitas pequeas llamadas bits por binary digit o dgito
binario, en cada una de las cuales slo se puede guardar un 0 o un 1. Puesto
que esta caja es demasiado pequea para guardar informacin ms complicada
(1) Es como pensar en una mquina de hacer chorizos: ponemos los ingredientes (mezcla,
tripa, etc.), y despus de dar vuelta la mana tenemos los chorizos.

El primer contacto

consola

Pg. 6

pantalla
teclado
memoria
(datos)

discos

CPU
(procesamiento)

impresora
otros
Figura 2.1: Esquema de transferencia de datos en la computadora.
que s/no o blanco/negro, los bits se agrupan en cajas un poco ms grandes llamadas bytes, que generalmente tienen 8 bits, conceptualmente alineados,
puesto que queremos que 00001111 sea distinto de 11110000. Ver esquema en la
figura 2.2.

un byte
}|
0

bits
Figura 2.2: Bits y byte.
Problema 2.1. Suponiendo que un byte tenga 8 bits:
a) Cuntas ristras distintas de 0 y 1 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, cuntos elementos distintos podra contener un byte? Sugerencia: si el byte tiene 8 bits puede ser
que hayan 8 ceros y ningn uno, o 7 ceros y 1 uno, o...
$
A su vez, para las computadoras ms recientes, estas unidades resultan demasiado pequeas 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.

2.2. Programas: edicin, compilacin, ejecucin


Por supuesto, queremos que la computadora haga algo con los datos que
le damos, pero tenemos que darle instrucciones sobre cmo hacerlo. El conjunto
de instrucciones y datos para realizar determinada tarea es lo que llamaremos
programa, y los mismos programas pueden considerarse como un tipo especial
de datos.
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
o corra nuestro programa, leyendo las instrucciones que contiene.
Los lenguajes de programacin son abstracciones que nos permiten escribir
las instrucciones de un programa de forma que un ser humano puede entender
ms fcilmente que ristras de ceros y unos. Las instrucciones para la mquina

2.3. El puntapi inicial

Pg. 7

se escriben como sentencias de acuerdo a las reglas del lenguaje en lo que


se llama programa fuente.
Hay distintos tipos de lenguajes de programacin, cada uno con sus defectos
y virtudes. Dentro de los que ms se usan en matemticas, estn (en orden ms o
menos cronolgico) Fortran, C, Pascal, C++, y otros integrados a sistemas con
posibilidades grficas y/o simblicas como Matlab, Maple o Mathematica. En
estas notas usaremos el lenguaje Pascal, que ha sido diseado para la enseanza
de programacin.
N. Wirth dio el nombre de Pascal al lenguaje en honor al matemtico francs Blaise Pascal (16231662), quien fue uno de los primeros en desarrollar
una calculadora mecnica, cuando tena unos 20 aos.
Sin embargo hubo muchas otras calculadoras antes, como las computadoras
lunares y planetarias del astrnomo iran al-Kashi (13931449), o la de W.
Schickard (15921635) que multiplicaba y divida, mientras que la de Pascal
(construida entre los aos 16421644) slo sumaba y restaba.

Luego de elegir un lenguaje de programacin, al desarrollar un programa lo


usual es primero escribir el programa fuente (el que nosotros entendemos) con
un editor de textos en la computadora, eventualmente guardando lo escrito en
el disco rgido o un diskette.
El programa fuente debe ser traducido a algo que la CPU pueda entender,
es decir las famosas ristras de 0 y 1. Este proceso se llama compilado, dando
por resultado un programa ejecutable, que es el que en realidad va a usar la
computadora.
- Esta descripcin basta para nuestros propsitos, en realidad todo el proceso requiere otros pasos intermedios que en nuestro caso sern hechos
automticamente.
- Algunos lenguajes son interpretados, es decir no existe la compilacin y no
se crea el ejecutable.

En la mayora de los casos an para gente experimentada habr problemas en la compilacin (por ejemplo, por errores de sintaxis), o al ejecutar
el programa los resultados no sern los esperados. Esto da lugar a un ciclo de
trabajo esquematizado en la figura 2.3.
Editar

fuente

Compilar

ejecutable

Ejecutar

Corregir

Figura 2.3: Esquema del desarrollo de un programa.


En fin, al momento de usar el programa ejecutable, el sistema operativo lo
aloja en algn lugar disponible de la memoria, quedando un esquema como el
de la figura 2.4.

2.3. El puntapi inicial


Para lo que sigue, ser conveniente ir mirando el primer programa Pascal
con el que trabajaremos: el programa holamundo (pg. 144).
En Pascal, todos los programas (fuentes) se dividen en dos partes o cuerpos. En la primera, a veces llamada de declaraciones, se coloca el nombre del

Pg. 8

El primer contacto

programa
ejecutable

datos
instrucciones

Memoria
Figura 2.4: Esquema del programa ejecutable en la memoria.
programa mediante program nombre(input,output) y otras sentencias que
iremos viendo.
- Siguiendo el estndar Pascal. Muchos compiladores aceptan la omisin de
(input, output), e inclusive algunos ignoran completamente esa parte.

La segunda parte, a veces llamada principal, empieza con begin y termina


con end. (punto . incluido), y entre ellos se ponen sentencias para realizar
acciones.
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 despus de un par de semanas.
- Tambin se pueden encerrar comentarios entre { y } , pero no los
usaremos a fin de seguir una sintaxis ms 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
extensin .p o .pas, para indicar que se trata de un programa Pascal. As, generalmente guardaremos el programa fuente de nombre pepe en el archivo pepe.p o
pepe.pas. Al compilarlo (con xito) se crea el ejecutable. Dependiendo del compilador y el sistema operativo, es posible que se cambie la extensin a .exe, de
modo que obtenemos el archivo pepe.exe, pero tambin es posible que (salvo
indicacin en contrario) el nombre sea algo genrico como a.out.
- O sea, la extensin para el ejecutable depende del sistema operativo y del
compilador, y puede no existir. Asimismo, puede no existir la extensin
para el programa fuente.

La prueba de fuego es editar, compilar y ejecutar el primer programa. Sin


embargo, los detalles de cmo realizar el ciclo de edicin-compilacin-ejecucin
dependen del sistema operativo y el compilador (la marca del compilador) que
estemos usando, de modo que habr que seguir las instrucciones de los manuales
o pedir auxilio a algn conocido con este primer paso.
Problema 2.2 (Hola Mundo). Copiar, compilar y ejecutar el programa holamundo, guardndolo en disco o diskette como holamundo.pas.
- Muchos compiladores, entre ellos el muy difundido Turbo Pascal, hacen que
al ejecutar un programa como holamundo se abra una ventana distinta que
se cierra automticamente al finalizar la ejecucin del programa. El proceso
puede ser tan rpido que apenas nos damos cuenta de que ha sucedido algo.
En estos casos es conveniente agregar un rengln con las instrucciones
writeln(<retorno> para fin); readln
al terminar el programa, antes de end. y agregando un ; (punto y
coma) al fin del rengln anterior.
Otra posibilidad es aprender los comandos para poder pasar de una
pantalla a otra.

2.3. El puntapi inicial

- Turbo Pascal y otros compiladores similares crean un ejecutable que queda


en memoria, y no guardan una copia en el disco salvo instruccin expresa.
En la mayora de los casos no nos va a interesar guardarla.

a) Observar con cuidado los signos de puntuacin y qu hace cada una de las
instrucciones:
El rengln inicial que comienza con program..., y que termina en ;.
En este programa es la nica sentencia de la parte declarativa.
El comentario inmediatamente despus de program..., explicando al
que lee el programa fuente cul es el propsito.
El cuerpo principal que empieza con begin y termina en end..
Hay tres sentencias en la parte principal, separadas por dos ;.
writeln escribe un rengln en la pantalla, y el texto a escribir se encierra
entre (comillas simples). Si no tiene argumentos, writeln escribe un
rengln vaco, i.e., sin caracteres.
b) Eliminar, repetir o cambiar las instrucciones. Por ejemplo:
i) eliminar el segundo writeln,
ii) y despus tambin el tercero,
iii) cambiar writeln por WRITELN, y despus 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 sangras, i.e., espacios 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 podra
escribirse el programa en un nico rengln, y un espacio o varios no hacen
diferencia:
i) Eliminar o agregar espacios al comienzo, en el medio y/o al final de
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).
- A los fines del programa fuente, los espacios, tabulaciones (tecla tab o
similar) y renglones son intercambiables (mientras no estn encerrados
entre comillas simples).

d) Agregar y/o eliminar comentarios.


Por ejemplo, agregar el comentario colorin, colorado despus de
$
writeln(y Chau!), en el mismo rengln y/o en el siguiente.
El uso de ; en Pascal resulta un poco confuso al principio, pero debe
tenerse en mente que se usa como , o ; se usan al construir una oracin
en castellano: para separar sentencias. Del mismo modo, . (punto) en Pascal
tiene el mismo sentido que el punto final en castellano. En cambio, , (coma)
se usa en forma distinta, por ejemplo, para separar argumentos de funciones
como se hace en matemticas.
Pero no desesperar: por el momento no es necesario entender todo lo que se
hace.

Pg. 9

Pg. 10

El primer contacto

2.4. Comentarios Bibliogrficos


El programa holamundo est tomado del libro de Kernighan y Ritchie [7], y
es clsica su inclusin al aprender a programar.

Captulo 3

Tipos de datos elementales


Recordemos que la informacin, incluyendo el programa ejecutable, se guarda en un lugar de memoria, como ristras de ceros y unos. Como nmeros y
caracteres se representan de esta forma, la computadora al tomar un dato debe
saber si se trata de uno u otro. Esto da lugar a distintos tipos de datos, como
estudiamos en este captulo.

3.1. Tipos, variables e identificadores


Supongamos que guardamos las letras siguiendo el orden del abecedario, a
como 0, b como 1, c como 10, y as sucesivamente. Vemos que no podramos distinguir entre el par de letras ba y la nica letra c, pues ambas se
representaran como 10.
Para evitar esta confusin, se decide que todas las letras ocupen siempre el
mismo espacio, por ejemplo un byte de 8 bits. De esta forma tendremos (ver el
problema 2.1) 28 = 256 posibilidades para los caracteres, lo cual es suficiente
para guardar las letras de nuestro alfabeto (pero no los de algunos alfabetos
orientales).
Habiendo decidido esto, nos toca ahora guardar nmeros. Como antes, es
conveniente guardar a todos los nmeros en la misma cantidad de bits. Si usramos 8 bits como hicimos para las letras, tendramos slo 256 nmeros disponibles, lo cual es bien pobre. Es conveniente que las cajas sean ms grandes.
Cuando la mquina lea estas cajas que guardan letras o nmeros, tiene que
saber cuntos bits juntos debe leer, e interpretar la ristra de bits segn sea una
letra o un nmero. Surgen as cuatro tipos elementales de datos, cada uno con
distinta codificacin interna:
boolean o lgica, para guardar los valores true (verdadero) o false
(falso),
char para guardar caracteres, i.e., letras, signos de puntuacin y otros
que veremos ms adelante,
integer para guardar nmeros enteros, como 1, 0, 5, y
real para guardar nmeros reales, i.e., nmeros como 123.456.
Llamamos a las variables lgicas booleanas, en honor a G. Boole (1815
1864), quien hizo importantes progresos al algebrizar la lgica.

En la figura 3.1 ilustramos algunas cajas con datos dentro de la memoria,


con sus tipos correspondientes.

Pg. 12

Tipos de datos elementales

a
char

verdadero
boolean

1234
integer

4321
integer

123.456
real

98.7654
real

Figura 3.1: Datos de tipos elementales en la memoria.


Ahora tenemos el problema de cmo acceder a esas cajas que guardan caracteres o nmeros. Esto es sencillo: les ponemos nombres para identificarlas. Las
cajas as nombradas se llaman variables pues podremos colocar en ellas datos
distintos o (ejem!) variables y los nombres que reciben se llaman identificadores.
En la figura 3.2 mostramos algunos nombres posibles para las cajas que
mostramos en la figura 3.1.

a
codigo

verdadero
fin

1234
a

4321
m

123.456
x

98.7654
y

Figura 3.2: Los datos con sus identificadores.


Cuando redactamos el programa, debemos indicar al compilador los nombres
y tipos de variables que usaremos, procedimiento llamado de declaracin de
variables. En el programa se pone una lista de variables y tipos despus 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 program o writeln que
usa Pascal para instrucciones del lenguaje. Adems, en Pascal hay nombres
reservados, que no podemos usar como identificadores.
- En la seccin B.3 (pg. 168) est la lista completa de los nombres reservados.

Aparte de estas palabras prohibidas, los identificadores en Pascal pueden ser


cualquier sucesin de letras maysculas o minsculas y dgitos, pero

siempre tienen que empezar con una letra,


no pueden tener espacios entre medio,
ni pueden tener caracteres como $, _ (guin bajo), +, etc.,
y por supuesto, dos variables distintas no pueden tener el mismo nombre.

A diferencia de otros lenguajes (como C o Mathematica), segn el estndar

3.2. Tipos numricos: entero y real


Pascal no se distingue entre maysculas o minsculas tanto en los identificadores
como en palabras claves. As, podemos poner sin problemas en alguna parte
writeln y en otra WriteLn (como hemos hecho en el problema 2.2).
- Sin embargo, algunos compiladores s diferencian entre maysculas y minsculas en identificadores y palabras reservadas.

Problema 3.1. Decidir cules de los siguientes son identificadores vlidos en


Pascal:
a) Pepe
b) Pepe Grillo c) PepeGrillo d) mn32xy
$
e) 32xymn f ) mn32 xy
g) M32nxY
h) mn_32
Tambin tenemos otro problema: cmo colocar los datos en estas cajas o
variables, proceso que se llama asignacin.
Una forma de hacerlo es mediante := (sin espacios intermedios). As, si
queremos guardar un 2 en la variable n que es de tipo entero ponemos n := 2,
y si queremos copiar los contenidos de la caja o variable a en la caja o variable
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
no es posible hacer asignaciones arbitrarias entre distintos tipos de variables, lo
que veremos con algn detalle en el problema 3.14.
- 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.
- Otros lenguajes usan otros smbolos para la asignacin, en vez de :=. Al
escribir en seudo cdigo, muchas veces se usa el smbolo , que parece
mucho ms apropiado y evita confusiones con el smbolo =.

Otra forma de colocar datos en las respectivas variables es mediante la


lectura de datos, por ejemplo, si a est declarado como real, la instruccin
readln(a) que veremos en la seccin 3.3 lee el nmero que se ingresa
por terminal y lo guarda en la variable a.
Desde ya que

nunca hay que usar el valor de una variable sin antes haber hecho una asignacin a la variable!

aunque hay compiladores que automticamente ponen algn valor al hacer las
declaraciones.
Para tratar de entender esta jerigonza, pasemos a estudiar algunos ejemplos
comenzando con los tipos numricos integer y real, para luego considerar los
tipos boolean y char.

3.2. Tipos numricos: entero y real


La vida sera un tanto aburrida si slo pudiramos cambiar valores de lugar, es ms divertida si podemos realizar operaciones entre estas variables. As,
suponiendo que las variables a, b y c son del tipo adecuado, una instruccin
como a := b + c hace que la CPU tome los datos que estn en las cajas o
variables b y c, las sume, y coloque el resultado en la caja o variable a.
Problema 3.2. Compilar y ejecutar el programa sumardos (pg. 144) analizando la sintaxis y qu hacen las distintas instrucciones. Por ejemplo:

Pg. 13

Pg. 14

Tipos de datos elementales


a) Cuntos comentarios tiene el programa fuente?, qu pasa si se los elimina?
- Nosotros pondremos siempre un primer comentario en el programa
fuente para indicar qu hace el programa. A su vez, al ejecutar el
programa trataremos de imprimir un cartel similar para que el usuario
sepa qu es lo que se pretende hacer. Usamos tambin esta filosofa
para imprimir un cartel final, indicando que el programa ha terminado
de ejecutarse.

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, separando los identificadores con , (coma).
Ver qu ocurre si se cambia var a, b: integer; por
i) var a; b: integer;
ii) var a: integer; var b: integer;
iii) var a: integer; b: integer;
c) Qu pasa si cambiamos el nombre de a por sumardos?
- El resultado puede depender del compilador. En general no es aconsejable 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, an cuando el compilador lo acepte.

d) Cules son las instrucciones en el cuerpo principal?


e) Observar el uso de la asignacin :=, 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 rengln, a diferencia de
writeln. En cualquier caso, los argumentos (si hay ms de uno) estn separados por , (coma).
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 rengln
writeln(La suma de , a, y , b, es , a + b);
Hay alguna diferencia en la salida?
g) La ltima lnea, writeln; writeln(** Fin **) es slo para avisar
al usuario que el programa ha terminado, y el programa ya no realizar
acciones. Eliminarla y verificar el comportamiento del programa (recordando
la nota al principio del problema 2.2).
h) Cambiar las sentencias de modo de obtener la suma de nmeros reales, i.e.,
poner real en vez de integer en la declaracin de variables. Compilar
y ejecutar el programa, observando los cambios en la salida.
i) Muchas veces uno se confunde al ingresar nmeros reales muy grande o muy
chicos, como 1 000 000 000 o .000 000 111 111. En estos casos usar la notacin cientfica puede ser til, escribiendo 109 o 1.11111 107 . En Pascal
podemos escribirlos como 10e9 y -1.11111e-7, respectivamente.
Modificar el programa poniendo valores reales de esta forma.
Qu pasa si se suman los dos nmeros anteriores?

3.3. Readln
j) Agregar una variable c, de tipo entero o real segn corresponda, hacer la
asignacin c := a + b e imprimir c en vez de a + b.
k) Modificarlo de modo de escribir tambin la resta (-), producto (*) y
divisin (div para enteros y / para reales), tanto en el caso de enteros
como de reales.
l) Qu pasa cuando se divide por 0?
$
Conceptualmente hay que distinguir en Pascal entre la variable, el identificador y el valor, i.e., entre la caja, su nombre y lo que contiene.
A fin de no ser demasiado latosos, es comn tanto en Pascal como en matemticas o la vida real, no distinguir entre identificador, valor y variable, uso
que nosotros seguiremos salvo cuando lleve a confusin: cuando decimos Jos
es simptico, en general queremos decir la persona cuyo nombre es Jos tiene
la cualidad de ser simptica, y no que el nombre en s es simptico, en cuyo
caso diramos el nombre Jos es simptico.
En Pascal no est predeterminado cmo se escribirn los enteros o reales a la
salida, y distintos compiladores ponen sus propios formatos, i.e., cuntos lugares
ocupa cada nmero escrito. Pero contamos con la opcin de especificar nosotros
mismos la cantidad de lugares, modificando lo establecido por el compilador.
Para usar esta opcin,(1) ponemos writeln(a:5) si queremos que el entero
a se escriba en exactamente 5 lugares (contando el eventual signo ). Si el
entero ocupara menos lugares, se ponen espacios en blanco a la izquierda. Si por
casualidad se necesitaran ms lugares, entonces se usarn los espacios necesarios,
de modo de no perder dgitos.
Para reales la cosa es parecida, pero algo distinta, ya que tenemos la parte
decimal o fraccionaria (la que viene despus de la coma decimal que para
nosotros es un punto). Si a es un nmero real, poniendo writeln(a:10) har
que a se escriba con exactamente 10 lugares, eventualmente poniendo espacios
a la izquierda si sobran lugares, u ocupando ms lugares si fuera necesario,
usando la notacin cientfica con exponentes y punto flotante. Pero si ponemos
writeln(a:10:5), entonces indicamos que queremos la notacin con punto fijo
en la que la parte decimal ocupa 5 lugares.
En realidad, las reglas son algo ms complejas, pero lo mejor es probar un
poco:
Problema 3.3 (Formatos de salida para nmeros). Modificar las instrucciones write y writeln del programa sumardos, experimentando con los
formatos de salida tanto con enteros como con reales.
$

3.3. Readln
Es un poco tedioso estar cambiando los valores en el programa fuente para
hacer los clculos. Mucho ms razonable es ingresar los datos a nuestro antojo:
Problema 3.4. El programa leerentero (pg. 144) 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 (lo que
est entre las comillas , pero sin las comillas) seguidos de <retorno>,
conservando los espacios intermedios cuando corresponda:
(1)

O sea, no es obligatorio usarla, pero resulta conveniente muchas veces.

Pg. 15

Pg. 16

Tipos de datos elementales


i) 23;
ii) 2 ;
iii) 2 3;
iv) 2a;
v) a2;
vi) <retorno><retorno>2.
c) Si readln no tiene argumentos, se lee el rengln entrado lo escrito hasta
<retorno> y lo escrito no se guarda (como la instruccin en la nota al
principio del problema 2.2, en la pgina 8).
Agregar el rengln
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 leerentero, hacer un programa para
ingresar por terminal dos nmeros enteros e imprimir su suma.
- Adems de readln existe la funcin read, pero postergaremos su uso para ms adelante (seccin 4.7). Basta decir por ahora que la instruccin
readln(a) es equivalente a las instrucciones read(a); readln.
As como con writeln y write podemos escribir ms de un argumento,
con readln (y read) podemos leer varios argumentos a la vez, pero al
menos por el momento no usaremos esta facilidad para evitar confusiones.
Los comportamientos de write y writeln para leer, y de read y readln
son prcticamente simtricos, aunque existen algunas diferencias importantes. Por ejemplo, en el tratamiento de los <retorno> como hemos visto en
el inciso b): readln(a) los ignora mientras no aparezca el dato a, pero
writeln(a) escribe un nico rengln por vez.
$

Cuando se programa profesionalmente, es muy importante que el programa


funcione an cuando los datos ingresados sean errneos, por ejemplo si se ingresa una letra en vez de un nmero, o el nmero 0 como divisor de un cociente.
Posiblemente se dedique ms tiempo a esta fase, y a la interfase entre la computadora y el usuario, que a hacer un programa que funcione cuando las entradas
son correctas.
Nosotros supondremos que el usuario siempre ingresa datos apropiados, y
no haremos (salvo excepcionalmente) deteccin de errores. Tampoco nos preocuparemos por ofrecer una interfase estticamente agradable. En cambio:

Siempre trataremos de dejar claro mediante carteles


qu hace el programa, qu datos han de ingresarse
en cada momento y dar una seal de finalizacin.

3.4. Funciones numricas


Pero volvamos a los tipos numricos.
No slo podemos sumar o multiplicar nmeros sino que, al igual que en las
calculadoras, hay ya funciones predeterminadas como la raz cuadrada, que en
Pascal se indica por sqrt.
- Una lista de las funciones predefinidas en Pascal est en el seccin B.1
(pg. 166).

3.5. La codificacin de enteros y reales


Problema 3.5. El programa raiz (pg. 145) calcula la raz cuadrada de un
nmero no negativo entrado por terminal.
a) Compilar y ejecutar el programa, analizando qu hacen las distintas instrucciones. Probar el programa ingresando nmeros reales, enteros, positivos y
negativos.

b) A fin de escribir en pantalla x, no es necesario hacer la asignacin previa


y := sqrt(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 declaracin).
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 nmero en vez de su raz cuadrada. Cambiar sqrt por sqr y verificar el
$
comportamiento del programa.
Problema 3.6. El programa segundos (pg. 145) permite pasar de segundos a
horas, minutos y segundos, usando la funcin mod: a mod b da esencialmente
el resto de la divisin de a por b.
- El nmero de segundos a ingresar no debe muy grande (no debe superar
maxint que definimos luego).

a) Agregarle instrucciones de modo de poder verificar si el resultado es correcto,


pasando de horas, minutos y segundos a segundos.
b) Qu pasa si se coloca el rengln writeln(hs, hs, ,... inmediatamente despus del rengln writeln(segs, segundos... y antes de
$
mins := segs div...?
Problema 3.7.
a) Hacer un programa para averiguar el comportamiento de mod, tomando
como entradas a = 7 y b = 3, calculando a mod b.
- Las definicin de est en la seccin C.3.

b) Comprobar si el valor de (a div b) * b + (a mod b) coincide con el


de a.
- El comportamiento de div y mod en Pascal es un tanto complicado cuando
alguno de los nmeros es negativo. Por ejemplo, segn el estndar a mod
b da error cuando b 0. Cuando b > 0, sin embargo, a mod b da el resto
de la divisin por b, entre 0 y b 1.
$

3.5. La codificacin de enteros y reales


En Pascal, los nmeros ocupan un nmero fijo de bits, por ejemplo 16 bits
para el tipo integer y 32 bits para el tipo real,(2) por lo que
(2) La cantidad de bits en cada caso depende del compilador. Incluso ambos tipos podran
tener asignados la misma cantidad de bits.

Pg. 17

Pg. 18

Tipos de datos elementales

En la mquina slo pueden representarse un nmero finito de nmeros, ya sean enteros o reales. En
otras palabras, la gran mayora de los nmeros no se
pueden representar exactamente en la computadora.

As, hay un mximo entero representable, que en Pascal se llama maxint,


y hay un menor nmero real positivo representable que llamaremos mn y del
cual hablaremos en el problema 4.17.
Problema 3.8.
a) Suponiendo que el tipo integer tenga asignados 16 bits, cuntos nmeros
enteros pueden representarse?
b) Anlogamente, si el tipo real tiene asignados 32 bits, cuntos nmeros
reales pueden representarse a lo sumo en la mquina?
- El resultado no estar del todo bien, dada la representacin interna
de los reales en la mquina con mantisa y exponente (que veremos
a continuacin): un mismo nmero puede representarse con distintas
mantisas y exponentes, e.g., 1 = 1 100 = 0.01 102 .
$

Problema 3.9. Hacer un programa para imprimir el valor de maxint correspondiente al compilador que se usa: no declarar variables y poner el rengln
writeln( El entero mximo es: , maxint)
- En los compiladores ms viejos, maxint = 32767, pero en compiladores ms
recientes puede ser maxint = 2147483647.
$

Sea que las variables de tipo integer y real ocupen el mismo lugar o no,
su codificacin como cadenas de bits es bien distinta. Los enteros se representan
consecutivamente (del menor al mayor), mientras que para representar los nmeros reales se dividen los bits en dos grupos, uno representando la mantisa y
otro el exponente, como se hace en la notacin cientfica al escribir 0.123 1045
(0.123 es la mantisa y 45 el exponente en base 10, pero la computadora trabaja
en base 2).
La representacin de nmeros reales mediante mantisa y exponente hace que
a diferencia de lo que sucede con los nmeros enteros la distancia entre un
nmero real que se puede representar y el prximo vaya aumentando a medida
que sus valores absolutos aumentan. Esta propiedad de densidad variable trae
inconvenientes para el clculo aproximado, como veremos en el problema 4.17
y en la seccin 5.1.
- Para entender la densidad variable, puede pensarse que hay la misma
cantidad de nmeros representados entre 1 (inclusive) y 2 (exclusive) que
entre 2 y 4, o que entre 4 y 8, etc. Por ejemplo, si hubieran slo 4 puntos en
cada uno de estos intervalos, tendramos un grfico como el de la figura 3.3.
Por el contrario, hay tantos nmeros enteros representados entre 10
(inclusive) y 20 (exclusive), como entre 20 y 30, etc. Es decir, entre 20 y 40
hay el doble de nmeros enteros representados que entre 10 y 20. En este
caso, la densidad es constante.

Como todo nmero entero es en particular un nmero real, es conveniente


poder pasar de la representacin como tipo integer a la representacin como
tipo real. Esto se hace automticamente en Pascal: si a es de tipo integer y
x es de tipo real, la asignacin

3.5. La codificacin de enteros y reales

1 2

Pg. 19

16

Figura 3.3: Esquema de la densidad variable en la codificacin de nmeros reales.


x := a
hace que automticamente se guarde el valor que tiene a (un entero) en x , de
modo que ahora el valor se codifica como real.
- En general, la cantidad de bits (y los valores de stos) usados en la representaciones como integer o real son distintas.
- Tambin la conversin se hace automticamente al ingresar datos: si x es
de tipo real, y tenemos la instruccin readln(x), el ingreso de 1 como
dato un entero hace que se guarde codificado como real en x . Ver
problema 3.2.h).

Problema 3.10. Compilar y ejecutar el programa enteroareal (pg. 145). Ob$


servar la declaracin de variables de distinto tipo en el mismo programa.
Problema 3.11. Decir por qu las siguientes declaraciones de variables son
incorrectas:
i)
ii)
iii)
iv)

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


var a, b, c: integer; 2c, d, e: real;
var: a, b, c: integer; d, e: real;
var a, b, c: integer; var d, e: real;
- Es posible que el compilador no de error en la ltima declaracin.
Sin embargo, debera haber una nica declaracin var.
$

As como escribimos un entero como real, es posible que queramos pasar de


un real a un entero:
Problema 3.12. Modificar el programa enteroareal de modo de leer x , hacer
la asignacin a := x e imprimir a: cul es el resultado?
$
Como puede observarse, no es posible cambiar de real a entero directamente:
debe eliminarse primero la parte fraccionaria del real, lo que tradicionalmente
se hace de dos formas distintas.
Una es truncar el real, eliminando la parte decimal. As, cuando truncamos:
1.1 1, 1.9 1, 1.9 1.
Otra forma es redondear el nmero real reemplazndolo por el entero ms
cercano: 1.1 1, 1.9 2, 1.9 2. Claro que en el redondeo tenemos
que decidir qu hacer con nmeros como 0.5, y en este caso en Pascal se usa
la convencin de redondear hacia arriba para positivos y hacia abajo para
negativos: 0.5 1, 0.5 1.
Problema 3.13. Las funciones de Pascal trunc, correspondiente a truncar, y
round, correspondiente a redondear, cuando aplicadas a un nmero real en la
forma trunc(x) o round(x) dan nmeros enteros.
a) Hacer un programa para averiguar el comportamiento de estas funciones,
tomando las 8 entradas 3, 3.1, 3.5, 3.7.
b) Qu pasa si la entrada es 1010 (ingresado como 10e10)?
c) Qu relacin hay entre estas funciones y las funciones piso, indicada por
bxc, y techo, indicada por dxe?, es decir, cmo pueden escribirse unas en
trminos de las otras y viceversa?

Pg. 20

Tipos de datos elementales

- Las definiciones de piso y techo estn en la seccin C.3.

En el prximo problema abundamos sobre las asignaciones entre variables


numricas de distinto tipo:
Problema 3.14 (Asignaciones entre distintos tipos). Supongamos que
hemos declarado
var n: integer; x: real;
En los siguientes, decir cul es el valor de la ltima variable asignada o si se
produce un error (sugerencia: hacer un programa, compilarlo y ejecutarlo):
a)
b)
c)
d)
e)
f)
g)
h)
i)
j)
k)
l)

x
x
x
x
x
x
x
x
x
n
n
n

:=
:=
:=
:=
:=
:=
:=
:=
:=
:=
:=
:=

3.7; n
3.7; n
3.5; n
3.7; n
3.5; n
3.7; n
3.5; n
3.7; n
3.5; n
maxint
maxint
maxint

:= x;
:= trunc(x);
:= trunc(x);
:= trunc(-x);
:= trunc(-x);
:= round(x);
:= round(x);
:= round(-x);
:= round(-x);
div 2; n := n * (n+1) / 2;
div 2; n := n * (n+1) div 2;
div 2; x := n * (n+1) / 2;

Tambin es posible hacer operaciones mezclando variables numricas de distintos tipos:


Problema 3.15. Hacer un programa donde se declaren a de tipo entero y b de
tipo real, se lean a y b, y se escriban en pantalla los resultados de: a + b, a b,
a/b, b/a. De qu tipo son los resultados en cada caso? Sugerencia: modificar
la declaracin de variables en el programa sumardos.
$

De aqu en ms supondremos que compilars y ejecutars cada programa mencionado en los problemas,
probndolo con distintos datos. En lo sucesivo, generalmente omitiremos esta consigna.

3.6. Variables lgicas


Estamos acostumbrados a valores numricos, y no es sorpresa que por ejemplo las variables del tipo integer o entero, admitan valores como 1 o 987.
Sin embargo, cuesta acostumbrarse a las variables de tipo lgico o boolean, que
slo admiten valores verdadero o true, y falso o false. Antes de trabajar
con estas variables en programacin, practiquemos un poco:
Problema 3.16. En cada caso, decidir si la expresin es verdadera o falsa:

3.6. Variables lgicas


i)
iii)
v)
vii)
ix)

Pg. 21

1 = 2.
1 2.
1 < 2 < 3.
1 < 2 o 2 < 0.
{a, b, c} = {b, a, c}.

ii)
iv)
vi)
viii)
x)

1 > 2.
4 5 > 43 .
1 < 2 < 0.
1 = cos 1.
{0, 1, 2, 3} N.

Habiendo estirado un poco los msculos, tiene sentido preguntarle a la


computadora si un nmero es positivo o no, y que guarde el resultado en una
variable lgica:
Problema 3.17. Compilar y ejecutar el programa positivo (pg. 146), analizando qu hacen las distintas instrucciones.
a) Agregar una variable pos, declarada como lgica (boolean), incluir en el
cuerpo del programa la asignacin pos := a > 0 e imprimir pos, en vez
de a > 0.
b) As como podemos poner distintos formatos en la salida para nmeros, si a es
una variable lgica podemos usar writeln(a:1) para indicar que queremos
imprimirla usando un nico lugar. Como en el problema 3.3, lo mejor es
probar: cambiar writeln(...,p) a writeln(...,p:1) y ver qu pasa.
Probar con otros valores (en vez de 1).
$
Debemos destacar que al comparar nmeros, Pascal usa a veces una sintaxis
ligeramente diferente a la usual:
Matemticas
=
6=
>

<

Pascal
=
<>
>
>=
<
<=

debiendo tener cuidado en no confundir :=, que indica asignacin, con =,


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 proposicin lgica con valor falso,
y no se modifica el valor de a.
As como para nmeros tenemos la suma, el producto o el inverso aditivo,
para variables lgicas tenemos la conjuncin y, a veces simbolizada con , la
disyuncin o, simbolizada con , y la negacin no, simbolizada con . En Pascal
usamos respectivamente and, or y not.
Por ejemplo si a y b son de tipo entero, pondramos
Matemticas

Pascal

(a > 0) (b > 0)
(a > 0) (b > 0)
(a > 0)

(a > 0) and (b > 0)


(a > 0) or (b > 0)
not (a > 0)

pero la expresin matemtica a < b < c debe escribirse en Pascal como (a <
b) and (b < c). Tambin 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.

Pg. 22

Tipos de datos elementales


Problema 3.18. 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:
i) a < b,
ii) 0 < a y 0 < b,
iii) 0 < a o 0 < b.
b) Una de las leyes de De Morgan establece que si p y q son proposiciones
(lgicas), entonces
(p q) = (p) (q).
Modificar el programa de modo de verificar esta ley cuando p = (a > 0)
y q = (b > 0), i.e., evaluar las expresiones a cada lado en la ley de De
$
Morgan y compararlas.

3.7. Caracteres
As como la computadora guarda internamente los nmeros como ristras de
ceros y unos en la computadora, tambin las letras se guardan como ristras de
ceros y unos. La forma en que se hace esta codificacin depende del sistema
operativo, pero un cdigo particularmente usado es el ASCII con el que se convierten a nmeros entre 0 y 127 algunos caracteres: letras como a , b ,..., z
o A ,..., Z , nmeros (dgitos) como 0,1,...,9, y smbolos como + , $ , etc.
En ASCII, los caracteres imprimibles como letras y smbolos empiezan a partir de 32, pero no todas las letras estn representadas, como la o las vocales
acentuadas del castellano. A fin de codificar tambin estos caracteres, hay extensiones para cubrir caracteres con nmeros hasta 255, pero estas extensiones
no son del estndar ASCII y dependen en general del sistema operativo.
En Pascal, dado un carcter podemos encontrar su nmero de orden mediante ord y recprocamente, dado un entero podemos ver qu carcter le corresponde mediante la funcin chr.(3) El estndar Pascal establece que:
Los caracteres 0 , 1 ,..., 9 estn numricamente ordenados y son consecutivos, pero
las letras minsculas, a , b ,..., z , si bien estn ordenadas, no son necesariamente consecutivas!
y lo mismo para las letras maysculas.
Afortunadamente, el cdigo ASCII con el que trabajaremos satisface
estos requisitos, y ms an, las letras minsculas a ,..., z son consecutivas y
lo mismo para las maysculas.
Desafortunadamente, si bien para nosotros est entre n y o , esto no
es as en el cdigo ASCII, pues la letra 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 alfabticamente.
Si bien los digrafos ch ( che) y ll ( elle) son consideradas como letras
en el abecedario espaol desde 1803 (la cuarta y decimocuarta, respectivamente), y por lo tanto son indivisibles, en 1994 el dcimo congreso de la Asociacin
de Academias de la Lengua Espaola acord que para su alfabetizacin se consideren como letras separadas (c-h y l-l, respectivamente).
- Como ya mencionamos, no vamos a usar ch, ll, ni tildes en los datos que
ingresemos a nuestros programas.
(3)

No confundir char con chr!

3.8. Comentarios Bibliogrficos


Problema 3.19 (Ordinales y caracteres). El programa caracteres1 (pg.
146) toma un carcter como entrada, retorna su nmero de orden y verifica la
correccin.
a) Qu pasa si cambiamos el nombre del programa de caracteres1 a
char ?, y a char1 ?
- Recordar lo dicho sobre identificadores al principio del captulo y en el
problema 3.2.c).

b) Qu pasa si se escribe como entrada tonto ?


c) Cules son los ordinales correspondientes a los caracteres a , A ,
(espacio), y tabulacin (tecla tab)?
d) Tambin podemos especificar el formato de salida de caracteres como
ya lo hicimos para nmeros y variables lgicas poniendo por ejemplo
writeln(c:5) para que se ocupen 5 espacios.
Cambiar el rengln writeln( es , chr(i)) a writeln( es ,
chr(i):7) para verificar el comportamiento.
e) Hacer un programa que, inversamente, dado un nmero retorne el carcter
correspondiente.
f ) Averiguar si hay caracteres correspondientes a nmeros mayores que 127 (el
resultado depender de la mquina). Y a nmeros mayores que 255?
g) Cul es el carcter correspondiente al nmero 7 (en ASCII)?
$

3.8. Comentarios Bibliogrficos


Los problemas 3.1 y 3.11 estn basados en similares del libro de Biggs [2].

Pg. 23

Captulo 4

Tomando control
Con las operaciones que hemos visto no podemos hacer mucho ms que lo
que hace una calculadora sencilla. Las cosas empiezan a ponerse interesantes
cuando disponemos de estructuras de control de flujo, esto es, instrucciones que
nos permiten tomar decisiones sobre si realizar o no determinadas instrucciones
o realizarlas repetidas veces.
Al disponer de estructuras de control, podremos verdaderamente comenzar a
describir algoritmos, es decir, instrucciones (no necesariamente en un lenguaje de
programacin) que nos permiten llegar a determinado resultado, y apuntar hacia
el principal objetivo de este curso: pensar en los algoritmos y cmo traducirlos
a un lenguaje de programacin.
Como es conocido, la palabra algoritmo deriva del nombre de Abu Jafar
Muhammad ibn Musa al-Khwarizmi.
Es interesante ubicar cronolgicamente a Al-Khwarizmi. Se presume que
naci alrededor de 780 en Bagdad (Irak), cuando Harun al-Rashid el califa
de Las mil y una noches comenzaba su califato, y muri en 850. Al-Mamun,
ho y sucesor de al-Rashid, continu la tradicin de su padre de patrocinar
las ciencias y fund la academia Casa del saber, a la que se incorpor AlKhwarizmi.
Al-Khwarizmi escribi y tradujo varios textos cientficos (de matemtica,
geografa, etc.) del griego al rabe. El ms importante de ellos es Hisab al-jabr
wal-muqabala, de donde surge la palabra lgebra con la que ahora designamos
a esa rama de la matemtica.
Tambin escribi un tratado sobre nmeros indo-arbigos que se ha perdido,
pero se conserv una traduccin al latn llamada Algoritmi de numero Indorum,
para indicar su autor, dando lugar a la palabra algoritmo.

Los programas irn aumentando en longitud, lo que nos obligar a encarar


de forma diferente su confeccin, debiendo pensar con ms cuidado primero en
qu queremos hacer, luego en cmo lo vamos a hacer, y finalmente pasar a los
detalles. Pascal, y casi todos los lenguajes de programacin, son poco naturales en este sentido, exigiendo por ejemplo la declaracin de variables al
principio del programa fuente, mientras que nosotros pensaremos el programa
de abajo hacia arriba (o casi), y recin al final miraremos cmo declarar las
variables necesarias.
En Pascal, las estructuras fundamentales de control son if, while, repeat
y for, que pasamos a estudiar.
- En Pascal hay otras estructuras de control, como case, que no veremos.
En otros lenguajes existen otras estructuras, pero casi siempre estn
los equivalentes de if y while, con las que se pueden simular las restantes.

4.1. If

Pg. 25

4.1. If
Supongamos que al cocinar decidimos bajar el fuego si el agua hierve, es decir
realizar cierta accin si se cumplen ciertos requisitos. Podramos esquematizar
esta decisin con la sentencia:
si el agua hierve entonces bajar el fuego.
A veces queremos realizar una accin si se cumplen ciertos requisitos y adems realizar una accin alternativa si no se cumplen. Por ejemplo, si para ir al
trabajo podemos tomar el colectivo o un taxi que es ms rpido pero ms caro que el colectivo dependiendo del tiempo que tengamos decidiramos tomar
uno u otro, que podramos 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 construccin
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 condicin (una expresin lgica), llamada condicin de control que pueda evaluarse como verdadera o falsa.
Recordando que ; separa expresiones,

Nunca debe ponerse un ; inmediatamente antes


de un else.

Problema 4.1.
a) Agregar el siguiente rengln al programa leerentero (pg. 144):
if (a > 10) then writeln(chr(7));
inmediatamente despus del rengln writeln(El entero...);.
Cul es el efecto de agregar este rengln?
b) Modificar el programa leerentero de modo que la computadora emita un
sonido cuando el nmero entrado es mayor que 0 y menor o igual a 10 (y
no haga nada en otro caso).
$
Problema 4.2. El valor absoluto para x R se define como
(
x
si x 0,
|x| =
x en otro caso.
El programa valorabsoluto (pg. 147) realiza este clculo. Observar en especial la estructura if...then...else... Qu pasa si se eliminan los parntesis despus del if ?, y si se cambia >= por > ?, y si se agrega ; antes
de else?
La funcin abs de Pascal hace el clculo del valor absoluto. Incorporarla al
programa y verificar que se obtienen los mismos resultados.
$
Problema 4.3. Hacer un programa que, dado un nmero real, encuentre su
piso y su techo.
Sugerencia: recordar el problema 3.13.
Sugerencia si la anterior no alcanza: usar algo como

Pg. 26

Tomando control

y := trunc(x); (* o round(x) *)
if (y >= x) then techo := y else techo := y + 1;
if (x >= y) then piso := y else piso := y - 1;

A veces es conveniente encadenar varios if:


Problema 4.4. El programa comparar (pg. 147) toma como datos los nmeros
enteros a y b y determina cul es mayor o si son iguales.
a) Qu pasa si se escriben las lneas que empiezan con if y terminan en ;
en un solo rengln?
b) Qu pasa si se incluyen ; al final de cada uno de los renglones originales?
c) Modificar el programa para que siempre escriba en la salida el nmero a
primero (i.e., si a = 5 y b = 3 escriba -5 es menor que 3 en vez de 3
es mayor que -5).
d) Modificarlo para que slo decida si a = b o a 6= b.
e) Modificarlo para que slo decida si a > b o a b.
$
Problema 4.5 (Comparacin de caracteres). El programa caracteres2 (en
la pg. 147) decide si un carcter entrado por terminal es una letra minscula
o una mayscula o no es una letra, mediante comparacin.
Modificarlo de modo de hacer un programa para clasificar un carcter ingresado como uno de los siguientes:
a) una letra minscula vocal,
c) una letra mayscula vocal,
e) un dgito (0, 1, . . . , 9),

b) una letra minscula consonante,


d) una letra mayscula consonante,
f ) ni letra ni dgito.

- Recordando lo dicho al principio de la seccin 3.7, no deben ingresarse


$
vocales con tildes o .

Problema 4.6. El Gobierno ha decidido establecer impuestos a las ganancias


en forma escalonada: los ciudadanos con ingresos hasta $10 000 no pagarn
impuestos; aqullos con ingresos superiores a $10 000 pero que no sobrepasen
$30 000, debern pagar 10 % de impuestos; aqullos cuyos ingresos sobrepasen
$30 000 pero no sean superiores a $50 000 debern pagar 20 % de impuestos, y
los que tengan ingresos superiores a $50 000 debern pagar 35 % de impuestos.
a) Hacer un programa para calcular el impuesto dado el monto de la ganancia.
Qu tipos de datos habr que usar?
b) Modificarlo para determinar tambin 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 ms). Sugerencia: no se pide calcular sino imprimir, y tal
vez pueda usarse algn formato apropiado para escribir reales, pero tambin
podra usarse round para el clculo.
$
Problema 4.7 (Aos bisiestos). Desarrollar un programa para decidir si un
ao dado es o no bisiesto.
Julio Csar (alrededor de 10044 a.C.) cambi el calendario egipcio, que
estaba basado en un ao de exactamente 365 das, a un nuevo calendario, el juliano, con aos bisiestos cada 4 aos aproximando mejor la verdadera longitud
del ao. Sin embargo, clculos posteriores mostraron que la longitud del ao
es aproximadamente 365.2422 das. Con el paso de los siglos, las diferencias
anuales de 0.0078 das en promedio se fueron acumulando, y hacia el ao 1582
el Papa Gregorio comenz un nuevo calendario (el gregoriano). Por un lado,

4.2. Begin-end

Pg. 27

se agregaron 10 das a la fecha, de modo que el 5 de octubre de 1582 pas a ser


el 15 de octubre (y nunca existieron los das entre el 6 y el 14 de octubre de
ese ao). Por otro lado, se decidi que los aos bisiestos seran precisamente
los divisibles por 4, excepto que aqullos que fueran divisibles por 100 seran
bisiestos slo cuando fueran divisibles por 400 (as el ao 1700 no es bisiesto
pero s el 2000). Con esta convencin, el ao promedio tiene 365.2425 das. No
todos los pases del mundo adoptaron este calendario inmediatamente. En Gran
Bretaa y lo que es ahora Estados Unidos de Norteamrica, se adopt recin
en 1752, por lo que se debieron agregar 11 das ms; Japn cambi en 1873,
Rusia en 1917 y Grecia en 1923. An hoy algunas iglesias ortodoxas mantienen
el calendario juliano, especialmente para determinar la fecha de las Pascuas.
- A veces con un pequeo esfuerzo podemos hacer el clculo ms eficiente.
Un esquema para resolver el problema anterior, si anio es el ao ingresado,
es
write(anio);
if (anio mod 400 = 0) then writeln( es bisiesto)
else if (anio mod 100 = 0) then writeln( no es bisiesto)
else if (anio mod 4 = 0) then writeln( es bisiesto)
else writeln( no es bisiesto)
Sin embargo, el esquema
write(anio);
if (anio mod 4 <>
else if (anio mod
else if (anio mod
else writeln( es

0) then writeln( no es bisiesto)


100 <> 0) then writeln( es bisiesto)
400 <> 0) then writeln( no es bisiesto)
bisiesto)

es ms eficiente, pues siendo que la mayora de los nmeros no son mltiplos


de 4, en la mayora de los casos haremos slo 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.8. Desarrollar un programa que, tomando como entrada un nmero natural entre 1 y 3000, lo escriba en romano (e.g., entrando 2999 se debe
obtener MMCMXCIX). Sugerencia: primero hacer un programa para tratar el
caso entre 1 y 30, y despus ir agregando posibilidades.
- Las letras usadas para nmeros romanos son I, V, X, L, C, D, M, correspondientes respectivamente a 1, 5, 10, 50, 100, 500, 1000.
- El problema es ciertamente tedioso para escribir, pero es tpico de muchas aplicaciones, en las que no hay atajos, o donde perdemos ms tiempo
tratando de encontrar un atajo que en escribir el programa.
$

4.2. Begin-end
A veces es conveniente y an necesario agrupar instrucciones, lo que en
Pascal se hace mediante begin...end como en la parte principal de todo
programa.
- La expresin end. (con punto) slo debe aparecer al final del programa
fuente.

Este agrupar es similar al uso de parntesis en expresiones matemticas,


( correspondiendo a begin y ) correspondiendo a end.
Como begin...end es similar a los parntesis, begin y end no son sentencias en s, por lo tanto

Pg. 28

Tomando control

No tiene sentido poner un ; inmediatamente antes


de un end.

Veamos un ejemplo donde podramos usar begin...end:


Problema 4.9. Sea f : R R la funcin definida como
(
x2 si x > 0,
f (x) =
0
si x 0.
a) Hacer un esquema del grfico de la funcin.
b) Hacer un programa para que, dando x como entrada, se imprima f (x).
c) Supongamos que queremos imprimir adems 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
f (x) = 0). Una posibilidad es, habiendo declarado a x y y como de tipo
real, poner dos if consecutivos, por ejemplo:
if (x > 0) then writeln( x, es positivo)
else writeln( x, no es positivo);
if (x > 0) then y := x*x else y := 0;
writeln(El valor de la funcion es , y);
Incluir en el programa del inciso anterior estas modificaciones.
d) Otra posibilidad, que puede parecer ms coherente, es poner un nico if,
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);
Modificar el programa del inciso b) incorporando esta variante. Qu
pasa 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.10. Supongamos que hemos declarado
var a, b, n, z: integer;
y que tenemos el rengln
if (n > 0) then if (a > b) then z := a else z := b;
a) Qu tiene de confuso este rengln?
b) Decir cul es el valor de z inmediatamente despus de ejecutar el rengln
original cuando los valores de a, b, n y z son:
i) a = 1, b = 2, n = 1, z = 0;
ii) a = 1, b = 2, n = 1, z = 0;

4.3. While

Pg. 29

iii) a = 2, b = 1, n = 1, z = 0;
iv) a = 2, b = 1, n = 1, z = 0;
c) Decir si el rengln es equivalente a:
i) if (n > 0) then begin
if (a > b) then z := a else z := b end;
ii) if (n > 0) then begin
if (a > b) then z := a end else z := b;

4.3. While
La estructura while es una estructura para realizar repetidas veces una
misma tarea. Junto con repeat y for que veremos ms adelante reciben
el nombre comn de lazos o bucles para indicar esta capacidad de repeticin.
Supongamos que voy al supermercado con cierto dinero para comprar la
mayor cantidad posible de botellas de cerveza. Podra ir calculando el dinero
que me va quedando a medida que pongo botellas en el carrito: cuando no
alcance para ms botellas, ir a la caja. Una forma de poner esquemticamente
esta accin es
mientras me alcanza el dinero, poner botellas en el carrito.
En Pascal, este esquema se realiza con la construccin while...do...,
donde do reemplaza a la , en la sentencia anterior, y entre while y do
debe ponerse una expresin lgica.
Observamos desde ya que:
Si la condicin no es cierta al comienzo, nunca se realiza la accin: si el
dinero inicial no me alcanza, no pongo ninguna botella en el carrito.
En cambio, si la condicin es cierta al principio, debe modificarse con
alguna accin posterior, ya que en otro caso llegamos a un lazo infinito,
que nunca termina.
Por ejemplo, si tomamos un nmero positivo y le sumamos 1, al resultado le sumamos 1, y as sucesivamente mientras los resultados sean
positivos. O si tomamos un nmero positivo y lo dividimos por 2, luego
otra vez por 2, etc. mientras el resultado sea positivo.
- Al menos en teora. Como veremos en los problemas 4.15 y 4.17, la
mquina tiene un comportamiento propio.

Por cierto, en el ejemplo de las botellas en el supermercado podramos realizar 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 prximo problema:
Problema 4.11. El programa resto (pg. 148) calcula el resto de la divisin de
a N por b N mediante restas sucesivas.
a) Antes de compilar y ejecutar el programa, hacemos una prueba de escritorio.
Por ejemplo, si ingresamos a = 10 y b = 3, podramos hacer como se indica
en el cuadro 4.1, donde indicamos los pasos sucesivos que se van realizando
y los valores de las variables a, b y r (el ltimo valor que aparece es el
que tiene antes de ejecutarse el paso correspondiente). Podemos comprobar
entonces que los valores de a y b al terminar el programa son los valores
originales, mientras que r se modifica varias veces.

Pg. 30

Tomando control
Paso
0
1
2
3
4
5
6
7
8
9
10
11

accin
(antes de empezar)
leer a
leer b
r := a
r b: verdadero
r := r - b
r b: verdadero
r := r - b
r b: verdadero
r := r - b
r b: falso
imprimir r

sin valor
10

sin valor

sin valor

3
10
7
4
1

Cuadro 4.1: Prueba de escritorio para el problema 4.11.

- Pods hacer la prueba de escritorio como te parezca ms clara. La


presentada es slo una posibilidad.
- Las pruebas de escritorio sirven para entender el comportamiento del
programa y detectar algunos errores (pero no todos). Son tiles al
comenzar a programar, en programas ni demasiado sencillos, como los
que hemos visto hasta ahora, ni demasiado complicados como varios
de los que veremos en los captulos siguientes.
Otra forma bastante ms primitiva de entender el comportamiento de un programa y eventualmente encontrar errores, es probarlo
con distintas entradas, como hemos hecho desde el comienzo, por ejemplo en el problema 2.2 y prcticamente en todo el captulo 3.
Es conveniente que hagas las pruebas de escritorio de los restantes
problemas de este captulo y el siguiente.
- Las pruebas de escritorio a veces se llaman pruebas experimentales de
programa, para diferenciarlas de las verificaciones de programa, que
son parte de la teora de computacin ms avanzada, y mediante las
cuales se comprueba la correccin del programa.

b) Hacer una prueba de escritorio con otros valores de a y b, por ejemplo con
0 < a < b (en cuyo caso la instruccin 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.
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 para comparar el valor obtenido con el resultado de
la operacin a mod b.
f ) Vamos a modificar el programa de modo de contar el nmero de veces que
se realiza el lazo while. Para ello agregamos un contador k, declarado como
entero, que antes del lazo while se inicializa a 0 poniendo k := 0 y dentro
del lazo se incrementa en 1 con k := k + 1. Hacer estas modificaciones
(recordando usar begin...end dentro del lazo while), imprimiendo el
valor final de k antes de finalizar el programa.
g) Modificar el programa para calcular tambin el cociente de a por b, digamos
c, mediante c := a div b. Qu diferencia hay entre el cociente y el valor
obtenido en el inciso f )?, podras explicar por qu?
h) Hay algn ejemplo donde tenga sentido declarar alguna de las variables a, b

4.3. While

Pg. 31

y r como de tipo real en vez de entero? Sugerencia: hay varias posibilidades,


una es pensar en radianes y b = 2.
$
Problema 4.12. El programa tablaseno1 (pg. 148) produce una tabla del seno
para ngulos expresados en grados. Tanto el ngulo inicial, el ngulo final como
el incremento, son entrados por el usuario.
a) Observar el uso de const por constante para dar un valor aproximado
de , anterior a la declaracin de variables. Observar tambin que no se
hace una asignacin 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 (es una
constante): tratar de hacer una asignacin a pi y observar qu pasa.
- Podemos pensar que al momento de compilar, la instruccin const a
= b hace que se reemplace cada ocurrencia de a en el programa fuente
por el valor b. Como no se puede hacer una asignacin a un valor (no
tiene sentido poner 1 := 2), no se pueden hacer asignaciones a a.

b) Antes o despus de ejecutar el programa, verificar que se obtienen los mismos


resultados de una prueba de escritorio (como en el problema 4.11, ahora con
las variables inicial , final , incremento, grados y radianes).
c) Algunos compiladores Pascal tienen pre-definida la constante pi , pero no es
estndar. Verificar si el compilador usado est en esta categora comentando
el rengln donde se define pi , i.e., encerrando el rengln entre (* y *).
d) En vez de poner manualmente un valor aproximado de , es comn usar la
igualdad = 4 arctan 1.
i) Cambiar el valor 3.14159265 dado a pi en el programa original, reemplazndolo por 4 * arctan(1). En general los compiladores no aceptan
este cambio, pues el estndar Pascal no lo permite.
ii) Eliminar el rengln donde se define a pi como una constante y definir en
cambio pi como una variable de tipo real, y en el cuerpo del programa
hacer la asignacin de la igualdad anterior. Comparar con los resultados
obtenidos originalmente.
- Podra usarse otro valor similar para , como 2arccos 0 o 2arcsen 1,
pero arctan (el arco tangente) es la nica funcin trigonomtrica inversa definida en el estndar Pascal.

e) Qu pasa si el valor de incremento es cero? Y si el valor de incremento


es positivo pero el de final es menor que el de inicial ?
f ) Modificar el programa tablaseno1 para producir en cambio una tabla de
logaritmos 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 comn de los lazos (while, repeat o for) es el clculo de
sumas o productos de muchos trminos. En forma similar al contador k del
problema 4.11.f ), si el resultado se va a guardar en 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 trminos deseados modificando
resultado. Ilustramos esta tcnica en el siguiente problema:
Problema 4.13 (Suma de Gauss). Para n N consideremos la suma
sn = 1 + 2 + 3 + + n =

n
X
i=1

i,

Pg. 32

Tomando control
que, segn la frmula de Gauss, es
sn =

n (n + 1)
.
2

Segn se dice, Karl Friederich Gauss (17771855) tena 8 aos cuando el


maestro de la escuela le dio como tarea sumar los nmeros de 1 a 100 para
mantenerlo ocupado (y que no molestara). Sin embargo, hizo la suma muy
rpidamente al observar que la suma era 50 101.
Las contribuciones de Gauss van mucho ms all de esta ancdota, sus
trabajos son tan profundos y en tantas ramas de las matemticas que le valieron
el apodo de prncipe de los matemticos. Para algunos, fue el ms grande
matemtico de todos los tiempos.

El programa gauss (pg. 149) calcula sn sumando los trminos (y no usando


la frmula de Gauss).
a) Cul es el valor de n al final del lazo? Sugerencia: hacer una prueba de
escritorio.
b) Qu pasa si se ingresa n 0?
c) La variable suma est declarada como de tipo real. Si se declarara como de
tipo entero, cul sera el mximo n para el cual se podra calcular sn (i.e.,
para el cual sn maxint)?
d) Cuntas asignaciones se realizan (en trminos de n) dentro del lazo?
e) Modificar el programa de modo que a la salida exprese tambin el valor original de n (algo como La suma de 1 a 100 es 5050 cuando n = 100).
f ) Modificar el lazo while para que se sume al derecho, i.e., primero los
$
trminos ms chicos.
Problema 4.14.
a) Modificar el programa gauss para obtener, dado n N, su factorial, denotado por n! y definido como
n! = 1 2 n.
Sugerencia: recordar lo dicho antes del problema 4.13 sobre el clculo de
sumas y productos de varios trminos.
b) Hay alguna diferencia entre los resultados multiplicando al derecho (de
menor a mayor) y al revs (de mayor a menor)?
- Es posible que s cuando n es grande, debido a errores numricos.

c) Determinar cuntos ceros hay al final de la expresin decimal de 30!, y comparar con la cantidad de ceros que aparecen en el resultado del programa.
Sugerencia: contar las veces que 2 y 5 son factores de 30!.
- 30! tiene 33 cifras decimales.
$
Problema 4.15. Recordando que la suma y producto de enteros son enteros,
nos preguntamos si el siguiente programa termina o no:
program terminaono(input, output);
var i: integer;
begin
i := 1;
while (i * i <= maxint) do i := i + 1;
writeln(El i maximo es: , i);
writeln; writeln(** Fin **)
end.

4.4. Repeat

Pg. 33

Los tres problemas anteriores son para practicar el


uso de while, pero apuntan tambin a algo muy importante: en la computadora es muy distinto trabajar
con nmeros enteros a trabajar con nmeros reales.
Los enteros dan soluciones exactas pero tienen limitaciones del tamao, mientras que con los reales
no tienen tantas limitaciones en el tamao, pero las
respuestas son slo aproximadas.

4.4. Repeat
Si estando en casa queremos ir al bar caminando, podramos poner
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 condicin de control va al comienzo, mientras que en repeat va al
final. As, la principal diferencia es que la accin se realiza siempre al menos
una vez con repeat, mientras con while puede no realizarse si la condicin de
control es inicialmente falsa.
Otra diferencia, pero de carcter formal, es que para ejecutar varias instrucciones en un lazo while debemos agruparlas con begin...end, mientras que
en un lazo repeat no es necesario usar begin...end pues ya estn agrupadas
entre repeat y until.
Problema 4.16. El programa cifras (pg. 149) calcula la cantidad de cifras en
base 10 de un nmero entero, sin tener en cuenta el signo pero considerando
que el nmero 0 tiene una cifra.
a) Observar el uso del contador c, anlogo al usado en el problema 4.11.f ).
b) Para qu est el rengln if (a < 0) then b := -a else b := a;?
c) Cambiar el lazo repeat por otro while de modo que ahora se considere que
el nmero 0 tiene 0 cifras.
$
Los errores numricos al trabajar con el tipo real estn relacionados con
la representacin interna de este tipo, como ya observamos al mencionar la
densidad variable en la seccin 3.5.
En el prximo problema calculamos dos indicadores importantes de esta
propiedad: mn , el psilon mnimo, que es el menor nmero real potencia de
2 positivo que se puede representar; y mq , el psilon de mquina, que es el
menor nmero positivo potencia de 2 que sumado a 1 da mayor que 1.
Problema 4.17. El programa epsmin (pg. 150) calcula mn y mq . Debemos
destacar que en principio los resultados exhibidos son slo aproximaciones segn
la mquina a potencias de 2, puesto que en su clculo o en su impresin pueden
intervenir errores numricos.
a) Observar que mn  mq . Esto permite que en el clculo de mq podamos
multiplicar por 2, pero no para calcular mn : ver qu sucede si para calcular
mn tomamos

Pg. 34

Tomando control

eps := 1;
repeat eps := eps/2 until (eps = 0);
eps := 2 * eps;
writeln(epsmin es: , eps);
b) Qu pasa si se calcula mq mediante
eps := 1;
repeat eps := eps/2 until (1 + eps = 1);
eps := 2 * eps;
writeln(epsmaq es: , eps);
?
- Es posible que los valores sean distintos, debido a la forma de trabajo
interna de la mquina.

c) Cambiar el lazo repeat en el clculo de mn por un lazo while haciendo


las modificaciones necesarias. Cul es ms natural, repeat o while? $
Para calcular mq y mn hemos violado justamente la regla de oro:

Nunca deben compararse nmeros reales por igualdad sino por diferencias suficientemente pequeas!

- Esto est relacionado con la diferencia entre error absoluto y relativo, que
no estudiaremos en el curso. Ver tambin los comentarios despus del problema 5.11.

4.5. For
Hay veces que queremos repetir una accin un nmero fijo de veces. Por
ejemplo, al subir una escalera con 10 escalones haramos algo como
hacer 10 veces: subir un escaln.
En Pascal no existe una estructura que traduzca esta accin directamente.
En cambio, cuenta con una estructura un poco ms flexible que nos permite
imitarla: si contamos los escalones con el contador e, que variar entre 1 y
10, podramos poner el esquema
para e desde 1 a 10 hacer subir el e-simo escaln.
Este esquema se reproduce en Pascal (habiendo declarando e como de tipo
entero) con la estructura
for e := 1 to 10 do subir el e-simo escaln
que ciertamente podra cambiarse por un lazo while:
e := 1;
while (e <= 10) do begin
subir el e-simo escaln;
e := e + 1
end

4.5. For

Pg. 35

En este caso resulta ms cmodo el lazo for, no slo porque es ms conciso,


sino tambin porque evita errores como el de olvidarse incrementar e dando
lugar a un lazo infinito.
As, una de las diferencias entre while (o repeat) y for es que no aparece
explcitamente una condicin lgica. Otra de las diferencias es que, segn el
estndar 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 convencin.
En general, usaremos for cuando sabemos exactamente la cantidad de veces
que hay que pasar por el lazo, por ejemplo para calcular la potencia xn cuando
x R y n N, donde debemos multiplicar x por s mismo n veces:
Problema 4.18. El programa potencia (pg. 150) calcula la potencia n-sima
del real x.
a) Compilarlo y ejecutarlo, verificando su comportamiento.
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 mquina
por ser muy grandes o muy pequeos. 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.
- Como mencionramos, segn el estndar el valor no est definido, y
el valor obtenido depende del compilador. Por ejemplo, para un lazo
for i := 1 to 10 do..., algunos compiladores al finalizar el lazo for
ponen como valor de la variable i a 10 (el ltimo), mientras que otros
ponen el valor 11 (el siguiente). O sea, no debe usarse esta variable
despus del lazo for sin antes re-asignarla.

d) Agregar sentencias para considerar tambin el caso de n negativo. Sugerencia: ab = 1/ab , usar la funcin abs y/o condicional if.
e) Una variante de for...to...do..., que va incrementando la variable de
control usada de a 1, es usar for...downto...do..., que va disminuyndola en 1 en cada paso.
Cambiar el rengln
for i := 1 to n do pot := pot * x;
por el siguiente:
for i := n downto 1 do pot := pot * x;
y verificar que el comportamiento es el mismo. Observar que con downto
el valor inicial debe ser mayor o igual que el valor final para que se realice
algn paso.
f ) Algunos compiladores admiten la operacin x ** y, que no es del estndar Pascal, para el clculo 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.
g) Otra forma de calcular la potencia en general es usar xy = eyln x . Incorporar esta frmula al programa para comparar con los resultados anteriores.
- Es posible que difieran debido a errores numricos.
- La frmula xy = eyln x se puede usar cuando y no es entero. Por
otro lado, cuando x e y son ambos enteros, seguramente tendremos
problemas de aproximacin al usarla.
$

Muchos de los problemas que hemos visto usando while o repeat pueden
hacerse con for con modificaciones sencillas, observando que para considerar

Pg. 36

Tomando control
valores descendientes con for, se debe usar downto en vez de to. Por ejemplo,
en el programa gauss (pg. 149) el lazo
while (n > 0) do begin suma := suma + n; n := n-1 end;
podra reemplazarse por el lazo
for i := n downto 1 do suma := suma + i;
donde la variable i es de tipo entero.
Sin embargo, no siempre se puede o es sencillo usar for, en particular, si no
sabemos de antemano cuntas veces debemos realizar el lazo.
Problema 4.19.
a) Modificar los programas tablaseno1 (pg. 148) y gauss (pg. 149) cambiando
el lazo while por uno for.
b) Podra cambiarse el lazo while por uno for en el programa resto (problema 4.11)?, y el lazo repeat por uno for en el programa epsmin (problema 4.17)?
$

4.6. Ingresando muchos datos: eoln


En esta seccin veremos distintas formas de ingresar muchos datos, que es
una operacin muy comn en las aplicaciones.
Problema 4.20. Hacer un programa que usando un lazo for calcule e
imprima la suma de cierta cantidad de enteros entrados por terminal. El usuario
debe ingresar primero el nmero de datos y despus cada uno de los datos a
sumar.
$
Es molesto contar la cantidad de datos antes de entrarlos cuando hay muchos
(y es fcil cometer errores), y es mucho ms cmodo ingresarlos y dar una seal
al programa cuando la entrada de datos ha terminado.
Problema 4.21. Modificar el programa del problema 4.20 cambiando el lazo
for por uno while o repeat, en donde despus de ingresar un dato el programa
pregunta si hay ms datos para ingresar o no, y el usuario responde ingresando
s o n .
Sugerencia: usar un esquema como
suma := 0;
repeat
write(Nuevo dato? (s/n): ); readln(c);
if (c = s) then begin
write(Ingresar el nuevo dato: ); readln(x);
suma := suma + x
end
until (c = n);

Tampoco la solucin del problema anterior es del todo satisfactoria. Es tediosa cuando hay muchos datos, y por prudencia deberamos volver a preguntar por
s/no si se ingresa algo que no sea s o n , lo que complica la programacin.
- Pero volveremos a esta idea en captulos posteriores.

Otra posibilidad es usar un dato imposible como seal, por ejemplo si se


ingresa 1 cuando los datos a sumar son positivos.

4.6. Ingresando muchos datos: eoln


Problema 4.22. Haciendo esta suposicin (i.e., el usuario slo ingresa los datos
a sumar que son positivos, y la seal de fin de datos que es 1), modificar
el programa del problema 4.21, agregando una variable booleana findatos y
$
eliminando c.
Ciertamente el dato imposible 1 del problema 4.22 no es muy conveniente, ya que tendremos que modificar el programa si en algn momento queremos
usarlo para sumar datos que pueden ser negativos. Ms cmodo sera ingresar como dato imposible alguna seal que no imponga limitaciones sobre los
nmeros a entrar (salvo que sean de tipo entero o real).
Una posibilidad es poner como dato imposible un dato vaco, es decir,
nada, o ms concretamente un <retorno> sin entrada de datos, usando la funcin
de Pascal eoln.
Pero antes de describir esta funcin, ser conveniente que repases un poco
lo dicho y hecho al introducir la funcin readln en la seccin 3.3.
Cuando ingresamos un dato, ste no es ledo por el programa hasta que
ingresamos <retorno>, dndonos la oportunidad de cambiar el dato en caso de
error.
Al ingresar nosotros <retorno>, no slo estamos indicando que terminamos
de ingresar nuestros datos, sino que se emite una seal especial que llamaremos
fin de lnea.
- La seal de fin de lnea depende del sistema operativo, y puede consistir
de uno o ms caracteres.

Si hemos declarado la variable a (de algn tipo), la sentencia readln(a)


hace que el programa lea el primer dato que no sea justamente <retorno> y lo
guarde en a si el dato es correcto (si es incorrecto, puede pasar cualquier cosa:
ver el problema 3.4.b)).
Si en el programa fuente tenemos la instruccin readln, sin argumentos,
entonces se lee el rengln ingresado, y si hemos entrado otros datos antes de
<retorno>, stos son ignorados (ver el problema 3.4.c)).
Cuando ponemos readln(a) lo que hacemos efectivamente es leer la seal
de fin de lnea adems del dato a, y al poner readln (sin argumentos) lo que se
hace es leer slo la seal de fin de lnea que no se guarda en ninguna variable.
La funcin de Pascal eoln (una abreviatura de end of line o fin de lnea),
sin argumentos, devuelve un valor lgico, i.e., verdadero o falso, segn haya o
no un <retorno> o fin de lnea por leer.
Como readln, eoln se queda a la espera de ingreso de datos, que necesariamente deben incluir un <retorno>. Si se han ingresado datos antes de <retorno>,
eoln retorna falso y si no, verdadero. Pero a diferencia de readln, eoln no lee
el fin de lnea, y debemos realizar esta lectura con readln sin argumentos.
- Sin embargo, despus de evaluarse eoln, un nuevo readln con argumentos
har que se ignoren los <retorno> que hayamos introducido aunque no se
hayan ledo, como ya hemos verificado en el problema 3.4.

Problema 4.23. El programa eolnprueba (pg. 151) es una prueba del funcionamiento de eoln, en el que se usa la variable a para entender el flujo de las
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.
Cules de las instrucciones en if se ejecutaron?, qu valor ha dado
eoln y por qu?

Pg. 37

Pg. 38

Tomando control
ii) Si en vez de ingresar slo <retorno>, se ingresa <retorno> (con un
espacio antes), cul ser el valor de a que se imprime al final? Verificar
la respuesta ejecutando el programa con esa entrada.
b) Despus del rengln writeln(Ahora el valor... agregar los renglones
write(Entrar un nuevo valor de a (entero): );
readln(a);
writeln(El valor final de a es , a);
i) Ejecutar el programa e ingresar <retorno> y 3<retorno> (con un 3
antes), cules son los valores sucesivos de a?, por qu?
ii) Si en la nueva variante ingresamos 4<retorno> (con un 4 antes) y
luego 5<retorno> (con un 5 antes), cules son los valores sucesivos
$
de a?, por qu?
Problema 4.24. En el programa eco (pg. 151) se hace un eco (echo en ingls) por pantalla de cada dato ingresado: cada vez que ingresamos un dato, se
imprime.
En este programa, slo se dan instrucciones al principio de cmo ingresar los
datos, no se pone un nuevo cartel para cada dato, y se termina la entrada de
datos ingresando un <retorno> sin dato.
a) Observar el uso de eoln para detectar que se ha entrado un <retorno> sin
dato, dando por finalizada la lectura de datos.
b) Qu pasa si se ingresa como dato 1?, y 1 ?
c) Qu pasa si en el programa fuente se agrega readln; al terminar el lazo
while?
d) Modificarlo para leer nmeros reales.
e) Modificarlo para leer caracteres. Qu pasa si se ingresa abc como uno de
los datos?
f ) Modificarlo para que al final imprima la cantidad de datos ingresados. $
Problema 4.25. El programa sumardatos (pg. 152) calcula la suma de nmeros reales entrados por terminal, finalizando la entrada de datos con <retorno>
sin dato. A diferencia del programa eco (pg. 151), se pone un cartel para cada
dato.
a) Observar las diferencias entre los programas eco y sumardatos.
b) Observar el uso de eoln para detectar que se ha entrado un <retorno> sin
dato, y el uso de readln para leer el fin de lnea que ha quedado. Qu
pasa si se elimina este readln?
c) Modificar el programa para que a la salida indique tambin la cantidad de
datos ingresados.
d) Modificar el programa para que tambin indique el promedio de los datos
entrados.
- El promedio debe ser una variable de tipo real, y no entera.

Problema 4.26. Podra cambiarse el lazo repeat...until (findatos)


del programa sumardatos por
repeat
write(Entrar un dato (fin = <retorno>): );
readln(x); s := s + x
until (eoln)
?

4.7. Read

Pg. 39

Y por
while (not eoln) do begin
write(Entrar un dato (fin = <retorno>): );
readln(x); s := s + x
end
?
Explicar en cada caso, eventualmente haciendo un programa para verificar
$
que las respuestas son correctas.

4.7. Read
Cuando queremos ingresar varios nmeros por la consola, no est mal ingresar uno por rengln como hemos hecho hasta ahora. Es posible poner una
instruccin como readln(a,b,c) para leer los nmeros a, b y c, que si se ingresan en un mismo rengln deben separarse por uno o ms espacios (la mquina
no puede saber si cuando ponemos 123 queremos poner 1 y 23 o 1, 2, 3, etc.),
pero no hay mucha diferencia para el usuario entre ingresar un espacio en blanco
( ) o un <retorno>.
Ms complicado es si queremos entrar un texto con varias palabras, ya que
no sera muy razonable entrar un carcter por rengln. En estos casos viene a
nuestro auxilio la funcin read: read(a) lee la variable a, sin importar si se
ha llegado a fin de lnea o no. Como ya mencionamos, para leer el fin de lnea
debemos usar readln sin argumentos.
- Claro que no se lee nada hasta que no hayamos ingresado <retorno>,
dndonos la posibilidad de modificar la entrada si hemos cometido algn
error.
- Recordar el problema 3.4.b). Si pusiramos readln(c), leeramos c y se
ignoraran los restantes caracteres hasta el fin de lnea.

Recordemos tambin el comentario en la pgina 16, donde decamos que


readln(a) es equivalente al grupo de las dos instrucciones read(a); readln.
Veamos read en accin.
Problema 4.27. El programa palabras (pg. 152) lee renglones entrados por
terminal, y cuenta la cantidad de palabras encontradas.
Ac entendemos por rengln una serie de caracteres ingresados con un
<retorno> final, sealando el fin de datos (que no hay ms renglones) con un
<retorno> sin caracteres previos.
Para simplificar, suponemos que se ingresan slo letras (maysculas o minsculas, pero sin tildes ni virgulillas), nmeros o espacios en blanco, pero ningn
otro carcter. De este modo, las palabras se dividen con espacios o por fin de
lnea.
Por ejemplo, el rengln
pienso

en las 23 variables x1

tiene 6 palabras, los espacios no cuentan.


a) Comparar la estructura con la de los programas eco (pg. 151) y sumardatos
(pg. 152), y los problemas 4.26, 4.24 y 4.25.
b) Qu pasa si se elimina del programa el ltimo readln (el que lee el fin de
lnea del rengln vaco)?
c) Modificar el programa de modo que tambin se puedan ingresar los signos
de puntuacin punto ( . ), coma ( , ), punto y coma ( ; ), dos puntos
( : ), admiracin ( ! ) y pregunta ( ? ), para separar palabras.
$

Pg. 40

Tomando control
Problema 4.28. Modificar el programa eco (pg. 151) con las ideas del programa palabras (pg. 152), para hacer un eco de los renglones ingresados. $
Problema 4.29. Hacer un programa que dado un nmero romano, entre 1 y
3000 como en el problema 4.8, lo escriba como nmero en base 10. Sugerencia:
usar las ideas del programa palabras del problema 4.27, pero leyendo un nico
rengln.
$

4.8. Comentarios Bibliogrficos


Los comentarios histricos sobre aos bisiestos estn basados en el libro de
Rosen [10]. El problema 4.10 est tomado del libro de Wirth [12].

Captulo 5

Aplicaciones
En este captulo mostramos que se pueden hacer bastantes matemticas con
los recursos de programacin que hemos visto.

5.1. Clculo numrico elemental


Ciertamente una de las aplicaciones ms importantes de la computadora
y a la cual debe su nombre es la obtencin de resultados numricos. A veces
es sencillo obtener los resultados deseados pero otras debido a lo complicado
del problema o a los errores numricos es sumamente difcil, lo que ha dado
lugar a toda un rea de las matemticas llamada clculo numrico.

5.1.1. Mezclando nmeros grandes y pequeos


Sorprendentemente, al trabajar con nmeros de tipo real pueden pasar cosas
curiosas como:
al sumar un nmero grande a uno pequeo (en relacin) puede dar nuevamente el nmero grande,
que la diferencia sea 0 an cuando los nmeros son distintos,
o que la suma no sea conmutativa.
Por supuesto, ya hemos visto este tipo de comportamiento al calcular mq
(problema 4.17), debido fundamentalmente a la densidad variable de nmeros
de tipo real mencionada en la seccin 3.5 (pg. 18).
Problema 5.1.
a) Encontrar x de tipo real tal que, para la mquina, x = x + 1.(1)
Sugerencia: buscar una potencia de 2, usando algo como
x := 1; repeat x := 2 * x; y := x + 1 until (x = y)

b) Hay alguna relacin entre x y 1/mq ? Por ejemplo, es x mq = 1 o


parecido?
$
Problema 5.2 (Nmeros armnicos). Para n N, el n-simo nmero armnico se definen como
n

Hn = 1 +
(1)

!!!

X1
1 1
1
+ + + =
.
2 3
n
i
i=1

Pg. 42

Aplicaciones
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.13 sobre suma
de Gauss), para calcular Hn aproximadamente.
b) En este caso particular, puede demostrarse que es ms preciso hacer la
suma de atrs hacia adelante que de adelante hacia atrs. Comprobar
que para n grande difieren los resultados realizando las sumas en estas dos
formas.
c) En los cursos de anlisis o clculo matemtico se ve que a medida que n
crece, Hn crece indefinidamente como ln n.
Ver que efectivamente la diferencia Hn ln n es aproximadamente constante para valores grandes de n (esta constante es la constante de Euler
= 0.5772156649 . . .), por ejemplo calculando las diferencias para n =
100, 1 000, 10 000.
Leonhard Euler (17071783) fue uno de los ms grandes y prolficos
matemticos, y muchas de las cosas que vemos estn relacionadas con su
nombre. Hizo contribuciones fundamentales en anlisis, teora de nmeros,
teora de grafos, la topologa, etc. La constante de Euler es relativamente
una contribucin menor.
Fue tan grande la influencia de Euler en las matemticas que se unificaron notaciones que el ide, como
la de (= 3.14159 . . .), del griego perypheria o circunferencia; i (= 1) por imaginario; y e (= 2.71828 . . .), del
$
alemn einhalt o unidad.

El siguiente problema muestra algunos de los inconvenientes al restar cantidades grandes y similares.
Problema 5.3 (Problemas numricos en la ecuacin cuadrtica). Como
es sabido, la ecuacin cuadrtica
ax2 + bx + c = 0

(5.1)

donde a, b, c R son datos con a 6= 0, tiene soluciones reales si


d = b2 4ac
no es negativo, y estn dadas por

b + d
x1 =
2a

x2 =

b d
.
2a

(5.2)

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


un aviso en caso contrario, y en caso afirmativo calcule x1 y x2 usando las
ecuaciones (5.2), y tambin las cantidades ax2i + bxi + c, i = 1, 2, viendo
cun cerca estn de 0.
b) Cuando d b2 , i.e., cuando |4ac|  b2 , pueden surgir inconvenientes numricos. Por ejemplo, calcular las races usando el programa del inciso anterior,
cuando a = 1, b = 107 y c = 1, verificando si se satisface la ecuacin (5.1)
en cada caso.
c) Uno de los problemas en el ejemplo del inciso anterior surge de restar nmeros grandes que son comparables. El siguiente fragmento de programa
alivia un poco la situacin:
if (b > 0) then x1 := -(b + sqrt(d))/(2 * a)
else x1 := (sqrt(d) - b)/(2 * a);
x2 := c / (x1 * a);

5.1. Clculo numrico elemental


i) Justificar esta construccin. Sugerencia: recordar que x1 + x2 = b/a
y x1 x2 = c/a.
ii) Hacer un programa con estas modificaciones y comparar con los resultados en b).
- No es importante recordar el truco. El ejemplo est aqu para mostrar cmo el rea de clculo numrico se preocupa en dar soluciones a
las dificultades computacionales.
$

El siguiente problema muestra dificultades no slo computacionales sino tambin matemticas al considerar una sucesin que se aproxima a 1 pero que es
elevada a potencias cada vez ms grandes.
Problema 5.4. Consideremos la sucesin

1 n
an = 1 +
para n N.
n
a) Hacer un programa para generar an dado n (de qu tipos sern n y an ?),
y usarlo para varios valores de n. En base a la experiencia, podras decir
que an es creciente (i.e., si an < an+1 para todo n N) o decreciente?
b) En los cursos de clculo o anlisis matemtico se demuestra que a medida
que n aumenta, an se parece cada vez ms a e = 2.718 281 828 459 . . .
Modificar el programa del inciso anterior para ver este comportamiento,
donde el usuario entra la tolerancia deseada, e.g., = 0.001, y el programa
dice el primer valor de n para el cual |an e| < .
- Para valores de pequeos, es muy posible que n supere maxint, por
lo que hay que tener cuidado en la implementacin.
$

5.1.2. Mtodos iterativos: puntos fijos


Una de las herramientas ms poderosas en matemticas, tanto para aplicaciones tericas como prcticas en este caso gracias a la capacidad de repeticin de la computadora son los mtodos iterativos. Casi todas las funciones
matemticas (salvo las elementales) como cos, sen, ln, etc., son calculadas por
la computadora mediante estos mtodos.
Pero, qu es iterar?: repetir una serie de pasos. Por ejemplo, muchas calculadoras elementales pueden calcular la raz cuadrada del nmero que aparece en
el visor. En una calculadora con esta posibilidad, ingresando cualquier nmero
(positivo), y apretando varias veces la tecla de raz cuadrada puede observarse que rpidamente el resultado se aproxima o converge al mismo nmero,
independientemente del valor ingresado inicialmente.
Como a lo mejor no tenemos disponible la calculadora, pero s la computadora y Pascal, hagamos el trabajo:
Problema 5.5 (Punto fijo de la raz cuadrada).
a) Utilizando la construccin
y := x; for i := 1 to n do y := sqrt(y)
hacer un programa que ingresando x R+ y n N, calcule
sr
q

x (= x1/2 ).
|
{z
}
n races

Pg. 43

Pg. 44

Aplicaciones
b) Ejecutar el programa para distintos valores de x positivo, y n ms o menos
grande dependiendo de x. Qu se observa?
$
Como habrs comprobado en el problema anterior, a medida que aumentamos el nmero de iteraciones, i.e., el valor de n, nos aproximamos cada vez ms
a 1. Por supuesto que si empezamos
con x = 1, obtendremos siempre el mismo

1 como resultado, ya que 1 = 1.


En general, si tenemos una funcin 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 funcin f (x) = x.


Problema 5.6. Hay algn otro punto fijo de la raz cuadrada, adems de
$
x = 1?
2
Problema 5.7.
Repetir los problemas anteriores considerando f (x) = x en
vez de f = x. Cules son los puntos fijos?, qu pasa cuando aplicamos
$
repetidas veces f comenzando desde x = 0, 0.5, 1 o 2?

En lo que resta de la seccin trabajaremos con funciones continuas, es decir,


funciones que pueden dibujarse sin levantar el lpiz 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
definicin rigurosa de funcin continua se da en los cursos de anlisis o clculo
matemtico.
- 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) =
0
si x = 0
pues cerca de x = 0 oscila demasiado,
y hay funciones que no son continuas como
signo(x) para x R, pues pega un salto en x = 0,
la funcin de Dirichlet,
(
1 si x Q,
f (x) =
0 si x R \ Q,
una funcin imposible de visualizar.

Muchas funciones de importancia terica y prctica tienen puntos fijos con


propiedades similares a la raz cuadrada y 1. Supongamos que x0 es un punto
dado o inicial y definimos
x1 = f (x0 ), x2 = f (x1 ), . . . , xn = f (xn1 ), . . .
y supongamos que tenemos la suerte que xn se aproxima o converge a ` a medida
que n crece, i.e.
xn `
cuando n es muy grande.
Puede demostrarse entonces que, si f es continua, ` es un punto fijo de f .
Por ejemplo, supongamos que queremos encontrar x tal que cos x = x. Mirando el grfico de la figura 5.1, vemos que efectivamente hay un punto fijo de

5.1. Clculo numrico elemental

Pg. 45

y
y=x
1

0.5

y = cos x
x
0.5

1.5

Figura 5.1: Grficos de y = cos x y y = x.


f (x) = cos x, i.e., un punto x tal que f (x ) = x . Podemos apreciar en el grfico que el punto buscado est entre 0.5 y 1, y probamos la tcnica mencionada:
dado x0 R definimos
xn+1 = cos xn

para n = 0, 1, 2, . . .,

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


En la figura 5.2, vemos cmo a partir de x0 = 0, nos vamos aproximando al
punto fijo, donde los trazos horizontales van desde puntos en el grfico de f a
la diagonal y = x y los verticales vuelven al grfico de f :
y
1

0.5
x
0.5

1.5

Figura 5.2: Aproximndose al punto fijo de cos x.


Problema 5.8 (Punto fijo de f (x) = cos x). Con las notaciones anteriores
para f y xi :
a) Usando un lazo for, hacer un programa que dados x0 y n calcule xn , y
observar el comportamiento para distintos valores de x0 y n.
b) Modificar el programa para que tambin imprima cos xn y comprobar que
para n ms o menos grande se tiene xn cos xn .
c) Modificar el programa para hacer 200 iteraciones, mostrando los resultados
intermedios cada 10. Observar que despus de cierta cantidad de iteraciones,
los valores de xk no varan.
- El valor de x200 es una buena aproximacin al nico punto fijo de
f (x) = cos x, an cuando puede ser que x200 6= cos x200 (= x201 ) debido
a errores numricos. El verdadero punto fijo es 0.739 085 133 . . ..

d) Modificar el programa de modo que no se hagan ms iteraciones si


|xk+1 xk | < ,
donde > 0 es un parmetro entrado por el usuario (e.g., = 0.000 01 =
105 ), an cuando k sea menor que n.

Pg. 46

Aplicaciones
Sugerencia: cambiar el lazo for a uno while o repeat.
Sugerencia si la anterior no alcanza: si x0 es el dato inicial y n el nmero
mximo de iteraciones, usando repeat podramos poner:
k := 0; y := x0;
repeat k := k + 1; x := y; y := cos(x)
until ((k >= n) or (abs(y - x) < eps))
- Observar que la condicin |xk+1 xk | < es idntica a |f (xk )xk | < . $

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


la ecuacin f (x) x = 0. As, si nos dan la funcin g y nos piden encontrar una
raz de la ecuacin 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 raz de la ecuacin tan x = 0, y para obtener un valor
aproximado de podemos tomar g(x) = tan x, f (x) = x tan x, y usar la
tcnica anterior:
Problema 5.9.
a) Encontrar los puntos fijos de f (x) = x tan x, i.e., los x para los que
f (x) = x, en trminos 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
funcin tan, habr que usar tan = sen / cos).
c) Verificar que con 3 o 4 iteraciones se obtiene una muy buena aproximacin
de comenzando desde x0 = 3.
d) Sin embargo, si empezamos desde x0 = 1, nos aproximamos a 0, otro punto
fijo de f .
e) f (/2) no est definida, y es previsible encontrar problemas cerca de este
punto. Como /2 1.5708, hacer una tabla de los valores obtenidos despus
de 10 iteraciones, comenzando desde los puntos 1.5, 1.51, . . . , 1.6 (desde 1.5
hasta 1.6 en incrementos de 0.01) para verificar el comportamiento.
f ) Si en vez de usar f (x) = x tan x usramos f (x) = x + tan x, los resultados
del inciso a) no varan. Hacer los incisos b) y c) con esta variante y verificar
si se obtienen resultados similares.
$
Cuando realizamos un lazo while o repeat para obtener una solucin aproximada como en el caso de las iteraciones de punto fijo es tradicional
considerar tres criterios de parada, saliendo del lazo cuando se cumple algunas
de las siguientes condiciones:
la diferencia en x es suficientemente pequea, i.e., |xn+1 xn | < x ,
la diferencia en y es suficientemente pequea, i.e., |yn+1 yn | < y (siendo
yk = f (xk )),
se ha llegado a un nmero mximo de iteraciones, i.e., n = nmx ,
donde x , y y nmx son datos, ya sea ingresados por el usuario o determinados
en el programa.
En el caso de iteraciones de punto fijo, estos criterios pueden ser bien diferentes.

5.1.3. El mtodo babilnico


La tcnica de punto fijo para encontrar races de ecuaciones no surgi con
las computadoras. Por ejemplo, el mtodo babilnico que estudiaremos en esta

5.1. Clculo numrico elemental

Pg. 47

seccin es una versin actualizada de una tcnica usada por los babilonios hace
miles de aos para aproximar a la raz cuadrada.
El mtodo resulta ser un caso particular de otro desarrollado por Newton
para funciones mucho ms generales. La idea de Newton es que si x es una raz
de la funcin derivable f , y x es un punto prximo a x , tendremos
f (x ) = 0 f (x) + f 0 (x) (x x),
y despejando x , suponiendo f 0 (x) 6= 0, queda
x x

f (x)
.
f 0 (x)

Esto establece un mtodo iterativo de punto fijo para la funcin g(x) =


x f (x)/f 0 (x): tomando x0 ms o menos cerca de la raz x , ponemos
xn+1 = xn

f (xn )
f 0 (xn )

para n = 0, 1, . . . ,

(5.3)

siempre que f 0 no se anule en los puntos xn . Este es el llamado mtodo de Newton


que se estudia en los cursos de clculo numrico. Variantes de este mtodo son
usados por las computadoras y calculadoras para calcular funciones como el
seno o el logaritmo o... la raz cuadrada!
Problema 5.10. Aplicar el mtodo de Newton para encontrar las iteraciones
en (5.3) en el caso f (x) = x2 a, donde a R+ es una constante dada.
- Para quien no sepa derivadas: si f (x) = x2 a, entonces f 0 (x) = 2x.

El mtodo babilnico para aproximar la raz cuadrada de a R+ , consiste


en aplicar sucesivamente las iteraciones
xn =

1
a 
xn1 +
2
xn1

para n = 1, 2, . . . ,

(5.4)

a partir de un valor inicial x0 dado (x0 > 0). Es decir xn = ga (xn1 ), donde
ga : R+ R+ est definida como
ga (x) =

1
a
x+
.
2
x

Los babilonios tomaron poder de la Mesopotamia (entre los ros Tigris y


ufrates) alrededor de 2000 a.C., desalojando a los sumerios, quienes haban
estado all desde 3500 a.C. Fueron los sumerios los que desarrollaron el sistema
sexagesimal (en base 60) que nos llega a la actualidad en la divisin de horas en
minutos y segundos, y los babilonios continuaron con el desarrollo del sistema
en base 60, llegando a un sistema que en algunos sentidos era ms avanzado
que el decimal nuestro.
Los babilonios tambin estudiaron la resolucin de ecuaciones cuadrticas
y cbicas, llegando al equivalente de la ecuacin cuadrtica (5.1) y muy posiblemente al mtodo que presentamos en esta seccin para aproximar races
cuadradas. El mtodo estara descripto en la tableta Yale, fechada entre 1800
y 1650 a.C.
Para llegar a este mtodo, los babilonios habran usado ideas geomtricas
para aproximar la media geomtrica de los nmeros x y a/x,
r
a
x ,
x

Pg. 48

Aplicaciones

que es el nmero

a buscado, por su media aritmtica (o promedio),


1
a
x+
.
2
x

Isaac Newton (16421727) es muy posterior a los babilonios. El mtodo


de Newton tambin es conocido como de Newton-Raphson, pues J. Raphson
(16481715) public este resultado unos 50 aos antes que se publicara el de
Newton.

Problema 5.11 (Mtodo babilnico). El programa babilonico (pg. 153) es


una versin para aplicar el mtodo babilnico.
a) Correr el programa para distintos valores de a, y luego modificarlo para
comparar el resultado con el valor dado por sqrt(a).
b) En el programa presentado, el valor inicial x0 , el nmero mximo de iteraciones y la tolerancia estn dados como constantes. Modificar el programa
para que estos datos puedan ser entrados interactivamente.
c) Uno de los criterios de parada usados en el programa es que la diferencia de
dos valores consecutivos en x sea suficientemente pequea. Agregar tambin un criterio para parar si la diferencia en y, |x2 a|, es suficientemente
pequea, con tolerancia eventualmente distinta a la anterior.
d) Modificar el programa para que imprima el criterio que se ha usado para
terminar. Ver cul es este criterio cuando x0 = 1, el nmero mximo de iteraciones es 10, y ambas tolerancias son 105 , para a = 0.0001, 0.01, 100, 10000.

e) Aproximar 2 es equivalente a aproximar 200, slo hay que correr en un


lugar la coma decimal en la solucin, 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 races cuadradas slo de nmeros entre 1 y 100, pero que despus el resultado escrito como salida sea el correspondiente al nmero original
(suponiendo siempre a > 0).
- En el programa original se consideran errores absolutos en vez de relativos. Estos errores se estudian en cursos de clculo numrico y estadstica, y tambin en otras ciencias como fsica o qumica.
El procedimiento de normalizacin o escalado que hicimos en este
inciso es esencial en clculo numrico: trabajar con papas y manzanas
y no con tomos y planetas, o, ms cientficamente, con magnitudes
del mismo orden.
Cuando se comparan papas con manzanas en la computadora, se
tiene en cuenta el valor de mq , pero al trabajar cerca del 0 hay que
tener en cuenta a mn (ver tambin el problema 4.17 y los comentarios
posteriores).

f ) Teniendo en cuenta las iteraciones definidas en (5.4) y eventualmente usando


el programa:
i) Qu pasa si a = 0? Y si x0 = 0?
ii) Qu pasa si x0 < 0 y a > 0?
iii) Qu pasa si a < 0 y x0 R?
- En el caso a < 0 se observa un comportamiento catico, es decir, oscila
sin ninguna ley aparente. Si convergiera a algn nmero ` R, ste
sera la raz cuadrada de un nmero negativo, lo cual es absurdo.

5.2. Nmeros enteros

Pg. 49

Tambin puede suceder que las iteraciones tengan comportamiento


cclico, por ejemplo al tomar f (x) = x3 y x0 = 1.
En fin, tambin las iteraciones pueden dispararse al infinito, por
ejemplo si tomamos f (x) = x2 y x0 > 1, o en forma oscilatoria, si
f (x) = x3 y x0 > 1.
Es decir, un mtodo iterativo puede no converger a una solucin.
Por otro lado, como hemos visto tambin en el problema 5.9, an
cuando un mtodo iterativo converja a una solucin, no siempre obtenemos el punto fijo que buscamos, salvo que empecemos con un valor
razonablemente cercano.
$

5.2. Nmeros enteros


No slo podemos obtener resultados numricos con la computadora, sino que
podemos inferir resultados tericos experimentando:
Problema 5.12.
a) Hacer un programa para calcular la suma de los primeros n nmeros impares
positivos (n N),
tn = 1 + 3 + + (2n 1) =

n
X

(2k 1).

k=1

b) Obtener el valor para distintos valores de n, y conjeturar una frmula similar


a la de Gauss en el problema 4.13.
c) Tratar de demostrar la frmula. Sugerencia: usar la frmula de Gauss o
induccin.
$

5.2.1. Mximo comn divisor y el algoritmo de Euclides


Dados a, b N, el mximo comn divisor entre a y b, indicado con mcd(a, b),
se define(2) como el mximo elemento del conjunto de divisores comunes de a y
b:
mcd(a, b) = max {d Z : d | a y d | b}.
- {d Z : d | a y d | b} no es vaco (pues contiene a 1) y est acotado superiormente (por mn {a, b}), por lo que mcd(a, b) est bien definido.

Cuando a, b Z, definimos
mcd(a, b) = mcd(|a|, |b|)
y
mcd(0, z) = mcd(z, 0) = |z|

para todo z Z, z 6= 0.

No tiene mucho sentido mcd(0, 0), pero por comodidad ponemos


mcd(0, 0) = 0.
Cuando mcd(a, b) = 1, es usual decir que los enteros a y b son primos entre
s o coprimos.
En la escuela elemental se ensea a calcular el mximo comn divisor efectuando primeramente la descomposicin en primos.
- Para nosotros, un nmero p es primo si p N, p > 1, y los nicos divisores
de p son 1 y p.
(2)

Como su nombre lo indica!

Pg. 50

Aplicaciones
Sin embargo, la factorizacin en primos es computacionalmente difcil, y en
general bastante menos eficiente que el algoritmo de Euclides, que an despus
de 2000 aos, es el ms indicado (con pocas variantes) para calcular el mximo
comn divisor.
Euclides (alrededor de 325265 a.C., aunque hay discusin sobre si se trata
de una persona o un grupo) escribi una serie de libros de enorme influencia en
las matemticas, inclusive en las actuales. En la proposicin 1 del libro VII, se
enuncia:
Dados dos nmeros distintos, y restando sucesiva y continuamente
el menor del mayor, si el nmero que queda nunca mide el anterior a
l hasta que queda una unidad, los nmeros originales sern primos
entre s.
Y en la proposicin 2,
Dados dos nmeros y no primos entre s, encontrar su mxima medida comn.
procediendo a construirlo. En lenguaje moderno, nos dice que para encontrar
el mximo comn divisor, la mxima medida comn, entre a y b, debemos
restar el menor del mayor hasta que los dos sean iguales.
- En los tiempos de Euclides se pensaba en nmeros como longitudes de
segmentos, y la multiplicacin de nmeros como un rea.
Un poco antesde Euclides, con Pitgoras y el descubrimiento de la
irracionalidad de 2, surgi el problema de la conmensurabilidad de segmentos, es decir si dados dos segmentos de longitudes a y b existe otro de
longitud c tal que a y b son mltiplos enteros
de c, i.e., c es la mxima

medida comn. Si a es irracional (como 2) y b = 1, entonces no existe


c, y el algoritmo de Euclides no termina nunca.

Problema 5.13 (Algoritmo de Euclides). El programa euclides (pg. 154)


es una versin de este algoritmo para encontrar mcd(a, b) cuando a, b Z,
cambiando las restas sucesivas por el clculo del resto mediante la funcin mod.
- En el problema 4.11 hicimos el proceso inverso: para calcular el resto hicimos restas sucesivas.

a) Teniendo en cuenta la descripcin original del algoritmo, sera correcto


cambiar el lazo principal por
(* lazo principal *)
repeat
if (a > b) then a := a - b else b := b - a
until (a = b);
?
Y por
(* lazo principal *)
repeat
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 nmero finito de pasos, por ejemplo en
no ms de |a| pasos.

5.2. Nmeros enteros

Pg. 51

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 instruccin while (b <> 0) do por while
(b > 0) do?
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) simultneamente?
g) Modificar el programa de modo que a la salida escriba tambin los valores
originales de a y b, e.g., El mximo comn 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 nmeros racionales, e.g., escribir 12/8 como 3/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.
- Atencin con los signos de p y q!

Problema 5.14. Pablito y su pap caminan juntos tomados de la mano. Pablito camina 2 metros en exactamente 5 pasos, mientras que su padre lo hace en
exactamente 3 pasos. Si empiezan a caminar juntos, cuntos metros recorrern
hasta marcar nuevamente el paso juntos?, y si el padre caminara 2 12 metros
en 3 pasos? Aclaracin: Se pregunta si habiendo en algn momento apoyado simultneamente los pies izquierdos, cuntos metros despus volvern a apoyarlos
simultneamente.
- Recordando el tema de la conmensurabilidad mencionado al introducir el
algoritmo de Euclides, no siempre el problema tiene
solucin. Por ejemplo,
si Pablito hace 1 metro cada 2 pasos y el pap 2 metros cada 2 pasos.
$

Problema 5.15. El mnimo comn mltiplo de a, b N, mcm(a, b), se define


en forma anloga al mximo comn divisor: es el menor entero del conjunto
{k N : a | k y b | k}.
a) En la escuela nos ensean que si a, b N entonces
mcm(a, b) mcd(a, b) = a b.
Escribir un programa para calcular mcm(a, b) para a, b N, usando esta
relacin.
b) Cmo podra extenderse la definicin de mcm(a, b) para a, b Z? Cul
sera el valor de mcm(0, z)?
$
Problema 5.16.
a) Qu relacin hay entre el mximo comn divisor o el mnimo comn mltiplo con el problema 5.14 (de Pablito y su pap)? Sugerencia: recordando que
el problema no siempre tiene solucin, volver a mirar la descripcin dada
por Euclides de su algoritmo pensando en nmeros cualesquiera, restando el
mayor del menor hasta llegar a una medida comn, y quizs el inciso h)
del problema 4.11 (pg. 30).
b) Hacer un programa para resolver ese problema, donde las entradas son el
nmero de pasos y la cantidad de metros recorridos tanto para Pablito como
para su pap.
- Como para la computadora todos los nmeros son racionales, el problema siempre tiene solucin.
$

Pg. 52

Aplicaciones

5.2.2. Ecuaciones diofnticas


Problema 5.17. Queremos resolver el siguiente problema:
Geri y Guille compraron botellas de vino para la reunin. Ambos
queran quedar bien y Guille gast $10 por botella, mientras que
Geri, que es ms sibarita, gast $16 por botella. Si entre los dos
gastaron $150, cuntas botellas compr cada uno?
a) Hacer un programa para resolver el problema. Sugerencia: se trata de resolver la ecuacin 10x + 16y = 150, con x, y Z, x, y 0. Por lo tanto,
0 x b 150
10 = 15c. Usando un lazo for, recorrer los valores de x posibles,
x = 0, 1, . . . , 15, buscando y Z, y 0.
- Para los valores dados, hay slo una solucin donde ambos gastan una
cantidad positiva.
- En este caso es ms eficiente recorrer los valores de y (desde 0 hasta
b 150
c = 9) pues hay menos valores a considerar.
16

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 incgnitas enteras no
negativas. El programa debe exhibir todas los pares (x, y) de soluciones o
decir que no hay soluciones posibles.
- La estrategia de resolucin es recorrer todas las posibilidades, una por una,
y por eso se llama de barrido.
Hay algoritmos mucho ms eficientes para encontrar las soluciones enteras de ax + by = c, basados en que existen valores A, B Z tales que
mcd(a, b) = Aa + Bb, y que valores apropiados de A y B pueden obtenerse mediante el algoritmo de Euclides, desarrollado en la seccin 5.2.1.
Los detalles se ven en cursos de matemtica discreta, teora elemental de
nmeros, o cursos ms especficos de programacin. Los curiosos pueden
$
consultar el libro de Knuth [8, vol. 2].
Diofanto de Alejandra (aproximadamente 200284 d.C.) fue el primero
en estudiar sistemticamente problemas de ecuaciones con soluciones enteras,
siendo autor del influyente libro Aritmtica. En su honor, las ecuaciones donde
las incgnitas son enteros se llaman diofnticas o diofantinas.
Otras ecuaciones diofnticas bien conocidas son las de la forma xn + y n =
n
z , donde las incgnitas son x, y, z N y n N est dado. Cuando n = 2, las
soluciones (x, y, z) forman una terna pitagrica, y cuando n > 2, tenemos la
clebre ecuacin de Fermat-Wiles, que no tiene soluciones.
Justamente Fermat (16011665) fue quien escribi en el margen de su copia
de la Aritmtica de Diofanto (traducida por Bachet) sobre la imposibilidad de
resolucin de la ecuacin xn + y n = z n en enteros cuando n > 2:
Encontr una demostracin verdaderamente destacable, pero el margen del libro es demasiado pequeo para contenerla.
Wiles demostr el teorema en 1994, casi 350 aos despus!

La tcnica del problema anterior puede extenderse para barrer ms de dos


nmeros:
Problema 5.18. En los partidos de rugby se consiguen tantos mediante tries
(5 tantos cada try), tries convertidos (7 tantos cada uno) y penales convertidos
(3 tantos cada uno).
Hacer un programa que ingresando la cantidad total de puntos que obtuvo
un equipo al final de un partido, determine las formas posibles de obtener ese
resultado.

5.2. Nmeros enteros

Pg. 53

Por ejemplo, si un equipo obtuvo 21 tantos, las formas posibles son:


Posibilidad
1
2
3
4

Tries
0
0
1
3

Tries convertidos
0
3
1
0

Penales
7
0
3
2

Tambin la tcnica de barrido puede usarse para ecuaciones no lineales


diofnticas:
Problema 5.19. Hacer un programa para que dado n N, determine si existen
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
$
adecuado.

5.2.3. Nmeros de Fibonacci


Problema 5.20 (Nmeros de Fibonacci). En 1202 Fibonacci propuso el
siguiente problema en su libro Liber Abaci:
Cuntos pares de conejos se producen a partir de una nica pareja
en un ao si cada mes cada par de conejos da nacimiento a un nuevo
par, el que despus del segundo mes se reproduce, y no hay muertes?
a) Resolver el problema (con lpiz y papel).
Los nmeros que aparecen en la solucin de este problema se conocen como
nmeros de Fibonacci, definidos como:
f1 = 1,

f2 = 1,

para n 3.

fn = fn1 + fn2

(5.5)

b) Hacer un programa para calcular el n-simo nmero de Fibonacci, usando


el esquema
a := 1; b := 1;
for i := 3 to n do begin c := a + b; b := a; a := c end;
(* aca a = n-esimo numero de Fibonacci *)
A veces, una variable como c en la que se guarda un resultado intermedio
se llama auxiliar o temporal.
c) La frmula de Euler-Binet establece que
fn =

(1 +

5) (1

2n 5

5)

(5.6)

- Crase o no, el miembro derecho es un nmero entero (podras decir


por qu, si no supieras el resultado?).

Incorporar al programa del inciso anterior el clculo del miembro derecho


de esta igualdad, y comparar los dos resultados.
d) Segn la frmula (5.6), fn crece exponencialmente, por lo que rpidamente
toma valores muy grandes: encontrar el mximo n tal que fn maxint.
Por lo tanto, fn debe calcularse como real y no entero (como en el caso
de n! en el problema 4.14).

Pg. 54

Aplicaciones
e) Comparar el nmero de Fibonacci fn con el redondeo (round) de
n
(1 + 5)

.
2n 5
A partir de qu n son iguales?, podras decir por qu?

Fibonacci contraccin de las palabras ho de Bonacci es un apodo


dado a Leonardo Bigollo (11801250), a quien tambin se lo llam Leonardo
de Pisa (para diferenciarlo del de Vinci).
Adems de Liber Abaci, Fibonacci public numerosos tratados sobre teora
de nmeros, geometra y la relacin entre ellos. Sin embargo, fue prcticamente ignorado durante la Edad Media, apareciendo sus resultados nuevamente
publicados (por Maurico) unos 300 aos despus.
Jacques Binet (17861856) public la frmula (5.6) para los nmeros de
Fibonacci en 1843, pero ya haba sido publicada por Leonhard Euler (1707
1783) en 1765, y an antes y en forma ms general por A. de Moivre (1667
1754) en 1730.
Mucho despus de Fibonacci, se observ que los nmeros fn aparecen en
muy diversos contextos, algunos insospechados como en la forma de las flores del girasol, y son de importancia tanto en las aplicaciones prcticas como
tericas. Por ejemplo, han sido usados para resolver problemas de confiabilidad de comunicaciones, y en variantes de rboles binarios que veremos en el
captulo 12.(3)
$

5.3. Comentarios Bibliogrficos


Las citas de los libros de Euclides estn tomadas del libro de Heath [6].

(3)

Veremos rboles binarios, pero no los de Fibonacci.

Captulo 6

Arreglos
A veces queremos tener muchas variables del mismo tipo, por ejemplo una
lista de los 10 primeros cuadrados perfectos,
1, 4, 9, 16, 25, 36, 49, 64, 81, 100.

(6.1)

Escribir un nombre para cada una de las variables ya sera engorroso, pero
qu pasa si ahora el problema requiere una lista de 100 o 1000 en vez de 10
datos? Para guardar los datos en casos como ste, es conveniente pensar una
estructura similar a la de vector, como en otras ramas de la matemtica o la
fsica, y que representamos como v = (v1 , v2 , . . . , vn ). En programacin, esta
estructura se llama arreglo (unidimensional).
Por ejemplo, podramos guardar los nmeros de (6.1) en el vector o arreglo
v = (v1 , v2 , . . . , v10 ) donde
v1 = 1,
v6 = 36,

v2 = 4,
v7 = 49,

v3 = 9,
v8 = 64,

v4 = 16,
v9 = 81,

v5 = 25,
v10 = 100.

Los nmeros debajo y a la derecha de las v, como el 5 en v5 , se llaman


subndices, o a veces simplemente ndices.

6.1. Dimensionamiento de arreglos


En presencia de un arreglo, la mquina guarda lugares consecutivos de memoria, todos del mismo tipo, a los cuales se accede mediante ndices (como 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 y de la
cantidad o dimensin, debemos hacer una declaracin apropiada en el encabezamiento del programa.
As, si quisiramos guardar en el arreglo v = (v1 , v2 , . . . , v10 ) los primeros 10
cuadrados perfectos, haramos la declaracin
v: array[1..10] of integer;
La parte 1..10 indica el rango donde se movern los ndices. A diferencia
de los vectores de matemtica o fsica, cuyos ndices siempre empiezan con 1,
en Pascal podemos hacer que los ndices tengan cualquier rango, e inclusive que
sean negativos, poniendo por ejemplo -30..20, aunque nosotros casi siempre
usaremos arreglos con ndices que empiezan en 1 o 0.
Sin embargo,

Pg. 56

Arreglos

Los ndices de los arreglos deben ser de tipo entero, y


por lo tanto los rangos posibles dependen de maxint.

Para guardar los 10 primeros cuadrados perfectos en v, habiendo declarado


v como antes, haramos las asignaciones:
v[1] := 1; v[2] := 4; v[3] := 9; v[4] := 16; v[5] := 25;
v[6] := 36; v[7] := 49; v[8] := 64; v[9] := 81; v[10] := 100;
En la figura 6.1 esquematizamos cmo se guarda el arreglo v en la memoria
de la mquina: las componentes tienen identificadores v[1], v[2], etc., y ocupan
lugares consecutivos del mismo tipo.

1
v[1]

4
9
v[2] v[3]

16
v[4]

25
36
49
64
81
100
v[5] v[6] v[7] v[8] v[9] v[10]

Figura 6.1: Esquema del arreglo v guardado en memoria.


Problema 6.1. Supongamos que en una serie de nmeros queremos encontrar
cuntos terminan en 0, cuntos en 1, etc.
a) Hacer un programa para realizar esta tarea, sin usar arreglos, donde la
lectura y fin de datos se haga en forma similar al programa sumardatos
(pg. 152). Al terminar de ingresar los datos, el programa debe imprimir
la cantidad de datos que terminaron en 0, 1, . . . , 9.
En el programa unidades (pg. 154) se hace el mismo trabajo, pero usando
arreglos.
En vez de declarar un contador para cada dgito 0, 1, . . . , 9, declaramos
el arreglo terminaen que nos provee de los 10 contadores necesarios. As,
la dimensin del arreglo terminaen es 10, y sus ndices varan entre 0 y
9. El contador para los que terminan en 0 es v[0], para los que terminan
en 1 es v[1], etc.
El uso del arreglo no slo facilita la declaracin, sino que despus es mucho
ms sencillo incrementar el contador pues no tenemos que hacer una serie
de if para determinar el contador adecuado.
Al comenzar el programa los valores del arreglo no estn definidos, y hay
que inicializarlos.
El ingreso de datos es similar al del programa sumardatos, con un lazo
while en vez de repeat (recordar el problema 4.26).
b) Por qu no se pone directamente digito := dato mod 10 en vez de
digito := abs(dato) mod 10 para determinar el ltimo dgito?
c) El arreglo terminaen est declarado de modo que sus ndices puedan
variar entre 0 y 9. Agregar el rengln
writeln(indice 10: , terminaen[10]);
antes del rengln writeln; writeln(** Fin **) y ver que los valores que se obtienen no tienen sentido (si es que el compilador no se
queja).
$

6.2. Bsqueda Lineal


Muchas veces necesitaremos un arreglo con, digamos, n elementos, pero no
conocemos n de antemano, y es muy fcil cometer errores en la programacin
porque podramos tratar de usar una componente que no existe. Por ejemplo
si declaramos a: array[1..10] of integer, sera un error poner a[0] :=
1 o a[20] := 0, como observamos al final del problema 6.1.
- Algunos compiladores pueden agregar instrucciones de modo de comprobar
que cuando se corra el programa se verifique que los ndices estn dentro
del rango permitido. Por supuesto, esto hace que la ejecucin del programa
sea ms lenta.

En estos casos tratamos de hacer la declaracin para guardar d elementos,


con d prudentemente ms grande que el rango con el que pensamos trabajar.
Claro que si hemos declarado el arreglo con d = 100 elementos y usamos slo
n = 20 lugares, estamos desperdiciando lugar.
Como el cambio de dimensin es frecuente, y sobre todo cuando hay muchos
arreglos con la misma dimensin, es usual declarar una constante, mediante
const, para indicar las dimensiones y mantenerlas en un lugar destacado del
programa. As, es conveniente hacer las declaraciones
.
.
.
const MAXN = 20;
.
.
.
var
.
.
.
a: array[1..MAXN] of integer;
.
.
.
Si queremos usar un arreglo mayor, bastar cambiar el valor de MAXN .
Veamos estas ideas en un ejemplo concreto.
Problema 6.2. El programa renglon (pg. 155) hace que un rengln ingresado por terminal se imprima nuevamente por terminal, imitando el eco que
hicimos en el programa eco (problema 4.24).
Modificar el valor de MAXC para limitar los renglones a que tengan a lo
sumo 5 caracteres, e ingresar el rengln mi mama me mima.
$

6.2. Bsqueda Lineal


Problema 6.3 (Bsqueda lineal). Una de las primeras aplicaciones de arreglos es ver si cierto elemento aparece o no en el arreglo, a veces llamado problema de bsqueda. Ms 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 .
La forma ms sencilla de hacer la bsqueda es comparar sucesivamente o
linealmente los elementos a1 , a2 , . . . , an del arreglo con x, tcnica que se llama
de bsqueda lineal.
- Podramos pensar en otros tipos de estrategias para la bsqueda, como al
azar o la binaria que veremos ms adelante.

El programa busquedalineal (pg. 156) muestra esta tcnica, donde se ingresa


un arreglo de ndatos enteros y el nmero x a buscar. Observar que:
Se leen datos como en el programa sumardatos (pg. 152), donde el fin de
la entrada de datos se seala con <retorno> sin datos. Como el nmero

Pg. 57

Pg. 58

Arreglos
de dato a ingresar es uno ms que el nmero de datos ya ingresado, se
mantiene ndatos adelantado en 1, y al terminar se lo retrocede.
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.
a) Sin embargo, al ingresar datos no se verifica si el nmero de datos ledo es
ya MAXN . Modificar el lazo de modo de que esa constante sea tenida en
cuenta (impidiendo que se entren ms de MAXN datos).
b) Manteniendo la modificacin anterior, cambiar el programa para que en vez
de ingresar un arreglo de longitud mxima 20, se pueda ingresar un arreglo
de longitud mxima 50. Sugerencia: slo habr que cambiar un nmero.
c) Observar que en la impresin del arreglo inicial se usa la funcin mod para
imprimir un mximo de 5 datos por rengln.
i) Por qu se pone el rengln
if ((ndatos mod 5) <> 0) then writeln;

ii) El 5 que indica la cantidad de datos por rengln se usa en dos lugares,
de modo que si se cambia el 5 por otro nmero, hay que acordarse de
cambiar ambos.
Definir la constante maxrenglon al principio del programa para eliminar este problema.
iii) Ahora modificar el programa de modo de imprimir 8 datos por rengln
como mximo, cambiando el programa en un nico lugar.
d) Para buscar x en el arreglo, se usa un lazo repeat. Cambiarlo por uno while
(y que el programa siga funcionando correctamente!).
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.
Sugerencia: empezar recorriendo desde atrs.
f ) Podra cambiarse la ltima parte del programa original por
(* lazo principal *)
i := ndatos;
while ((a[i] <> x) and (i > 1)) do i := i - 1;
(* resultados *)
if (x <> a[i]) then writeln(no se encontro)
else writeln(se encontro en el lugar , i:1); ?
En caso negativo, decir por qu no, y en caso afirmativo, qu ventajas
y desventajas tendra. Sugerencia: cul 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 cuntas
veces aparece x en el arreglo, y los ndices correspondientes (i.e., los i para
los cuales ai = x).
- 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
if (a[i] = x) then begin

6.3. Polinomios

Pg. 59

veces := veces + 1;
indice[veces] := i
end;

y luego imprimir el arreglo indice (entre 1 y veces si veces > 0). Observar
que en esta variante no es necesaria la variable seencontro.
$
Problema 6.4.
a) Hacer un programa que dado el arreglo a = (a1 , a2 , . . . , an ) de enteros,
encuentre el mximo. Por ejemplo, si a = (1, 3, 2, 3, 1, 0), el mximo es
3.
b) Modificarlo para que tambin encuentre el primer lugar donde aparece el
mximo. En el ejemplo anterior, el primer ndice es 2.
c) Modificarlo para que imprima todos los lugares donde aparece el mximo.
$
En el ejemplo, el mximo es 3 y aparece en los lugares 2 y 4.
Problema 6.5. Hacer un programa que dados los arreglos a = (a1 , a2 , . . . , am )
y b = (b1 , b2 , . . . , bn ) de enteros (ambos dimensionados por MAXN ), forme un
tercer arreglo c = (c1 , c2 , . . . , cm+n ) (dimensionado por 2 MAXN ), consistente
en los elementos de a seguidos por los de b.
Por ejemplo, si a = (1, 3, 5, 3) y b = (7, 1, 5, 8, 3), tendremos m = 4, n = 5, y
$
c = (1, 3, 5, 3, 7, 1, 5, 8, 3).
Problema 6.6. Hacer un programa para purgar el arreglo de enteros a ingresado, i.e., eliminar los elementos repetidos.
Por ejemplo, si inicialmente a = (3, 5, 2, 6, 2, 1, 3, 2), al final del programa
debe ser a = (3, 5, 2, 6, 1). Sugerencia: buscar cada elemento entre los ya
$
puestos para decidir si agregarlo o no.

6.3. Polinomios
Los polinomios son las funciones ms sencillas que podemos considerar, para
su clculo slo se necesitan sumas y productos. Adems los usamos diariamente,
por ejemplo un nmero en base 10 es en realidad un polinomio evaluado en 10.
Pero tambin los polinomios sirven para aproximar tanto como se desee a
casi cualquier funcin, lo que constituye un tema central de las matemticas,
y se estudia tanto en los cursos tericos de anlisis matemtico como en los
aplicados de anlisis numrico.
A modo de ejemplo visual, en la figura 6.2 mostramos cmo se podra aproximar a la funcin sen x para x cercano a 0, mediante los denominados polinomios interpoladores de Lagrange, tomando los valores del seno en los puntos
x = 0, /4, /2 y . Como se puede apreciar, cerca de x = 0 se obtiene una muy
buena aproximacin. Tratamos este ejemplo con ms detalle en el problema 6.8.
1
0.5

Figura 6.2: Aproximacin de sen x (en trazo discontinuo) mediante un polinomio


de grado 3.
Nuestra primer tarea ser evaluar un polinomio dado:

Pg. 60

Arreglos
Problema 6.7 (Evaluacin de Polinomios). Hacer un programa que dada
una lista de coeficientes (reales) de un polinomio, (a0 , a1 , a2 , . . . , an ) y un nmero
x R, entrados por terminal, evale el polinomio an xn + an1 xn1 +
+ a1 x + a0 .
Hacerlo de tres formas:
a) Calculando la suma de trminos como se indica, calculando xk como en el
problema 4.18.
b) Como el anterior, pero las potencias en la forma xk+1 = xk x, guardando
xk en cada paso.
c) Usando la regla de Horner
(( ((an x + an1 ) x + an2 ) + ) x + a1 ) x + a0 .
- En las dos primeras versiones se hacen n sumas, n productos y se calculan
n 1 potencias, que en la primera versin 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 slo n sumas y n productos.
William George Horner (17861837) fue indudablemente una persona muy
capaz: a los 18 aos era director de la escuela Kingswood (en Bristol, Inglaterra). Sin embargo, sus contribuciones matemticas no han sido demasiadas, y
conservamos el nombre de regla de Horner pues De Morgan la denomin as y
le dio amplia difusin en los muchos artculos que escribi. De todos modos, el
mtodo era conocido por Zhu Shie unos 500 aos antes.
$

Problema 6.8 (Polinomios interpoladores de Lagrange). Un polinomio


P (x) = an xn + an1 xn1 + + a1 x + a0 , de grado a lo sumo n, est determinado por los n + 1 coeficientes. Supongamos que no conocemos los coeficientes,
pero podemos conocer los valores de P (x) en algunos puntos, cuntos puntos
necesitaremos para determinar los coeficientes? Como hay n + 1 coeficientes,
es natural pensar que quedarn determinados por n + 1 ecuaciones, i.e., que
bastarn n + 1 puntos.
Una forma de resolver el problema es con el polinomio interpolador de Lagrange. Dados (xi , yi ), i = 1, . . . , n + 1, definimos:
P (x) =

n+1
X
i=1

yi

Y x xj
.
xi xj

(6.2)

j6=i

- El polinomio en general resulta de grado n, y no necesariamente n.


Pensar, por ejemplo, en 3 puntos sobre una recta: determinan un polinomio
de grado 1 y no 2.
- En clculo numrico se ven formas ms eficientes (esencialmente extensiones de la regla de Horner) para el cmputo de estos polinomios interpoladores.

a) Ver que efectivamente, P (x) definido por la ecuacin (6.2) satisface P (xi ) =
yi para i = 1, . . . , n + 1.
b) Desarrollar un procedimiento para evaluar P (x) dado por la ecuacin (6.2),
donde los datos son (xi , yi ), 1 i n + 1, y x. Aclaracin: slo se pide una
traduccin literal de la ecuacin (6.2).
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.

6.3. Polinomios

Pg. 61

d) Ampliar el programa para calcular una aproximacin de sen /4, usando los
valores del seno para 0, /6, /2 y .
- Como mencionamos en el problema 4.12, es usual poner = 4
arctan 1, aunque en este problema en particular no se necesita un valor
especfico de (considerar la funcin sen x en vez de sen x).

La figura 6.2 muestra cmo se parecen las grficas de sen x y el polinomio


de Lagrange en el intervalo [0, ].
Aunque considerado como francs, Joseph-Louis Lagrange (17361813) naci en Turn (Italia) como Giuseppe Lodovico Lagrangia.
Lagrange fue uno de los fundadores del clculo de variaciones (rea relacionada con la mecnica) y las probabilidades, e hizo numerosas contribuciones
en otras reas como astronoma y ecuaciones diferenciales.
$

Problema 6.9 (Escritura en base entera). La idea de la regla de Horner


se usa tambin en el caso de escritura en base b (entero > 1). Si n Z es
no-negativo y
n=

k
X

ai bi ,

donde ai Z y 0 ai < b,

(6.3)

i=0

entonces a0 se obtiene como resto de la divisin de n por b, por lo que b | (na0 ),


y n1 = (na0 )/b es un entero que tiene resto a1 cuando dividido por b, entonces
b | (n1 a1 ), etc.
Un esquema para encontrar los coeficientes a0 , a1 , . . . , ak de n en base b es
m := n; j := -1;
repeat
j := j + 1; a[j] := m mod b; m := m div b
until m = 0
y para encontrar n dados los coeficientes en base b (si ak es el ltimo coeficiente
no nulo)
j := k; n := a[j];
while (j > 0) do begin j := j-1; n := n * b + a[j] end
a) Implementar dos programas para que dados la base b y n, encontrar los coeficientes ai , y recprocamente, dados la base b y los coeficientes ai , calcular
n usando la regla de Horner.
b) En la ecuacin (6.3), cmo se relacionan k y logb n (si ak 6= 0)?
$

Captulo 7

Funciones y Procedimientos
Nuestros programas se han hecho cada vez ms largos, y a medida que avancemos lo sern an ms. La longitud y complejidad de los programas profesionales es tal que una sola persona no puede siquiera escribirlos completamente.
A fin de abordar esta dificultad, se han desarrollado una serie de tcnicas, y
en este captulo daremos los primeros pasos hacia una de ellas, la modularizacin,
para lo cual Pascal cuenta con dos mecanismos: funciones y procedimientos.
Aunque la ventaja de usar funciones o procedimientos ir quedando ms
clara con los ejemplos que veremos en ste y otros captulos, podemos decir que
en general son convenientes para:
poner en un nico lugar clculos idnticos que se realizan en distintas
partes del programa,
o poner por separado alguna accin permitiendo su fcil reemplazo (y con
menor posibilidad de error),
y no menos importante, haciendo el programa ms fcil de entender,
dejando una visin ms global y no tan detallada en cada parte (viendo
el bosque y no el rbol).
Seguramente recordars el uso que con el mismo espritu hemos dado a
const, por ejemplo en el problema 6.3, para hacer un nico cambio que afecta
a varias partes del programa.

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 ms
variables. Pero necesariamente no puede tener todas las funciones y slo
algunas se han incluido. Por ejemplo, la potencia xn (x R y n N) no est
incluida en Pascal, pero hemos hecho su clculo en el problema 4.18.
Supongamos que queremos hacer un programa para comparar los valores xn
m
y y , donde x, y R y n, m N son datos ingresados por el usuario. Nuestro
programa tendra los siguientes pasos:
1.
2.
3.
4.

Poner carteles iniciales.


Leer los datos x y n, y luego los datos y y m.
Calcular xn y y m .
Comparar el resultado, imprimiendo la decisin.

7.1. Funciones

Pg. 63

5. Imprimir un cartel final.


Cada una de las acciones mencionadas no es difcil. Por ejemplo para calcular
xn y y m podramos 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. Hacer un programa implementado estas ideas.

Claro que si contramos con una funcin de Pascal para calcular xn para x
R y n N, digamos potencia(x,n), podramos reemplazar los dos renglones
anteriores por
xn := potencia(x,n); ym := potencia(y,m);
Aunque Pascal no cuenta con una funcin que realice este clculo, nos da un
mecanismo para definirla nosotros.
En Pascal, las funciones (y procedimientos que veremos en un rato) se declaran en la parte declarativa(1) del programa, formando un bloque de estructura
similar a la de los programas.
Por ejemplo, para definir la funcin potencia (2) pondramos antes del cuerpo
principal del programa,
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
end;
Observamos que
Se comienza con el nombre de la funcin, anteponiendo function (o
procedure para procedimientos) en vez de program, luego una parte
de declaraciones propias, y despus un cuerpo principal encerrado entre
begin y end; (con ; en vez de . porque no es el final del programa).
Los argumentos de la funcin 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 es importante: 23 6= 32 .
Las funciones en Pascal calculan un valor de alguno de los tipos elementales boolean, char, integer o real.
- Tambin podran ser de tipo puntero, que no veremos en este curso.
Segn el estndar no deben ser de otro tipo, como arreglos, aunque
muchos compiladores lo permiten.

El tipo del valor calculado debe ser declarado, lo que se hace colocando : luego de la declaracin de argumentos. En nuestro ejemplo, el
resultado es de tipo real.
Decimos que la funcin devuelve o retorna el valor calculado.
Dentro del bloque correspondiente a la funcin, el nombre de sta debe
ser asignado, y ser el valor retornado a otras partes del programa.
(1)

Podra ser de otro modo?


Usaremos estetipodeletra para sealar que se trata de una funcin o procedimiento hecho
por nosotros.
(2)

Pg. 64

Funciones y Procedimientos

- Sin embargo, el nombre de la funcin no es el identificador de una


variable, como veremos en el problema 7.2.h).

No es importante el lugar de la asignacin dentro de la funcin, y no


tiene por qu ser la ltima instruccin en la funcin. Inclusive es posible
hacer varias asignaciones, pero al menos debe haber una que se ejecute
antes de que termine la funcin.
Adems de los argumentos y el valor a retornar, la funcin puede tener
otras variables propias. En nuestro ejemplo, 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 funcin.
Finalmente, el valor calculado por la funcin tiene que ser asignado (o
impreso) dentro de la parte principal del programa.
- O en otras funciones o procedimientos declaradas posteriormente.
Veremos ejemplos ms adelante.

En estos casos decimos que el programa llama (en ingls call) o invoca
a la funcin. Por ejemplo, con la instruccin
xn := potencia(x,n)
estamos llamando a la funcin potencia y asignando el resultado a xn.
Problema 7.2. En el programa potencias (pg. 157) hemos incorporado la
funcin potencia. Leerlo, estudiando las distintas instrucciones, y ejecutarlo.
Observemos que la variable k no est definida al comienzo del programa,
sino slo en la funcin potencia: es local a la funcin pero desconocida para
el resto del programa.
a) Incluir el rengln
writeln( El valor de k es: , k:1);
inmediatamente antes del cartel final del programa. El compilador seguramente rechazar esta accin pues k no est declarada.
b) Manteniendo el rengln problemtico, incluir una declaracin de k al principio del programa (fuera de la funcin), y hacer la asignacin k := 1 antes
del rengln del inciso anterior. Compilar y ejecutar nuevamente el programa,
observando que ahora no hay inconvenientes.
c) Repetir los pasos anteriores cambiando k por z , comprobando que z vive
slo dentro de la funcin.
Al declarar la funcin, los argumentos a y b se separan con ;, pero al
llamar la funcin se separan con ,: potencia(x,n).
d) Cambiar ; por , en la declaracin de la funcin, i.e., poner
function potencia(a: real, b: integer): real;
y comprobar que el compilador no acepta el cambio.
e) De modo similar, cambiar xn := potencia(x,n); por
xn := potencia(x;n);
y comprobar que este cambio tampoco es aceptado.
- Sin embargo, si dos o ms argumentos consecutivos son del mismo tipo,
podemos separarlos con una coma juntando las declaraciones. Para ejemplificar, si a y b son de tipo real, podemos declarar tanto
function f(a: real; b: real):...

7.1. Funciones

Pg. 65

como
function f(a, b: real):...
Es decir, el uso es similar a las , que se usan al declarar variables
con var. Veremos un ejemplo en el problema 7.7.

Las variables que son argumentos de la funcin tambin son locales a ella,
y es posible que coincidan con los nombres de otras variables del programa,
llamadas globales.
f ) Cambiar la definicin de la funcin a
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 comportamiento
es el mismo.
La localidad de los argumentos tambin se traduce en que podemos modificarlos dentro de la funcin, sin modificar otras variables con el mismo
identificador del programa.
f ) Por ejemplo, cambiar la declaracin de la funcin poniendo
function potencia(x: real; n: integer): real;
var z: real;
begin
z := 1;
while (n > 0) do begin
z := x * z; n := n - 1 end;
potencia := z
end;
El valor final de z es el mismo en uno u otro caso, pero el valor de la
variable n (local a la funcin) se va modificando con la nueva definicin.
g) Ejecutar el programa, y verificar que sin embargo, el valor de n que se
imprime al final es el mismo que el ingresado inicialmente: corresponde a la
variable global n, y no a la variable n local a la funcin.
- Si bien podemos poner cualquier identificador a los argumentos, no
deben ser los mismos que las otras variables locales a la funcin. Por
ejemplo no debemos poner
function potencia(x: real; n: integer): real;
var k: integer; x: real;
.
.
.

h) Ya hemos mencionado que el nombre de la funcin debe ser asignado antes


de terminar la funcin, 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

Pg. 66

Funciones y Procedimientos
?

end;

i) Cambiar la funcin potencia de modo de que el clculo se haga mediante la


frmula xn = exp(n ln x) (como vimos en el problema 4.18), comprobando
que se obtienen (aproximadamente) los mismos resultados.
$

7.2. El mtodo de la biseccin


Supongamos que tenemos una funcin continua f definida sobre el intervalo
[a, b] a valores reales,(3) y que f (a) y f (b) tienen distinto signo, como en la
figura 7.1. Cuando la dibujamos desde el punto (a, f (a)) hasta (b, f (b)), vemos
que en algn momento cruzamos el eje x, y all encontramos una raz de f , i.e.,
un valor de x tal que f (x) = 0.

a = a0

c0 = a1 = a2

c1 = b2

b = b0 = b1

c2

Figura 7.1: Una funcin continua con distintos signos en los extremos.
En el mtodo de la biseccin 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
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
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
se han realizado un mximo de iteraciones, condiciones que llamamos criterios
de parada.
- Recordando la filosofa de comparar papas con manzanas, el valor y a
poner depender del problema que se trate de resolver.
- Tambin en este sentido, observamos que 210 = 1 024 103 y 220 =
1 048 576 106 , por lo que el intervalo inicial se divide aproximadamente
en 1000 despus de 10 iteraciones y en 1 000 000 = 106 despus de 20 iteraciones. Es decir, despus de 10 iteraciones el intervalo mide el 0.1 % del
intervalo original, y despus de 20 iteraciones mide el 0.0001 % del intervalo
original. No tiene mucho sentido considerar mucho ms de 10 iteraciones en
este mtodo, salvo que los datos originales y la funcin f puedan calcularse
con mucha precisin.

Problema 7.3 (Mtodo de la biseccin para encontrar races). El programa biseccion (pg. 158) utiliza el mtodo de biseccin para encontrar races
en el caso particular
f (x) = x(x + 1)(x + 2)(x 4/3).
(3)

Recordar los comentarios en la pgina 44.

7.2. El mtodo de la biseccin


a) Observar la declaracin de la funcin 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. En la inicializacin, 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 iterando si la funcin toma signos distintos en los
extremos, se define la variable lgica seguir que indica si la condicin
es cierta o no.
4. En el lazo principal:
Se incrementa el contador iter , se calcula el punto medio del intervalo, 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.
Tambin paramos si ya hemos llegado al mximo de iteraciones.
Si no, calculamos el nuevo intervalo, cuidando de que los signos en
los extremos sean distintos.
Observar que slo necesitamos el signo de ypoco para determinar
el nuevo intervalo, de modo que no hemos conservado el valor de
ymucho.
5. En la salida tenemos en cuenta las distintas posibilidades por las cuales
seguir es falsa:
Si la condicin de distinto signo en los extremos no se satisfaca
inicialmente, no se han realizado iteraciones (por lo que el valor de
iter es 0), y ponemos un cartel apropiado.
En otro caso sealamos los valores obtenidos, poniendo un cartel
especial si el error no es suficientemente pequeo (y por lo tanto el
nmero de iteraciones es mximo).
6. Terminamos poniendo un cartel de Fin.
b) Dada la forma de f , en este caso conocemos exactamente las races. Bosquejar el grfico de f en el intervalo [3, 2], y dar valores iniciales de poco
y mucho para obtener aproximaciones a cada una de ellas ejecutando el
programa.
c) En caso de que haya ms de una raz en el intervalo inicial, la solucin elegida
depende de los datos iniciales. Verificar este comportamiento ejecutando
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 raz x = 1 en
una iteracin?
- En general, nunca obtendremos el valor exacto de la raz: recordar que
en la computadora slo existen racionales (y pocos!).

e) x = 0 es raz, 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 pequeos, entonces se imprima la correspondiente solucin
y no se realice el lazo de biseccin.

Pg. 67

Pg. 68

Funciones y Procedimientos
f ) Agregar tambin la impresin de carteles apropiados cuando f (poco)
f (mucho) 0 y no se est en las condiciones del inciso anterior.
g) El programa no verifica si poco < mucho, y podra suceder que poco >
mucho. Tiene esto importancia?
h) Teniendo en cuenta las notas al principio de la seccin, tendra sentido
agregar al programa un criterio de modo de parar si los extremos del intervalo estn suficientemente cerca? Si la nueva tolerancia fuera x , cuntas
iteraciones deben realizarse para alcanzarla, en trminos de x y los valores
originales de mucho y poco?
i) Modificar el programa de modo que en vez de considerar hasta un mximo
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 mtodo de la biseccin es bien general y permite encontrar las races de
muchas funciones. Al programarlo, hemos separado la declaracin de la funcin
f de modo de poder cambiarla fcilmente segn la aplicacin, sin necesidad de
recorrer todo el programa buscando las apariciones de f (habr, s, que cambiar
tambin los carteles iniciales).
Problema 7.4. Cambiar la definicin de la funcin en el programa biseccion,
para responder a los siguientes ejemplos (hacer primero un bosquejo del grfico
para estimar valores iniciales de poco y mucho):
a) Resolver aproximadamente las ecuaciones:
i) x2 5x + 2 = 0,

ii) x3 x2 2x + 2 = 0.

Resolver tambin estas ecuaciones en forma exacta y comparar con los resultados obtenidos.
- La primera ecuacin tiene 2 races y la segunda 3.

b) Encontrar una solucin aproximada de cos x = x y comparar con los resultados del problema 5.8.
c) Obtener una solucin aproximada de cada una de las ecuaciones
2 ln x = x

x3 sen x + 1 = 0.

- La primera ecuacin tiene una raz, y la segunda tiene infinitas.

Problema 7.5 (Inters sobre saldo). Consideremos el siguiente problema:


Beli quiere comprar un artculo que cuesta $100 de lista. Puede comprarlo al contado con un 10 % de descuento, o con un plan de pagos
de anticipo y 9 cuotas mensuales de $10 c/u, cul es la tasa de
inters (nominal anual) que cobra el negocio?
Resulta que hay muchas formas de calcular la tasa de inters.(4) En este
problema estudiamos el llamado inters sobre saldo, que da como respuesta a la
pregunta anterior una tasa de 29.07 %.
Supongamos que pedimos prestada una cantidad c (en $) con un tasa de
inters anual (nominal) r (en %), que pagaremos en cuotas mensuales fijas de
p (en $) comenzando al final del primer mes, y que el prstamo tiene las caracterstica de que el inters se considera sobre el saldo, esto es, si bm es el saldo
adeudado a fin del mes m, justo antes de pagar la cuota m-sima, y cm es el
(4)

Y el contador dir cunto quers que te de?

7.2. El mtodo de la biseccin

Pg. 69

saldo inmediatamente despus de pagar esa cuota, poniendo t = 1+r/(10012),


tendremos:
b1 = tc,

c1 = b1 p,

b2 = tc1 ,

c2 = b2 p, . . .

y en general
cm = bm p = t cm1 p,

(7.1)

donde inclusive podramos poner c0 = c.


a) Programar una funcin saldo(c, r, p, m) que dados el monto inicial c, la tasa
r, y el pago mensual p, calcule el saldo inmediatamente despus de pagar la
m-sima cuota, cm = saldo(c, r, p, m) para m = 1, 2, . . . Aclaracin: no se
pide encontrar una frmula, slo escribir la funcin Pascal.
b) Verificar la correccin de la funcin 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 correspondiente debe ser aproximadamente 0).
c) Considerando que c y r estn fijos, existe un valor de p de modo que el
saldo sea siempre el mismo, i.e., cm+1 = cm para m = 1, 2, . . . ?, cul?
d) Hacer un programa de modo que dados r, c y p (p mayor que el monto en el
inciso c)) calcule el nmero total de cuotas n. Aclaracin: todas las cuotas
deben ser iguales, excepto tal vez la ltima que puede ser menor.
- Observar la similitud con el programa resto del problema 4.11. Sin
embargo, no puede usarse mod.

e) Para c, r y p fijos, la funcin saldo(c, r, p, m) es decreciente con m si p es


mayor que el monto calculado en el inciso c). Cmo es el comportamiento
cuando slo vara c?, y si slo vara r?
f ) Hacer un programa que usando el mtodo de biseccin, y dados c, r y
el nmero total de cuotas n, calcule p de modo que la deuda se cancele
exactamente con n cuotas fijas, es decir cn = 0. Podramos usar poco = 0
y mucho = a?
g) El resultado anterior, p, en general no podr pagarse en pesos y centavos
(habr ms de dos decimales). Si los pagos se harn en pesos y centavos,
qu tolerancias en p pondremos? Modificar (si es necesario) el programa
anterior para incorporar esta tolerancia.
h) Beli tambin ha visto en un folleto de propaganda una cmara digital por
la que tendra que pagar 15 cuotas mensuales de $118.50 cada una (sin
anticipo, a partir del primer mes). En el folleto, en letras ms pequeas,
est anunciado que la tasa (nominal anual) es de 32.93 %, recomendando
consultar sobre ltimo precio efectivo. Qu descuento debera pedir Beli
si quiere comprar la cmara al contado (hoy)?
- En calculadoras financieras y planillas de clculo estn implementadas funciones similares. Por ejemplo, en MS Excel estn las funciones (donde p
debe ser negativo si es un pago, y r es la tasa anual):
AMORT(r,n,c): Calcula p dados n, r y c.
VALACT(r,n,p): Calcula c dados n, r y p.
TASA(n,p,c): Calcula r dados n, c y p.
NPER(r,p,c): Calcula n dados r, a y p.
As, para calcular el pago mensual si r = 8 %, n = 10 y c = $10000,
ponemos AMORT(8 %/12,10,10000) para obtener 1037.03. En el problema
original, calcularamos TASA(9,-10,80)*12 dando por resultado 29.07 %. $

Pg. 70

Funciones y Procedimientos

7.3. Procedimientos
Los procedimientos y funciones son muy parecidos entre s, y a veces se
los engloba bajo el nombre comn de rutinas.(5) De hecho, en lenguajes como
C no hay distincin entre ellos. En Pascal, la diferencia ms obvia es que los
procedimientos no tienen un resultado visible.
Pascal nos permite mezclar funciones y procedimientos, con la nica restriccin de que se deben declarar en el orden en que son usados: una funcin o
procedimiento no puede llamar a una funcin o procedimiento que no ha sido
an declarada.
- Una posibilidad intermedia es el uso de forward, que no veremos.

Ms an, como veremos en el problema 7.6.d) y en el problema 7.8.b), es posible poner una funcin o procedimiento dentro de otra funcin o procedimiento,
y aqullos sern locales a stos (desconocidos para otras partes del programa).
Esta accin se llama anidamiento de funciones o procedimientos.
Podemos pensar, recordando la figura 2.4, que al alojarse en la memoria el
programa ejecutable, hay un espacio reservado para funciones y procedimientos,
como se indica en la figura 7.2. A su vez, funciones y programas pueden repetir
el esquema si hay anidamiento.
datos
globales
funciones,
procedimientos
instrucciones
(parte
principal)

datos
locales
instrucciones
locales
datos
locales
instrucciones
locales

programa
ejecutable

Figura 7.2: Esquema de programa, funciones y procedimientos en la memoria.


Volvamos al problema 4.12 (pg. 31) donde hicimos una tabla del seno usando
el programa tablaseno1 (pg. 148). Podemos esquematizar ese programa por
medio de los siguientes pasos:
1.
2.
3.
4.

Poner carteles.
Leer los datos, en este caso inicial , final e incremento.
Hacer e imprimir la tabla.
Sealar el fin del programa.

Pascal nos permite poner cada uno de estos pasos como procedimiento, poniendo en el cuerpo principal del programa:(6)
begin
cartelesiniciales;
leerdatos;
imprimirtabla;
(5)

Aunque en algunos lenguajes, las rutinas son nuestros procedimientos!


Parece una exageracin, pero es posible ver ejemplos an ms extremos en programas
avanzados.
(6)

7.3. Procedimientos

Pg. 71

cartelfinal
end.
donde cartelesiniciales, leerdatos, imprimirtabla y cartelfinal son procedimientos
que realizarn las acciones correspondientes.
La ventaja de hacerlo es que podemos preocuparnos por cada uno de los
procedimientos y si las hubiera, funciones por separado. As, podramos
definir el procedimiento cartelesiniciales como
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 (pg. 159) hemos reescrito el programa tablaseno1, con las modificaciones mencionadas.
a) Comparar ambos programas, observando cmo se han declarado los procedimientos en tablaseno2 y cmo se corresponden con las sentencias de
tablaseno1.
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.12 nos preocupamos por cmo dar un valor aproximado de
, teniendo las alternativas de definirlo manualmente o usar una frmula
como = 4 arctan 1, a la que podramos agregar el uso de la tcnica de
punto fijo del problema 5.9.
Eliminar la declaracin de pi como constante en el programa tablaseno2,
declararlo en cambio como variable real e incluir el procedimiento:
procedure calculodepi;
begin pi := 4 * arctan(1) end;
como primer procedimiento, incluyendo la sentencia calculodepi en el cuerpo
principal del programa. Compilar y ejecutar el programa con estas modificaciones.
d) En realidad, el valor de pi se usa slo para pasar de grados a radianes.
Eliminar las declaraciones de pi , calculodepi y la sentencia calculodepi del
programa, y en cambio colocarlas dentro del procedimiento imprimirtabla.
Debera quedar algo como:
procedure imprimirtabla;
var
grados: integer;
radianes, pi: real;
procedure calculodepi;
begin pi := 4 * arctan(1) end;
begin
calculodepi;
writeln(Angulo

Seno);

Pg. 72

Funciones y Procedimientos

grados := inicial;
while (grados <= final) do begin
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
$
versin original de tablaseno2.

7.4. Pasando por valor o por referencia


Tanto los procedimientos como las funciones tienen en general uno o ms
parmetros que son los argumentos para evaluarlos.(7) Tenemos que distinguir
entre los parmetros que estn en la descripcin de la funcin o procedimiento,
llamados parmetros formales y los que se especifican en cada llamada de la
funcin o procedimiento, llamados parmetros reales.
Los parmetros formales se utilizan solamente dentro del cuerpo de la funcin o procedimiento y son locales a l, es decir, son desconocidos fuera de la
funcin o procedimiento. Por supuesto, los parmetros reales deben tener el
mismo tipo que los formales. De alguna forma, podemos pensar que la funcin
o procedimiento tiene sus propias cajas (los parmetros formales) en las que
alojar los valores con los que se llaman (los parmetros reales). Decimos tambin
que los parmetros reales se pasan a la funcin o procedimiento.
Por ejemplo, volviendo al programa potencias (problema 7.2), recordemos
que la funcin potencia se declaraba como
function potencia(a: real; b: integer):...
En este caso, los parmetros a y b son los parmetros formales, mientras que
al hacer la llamada
xn := potencia(x,n)
x y n se convierten en los parmetros reales. Los valores de x y n cajas
globales del programa se copian (respectivamente) en las cajas locales a y
b.
Pero no siempre esta copia de valores es adecuada:
Problema 7.7 (Intercambio de variables). Supongamos que queremos intercambiar los valores de las variables u y v, es decir poner en u lo que est en
v y recprocamente. No podemos poner sencillamente u := v; v := u pues
quedara u = v.(8) La forma usual de hacer el intercambio es usando una variable intermedia o temporal t (del mismo tipo que u y v), siguiendo un esquema
como t := u; u := v; v := t, como se indica en la figura 7.3.
- Ya hemos visto este tipo de ideas en el problema 5.20.b).

Si queremos implementar este intercambio como procedimiento, el primer


impulso es poner (suponiendo variables enteras) un procedimiento como en el
programa intercambio (pg. 160).
a) Ejecutar el programa, y comprobar que los valores de x y y no han cambiado.
(7)
(8)

Pero podran no tenerlos, como en el procedimiento carteles del programa tablaseno2.


Ante la duda, recomendamos hacer pruebas de escritorio en este problema.

7.4. Pasando por valor o por referencia


2

u
1

Pg. 73

v
3

Figura 7.3: Intercambio de los valores de u y v usando la variable intermedia t.


Los nmeros indican el orden de los pasos a realizar.
El mecanismo de copiar los valores de x, y en las cajas locales a, b es la que
produce el resultado no deseado. Por suerte, existe un mecanismo que al llamar a
la funcin o procedimiento hace equivalentes las variables correspondientes. En
Pascal, esto se hace incorporando la palabra var en los parmetros formales
deseados.
b) Cambiar el procedimiento incorrecto del programa anterior por el siguiente:
procedure correcto(var a, b: integer);
(* a, b pasados por referencia *)
var t: integer;
begin t := a; a := b; b := t end;
Probarlo y comprobar que los valores finales de x y y 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 parmetros reales,
o directamente con stos. Este mecanismo se llama de sustitucin y es usual
distinguir las siguientes clases de sustitucin de parmetros:
Sustitucin por valor: El parmetro real se evala, y el valor resultante sustituye al correspondiente parmetro formal.
Sustitucin por referencia: El parmetro real es una variable; los posibles ndices se evalan, y la variable as identificada sustituye a su
correspondiente parmetro formal. Es utilizada si el parmetro representa el resultado de un procedimiento.
Por ejemplo, nos preguntamos cules son los valores finales del arreglo a
despus de la ejecucin del fragmento:
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.
En este caso, tenemos la sustitucin por valor: la variable x en el procedimiento P tiene valor inicial 10 = a1 . El valor final de a es (10, 20).
Si cambiamos la definicin del procedimiento P a
procedure P(var x: integer);
begin i := i + 1; x := x + 2 end;
tendremos la sustitucin por referencia: en el procedimiento P , x = a1 y la
sentencia x := x + 2 significa a[1] := a[1] + 2. Por lo tanto, el valor
final de a es (12, 20).

Pg. 74

Funciones y Procedimientos

- Hay otras formas de sustitucin, que no estn implementadas en Pascal.


Por ejemplo la llamada por nombre, en la que el parmetro real sustituye
literalmente al parmetro formal y no hay evaluacin. En nuestro ejemplo,
al hacer la llamada P(a[i]), la sentencia x := x + 2 en el procedimiento
P significa a[i] := a[i] + 2; y el valor final de a es (10, 22).

Recordar entonces que:


En Pascal la sustitucin por valor es la usual, si queremos sustitucin por
referencia anteponemos la palabra var al parmetro formal.
No podemos poner como parmetro real una constante cuando el parmetro se pasa por referencia. Por ejemplo las declaraciones:
.
.
.
procedure P(var x: integer);
.
.
.
begin (* parte principal *)
.
.
.
P(1);
.
.
.
end.
son incorrectas.
Si hay varios parmetros, algunos pueden pasarse por valor y otros por
referencia (con var). Si hay una lista de parmetros separados por comas
(,), como en proc(var a, b: integer; c, d: integer), entonces
los parmetros a y b se pasan por referencia mientras que los parmetros c y d se pasan por valor. Es conveniente evitar estas confusiones,
poniendo explcitamente la palabra var delante de cada parmetro que
se pasar por referencia.
Problema 7.8. En los siguientes fragmentos de programas, determinar los valores de los parmetros de las sentencias writeln y luego comprobar las respuestas
ejecutndolos (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)
end.
b) var i, j, k: integer;
procedure P(var i: integer);
begin i := i + 1; writeln(i, j, k) end;
procedure Q( h: integer; var j: integer);
var i: integer;
procedure R;
begin i := i + 1 end;

7.5. Comentarios Bibliogrficos

begin i := j;
if (h = 0) then P(j) else if h = 1 then P(i) else R;
writeln( i, j, k)
end;
begin i := 0; j := 1; k := 2; Q(0,k); Q(1,i); Q(2,j)
end.
- Observar que el procedimiento R es local al procedimiento Q, y desconocido
globalmente. Recordar el problema 7.6.d).
$

7.5. Comentarios Bibliogrficos


La seccin 7.4 incluyendo los problemas est basada en los libros de
Wirth [11, 12].

Pg. 75

Captulo 8

Todos juntos: arreglos,


funciones y procedimientos
8.1. Definiendo nuestros propios tipos de datos:
type
Hemos visto cuatro tipos de datos elementales boolean, char, integer y
real pero Pascal tambin nos permite crear nuevos tipos mediante la palabra
type. Esto es especialmente conveniente cuando trabajamos con varios arreglos
de las mismas caractersticas (y como veremos ms tarde, tambin para otras
estructuras).
Supongamos por ejemplo que queremos trabajar con dos arreglos, a y b,
declarados como
var a, b: array[1..100] of integer
Puesto que tienen las mismas caractersticas, podramos definir un tipo para
estos arreglos poniendo
type arregloentero = array[1..100] of integer
despus de la declaracin de constantes y antes de la de variables. Observar que
usamos el signo =, como en las constantes, y no : que usamos al declarar
variables.
Luego podemos declarar (en la parte de var)
a, b: arregloentero
lo que nos permite hacer asignaciones de la forma a := b, y no tener que
hacer un lazo para la asignacin.(1)
Ms importante, es altamente conveniente y recomendable usar como argumentos de funciones o procedimientos nicamente parmetros 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 modificaciones toma entonces la forma del cuadro 8.1, y la estructura de funciones
y procedimientos es similar (pueden tener constantes, tipos, etc. propios), slo
que terminan con end; en vez de end..
(1) No obstante, no es posible hacer la comparacin a = b. En este caso habr que usar
un lazo.

8.2. Ingreso e impresin de arreglos


1.
2.
3.
4.
5.

program nombre (input, output);


const si hubiera que definir constantes.
type si hubiera definidos tipos propios.
var si hay variables.
Si las hubiera, funciones (con function) y procedimientos
(con procedure). Deben declararse teniendo en cuenta el
orden en que sern usadas.
6. begin
7. Instrucciones.
8. end.
Cuadro 8.1: Estructura de un programa Pascal.

8.2. Ingreso e impresin de arreglos


Ahora que contamos con funciones y procedimientos, es un buen momento
para repasar la entrada de datos del programa busquedalineal del problema 6.3.
Problema 8.1 (Ingreso de arreglos). Supongamos que queremos ingresar
un arreglo a = (a1 , a2 , . . . , an ) de datos (de algn tipo), ingresando un dato
por rengln, poniendo los carteles en cada paso, y terminando la entrada con
<retorno> sin datos. Siguiendo el esquema del programa busquedalineal, e
incorporando el control del nmero de datos, de ahora en ms vamos a usar un
procedimiento para englobar esta accin.
Si declaramos const MAXN = 100, y los tipos
tipodato = integer; (* real, char,... *);
tipoarreglo = array[1..MAXN] of tipodato;
podemos leer arreglos del tipo tipoarreglo mediante el siguiente procedimiento:
procedure leerarreglo(var a: tipoarreglo; var n: integer);
var findatos: boolean;
begin
write(Entrar numeros enteros,); (* o reales o... *)
writeln( uno por renglon y no mas de , MAXN:1, .);
writeln( Fin con retorno sin entrada de datos.);
n := 1; findatos := false;
repeat
if (n > MAXN) then begin
findatos := true;
writeln(** Cantidad de datos excedida **)
end
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;

Pg. 77

Pg. 78

Todos juntos: arreglos, funciones y procedimientos


Podemos tambin copiar la impresin de arreglos del mismo programa, con
las modificaciones del problema 6.3:
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
begin
writeln(** Prueba de lectura e impresion de arreglos);
writeln;
leerarreglo( arreglo, ndatos);
writeln(El arreglo leido es:);
escribirarreglo( arreglo, ndatos);
writeln; writeln(** Fin **)
end.
Cuando est funcionando correctamente, cambiar el tipo de datos ingresado
de entero a real.
$
Problema 8.2. Usando las ideas del problema 8.1, modificar el programa renglon (problema 6.2) para darle una forma estructurada, verificando que el programa funcione al hacer cada modificacin:
a) Declarar el tipo tiporenglon con type, como arreglo de MAXC caracteres,
y r como del tipo tiporenglon.
b) Hacer un procedimiento leerrenglon para leer el rengln, donde los argumentos sean s de tipo tiporenglon, y long de tipo entero, para indicar su
longitud.
c) Anlogamente, hacer un procedimiento escribirrenglon de modo de imprimir
el rengln. Los argumentos ahora son de s de tipo tiporenglon, y long de
tipo entero (para indicar la longitud).
Despus de estos cambios, en la parte principal del programa debe quedar
algo como:
(* entrada *)
leerrenglon(r, n);
writeln;
(* salida *)
writeln(El renglon ingresado es: );
writeln;
escribirrenglon(r, n);
d) Modificar el programa obtenido con los incisos anteriores, de modo de hacer
el eco de varios renglones, imprimiendo cada rengln apenas ingresado

8.2. Ingreso e impresin de arreglos

Pg. 79

(recordar los problemas 4.24 y 4.28), pero guardando los datos ingresados
en un arreglo.
$
Problema 8.3.
a) Desarrollar un programa que, dado un arreglo de enteros entrado 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: hacer los intercambios a1 an , a2 an1 ,...
Sugerencia si la anterior no alcanza:
i := 1; j := n;
while (i < j) do begin
t := a[i]; a[i] := a[j]; a[j] := t;
i := i + 1; j := j - 1
end;

b) Desarrollar un programa que leyendo un rengln (como en el problema 8.2)


lo escriba al revs, e.g., si la entrada es
dabale arroz a la zorra el abad
el programa escriba
daba le arroz al a zorra elabad

Problema 8.4. Consideremos la funcin


function comparar(
x: tiporenglon; nx: integer;
y: tiporenglon; ny: integer): integer;
var nmax, i: integer;
begin
if (nx < ny) then nmax := nx else nmax := ny;
(* se supone que nmax > 0 *)
i := 1;
while ((i < nmax) and (x[i] = y[i])) do i := i + 1;
(* i es la primer posicion donde difieren o i = nmax *)
if (ord(x[i]) < ord(y[i])) then comparar := -1
else if (ord(x[i]) > ord(y[i])) then comparar := 1
(* a partir de aca tenemos x[i] = y[i] *)
else if (nx < ny) then comparar := -1
else if (nx > ny) then comparar := 1
else comparar := 0
end;
que compara alfabticamente segn el orden en Pascal los renglones x
y y del tipo tiporenglon como en el problema 8.2, y de longitudes nx y ny
respectivamente.
a) Cul es el uso de nmax ?
b) Encontrar el valor retornado por la funcin cuando
i) x = mi mama , y = me mima .
ii) x = mi mama , y = mi mama me .
c) Dar un ejemplo donde el valor retornado es 0.
d) Cambiar el lazo while por uno repeat equivalente.
e) Hacer un programa para leer dos renglones, y usando la funcin comparar ,
escriba primero el que viene adelante alfabticamente, y luego el otro. $

Pg. 80

Todos juntos: arreglos, funciones y procedimientos

8.3. La caja de herramientas


Iremos presentando cada vez menos programas completos, bajo el entendimiento de que cuestiones comunes ya han sido vistas, como leer o imprimir
arreglos.
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 ms
usados, o quizs ms difciles de reproducir. De esta forma, cuando necesitemos
alguno, podemos simplemente hacer una copia en el programa, haciendo quizs
pequeos cambios, sin necesidad de escribirlos mucho menos pensarlos cada
vez.
- Los sistemas de programacin ms avanzados incluyen en bibliotecas estas
funciones o procedimientos, muchas veces en forma binaria difcil de modificar. Aunque similar, el concepto es ligeramente diferente del de la caja
de herramientas.

Por ejemplo, los procedimientos leerarreglo y escribirarreglo en el problema 8.1 nos dan una plantilla (template en ingls) para leer o imprimir arreglos
de nmeros 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
texto,(2) los procedimientos leerarreglo y escribirarreglo del problema 8.1, y practicar 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 querrs incorporar a la caja de herramientas, algunos que ya hemos visto como el mtodo de la
biseccin (seccin 7.3), y otros que veremos ms adelante, como alguno de los
mtodos de bsqueda y clasificacin del captulo 10.

8.4. Arreglos multidimensionales


Los arreglos unidimensionales como los que vimos no son apropiados
para guardar la informacin contenida en una tabla (como la del seno o del
logaritmo). En estos casos es ms conveniente usar un arreglo de arreglos,
tambin llamado arreglo multidimensional.
Por ejemplo, si queremos guardar una matriz de reales de 2 3, podemos
pensar que necesitamos un arreglo de dimensin 2 (cantidad de filas), cuyos
elementos individuales son arreglos de dimensin 3 (cantidad de columnas).
Hacemos entonces la declaracin
m: array[1..2] of array[1..3] of real;
o equivalentemente,
m: array[1..2,1..3] of real;
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
inconvenientes en considerar estructuras con cualquier nmero de dimensiones.
Si en vez de nmeros guardamos caracteres, las filas pueden pensarse como
renglones, como hacemos en el siguiente problema.
(2)

En algunos sistemas operativos, es conveniente que tengan la extensin .txt.

8.5. Strings

Pg. 81

Problema 8.6. En este problema consideraremos un texto como una serie de


renglones o lneas no vacas (cada una tiene al menos un carcter). Declaramos
const
MAXC = 255; (* maxima cantidad de caracteres por renglon *)
MAXR = 20; (* maximo numero de renglones *)
type
tiporenglon = array[1..MAXC] of char;
tipotexto = array[1..MAXR] of tiporenglon;
caracteresenrenglon = array[1..MAXR] of integer;
var
nr,
(* numero de renglones *)
nc
(* numero de caracteres en renglon *)
: integer;
texto: tipotexto;
cenr: caracteresenrenglon;
a) Desarrollar un procedimiento o funcin para leer no ms de MAXR renglones, con no ms de MAXC caracteres por rengln, dando como seal de fin
de entrada un rengln vaco.
Sugerencia: hacer un esquema como
nr := 0; (* numero de renglon leido, al ppo. ninguno *)
while (not eoln) do begin (* mientras el renglon no sea vacio *)
nr := nr + 1;
(* hay un renglon mas *)
leerrenglon(texto[nr], cenr[nr]) (* acordarse de readln! *)
end;
readln; (* leer el renglon vacio *)

b) Incorporar un procedimiento para escribir renglones, y verificar que el proceso de lectura desarrollada en el inciso anterior es correcto.
- A veces arreglos como texto y cenr se dicen paralelos, pues la informacin se va actualizando simultneamente. Cuando veamos registros, en la
$
seccin 10.4, veremos otra forma de guardar informacin de este tipo.

8.5. Strings
Problema 8.7. El tipo string no es estndar en Pascal, pero existe en casi
todos los compiladores, en particular en Turbo Pascal. Probar el comportamiento del compilador con un programa que lee un rengln entrado por terminal, va
readln(s), donde se declara s como string[100], indicando su longitud, y/o
simplemente como string.
- Ms precisamente, segn el estndar Pascal el tipo string es una denominacin genrica para el tipo char y para arreglos empaquetados de
caracteres, que se declaran con algo como
var s: packed array[1..10] of char
Lo que no se admite en el estndar es una declaracin como
var s: string[10]
que es ms o menos equivalente en los compiladores que la aceptan.
En ambos casos, podemos pensar que se tiene un arreglo mixto, donde
el primer ndice indica la cantidad de caracteres, y los restantes lugares son
los caracteres. En el proceso se cambia de tipo char a integer o viceversa,

Pg. 82

Todos juntos: arreglos, funciones y procedimientos

de modo que tanto el string como el packed array en realidad tienen


todos los elementos del mismo tipo.

En caso de que el compilador acepte alguna de estas variantes, agregar al


programa las instrucciones:
a) writeln(s), para escribir s,
b) length(s), para averiguar su longitud, y
c) for i := 1 to length(s) do writeln( i:3, : , s[i]), para escribirlo carcter por carcter, indicando el ndice.
d) Modificar el programa del problema 8.6, cambiando el tipo renglon por el
$
tipo string.

8.6. Manejo elemental de archivos de texto


A veces resulta til guardar los resultados obtenidos por un programa, para
despus leerlos, imprimirlos o utilizarlos en otros programas. Dado el tamao 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,
modificar o imprimir) con un editor de textos.
- En algunos sistemas operativos es posible redirigir la entrada y salida, de
modo que se lean los datos desde un archivo en vez de ingresarlos con el
teclado, o se escriban los resultados a un archivo en vez de escribirlos en la
terminal.
Este es el propsito original del (input, output) en el ttulo de los
programas Pascal.

El lenguaje Pascal no es especialmente apto para leer y escribir archivos, y es


por ello que se han realizado muchas extensiones (que no respetan el estndar)
tratando de mejorarlo, pero trataremos de ceirnos al estndar. En l se establece
otro tipo primitivo de datos adems de los que ya hemos visto: el tipo text, o
archivo de texto.
- Las variables de tipo text que son argumentos de una funcin o procedimiento deben pasarse por referencia, i.e., anteponiendo var (ver seccin 7.4).

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
deconsolaaarchivo (pg. 161) y dearchivoaconsola (pg. 162) respectivamente, y
que pasamos a comentar.
Al leer los programas, observamos que 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.
- Nos hemos permitido usar string para la variable correspondiente, aunque no es estndar 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.
Ha de tenerse cuidado con rewrite, pues si el archivo no existe, se crea uno
nuevo, pero si ya existe, sus contenidos son borrados.

8.6. Manejo elemental de archivos de texto


En compiladores que siguen el modelo de Turbo Pascal para entrada/salida,
hay que modificar los programas, ya que no aceptan el estndar.
Para escribir un archivo, hay que cambiar el rengln
rewrite(archivo, nombre);
por
assign(archivo, nombre); rewrite(archivo);
en el programa deconsolaaarchivo. Del mismo modo, para leer desde un archivo, hay que modificar el rengln
reset(archivo, nombre);
por
assign(archivo, nombre); reset(archivo);
en el programa dearchivoaconsola.
Sintetizamos estas diferencias en el cuadro 8.2.
escribir
archivo
leer
archivo

Estndar
rewrite(archivo, nombre)
reset(archivo, nombre)

Turbo Pascal
assign(archivo, nombre);
rewrite(archivo)
assign(archivo, nombre);
reset(archivo)

Cuadro 8.2: Diferencias entre el estndar y Turbo Pascal para leer o escribir
archivos.
Luego de leer el nombre externo y relacionarlo con el interno, debemos leer
de consola e ir escribiendo el archivo en un caso, y recprocamente, leer del
archivo y escribir en consola en el otro.
En el primer caso vemos una estructura similar a la lectura de renglones
del problema 8.6, excepto que no usamos write(c) sino write(archivo, c)
para escribir c en el archivo y no la consola, debiendo incorporar el nombre
del archivo. Del mismo modo, writeln(archivo) (sin el argumento c) escribe
un fin de lnea terminando el rengln en el archivo.
En el segundo caso, cuando leemos del archivo en dearchivoaconsola e
imprimimos en la terminal, volvemos a encontrar una estructura conocida,
excepto que ahora el fin de datos que anteriormente sealbamos con
<retorno> vaco, ahora se seala 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 seal en el archivo que estamos leyendo.
- Hay que tener cuidado pues no debe llamarse a eoln si se ha llegado al
final del archivo, ya que un archivo de texto puede no tener fin de lnea:
primero siempre debe llamarse a eof.

Tambin, read(c) se cambia por read(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 en el programa, y en el caso de estar escribiendo coloca en el
archivo la seal de fin de archivo.
Problema 8.8 (Archivos de texto).

Pg. 83

Pg. 84

Todos juntos: arreglos, funciones y procedimientos

- Recordar los cambios a realizar usando assign si se sigue el modelo


de Turbo Pascal.

a) Compilar y ejecutar el programa deconsoloaarchivo, y verificar su comportamiento abriendo el archivo creado con un editor de textos.
- El directorio en el cual el programa crear o buscar el archivo depende
del compilador, del sistema operativo, y si lo hubiere, de la ubicacin
e instalacin del ejecutable.
- Como ya mencionamos, en algunos sistemas operativos es conveniente
que los archivos de texto tengan la extensin .txt.

b) Del mismo modo, compilar y ejecutar el programa dearchivoaconsola.


c) Tomando las partes necesarias de los programas deconsolaaarchivo y dearchivoaconsola, y tal vez del problema 8.6, hacer un programa dearchivoaarchivo
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
a las variables para el archivo de entrada y el de salida.
- Ya que estamos usando string, podramos haber ledo todo un rengln en
vez de carcter por carcter. Sin embargo, es posible que los renglones
tengan ms de la longitud permitida por string, usualmente 255 caracte$
res.

8.7. Comentarios Bibliogrficos


La descripcin de tipos y archivos de textos en Pascal est basada en el libro
de Jensen y Wirth [4].

Captulo 9

Nmeros Aleatorios y
Simulacin
Muchas veces se piensa que en matemticas las respuestas son siempre exactas, olvidando que las probabilidades forman parte de ella y que son muchas
las aplicaciones de esta rama de las matemticas.
Una de estas aplicaciones es la simulacin, tcnica muy usada por fsicos,
ingenieros y economistas cuando es difcil llegar a una frmula que describa el
sistema o proceso. As, simulacin es usada para cosas tan diversas como el
estudio de las colisiones de partculas en fsica nuclear y el estudio de cuntos
cajeros poner en el supermercado para que el tiempo de espera de los clientes
en las colas no sea excesivo.
La simulacin mediante el uso de la computadora es tan difundida, que hay
lenguajes de programacin (en vez del Pascal o C) especialmente destinados a
este propsito.

9.1. Nmeros aleatorios


Cuando en la simulacin interviene el azar o la probabilidad, se usan nmeros generados por la computadora que reciben el nombre de aleatorios (o, ms
correctamente, seudo-aleatorios). No es nuestra intencin aqu hacer una descripcin de qu son estos nmeros o cmo se obtienen: basta con la idea intuitiva
de que la computadora nos da un nmero en cierto rango, y cualquier nmero
del rango tiene la misma probabilidad de ser elegido por la computadora.
En general, los nmeros aleatorios se obtienen a partir de un valor inicial o
semilla (seed en ingls); de modo que si no se indica lo contrario cambiando
la semilla siempre obtenemos la misma sucesin de nmeros aleatorios. Un
mtodo eficaz para obtener nmeros verdaderamente aleatorios, es cambiar
la semilla de acuerdo a la hora que indica el reloj de la computadora.
Lamentablemente, en el estndar Pascal no est definida una funcin para
generar nmeros aleatorios. An cuando un compilador tenga implementada
una tal funcin, el nombre con la que se llama y an el tipo de resultado por
ejemplo si son nmeros reales entre 0 y 1 o enteros entre 0 y maxint dependen
tambin del compilador.
Dado que muchos compiladores siguen el modelo de Turbo Pascal, nosotros
seguiremos la notacin de este compilador para no complicar ms de lo necesario,
apartndonos (nuevamente) del estndar.

Pg. 86

Nmeros Aleatorios y Simulacin

- Si no se dispone de una rutina para generar nmeros aleatorios, es necesario instalar una propia. Sin embargo, obtener nmeros aleatorios con la
computadora no es tan sencillo como tirar dados, y hay mucha teora matemtica detrs de los buenos generadores: los interesados puede consultar
el excelente libro de Knuth [8, vol. 2].
Una posibilidad es usar las rutinas que aparecen como ejemplo en el
mismo estndar de Pascal extendido (ISO 10206) adaptado al estndar de
Pascal sin extensiones que es el que usamos.(1)

Turbo Pascal cuenta con las funciones random y randomize, y la variable


randseed . random sin argumentos da un nmero aleatorio entre 0 y 1; y cuando
incluimos un argumento n N, random(n) da un nmero aleatorio entero entre
0 y n 1. La variable randseed es la semilla, que puede cambiarse de acuerdo
al reloj de la computadora mediante la instruccin randomize.

9.2. Aplicaciones
Problema 9.1. El programa dado (pg. 162) hace una simulacin de tirar un
dado mediante nmeros aleatorios obtenidos con la sentencia random.
a) La sentencia randomize sirve para comenzar una nueva serie de nmeros
aleatorios. Eliminarla, comentndola, ejecutar repetidas veces el programa
y comprobar que siempre se obtienen los mismos resultados (o sea no es
muy al azar).
- Salvo para programas que tienen un tiempo de ejecucin grande, no
debe hacerse ms de una llamada a randomize.

b) Modificar el programa para simular tirar una moneda con resultados cara
o ceca.
$
Problema 9.2. El programa dados (pg. 163) hace una simulacin para encontrar la cantidad de veces que se necesita tirar un dado hasta que aparezca un
nmero prefijado, entrado por el usuario. Gracias a la sentencia randomize, el
resultado en general ser distinto con cada ejecucin. Observar que si el usuario
entra un nmero menor que 1 o mayor que 6, el programa no termina nunca.
a) Ejecutar el programa varias veces, para tener una idea de cunto tarda en
aparecer un nmero.
b) En vez de correr varias veces el programa, modificarlo de modo de realizar
n simulaciones (n entrado por el usuario), mostrando como resultado el
promedio(2) de los tiros que tard en aparecer el nmero predeterminado.
Sugerencia: encerrar el lazo repeat dentro de un lazo for, y tener cuidado
con el tipo de dato donde se guardan las acumuladas.
c) Modificar el programa original a fin de simular que se tiran simultneamente
dos dados, y contar el nmero 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 cuntas 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 sali un seis y si no
se vuelve a 0.
(1) Una copia del estndar en formato pdf puede encontrarse en http://www.
pascal-central.com/standards.html
(2) Recordemos que el promedio debe ser una variable real.

9.2. Aplicaciones

- En ste y en el problema 9.2, surge la duda de si el programa terminar


alguna vez, dado que existe la posibilidad de que nunca salga el nmero
prefijado, o que nunca salga k veces consecutivas. Sin embargo, se puede
demostrar matemticamente que la probabilidad de que esto suceda es 0
(suponiendo que el generador de nmeros aleatorios sea correcto).
$

Problema 9.4.
a) Desarrollar un programa para hacer una lista de r nmeros enteros, elegidos
aleatoria y uniformemente entre 0 y s 1, donde r, s N son entrados
por el usuario.
b) Modificar el programa de modo que, recorriendo linealmente la lista generada en el inciso anterior, al terminar imprima la cantidad de veces que se
repite cada elemento. Sugerencia: agregar un segundo arreglo para contar
las apariciones (ver tambin el problema 6.1).
- La cantidad de apariciones deberan ser muy similares, aproximadamente r/s cada uno, cuando r  s.
$

Problema 9.5 (Dos con el mismo cumpleaos). Mucha gente suele sorprenderse cuando en un grupo de personas hay dos con el mismo da de cumpleaos: la probabilidad de que esto suceda es bastante ms alta de lo que se
cree normalmente.
Supongamos que en una sala hay n (n N) personas y supongamos, para
simplificar, que no hay aos bisiestos (no existe el 29 de febrero), de modo que
podemos numerar los posibles das de cumpleaos 1, 2, . . . , 365.
a) Para qu valores de n se garantiza que haya al menos dos personas que
cumplen aos el mismo da? Sugerencia: recordar el principio del casillero,
tambin conocido como del palomar o de Dirichlet.
- El principio de Dirichlet dice que si hay n + 1 objetos repartidos en n
casillas, hay al menos una casilla con 2 o ms objetos.

b) Si la sala es un cine al cual van entrando de a una las personas, cuntas


personas, en promedio, entrarn hasta que dos de ellas tengan el mismo
da de cumpleaos? Responder esta pregunta escribiendo un programa que
genere aleatoriamente das de cumpleaos (nmeros entre 1 y 365) hasta que
haya dos que coincidan, retornando la cantidad de personas necesarias.
Hacer varias corridas para tener una idea ms acabada.
Si en el programa se usan arreglos, cul ser la dimensin, de acuerdo
al inciso anterior?
c) Basado en el punto anterior, si en tu curso hay 30 compaeros, apostaras
$
que hay dos que cumplen aos el mismo da?
Problema 9.6. En este problema usaremos la sentencia random (sin argumentos) de Turbo Pascal que da un nmero aleatorio real entre 0 y 1.
a) Hacer un programa que ela aleatoriamente el nmero 1 aproximadamente
el 45 % de las veces, el nmero 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
.45, .45 + .35, . . .
b) Generalizar al caso en que en vez de la lista (1, 2, 3, 4) se d una lista
(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
verificar que las frecuencias son similares a las deseadas.
$

Pg. 87

Pg. 88

Nmeros Aleatorios y Simulacin


Problema 9.7. El compilador de Pascal de Ana tiene la sentencia aleat, que
obtiene nmeros aleatorios r R con 0 r < 1, i.e., como la sentencia random
de Turbo Pascal sin argumentos.
a) Qu instruccin debe poner Ana en vez de random(n) (que no es reconocida
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 debera usar?
c) Y si necesitara nmeros reales entre a y b (que pueden ser a pero no b),
donde a < b?
$
Existen muchos mtodos, que reciben el nombre de mtodos de Monte Carlo,
para aproximar cantidades determinsticas, i.e., no aleatorias, mediante probabilidades. Los dos problemas siguientes son ejemplos de esta tcnica.
Problema 9.8. Hacer un programa para simular una mquina que emite nmeros al azar (uniformemente distribuidos) en el intervalo (0, 1) uno tras otro
hasta que su suma excede 1. Comprobar que al usar la mquina muchas veces, la
cantidad promedio de nmeros emitidos es aproximadamente e = 2.71828 . . . $
Problema 9.9. Hacer un programa para calcular tomando n pares de nmeros aleatorios (a, b), con a y b entre 1 y 1, contar cuntos de ellos estn
dentro del crculo unidad, i.e., a2 + b2 < 1. El cociente entre este nmero y n
(ingresado por el usuario), es aproximadamente el cociente entre las reas del
$
crculo de radio 1 y el cuadrado de lado 2.

Captulo 10

Bsqueda y clasificacin
Siempre estamos buscando algo y es mucho ms fcil encontrarlo si los datos
estn clasificados u ordenados. No es sorpresa que bsqueda y clasificacin sean
temas centrales en informtica y que haya una enorme cantidad de material
escrito al respecto. Por ejemplo, en sus clsicos libros [8] Knuth dedica al tema
todo el volumen 3 (que por supuesto, usa material de los volmenes anteriores).
Ac hacemos una introduccin al tema siguiendo, en mnima proporcin, la
presentacin de Wirth en [12].

10.1. Bsqueda lineal con centinela


Empecemos recordando lo hecho en el problema 6.3 y el programa busquedalineal en el que recorramos linealmente el arreglo a = (a1 , a2 , . . . , an ) buscando
x (suponiendo n > 0).
Problema 10.1. Al buscar x en el arreglo (a1 , a2 , . . . , an ), el programa busquedalineal (pg. 156) usa un lazo repeat:
i := 0;
repeat i := i + 1; seencontro := (a[i] = x)
until (seencontro or (i = n));
if (seencontro) then... (* se encontro en la posicion i *)
Con cules de las siguientes alternativas se podra 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) then... (* se encontro en la posicion i *)
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 *)

Pg. 90

Bsqueda y clasificacin
En el lazo while o repeat de los esquemas en el problema anterior para
bsqueda lineal se hacen dos comparaciones, pero podemos mejorarlo haciendo
una sola colocando a x como centinela en alguna posicin de modo que siempre
lo encontremos.
Por ejemplo, una modificacin del esquema del inciso d) del problema anterior que 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 *)
Observamos que siempre termina, ya sea porque x es un elemento del arreglo
original, en cuyo caso i es el lugar que ocupa, o bien porque no se encontr, en
cuyo caso i = 0.
- No hay nada misterioso en ir desde atrs hacia adelante, podramos haber
puesto el centinela en la posicin n + 1 y recorrer de adelante hacia atrs.

Problema 10.2 (Bsqueda lineal con centinela). Hacer una implementacin con ambas variantes (con y sin centinela) como procedimientos, incluyendo
un contador para la cantidad de comparaciones en cada una, y probar el comportamiento en distintos ejemplos (entrar el arreglo como en el procedimiento
leerarreglo (pg. 77), o generar uno aleatoriamente).
$
Problema 10.3. En este problema queremos hacer un procedimiento para eliminar elementos repetidos del arreglo de enteros a = (a1 , a2 , . . . , an ), como
en el problema 6.6, pero cuando a est ordenado de menor a mayor, e.g., si
a = (1, 2, 2, 5, 6, 6, 9), queremos que al fin del procedimiento sea a = (1, 2, 5, 6, 9).
En este caso podemos poner algo como
procedure purgarordenado(var a: arreglo; var n: integer);
(* sacar elementos repetidos del arreglo
ordenado a de longitud n. *)
var i, m: integer;
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;
to.

Hacer un programa para verificar el comportamiento de este procedimien$

10.2. Bsqueda binaria


Cuando a = (a1 , a2 , . . . , an ) est ordenado, la bsqueda de x en a se facilita
enormemente, por ejemplo al buscar en un diccionario o en una tabla.
Quizs el mtodo ms eficiente para la bsqueda en un arreglo ordenado
sea el de bsqueda binaria, que es el equivalente al mtodo de la biseccin para
encontrar races de funciones (problema 7.3), slo que en un entorno discreto
y no continuo: sucesivamente dividir en dos y quedarse con una de las mitades.

10.2. Bsqueda binaria

Pg. 91

Problema 10.4 (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. Despus de
cada intento nuestro, nos dicen si ganamos o si el regalo est hacia
la derecha o izquierda.
a) Ver que siempre se puede ganar si nos dan cuatro oportunidades.
b) Simular este juego en la computadora: la computadora elige aleatoriamente
una ubicacin para el regalo, y luego en cada eleccin del usuario el programa
responde si el regalo est en la caja elegida (y termina), a la derecha o a la
izquierda, orientando la bsqueda. Si despus de cuatro oportunidades no
se acert la ubicacin, el programa termina dando el nmero de caja donde
estaba el regalo.
c) Cambiar el programa de cuatro a tres oportunidades, y ver que no siempre
se puede ganar.
d) Cuntas oportunidades habr que dar para n cajas, suponiendo una estrategia de bsqueda binaria? Sugerencia: la respuesta involucra las funciones
techo y log2 .
$
Problema 10.5. Se ha roto un cable maestro de electricidad en algn punto
de su recorrido subterrneo de 50 cuadras. La compaa local de electricidad
puede hacer un pozo en cualquier lugar para comprobar si hasta all el cable
est sano, y bastar con detectar el lugar de la falla con una precisin de 5m.
Por supuesto, una posibilidad es ir haciendo pozos cada 5m, pero el encargado no est muy entusiasmado con la idea de hacer tantos pozos, porque hacer
(y despus tapar) los pozos cuesta tiempo y dinero, y los vecinos siempre se
quejan por el trnsito, que no tienen luz, etc.
Qu le podras sugerir al encargado?
$
En lo que resta de esta seccin, suponemos que a est ordenado no decrecientemente, i.e., a1 a2 an (y 1 n).
Problema 10.6 (Bsqueda Binaria).
a) Una primer idea para programar bsqueda binaria 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 continuacin comparar x con a[mucho] *)
Ver que esta idea no es del todo correcta. Sugerencia: considerar a =
(1, 3) y x = 2.
b) El problema con el lazo anterior es que cuando
mucho = poco + 1,

(10.1)

como medio = b(poco + mucho)/2c obtenemos


medio = poco.
i) Verificar que (10.1) implica (10.2).

(10.2)

Pg. 92

Bsqueda y clasificacin
ii) Verificar que si en cambio, mucho poco + 2, entonces poco < medio <
mucho.
c) Hacer un procedimiento implementando bsqueda binaria con el esquema:
poco := 1; mucho := n;
while (poco + 1 < mucho) do begin
medio := (poco + mucho) div 2;
if (a[medio] < x) then poco := medio
else mucho := medio
end;
(* a continuacin comparar x
con a[mucho] y con a[poco] *)
y aplicarlo en un programa para encontrar un elemento en un arreglo (or$
denado no decrecientemente!) ingresados por terminal.

10.3. Mtodos elementales de clasificacin


Nuestra prxima tarea consistir en ver cmo clasificar u ordenar un arreglo a = (a1 , a2 , . . . , an ) de elementos no necesariamente distintos, poniendo el
resultado en el mismo arreglo a tratando de evitar usar otro arreglo, pero
con sus elementos ordenados de menor a mayor. Para ello veremos tres mtodos elementales para clasificar que podemos relacionar con la forma con que
generalmente cada uno ordena las cartas de un juego, suponiendo que las cartas
estn colocadas en la mesa, y las tenemos que levantar:
Insercin directa: Levantamos la primer carta, y luego vamos tomando
de a una las que quedan sobre la mesa. Para cada carta (a partir
de la segunda) que levantamos, buscamos su posicin de derecha a
izquierda en el abanico que tenemos en la mano, la colocamos en el
lugar correspondiente y continuamos levantando la prxima carta.
Podemos escribir el algoritmo resultante como lazo dentro del cual
usamos bsqueda lineal para encontrar la posicin adecuada:
for i := 2 to
x := a[i];
while (x <
a[j] :=
end;
a[j] := x
end

n do begin
a[0] := x; j := i;
a[j-1]) do begin
a[j-1]; j := j-1

- Observar el uso de centinela: a debe ser declarado de modo de


admitir a0 .

Seleccin directa: Levantamos las cartas al mismo tiempo (todas juntas),


las abrimos en abanico, y comenzando desde la izquierda buscamos la
menor y la colocamos al principio, luego buscamos la segunda menor
y la ubicamos en la segunda posicin, etc.
Ac podemos poner:
for i := 1 to n-1 do begin
k := i; x := a[i];
for j := i+1 to n do
if (x > a[j]) then begin
(* nuevo minimo *)

10.3. Mtodos elementales de clasificacin

Pg. 93

k := j; x := a[k] end;
a[k] := a[i]; a[i] := x
end
- Comparar con el problema 6.4.

Intercambio directo o burbujeo: Aqu tambin levantamos las cartas al


mismo tiempo y las abrimos en abanico, pero vamos mirando de derecha a izquierda buscando un par de cartas consecutivas fuera de
orden y cuando lo encontramos las intercambiamos. Seguimos recorriendo con la mirada el abanico hasta que no haya ningn 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
(* intercambiar *)
x := a[j-1]; a[j-1] := a[j]; a[j] := x
end
- Por supuesto en cada uno de los mtodos podemos cambiar el sentido del
recorrido, empezando desde la derecha o desde la izquierda.

Problema 10.7 (Mtodos elementales de clasificacin). Implementar los


algoritmos anteriores como procedimientos en un nico programa. Agregar en
cada uno contadores para el nmero de asignaciones y comparaciones (entre
datos como x o ak y no entre ndices como i, j o k) hechas en cada caso, y
comparar estas cantidades cuando:
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).

En el cuadro 10.1 mostramos una comparacin entre los tres mtodos con
arreglos de 10 000 enteros construidos de tres formas: ordenado, ordenado al
revs, y aleatorio. Los tiempos en el cuadro son para una mquina particular
(y no demasiado rpida para los estndares cuando se realizaron las pruebas) y
determinado compilador, y debe considerarse slo la proporcin entre ellos. Se
contaron las comparaciones y asignaciones de arreglos (y no cuando se comparan
o asignan ndices).
Observamos en la tabla que tanto seleccin directa como intercambio directo
realizan siempre el mismo nmero de comparaciones, n(n 1)/2, y que an
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 insercin directa con un arreglo aleatorio, hay muchas asignaciones pero
comparando con los otros mtodos elementales no llevan tanto tiempo.
Sera distinto si los elementos fueran arreglos, por ejemplo strings al clasificar
palabras, o registros (que veremos un poco ms adelante).
Muchos libros de programacin insisten en presentar el mtodo de intercambio directo o burbujeo y a veces ningn otro que es claramente inferior a
cualquiera de los otros: an en el caso del arreglo ordenado, se hace la misma
cantidad de comparaciones que seleccin directa, y ninguna asignacin, pero el
tiempo es superior! Hemos incluido este mtodo slo para que sepas que existe
y que es bastante malo.
Nosotros casi siempre elegiremos el de seleccin directa o insercin directa:
seleccin directa hace relativamente pocas asignaciones, insercin directa relativamente pocas comparaciones.

Pg. 94

Bsqueda y clasificacin
Arreglo ya ordenado
comparaciones
asignaciones
tiempo (segs.)

seleccin
49 995 000
29 997
1.10

intercambio
49 995 000
0
1.43

insercin
9 999
29 997
0.00

Arreglo ordenado al revs


seleccin
49 995 000
25 029 997
1.27

comparaciones
asignaciones
tiempo (segs.)

intercambio
49 995 000
149 985 000
1.87

insercin
50 004 999
50 024 997
1.45

Arreglo aleatorio
seleccin
49 995 000
103 894
1.10

comparaciones
asignaciones
tiempo (segs.)

intercambio
49 995 000
75 041 274
1.75

insercin
25 023 757
25 043 755
0.72

Cuadro 10.1: Comparacin de algoritmos de clasificacin, para arreglos enteros


de longitud 10000.
Problema 10.8 (Clasificacin por conteo). La clasificacin es muy sencilla
si sabemos de antemano que el rango de los elementos de a = (a1 , a2 , . . . , an ) es
pequeo, digamos 1 ai m para i = 1, 2, . . . , n.
En efecto, imaginemos que tenemos n bolitas alineadas, cada una con un
nmero entre 1 y m, que tenemos que clasificar.
Armamos m cajas numeradas de 1 a m, luego colocamos cada una de las
bolitas en la caja que tiene su nmero, y finalmente vaciamos los contenidos de
las cajas ordenadamente, empezando desde la primera, alineando las bolitas a
medida que las sacamos, como tratamos de ilustrar con la figura 10.1.


disposicin inicial

1
2
3
poniendo en cajas


disposicin final

Figura 10.1: Ordenando por conteo.


En el algoritmo, en vez de colocar cada bolita en su caja, contamos las
veces que apareci:
(* al principio, las cajas estan vacias *)
for k := 1 to m do cuenta[k] := 0;

10.4. Registros (records)

Pg. 95

(* ponemos cada bolita en su caja,


aumentando el numero de bolitas en esa caja *)
for i := 1 to n do cuenta[a[i]] := cuenta[a[i]] + 1;
(* ahora vaciamos las cajas, alineando las
bolitas a medida que las sacamos *)
i := 0;
(* lugar en la linea *)
for k := 1 to m do
(* para la caja 1, 2,... *)
while (cuenta[k] > 0) do begin (* si tiene una bolita *)
cuenta[k] := cuenta[k] - 1; (* la sacamos *)
i := i + 1; a[i] := k (* y la ponemos en el *)
(* siguiente lugar en la linea *)
end
a) Ver que el algoritmo es correcto, i.e., al finalizar el arreglo a est ordenado,
an cuando haya valores r, 1 r m tales que ai 6= r para todo i.
b) Implementar el algoritmo y comparar su eficiencia con los otros mtodos,
poniendo contadores para el nmero de asignaciones y comparaciones.
c) A lo sumo cuntas asignaciones de arreglos se hacen en este algoritmo (la
respuesta involucra n y m)? Cundo lo usaras?
- Ya hemos usado una tcnica similar en los problemas 6.1 y 9.4.

10.4. Registros (records)


En aplicaciones informticas, y an en algunas matemticas como veremos,
es conveniente agrupar la informacin que tenemos sobre un objeto. Por ejemplo,
para una persona podramos tener su apellido, nombres, nmero 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 tendra el mismo
ndice para cada uno de los arreglos. Otra forma ms razonable es el uso de
registros.
Un registro es una estructura que consiste en un nmero 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 nmeros complejos de
la forma a + b i, con a, b R, podemos poner:
type complejo = record re, im: real end;
var z: complejo;
- Observar que la declaracin del registro termina con end, pero no hay un
begin.

El registro ocupa un lugar de memoria donde los campos son consecutivos,


de modo que podemos pensar que el complejo z = a + b i se guarda en una caja
como se muestra en la figura 10.2. En el ejemplo, cada sub-caja o campo es
de tipo real.
Para acceder a una componente del registro, el nombre del registro es seguido por un punto (.) y el correspondiente identificador del campo. As, si
quisiramos asignar a z el valor 3 + 5 i, pondramos
z.re := 3; z.im := 5;

Pg. 96

Bsqueda y clasificacin

a b
re im
Figura 10.2: Esquema del registro de tipo complejo en memoria.
Si z 0 es otro nmero complejo, podemos hacer la asignacin
zp := z;
mientras que la suma z 00 = z + z 0 puede expresarse como:
zpp.re := z.re + zp.re; zpp.im := z.im + zp.im;

Las dos operaciones vlidas para variables de registro (completas) son la seleccin de componentes y la
asignacin.

Es decir, la asignacin zpp := zp + z es incorrecta, as como la pregunta


zp = z.
Es importante destacar tambin la localidad de los nombres de los campos
de un registro. La declaracin
var a: array[2..8] of integer;
a: real;
es incorrecta, pero la declaracin
var a: array[2..8] of integer;
b: record
a: real;
b: boolean
end;
es correcta, pues (en este caso) b.a indica la componente a de b, mientras que
a indica un arreglo. Del mismo modo, poniendo solamente b sabemos que nos
estamos refiriendo al registro b y no a una de sus componentes.
Cuando trabajamos con un registro en Pascal, es posible acceder directamente a sus componentes mediante la sentencia with (en castellano, con).
Por ejemplo, para asignar a z el valor 3 + 5 i, podramos poner:
with z do begin re := 3; im := 5 end;
Problema 10.9. Suponiendo que declaramos el tipo complejo como ms arriba:
a) Hacer procedimientos para leer y escribir un complejo por terminal.
b) Recordando que el producto de complejos se define como
(a + b i) (c + d i) = (ac bd) + (ad + bc) i,
hacer un programa para calcular la suma y el producto de dos complejos
entrados por terminal.
$
Problema 10.10. Supongamos que queremos trabajar con un arreglo de registros, en cada uno de los cuales se guarda un nmero, por ejemplo correspondientes a el cdigo del curso y un nmero de identificacin del alumno. Declaramos

10.4. Registros (records)

const MAXN = 20;


type
tipoinfo = record curso: integer; nroid: integer end;
tipoarreglo = array[0..MAXN] of tipoinfo;
Una variante del procedimiento leerarreglo (captulo 8, pg. 77), para leer
datos es:
procedure leerdatos(var a: tipoarreglo; var n: integer);
function nuevodato: boolean;
begin
if (n > MAXN) then nuevodato := false
else begin
writeln;
write(Entrar el dato , n:2);
writeln( (fin = <retorno>): );
write(
Entrar el codigo del curso: );
if (eoln) then begin readln; nuevodato := false end
else begin
with a[n] do begin
readln(curso);
write( Entrar el numero de id: );
readln(nroid)
end;
nuevodato := true
end
end
end;
begin
writeln(** Entrada de datos);
n := 1;
while (nuevodato) do n := n + 1;
n := n - 1
end;
Hacer un procedimiento para escribir arreglos de este tipo, y hacer un pro$
grama incorporando ambos procedimientos para probarlos.
Problema 10.11 (Clasificacin de arreglos de registros). Supongamos
que hemos hecho las declaraciones del problema 10.10, y el arreglo a es de tipo
tipoarreglo. Podemos clasificar a segn curso o segn nroid . La componente
por la cual se clasifica se denomina llave o clave (en ingls key). Por ejemplo si
tenemos el registro x con el curso 8 y el nmero de identidad 5, y el registro
y con el curso 3 y nmero de identidad 10, ordenndolos (de menor a mayor)
por curso vendr primero y antes que x, pero si los ordenamos por nroid vendr
primero x.
a) Hacer una funcin mayor(x, y, llave) que retorna un valor lgico, y donde x, y son del tipo tipoinfo 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 > y.z
donde z puede ser curso o nroid segn el valor de llave.
b) Modificar alguno de los mtodos de clasificacin vistos, de modo de poder
clasificar un arreglo de registros, cambiando la comparacin entre datos de
la forma x > y por mayor(x, y, llave).

Pg. 97

Pg. 98

Bsqueda y clasificacin
c) Hacer un programa para ingresar el arreglo a y clasificarlo segn curso o
nroid a eleccin del usuario, escribiendo por pantalla el resultado.
$
- Un mtodo de clasificacin se llama estable si el orden relativo de elementos
con llaves iguales permanece inalterado en el proceso de clasificacin.
Por ejemplo, el mtodo usado en el problema 10.11 ser estable si ordenando primero por nroid y despus por curso, personas con el mismo
nmero curso aparecen en orden creciente de nroid .
El mtodo de seleccin directa no es estable, mientras que insercin e
intercambio lo son (dependiendo de cmo se implementen).

Problema 10.12. Queremos escribir un programa que tome como entradas


caracteres (un carcter por lnea y fin de entrada con <retorno> sin datos),
y como salida escriba los caracteres ingresados y la cantidad de apariciones,
ordenados decrecientemente segn la cantidad de apariciones.
Por ejemplo, si la entrada son los caracteres a , b , c , c , d , b , c ,
d , c , b , b , c , (uno por lnea), la salida debe ser algo como:
Caracter
c
b
d
a

Cantidad de apariciones
5
4
2
1

Para esta tarea vamos a construir los tipos de datos


type
tipoinfo = record letra: char; cuenta: integer end;
tipoarreglo = array[1..MAXN] of tipoinfo;
A medida que se ingresa una letra la comparamos con las letras ya ingresadas
mediante bsqueda lineal (eventualmente con centinela al final). Si la letra ya
apareci, se incrementa en 1 el campo cuenta correspondiente, en otro caso, se
agrega al arreglo un nuevo registro con la nueva letra, poniendo cuenta = 1.
Luego se puede ordenar el arreglo de registros segn el campo cuenta, en
forma similar a la del problema 10.11.
Hacer un programa con estas ideas.
$

10.5. Comentarios Bibliogrficos


Los temas de bsqueda (con centinela y binaria) y clasificacin estn tomados
del libro de Wirth [12] como mencionamos. Partes de la seccin 10.4 estn
basadas en el libro de Jensen y Wirth [4, cap. 7].

Captulo 11

Recursin
Teniendo en mente las sumas de Gauss del problema 4.13, supongamos que
queremos encontrar todas las sumas
s1 = 1,

s2 = 1 + 2,

s3 = 1 + 2 + 3,

...

sn = 1 + 2 + + n.

(11.1)

Podramos calcular cada una de ellas separadamente, por ejemplo usando la


frmula de Gauss an = n (n + 1)/2, pero tambin podramos poner
s1 = 1,

s2 = s1 + 2,

s3 = s2 + 3,

...

sn = sn1 + n,

(11.2)

como hemos visto en la seccin 5.2.3.


Claro que no habra mucha diferencia con el esquema que vimos en el problema 4.13, en el que cambiando la variable suma por un arreglo tendramos
suma[0] := 0; for i := 1 to n do suma[i] := suma[i-1] + i
calculando en cada paso del lazo for la cantidad si .
Cambiando suma por producto en las ecuaciones (11.1) o (11.2), obtenemos
el factorial (recordar el problema 4.14), y en realidad es usual definir n! como
0! = 1

n! = n (n 1)! para n N.

(11.3)

Estas dos condiciones, la de valor inicial y la frmula para obtener los


siguientes, nos permiten calcular n! para cualquier n N. Por ejemplo, si queremos calcular 4! usando la ecuacin (11.3), tendramos sucesivamente:
4! = 4 3! = 4 (3 2!) = 4 3 (2 1!) =
= 4 3 2 (1 0!) = 4 3 2 1 1 = 24.
Cuando se dan uno o ms valores iniciales y una frmula para calcular los
valores subsiguientes, decimos que se ha dado una relacin de recurrencia. Estas
relaciones dan lugar a lo que se llama induccin en matemtica y recursin en
programacin.
Adems de las sumas de Gauss y el factorial, ya hemos visto otras relaciones
de recurrencia. Por ejemplo, la misma potencia,
x0 = 1

xn = x xn1

para n N,

o el clculo de la funcin cm = saldo(c, r, p, m) en el problema 7.5 segn la


ecuacin (7.1), donde c0 = c.
Los nmeros de Fibonacci tambin satisfacen una relacin de recurrencia,
dada por la ecuacin (5.5).

Pg. 100

Recursin

- La importancia de las relaciones de recurrencia es tal que en cursos de matemtica discreta se estudian las soluciones de las relaciones de recurrencia
lineales, homogneas y con coeficientes constantes, que son de la forma
an = A an1 + B an2

para n > 1,

()

donde A y B son constantes, y a0 y a1 son dados. Se ve que si la ecuacin


cuadrtica caracterstica, x2 = A x + B, tiene dos races distintas r y r0
(podran ser complejas), entonces los an en () se pueden expresar como
n

an = c rn + c0 (r0 ) ,
y la frmula de Binet (5.6) es un caso particular de esta ecuacin.
A su vez, estas relaciones estn muy emparentadas con las ecuaciones
diferenciales. Por ejemplo, la ecuacin () tiene el correlato
y 00 = A y 0 + B y,
donde y = y(t), y se dan los datos iniciales y(0) y y 0 (0), y se resuelve
usando la misma ecuacin caracterstica.

Veamos cmo se traducen estas ideas a la programacin.

11.1. Funciones y procedimientos definidos recursivamente


En los problemas siguientes vamos a encontrar varios ejemplos que anteriormente resolvamos con un lazo for o similar, ahora reescritos en forma recursiva.
Problema 11.1 (Factorial recursivo). El siguiente esquema muestra una
funcin recursiva, i.e., que se llama a s misma, para calcular n!, basado en la
ecuacin (11.3):
function factorial(n: integer): integer;
begin
if (n > 1) then factorial := n * factorial(n - 1)
else factorial := 1
end;
- Observar que la variable sobre la que se hace la recursin, en este caso n,
no tiene antepuesta la palabra var en la definicin de la funcin. 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.
- Ahora podemos apreciar un poco ms por qu la funcin (o procedimiento) no es exactamente una variable local a la funcin (recordar el problema 7.2.h)).

a) Hacer un programa para calcular n! usando la funcin anterior.


b) Obtener el mximo valor de n para el cual se puede calcular n! (i.e., n!
maxint) en la versin del inciso a).
En vista de este resultado, es conveniente cambiar el tipo de los valores
retornados por factorial de integer a real en la funcin en a) (como hemos
hecho en los problemas 4.13 y 4.14).
c) La frmula de Stirling establece que cuando n es bastante grande,

n! nn en 2n.

11.1. Funciones y procedimientos definidos recursivamente

Pg. 101

Hacer un programa para calcular esta aproximacin, y probarla con n =


10, 100, 1000. Comparar con los resultados obtenidos en a) (habiendo hecho
las modificaciones indicadas en el inciso b)).
$
Expliquemos un poco cmo funciona recursin.
Una funcin o procedimiento que no usa recursin (no se llama a s misma), ocupa un lugar en la memoria al momento de correr el programa. Ese
lugar contiene las cajas correspondientes a sus variables locales, y tambin
las instrucciones que debe seguir (recordar la figura 7.2, pg. 70).
Cuando la funcin o procedimiento se llama a s misma, podemos pensar
que al ejecutarse el programa se generan automticamente copias de la funcin
(tantas como sean necesarias), cada copia con sus propios lugares para cdigo
y variables locales. Cuando la copia de la funcin o procedimiento que ha sido
llamada por la recursin termina su tarea, el espacio es liberado. Este espacio de
memoria usado por recursin no est reservado por el programa en el momento
de compilar (como sucede con los arreglos y las funciones declaradas), pues no
puede saber de antemano cuntas veces se usar la recursin. Por ejemplo, para
calcular n!, se necesitan unas n copias de la funcin: cambiando n cambiamos
el nmero de copias necesarias. Este espacio de memoria especial se llama stack
o pila, estructura que veremos tambin en el problema 11.7 y un poco ms
formalmente en la seccin 12.1.
- Un resultado de computacin terica dice que toda funcin o procedimiento
recursivo puede reescribirse sin usar recursin con lazos while y arreglos
(que se usan como pilas para ir guardando los datos intermedios).

Problema 11.2. Hacer programas que usen funciones recursivas en vez de lazos
for, while o repeat para los siguientes casos:
a) Calcular xn cuando n N, como en el problema 7.2.
b) Dar una definicin inductiva del mnimo de un arreglo (a1 , a2 , . . . , an ) de
enteros, y usarla para definir una funcin en un programa como en el problema 6.4.
c) Rehacer el problema 7.5, cambiando la definicin de la funcin saldo a una
recursiva, segn la ecuacin (7.1).
$
La recursin tambin puede usarse en procedimientos:
Problema 11.3. Usando el procedimiento recursivo
procedure imprimir(a: tipoarreglo; n: integer);
begin
if (n > 1) then imprimir(a, n-1);
writeln(a[n]) (* o write para caracteres o... *)
end
hacer un programa para imprimir un arreglo a = (a1 , a2 , . . . , an ) de enteros.
Qu pasa si se cambia el orden de los renglones (i.e., primero writeln...
y despus if...)?
$
La recursin se puede usar en ms de un argumento, i.e., no siempre se
llama a la funcin con n 1.
Problema 11.4. Usando recursin, reescribir el algoritmo de Euclides (seccin 5.2.1) para encontrar el mximo comn divisor entre dos enteros positivos.
Sugerencia: mcd(a, b) = mcd(a b, b) si a > b.
$

Pg. 102

Recursin

Problema 11.5. Hacer un programa para calcular el n-simo nmero de Fibonacci (ver problema 5.20), donde n N es ingresado por el usuario, usando
recursin sobre dos parmetros con dos condiciones iniciales.
Sugerencia: if n > 2 then fib := fib(n-1) + fib(n-2) else fib := 1.
$
Problema 11.6. Para m, n N, consideremos una cuadrcula rectangular de
dimensiones m n (4 3 en la figura 11.1), e imaginmosnos que se trata
de un mapa, donde los segmentos son calles y los puntos remarcados son las
intersecciones.
1

10

20

35

10

15

Figura 11.1: Contando la cantidad de caminos posibles.


Nos preguntamos de cuntas maneras podremos ir desde la esquina ms
hacia el sudoeste, de coordenadas (0, 0), a la esquina ms hacia el noreste, de
coordenadas (m, n), si estamos limitados a recorrer las calles nicamente en
sentido oesteeste o surnorte, segn corresponda.
Para resolver el problema, podemos pensar que para llegar a una interseccin
hay que hacerlo desde el oeste o desde el sur (salvo cuando la interseccin est
en el borde oeste o sur), y por lo tanto la cantidad de caminos para llegar a
la interseccin es la suma de la cantidad de caminos llegando desde el oeste
(si se puede) ms la cantidad de caminos llegando desde el sur (si se puede).
Los nmeros en la figura 11.1 indican, para cada interseccin, la cantidad de
caminos para llegar all desde (0, 0) mediante movimientos permitidos.
a) Hacer un programa para calcular recursivamente la cantidad h(m, n) de
caminos para llegar desde (0, 0) a (m, n), donde m y n son ingresados por
el usuario.
b) En cursos de matemtica discreta se demuestra que
h(m, n) =

(m + n)!
(m + n) (m + n 1) (m + 1)
=
.
m! n!
n (n 1) 1

Incorporar al programa del inciso anterior una funcin con el clculo de


h(m, n) (por ejemplo, con un lazo) y comparar con el obtenido anteriormente.
c) Modificar el programa del inciso a) de modo de calcular la cantidad de
caminos cuando la interseccin (r, s) est bloqueada y no se puede pasar
por all, donde r y s son ingresados por el usuario (0 < r < m y 0 < s < n).
Sugerencia: poner h(r, s) = 0.
d) Supongamos ahora que, al revs 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
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

11.2. Los Grandes Clsicos de la Recursin

Pg. 103

cualquier camino de (0, 0) a (r, s) y despus cualquier camino de (r, s) a


(m, n).
e) De acuerdo al inciso b), la cantidad de caminos en el inciso d) es h(r, s)
h(m r, n s). Verificar que esto es cierto.
f ) Anlogamente, la cantidad de caminos del inciso c) es h(m, n) h(r, s)
h(m r, n s). Verificar que esto es cierto en el mismo programa.
$

11.2. Los Grandes Clsicos de la Recursin


En todo curso de programacin que se precie de tal, entre los ejemplos de
recursin se encuentran el factorial, los nmeros de Fibonacci, y las torres de
Hanoi.
Ya vimos el factorial y los nmeros de Fibonacci... o sea...
Problema 11.7 (Las torres de Hanoi). Segn la leyenda, en un templo
secreto de Hanoi hay 3 agujas y 64 discos de dimetro 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 inicialmente 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 dimetro menor. Cuando
los monjes terminen de transferir todos los discos a la segunda aguja, ser el fin
del mundo.
Nuestra intencin es(1) hacer un programa para realizar esta transferencia,
mostrando los pasos realizados.
Supongamos que tenemos n discos, pintados de 1 a n, de menor a mayor. Supongamos tambin 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, podramos definir
type aguja = array[0..10] of integer
y declarar, por ejemplo, var a, b, c: aguja. Usamos la posicin 0 para guardar la cantidad de discos en la aguja (e.g a0 = na ), y las siguientes posiciones,
(a1 , a2 , . . . , ana ), para indicar que en la posicin 1 (la de ms abajo) de la aguja a
est el disco a1 , en la 2 el disco a2 , etc. Inicialmente ser a = (n, n, n1, . . . , 2, 1)
(de mayor a menor), b = (0, . . .) y c = (0, . . .).
Si hay n discos en la aguja a, y ninguno en la agujas b y c, para poner el
disco ms grande en la aguja b, habr que poner los n 1 restantes en la aguja
c primero, despus colocar la n-sima en la aguja b, y volver a poner los n 1
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,...
a) Podemos hacer un procedimiento recursivo pasar (k, x, y, z) que pase k
discos de la aguja x a la y usando la z,(2) imprimiendo los discos que hay
en cada aguja al terminar. Para k = 1 slo 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)
(1)

Quin lo duda!
Con movimientos permitidos: recordar que el disco a pasar de una aguja a otra debe ser
menor que cualquier otro disco en esas agujas.
(2)

Pg. 104

Recursin
como consistente en los pasos pasar (k 1, x, z, y); pasar (1, x, y, z) y luego
pasar (k 1, z, y, x):

procedure pasar( k: integer; var x, y, z: aguja);


(* pasar k discos de x a y, usando z *)
begin
if (k > 1) then begin
pasar(k-1, x, z, y);
pasar(1, x, y, z);
pasar(k-1, z, y, x)
end
else begin (* pasar una de x a y *)
y[0] := y[0] + 1;
(* ponemos uno mas en y *)
y[y[0]] := x[x[0]]; (* que es el de arriba en x *)
x[0] := x[0] - 1;
(* y lo sacamos de x *)
imprimir
end
end;
donde hemos supuesto un procedimiento imprimir que imprime los elementos 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.
Ver que el procedimiento es correcto.
b) Podra cambiarse el procedimiento anterior poniendo
.
.
.
if (k > 1) then begin
pasar(1, x, z, y);
pasar(k-1, x, y, z);
pasar(1, z, x, y)
end
.
.
.

c) Usar el procedimiento del inciso a) en un programa que imprima una sucesin de movimientos para transferir n discos de una aguja a otra. Verificar
el programa para n = 1, 2, 3, 4, 5.
d) 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 programa.
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+ +2n1 = 1+2(1+2+ +2n2 ) =
1 + 2(2n1 1).
e) Suponiendo que transfieren un disco por segundo, cunto tardarn los monjes en transferir los 64 discos? Cuntos aos tardara una computadora en
calcular la solucin para n = 64, suponiendo que tarda un nanosegundo por
movimiento(3) (nano = dividir por mil millones)? Bajo la misma suposicin
sobre la velocidad de la computadora, cul es el valor mximo de n para
calcular los movimientos en 1 minuto?
- En el problema 11.8 se da una variante para resolver el problema de las
torres de Hanoi sin usar arreglos.
- La estructura que estamos usando para los arreglos, agregando atrs y
(3)

Y que no hay cortes de luz!

11.2. Los Grandes Clsicos de la Recursin

Tapa del juego original

Pg. 105

Fotografa del juego

Figura 11.2: Las torres de Hanoi.


sacando tambin desde atrs, es un ejemplo de pila (como la de platos),
sobre la que hablaremos ms en la seccin 12.1.
En realidad, las torres de Hanoi es un juego inventado en 1883 por el
matemtico francs Edouard Lucas (18421891), quien agreg la leyenda. El
juego, ilustrado en la figura 11.2, usualmente se les da a los chicos con entre
4 y 6 discos, a veces de distintos colores. Notablemente, variantes del juego se
usan en tratamiento e investigacin de psicologa y neuro-psicologa.
Lucas es ms conocido matemticamente por su test de primalidad
esencialmente el que ms se usa en la actualidad para determinar si un
nmero es primo o no.
Hay muchas variantes del problema de las torres de Hanoi. Por ejemplo,
que los discos no estn inicialmente todos sobre una misma aguja, o que haya
$
ms de tres agujas.

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. Ms an, cambi arreglos
por caracteres!:
program hanoisinarreglos(input, output);
(*
Solucion recursiva de las torres de Hanoi,
sin usar arreglos.
*)
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 begin
write(pasar el disco 1);
writeln( de ", x, " a ", y,")

Pg. 106

Recursin

end
end;
begin
writeln(** Problema de las torres de Hanoi:);
writeln(
Pasar n discos de la aguja "a" a la "b");
write(
usando la "c", mediante movimientos );
writeln( permitidos.);
writeln;
write( Entrar el numero de discos: ); readln(n);
writeln;
pasar(n, a, b, c);
writeln; writeln(** Fin **)
end.
Es correcto el programa de Flor? Por qu?

11.3. Comentarios Bibliogrficos


Las imgenes de la figura 11.2 fueron tomadas (en el 2006) de http://www.
cs.wm.edu/~pkstoc/ y http://en.wikipedia.org/wiki/Tower_of_Hanoi respectivamente.

Captulo 12

Objetos combinatorios
Muchos de los problemas que vimos en el captulo anterior se pueden reescribir sin recursin con lazos for, como el factorial, seleccin directa, los nmeros
de Fibonacci, o an la cantidad de caminos del problema 11.6, y nos quedamos
con la impresin de que recursin no es muy til. Otro problema muy distinto
al de contar objetos es generarlos, por ejemplo para encontrar alguno o todos
los que satisfacen cierto criterio, y aqu es donde recursin muestra toda su
potencia.
Puesto que el nmero de objetos a generar puede ser muy grande, como 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
los objetos. Una estructura particularmente til para esta modificacin es la de
pila, con la que comenzamos.

12.1. Pilas y colas


Las pilas, como las de platos, y las colas, como las de supermercados, son dos
estructuras familiares que trataremos de formalizar un poco aqu, ms que nada
para poner en perspectiva lo que estamos haciendo y no siendo estrictamente
rigurosos.
Tanto en pilas como en colas podemos pensar que tenemos una serie de
objetos todos del mismo tipo esperando en lnea 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 comnmente cola (sin aditivos), o
ms formalmente cola fifo, por first in first out, el primero en llegar es el primero
en ser tratado.
No es difcil imaginar distintas acciones comunes:
Crear o inicializar la cola, construyendo la cola vaca.
Agregar un elemento, ubicndolo al final y actualizando la cola.
Quitar un elemento, tomando, segn corresponda, el primero o el ltimo
y actualizando la cola.
Destruir la cola, cuando no la necesitamos ms, limpiando lo que ensuciamos.
En fin, podramos agregar otras, como averiguar el estado de la cola (si est
vaca o llena).
Es tradicional en computacin llamar con distintos nombres a estas acciones,
segn se trate de una pila o cola. Por ejemplo, agregar un elemento a una pila se

Pg. 108

Objetos combinatorios

llama push o empujar y en la cola put o poner. En cambio, el quitar un elemento


se llama pop o saltar para pilas, y get u obtener para colas.
- Los nombres push y pop para pilas sugieren que una imagen ms adecuada
que la pila de platos son los monederos cilndricos con un resorte, como
tienen algunos taxistas, en los que se ponen las monedas empujando.

No es casualidad que put y get sean nombres en Pascal de funciones o procedimientos para entrada o salida (que no cubriremos, siendo ms 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 muchsimas generalizaciones, constituye un
tipo de datos abstracto (TDA): no importa tanto cmo se va a implementar o
representar en la mquina, sino saber que contamos con las operaciones de crear
o destruir la cola, agregarle o quitarle un elemento, etc.
Sin embargo, la implementacin de una cola como fifo o lifo puede tener
consecuencias decisivas en los algoritmos, como veremos en el captulo 14. Adems, en lenguajes no tan abstractos como Pascal, debemos tener cuidado en la
implementacin de TDA. Tanto con Pascal como con el lenguaje C se pueden
usar punteros y listas encadenadas , que son ms apropiados para implementar
colas. Sin embargo, en este curso nos arreglaremos con arreglos.
Como sabemos, si queremos trabajar con un arreglo tenemos que declararlo
al comienzo, dando su dimensin y el tipo de elementos. Por lo tanto, si vamos a
representar una cola con un arreglo, no podemos crearlo de la nada, y tenemos
que prever su existencia y dar una dimensin adecuada al problema. Del mismo
modo, no podemos destruirlo, y nos limitaremos a ignorarlo cuando no lo
necesitemos ms.
Suponiendo que hemos declarado el tipo dato, que puede ser un registro u
otro arreglo, podemos implementar una pila declarando
pila: array[1..MAXP] of dato
donde MAXP es una constante apropiada. Si npila es la cantidad de elementos,
declarado con la misma localidad que pila, podemos hacer:
Crear la pila:
procedure inicializar;
begin npila := 0 end;
Agregar un elemento e:
procedure push(e: dato);
begin
if (npila < MAXP) then begin
npila := npila + 1; pila[npila] := e end
end;
- Siguiendo con la filosofa 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.

Quitar un elemento:
procedure pop(var e: dato);
begin
if (npila > 0) then begin

12.2. Generando Subconjuntos

Pg. 109

e := pila[npila]; npila := npila - 1 end


end;
- Valen los mismos comentarios que para push: no hemos previsto
acciones para cuando se quiera sacar un elemento de una pila
vaca. Habr que agregar mensajes si existiera esa posibilidad.

Observar la simetra entre las acciones de push y pop: una realiza exactamente los pasos inversos de la otra.
Para colas (fifo), las cosas son ligeramente diferentes. En vez de tener un
ndice como en el caso de la pila, mantenemos dos: uno, digamos ppocola, sealando el principio de la cola en el arreglo, y otro, digamos fincola, sealando
el final. fincola se incrementa al agregar un dato, mientras que ppocola aumenta cuando se extrae un dato, de modo que los elementos vivos en principio
estarn entre ppocola y fincola (inclusivo en ambos casos).
Claro que si la cola est definida como un arreglo de MAXC elementos,
las cosas se complican cuando agregamos ms de MAXC elementos a la cola,
an cuando hayamos quitado algunos y haya menos de MAXC elementos en
la cola. En este caso necesitamos usar aritmtica mdulo MAXC . No entramos
en detalles porque en los ejemplos que daremos en los captulos posteriores
supondremos que MAXC es lo suficientemente grande como para evitar este
problema.
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,
por ejemplo en el programa renglon (pg. 155). Tambin hemos visto un atisbo
del uso de pilas en el problema 11.7 de las torres de Hanoi, donde las agujas
eran pilas en s.
En la prxima seccin veremos que la tcnica de mezclar recursin con pilas
puede usarse para generar distintos objetos combinatorios, aunque las pilas no
siempre aparecern explcitamente.

12.2. Generando Subconjuntos


Problema 12.1 (Cadenas de bits). Supongamos que queremos imprimir
todas las cadenas de bits (arreglos de 0 y 1) de longitud n.
Consideramos un (nico) arreglo a donde pondremos las cadenas que construimos (y que sern destruidas al construir nuevas cadenas). Por comodidad,
pensamos que a y n son globales, y tenemos las declaraciones:
const MAXN = 10;
..
.
type tipoarreglo: array[1..MAXN] of integer;
..
.
var
a: tipoarreglo;
n: integer;
..
.
Dado que las cadenas de bits de longitud n se obtienen agregando un 0 o un
1 a las cadenas de longitud n 1, para construirlas podemos hacer la llamada
en el cuerpo principal a cadena(1), donde cadena est dada por:
procedure cadena(k: integer);

Pg. 110

Objetos combinatorios

var i: integer;
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 haceralgo(a, n) (* hacer algo cuando k = n *)
end (* for *)
end;
En este caso, haceralgo(a, n) ser un procedimiento para imprimir los valores
a1 , . . . , an .
Observar que usamos al arreglo a como una pila. Para k fijo, el elemento en
la posicin k tiene un valor de 0 cuando i = 0, que se mantiene para valores
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 el parmetro del procedimiento sobre el cual se hace la recursin, y no una variable
global.
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
longitud n, siguiendo las indicaciones anteriores.
c) Agregar un contador (global) para contar las cadenas de longitud n, y ver
que la cantidad de cadenas es 2n .
d) En el procedimiento cadena propuesto se va hacia adelante, llamando a
cadena(1) en el cuerpo principal, y aumentando el valor del argumento k
en cada llamada del procedimiento hasta llegar a k = n, ya que n es global.
Esto contrasta con el uso de recursin en, por ejemplo, el factorial, donde
vamos hacia atrs disminuyendo el valor del argumento en cada llamada.
Redefinir el procedimiento cadena de modo que en l se compare k con
0 o 1 en vez de n, y se haga la llamada cadena(n) en el cuerpo principal. $
La tcnica que hemos usado es bastante general y puede usarse en muchos
problemas. Sin embargo, para encontrar las cadenas de bits no es necesario usar
recursin:
Problema 12.2. Resolver el problema 12.1 sin usar recursin, usando que las
cadenas de bits de longitud n pueden pensarse como los coeficientes en base 2
de los nmeros entre 0 y 2n 1. Sugerencia: para k = 0, . . . , 2n 1, construir
$
la lista de coeficientes en base 2 e imprimirla.
Problema 12.3 (Generando subconjuntos). En el problema 12.1 esencialmente se construyen todos los subconjuntos de {1, 2, . . . , n}, ya que las cadenas de bits de longitud n se pueden considerar como vectores caractersticos. Dado un conjunto A {1, 2, . . . , n} definimos su vector caracterstico,
b(A) = (b1 , b2 , . . . , bn ), mediante
(
1 si i A,
bi =
0 si no.
Es claro que dos conjuntos distintos tienen vectores caractersticos 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 biunvoca
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 vaco con una raya -.

12.3. Caminante, no hay caminos...

Pg. 111

Por ejemplo, si n = 2 la salida debera ser algo como:


-

1 2

Una vez que sabemos cmo generar una familia de objetos, es ms sencillo
generar o contar objetos de la familia con caractersticas particulares, como
vemos en los siguientes problemas.
Problema 12.4. Supongamos que queremos contar la cantidad h(n) de cadenas
de n bits que no contienen dos ceros sucesivos:
a) Hacer un programa para calcular h(n) para n N, usando el programa
del problema 12.1 y donde el procedimiento haceralgo en vez de imprimir
haga una variante de bsqueda lineal para encontrar dos 0 consecutivos en
cada cadena construida, verificando si se trata de una cadena vlida antes
de aumentar un contador adecuado. Sugerencia: recordar el problema 9.3.
b) Comparar el nmero h(n) obtenido anteriormente con los nmeros de Fibonacci y hacer un nuevo programa para calcular h(n) directamente.
$
Problema 12.5. Hacer un programa que dado k y n, k, n Z, 1 k n,
cuente e imprima todos los subconjuntos de {1, . . . , n} con k elementos.
`
n!
- Hay nk = k!(nk)!
subconjuntos.
$

12.3. Caminante, no hay caminos...


Problema 12.6 (Generando caminos). En este problema imprimiremos todos los caminos que hemos contado en el problema 11.6.
Para ello consideramos una arreglo global camino, que usaremos como pila,
y en el que guardaremos las intersecciones o esquinas por las que hay que
pasar. Como stas tienen dos coordenadas, digamos x y y, declaramos
..
.
type esquina = record x, y: integer end;
..
.
var camino: array[1..MAXK] of esquina;
..
.
donde MAXK es una constante para la mxima longitud del camino.
En el problema original todos los caminos tienen longitud m+n, pero podran
ser distintas si, por ejemplo, tambin considerramos la posibilidad de ir en
diagonales de suroeste a noreste. Para nuestros ejemplos, 10 o 20 son valores
razonables para MAXK .
Tambin consideramos a los datos m y n como variables globales, y agregamos la variable global k que indicar la cantidad de elementos en la pila.
El trabajo lo har el procedimiento llegardesde:
procedure llegardesde(i, j: integer);
begin
k := k + 1; (* poner una piedrita *)
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 *)
haceralgo(camino, n);

Pg. 112

Objetos combinatorios

k := k - 1 (* sacar la piedrita *)
end;
donde haceralgo es un procedimiento para imprimir los valores camino 1 ,. . . ,
camino r , y haremos la nica llamada llegardesde(0,0) en el cuerpo principal.
Estudiemos el procedimiento llegardesde, haciendo una prueba de escritorio en todo caso:
La recursin se hace sobre i y j, mientras que k es la cantidad de elementos
en la pila. Es decir, k es la cantidad de esquinas en el camino, y por lo
tanto su longitud.
Inicialmente deber ser k = 0, puesto que no tenemos camino.
A diferencia del problema 12.1, k es global y no el parmetro del procedimiento.
Al entrar al procedimiento, k se incrementa en 1, y se incorpora la interseccin (i, j) al camino en la (nueva) posicin k .
El comentario poner una piedrita se refiere a dejar una marca o
huella all para saber por dnde pasamos cuando volvemos.
Si i < m o j < n, se puede continuar (hacia la derecha o hacia arriba),
por lo que llamamos al procedimiento recursivamente, incrementando los
valores respectivos. Observar que no hay un else: queremos que cada
caso contribuya con un camino distinto.
Si i = m y j = n, hemos llegado a la esquina deseada, e imprimimos el
camino.
Una vez recorridos los caminos que siguen hacia el este, o el norte, o impreso el camino, debemos retornar, borrando nuestras huellas para permitir
que la posicin k pueda ser ocupada por otra esquina. Por eso se pone la
instruccin k := k - 1 al terminar el procedimiento.
En otras palabras, al terminar quitamos de la pila camino el elemento
(la piedrita) que habamos puesto al comienzo.
a) Hacer un programa con estas instrucciones, probando con valores pequeos,
e.g., m = 3 y n = 2 mientras se van corrigiendo los errores (recordar poner
k := 0 y la llamada llegardesde(0,0)).
b) Qu pasa si se elimina la instruccin k := k - 1 en el procedimiento
llegardesde?
c) Agregar un contador, de manera de ir contando la cantidad de caminos
encontrados y que la salida sea algo como
1:
2:
3:

(0,0) (1,0) (2,0) (2,1)


(0,0) (1,0) (1,1) (2,1)
(0,0) (0,1) (1,1) (2,1)

Hay 3 caminos
cuando m = 2 y n = 1.
d) Como en el problema 12.1.d), no hay ningn misterio en ir hacia adelante.
Cambiar el procedimiento llegardesde por otro, digamos llegara, de modo
que en el cuerpo principal se haga la llamada llegara(m,n) en vez de
llegardesde(0,0) (atencin en imprimir el camino al derecho y no al
revs).
$

12.4. Generando permutaciones

Pg. 113

12.4. Generando permutaciones


Otros objetos combinatorios de inters son las permutaciones: una permutacin 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 parntesis, ( ),
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.
Problema 12.7 (Generando permutaciones). En este problema, copiando
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
(* buscar y agregar los que faltan *)
for j := 1 to n do
if (falta[j]) then begin
a[k] := j; (* poner j en la posicion k *)
if (k < n) then begin
(* ir a la proxima posicion,
pero j no se puede usar *)
falta[j] := false;
poner(k+1);
(* al volver, dejar libre a j para
poder ponerlo en otra posicion *)
falta[j] := true
end (* if k < n *)
else haceralgo (* cuando k = n *)
end (* if falta[j] *)
end;
Observamos que:
Los arreglos a y falta son globales y deben tener una longitud adecuada (10
es suficiente para nosotros). Tambin n es global.
a tiene la estructura de pila (de longitud k ) en el que guardamos la permutacin que estamos construyendo.
El tratamiento de k es similar al problema 12.1, en el sentido que k es el
parmetro del procedimiento sobre el que se hace la recursin, y no hacemos
(demasiado) explcita la inclusin o eliminacin de elementos.
Esto es a diferencia del tratamiento en el problema 12.6, donde k es
global, y se incrementa y disminuye explcitamente en cada paso de recursin.
falta es un arreglo cuyos elementos son de tipo lgico (y tiene la misma
longitud del arreglo a), de modo que falta i es falso o verdadero de acuerdo a
si el elemento i ya ha sido colocado o no (respectivamente) en la permutacin.
Inicialmente faltan todos los elementos en la permutacin, i.e., falta i
es verdadero para todo i = 1, . . . , n.
Podramos pensar que falta tambin es una pila, en la que ponemos valores
verdadero o falso, pero esto no es as pues cambiamos los valores en lugares
arbitrarios, no necesariamente el ltimo.

Pg. 114

Objetos combinatorios

En cualquier paso, cuando se agrega el elemento j en la posicin k, se pone


falta[j] := false, se consideran las posiciones siguientes (incrementando
k) y al volver se vuelve a poner falta[j] := true. Este borrado de huellas es similar al hecho en el procedimiento llegardesde del problema 12.6
cuando primero se incrementaba k, se hacan cosas, y luego se disminua al
terminar.
Se hace la nica llamada poner(1) en el cuerpo principal.
a) Usando las ideas anteriores, hacer un programa para imprimir todas las
permutaciones de n elementos.
b) Eliminar (comentndolo) el rengln falta[j] := true del procedimiento
poner , y comprobar el comportamiento.
c) Agregar un contador, como en los problemas anteriores, a fin de ir contando
las permutaciones obtenidas, imprimiendo cada permutacin con su nmero
de orden.
- Hay varios algoritmos para generar las permutaciones de {1, . . . , n}, algunos bastante diferentes al que presentamos, y otros ms eficientes. Observar
que con el presentado, las permutaciones obtenidas estn ordenadas lexicogrficamente, cosa que no ocurre en otros algoritmos.
$

Problema 12.8. Hacer un programa que dado k y n, k, n Z, 1 k n,


imprima todas las permutaciones (v1 , v2 , . . . , vk ), con vi {1, . . . , n}, entendiendo por permutacin a que no haya elementos repetidos, pero el orden es
importante.
- Hay

n!
(nk)!

de estas permutaciones.

- Comparar con el problema 12.5.


- Una posibilidad es truncar las permutaciones generadas en el programa
del problema 12.7. Otra es usar justamente el problema 12.5 y para cada
subconjunto usar las permutaciones de k elementos.
$

12.5. Objetos combinatorios generados al azar


La cantidad de subconjuntos es enorme para n ms o menos grande, por
ejemplo, para 220 es aproximadamente un milln. Peor an es el caso de de
permutaciones, para n = 10 hay 10! = 3 628 800 permutaciones. En estos casos,
ya sea porque no queremos o no podemos, debemos contentarnos con generar
uno o unos pocos objetos al azar.
En general, podemos pensar en tomar cualquiera de los algoritmos que usamos para generar todos los objetos de cierto tipo, y cambiar en lugares apropiados por un nmero aleatorio.
Problema 12.9. Usando la funcin random de Turbo Pascal, encontrar aleatoriamente un subconjunto de {1, . . . , n}, decidiendo para cada elemento si se
agrega o no con probabilidad 1/2.
- O sea, es equivalente a generar primero una cadena de n bits aleatoriamente
y luego expresarla como conjunto.
$

En el caso de las permutaciones tambin est el inters en los juegos, por


ejemplo generar una mano de cartas.
Problema 12.10 (Generando manos aleatoriamente). Maggie quiere
jugar cada semana al Quini, y pens en generar con la computadora 6 nmeros
al azar entre 0 y 45, usando un programa Pascal.

12.5. Objetos combinatorios generados al azar

Pg. 115

Primero pens en ir sacando nmeros al azar, guardando aqullos que fueran


diferentes a los que ya haba sacado. Acordndose que en el apunte del curso
deca que en general se podra tomar como base la versin para generar todos
los objetos, se plante un esquema como:
for k := 1 to 6 do begin
repeat j := random(46) until falta[j];
a[k] := j; falta[j] := false
end
a) Hacer un programa usando ese esquema.
Viendo que la cosa era fcil, Maggie decidi usar una variante para simular
repartir cartas en un juego, eligiendo una mano de m elementos al azar entre
{1, 2, . . . , n}, donde el orden ahora era importante, porque era para repartir las
cartas.
b) Hacer un programa con estas modificaciones. n y m (1 m n) deben ser
ingresados por el usuario, y como salida se deben imprimir los m nmeros
en el orden en que fueron sorteados.
Pero Maggie pens que si n era grande y m = n, saldran muchos nmeros
repetidos antes de llegar a elegir el ltimo.
c) Agregar un contador al programa del inciso anterior para contar la cantidad
de veces que se usa random, y probarlo para n = 10, 30, 100 y m = n. Ver
que, efectivamente, la cantidad de veces que se usa random crece rpidamente. Pero cuando n = 46 y m = 6, como en el caso del Quini, hay muy
pocas llamadas a random adicionales.
Maggie record algo vagamente que en la parte de mtodos de clasificacin elementales se haca mencin a las cartas que uno tomaba para despus
ordenarlas. Se le ocurri entonces empezar con las cartas numeradas de 1 a n,
ordenadas crecientemente, sacar una al azar, de las restantes sacar una al azar
(entre 1 y n 1), y as sucesivamente, hasta sacar las m pedidas. Claro, la cosa
se complicaba al programar porque tena que correr las cartas del final, para
tapar el agujero que dejaba la que sala.
d) Hacer un programa con estas ideas.
As que Maggie decidi mirar la parte de clasificacin del apunte para ver el
tema de las cartas, viendo que el procedimiento anterior era bastante parecido al
mtodo de insercin directa, slo que al revs. Mirando al de seleccin directa,
le pareci que sera ms fcil para darlo vuelta. Concretamente, elegir una
carta entre 1 y n, digamos k, ponerla en la posicin 1 y poner la que estaba
en la posicin 1 en la posicin k, luego elegir una carta al azar que est entre
las posiciones 2 y n, e intercambiarla con la segunda, y as sucesivamente hasta
haber hecho m intercambios, imprimiendo luego las m primeras cartas.
e) Hacer un programa con este esquema. Debera quedar algo como
for i := 1 to n do a[i] := i;
for i := 1 to m do begin
j := 1 + random(n+1-i);
t := a[i]; a[i] := a[j]; a[j] := t
end
Maggie es asombrosa!

Pg. 116

Objetos combinatorios

- El problema de las manos es muy sencillo de plantear, y tiene aplicaciones ms all de los juegos, como en estadstica. Sin embargo, encontrar
algoritmos correctos y eficientes no es sencillo, a diferencia del caso de subconjuntos que se equiparan con las cadenas de bits (problema 12.9), que
a propsito tambin tiene aplicaciones en estadstica (para encontrar
una muestra al azar).
La correccin 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 problema es el de decidir su eficiencia: cuntos pasos realiza en trminos de n. Ninguna de estas preguntas es fcil de responder desde la teora,
pero nosotros vamos a aprovechar el trabajo que otros han hecho: los algoritmos aqu presentados son correctos, aunque el ltimo es ms eficiente
en general.
El algoritmo en e) est presentado en el libro de Knuth [8, Vol. 2,
pg. 126], donde se hacen los anlisis de correccin y eficiencia. Es atribudo
independientemente a L. E. Moses y R. V. Oakford (1963) y R. Durstenfeld
(1964). Pero existen otros algoritmos que son ms eficientes cuando m es
chico comparado con n (ver, por ejemplo, el algoritmo de R. Floyd descripto
en libro de Bentley [1, pg. 139]).
$

Captulo 13

rboles binarios ordenados


Como mencionramos al presentar los mtodos de clasificacin en la seccin 10.3, una de las mayores causas de ineficiencia al clasificar es el movimiento de datos, problema que se agudiza al mover grandes datos como
arreglos o registros. En esta seccin nos preocuparemos por dar un mtodo donde clasificamos datos pero una vez construido un registro ste no se mueve.
Hasta ahora nos hemos concentrado en la estructura lineal de los arreglos
para guardar informacin, como se ilustra en la figura 13.1, donde cada punto
o nodo representa un elemento del arreglo, y las flechas sealan al siguiente
elemento.

Figura 13.1: Una estructura lineal.


Pero podemos pensar en una configuracin como la de la figura 13.2, donde
tambin tenemos nodos unidos por flechas. Los nodos estn en distintos pisos
o niveles, marcados por las rectas punteadas (s, a medida que bajamos el nivel
aumenta).
nivel 0
nivel 1
nivel 2
nivel 3

Figura 13.2: Un rbol binario ordenado.


Si tuviramos 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 a otro cualquiera en a lo sumo
2k pasos, an cuando puede haber hasta
1 + 2 + 4 + + 2k = 2k+1 1
nodos. Esta observacin es muy importante si guardamos y buscamos datos en
los nodos: la diferencia entre las estructuras de las figuras 13.1 y 13.2 es similar
a la que hay entre bsqueda lineal y bsqueda binaria.

Pg. 118

rboles binarios ordenados

Una estructura como la de la figura 13.2 se llama rbol binario ordenado


o simplemente rbol binario.(1) El nodo de ms arriba (en el nivel 0) se llama
raz, y cada nodo est conectado hacia abajo con 0, 1 o 2 (y no ms) hos, a la
izquierda o a la derecha.
- El nombre de rbol binario ordenado parece un poco largo. Informalmente,
se llama rbol por la forma de ramas, aunque la raz se representa arriba,
binario porque cada rama se divide en a lo sumo 2, y ordenado porque
distinguimos entre el ho a la izquierda y a la derecha.
- Como vimos, un rbol binario con k niveles puede tener hasta 2k+1 1
nodos. En contrapartida, si tiene exactamente k niveles, no puede tener
menos de k + 1 nodos.

Veamos cmo usar esta estructura para guardar informacin.


Supongamos que, como en el problema 10.12, ingresamos letras, por ejemplo
b , d , c , d , a , d , a , a , d , e , d , e , a , b , a ,
en ese orden, y queremos imprimir la cantidad de veces que apareci cada una
de ellas, pero ordenadas alfabticamente, i.e., queremos una salida como
Caracter
a
b
c
d
e

Cantidad de apariciones
5
2
1
5
2

Si declaramos, en forma similar al problema 10.12,


type
tipodato = char;
(* el tipo de datos a ingresar *)
nodo = record
llave: tipodato; (* dato *)
cuenta: integer (* veces que aparecio *)
end;
tipoarreglo = array[1..MAXN] of nodo;
y guardamos los datos en un arreglo de tipo tipoarreglo, digamos arreglo,
despus de ingresar los datos como en el aquel problema, y antes de clasificarlos,
tendramos una disposicin como en la figura 13.3.
b 2

d 5

c 1

a 5

e 2

arreglo 1

arreglo 2

arreglo 3

arreglo 4

arreglo 5

Figura 13.3: Disposicin del arreglo de registros luego de ingresar los datos.
Para dar una estructura de rbol binario a los datos, hacemos lo siguiente: el
primer dato ingresado es la raz del rbol, y al ingresar cada dato subsiguiente lo
comparamos con los ya existentes, yendo hacia abajo a la izquierda si el nuevo
dato es menor o hacia la derecha si es mayor (o incrementando cuenta si es
igual). As, al terminar de ingresar los datos en nuestro ejemplo quedara un
rbol binario como el de la figura 13.4.
Despus de mirar un rato esta figura, nos damos cuenta que si proyectamos
los registros sobre una recta horizontal como en la figura 13.5, obtenemos los
(1)

Slo veremos rboles binarios ordenados.

Pg. 119

b 2
a 5

d 5
c 1

e 2

Figura 13.4: Disposicin del rbol binario luego de ingresar los datos.
registros clasificados alfabticamente. Despus de pensar otro rato ms, nos
damos cuenta que no se trata de una casualidad: en la figura 13.4 todo lo
que queda a la izquierda y abajo de un registro tiene que tener menor llave
y anlogamente hacia la derecha y abajo. As, en nuestro ejemplo, todo lo que
es menor que b , i.e. a , est a la izquierda, mientras que c , d y e estn
a la derecha. Cuando miramos a d , c est a la izquierda, pero tambin b
(que tena a d a su derecha) y a (que estaba a la izquierda de b ), etc.
a 5

b 2

c 1

d 5

e 2

Figura 13.5: Los registros de la figura 13.4 proyectados.


Nos queda la inquietud de cmo guardar las flechas en la computadora,
i.e., la informacin sobre los hos a izquierda y derecha. Para esto agregamos a
los registros dos campos donde se guardar el ndice del ho correspondiente:
type
indice = integer; (* para senialar los indices *)
tipodato = char; (* 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[1..MAXN] of nodo;
An cuando el arreglo con los datos ingresados sigue formando una pila,
ahora nos queda una disposicin como en la figura 13.6, donde con 0 indicamos
que no hay hos (a izquierda o derecha) y, claro, dibujamos las flechas slo
para orientarnos (no estn en la computadora). Observar que esta figura es
bsicamente la figura 13.3 a la que hemos agregado flechas, pero tambin es
equivalente a la figura 13.4.

b 2 4 2

d 5 3 5

c 1 0 0

a 5 0 0

e 2 0 0

Figura 13.6: Los registros con ndices para la estructura de rbol binario.
Ya estamos en condiciones de abordar el siguiente problema:
Problema 13.1. El programa arbolbinario (pg. 163), muestra la construccin
de un rbol binario ordenado usando arreglos como indicamos anteriormente.
En los nodos se guarda informacin, que en nuestro ejemplo es llave y cuenta.
En llave guardamos un entero, pero podra ser una palabra de un texto, o

Pg. 120

rboles binarios ordenados

un apellido, etc., mientras que en cuenta guardamos el nmero de veces que


apareci ese dato. En el rbol, un nodo tiene a su izquierda un subrbol 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.
Estudiando el programa, observamos que
Los nodos con la informacin se guardan en un arreglo de nodos llamado
(curiosamente!) arbol .
Hay una funcin 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
elementos: a medida que llega nueva informacin se aumenta narbol y se
incorpora al final.
Pero la informacin de la estructura de rbol bien diferente a la de
pila se guarda en los registros: en izquierda (o derecha) colocamos la
posicin en el arreglo del ho a la izquierda (o derecha). Para indicar que
no hay un ho (a izquierda o derecha) introducimos la constante nada,
que podra tomar cualquier valor imposible para ndices del arreglo
arbol y que hemos puesto en 0.
Para procesar los datos ingresados, usamos el procedimiento recursivo
binario que primero se fija si se est en una posicin vaca, i.e., sin
datos, en cuyo caso se agrega un nodo al rbol, se guarda el dato, se
coloca cuenta en 1, y se inicializan los hos a nada pues estn vacos.
Si la posicin no est vaca, 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 o derecha, de acuerdo a si es menor o mayor.
El procedimiento enorden imprime los datos en (ejem!) orden con la
cantidad de apariciones en forma recursiva: para mantener el orden, deben
imprimirse primero los descendientes a la izquierda, luego el nodo mismo,
y finalmente los descendientes a derecha. La condicin de parada para
la recursin es encontrar el valor nada.
Observar que no necesitamos intercambiar los registros para poder
imprimirlos ordenadamente (recordar la proyeccin mencionada anteriormente).
a) Cambiar la impresin en orden por post orden (primero se imprimen los
subrboles y despus el nodo) y por pre orden (primero el nodo y despus
los subrboles).
b) Cambiar el procedimiento enorden para imprimir los datos clasificados de
mayor a menor (en vez de menor a mayor).
c) Cambiar el procedimiento enorden para imprimir tambin el nivel en que
se ha alojado la informacin.
Sugerencia: agregar un argumento para el nivel, formando parte de la recursin, o, alternativamente, redefinir los nodos de modo de incluir el nivel
cuando se construyen.
Sugerencia si la anterior no alcanza: para la primer variante podramos
poner
procedure enorden(w: indice; nivel: integer);
begin
if (w <> nada) then
with arbol[w] do begin
enorden(izquierda, nivel+1);

13.1. Comentarios Bibliogrficos

Pg. 121

writeln(llave:10, cuenta:20, nivel:20);


enorden(derecha, nivel+1)
end
end;

haciendo la llamada enorden(raiz, 0) en la parte principal.


d) Agregar al programa la impresin de la profundidad (= mximo nivel) del
rbol al terminar de ingresar los datos.
e) Ver que si los datos se ingresan ya ordenados, el rbol correspondiente se
reduce bsicamente a un arreglo unidimensional: dar un ejemplo con 5 caracteres distintos, donde la profundidad sea 4.
- Para eliminar este problema, se han diseado una serie de variantes,
como rboles balanceados, rboles B, rboles de Fibonacci, etc. Observar que sacar nodos de un rbol es un proceso bastante ms complicado
que sacar nodos de una lista (ver por ejemplo el libro de Wirth [12,
cap. 4]).
$

rboles binarios estn relacionados con los temas vistos en el captulo 12 de


objetos combinatorios: crase o no, en ese captulo ya habamos visto y recorrido rboles binarios, aunque tenan una estructura particular que nos permita
ahorrar espacio.
Por ejemplo, en el problema 12.1 construamos 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 ho 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 ho a derecha como la cadena c1 de longitud
k + 1 que se obtiene agregando un 1. En la funcin recursiva del problema 12.1
el nivel que estbamos construyendo era precisamente k, y cuando llegbamos
a k = n, imprimamos la cadena.
Tambin hay rboles en los problemas 12.6 y 12.7, slo que los nodos
pueden tener ms de dos hos. A diferencia del problema de los subconjuntos
o cadenas de bits, el recorrido del rbol (que no es binario) es ms complicado
porque la descripcin de los nodos lo es.
- La variable k del procedimiento llegardesde o en el procedimiento poner indica tambin 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 tcnica de borrar las huellas que usamos en ambos casos es conocida como backtracking
o rastreo inverso.

13.1. Comentarios Bibliogrficos


El programa arbolbinario est basado en las presentaciones de Wirth [12,
pg. 210] y de Kernighan y Ritchie [7, pg. 153].

Captulo 14

Grafos
Si tomamos un conjunto de ciudades y las carreteras que las unen, y representamos grficamente a las ciudades como puntos y a las carreteras como
segmentos o curvas uniendo esos puntos, obtenemos una figura similar a la 11.6,
en la cual pensbamos que los segmentos eran calles y los puntos las intersecciones.
La idea subyacente en ambos casos es la de grafo, un conjunto de vrtices (las
ciudades o intersecciones) que indicaremos por V , y un conjunto de aristas (las
rutas o calles), que indicaremos por E, cada una de las cuales queda definida
por dos vrtices. Si llamamos al grafo G, normalmente pondremos G = (V, E).
No slo los grafos estn relacionados con calles o rutas. En comunicaciones
tambin podemos pensar que los vrtices son computadoras y las aristas representan la posibilidad de que dos computadoras se conecten entre s. O, saliendo
de las comunicaciones, podemos pensar en un rbol genealgico, donde los vrtices son las personas y las aristas relacionan padres con hos. En fin, los ejemplos
de aplicaciones de grafos son muy variadas, y muchas veces sorprendentes.
En este captulo daremos una simple, y seguramente demasiado breve, descripcin de los trminos y propiedades que usaremos, dejando para los cursos
de matemtica discreta las definiciones rigurosas y las demostraciones. En el
proceso estableceremos nomenclatura y notaciones que pueden diferir entre autores.
A fin de distinguir los vrtices entre s es conveniente darles nombres, pero
para el uso computacional supondremos que si hay n vrtices, stos se denominan
1, 2, . . . , n, i.e., V = {1, 2, . . . , n}. Ms an, casi siempre usaremos el nombre n
(o algo que empiece con n) para el cardinal de V .
En los grafos que veremos, las aristas estn formadas por un par de vrtices,
y como el orden no importar, indicaremos la arista que une el vrtice a con el
vrtice b por {a, b}. Claro que decir {a, b} E es lo mismo que decir {b, a} E.
As como n es el nombre oficial de |V |, el nombre oficial para |E| es m.
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). A veces, un vrtice no tiene
vecinos no hay aristas que inciden sobre l y entonces decimos que es un
vrtice aislado.
Slo consideraremos grafos simples, en los que no hay aristas de la forma
{v, v} uniendo un vrtice con s mismo, ni aristas paralelas uniendo los mismos
vrtices.
 En este caso, podemos relacionar n = |V | y m = |E|: si hay n elementos,

hay n2 = n (n 1)/2 subconjuntos de 2 elementos, de modo que m n2 .

Pg. 123
2

Figura 14.1: Un grafo con 6 vrtices y 7 aristas. El vrtice 5 es aislado.

En la figura 14.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 vrtice 5 es aislado.
Siguiendo con la analoga de calles y rutas, es comn hablar de camino, una
sucesin de vrtices el orden es importante de la forma (v1 , v2 , . . . , vk ),
donde {vi , vi+1 } E para i = 1, . . . , k 1. Claro que podemos describir un
camino tanto por los vrtices como por las aristas intermedias, y en vez de
poner (v1 , v2 , . . . , vk ) podramos considerar ({v1 , v2 }, {v2 , v3 }, . . . , {vk1 , vk }).
La longitud del camino (v1 , v2 , . . . , vk ) es k 1, la cantidad de aristas que tiene
(y no la cantidad de vrtices k).
Si u = v1 y v = vk , decimos que el camino (v1 , v2 , . . . , vk ) es un camino de
u a v, o que une u y v, o simplemente camino uv.
Un camino en principio puede tener vrtices repetidos, y si se cierra sobre
s mismo de modo que v1 = vk , decimos que se trata de un camino cerrado,
mientras que si no tiene vrtices repetidos decimos que es un camino simple.
Por ejemplo, en la figura 14.1, {3, 2, 6, 3, 4} es un camino 34 de longitud 4,
{1, 2, 3} es un camino simple y {4, 3, 2, 1, 3, 6, 4} es un camino cerrado.
Un ciclo es un camino cerrado sin aristas repetidas (pero puede tener varios
vrtices repetidos). Por ejemplo, {4, 3, 2, 1, 3, 6, 4} es un ciclo en el grafo de la
figura 14.1.
De fundamental importancia es reconocer si un grafo es conexo, es decir, si
existe un camino desde cualquier vrtice a cualquier otro vrtice. La relacin
definida en V V por u v si y slo si u = v o existe un camino uv, es
una relacin de equivalencia, y las clases de equivalencia se llaman componentes
conexas del grafo. Por ejemplo, el grafo de la figura 14.1 no es conexo, pues tiene
un vrtice aislado, y sus componentes conexas son {1, 2, 3, 4, 6} y {5}.
Por otra parte, si un grafo es conexo pero no tiene ciclos, decimos que es un
rbol. En la figura 14.2.a) mostramos otro grafo no conexo, y en b) un rbol.

a) grafo no conexo con 9 vrtices y 9 aristas.

b) rbol con 7 vrtices.

Figura 14.2: Un grafo no conexo y un rbol.

Pg. 124

Grafos

Si el grafo es conexo (y simple), como se puede unir un vrtice con los n 1


restantes, debe haber al menos n1 aristas. De modo que para un grafo (simple)
conexo, m tiene que estar bsicamente entre n y n2 /2.(1)
A veces se consideran grafos dirigidos o digrafos, en los que las aristas estn
orientadas, y por lo tanto se indican como (a, b) (y se llaman arcos en vez de
aristas), distinguiendo entre (a, b) y (b, a). No estudiaremos este tipo de grafos
en este captulo, pero vale la pena mencionar que hemos trabajado con el grafo
de la figura 11.6 como si fuera un digrafo: las calles slo iban de sur a norte o de
oeste a este, y el rbol binario del captulo 13 es (claro) un rbol pero tambin
dirigido, donde las arcos toman la direccin que se aleja de la raz.
Dada su estructura, es ms sencillo trabajar con rboles que con grafos.
Como hemos dicho, un rbol es un grafo (simple) conexo y sin ciclos, pero hay
muchas formas equivalentes de describirlo, algunas de las cuales enunciamos
como teorema (que por supuesto creeremos):
14.1. Teorema (Caracterizaciones de rboles). Un grafo simple G =
(V, E) con |V | = n es un rbol si y slo si se cumple alguna de las siguientes condiciones:
Para cualquier a, b V existe un nico camino que los une.
G es conexo y |E| = n 1.
G no tiene ciclos y |E| = n 1.
G es conexo, y si se agrega una arista entre dos vrtices cualesquiera, se
crea un nico ciclo.
e) G es conexo, y si se quita cualquier arista pero no los vrtices en los
que incide queda no conexo.

a)
b)
c)
d)

A veces en un rbol consideramos un vrtice particular como raz, y miramos


a los otros vrtices como descendientes de la raz: los que se conectan mediante
una arista a la raz son los hos, los que se conectan con un camino de longitud
2 son los nietos y as sucesivamente. Dado que hay un nico camino de la raz
a cualquier otro vrtice, podemos clasificar a los vrtices segn niveles: la raz
tiene nivel 0, los hos nivel 1, los nietos nivel 2, etc., como hemos visto al estudiar
rboles binarios ordenados en el captulo 13.
Por supuesto, podemos pensar que los nietos son hos de los hos, los hos
padres de los nietos, etc., de modo que en un rbol con raz hablaremos
de padres, hos, ascendientes y descendientes de un vrtice. Habr uno o ms
vrtices sin descendientes, llamados hojas, mientras que la raz ser el nico
vrtice sin ascendientes. Tambin es comn referirse al conjunto formado por
un vrtice (aunque el vrtice no sea la raz) y sus descendientes como una rama
del rbol.

14.1. Representacin de grafos en la computadora


Antes de meternos de lleno con los algoritmos, nos falta una cosita ms:
cmo guardar la informacin de un grafo en la computadora? Ya sabemos que
los vrtices son 1, 2, . . . , n, pero y las aristas?
Hay muchas formas de representar un grafo en general, y nosotros en este
captulo nos limitaremos a dos: dar la lista de aristas, y dar la matriz de adyacencias, una matriz cuyas entradas son slo 0 o 1 y de modo que la entrada ij
es 1 si y slo si {i, j} E.
(1)

Ms precisamente, entre n 1 y n (n 1)/2.

14.1. Representacin de grafos en la computadora

Pg. 125

La representacin mediante matriz de adyacencias es cmoda, y relativamente fcil de entender. Quizs sera ms eficiente para los algoritmos que
veremos dar para cada vrtice una lista de sus vecinos. Esta tercera forma
puede implementarse mediante arreglos, pero es mucho ms natural usar listas
encadenadas, que no veremos en este curso.
En esta seccin y las dos siguientes (salvo indicacin en contrario) supondremos las declaraciones:
const
MAXN = 20; (* maximo numero de vertices *)
MAXM = 100; (* maximo numero de aristas *)
type
arreglodevertices = array[1..MAXN] of integer;
tipoarista = record i, j: integer end;
arreglodearistas = array[1..MAXM] of tipoarista;
matrizNN = array[1..MAXN,1..MAXN] of integer;
var
ngrafo, mgrafo: integer;
aristasgrafo: arreglodearistas;
adyacencias: matrizNN;
usando para las aristas una representacin como arreglo de registros.
Usualmente el ingreso de datos es ms 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 leer y para pasar de una a otra representacin:
Problema 14.2. En nuestros programas supondremos que ngrafo, el nmero
de vrtices del grafo, es entrado explcitamente (recordando que los vrtices
sern entonces 1, 2, . . . , ngrafo).
a) Hacer un procedimiento entrargrafo para ingresar por terminal el nmero
de vrtices, ngrafo, y las aristas, cada una representada por dos nmeros
enteros que son los vrtices que la determinan, formando un arreglo de
longitud mgrafo.
Sugerencia: seguir un esquema como
write(Ingresar el numero de vertices: ); readln(ngrafo);
writeln;
writeln(Ingresar las aristas, indicando sus extremos);
writeln(y fin de datos con <retorno> vacio);
mgrafo := 0;
nuevaarista := true;
while (nuevaarista) do begin
mgrafo := mgrafo + 1;
write( Entrar la arista , mgrafo:2);
writeln( (fin = <retorno>):);
write(
Entrar el primer vertice:
);
if (not eoln) then with aristasgrafo[mgrafo] do begin
read(i);
write(
Entrar el segundo vertice:
);
readln(j)
end (* with *)
else begin
nuevaarista := false;

Pg. 126

Grafos

mgrafo :=
readln (*
end (* if
end (* while

mgrafo - 1; (* habiamos avanzado en 1 *)


leer el fin de linea *)
not eoln *)
*)

- Como es usual, supondremos que el usuario ingresa correctamente los


datos: los vrtices de las aristas que se ingresan son enteros entre 1 y
ngrafo y no hay aristas repetidas o de la forma {i, i}. Tambin suponemos que las dimensiones MAXN y MAXM son adecuadas para el
problema a resolver.

b) Hacer un procedimiento escribiraristas, que imprima en forma de tabla las


aristas: una arista por rengln, y en cada rengln los vrtices de la arista.
Hacer un programa con los procedimientos entrargrafo y escribiraristas,
para comprobar que estn funcionando correctamente.
c) Considerando el procedimiento dearistasaadyacencias, dado por
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
adyacencias[i,j] := 1; adyacencias[j,i] := 1
end
end;
hacer un programa que lea el nmero de vrtices, las aristas, calcule la
matriz de adyacencias, e imprima para cada vrtice, los vrtices que son
adyacentes.
Por ejemplo, si la entrada es el grafo de la figura 14.1, la salida debera
ser algo como
Vertice
1
2
3
4
5
6

Vecinos
2 3
1 3 6
1 2 4
3 6
2

d) Hacer un procedimiento que, dada la matriz de adyacencias, construya el


arreglo de aristas (a fin de evitar repeticiones, es conveniente construir slo
aristas de la forma {i, j} con i < j), y verificarlo incorporndolo al programa
anterior.
$
Problema 14.3. Cambiar el procedimiento entrargrafo del inciso a) del problema anterior, para que en vez de leer por pantalla el nmero de vrtices y las
aristas, las lea de un archivo de texto, y comprobar que funciona correctamente.
El archivo de texto debe contener en la primer lnea el nmero de vrtices,
y en las restantes los vrtices de cada arista: por lo menos debe tener un rengln (correspondiente al nmero de vrtices), y a partir del segundo debe haber
exactamente dos datos por rengln.
Por ejemplo, para el grafo de la figura 14.1, el archivo tendra (ni ms ni
menos):
6

14.2. Recorriendo un grafo

1
1
2
2
3
3
4

Pg. 127

2
3
3
6
4
6
6

En lo que sigue vamos a suponer que ingresamos un


grafo leyendo un archivo de texto como en el problema 14.3, y que tenemos disponible el procedimiento
dearistasaadyacencias.

Problema 14.4 (Grado de vrtices). Dado un grafo G = (V, E), para cada
vrtice v V se define su grado o valencia, (v), como la cantidad de aristas
que inciden en v, o equivalentemente, la cantidad de vecinos de v (excluyendo
al mismo v).
Por ejemplo, en el grafo de la figura 14.1, los grados son (1) = 2, (2) =
3, (3) = 4, (4) = 2, (5) = 0, (6) = 3.
a) Hacer un programa que dado un grafo calcule (v) para todo v V , imprimiendo el resultado como tabla.
b) Uno de los primeros teoremas que se ven en teora de grafos dice que si U
es el conjunto de vrtices de grado impar, entonces
X
(v) es par.
vU

Agregar un procedimiento para hacer este clculo y verificar el teorema.


$
Problema 14.5. Es claro que si (u = v1 , v2 , . . . , vk = v) es un camino uv,
entonces (vk , vk1 , . . . , v1 ) es un camino vu, y lo mismo para un ciclo.
Hacer un programa en el que ingresando un grafo (de un archivo de texto)
y una sucesin de vrtices (v1 , v2 , . . . , vk ) (ingresada interactivamente):
a) decida si (v1 , v2 , . . . , vk ) es un camino, i.e., si {vi , vi+1 } E para i =
1, . . . , k 1,
b) en caso afirmativo, imprima el camino inverso, (vk , vk1 , . . . , v1 ),
- Si slo se desea imprimir y no construir, se puede usar simplemente
downto. Ver tambin el problema 8.3 y el problema 11.3.

c) verifique si (v1 , v2 , . . . , vk ) es un ciclo.

14.2. Recorriendo un grafo


Cuando consideramos grafos, es de inters tener algoritmos para recorrerlo, i.e., visitar los vrtices en forma ordenada, evitando visitar vrtices ya
visitados, y siempre caminando por las aristas del grafo.
Exactamente qu hacer cuando se visita un vrtice depender del problema, y en general podemos pensar que visitar es sinnimo de procesar. Por

Pg. 128

Grafos

ejemplo, al imprimir en orden el rbol binario ordenado del problema 13.1,


procesar significaba imprimir el nodo.
Claro que si el grafo no es conexo, no ser posible recorrerlo por completo, visitando todos los vrtices. Por suerte, con un pequeo esfuerzo extra, los
algoritmos que veremos tambin nos dirn si el grafo es conexo o no.
Estos algoritmos se encuadran en la estructura del algoritmo recorrer, que
mostramos en seudo-cdigo en el cuadro 14.1, donde con denotamos la
asignacin, con la sangra implcitamente sealamos un par begin-end o
comienzo-fin para agrupar instrucciones, y en negrita las palabras reservadas como Comienzo, Fin, mientras, etc.

Algoritmo recorrer
Entrada:
Salida:

un grafo G = (V, E) y un vrtice r V .


los vrtices que se pueden alcanzar desde r visitados.

Comienzo
Q {r};
mientras Q 6= hacer
sea i Q;
sacar i de Q;
visitar i ;
para todo j adyacente a i hacer
si j no est visitado y j
/ Q entonces agregar j a Q
Fin

Cuadro 14.1: Esquema del algoritmo recorrer.


- Al escribir en seudo-cdigo muchas veces quedan sentencias oscuras, donde
no es claro qu accin concreta debe realizarse. Por ejemplo, qu tipo de
cola es Q o qu quiere decir que i est visitado en el algoritmo recorrer.

En el algoritmo se indica el vrtice r a partir del cual se iniciar el recorrido.


En la cola Q, que inicialmente contiene slo a r, se guardan los vrtices que
van a visitarse, y se van sacando los que se visitan. As, en todo momento del
algoritmo habr tres clases de vrtices: los ya visitados, los que estn en la cola
(y todava no se han visitado), y los que no fueron visitados ni estn en la cola
(porque todava no visitamos ninguno de sus vecinos).
Como no queremos agregar a la cola un vrtice ya agregado, debemos guardar
informacin que nos diga si un vrtice 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 vrtice j en la cola porque es vecino del vrtice i que estamos
visitando, pondremos padre j = i (> 0). As, la condicin 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 vrtices que
visitamos van formando un rbol(2) que cuando el grafo es conexo se llama
generadoro de expansin del grafo, pues contiene a todos los vrtices del grafo
original y las aristas del rbol son tambin aristas del grafo original (pero pueden
no ser todas).
(2) Esto ir quedando ms claro con los ejemplos. Es una propiedad que no demostraremos,
como tantas otras!

14.3. Recorrido en profundidad y a lo ancho

Pg. 129

Por supuesto que si el grafo que queremos recorrer es ya un rbol, no se formar uno nuevo. Pero s que podemos tomar al vrtice r, con el que empezamos
el recorrido, como raz, y el arreglo padre justamente nos dir quin es el padre
de cada vrtice en el rbol que se forma.
Pondremos padre r = r para indicar que r es justamente la raz y no podemos
seguir ms arriba, de modo que tambin para i = r la condicin padre i > 0
es equivalente a que el vrtice i se ha incorporado alguna vez a la cola.
Otra cosa que necesitamos especificar es cmo elegir el vrtice en Q que se
sacar (si hay ms de uno). Lo ms sencillo es implementar a Q como pila, i.e.,
como cola lifo. Pero tambin podramos pensar en implementar como cola fifo
(sale primero el que lleg primero), o usar otros criterios para elegir el vrtice
que sale. De esta forma vamos a llegar a los distintos algoritmos que veremos en
el resto del captulo: recorrido en profundidad, recorrido a lo ancho, y cuando
las aristas tengan pesos, el algoritmo de Dkstra para encontrar el camino ms
corto y el de Prim para encontrar un rbol generador mnimo.

14.3. Recorrido en profundidad y a lo ancho


Problema 14.6 (Recorrido en profundidad). Si la cola Q del algoritmo
recorrer es una pila, visitaremos primero los vrtices que se han incorporando
ms recientemente.
En este caso, si el grafo fuera ya un rbol, resultar que primero visitaremos
toda una rama hasta el fin antes de recorrer otra,(3) lo que hace que este tipo
de recorrido se llame en profundidad o de profundidad primero. Otra forma de
pensarlo es que vamos caminando por las aristas (a partir de la raz) hasta llegar
a una hoja, luego volvemos por una arista (o las necesarias) y bajamos hasta
otra hoja, y as sucesivamente.
- O sea, estamos haciendo el rastreo inverso que mencionamos en el captulo 13, que es el poner y sacar la piedrita del captulo 12.

El procedimiento correspondiente es de la forma


procedure profundidad;
var
avisitar: arreglodevertices; (* la cola como pila *)
hay: integer; (* la cantidad de elementos en la pila *)
i, j: integer; (* variables auxiliares *)
begin
(* inicializacion *)
for i := 1 to ngrafo do padre[i] := 0;
(* al principio solo esta r *)
padre[r] := r;
hay := 1;
avisitar[1] := r;
while (hay > 0) do (* mientras la pila no es vacia *)
begin
i := avisitar[hay]; (* tomar un vertice no visitado *)
hay := hay - 1;
(* sacarlo de la pila *)
visitar(i);
(* y visitarlo *)
(* examinar vecinos *)
for j := 1 to ngrafo do
(3)

Bah, que nos vamos por las ramas.

Pg. 130

Grafos

if ((adyacencias[i,j] > 0) and (padre[j] = 0)) then


begin (* agregar j a la cola *)
padre[j] := i;
hay := hay + 1;
avisitar[hay] := j
end (* if adyacencias > 0 *)
end (* while *)
end;

Por comodidad suponemos que las variables ngrafo, adyacencias, r y padre


son globales.
a) Hacer un programa con este procedimiento, tomando los datos del grafo de
un archivo de texto e ingresando interactivamente r, donde visitar (i) sea
incrementar en uno un contador nvisitados (declarado como global), que
se imprime al terminar el programa con el cartel se visitaron nvisitados
vertices.
La estructura debe ser algo como
poner carteles;
entrargrafo;
dearistasaadyacencias;
ingresar la raz;
otras inicializaciones;
profundidad ;
imprimir nvisitados
Para los datos del grafo de la figura 14.1, nvisitados = 5 cuando r = 1 y
nvisitados = 1 cuando r = 5.
b) Si, siendo nvisitados como en el inciso anterior, tenemos nvisitados = ngrafo,
entonces habremos visitado todos los vrtices, i.e., el grafo es conexo. Agregar al programa anterior un cartel que diga si el grafo es conexo o no.
En el caso del grafo de la figura 14.1, el grafo no es conexo. Agregar las
aristas {3, 5} y {4, 5} y correr nuevamente el programa: ahora el grafo debe
ser conexo.
c) Agregar al procedimiento visitar (i) instrucciones para guardar en un arreglo
visitados la informacin que i se visit en ese paso, imprimiendo el arreglo
al final con el cartel Los vrtices se visitaron en el orden:.
En el caso del grafo de la figura 14.1 y r = 1, los vrtices se visitan en
el orden 1, 3, 6, 4, 2.
d) Para construir el rbol resultante, que es generador de la componente conexa
que contiene a la raz r, usamos el procedimiento hacerarbol :
procedure hacerarbol;
(* Construir el arbol generador usando padre *)
var k, pk: integer;
begin
marbol := 0;
for k := 1 to ngrafo do begin
pk := padre[k];
if ((pk > 0) and (pk <> k)) then begin
marbol := marbol + 1;
with aristasarbol[marbol] do begin
i := pk; j := k end
end (* if pk *)
end (* for *)
end;

14.3. Recorrido en profundidad y a lo ancho

Pg. 131

Nuevamente, por comodidad, suponemos que aristasarbol y marbol son


globales.
Incorporar este procedimiento al programa anterior, imprimiendo las
aristas resultantes si marbol > 0 (o equivalentemente, cuando nvisitados =
1), o un cartel apropiado cuando no las hay.
Cuando el grafo es el de la figura 14.1 y r = 1, las aristas del rbol son
{1, 2}, {1, 3}, {3, 4}, {3, 6}. En cambio el rbol se reduce al vrtice r cuando
r = 5, y no hay aristas.
- Una fuente de ineficiencia en la implementacin 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 ms
conveniente usar directamente una lista de vecinos para cada vrtice, puesto
que entonces el algoritmo har del orden de m pasos.
- El orden en que se recorren los vrtices tanto en el recorrido en profundidad como el recorrido a lo ancho que veremos luego est determinado
tambin por la numeracin de los vrtices. Ac, al usar la matriz de adyacencias para buscar los vecinos, seguimos el orden de los naturales, pero
podra ser otro. En la mayora de las aplicaciones, la numeracin dada a
los vrtices no es importante: si lo fuera, hay que sospechar del modelo y
mirarlo con cuidado.
$

Problema 14.7. Un clebre 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 slo si el grafo es conexo y el grado de cada vrtice es par.
Recordando el problema 14.4, modificar el programa del problema 14.6 para
que a la salida determine tambin si el grafo tiene o no un ciclo de Euler usando
el teorema.
- Un problema muy distinto es encontrar un ciclo de Euler en caso de existir.
Como ya mencionamos (pg. 42), Euler ha sido uno de los matemticos
ms destacados de todos los tiempos.
Entre otros tantos aportes fundamentales, Euler fue quien origin el estudio
de teora de grafos y la topologa al resolver en 1736 el famoso problema de
los puentes de Knigsberg (hoy Kaliningrad, en Rusia), donde el ro Pregel se
bifurcaba dejando dos islas (y dos costas), y las islas y las costas se unan por
siete puentes. Euler resolvi el problema, demostrando que no se podan recorrer
todos los puentes pasando una nica vez por ellos, demostrando el teorema que
$
hoy llamamos de grafos eulerianos.

Problema 14.8. Modificar el programa del problema 14.6 de modo que:


a) Ingresando los vrtices s y t, s 6= t, se exhiba un camino st o se imprima
un cartel diciendo que no existe tal camino.
Sugerencia: usar el procedimiento profundidad con raz s y si al finalizar
resulta padre t > 0, construir el camino siguiendo padre hasta llegar a s
(recordando el problema 14.5).
Sugerencia si la anterior no alcanza: para construir el camino st se puede
hacer
procedure hacercamino;
(* Construir el camino s-t usando padre *)
var i, j, k: integer;
begin
ncamino := 1; camino[1] := t; i := t;
while (i <> s) do begin
i := padre[i];
ncamino := ncamino + 1;

Pg. 132

Grafos

camino[ncamino] := i
end;
(* dar vuelta el arreglo *);
i := 1; j := ncamino;
while (i < j) do begin
k := camino[i]; camino[i] := camino[j]; camino[j] := k;
i := i + 1; j := j - 1
end
end;

b) Se imprima una (y slo una) de las siguientes:


i) G no es conexo,
ii) G es un rbol,
iii) G es conexo y tiene al menos un ciclo.
c) Dada la arista {u, v}, se imprima si hay o no un ciclo en G que la contiene,
y en caso afirmativo, imprimir uno de esos ciclos.
Sugerencia: si hay un ciclo que contiene a la arista {u, v}, debe haber un
camino uv en el grafo que se obtiene borrando la arista {u, v} del grafo
original.
Problema 14.9 (Recorrido a lo ancho). Si en el algoritmo recorrer, en vez
de implementar la cola como lifo (pila), la implementamos como fifo, visitamos
primero el vrtice r, luego sus vecinos, luego los vecinos de los vecinos, etc. Si
el grafo fuera ya un rbol, visitaremos primero la raz, despus todos sus hos,
despus todos sus nietos, etc., por lo que se el recorrido se llama a lo ancho.
El procedimiento correspondiente toma ahora la forma
procedure ancho;
var
avisitar: arreglodevertices; (* la cola *)
ppo, fin: integer; (* los extremos de la cola *)
i, j: integer; (* variables auxiliares *)
begin
(* inicializacion *)
for i := 1 to ngrafo do padre[i] := 0;
(* al principio solo esta r *)
padre[r] := r;
ppo := 1; fin := 1; (* los extremos de la cola *)
avisitar[1] := r;
while (ppo <= fin) do (* mientras la cola no es vacia *)
begin
i := avisitar[ppo]; (* tomar un vertice no visitado *)
ppo := ppo + 1;
(* sacarlo de la cola *)
visitar(i);
(* y visitarlo *)
(* examinar vecinos *)
for j := 1 to ngrafo do
if ((adyacencias[i,j] > 0) and (padre[j] = 0)) then
begin (* agregar j a la cola *)
padre[j] := i;
fin := fin + 1;
avisitar[fin] := j
end (* if adyacencias > 0 *)
end (* while *)
end;

14.4. Grafos con pesos

Pg. 133

Repetir los incisos del problema 14.6.


Con el grafo de la figura 14.1 y tomando r = 1, ahora los vrtices se visitan
en el orden 1, 2, 3, 6, 4, y las aristas del rbol generador de la componente conexa
$
de r son {1, 2}, {1, 3}, {3, 4}, {2, 6}.
En general, an cuando el grafo sea un rbol binario ordenado, el recorrido
a lo ancho es distinto de los recorridos en orden, pre orden o post orden
que hemos visto en el problema 13.1, pues ahora visitamos los vecinos de la raz,
luego los vecinos de los vecinos, etc., en otras palabras, recorremos el rbol por
niveles.
Problema 14.10. Agregar un procedimiento al programa arbolbinario para imprimir el rbol construido por niveles: primero la informacin del vrtice en el
$
nivel 0, luego la de los vrtices en el nivel 1, etc., hasta la profundidad.

14.4. Grafos con pesos


Algunas veces la informacin interesante de un grafo est en los vrtices,
y las aristas nos dicen que, por alguna razn, dos vrtices estn relacionados
y nada ms. Tal es el caso de los rboles binarios de la seccin 13, donde la
conexin estaba asociada al orden entre los datos guardados en los vrtices.
Otras veces, las aristas tienen informacin adicional que queremos considerar. Por ejemplo si las aristas indican rutas entre ciudades, la informacin
podra 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. Consideraremos aqu slo el caso we 0 para todo e E.
En la figura 14.3 vemos un ejemplo de grafo con pesos, marcados con recuadros en las aristas correspondientes, pero por comodidad reproducimos los
datos en el cuadro al costado.
2

2
2

3
1

1
2

6
1
3

3
4

Arista e

peso we

{1,2}
{1,4}
{1,5}
{2,3}
{2,4}
{3,4}
{3,5}
{3,6}
{4,6}
{5,6}

2
3
8
2
1
1
2
1
1
3

Figura 14.3: Un grafo con pesos en las aristas.


Pensando que los vrtices representan ciudades y los costos de las aristas
representan distancias, si tuviramos que ir de una ciudad a otra, teniendo
distintas rutas alternativas para elegir, es razonable preguntarse cul de ellas
ser la ms corta (o la ms barata). ste es el problema del camino ms corto:
en un grafo pesado, y dados dos vrtices s, t V , encontrar un camino (s =
v1 , v2 , . . . , vk = t) con peso total mnimo, donde el peso total de un camino se
define como
w{v1 ,v2 } + w{v2 ,v3 } + + w{vk1 ,vk } =

k1
X
i=1

w{vi ,vi+1 } .

Pg. 134

Grafos

Observemos que el valor de k no est fijo: no nos interesa si tenemos que


usar una arista o cien, slo nos interesa que la suma de las distancias para ir de
s a t sea mnima.
Por ejemplo, en el grafo de la figura 14.3 podemos usar varios caminos para
ir del vrtice 1 al 5: el camino (1, 5) usa una nica arista (k = 2) y tiene peso 8,
el camino (1, 2, 3, 5) usa 3 aristas y tiene costo total 2 + 2 + 2 = 6, y en realidad
no hay otro con menor costo. Sin embargo, el camino (1, 4, 3, 5) tambin tiene
costo 3 + 1 + 2 = 6, i.e., puede haber ms de un camino con distancia mnima.
Siguiendo con el ejemplo de las ciudades, otro problema interesante es pensar
que inicialmente las ciudades no estn unidas, los pesos de las aristas representan
ahora los costos de construccin de los caminos correspondientes, y queremos
construir carreteras de modo de que todas las ciudades puedan unirse entre
s mediante estas nuevas rutas, pero el costo de la construccin sea lo menor
posible.
El problema es entonces encontrar un subconjunto de aristas que, junto con
los vrtices originales, formen un subgrafo conexo, y tal que la suma de los costos
de ese subconjunto de aristas sea lo menor posible.
Claro que si el grafo original no es conexo, no podremos encontrar un subgrafo conexo. Por otro lado, no pueden formarse ciclos porque podramos sacar
del ciclo la arista con mayor costo manteniendo la conexin.
- Este razonamiento es correcto si en el ciclo hay alguna arista con costo
estrictamente positivo, pero podramos tener problemas si todas tienen
costo cero o negativo.

Es decir, queremos un subgrafo conexo y sin ciclos, y por lo tanto el conjunto


de aristas a elegir debe forman un rbol generador, que haga mnima la suma
de los pesos de las aristas que lo componen,
X
we .
e en el rbol

Un rbol con estas propiedades se llama rbol generador mnimo. Podra haber
ms de un rbol con costo mnimo, por ejemplo si los costos de todas las aristas
son 1 y el grafo no es un rbol.
Volviendo al grafo de la figura 14.3, podemos formar un rbol generador con
las aristas {1, 2}, {1, 4}, {2, 3}, {3, 6}, {5, 6} (siempre un rbol generador debe
tener n 1 aristas), con peso total 2 + 3 + 2 + 1 + 3 = 11, y si reemplazamos la
arista {5, 6} por la arista {3, 5}, reducimos el costo en 1. En este ejemplo, dados
los datos, se forma un ciclo donde todas las aristas tienen peso 1, y por lo tanto
hay ms de un rbol mnimo (de peso total 7): podras encontrar dos de ellos
a simple vista?
En las secciones siguientes veremos algoritmos para resolver estos problemas,
el de la ruta ms corta y el del mnimo rbol generador, pero primero hagamos
los cambios necesarios sobre lo que vimos en la seccin 14.1, para incorporar los
pesos en las aristas, y adecuarlas a nuestros algoritmos.
El primer cambio es incorporar el costo en cada arista, lo que se puede hacer
agregando o cambiando las declaraciones para tener:
type
tipocosto = integer; (* podria ser real *)
tipoarista = record
i, j: integer; (* los extremos *)
w: tipocosto
(* el costo *)
end;
arreglodearistas = array[1..MAXM] of tipoarista;

14.4. Grafos con pesos

Pg. 135

En el caso de grafos sin pesos, usbamos la matriz adyacencias que contena


un 1 en el lugar ij para indicar que i y j eran vecinos, y un 0 en otro caso.
En los algoritmos que veremos, es conveniente en cambio pensar que si i y j no
son vecinos, entonces el costo de unirlos es enorme, que simbolizamos como
(infinito). Claro que no podemos poner infinito como valor de una variable, lo
que queremos es algo bien grande, cosa de que la arista nunca se considere. La
matriz de adyacencias se cambia entonces a una matriz costos, definida como
(
w{i,j} si {i, j} E,
costos ij =

en otro caso,
por lo que declararemos
type
matrizNN = array[1..MAXN,1..MAXN] of tipocosto;
var
costos: matrizNN;
infinito: tipocosto;
Nuestro prximo objetivo es reproducir lo que hicimos en los problemas 14.2
y 14.3:
Problema 14.11. Haciendo las declaraciones de constantes, tipos y variables
mencionadas:
a) Hacer un procedimiento entrargrafo, de modo de leer de un archivo de texto
los datos ngrafo y luego los extremos y costos de cada arista, en forma
similar al problema 14.3.
Por ejemplo, el grafo de la figura 14.3 tendra asociado un archivo con
las entradas:
6
1
1
1
2
2
3
3
3
4
5

2
4
5
3
4
4
5
6
6
6

2
3
8
2
1
1
2
1
1
3

b) Hacer un procedimiento escribiraristas, que imprima las aristas ingresadas


como tabla, poniendo primero los vrtices y luego el costo (una arista por
rengln).
c) Escribir tambin el procedimiento dearistasacostos:
procedure dearistasacostos;
var i, j, k: integer;
begin
(* infinito = numero mas grande que suma de costos *)
infinito := 1;
for k := 1 to mgrafo do
infinito := infinito + aristasgrafo[k].w;
for i := 1 to ngrafo do
for j := 1 to ngrafo do

Pg. 136

Grafos

costos[i,j] := infinito;
for k := 1 to mgrafo do
with aristasgrafo[k] do begin
costos[i,j] := w; costos[j,i] := w
end
end;

Ac ponemos infinito como la suma de los pesos de todas las aristas ms


1, de modo que ningn subconjunto de aristas alcance ese peso (recordemos
que suponemos que los pesos son no negativos).
d) Usando los procedimientos entrargrafo, escribiraristas y dearistasacostos,
hacer un programa que imprima el valor de la variable infinito, para verificar
$
que todo funciona bien.

De ahora en ms, vamos a suponer que ingresamos


un grafo leyendo un archivo de texto como en el
problema 14.11, y que tenemos disponible el procedimiento dearistasacostos.

14.5. Camino ms corto: Dkstra


Como mencionamos anteriormente, el problema del camino ms corto es
dado un grafo pesado y los vrtices s y t, encontrar el camino st de mnimo
costo. Tal vez el algoritmo ms conocido para resolver este problema sea el de
Dkstra, que sigue la estructura del algoritmo recorrer: se comienza desde un
vrtice, en este caso s, se lo coloca en una cola, y se visitan los vrtices de la
cola.
E. W. Dkstra (19302002) naci y muri en Holanda. Fue uno de los ms
grandes intelectos que contribuyeron a la lgica matemtica subyacente en los
programas de computacin y sistemas operativos. Entre sus muchas y destacadas contribuciones est el algoritmo para el camino ms corto que presentamos,
publicado en 1959.

Nuevamente habr tres clases de vrtices: los visitados, los que estn en
la cola y no han sido an visitados, y los que nunca estuvieron en la cola.
Como novedad, para cada i V consideraremos el valor di de la distancia ms
corta de s a i mediante caminos de la forma (s = v1 , v2 , . . . , vk , i) donde todos
los vrtices excepto i ya han sido visitados. A fin de indicar que no existe tal
camino, pondremos di = , siendo ste el valor inicial para todo i (cuando no
hay nodos visitados).
A diferencia de los recorridos de grafos de secciones anteriores, donde se
visitan los vrtices tomando ya sea el ltimo o el primero en entrar a la cola, en
el algoritmo de Dkstra elegimos el vrtice de la cola con menor distancia di .
El algoritmo termina cuando t es el vrtice que se visita, y entonces dt es la
distancia del menor camino de s a t, o cuando la cola es vaca, en cuyo caso no
existe un camino desde s hacia t y necesariamente el grafo no es conexo.
Al visitar un vrtice i y examinar un vrtice vecino j, verificamos si
di + w{i,j} < dj .

(14.1)

14.5. Camino ms corto: Dkstra

Pg. 137

Si esta desigualdad es vlida, quiere decir que el camino ms corto para ir desde
s a j (slo por vrtices ya visitados) es ir desde s a i con el camino para i, y luego
usar la arista {i, j}. Por lo tanto, actualizamos dj poniendo dj = di + w{i,j} .
Tambin agregaremos j a la cola si no se ha agregado an.
Es una propiedad del algoritmo, que no demostraremos, que si j es un vecino de i el vrtice que se est visitando y j ya se ha visitado, la desigualdad (14.1) no puede darse, por lo que, a diferencia del algoritmo recorrer no es
necesario verificar si j ya ha sido visitado.
- Para demostrar esta propiedad y que el algoritmo es correcto se usa
de forma esencial que we 0 para todo e E. Nosotros dejaremos estas
propiedades para cursos de matemtica discreta o teora de grafos.

Otra simplificacin respecto del algoritmo recorrer es que dado que estamos
poniendo w{i,j} = cuando {i, j}
/ E, los vrtices j adyacentes a i se caracterizan por w{i,j} < , y los vrtices j que no han ingresado en la cola son los que
satisfacen dj = . El algoritmo resultante est esquematizado en seudo-cdigo
en el cuadro 14.2.
- Observar que ponemos w{i,i} = . Cuando i se incorpora a Q es di <
y la desigualdad (14.1) no es vlida, de modo que i no vuelve a ponerse en
la cola Q ni se modifica di .

Algoritmo de Dkstra
Entrada:
Salida:

un grafo pesado G = (V, E, W ) y los vrtices s, t V , s 6= t.


la distancia dt de un camino ms corto entre s y t (dt = ,
si tal camino no existe).

Comienzo
para todo i V hacer di ;
ds 0; Q {s};
repetir
sea i Q tal que di = mnjQ dj ;
si i 6= t entonces
sacar i de Q;
( examinar vecinos )
para todo j V hacer
si di + w{i,j} < dj entonces
si dj = entonces agregar j a Q;
dj di + w{i,j}
hasta que i = t o Q =
Fin

Cuadro 14.2: Esquema del algoritmo de Dkstra.


Problema 14.12 (Algoritmo de Dkstra para el camino ms corto).
En este problema implementamos el algoritmo de Dkstra con el procedimiento
mascorto definido como:
procedure mascorto;
var
avisitar: arreglodevertices; (* la cola *)
hay: integer; (* la cantidad de elementos en la cola *)

Pg. 138

Grafos

(* variables auxiliares *)
i, j, k, kmin: integer;
d, dmin: tipocosto;
begin
(* inicializacion *)
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
kmin := k; i := j; dmin := d end
end;
(* ahora tenemos el nuevo i *)
if (i <> t) then begin
(* intercambiamos i con el ultimo en la cola *)
avisitar[kmin] := avisitar[hay];
(* y sacamos i de la cola *)
hay := hay - 1;
(* examinar vecinos de i *)
for j := 1 to ngrafo do
if (dist[i] + costos[i,j] < dist[j]) then begin
if (dist[j] = infinito) then begin
(* agregar j a la cola *)
hay := hay + 1; avisitar[hay] := j end;
(* actualizar dist y padre *)
dist[j] := dist[i] + costos[i,j];
padre[j] := i
end
end (* if i <> t *)
until ((i = t) or (hay = 0))
end;

Observemos que:
La distancia desde s al vrtice i, usando como vrtices intermedios slo
vrtices ya visitados, se indica por dist i . Inicialmente dist i = para todo
i.
El arreglo padre es similar al visto en los procedimientos profundidad
(en el problema 14.6) o ancho (en el problema 14.9), y nos servir para
reconstruir el camino de s a t (ver tambin el problema 14.8.a)). padre
puede eliminarse si no se desea conocer este camino, pues para reconocer
si un vrtice ha ingresado alguna vez a la cola podemos usar dist.
La cola de vrtices a visitar se guarda en el arreglo avisitar , que inicialmente slo contiene al vrtice s.
En el lazo principal, se busca en la cola el vrtice con menor valor de
dist y se lo intercambia con el ltimo de la cola. El procedimiento para
encontrar el mnimo y modificar la cola es similar al proceso de seleccin
directa (pg. 92).

14.5. Camino ms corto: Dkstra

Pg. 139

Una vez que determinamos el vrtice a visitar, i, lo comparamos con t,


el vrtice al cual queremos llegar. Si i = t, hemos llegado y se termina el
procedimiento.
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 (14.1). Antes de cambiar dist j , sin embargo, preguntamos si j ha
estado en la cola comparando dist j con infinito.
La distancia entre s y t al terminar el procedimiento es dist t (que podra
ser si no existe camino st).
a) Hacer un programa que, ingresando un grafo mediante datos en un archivo
de texto (como en el problema 14.11), y los vrtices s y t (ingresados por
terminal), determine la distancia ms corta entre ellos.
La estructura de la parte principal del programa debe ser algo como:
poner carteles;
entrargrafo;
ingresar s y t;
dearistasacostos;
otras inicializaciones;
mascorto;
imprimir dist t o que no hay camino st
Verificar que para el grafo de la figura 14.3, el camino ms corto entre 1
y 5 tiene distancia total 6.
b) Agregar al programa el procedimiento hacercamino del problema 14.8 para
construir el camino st usando el arreglo padre.
En el ejemplo del inciso anterior, el camino recorrido es (1, 2, 3, 5).
c) Cambiar el programa de modo que calcule las distancias mnimas de s (ingresado por el usuario) a todos los otros vrtices del grafo (slo las distancias,
no los caminos).
d) Modificarlo de modo que calcule todas las distancias de i a j, para 1 i <
j ngrafo.
$
- Hay otros algoritmos para encontrar el camino ms corto entre un par de
vrtices. En particular, el de Floyd-Warshall (ver por ejemplo [9, pg. 129])
encuentra todos los caminos entre cualquier par de vrtices an en presencia
de aristas con costos negativos (o determina que hay un ciclo de peso
negativo), tardando del orden de n3 pasos.
- Tcnicamente el tipo de cola que usamos en los procedimientos mascorto
y arbolminimo en la prxima seccin 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 prioridad para sacar un elemento de la cola.
Nuestra implementacin de este tipo de colas es un tanto rudimentaria
a fin de mantener la(4) claridad en los programas. El uso de la variante
de seleccin directa tarda del orden de |V | pasos cada vez que se elige el
prximo vrtice a visitar. En cambio, el uso de variantes de algoritmos
de clasificacin ms avanzados hace que se tarde a lo sumo del orden de
log2 (|V |) pasos para esa eleccin.
- Otra fuente de ineficiencia es el uso de la matriz de adyacencias (o costos)
si m  n2 , como hemos observado al final del problema 14.6.

(4)

Alguna?

Pg. 140

Grafos

14.6. Mnimo rbol generador: Prim


Hay varios algoritmos para encontrar un rbol generador mnimo, y nosotros
veremos aqu el debido a Prim, pues podemos ponerlo dentro de la estructura
del algoritmo recorrer. Otro algoritmo, ms eficiente en la mayora de los casos
prcticos, es el de Kruskal, que no veremos pues su implementacin es ms
elaborada.
El algoritmo de Kruskal fue publicado en 1956, mientras que el de Prim fue
publicado en 1959. Dkstra tambin obtuvo en forma independiente el algoritmo
de Prim y lo public en 1959, en el mismo trabajo donde presenta su algoritmo
para el camino ms corto, lo que no es sorpresa dada la similitud.

Prcticamente todos los algoritmos para encontrar un rbol generador de


mnimo peso se basan en ir agregando a un conjunto de aristas, una de menor
costo que no forme un ciclo con las ya elegidas. En el algoritmo de Prim, empezamos con un vrtice, r, y vamos agregando aristas manteniendo siempre un
rbol. A fin de describir el algoritmo es conveniente pensar que el rbol resultante ser T = (V (T ), E(T )). Si inicialmente el rbol slo consiste de un vrtice
y no tiene aristas, podramos sintetizar el algoritmo de Prim de esta forma:
V (T ) {r}; E(T ) ; U V \ V (T );
mientras exista una arista con un vrtice en V (T ) y otro en U hacer
elegir entre ellas la de menor peso, digamos {t, u};
sacar u de U y agregarlo a V (T );
agregar {t, u} a E(T )
As presentado, el algoritmo es un poco ineficiente. Para mejorarlo y a la vez
hacerlo parecer al algoritmo recorrer, no consideraremos U sino una cola Q de
vrtices que no estn en V (T ) pero que se conectan con alguno que s lo est.
Para cada v Q mantenemos la mnima distancia, dv , de v a los vrtices de T .
Inicialmente ser dv = para todo v V , de modo que dv < es equivalente
a decir que v est o estuvo en Q.
Recordemos que en el algoritmo recorrer mantenamos una cola con los vrtices a visitar, y se formaban tres clases de vrtices: los visitados, los que estaban
en la cola, y los que nunca haban ingresado en la cola. En el algoritmo de Prim
se sigue la misma idea, donde visitar (i) significa agregar i a V (T ) y actualizar
otros datos de T que se necesiten, y un vrtice i en la cola se visita cuando
di = mnjQ dj , como hicimos en el algoritmo de Dkstra.
Sin embargo, la actualizacin de la distancia para los vecinos del vrtice i
visitado en el algoritmo de Dkstra se hace de acuerdo a la desigualdad (14.1),
mientras que ahora tendremos que analizar la desigualdad
w{i,j} < dj ,

(14.2)

y cambiar dj a w{i,j} en caso de ser vlida.


Otra diferencia importante es que, en el algoritmo de Dkstra, si j ya se ha
visitado y se est visitando i, la desigualdad (14.1) no puede darse, pero en el
algoritmo de Prim podra darse la desigualdad (14.2). De modo que para cada
v V debemos mantener informacin sobre si v V (T ) o no.
El algoritmo toma la estructura presentada en el cuadro 14.3.
Estamos en condiciones de definir el procedimiento arbolminimo implementando el algoritmo de Prim, suponiendo que las variables costoarbol y narbol (el
cardinal de V (T )) son globales:

14.6. Mnimo rbol generador: Prim

Algoritmo de Prim
Entrada:
Salida:

un grafo pesado G = (V, E, W ) y un vrtice r.


el costo costoarbol de un rbol generador de mnimo peso
con raz r, o que el grafo no es conexo.

Comienzo
para todo i V hacer di ;
Q {r}; dr 0;
V (T ) ; costoarbol 0;
mientras Q 6= hacer
Sea i Q tal que di = mnjQ dj ;
sacar i de Q;
( actualizar datos de T )
poner i en V (T ); costoarbol costoarbol + di ;
( examinar vecinos de i )
para todo j V hacer
si j
/ V (T ) y w{i,j} < dj entonces
si dj = entonces agregar j a Q;
dj w{i,j} ;
si |V (T )| < |V | entonces el grafo no es conexo
en otro caso el costo mnimo es costoarbol
Fin

Cuadro 14.3: Esquema del algoritmo de Prim.

procedure arbolminimo;
var
avisitar: arreglodevertices; (* la cola Q *)
visitado: array[1..MAXN] of boolean; (* V(T) *)
hay: integer; (* la cantidad de elementos en la cola *)
(* variables auxiliares *)
i, j, k, kmin: integer;
d, dmin: tipocosto;
begin
(* inicializacion *)
for i := 1 to ngrafo do begin
padre[i] := 0;
dist[i] := infinito;
visitado[i] := false
end;
narbol := 0;
costoarbol := 0;
(* r es la raiz *)
padre[r] := r; dist[r] := 0; hay := 1; avisitar[1] := r;
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];

Pg. 141

Pg. 142

Grafos

if (d < dmin) then begin


kmin := k; i := j; dmin := d end
end;
(* intercambiamos i con el ultimo en la cola *)
avisitar[kmin] := avisitar[hay];
(* y sacamos i de la cola *)
hay := hay - 1;
(* poner i en V(T) y actualizar datos *)
visitado[i] := true;
narbol := narbol + 1;
costoarbol := costoarbol + dist[i];
(* examinar vecinos de i *)
for j := 1 to ngrafo do
if ((not visitado[j]) and (costos[i,j] < dist[j]))
then begin
if (dist[j] = infinito) then begin
(* agregar j a la cola *)
hay := hay + 1; avisitar[hay] := j end;
(* actualizar dist y padre *)
dist[j] := costos[i,j];
padre[j] := i
end
until (hay = 0)
end;

Observamos las similitudes y diferencias con el procedimiento mascorto en


el que implementamos el algoritmo de Dkstra:
La matriz de costos y el valor infinito son idnticos.
En el algoritmo de Prim no existen s y t, ni por supuesto el camino
que los une, en cambio tenemos r y eventualmente el rbol con raz r.
En el procedimiento dijkstra s es el primero en ingresar a Q mientras que
ahora es r.
La distancia se actualiza comparando dist j con w{i,j} en vez de con
w{i,j} + dist i .
Al terminar el algoritmo de Prim, debemos decidir si T es un rbol generador, en cuyo caso ser de mnimo costo, o no, en cuyo caso tendremos
narbol < ngrafo.
En el algoritmo de Dkstra buscbamos un camino y en el de Prim un
rbol. Si queremos construir el rbol usando el arreglo padre, debemos
usar un procedimiento como hacerarbol del problema 14.6, en vez de
hacercamino.
Problema 14.13 (Algoritmo de Prim para el mnimo rbol generador). Hacer un programa para implementar el algoritmo de Prim usando el
procedimiento arbolminimo y las observaciones anteriores.
En el grafo de la figura 14.3 tomando r = 1 se obtiene el rbol de aristas
{1, 2}, {4, 3}, {2, 4}, {3, 5} y {4, 6}, con costo total 7. Verificar que tomando
r = 6, el rbol mnimo obtenido es distinto (pero los costos son iguales, claro). $
- Hemos tratado de mantener la presentacin de los algoritmos y programas
de este captulo algoritmo recorrer y los procedimientos profundidad ,
ancho, dijkstra y prim lo ms parecidas entre s a fin de resaltar las
semejanzas, lo que tambin afecta (un poquito) a la eficiencia. Es muy
posible que los veas con un disfraz bastante distinto en otras referencias.

14.7. Comentarios Bibliogrficos

Pg. 143

- Dado la similitud entre nuestras implementaciones de los algoritmos de


ancho primero, Dkstra y Prim, no es sorprendente que valgan las mismas
observaciones hechas al final de los problemas 14.6 y 14.12.

14.7. Comentarios Bibliogrficos


La presentacin unificada de los algoritmos recorrer, Dkstra y Prim toma
ideas de [9], pero los algoritmos finales son bastante diferentes: los errores son
mos y no de Papadimitriou y Steiglitz!

Apndice A

Programas mencionados
Problema 2.2: holamundo
program holamundo(input, output);
(* primer programa *)
begin
writeln(Hola Mundo!);
writeln;
writeln(y Chau!)
end.

Problema 3.2: sumardos


program sumardos(input, output);
(* sumar dos numeros enteros *)
var a, b: integer;
begin
writeln(** Programa para sumar dos numeros enteros);
writeln;
a := 1;
b := 2;
write(La suma de , a);
write( y , b);
writeln( es , a + b);
writeln; writeln(** Fin **)
end.

Problema 3.4: leerentero


program leerentero(input, output);
(* Leer un entero entrado por terminal *)
var a: integer;
begin
writeln(** Programa para leer un dato entero);

raiz (Problema 3.5)

writeln;
write(Entrar un entero: ); readln(a);
writeln;
writeln(El entero leido es , a);
writeln; writeln(** Fin **)
end.

Problema 3.5: raiz


program raiz(input, output);
(*
Obtener la raiz cuadrada de un numero real x.
Se supone que x no es negativo.
*)
var x, y: real;
begin
writeln(** Calcular la raiz cuadrada de x **);
writeln;
write(Entrar x (> 0): ); readln(x);
y := sqrt(x);
writeln;
writeln(La raiz cuadrada de , x, es , y);
writeln; writeln(** Fin **)
end.

Problema 3.6: segundos


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;
hs := mins div 60; mins := mins mod 60;
writeln(hs, hs, , mins, mins, , segs, segs.);
writeln; writeln(** Fin **)
end.

Problema 3.10: enteroareal


program enteroareal(input, output);
(* Asignar un entero a un real *)

Pg. 145

Pg. 146

Programas mencionados

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.

Problema 3.17: positivo


program positivo(input, output);
(* dice si un numero entero es positivo *)
var a: integer;
begin
writeln(** Ver si un numero entero es positivo);
writeln;
write(Entrar un numero: ); readln(a);
writeln;
writeln(es positivo?: , a > 0);
writeln; writeln(** Fin **)
end.

Problema 3.19: caracteres1


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);
writeln;
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,
o los que sean necesarios *)
write( Verificacion:);
write( el caracter correspondiente a , i:1);
writeln( es , chr(i));
writeln; writeln(** Fin **)
end.

valorabsoluto (Problema 4.2)

Pg. 147

Problema 4.2: valorabsoluto


program valorabsoluto(input, output);
(* encuentra el valor absoluto del numero real x *)
var x, y: real;
begin
writeln(** Encontrar el valor absoluto de un numero real);
writeln;
write(Entrar el numero: ); readln(x);
if (x >= 0) then y := x else y := -x;
writeln;
writeln(El valor absoluto de , x, es , y);
writeln; writeln(** Fin **)
end.

Problema 4.4: comparar


program comparar(input, output);
(* decidir cual numero entero es mayor o si son iguales *)
var a, b: integer;
begin
writeln(** Comparar dos numeros enteros);
writeln;
write(Entrar un numero entero: ); readln(a);
write(Entrar otro numero entero: ); readln(b);
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.5: caracteres2


program caracteres2(input, output);
(* decidir si un caracter es una letra mayuscula o
minuscula, o no es letra, comparando caracteres *)
var c: char;
begin
writeln(** Decide si un caracter es una letra );
writeln( mayuscula o minuscula o no es letra);
writeln;
write( Entrar un caracter: ); readln(c); writeln;
write(c);

Pg. 148

Programas mencionados

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
writeln( no es una letra);
writeln; writeln(** Fin **)
end.

Problema 4.11: resto


program resto(input, output);
(* Hallar el resto de la division entre dos numeros
enteros positivos mediante restas sucesivas. *)
var a, b, r: integer;
begin
writeln(** Hallar el resto de la division entre a y b,);
writeln( (enteros positivos) mediante restas sucesivas);
writeln;
write(Entrar el valor de a: ); readln(a);
write(Entrar el valor de b: ); readln(b);
r := a;
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.12: tablaseno1


program tablaseno1(input, output);
(* hacer una tabla del seno donde los angulos
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): );

gauss (Problema 4.13)

readln(inicial);
write(Entrar el valor final (en grados): );
readln(final);
write(Entrar el valor de incremento (en grados): );
readln(incremento);
writeln;
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;
writeln; writeln(** Fin **)
end.

Problema 4.13: gauss


program gauss(input, output);
(* sumar 1 + 2 +...+ n *)
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;
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.16: cifras


program cifras(input, output);
(* determinar la cantidad de cifras significativas
(en base 10, ignorando el signo) de un entero
entrado por terminal.
E.g. 246 -> 3, -908 -> 3, pero 0 -> 1 *)

Pg. 149

Pg. 150

Programas mencionados

var a, b, c: integer;
begin
writeln(** Determinar la cantidad de cifras de un);
writeln( entero (sin tener en cuenta el signo));
writeln;
write(Entrar el numero: ); readln(a); writeln;
if (a < 0) then b := -a else b := a;
c := 0;
repeat
c := c + 1;
b := b div 10
until b = 0;
writeln(La cantidad de cifras de , a:1, es , c:1);
writeln; writeln(** Fin **)
end.

Problema 4.17: epsmin


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;
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.

Problema 4.18: potencia


program potencia(input, output);
(* calculo de potencias con exponente natural *)
var

eolnprueba (Problema 4.23)

x, pot: real;
n, i: integer;
begin
writeln(** Calculo de la potencia x a la n);
writeln;
write(Entrar x (real): ); readln(x);
write(Entrar n (entero positivo): ); readln(n);
pot := 1;
for i := 1 to n do pot := pot * x;
writeln;
writeln(x, a la , n:1, es , pot);
writeln; writeln(** Fin **)
end.

Problema 4.23: eolnprueba


program eolnprueba(input, output);
(* prueba del funcionamiento de eoln *)
var a: integer;
begin
writeln(** Prueba del funcionamiento de eoln);
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.24: eco


program eco(input, output);
(* Ingresar numeros y hacer un eco de ellos por pantalla. *)
var
n: integer;
begin
writeln(** Programa para hacer un <<eco>> por pantalla);
writeln(
de numeros enteros ingresados.);
writeln(
El fin de datos es <retorno> sin datos.);
writeln;
writeln( Ingresar enteros (fin de datos con <retorno>):);
while (not eoln) do begin readln(n); writeln(n) end;
writeln; writeln(** Fin **)

Pg. 151

Pg. 152

Programas mencionados

end.

Problema 4.25: sumardatos


program sumardatos(input, output);
(* Calcular la suma de datos entrados por terminal, donde
el fin de datos se indica con <retorno> sin datos *)
var
s, x: real;
findatos: boolean;
begin
writeln(** Calcular la suma de los datos entrados);
writeln( indicando el fin de datos con doble <retorno>);
writeln;
s := 0; findatos := false; (*
repeat
write(Entrar un dato (fin
if (eoln) then findatos :=
else begin readln(x); s :=
until (findatos);

inicializacion *)
= <retorno>): );
true
s + x end

readln; (* para leer el ultimo fin de datos,


en este caso innecesario *)
writeln;
writeln(La suma de los datos es , s);
writeln; writeln(** Fin **)
end.

Problema 4.27: palabras


program palabras(input, output);
(* leer renglones y contar la cantidad de palabras *)
var
c: char;
cuenta: integer;
enpalabra: boolean;
begin
writeln(** Contar la cantidad de palabras ingresadas.);
writeln(Entrar renglones (Fin con renglon "vacio"));
writeln;
cuenta := 0;
while (not eoln) do begin
(* renglon no vacio: leerlo *)
enpalabra := false; (* no hay palabras leyendose *)
repeat (* leer caracteres hasta el fin del renglon *)

babilonico (Problema 5.11)

read(c);
if (c = ) then enpalabra := false
else if (not enpalabra) then (* nueva palabra *)
begin enpalabra := true; cuenta := cuenta + 1 end
until eoln; (* fin del renglon *)
readln (* leer fin del renglon *)
end;
readln; (* leer fin de linea de renglon vacio *)
writeln(Se ingresaron , cuenta:1, palabras);
writeln; writeln(** Fin **)
end.

Problema 5.11: babilonico


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;
var
it: integer;
a, x, y: real;
begin
(* carteles *)
writeln(** Metodo babilonico o de Newton para);
writeln( aproximar la raiz cuadrada del numero);
writeln( real positivo "a"); writeln;
(* datos de entrada *)
write( Entrar a (> 0): ); readln(a); writeln;
(* inicializacion *)
it := 1; x := x0; y := (x + a/x)/2;
(* lazo principal *)
while ((it <= itmax) and (abs(x - y) > tol)) do
begin x := y; y := (x + a/x)/2; it := it + 1 end;
(* salida *)
writeln(Iteraciones realizadas: , it - 1);
if (it > itmax) then
writeln(** Iteraciones maximas alcanzadas **);
writeln(Solucion aproximada obtenida: , y:20:10);
(* fin *)

Pg. 153

Pg. 154

Programas mencionados

writeln; writeln(** Fin **)


end.

Problema 5.13: euclides


program euclides(input, output);
(*
Algoritmo de Euclides, usando restos
en vez de restas sucesivas
*)
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);
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.

Problema 6.1: unidades


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);

renglon (Problema 6.2)

writeln;
(* inicializar el arreglo *)
for digito := 0 to 9 do terminaen[digito] := 0;
(* entrar datos *)
write(Entrar un entero (fin = <retorno>): );
while (not eoln) do begin
readln(dato);
digito := abs(dato) mod 10;
terminaen[digito] := terminaen[digito] + 1;
write(Entrar un entero (fin = <retorno>): )
end;
readln;
(* salida *)
writeln;
writeln( Digito
numero de datos);
for digito := 0 to 9 do
writeln( digito:4, terminaen[digito]:20);
writeln; writeln(** Fin **)
end.

Problema 6.2: renglon


program renglon(input, output);
(*
Leer un renglon ingresado por la terminal
y escribirlo tambien por la terminal
*)
const MAXC = 255; (* maximo tamanio de arreglo *)
var
n: integer; (* numero de caracteres en el renglon *)
i: integer; (* variable auxiliar para imprimir *)
r: array[1..MAXC] of char; (* el renglon a procesar *)
begin
(* titulo *)
writeln(** Leer un renglon ingresado por terminal,);
writeln(
y volver a escribirlo);
writeln;
(* entrada: leer renglon *)
write(Escribir algo );
writeln((no mas de , MAXC:1, caracteres.):);
n := 0;
while ((not eoln) and (n < MAXC)) do begin
n := n + 1; read(r[n]) (* aca es read y no readln *)
end;
readln; (* para leer el ultimo fin de linea *)

Pg. 155

Pg. 156

Programas mencionados

(* salida: imprimirlo *)
writeln;
writeln(El renglon ingresado es: );
writeln;
for i := 1 to n do write(r[i]);
writeln;
(* Fin *)
writeln; writeln(** Fin **)
end.

Problema 6.3: busquedalineal


program busquedalineal(input, output);
(*
Dado un arreglo a1, a2,... y un elemento x, ver
si x = ai para algun i.
*)
const MAXN = 20;
var
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
write(Entrar el dato , ndatos:1);
write( (fin = <retorno>): );
if (eoln) then begin (* no hay mas datos *)
findatos := true; readln end
else begin (* nuevo dato *)
readln(a[ndatos]); (* incorporarlo al arreglo *)
ndatos := ndatos + 1 (* para el proximo *)
end
until (findatos);
ndatos := ndatos - 1;
(* imprimir el arreglo *)
writeln(El arreglo es: ); writeln;
for i := 1 to ndatos do begin
write(a[i]:10);

potencias (Problema 7.2)

if ((i mod 5) = 0) then writeln


end;
if ((ndatos mod 5) <> 0) then writeln;
writeln;
(* elemento a buscar *)
write(Entrar el entero x a buscar: ); readln(x);
writeln;
(* lazo principal: suponemos ndatos > 1 *)
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
writeln(No se encontro);
writeln; writeln(** Fin **)
end.

Problema 7.2: potencias


program potencias(input, output);
(*
Primer ejemplo de uso de funciones,
definiendo potencia(x,n) para comparar
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
end;
begin
(* carteles iniciales *)
writeln(** Comparar x a la n con y a la m);
writeln;
(* entrar datos *)
write(Entrar x (real): ); readln(x);

Pg. 157

Pg. 158

Programas mencionados

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);
(* comparar e imprimir *)
write(x:10:5, a la , n:1);
if (xn < ym) then write( es menor que )
else if (xn > ym) then write( es mayor que )
else write( es igual a );
writeln(y:10:5, a la , m:1);
(* cartel final *)
writeln; writeln(** Fin **)
end.

Problema 7.3: biseccion


program biseccion(input, output);
(*
Metodo de biseccion para encontrar raices de funciones
reales y continuas.
Cambiar la funcion cuando se investigue otra ecuacion.
Version simplificada que se va mejorando
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 *)
: 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;

tablaseno2 (Problema 7.6)

write(Implementacion para );
writeln(f(x) = x (x + 1) (x + 2) (x - 4/3));
writeln;
(* datos de entrada *)
write( Entrar cota inferior para x: ); readln(poco);
write( Entrar cota superior para x: ); readln(mucho);
writeln;
(* inicializacion *)
ypoco := f(poco); ymucho := f(mucho);
seguir := (ypoco * ymucho < 0);
iter := 0;
(* lazo principal *)
while (seguir) do begin
iter := iter + 1;
x := (poco + mucho) / 2;
y := f(x);
if (abs(y) < dify) then seguir := false
else if (iter = maxiter) then seguir := false
else if (y * ypoco < 0) then mucho := x
else begin poco := x; ypoco := y end
end;
(* 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;
(* fin *)
writeln; writeln(** Fin **)
end.

Problema 7.6: tablaseno2


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;

Pg. 159

Pg. 160

Programas mencionados

begin
writeln(** Hacer una tabla del seno dando valores);
writeln( inicial, final, y del incremento (en grados).);
writeln
end;
procedure leerdatos;
begin
write(Entrar el valor inicial (en grados): );
readln(inicial);
write(Entrar el valor final (en grados): );
readln(final);
write(Entrar el valor de incremento (en grados): );
readln(incremento);
writeln
end;
procedure imprimirtabla;
var
grados: integer;
radianes: real;
begin
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
cartelesiniciales;
leerdatos;
imprimirtabla;
cartelfinal
end.

Problema 7.7: intercambio


program intercambio(input, output);
(* prueba de intercambio de valores entre variables *)
var x, y: integer;
procedure incorrecto(a, b: integer);
(* a, b pasados por valor *)
var t: integer;
begin t := a; a := b; b := t end;

deconsolaaarchivo (Problema 8.8)

begin
writeln(** Prueba de intercambio de variables);
writeln;
x := 5; y := 2;
writeln(Antes: x = , x:2, , y = , y:2);
incorrecto(x,y);
writeln(Despues: x = , x:2, , y = , y:2);
writeln; writeln(** Fin **)
end.

Problema 8.8: deconsolaaarchivo


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
(* carteles *)
writeln(** Copiar de consola a archivo);
writeln;
(* nombre de archivo a escribir *)
writeln( Entrar el nombre del archivo a escribir);
writeln( Atencion: si ya existe se borra!);
write( Archivo: ); readln(nombre);
rewrite(archivo, nombre);
(* copiar de consola a archivo *)
writeln;
write(Entrar datos en , nombre);
writeln( (Fin con retorno "vacio"));
while (not eoln) do begin
repeat
read(c); write(archivo, c)
until eoln;
readln;
writeln(archivo)
end;
close(archivo);
(* Fin *)
writeln; writeln(** Fin **)
end.

Pg. 161

Pg. 162

Programas mencionados

Problema 8.8: dearchivoaconsola


program dearchivoaconsola(input, output);
(* Copiar de archivo a consola *)
var
archivo: text;
c: char;
nombre: string;
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;
while (not eof(archivo)) do
if (eoln(archivo)) then begin
readln(archivo); writeln end
else begin
read(archivo, c); write(c) end;
close(archivo);
(* Fin *)
writeln; writeln(** Fin **)
end.

Problema 9.1: dado


program dado(input, output);
(*
Simular tirar un dado.
Usa la sentencia "random" de Turbo Pascal.
*)
var d: integer;
begin
writeln(** Simular tirar un dado);
writeln;
randomize;
d := 1 + random(6);

dados (Problema 9.2)

writeln(El dado que salio es , d:1);


writeln;
writeln(** <retorno> para Fin **); readln
end.

Problema 9.2: dados


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);
writeln;
write(Entrar el valor que debe aparecer );
write((entre 1 y 6): );
readln(numero);
randomize;
tiros := 0;
repeat tiros := tiros + 1
until (1 + random(6) = numero);
writeln;
write(El numero , numero:1);
writeln( tardo , tiros, tiros hasta aparecer.);
writeln;
writeln(** <retorno> para Fin **); readln
end.

Problema 13.1: arbolbinario


program arbolbinario(input, output);
(*
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 caracteres entrados por terminal,
contando las apariciones.
*)

Pg. 163

Pg. 164

Programas mencionados

const
MAXN = 100; (* maximo numero de datos a guardar *)
nada = 0; (* para cuando no hay hijos *)
type
indice = integer; (* para senialar los indices *)
tipodato = char; (* 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[1..MAXN] of nodo;
var
narbol: integer;
arbol: tipoarbol;
raiz: indice;
dato: tipodato;

(*
(*
(*
(*

numero de nodos en el arbol *)


el arbol! *)
donde empieza el arbol *)
dato leido *)

function entrardatos(var dato: tipodato): boolean;


begin
write(Entrar el caracter (fin = <retorno>): );
if (eoln) then
begin entrardatos := false; readln end
else
begin entrardatos := true; readln(dato) end
end;
procedure binario(x: tipodato; var p: indice);
var y: tipodato;
begin
if (p = nada) then begin
(* nueva entrada *)
narbol := narbol + 1;
p := narbol;
with arbol[p] do begin
llave := x; cuenta := 1;
izquierda := nada; derecha := nada
end
end
else begin
y := arbol[p].llave;
if (x = y) then
(* dato existente *)
arbol[p].cuenta := arbol[p].cuenta + 1
else if (x < y) 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: indice);

arbolbinario (Problema 13.1)

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 caracteres y contar);
writeln( las veces que aparecieron);
writeln;
(* inicializacion *)
narbol := 0;
raiz := nada;
(* lectura de datos y construccion del arbol *)
while (entrardatos(dato)) do binario(dato, raiz);
(* salida y fin *)
writeln;
writeln(Impresion en orden:);
writeln;
writeln(
Caracter
Cantidad de Apariciones);
enorden(raiz);
writeln; writeln(** Fin **)
end.

Pg. 165

Apndice B

Breve referencia de Pascal


B.1. Operadores
B.1.1. Aritmticos
Operador
+ (unitario)
- (unitario)
+ (binario)
- (binario)
*
div
/
mod

operacin

operando

resultado

identidad
inversin de signo
suma
resta
producto
divisin entera
divisin
resto

entero o real
entero o real
entero o real
entero o real
entero o real
entero
entero o real
entero

mismo que operando


mismo que operando
entero o real
entero o real
entero o real
entero
real
entero

B.1.2. Relacionales
Operador
=
<>
<
>
<=
>=

operacin

operando

resultado

igualdad
desigualdad
menor
mayor
menor o igual
mayor o igual

simple, string o puntero


simple, string o puntero
simple o string
simple o string
simple o string
simple o string

lgico
lgico
lgico
lgico
lgico
lgico

B.1.3. Lgicos
Operador

operacin

operando

resultado

not
or
and

negacin
disyuncin
conjuncin

lgico
lgico
lgico

lgico
lgico
lgico

B.2. Identificadores estndares

Pg. 167

B.1.4. Precedencia
Operador
*
=

/
<>

Clasificacin
negacin lgica
multiplicacin
adicin
relacin

not
div mod and
+ - or
> < >= <= in

B.2. Identificadores estndares


Constantes
false

maxint

true

Tipos
boolean

char

integer

real

text

Variables
input

output

Funciones con argumentos numricos


Funcin

operacin

argumento

resultado

abs
arctan
chr
exp
ln
odd ()
round ()
sin
sqr
sqrt
trunc ()

valor absoluto
arco tangente
carcter en orden
exponencial (ex )
logaritmo (base e)
si impar
redondeo
seno
cuadrado
raz cuadrada
truncar

entero o real
entero o real
entero
entero o real
entero o real
entero
real
real o entero
real o entero
real o entero ( 0)
real

entero o real
real
carcter
real
real
lgico
entero
real
real o entero
real
entero

()

odd (x) es verdadero si x es impar.

()

round (x) = trunc(x + 0.5) para x 0, y round (x) = trunc(x 0.5) para

x 0.
(
()

trunc(x ) =

bxc
dxe

si x 0,
si x < 0.

- Las funciones trigonomtricas cos y sin tienen sus argumentos en


radianes (y no grados). Del mismo modo la funcin trigonomtrica
inversa arctan retorna un ngulo en radianes. Para pasar entre grados y radianes, usar que 180 = radianes. La constante no est
definida en Pascal, pero puede usarse que = 4 arctan 1. Observar
que las funciones tan, arccos o arcsen no estn definidas en Pascal.

Otras funciones
eof

eoln

ord

Procedimientos
dispose get
read
readln
writeln

pred
new
reset

succ
pack
rewrite

page
unpack

put
write

Pg. 168

Breve referencia de Pascal

B.3. Nombres reservados


and
downto
function
nil
program
type

array
do
goto
not
record
until

begin
else
if
of
repeat
var

case
end
in
or
set
while

const
file
label
packed
then
with

div
for
mod
procedure
to

Apndice C

Algunas notaciones y
smbolos usados
Ponemos aqu algunas notaciones, abreviaturas y expresiones usadas (que
pueden diferir de algunas ya conocidas), slo como referencia: deberas mirarlo
rpidamente y volver cuando surja alguna duda.

C.1. Lgica

x2 puede leerse como si x es positivo,

implica o entonces. x > 0 x =


entonces...

si y slo si. Significa que las condiciones a ambos lados son equivalentes.
Por ejemplo x 0 |x| = x se lee x es positivo si y slo si...

existe. k Z tal que... se lee existe k entero tal que...

para todo. x > 0, x = x2 se lee para todo x positivo,...

La negacin lgica no. Si p es una proposicin lgica, p se lee no p.


p es verdadera p es falsa.

La conjuncin lgica y. Si p y q son proposiciones lgicas, p q es


verdadera tanto p como q son verdaderas.

La disyuncin lgica o. Si p y q son proposiciones lgicas, 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.

no pertenece. x A significa que x no es un elemento de A.

unin de conjuntos, A B = {x : x A o x B}.

interseccin de conjuntos, A B = {x : x A y x B}.

diferencia de conjuntos, A \ B = {x : x A y x
/ B}.

Pg. 170
| |,
#

Algunas notaciones y smbolos usados

cardinal, |A|, o a veces #(A), es la cantidad de elementos en el conjunto


A.
El conjunto vaco, #() = 0.

C.3. Nmeros: conjuntos, relaciones, funciones


N

El conjunto de nmeros naturales, N = {1, 2, 3, . . . }. Observar que para


nosotros 0
/ N.

Los enteros, Z = {0, 1, 1, 2, 2, . . . }.

Los racionales p/q, donde p, q Z, q 6= 0.

Los reales. Son todos los racionales ms nmeros como


no tienen una expresin decimal peridica.

R+

Los reales positivos, R+ = {x R : x > 0}.

Los complejos, de la forma


z = a + bi, con a, b R y donde i es la
unidad imaginaria, i = 1 .

Dado el nmero x, x representa dos nmeros: x y x.

Para m, n Z, m | n se lee m divide a n, o n es mltiplo de m, y significa


que existe k Z tal que n = km.

aproximadamente. x y se lee x es aproximadamente igual a y.

mucho menor. x  y se lee x es mucho menor que y.

mucho mayor. x  y se lee x es mucho mayor que y.

|x|

El valor
p absoluto o mdulo del nmero x. Si z = a + b i C con a, b R,
|z| = a2 + b2 .

bxc

El piso de x, x R. Es el mayor entero que no supera a x, por lo que


bxc x < bxc + 1: bc = 3, bc = 4, bzc = z z Z.

[x]

La parte entera de x, x R. [x] = bxc. Nosotros usaremos la notacin


bxc, siguiendo la costumbre en las reas relacionadas con la computacin.

dxe

El techo de x, x R. Es el primer entero que no es menor que x, por


lo que dxe 1 < x dxe: de = 4, de = 3, dze = z z Z.

2, , etc., que

ex ,
exp(x) La funcin 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.
- Para no confundir con los logaritmos en base 10, evitaremos la
notacin log x: si nos referimos a la base e usaremos ln x, y en
otras bases logb x.

C.4. Nmeros importantes en programacin


sen x,
sin x
cos x

Pg. 171

Las funcin trigonomtrica seno, definida para x R. Su inversa es la


funcin arcsen (arcsin en ingls).
Las funcin trigonomtrica coseno, definida para x R. Su inversa es
la funcin arccos.

tan x

Las funcin trigonomtrica tangente, definida como tan x = sen x/ cos x.


Su inversa es la funcin arctan.
arcsen x,
arccos x,
arctan x Funciones trigonomtricas inversas respectivamente de sen, cos y tan.
sgn(x),
signo(x) Las funcin signo, definida para x R por

si x > 0,
1
signo(x) = 0
si x = 0,

1 si x < 0.
- Algunos autores consideran que signo(0) no est definido.

P
Q

Pn

ai = a1 + a2 + + an .
Qn
Indica producto, i=1 ai = a1 a2 an .
Indica suma,

i=1

C.4. Nmeros importantes en programacin


maxint El mximo entero que admite el compilador.
mn

El menor nmero positivo que admite el compilador.

mq

El menor nmero positivo que sumado a 1 da mayor que 1. Su valor


depende del compilador.

C.5. Generales
i.e.

es decir o esto es, del latn id est.

e.g.

por ejemplo, del latn exempli gratia.

Bibliografa
[1] J. L. Bentley: More Programming Pearls, Addison-Wesley, 1988.
(Pg. 116.)
[2] N. L. Biggs: Introduction to Computing with Pascal, Oxford Science Publications, 1989. (Pg. 23.)
[3] A. Engel: Exploring Mathematics with your computer, The Mathematical
Association of America, 1993. (Pg. 3.)
[4] K. Jensen y N. Wirth: PascalUser Manual and Report, Springer Verlag, 1985. (Pgs. 3, 84 y 98.)
[5] R. Johnsonbaugh: Matemticas Discretas (4"a ed.), Prentice Hall, 1999.
[6] Sir T. L. Heath: The thirteen books of Euclids Elements, vol. 2, Dover
Publications, 1956. (Pg. 54.)
[7] B. W. Kernighan y D. M. Ritchie: El lenguaje de programacin C
(2"a ed.), Prentice-Hall Hispanoamericana, 1991. (Pgs. 10 y 121.)
[8] D. E. Knuth: The Art of Computer Programming, Addison-Wesley. Vol. 1
(3"a ed.), 1997; vol. 2 (3"a ed.), 1997; vol. 3 (2"a ed.), 1997. (Pgs. 52, 86,
89 y 116.)
[9] C. H. Papadimitriou y K. Steiglitz: Combinatorial Optimization, Algorithms and Complexity, Dover, 1998. (Pgs. 139 y 143.)
[10] K. H. Rosen: Elementary Number Theory and its Applications (3"a ed.),
Addison Wesley, 1993. (Pg. 40.)
[11] N. Wirth: Introduccin a la Programacin Sistemtica, El Ateneo, 1984.
(Pgs. 3 y 75.)
[12] N. Wirth: Algoritmos y Estructuras de Datos, Prentice-Hall Hispanoamericana, 1987. (Pgs. 3, 40, 75, 89, 98 y 121.)

ndice alfabtico
mq , 33, 171
mn , 18, 33, 171
, ver pi
;, 8
antes de else, 25
antes de end, 28
ao bisiesto, 26
adyacencia (matriz de), 124
adyacente (vrtice), 122
aguja (en Hanoi), 103, 109
Al-Khwarizmi, 24
algoritmo, 24
de bsqueda, ver bsqueda
de clasificacin, ver clasificacin
Dkstra, 136
Euclides, 49
Floyd-Warshall, 139
para generar caminos, 111
para grafos, ver grafo
para permutaciones, 113
para subconjuntos, 109
Prim, 140
recorrer, 127
Ana, 87
and, 21
aproximacin, ver tambin convergencia, mtodo Monte Carlo
factorial, 100
nmero armnico Hn , 42
raz cuadrada, 46
raz de ecuacin, 66
sen x, 61
rbol
binario ordenado, 117
caracterizacin, 124
de expansin, 128
definicin, 123
generador, 128
mnimo, 134
hoja, 124
rama, 124
archivo

caja de herramientas, 80
de texto, 82, ver tambin editor
de textos
para guardar programa, 8
arista
de grafo, 122
extremo, 122
lista de, 124
array, ver arreglo
arreglo, 55
dimensin, 55
ASCII (cdigo), 22, 23
backtracking, ver rastreo inverso
begin-end, 27
en parte principal, 8
para funciones y procedimientos, 63
Beli, 68, 69
Bigollo, ver Fibonacci
Binet, 54
biseccin (mtodo), 66
bit, 5
Boole, 11
boolean, ver variable lgica
bucle (lazo), 29
bsqueda
binaria, 90
en grafo, 127
lineal, 57
lineal con centinela, 89
byte, 6
catico, 48
caja de herramientas, 80
clculo
numrico, ver sec. 5.1
suma o producto, 31
calendario (juliano y gregoriano), 26
camino
uv, 123, 131
en grafo, 123
carcter
comparacin, 26

Pg. 174
imprimible, 22
y ordinal, 23
case, 24
char, 11, ver tambin carcter
chr, 22
ciclo
de Euler, 131
en grafo, 123
clasificacin
rbol binario, 117
caracteres, 26
comparacin de mtodos, 94
estabilidad, 98
insercin directa, 92
intercambio directo, 93
mtodos, 92
por conteo, 94
registros, 97, 98
seleccin directa, 92
cola, 107
de prioridad, 139
fifo, 107, 132
lifo (pila), 107, 132
componente conexa, 123
condicin
de control, 25
consola, 5
const, 31, 57, 62
contador, 30, 31, 33, 86, 110112,
114
en for, ver variable de control
en arreglo, 56
en lazo, 67, 90, 93, 95
control
condicin de, 25
variable de, 35
convergencia, 43, 44, ver tambin sec.
5.1
mtodo de punto fijo, 43, 48
polinomios, 59
CPU, 5
criterio de parada, 46, 48, 66, 68
cuadrado perfecto, 55
dato
tipo de, 11
de Moivre, A., 54
De Morgan, 60
leyes de, 22
densidad, 18
Dkstra, 136, 140
algoritmo, 136
Diofanto, 52

ndice alfabtico
Dirichlet, 44
principio de, 87
div, 15
do
y for, 34
y while, 29
downto, 35, 36, ver tambin for-do
Durstenfeld R., 116
e (= 2.718 . . .), 35, 43, 100, 170
Monte Carlo, 88
eco, 38
de renglones, 40, 78
ecuacin
cuadrtica, 42
diofntica, 52
no lineal, 53
editor de textos, 7, 84, ver tambin
archivo de texto
else, ver tambin if-then
; antes de, 25
end, ver tambin begin-end
; antes de, 28
sin begin, 95
entero, ver integer
entrada, 5
eof, 83
eoln, 36
psilon
mquina (mq ), 33
mnimo (mn ), 33
error
absoluto y relativo, 34, 48
numrico, 32, 33, 35, 41, 42, 45,
67
estrategia, ver mtodo
Euclides, 50
algoritmo para mcd, 49, 50, 52
Euler, 42, 54
ciclo de, 131
constante de (), 42
Excel, 69
exponente, 18
factorial, 32, 53
recursivo, 100
Fermat, 52
Fibonacci, 54
nmero de, 53, 111
Flor, 105
Floyd R., 116, 139
Floyd y Warshall (algoritmo), 139
for-do, 34

ndice alfabtico
formato
a la salida, 15
para caracteres, 23
para entero, 15
para real, 15
para variables lgicas, 21
frmula
Euler-Binet, 53
Gauss, 32, 49, 99
para xy , 35
Stirling para n!, 100
forward, 70
funcin, ver tambin procedimiento,
ver cap. 7
function, ver cap. 7
Gauss, 32
suma de, 31, 41, 99
generador, ver rbol generador
Geri, 52
get (en Pascal), 108
get (en cola), 108
grafo, 122, 127
arista, 122
con pesos, 133
conexo, 123
dirigido (digrafo), 124
representacin, 124
simple, 122
vrtice, 122
Gregorio (Papa), 26
Guille, 52
Hanoi (torres de), 103
Horner, 60
regla, 60

Pg. 175
Leonardo de Pisa, ver Fibonacci
lineal
estructura de arreglo, 117
lista, ver tambin arreglo
de aristas, 124
encadenada, 108, 125
llamar
funcin o procedimiento, 64
Maggie, 114
mantisa, 18
matriz
arreglo multidimensional, 80
de adyacencias, 124
mximo comn divisor, ver mcd
maxint, 18, 171, ver tambin integer
mcd, 49, 52
recursivo, 101, 102
memoria (de computadora), 5
mtodo
babilnico, 46
barrido, 52
biseccin, 66
clasificacin, ver clasificacin
Monte Carlo
e, 88
, 88
Newton-Raphson, 47
punto fijo, 43
mnimo comn mltiplo (mcm), 51
mod, 17
Moses L. E., 116

Kruskal, 140

Newton, 47
nivel (en rbol), 124
normalizacin, 48
not, 21
notacin
cientfica, 14, 15, 18
punto fijo, 15
punto flotante, 15
nmero
armnico (Hn ), 41
de Fibonacci, ver Fibonacci
entero, ver tambin seccin 5.2,
integer
factorial, ver factorial
primo, 49, ver primo
real, ver real
romano, 27, 40

Lagrange, 60, 61
lazo, 29

Oakford R. V., 116


or, 21

identificador (de variable), 11


if-then, 25
ndice, 55, ver tambin subndice
input (al comienzo de programa), 8
integer, 11
invocar
funcin o procedimiento, 64
juego, ver tambin problema
cartas (clasificar), 92
cartas (manos), 114
Julio Csar, 26

Pg. 176
ord, 22
ordinal y carcter, 23
output (al comienzo de programa),
8
Pablito, 51
packed array, 81
parmetro
formal, 72
real, 72
pasar (por valor o referencia), 72
Pascal (Blaise), 7
permutacin
de n en k, 114
pi (), 31, 61
en programa, 31, 71
Monte Carlo, 88
punto fijo, 46
pila, 107
en recorrido, 129
piso, 20, 170
Pitgoras, 50
terna pitagrica, 52
polinomio, 59
Lagrange, 59, 60
pop (en pila), 108
potencia (clculo de), 35, 62, 64
Prim, 140
algoritmo, 140
primo, 49, 105
primos entre s (coprimos), 49
principio
de Dirichlet, casillero o palomar,
ver Dirichlet, principio de
problema
cajas, 91
de manos, 114
del cumpleaos, 87
inters sobre saldo, 68
numrico, 42
torres de Hanoi, 103, 105
procedimiento, ver funcin, procedure
procedure, ver cap. 7
programa, 6
corrida, 6
ejecucin, 6
ejecutable, 7
fuente, 7
mencionado
arbolbinario, 119, 133, 163
babilonico, 48, 153
biseccion, 66, 68, 158
busquedalineal, 57, 77, 89, 156

ndice alfabtico
caracteres1, 23, 146
caracteres2, 26, 147
cifras, 33, 149
comparar, 26, 147
dado, 86, 162
dados, 86, 163
dearchivoaconsola, 82, 84, 161
deconsolaaarchivo, 82, 84, 161
eco, 38, 39, 151
enteroareal, 19, 145
eolnprueba, 37, 151
epsmin, 33, 36, 150
euclides, 50, 154
gauss, 32, 36, 149
holamundo, 7, 8, 144
intercambio, 72, 160
leerentero, 15, 25, 144
palabras, 39, 152
positivo, 21, 22, 146
potencia, 35, 150
potencias, 64, 72, 157
raiz, 17, 145
renglon, 57, 109, 155
resto, 29, 36, 148
segundos, 17, 145
sumardatos, 38, 39, 56, 57, 152
sumardos, 13, 15, 20, 144
tablaseno1, 31, 36, 70, 71, 148
tablaseno2, 71, 72, 159
unidades, 56, 154
valorabsoluto, 25, 146
prueba de escritorio, 29
puntero, 63, 108
push (en pila), 108
put (en Pascal), 108
put (en cola), 108
Raphson, 47
rastreo inverso, 121, 129
read, 16
readln, 15
real, 11
record, 95
recorrido de grafo, 127
a lo ancho, 129, 132
en profundidad, 129
redondear, 19, ver tambin truncar
registro, 95
regla de Horner, 60
relacin de recurrencia, 99
repeat-until, 33
reset, 82

ndice alfabtico
retornar (en funcin o procedimiento), 63
rewrite, 82
romano, ver nmero romano
rutina, 70, ver tambin funcin, procedimiento
salida, 5
seudo-cdigo, 128
signo (funcin), 44, 171
Stirling, 100
string, 81, ver tambin cadena de
caracteres
subndice, 55, ver tambin ndice
techo, 20, 170
tcnica, ver mtodo
teorema
caracterizacin de rboles, 124
Euler, 131
suma de grados, 127
terminal, 5
terna pitagrica, 52
text, ver tambin archivo de texto
como argumento, 82
tipo, 82
texto, ver editor de, archivo de
then, ver if-then
tipo (de variable), 11
to, ver for-do
truncar, 19, ver tambin redondear
type, 76
until, ver repeat-until
var
declaracin de variable, 12
pasar por referencia, 73
variable, 11
auxiliar, 53
carcter (char), 11, 22
de control en for, 35
elemental, 11
entera (integer), 11, 13
global, 65
lgica (boolean), 11, 20
local, 64
real (real), 11, 13
temporal, 53, 72
vrtice
adyacente, 122
aislado, 122
de grafo, 122
grado, 127

Pg. 177
von Neumann, 5
Warshall
y Floyd (algoritmo), 139
while-do, 29
Wiles, 52
with, 96
write, 14
writeln, 9

You might also like