Documentos de Académico
Documentos de Profesional
Documentos de Cultura
com
o m
Notas para los cursos de
.c
os
Computación y Programación
con el lenguaje Pascal r
fo
.m
na
Néstor E. Aguilera
tu
2003
on
yc
dm
.a
w
w
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Índice general
1. Preliminares 1
1.1. ¿Qué es esto? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Cómo usar este libro . . . . . . . . . . . . . . . . . . . . . . . . . 2
m
1.3. Organización y convenciones usadas . . . . . . . . . . . . . . . . 3
o
.c
2. El primer contacto 5
os
2.1. Un poco, muy poco, sobre cómo funciona la computadora . . . . 5
2.2. El puntapié inicial . . . . . . . . . . . . . . . . . . . . . . . . . . 6
r
fo
3. Tipos de datos elementales 9
.m
3.4. Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
dm
4. Tomando control 21
4.1. “If” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
.a
4.2. “While” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
w
4.3. “Repeat” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
w
4.4. “For” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
w
4.5. “Eoln” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5. Aplicaciones 35
5.1. Cálculo numérico elemental . . . . . . . . . . . . . . . . . . . . . 35
5.1.1. Mezclando números grandes y pequeños . . . . . . . . . . 35
5.1.2. Métodos iterativos: puntos fijos . . . . . . . . . . . . . . . 37
5.1.3. El método babilónico . . . . . . . . . . . . . . . . . . . . . 40
5.2. Números enteros y divisibilidad . . . . . . . . . . . . . . . . . . . 42
5.2.1. Ecuaciones diofánticas . . . . . . . . . . . . . . . . . . . . 42
5.2.2. Máximo común divisor y el algoritmo de Euclides . . . . . 43
5.2.3. Números primos . . . . . . . . . . . . . . . . . . . . . . . 45
5.3. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 47
6. Arreglos 49
6.1. Dimensionamiento de arreglos . . . . . . . . . . . . . . . . . . . . 49
6.2. Búsqueda Lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.3. Cribas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.4. Polinomios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.5. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 56
7. Funciones y Procedimientos 59
7.1. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2. El método de la bisección . . . . . . . . . . . . . . . . . . . . . . 63
7.3. Procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.4. Pasando por valor o por referencia . . . . . . . . . . . . . . . . . 68
m
8.2. Ingreso e impresión de arreglos . . . . . . . . . . . . . . . . . . . 74
o
8.3. La caja de herramientas . . . . . . . . . . . . . . . . . . . . . . . 76
.c
8.4. Arreglos multidimensionales . . . . . . . . . . . . . . . . . . . . . 77
os
8.5. “Strings” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
8.6. Manejo elemental de archivos de texto . . . . . . . . . . . . . . . 78
8.7. Problemas Adicionales . . . . . . . . . . . . . . . . . r . . . . . . . 81
fo
.m
9.2. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.3. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 88
tu
10.Búsqueda y clasificación 93
10.1. Búsqueda lineal con centinela . . . . . . . . . . . . . . . . . . . . 93
.a
11.Recursión 109
11.1. Funciones y procedimientos definidas recursivamente . . . . . . . 109
11.2. Los Grandes Clásicos de la Recursión . . . . . . . . . . . . . . . . 112
11.3. Problemas Adicionales . . . . . . . . . . . . . . . . . . . . . . . . 115
m
Problema 2.2: holamundo . . . . . . . . . . . . . . . . . . . . . . . . . 159
Problema 3.2: sumardos . . . . . . . . . . . . . . . . . . . . . . . . . . 159
o
Problema 3.3: leerdato . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
.c
Problema 3.4: raiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
os
Problema 3.7: enteroareal . . . . . . . . . . . . . . . . . . . . . . . . . 160
Problema 3.13: segundos . . . . . . . . . . . . . . . . . . . . . . . . . . 161
r
fo
Problema 3.16: positivo . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Problema 3.18: caracteres1 . . . . . . . . . . . . . . . . . . . . . . . . . 161
.m
m
C. Algunas notaciones y sı́mbolos usados 203
C.1. Lógica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
o
C.2. Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
.c
C.3. Números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
os
C.4. Números importantes en programación . . . . . . . . . . . . . . . 205
C.5. Generales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
r
fo
D. Sobre los compiladores Pascal 207
.m
Bibliografı́a 209
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 1
Preliminares
m
1.1. ¿Qué es esto?
o
.c
Algunos se enloquecen por la cerveza, otros prefieren vino, y también están
os
los abstemios. Pero todos estamos de acuerdo en que la computadora ocupa, y
cada vez más, un lugar importante en nuestras vidas.
r
fo
Decimos esto pensando en internet, el acceso a la información, la cuenta
del banco y los impuestos, multimedios como gráficos, sonidos, pelı́culas y jue-
.m
gos (videos), y siguiendo con una larga lista que, sin embargo, difı́cilmente in-
cluirá resolución de problemas matemáticos.
na
ros encuentros con la computadora y sus aplicaciones tienen que ver con hacer
“click” con el “ratón” para acceder a juegos, “browsers” y el correo electrónico.
on
“computadora” tiene que ver más con matemáticas que con “informática”.
Tratamos de ubicarnos entonces entre dos extremos. Por un lado el uso de
la computadora como un fin y no como un medio, donde lo importante es la
computadora relegando a un plano secundario, o aún ignorando, las matemáti-
cas. Por otro lado, el aprendizaje de sistemas avanzados con los que “apretando
botones” obtenemos resultados, muchas veces sin saber de dónde salen ni cuán
confiables son (o de qué estamos hablando).
Y seguramente que queremos apartarnos lo más posible de la peor de to-
das las propuestas, lamentablemente muy difundida: el aprendizaje rutinario de
técnicas de dudoso valor que son rápidamente olvidadas.
El núcleo del libro es la resolución de problemas para desarrollar el pensa-
miento matemático y las conexiones con el pensamiento algorı́tmico, pasando
de abstracciones al resultado concreto de un programa para la computadora.
Pág. 2 Preliminares
m
que en matemáticas— se aprende mirando lo que han hecho otros, y es inclusive
o
deseable el “reuso” de partes ya hechas. Sin embargo los programas presenta-
.c
dos difı́cilmente puedan considerarse “estado del arte”, y se ha sacrificado la
eficiencia y la brevedad tratando de hacer una presentación clara.
os
También sólo se usa seudo-código en los capı́tulos más avanzados (y en mı́ni-
r
ma medida), a contra corriente de otros cursos introductorios, con la convicción
fo
de que es muy difı́cil entender —e imposible concretar— qué se está diciendo en
.m
seudo-código. Es como tratar de aprender una sı́ntesis de los idiomas antes de
aprender a hablar en alguno (y podemos recordar el fracaso de la “matemática
na
moderna”).
En el libro usamos el lenguaje Pascal. Considerado “obsoleto” por distin-
tu
tas razones, tiene la gran ventaja de haber sido diseñado especialmente para la
on
fuerza de recursión.
w
Los dos últimos capı́tulos toman rumbos distintos y son bastante indepen-
dientes entre sı́. El capı́tulo 13 está pensado para aquéllos que ya hayan visto
un curso de matemática discreta o teorı́a de grafos, implementando (en forma
elemental) los primeros algoritmos de grafos. En cambio, el capı́tulo 14 muestra
las posibilidades de las listas encadenadas, liberándonos de algunas ataduras de
los arreglos.
m
pero para eso debe entender primero qué es lo que hacen las distintas partes y
o
—por sobre todo— a dónde se quiere llegar.
.c
La adquisición de conocimientos no es gratuita y requiere esfuerzo y tiempo,
y las equivocaciones forman parte inevitable de este proceso: ¡habrá que estar
os
dispuesto a pasar un buen rato en la silla!
r
Aún cuando no podamos resolver un problema en programación o en ma-
fo
temáticas, es poco útil que alguien nos cuente la solución si antes no hemos
.m
hecho un trabajo propio para apreciarla, e inclusive criticarla.
En algunos problemas se incluyen “sugerencias” para la resolución, que están
na
les parecerá que el material presentado es excesivo, y habrá otros que querrán
resolver más problemas: para éstos se incluye una sección de “Problemas Adi-
.a
En fin, habrá algunos que querrán seguir avanzando aún más, quizás después
w
Pág. 4 Preliminares
m
Usamos otros tipos de letra para indicar los nombres de los programas, ası́,
mientras que lo que escribiremos en la computadora está indicado en “mono-
o
tipo”, ası́, algunas veces entre comillas dobles, para recalcar algún fragmento,
.c
“ como éste ”, reservando las comillas simples para caracteres, como ‘ a ’.
os
Siguiendo la tradición norteamericana, la computadora expresa los núme-
ros poniendo un “punto” decimal en vez de la “coma”, de modo que para no
r
fo
confundirnos seguiremos esa práctica en todo el texto. Ası́, 1.589 es un número
entre 1 y 2, mientras que 1589 es un número entero, mayor que mil.
.m
“ñ”) al escribir los programas o mostrar resultados por pantalla, de modo que
en la escritura de códigos los obviaremos (escribiendo “a” o “ni”).
tu
pendiendo del sistema operativo, es posible que deba pulsarse en cambio la tecla
yc
Capı́tulo 2
El primer contacto
m
2.1. Un poco, muy poco, sobre cómo funciona
o
la computadora
.c
os
Conceptualmente, la computadora es una máquina que toma o accede a
datos, los procesa y devuelve resultados. Los datos o entradas y los resultados
r
fo
o salidas pueden ser simples como números o letras, o mucho más complicados
como una matriz o una base de datos.
.m
accede los datos y retorna los resultados secuencialmente, es decir, de a uno por
vez, y los datos a los que accede se guardan en una lugar denominado memoria.
on
tá constituida por muchas cajitas pequeñas llamadas bits por “Binary Digit”
w
1. Puesto que esta caja es demasiado pequeña para guardar información más
complicada que “sı́/no” o “blanco/negro”, los bits se agrupan en cajas un poco
más grandes llamadas bytes, que generalmente tienen 8 bits, conceptualmente
alineados, puesto que queremos que 00001111 sea distinto de 11110000.
A su vez, para las computadoras más recientes, estas unidades resultan dema-
siado pequeñas para alimentar a la CPU, por lo que los bits o bytes se agrupan
formando cajas de, por ejemplo, 32, 64 o 128 bits (usualmente potencias de 2),
siempre conceptualmente alineadas.
Los mismos programas pueden considerarse como un tipo especial de datos,
con “instrucciones” que dicen a la CPU qué hacer. En particular, el sistema
operativo de la computadora es un programa que alimenta constantemente a la
CPU, y le va a indicar, por ejemplo, que ejecute nuestro programa, leyendo las
instrucciones que contiene.
m
ceros y unos. Las instrucciones para la máquina se escriben como sentencias, de
o
acuerdo a la sintaxis del lenguaje, en lo que se llama programa fuente y deben
.c
ser traducidas a instrucciones que la CPU pueda entender. Este proceso se llama
compilado, dando por resultado un programa ejecutable.
os
El ciclo usual es entonces: escribir nuestro programa con un editor de textos,
r
eventualmente guardándolo en el disco rı́gido o diskette, y luego compilarlo y
fo
ejecutarlo.
.m
Nota: Esta descripción basta para nuestros propósitos, en realidad todo el pro-
ceso requiere otros pasos intermedios que en nuestro caso serán hechos au-
na
tomáticamente.
Nota: Algunos lenguajes son interpretados, es decir no existe la compilación y
tu
no se crea el ejecutable.
on
Aún cuando estemos trabajando con Pascal, los detalles de cómo realizar
el ciclo de edición-compilación-ejecución dependen del sistema operativo y el
yc
“ (input, output) ”.
m
rápido que apenas nos damos cuenta de que ha sucedido algo. En estos casos
es conveniente agregar un renglón con las instrucciones
o
.c
writeln(’<retorno> para fin’); readln
os
al terminar el programa, antes de “ end. ” y agregando un “ ; ” al fin del renglón
anterior.
r
fo
a) Observar con cuidado los signos de puntuación y qué hace cada una de las
instrucciones:
.m
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 3
m
Recordemos que la información, incluyendo el programa, se guarda en un
lugar de memoria, como ristras de ceros y unos. Como números y caracteres se
o
representan de esta forma, la computadora al tomar un dato debe saber si se
.c
trata de uno u otro. Esto da lugar a distintos tipos de datos, como estudiamos
os
en este capı́tulo.
r
fo
3.1. Tipos, variables e identificadores
.m
ejemplo, un byte de 8 bits. De esta forma tendremos (ver problema 2.1) 28 = 256
posibilidades para los caracteres, lo cual es suficiente para guardar las letras de
yc
Habiendo decidido esto, nos toca ahora guardar números. Como antes, es
conveniente guardar a todos los números en la misma cantidad de bits. Si usára-
mos 8 bits como hicimos para las letras, tendrı́amos sólo 256 números disponi-
.a
bles, lo cual es bien pobre. Es conveniente que las “cajas” sean más grandes.
w
Cuando la máquina lea estas cajas que guardan letras o números, tiene que
w
saber cuántos bits juntos tiene que leer, e interpretar la ristra de bits según sea
w
una letra o un número. Surgen ası́ cuatro tipos elementales de datos, cada uno
con distinta codificación interna:
• char para guardar caracteres, i.e. letras, signos de puntuación y otros que
veremos más adelante,
Ahora tenemos el problema de cómo acceder a esas cajas que guardan carac-
teres o números. Esto es sencillo: les ponemos nombres para identificarlas. Las
cajas ası́ nombradas se llaman variables pues podremos colocar en ellas datos
distintos o (¡ejem!) variables y los nombres que reciben se llaman identificadores.
Cuando redactamos el programa, debemos indicar al compilador los nom-
bres y tipos de variables que usaremos, procedimiento llamado declaración de
variables. En el programa se pone una lista de variables y tipos después de la
palabra clave “ var ”.
Una cosa trae a la otra, y tenemos el problema de que no podemos poner
cualquier nombre. Por ejemplo, no es conveniente usar “ writeln ” o “ write ”,
que son usados por Pascal para instrucciones del lenguaje. Además, en Pascal
hay nombres reservados, que no podemos usar como identificadores1 . Aparte
de estas palabras prohibidas, los identificadores en Pascal pueden ser cualquier
sucesión de letras mayúsculas o minúsculas y dı́gitos, pero
m
• ni pueden tener caracteres como $, , +, etc.,
o
.c
• y por supuesto, dos variables distintas no pueden tener el mismo nombre.
os
A diferencia de otros lenguajes (como “C” o Mathematica), según el estándar
r
fo
Pascal no se hace diferencia entre mayúsculas o minúsculas tanto en los identi-
ficadores como en palabras claves. Ası́, podemos poner sin problemas en alguna
.m
Pascal:
a) mn32xy, b) 32xymn, c) mn32 xy, d ) M32nxY, e) mn 32. ✄
yc
También tenemos otro problema: cómo colocar los datos en estas cajas o
dm
Desde ya que
aunque hay compiladores que automáticamente ponen algún valor al hacer las
declaraciones.
Para tratar de entender esta jeringoza, pasemos a estudiar algunos ejemplos
comenzando con los tipos numéricos “ integer ” y “ real ”, para luego conside-
rar los tipos “ boolean ” y “ char ”.
o m
La vida serı́a un tanto aburrida si sólo pudiéramos cambiar valores de lugar,
.c
es más interesante si podemos realizar operaciones entre estas variables. Ası́,
os
suponiendo que las variables a, b y c son del tipo adecuado, una instrucción
como “ a := b + c ” hace que la CPU tome los datos que están en las cajas o
r
fo
variables b y c, las sume, y coloque el resultado en la caja o variable a.
.m
var a, b: integer;?
w
m
h) Cambiar las sentencias de modo de obtener la suma de números reales, i.e.
o
poner “ real ” en vez de “ integer ” en la declaración de variables. Compilar
.c
y ejecutar el programa, observando los cambios en la salida.
os
i) Agregar una variable c, de tipo entero o real según corresponda, hacer la
asignación “ c := a + b ” e imprimir c en vez de a + b.
r
fo
j ) Modificarlo de modo de escribir también la resta (“ - ”), producto (“ * ”) y
.m
división (“ div ” para enteros y “ / ” para reales), tanto en el caso de enteros
como de reales.
na
simpático”, en general queremos decir “la persona cuyo nombre es José tiene la
cualidad de ser simpática”, y no que el nombre en sı́ es simpático, en cuyo caso
.a
3.2.1. “Readln”
w
Problema 3.3. El programa leerdato (pág. 159) lee un entero ingresado por
terminal y lo imprime. Observar la sintaxis e instrucciones, similares al programa
sumardos, la única novedad es el uso de “ readln ”.
a) Compilar y ejecutar el programa, comprobando su funcionamiento.
b) Ingresar, en ejecuciones sucesivas del programa, los siguientes datos (sin las
comillas) seguidos de <retorno>, conservando los espacios intermedios
cuando corresponda:
i) “ 23 ”; ii) “ 2 ”; iii) “ 2 3 ”;
iv ) “ 2a ”; v ) “ a2 ”; vi) “ <retorno><retorno>2”.
m
to, con “ readln ” (y “ read ”) podemos leer varios argumentos a la vez, pero
o
no usaremos esta facilidad para evitar confusiones.
.c
Los comportamientos de “ write ” y “ writeln ” para leer, y de “ read ”
y “ readln ” son prácticamente simétricos, aunque existen algunas diferencias
< >
os
importantes. Por ejemplo, en el tratamiento de los retorno ’s o finales de
lı́nea, como hemos visto en el inciso b.vi): “ readln(a) ” los ignora mientras no
r ✄
fo
aparezca el dato a, pero “ writeln(a) ” escribe un único renglón por vez.
.m
Cuando se programa profesionalmente, es muy importante que el programa
funcione aún cuando los datos ingresados sean erróneos, por ejemplo si se ingresa
na
que a hacer un programa que funcione cuando las entradas son correctas.
on
yc
camente agradable.
w
m
Nota: Una lista de las funciones predefinidas en Pascal está en el apéndice B
(pág. 201).
o
.c
3.2.3. La codificación de enteros y reales
os
En Pascal, los números ocupan un número fijo de bits, por ejemplo 16 bits
r
para el tipo “ integer ” y 32 bits para el tipo “ real ”2 , por lo que en la máquina
fo
.m
ya sean enteros o reales. Ası́, hay un máximo entero representable, que en Pascal
se llama maxint, y hay un menor número positivo representable que llamaremos
tu
Problema 3.5.
yc
m
instrucción “ readln(x) ”, el ingreso de 1 como dato —un entero— hace que
se guarde codificado como real en x .
o
.c
Problema 3.7. Compilar y ejecutar el programa enteroareal (pág. 160). Obser-
os
var la declaración de variables de distinto tipo en el mismo programa. ✄
r
Problema 3.8. Decir por qué las siguientes declaraciones de variables son in-
fo
correctas:
.m
Ası́ como escribimos un entero como real, es posible que querramos pasar de
un real a un entero:
yc
m
k ) n := maxint div 2; n := n * (n+1) div 2;
o
l ) n := maxint div 2; x := n * (n+1) / 2; ✄
.c
También es posible hacer operaciones mezclando variables numéricas de dis-
os
tintos tipos:
r
fo
Problema 3.12. Hacer un programa donde se declara a de tipo entero y b de
tipo real, se lean a y b, y se escriba en pantalla los resultados de: a + b, a ∗ b,
.m
a/b, b/a. ¿De qué tipo son los resultados en cada caso? Sugerencia: modificar
✄
na
Problema 3.14.
a) Hacer un programa para averiguar el comportamiento de “ mod ”, tomando
como entradas a = ±7 y b = ±3.
b) Comprobar si el valor de “ (a div b) * b + (a mod b) ” coincide con el
de a. ✄
m
Habiendo estirado un poco los músculos, tiene sentido preguntarle a la com-
o
putadora si un número es positivo o no, y que guarde el resultado en una variable
.c
lógica:
os
Problema 3.16. Compilar y ejecutar el programa positivo (pág. 161), anali-
zando qué hacen las distintas instrucciones.
r
fo
Agregar una variable pos, declarada como lógica (“ boolean ”), incluir en el
.m
cuerpo del programa la asignación “ pos := a > 0 ” e imprimir pos, en vez de
“ a > 0 ”. ✄
na
Debemos destacar que al comparar números, Pascal usa a veces una sintaxis
tu
Matemáticas Pascal
yc
= =
6= <>
dm
> >
≥ >=
.a
< <
≤
w
<=
w
Matemáticas Pascal
(a > 0) ∧ (b > 0) (a > 0) and (b > 0)
(a > 0) ∨ (b > 0) (a > 0) or (b > 0)
¬(a > 0) not (a > 0)
pero la expresión matemática a < b < c debe escribirse en Pascal como “ (a <
b) and (b < c) ”. También observamos que para preguntar si a 6= b en Pascal
puede ponerse tanto “ a <> b ” o como “ not (a = b) ”, y de modo similar para
las desigualdades.
m
i) a < b, ii) 0 < a y 0 < b, iii) 0 < a o 0 < b.
o
b) Una de las leyes de De Morgan establece que si p y q son proposiciones
.c
(lógicas), entonces
¬(p ∧ q) = (¬p) ∨ (¬q).
os
Modificar el programa de modo de verificar esta ley cuando p = (a > 0)
r
fo
y q = (b > 0), i.e. evaluar las expresiones a cada lado en la ley de De Morgan
✄
.m
y compararlas.
na
3.4. Caracteres
tu
acentuadas del castellano. A fin de codificar también estos caracteres, hay ex-
w
tensiones para cubrir caracteres con números hasta 255, pero estas extensiones
w
• las letras minúsculas, ‘ a ’,‘ b ’,. . . ,‘ z ’, si bien están ordenadas, ¡no son
necesariamente consecutivas!
m
b) ¿Qué pasa si se escribe como entrada “ tonto ” ?
o
c) ¿Cuál es el ordinal correspondiente a los caracteres ‘ a ’, ‘ A ’, ‘ ’ (espacio),
.c
y tabulación (tecla “tab”)?
os
d ) Hacer un programa que, inversamente, dado un número retorne el carácter
correspondiente.
r
fo
e) Averiguar si hay caracteres correspondientes a números mayores que 127 (el
.m
resultado dependerá de la máquina). ¿Y a números mayores que 255?
f ) ¿Cuál es el carácter correspondiente al número 7 (en ASCII)? ✄
na
tu
on
yc
dm
.a
w
w
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 4
Tomando control
m
Con las operaciones que hemos visto, no podemos hacer mucho más que lo
que hace una calculadora sencilla. Las cosas empiezan a ponerse interesantes
o
cuando disponemos de estructuras de control de flujo, esto es, instrucciones que
.c
nos permiten tomar decisiones sobre si realizar o no determinadas instrucciones
os
o realizarlas repetidas veces.
Al contar con estructuras de control, podremos verdaderamente comenzar a
r
fo
describir algoritmos, es decir, instrucciones (no necesariamente en un lenguaje
de programación) que nos permiten llegar a determinado resultado, y encarar
.m
a un lenguaje de programación.
Los programas irán aumentando en longitud, lo que nos obligará a encarer
tu
detalles. Pascal, y casi todos los lenguajes de programación, son poco “natu-
rales” en este sentido, exigiendo —por ejemplo— la declaración de variables al
yc
de “abajo hacia arriba” (o casi), y recién al final miraremos cómo declarar las
variables necesarias.
.a
Nota: En Pascal hay otras estructuras de control, como “ cases ”, que no ve-
w
4.1. “If”
Supongamos que al cocinar decidimos bajar el fuego si el agua hierve, es decir
realizar cierta acción si se cumplen ciertos requisitos. Podrı́amos esquematizar
esta decisión con la sentencia:
o m
El programa valorabsoluto (pág. 162) realiza este cálculo. Observar qué rea-
.c
lizan las distintas instrucciones. ¿Qué pasa si se eliminan los paréntesis después
os
del “ if ” ?, ¿y si se cambia “ >= ” por “ > ” ?, y si se agrega “ ; ” antes de “ else ”?
La función “ abs ” de Pascal hace el cálculo del valor absoluto. Incorporarla
r ✄
fo
al programa y verificar que se obtienen los mismos resultados.
.m
A veces es conveniente encadenar varios “ if ”:
na
Problema 4.2. El programa comparar (pág. 162) toma como datos los números
enteros a y b y determina cuál es mayor o si son iguales.
tu
“ ; ” en un solo renglón?
b) ¿Qué pasa si se incluyen “ ; ” al final de cada uno de los renglones?
yc
m
del año. Sin embargo, cálculos posteriores mostraron que la longitud del año
o
es aproximadamente 365.2422 dı́as. Con el paso de los siglos, las diferencias
.c
anuales de 0.0078 dı́as en promedio se fueron acumulando, y hacia el año 1582
el Papa Gregorio comenzó un nuevo calendario (el gregoriano). Por un lado,
os
se agregaron 10 dı́as a la fecha, de modo que el 5 de octubre de 1582 pasó a ser
el 15 de octubre (y nunca existieron los dı́as entre el 6 y el 14 de octubre de
r
fo
ese año). Por otro lado, se decidió que los años bisiestos serı́an precisamente
los divisibles por 4, excepto que aquéllos que fueran divisibles por 100 serı́an
.m
bisiestos sólo cuando divisibles por 400 (ası́ el año 1700 no es bisiesto pero
sı́ el 2000). Con esta convención, el año promedio tiene 365.2425 dı́as. No to-
na
dos los paı́ses del mundo adoptaron este calendario inmediatamente. En Gran
Bretaña y lo que es ahora Estados Unidos de Norteamérica, se adoptó recién
tu
en 1752, por lo que se debieron agregar 11 dı́as más; Japón cambió en 1873,
on
Rusia en 1917 y Grecia en 1923. Aún hoy algunas iglesias ortodoxas mantienen
el calendario juliano, especialmente para determinar la fecha de las Pascuas.
yc
Nota: A veces con un pequeño esfuerzo podemos hacer el cálculo más eficiente.
Un esquema para resolver el problema anterior, si anio es el año ingresado, es
dm
write(anio);
if (anio mod 400 = 0) then writeln(’ es bisiesto’)
.a
o m
Veamos un ejemplo donde podrı́amos usar “ begin...end ”:
.c
Problema 4.7. Sea f : R → R la función definida como
os
(
x2 si x > 0,
r
fo
f (x) =
0 si x ≤ 0.
.m
f (x) = 0). Una posibilidad es, habiendo declarado a x y y como del tipo
yc
programa fuente.
m
i) a = 1, b = 2, n = 1, z = 0;
o
ii) a = 1, b = 2, n = −1, z = 0;
.c
iii) a = 2, b = 1, n = 1, z = 0;
os
iv ) a = 2, b = 1, n = −1, z = 0;
c) Decir si el renglón es equivalente a:
r
fo
i) if (n > 0) then begin
.m
4.2. “While”
yc
ben el nombre común de lazos o bucles para indicar esta capacidad de repetición.
Supongamos que voy al supermercado con cierto dinero para comprar la
.a
nero que nos va quedando a medida que vamos poniendo botellas en el carrito:
w
cuando no alcance para más botellas, vamos a la caja. Una forma de poner
esquemáticamente esta acción como
w
m
3 r := b 10
o
4 r ≥ b: verdadero
.c
5 r := r - b 7
os
6 r ≥ b: verdadero
7 r := r - b 4
r ≥ b: verdadero r
8
fo
9 r := r - b 1
.m
10 r ≥ b: falso
11 imprimir r
na
donde indicamos los pasos sucesivos que se van realizando (excepto la impre-
tu
sión de carteles sin variables), y los valores de las variables a, b y r (el último
on
hemos visto hasta ahora, ni demasiado complicados como varios de los que
w
diferente.
m
el incremento, son entrados por el usuario.
o
a) Observar el uso de “ const ” para dar un valor aproximado de π, anterior
.c
a la declaración de variables. Observar también que no se hace una asig-
os
nación a pi , sino que se usa el signo “ = ”. El uso de const hace que no se
puedan hacer asignaciones a pi pues no es una variable: tratar de hacer una
r
fo
asignación a pi y observar qué pasa.
.m
b) Antes o después de ejecutar el programa, verificar que se obtienen los mismos
resultados de una “prueba de escritorio” (como en el problema 4.9, ahora
na
igualdad π = 4 arctan 1.
dm
X
n
sn = 1 + 2 + 3 + · · · + n = i,
i=1
n × (n + 1)
sn = .
2
Según se dice, Karl Friederich Gauss (1777–1855) tenı́a 8 años cuando el
m
maestro de la escuela le dio como tarea sumar los números de 1 a 100 para
o
mantenerlo ocupado (y que no molestara). Sin embargo, hizo la suma muy
rápidamente al observar que la suma era 50 × 101.
.c
El programa gauss (pág. 164) calcula sn sumando los términos (y no usando
os
la fórmula de Gauss).
r
fo
a) ¿Cuál es el valor de n al final del lazo? Sugerencia: hacer una “prueba de
escritorio”.
.m
100).
f ) Modificar el lazo “ while ” para que se sume “al derecho”, i.e. primero los
✄
.a
factorial,
n! = 1 × 2 × · · · × n.
w
a) ¿Hay alguna diferencia entre los resultados multiplicando “al derecho” (de
menor a mayor) y “al revés” (de mayor a menor)?
Nota: es posible que sı́ cuando n es grande, debido a errores numéricos.
b) Determinar cuántos ceros hay al final de la expresión decimal de 30!, y com-
parar con la cantidad de ceros que aparecen en el resultado del programa.
Sugerencia: contar las veces que 2 y 5 son factores de 30!.
Nota: 30! tiene 33 cifras decimales. ✄
4.3. “Repeat”
Si queremos ir a la facultad caminando, podrı́amos poner
m
a) ¿Para qué está el renglón “ if (a < 0) then b := -a else b := a; ”?
o
.c
b) Cambiar el lazo “ repeat ” por otro “ while ” de modo que ahora se consi-
dere que el número 0 tiene 0 cifras. ✄
os
Los errores numéricos al trabajar con el tipo “ real ” están relacionados
r
fo
con la representación interna de este tipo, como ya observamos al mencionar la
densidad variable en el capı́tulo 3 (pág. 14).
.m
Problema 4.14. El programa epsmin (pág. 165) calcula εmin y εmaq . Debemos
destacar que en principio los resultados exhibidos son sólo aproximaciones según
yc
a) Observar que εmin εmaq . Esto permite que en el cálculo de εmaq podamos
.a
multiplicar por 2, pero no para calcular εmin: ver qué sucede si para calcular
εmin tomamos
w
eps := 1;
w
eps := 2 * eps;
writeln(’epsmin es: ’, eps);
4.4. “For”
Hay veces que queremos repetir una acción un número fijo de veces. Por
ejemplo, al subir una escalera con 10 escalones harı́amos algo como
m
En Pascal no existe una estructura que traduzca esta acción directamente4 .
o
En cambio, cuenta con una estructura un poco más flexible que nos permite
.c
imitarla: si contamos los escalones con el “contador” e, que variará entre 1 y
os
10, podrı́amos poner el esquema
r
fo
para e desde 1 a 10 hacer subir el e-ésimo escalón.
.m
e := 1;
yc
e := e + 1
end
.a
En este caso resulta más cómodo el lazo “ for ”, no sólo porque es más
w
b) No es posible tomar bases y exponentes muy grandes sin que haya overflow
o underflow, i.e. resultados que no se pueden representar en la máquina por
ser muy grandes o muy pequeños. Probar con distintos valores hasta obtener
este comportamiento, e.g. 1010 , 100100, (1/10)10 , (1/100)100, etc.
c) Modificar el programa de modo de imprimir el valor de i al finalizar el lazo.
Nota: Como mencionáramos, según el estándar el valor no está definido,
pero muchos compiladores no respetan esta convención y el valor obtenido
puede depender del compilador: no debe usarse esta variable después del
lazo “ for ” sin antes re-asignarla.
d ) Agregar sentencias para considerar también el caso de n negativo. Sugeren-
cia: ab = 1/a−b , usar la función “ abs ” y condicional “ if ”.
e) Algunos compiladores admiten la operación “ x ** y ”, que no es del están-
dar Pascal, para el cálculo de la potencia xy (x ∈ R+ , y ∈ R). Verificar si
el compilador usado admite esta sentencia y, en caso afirmativo, comparar
con el resultado anterior cuando y ∈ N.
m
f ) Otra forma de calcular la potencia en general es usar xy = ey×ln x . Incorpo-
o
rar esta fórmula al programa para comparar con los resultados anteriores.
.c
Nota: Es posible que difieran debido a errores numéricos. ✄
r os
Muchos de los problemas que hemos visto usando “ while ” o “ repeat ”
fo
pueden hacerse con “ for ” con modificaciones sencillas, pero no todos:
.m
Problema 4.16.
na
a) Modificar los programas tablaseno (pág. 163) y gauss (pág. 164) cambiando
tu
(problema 4.14)? ✄
dm
Un problema muy común es entrar datos por terminal para realizar alguna
acción, como sumarlos:
.a
w
Problema 4.17.
w
w
4.5. “Eoln”
Ciertamente es molesto el “dato imposible −1” del problema 4.17, ya que
tendremos que modificar el programa si en algún momento queremos usarlo para
sumar datos que pueden ser negativos. Más cómodo serı́a ingresar como “dato
imposible” alguna señal que no imponga limitaciones sobre los números a entrar
(salvo que sean de tipo entero o real).
Una posibilidad es poner como “dato imposible” un “dato vacı́o”, es decir,
nada, o más correctamente un <retorno> sin entrada de datos, usando la
función de Pascal “ eoln ”.
Pero antes de describir esta función, será conveniente repasar un poco lo di-
cho —y hecho— al introducir la función “ readln ” en la sección 3.2.1 (pág. 12).
Cuando ingresamos un dato, éste no es leı́do por el programa hasta que
ingresemos <retorno>, dándonos la oportunidad de cambiarlo5. Si hemos de-
clarado la variable a (de algún tipo), la sentencia “ readln(a) ” hace que el
m
programa lea el primer dato que no sea justamente <retorno> y lo guarde
o
en a si el dato es correcto (si es incorrecto, puede pasar cualquier cosa: ver el
.c
problema 3.3.b)).
os
Si en el programa fuente tenemos la instrucción “ readln ”, sin argumentos,
entonces se lee el renglón, y si hemos entrado otros datos antes de <retorno>,
éstos son ignorados (ver el inciso c) del problema citado).
r
fo
Al ingresar nosotros <retorno>, no sólo estamos indicando que terminamos
.m
de ingresar nuestros datos, sino que se emite una señal especial que llamaremos
fin de lı́nea 6 . Cuando ponemos “ readln(a) ” lo que hacemos efectivamente es
na
sin argumentos, devuelve un valor lógico, i.e. verdadero o falso. Como “ readln ”,
on
Problema 4.18. El programa eolnprueba (pág. 166) es una prueba del funcio-
namiento de “ eoln ”, en el que se usa la variable a para entender el flujo de las
w
instrucciones.
a) Compilar y ejecutar el programa, observando que luego de imprimir el valor
de a antes del lazo, queda a la espera del ingreso de datos:
i) Ingresar “ <retorno> ”, sin otros caracteres, y observar el valor final
de a. ¿Cuáles de las instrucciones en “ if ” se ejecutaron?, ¿qué valor
ha dado “ eoln ” y por qué?
ii) Si en vez de ingresar sólo “ <retorno> ”, se ingresa “ <retorno> ”
(con un espacio antes), ¿cuál será el valor de a que se imprime al
final? Verificar la respuesta ejecutando el programa con esa entrada.
5 Al menos para compiladores “razonables”.
6 La señal de “fin de lı́nea” depende del sistema operativo.
m
a) Observar el uso de “ eoln ” para detectar que se ha entrado un <retorno>
o
sin dato, y el uso de “ readln ” para “leer” el fin de lı́nea que ha quedado.
.c
¿Qué pasa si se elimina este “ readln ”?
os
b) Modificar el programa para que a la salida indique también la cantidad de
datos ingresados.
r
fo
c) Modificar el programa para que también indique el promedio de los datos
.m
entrados. ✄
na
tu
on
yc
dm
.a
w
w
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 5
Aplicaciones
m
En este capı́tulo mostramos que se puede hacer bastante matemáticas con
los recursos de programación que hemos visto.
o
.c
os
5.1. Cálculo numérico elemental
r
fo
Ciertamente una de las aplicaciones más importantes de la computadora —y
a la cual debe su nombre— es la obtención de resultados numéricos. A veces es
.m
parecidos, el resultado puede ser 0 aunque los números no sean iguales. Sor-
prendentemente, al trabajar con números de tipo “ real ” pueden pasar cosas
curiosas como:
.a
w
Problema 5.1.
a) Encontrar x de tipo real tal que, para la máquina, ¡x = x + 1!.
Sugerencia: buscar una potencia de 2, usando algo como
x := 1; repeat x := 2 * x; y := x + 1 until (x = y)
Pág. 36 Aplicaciones
m
Ver que efectivamente la diferencia Hn − ln n es aproximadamente cons-
o
tante para valores grandes de n (esta constante es la constante de Euler
.c
γ = 0.5772156649 . . .), por ejemplo calculando las diferencias para n =
os
100, 1000, 10000. ✄
r
El siguiente problema muestra algunos de los inconvenientes al restar canti-
fo
dades grandes y similares.
.m
ax2 + bx + c = 0 (5.1)
tu
−b + d −b − d
x1 = y x2 = . (5.2)
dm
2a 2a
a) Hacer un programa que, dados a, b y c, verifique si a 6= 0 y d ≥ 0, poniendo
.a
Por ejemplo, calcular las raı́ces usando el programa del inciso anterior,
cuando a = 1, b = 10000 y c = 1, verificando si se satisface la ecuación (5.1)
en cada caso.
Nota: Las soluciones con varios dı́gitos son:
x1 = −0.00010000000100000 . . . y x2 = −9999.99989999999899999 . . .
c) Uno de los problemas en el ejemplo del inciso anterior surge de restar núme-
ros grandes que son comparables. El siguiente fragmento de programa alivia
un poco la situación:
if (b > 0) then x1 := -(b + sqrt(d))/(2 * a)
else x1 := (sqrt(d) - b)/(2 * a);
x2 := c / (x * a);
m
(positivo), y apretando varias veces la tecla de “raı́z cuadrada” puede observarse
que rápidamente se converge al mismo número, independientemente del valor
o
.c
ingresado inicialmente.
Como a lo mejor no tenemos disponible la calculadora, pero sı́ la computa-
os
dora y Pascal, hagamos el trabajo:
Problema 5.4 (Punto fijo de la raı́z cuadrada).
r
fo
a) Utilizando la construcción
.m
y := x; for i := 1 to n do y := sqrt(y)
na
q
√ n
on
··· x (= x1/2 ).
| {z }
yc
n raı́ces
Pág. 38 Aplicaciones
Nota: Por supuesto, hay funciones continuas que “no se pueden dibujar” como
1/x para x > 0, pues cerca de x = 0 se nos acaba el papel,
sen 1/x para x > 0, pues cerca de x = 0 oscila demasiado,
(
x sen 1/x si x 6= 0,
f (x) = pues cerca de x = 0 oscila demasiado.
0 si x = 0,
Otra forma de describir que una función es continua es decir que a cambios
“pequeños” en x corresponden cambios “pequeños” en f (x), aunque la “pe-
√
queñez” relativa sea muy distinta (por ejemplo, f (x) = 3 x es continua, pero
m
si cambiamos de 0 a 0.001 en x, hay sólo un cambio de 0.1 de f (0) a f (0.001)).
o
Tener en cuenta también que puede ser que a pequeños cambios de f (x)
.c
pueden no corresponder pequeños cambios en x. Por ejemplo una función cons-
tante es continua.
os
Muchas funciones de interés interactúan de manera interesante con sus pun-
r
tos fijos como en el caso de la raı́z cuadrada: supongamos que x0 es un punto
fo
dado y definimos
.m
f (`) = `,
y
y=x
1
0.5 y = cos x
x
0.5 1 1.5
m
punto fijo, donde los trazos horizontales van desde puntos en el gráfico de f a
la diagonal y = x y los verticales vuelven al gráfico de f :
o
.c
y
os
1
r
fo
0.5
.m
na
x
0.5 1 1.5
tu
on
Problema 5.6 (Punto fijo de f (x) = cos x). Con las notaciones anteriores
para f y xi :
.a
|xk+1 − xk | < ε,
Pág. 40 Aplicaciones
m
c) Verificar que con 3 o 4 iteraciones se obtiene una muy buena aproximación
o
de π comenzando desde x0 = 3.
.c
d ) Sin embargo, si empezamos desde x0 = 1, nos aproximamos a 0, otro punto
os
fijo de f .
e) f (π/2) no está definida, y es previsible encontrar problemas cerca de este
r
fo
punto. Como π/2 ≈ 1.5708, hacer una tabla de los valores obtenidos después
de 10 iteraciones, comenzando desde los puntos 1.5, 1.51, . . . , 1.6 (desde 1.5
.m
función.
f ) Si en vez de usar f (x) = x − tan x usáramos f (x) = x + tan x, los resultados
tu
del inciso a) no varı́an. Hacer los incisos b) y c) con esta variante y verificar
✄
on
El problema 5.7 nos muestra que aun cuando un método iterativo nos lleve a
encontrar un punto fijo, no siempre es el que buscamos, salvo que empecemos con
.a
El método era conocido por los babilonios (entre 2000 y 200 años a. de C.),
quienes usaron ideas geométricas para aproximar la media geométrica de los
números x y a/x, r
a
x× ,
x
√
que es el número a buscado, por su media aritmética (o promedio),
1 a
x+ .
2 x
Mucho más tarde, Isaac Newton (1642–1727) desarrolló un método basado
en el análisis matemático para encontrar raı́ces de funciones mucho más gene-
rales, que tiene como caso particular al método babilónico. El método de Newton
también es conocido como de Newton-Raphson, pues J. Raphson (1648–1715)
publicó este resultado unos 50 años antes que se publicara el de Newton.
a) Probar que si a > 0 y x > 0, entonces
√ fa (x) > 0, y si fa (x) = x (x es un
punto fijo de fa ) entonces x = a.
m
b) En particular, si x0 > 0 y definimos√xn como en la ecuación (5.3), entonces
o
xn > 0 para √todo n ∈ N, y si xn = a para algún n, entonces xn = xn−1 =
.c
· · · = x0 = a. Por lo tanto, si suponemos precisión
√ “infinita” el método
os
nunca da la respuesta exacta salvo que x0 = a.
c) Bosquejar un gráfico similar al segundo del problema 5.6, cuando a = 2 y
r
fo
x0 = 1.
.m
d ) ¿Qué pasa si a = 0? ¿Y si x0 = 0?
e) ¿Qué pasa si x0 < 0 y a > 0?
na
también un criterio para parar si la diferencia “en y”, |x2 − a|, es suficien-
w
Pág. 42 Aplicaciones
m
No sólo podemos obtener resultados numéricos con la computadora, sino que
podemos inferir resultados teóricos experimentando:
o
.c
Problema 5.10.
os
a) Hacer un programa para calcular la suma de los primeros n números impares
positivos (n ∈ N),
r
fo
X
n
.m
a la de Gauss, sn = n×(n+1)
2 (ver el problema 4.11).
on
Ambos querı́an quedar bien y Guille gastó $5 por botella, mientras que Geri,
w
que es más sibarita, gastó $8 por botella. Si entre los dos gastaron $81, ¿cuántas
w
m
enteros no-negativos x, y tales que x2 + y 2 = n, exhibiendo en caso positivo
un par de valores de x, y posibles, y en caso contrario imprimiendo un cartel
o
adecuado. ✄
.c
os
5.2.2. Máximo común divisor y el algoritmo de Euclides
r
Dados a, b ∈ N, el máximo común divisor entre a y b, mcd(a, b), se define2
fo
como el máximo elemento del conjunto de divisores comunes de a y b:
.m
mcd(a, b) = máx D,
na
donde
tu
D = {d ∈ Z : d | a y d | b}.
on
Cuando a, b ∈ Z, definimos
dm
mcd(0, 0) = 0.
w
Cuando mcd(a, b) = 1, es usual decir que los enteros a y b son primos entre
sı́ o coprimos.
En la escuela elemental a veces se enseña a calcular el máximo común divisor
efectuando primeramente la descomposición en primos. Sin embargo, la facto-
rización en primos es computacionalmente difı́cil3 y en general bastante menos
eficiente que el algoritmo de Euclides, que aún después de 2000 años, es el más
indicado (con pocas variantes) para calcular el máximo común divisor.
Euclides (alrededor de 300 a. de C.) escribió una serie de libros de enorme
influencia en las matemáticas, aún las actuales. En la proposición 1 del libro VII,
se enuncia:
2 ¡Como su nombre lo indica!
3 Ver también las notas en la pág. 46.
Pág. 44 Aplicaciones
Y en la proposición 2,
m
como longitudes de segmentos, y la multiplicación de números es un área. Un
poco
√ antes de Euclides, con Pitágoras y el descubrimiento de la irracionalidad
o
de 2, surgió el problema de la conmensurabilidad de segmentos, es decir si
.c
dados dos segmentos de longitudes a y b existe otro de longitud c tal que √a y
os
b son múltiplos enteros de c, i.e. c = mcd(a, b). Si a es irracional, como 2, y
b = 1, entonces no existe c, y el algoritmo de Euclides no termina.
r
fo
Problema 5.13 (Algoritmo de Euclides). El programa euclides (pág. 168)
.m
es una versión de este algoritmo para encontrar mcd(a, b) cuando a, b ∈ Z, cam-
biando las restas sucesivas por el cálculo del resto mediante la función “ mod ”.
na
Nota: En el problema 4.9 hemos hecho el proceso inverso: para calcular el resto
hicimos restas sucesivas.
tu
(* lazo principal *)
repeat
dm
¿Y por
w
(* lazo principal *)
w
repeat
w
if (a > b) then a := a - b
else if (a < b) then b := b - a
until (a = b); ?
Explicar en cada caso.
b) Ver que el programa termina en un número finito de pasos, por ejemplo en
no más de |a| pasos.
Nota: Observar que en el método babilónico (sección 5.1.3), hemos debido
poner “criterios de parada”, pero no aquı́.
c) Modificar el programa de modo que a la salida escriba la cantidad de veces
que realizó el lazo “ while ”.
d ) ¿Qué pasa si se cambia la instrucción “ while (b <> 0) do ” por “ while
(b > 0) do ”?
e) ¿Qué pasa si se eliminan los renglones donde se consideran los casos a < 0
y b < 0?
f ) ¿Y si se hacen los cambios d ) y e) simultáneamente?
g) Modificar el programa de modo que a la salida escriba también los valores
originales de a y b, e.g. “ El máximo común divisor entre 12 y 8 es 4 ”
si las entradas son a = 12 y b = 8.
h) Una de las primeras aplicaciones del mcd es “simplificar” números raciona-
les, e.g. escribir 12 3
8 como 2 . Hacer un programa que dados los enteros p y q,
con q 6= 0, encuentre m ∈ Z y n ∈ N de modo que pq = m n y mcd(m, n) = 1.
Nota: ¡Atención con los signos de p y q! ✄
Problema 5.14. Pablito y su papá caminan juntos tomados de la mano. Pa-
blito camina 2 metros en exactamente 5 pasos, mientras que su padre lo hace en
exactamente 3 pasos. Si empiezan a caminar juntos, ¿cuántos metros recorrerán
hasta marcar nuevamente el paso juntos?, ¿y si el padre caminara 2 21 metros
m
en 3 pasos? Aclaración: Se pregunta si habiendo en algún momento apoyado si-
o
multáneamente los pies izquierdos, cuántos metros después volverán a apoyarlos
.c
simultáneamente.
os
Nota: Recordando el tema de la conmensurabilidad mencionado al introducir
el algoritmo de Euclides, no siempre el problema
√ tiene solución. Por ejemplo,
r ✄
fo
si Pablito hace 1 metro cada 2 pasos y el papá 2 metros cada 2 pasos.
Problema 5.15. El mı́nimo común múltiplo de a, b ∈ N, mcm(a, b), se define
.m
{k ∈ N : a | k y b | k}.
a) Demostrar que si a, b ∈ N entonces mcm(a, b) × mcd(a, b) = a × b.
tu
a) ¿Qué relación hay entre el máximo común divisor o el mı́nimo común múlti-
.a
tando el mayor del menor hasta llegar a una “medida común”, y quizás el
w
Pág. 46 Aplicaciones
a = p1 × p2 × · · · × pk
debe tener algún factor primo que lo divida, digamos pi (para algún i entre 1
y k) y tendremos
pi | a y pi | ap1 × p2 × · · · × pk ,
m
Problema 5.17.
o
a) Probar que si el entero a > 1 no es√primo, entonces existen enteros b y c,
.c
√ que 1, tales que b ≤ a y a = b × c. Sugerencia: si a = b × c
ambos mayores
y 1 < b, c < a, entonces b × c < . . .
os
b) Usando el resultado anterior, hacer un programa que determine si el en-
r
fo
tero a > 1√entrado por terminal es primo o no, viendo si los números
2, 3, 4, . . . , b ac lo dividen o no. ✄
.m
programa en el problema 5.17, e imprime todos los factores primos del número
a > 1 entrado por terminal.
tu
a) Ver que efectivamente el programa determina todos los factores primos cuan-
on
do a > 1.
yc
Factor Multiplicidad
2 3
3 1
19 1
✄
m
an = 1 + , para n ∈ N.
o
n
.c
a) Hacer un programa para generar an dado n, y usarlo para varios valores de n.
os
En base a la experiencia, ¿podrı́as decir que an es creciente (i.e. si an < an+1
para todo n ∈ N) o decreciente?, ¿podrı́as demostrar esta propiedad?
r
fo
b) En los cursos de Análisis Matemático se demuestra que a medida que n
.m
aumenta, an se parece cada vez más a e = 2.718281828459 . . .. Modificar el
programa del inciso anterior para ver este comportamiento, donde el usuario
na
(5 tantos cada try), tries convertidos (7 tantos cada uno) y penales convertidos
dm
1 0 0 7
2 0 3 0
3 1 1 3
4 3 0 2 ✄
Pág. 48 Aplicaciones
m
minar en el par (0, mcd(a)b) (o su simétrico).
√
o
b) Si y < x ≤ y(1 + 5)/2, hay un único movimento √ posible desde (x, y).
.c
Además, si se llega a (z, y), debe ser y > z(1 + 5)/2.
os
c) Ver que si un juego empieza en (a, b) y a ≥ b,√el primer jugador tiene una
estrategia ganadora si a = b o si a > b(1 + 5)/2. En caso contrario, el
r
segundo jugador tiene una estrategia ganadora.
fo
d ) Desarrollar un programa para jugar el juego de Euclides en el que:
.m
Capı́tulo 6
Arreglos
m
A veces queremos tener muchas variables del mismo tipo, por ejemplo una
lista de los 100 primeros números primos. Escribir un nombre para cada una de
o
las variables ya serı́a engorroso, pero ¿qué pasa si ahora el problema requiere una
.c
lista de 1000 en vez de 100 datos? Para estos casos es conveniente usar una es-
os
tructura similar a la de vector, que podrı́amos pensar como v = (v1 , v2 , . . . , vn ),
para guardar los datos. En programación, esta estructura se llama arreglo (uni-
r
fo
dimensional).
.m
moria, todos del mismo tipo, a los cuales se accede mediante ı́ndices (como
on
v: array[1..100] of integer;
w
1, podemos hacer que los ı́ndices sean negativos, poniendo el rango adecuado
(por ejemplo “ -30..20 ”), aunque nosotros casi siempre usaremos arreglos con
ı́ndices que empiezan en 1 o 0.
Problema 6.1. En el programa 6.1 (pág. 170) entramos números enteros y al
terminar el ingreso de datos se imprime la cantidad de datos que terminaron en
0, 1, . . . , 9. Observemos que:
• En vez de declarar un contador para cada dı́gito 0, 1, . . . , 9, declaramos el
arreglo terminaen que nos provee de los 10 contadores necesarios, con ı́ndices
entre 0 y 9.
• El uso del arreglo no sólo facilita la declaración, sino que después es mucho
más sencillo incrementar el contador pues no tenemos que hacer una serie de
if’s para determinar el contador adecuado.
Pág. 50 Arreglos
• Al comenzar el programa los valores del arreglo no están definidos, y hay que
inicializarlos.
• El ingreso de datos está tomado del programa sumardatos (pág. 167), excepto
que al entrar un dato se determinar el dı́gito de las unidades del dato, d, y se
incrementa el contador correspondiente.
¿Por qué no se pone directamente “ digito := dato mod 10 ” en vez de
“ digito := abs(dato) mod 10 ” para determinar el dı́gito? ✄
m
1 ” o “ a[20] := 0 ”.
o
Nota: Algunos compiladores pueden agregar instrucciones de modo de compro-
.c
bar que cuando se corra el programa se verifique que los ı́ndices están dentro
os
del rango permitido. Por supuesto, esto hace que la ejecución del programa sea
más lenta.
r
En estos casos tratamos de hacer la declaración para guardar d elementos,
fo
con d prudentemente más grande que el rango con los que pensamos trabajar.
.m
Claro que si hemos declarado el arreglo con d = 100 elementos y usamos sólo
n = 20 lugares, estamos desperdiciando lugar.
na
.
var
.
.
.a
.
a: array[1..MAXN] of integer;
w
o m
ii) El 5 que indica la cantidad de datos por renglón se usa en dos lugares,
.c
de modo que si se cambia el 5 por otro número, hay que acordarse de
cambiar ambos.
os
Definir una constante, maxrenglon al principio del programa para
eliminar este problema.
r
fo
iii) Ahora modificar el programa de modo de imprimir 8 datos por renglón
.m
como máximo, cambiando el programa en un único lugar.
d ) Para buscar x en el arreglo, se usa un lazo “ repeat ”. Cambiarlo por uno
na
(* lazo principal *)
i := ndatos;
while ((a[i] <> x) and (i > 1)) do i := i - 1;
.a
(* resultados *)
w
En caso negativo, decir por qué no, y en caso afirmativo, qué ventajas y
desventajas tendrı́a. Sugerencia: ¿cuál es el último valor de i si x no está en
el arreglo?
g) Modificar el lazo principal de modo que al terminar el programa diga cuántas
veces aparece x en el arreglo, y los ı́ndices correspondientes (i.e. los i para
los cuales ai = x).
Nota: Observar que el lazo principal cambia sustancialmente.
Sugerencia: una posibilidad es ir imprimiendo i a medida que aparece. Otra
posibilidad es agregar un arreglo para guardar los ı́ndices i, por ejemplo:
(* lazo principal *)
veces := 0;
for i := 1 to ndatos do
Pág. 52 Arreglos
6.3. Cribas
Cuando debemos seleccionar en un arreglo todos los elementos que satisfacen
cierto criterio, y el criterio para cada elemento depende de los elementos que le
precedieron, es conveniente usar una criba (o cedazo o tamiz). Quizás la más
conocida de las cribas sea la atribuı́da a Eratóstenes para encontrar los números
primos entre 1 y n (n ∈ N), donde el criterio para decidir si un número k es
m
primo o no es la divisibilidad por los números que le precedieron.
o
Eratóstenes (contemporáneo de Arquı́medes y posterior a Euclides) nació en
.c
Cirene (en Libia de hoy) alrededor del 276 a. de C.. Vivió gran parte de su
os
vida en Alejandrı́a (Egipto), donde murió alrededor de 194 a. de C., después de
haber sido bibliotecario de la famosa biblioteca. Hizo importantes contribuciones
r
en matemáticas, astronomı́a, geografı́a y filosofı́a. Por ejemplo, fue el primero
fo
en medir correctamente la circunferencia de la Tierra (y pensar que Colón
.m
usaba un huevo, ¡1700 años después!).
trar todos los primos menores o iguales que un dado n ∈ N. Una posibilidad es
tu
de Eratóstenes.
La idea es empezar con la lista
yc
2 3 4 . . . n.
dm
2 3 64 5 66 . . .
w
w
Ahora miramos al primer número que no está marcado (no tiene recuadro
w
2 3 64 5 66 7 68 6 9 16 0 11 16 2 . . .
m
(* p = 2i + 1 es primo *)
cuenta := cuenta + 1; p := i + i + 1;
o
(* ahora eliminar multiplos impares de p *)
.c
k := i + p; (* = (3p - 1)/2 = (p - 1)/2 + p,
os
de modo que 2k + 1 = 3p *)
while (k <= MAXN) do begin
r
esprimo[k] := false;
fo
k := k + p (* 2k+1 se incrementa en 2p *)
.m
end
na
end;
y verificar que el nuevo procedimiento es correcto.
tu
e) Una vez √que hemos “recuadrado” o “tachado” todos los números que no
on
i := 1; p := 3; (* p = 2i+1 *)
while (p * p <= MAXP) do begin
.a
k := p + i; (* 2k+1 = 3p *)
w
esprimo[k] := false;
k := k + p (* 2k+1 se incrementa en 2p *)
end
end; (* if esprimo *)
i := i + 1; p := p + 2
end; (* while p * p *)
while (i <= MAXN) do begin
if (esprimo[i]) then cuenta := cuenta + 1;
i := i + 1
end;
1 Recordar el problema 5.18.d).
2 Recordar el ya citado problema 5.17. Ver también el ejemplo dado más arriba, tomando
n = 12.
Pág. 54 Arreglos
m
for i := 1 to n do girar media vuelta la llave de las celdas i, 2i, 3i,...
o
comenzando con todas las celdas cerradas. Un prisionero era liberado si al final
.c
de este proceso su puerta estaba abierta. ¿Qué prisioneros fueron liberados?
os
Sugerencia: ¡no pensar, hacer el programa! ✄
r
Problema 6.5 (El problema de Flavio Josefo I). Durante la rebelión judı́a
fo
contra Roma (unos 70 años d. de C.), 40 judı́os quedaron atrapados en una
.m
suicidarı́a. Conocemos esta historia por Flavio Josefo (historiador famoso), quien
siendo el último de los sobrevivientes del cı́rculo, no se suicidó. El problema es
tu
ver en qué posición debió colocarse Flavio Josefo dentro del cı́rculo para quedar
on
3 1
4 5
5 3
cuando n = 5 y m = 3.
Sugerencia: eliminar el arreglo flavio y usar otro, digamos orden, de mo-
do que “ orden[i] ” indicará en qué vuelta fue eliminado i. Si se inicia-
liza “ orden[i] := n ” para todo i, y consideramos que en cada vuelta
“ orden[i] = n ” indica que vive, tampoco es necesario el arreglo vive ni el
lazo para encontrar el sobreviviente: será el único con “ orden[i] = n ” al
terminar.
e) ¿Cómo podrı́a modificarse el programa, de modo que siempre se trabaje
con un arreglo (o sub-arreglo) de longitud (por ejemplo) vivos, de modo de
no tener que considerar los que ya no viven? Sugerencia: ir “corriendo” las
posiciones en el arreglo a medida que “se cierra” el cı́rculo.
m
Nota: En el capı́tulo 14 veremos que las listas encadenadas se adaptan
mejor que los arreglos para esta variante.
o
.c
f ) El lazo “ repeat...until (cuenta = m) ” en el programa original es muy
primitivo. Por ejemplo, si n = 1000, m = 100, cuando quedan 10 sobrevi-
os
vientes debemos pasar unas 10 veces por cada uno de los 1000 elementos.
r
¿Podrı́a mejorarse el lazo usando “ mod ”? Sugerencia: recordar el programa
fo
resto (pág. 163). ✄
.m
na
6.4. Polinomios
tu
¿Para qué estudiar polinomios? ¿No basta con estudiar las funciones lineales
on
Pág. 56 Arreglos
X
k
n= ai b i , donde ai ∈ Z y 0 ≤ ai < b, (6.1)
m
i=0
o
.c
y n1 = (a− a0 )/b es un entero que tiene resto a1 cuando dividido por b, entonces
b | (n1 − a1 ), etc.
os
Un esquema para encontrar los coeficientes a0 , a1 , . . . , ak de n en base b es
m := n; j := -1;
r
fo
repeat
.m
no nulo)
on
j := k; n := a[j];
while (j > 1) do begin j := j-1; n := n * b + a[j] end
yc
a) Implementar dos programas para que dados la base b y n, encontrar los coe-
dm
X
k
n= ai 2 i ,
i=0
y haciendo
2
a2 +···+2k−1 ak−1 +2k ak
xn = xa0 +2a1 +2
k−1 k
= xa0 · (x2 )a1 · (x4 )a2 · · · (x2 )ak−1 · (x2 )ak .
m
if (m mod 2 = 1) then producto := producto * potencia
o
end
.c
b) Comparando con el problema 4.15, donde se usaba un lazo “ for ”: ¿cuántos
os
operaciones aritméticas (productos y divisiones) se realizan en cada caso?
(Recordar el problema 6.7.b))
r ✄
fo
.m
na
tu
on
yc
dm
.a
w
w
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 7
Funciones y Procedimientos
m
Nuestros programas se han hecho cada vez más largos, y a medida que avan-
cemos lo serán aún más. La longitud y complejidad de los programas profesio-
o
nales es tal que una sola persona no puede siquiera escribirlos completamente.
.c
A fin de abordar esta dificultad, se han desarrollado una serie de técnicas, y
os
en este capı́tulo daremos los primeros pasos hacia una de ellas, la modularización,
para lo cual Pascal cuenta con dos mecanismos: funciones y procedimientos.
r
fo
Aunque la ventaja de usar funciones o procedimientos irá quedando más
clara con los ejemplos que veremos en éste y otros capı́tulos, podemos decir que
.m
jando una visión más global y no tan detallada en cada parte (viendo el
bosque y no el árbol).
.a
El lector seguramente recordará el uso que con el mismo espı́ritu hemos dado
w
a “ const ”, por ejemplo en el problema 6.2, para hacer un único cambio que
w
7.1. Funciones
Como hemos visto, Pascal cuenta con una serie de funciones pre-definidas,
como “cos” o “ln”, de una variable, o la suma, “+”, que se aplica a dos o más
variables. Pero —necesariamente— no puede tener todas las funciones y sólo
algunas se han incluido. Por ejemplo, el cálculo de la potencia xn cuando x ∈ R
y n ∈ N no está incluida en Pascal, pero lo hemos hecho en el problema 4.15.
Supongamos que queremos hacer un programa para comparar los valores xn
y y , donde x, y ∈ R y n, m ∈ N son datos ingresados por el usuario. Nuestro
m
Claro que si contáramos con una función de Pascal para calcular xn para
m
x ∈ R y n ∈ N, digamos “ potencia(x,n) ”, podrı́amos reemplazar los dos
o
renglones anteriores por
.c
xn := potencia(x,n); ym := potencia(y,m);
os
Aunque Pascal no cuenta con una función que realice este cálculo, nos da un
mecanismo para definirla nosotros.
r
fo
En Pascal, las funciones y procedimientos (que veremos en un rato) se de-
.m
begin
z := 1;
.a
for k := 1 to b do z := z * a;
w
potencia := z
end;
w
w
Observamos que
m
a) Observar que la variable k ya no está definida al comienzo del programa,
sino sólo en la función “ potencia ”: es local a la función pero desconocida
o
para el resto del programa.
.c
i) Incluir el renglón
os
writeln(’ El valor de k es: ’, k:1);
r
fo
inmediatamente antes del cartel final del programa. El compilador se-
.m
guramente rechazará esta acción pues k no está declarada.
ii) Manteniendo el renglón problemático, incluir la declaración de k como
na
m
var z: real;
o
begin
.c
z := x;
os
while (n > 1) do begin
z := x * z; n := n - 1 end;
potencia := z
r
fo
end;
.m
tos, no deben ser los mismos que las variables locales a la función.
Por ejemplo no debemos poner
.a
.
.
w
a = a0 c0 = a1 = a2 c1 = b2 b = b0 = b1
c2
o m
.c
os
r
Figura 7.1: una función continua con distintos signos en los extremos.
fo
.m
En el método de la bisección se comienza tomando a0 = a y b0 = b, y para
i = 0, 1, 2, . . . se va dividiendo sucesivamente en dos el intervalo [ai , bi ] tomando
na
son opuestos (que podemos expresar como f (ai )f (bi ) < 0). Se finaliza cuando
se obtiene un valor de x tal que |f (x)| es suficientemente chico, |f (x)| < εy , o
on
Nota: También en este sentido, observamos que 210 = 1024 ≈ 103 y 220 =
1048576 ≈ 106 , por lo que el intervalo inicial se divide aproximadamente en
.a
m
los extremos sean distintos.
Observar que sólo necesitamos el signo de ypoco para determinar
o
.c
el nuevo intervalo, de modo que no hemos conservado el valor de
ymucho.
os
5. En la salida tenemos en cuenta las distintas posibilidades por las cuales
r
seguir es falsa:
fo
.m
• Si la condición de distinto signo en los extremos no se satisfacı́a
inicialmente, no se han realizado iteraciones (por lo que el valor de
na
c) En caso de que haya más de una raı́z en el intervalo inicial, la solución elegida
w
el programa sucesivamente con los valores .8, 1 y 1.2 para mucho, pero
tomando poco = −3 en todos estos casos.
d ) ¿Por qué si ponemos poco = −3 y mucho = 1 obtenemos la raı́z x = −1 en
una iteración?
Nota: En general, nunca obtendremos el valor exacto de la raı́z: recordar
que en la computadora sólo existen racionales (¡y pocos!).
e) x = 0 es raı́z, pero ¿qué pasa si ponemos poco = 0 y mucho = 1? Modificar
el programa de modo que si f (poco) o f (mucho) sean en valor absoluto
suficientemente pequeños, entonces se imprima la correspondiente solución
y no se realice el lazo de bisección.
f ) Agregar también la impresión de carteles apropiados cuando f (poco) ×
f (mucho) ≥ 0 y no se está en las condiciones del inciso anterior.
g) El programa no verifica si poco < mucho, y podrı́a suceder que poco >
mucho. ¿Tiene esto importancia?
h) Teniendo en cuenta las notas al principio de la sección (pág. 63), ¿tendrı́a
sentido agregar al programa un criterio de modo de parar si los extremos
del intervalo están suficientemente cerca? Si la nueva tolerancia fuera εx ,
¿cuántas iteraciones deben realizarse para alcanzarla, en términos de εx y
los valores originales de mucho y poco?
i) Modificar el programa de modo que en vez de considerar hasta un máximo
de iteraciones, el programa termine cuando o bien se ha encontrado x tal que
|f (x)| < εy o bien se llega a poco y mucho de modo que |poco − mucho| <
εx . ✄
El método de la bisección es bien general y permite encontrar las raı́ces de
muchas funciones. Al programarlo, hemos separado la declaración de la función
f de modo de poder cambiarla fácilmente según la aplicación, sin necesidad de
m
recorrer todo el programa buscando las apariciones de f (habrá, sı́, que cambiar
o
también los carteles iniciales).
.c
Problema 7.4. Cambiar la definición de la función en el programa biseccion,
os
para responder a los siguientes ejemplos (hacer primero un bosquejo del gráfico
para estimar valores iniciales de poco y mucho):
r
fo
a) Resolver aproximadamente las ecuaciones:
.m
x2 − 5x + 2 = 0 y x3 − x2 − 2x + 2 = 0.
na
2 − ln x = x y x3 sen x + 1 = 0.
.a
m
c) Considerando que a y r están fijos, ¿existe un valor de p de modo que el
o
saldo sea siempre el mismo, i.e. dm+1 = dm para m = 1, 2, . . . ?, ¿cuál?
.c
d ) Para a, r e p fijos, la función saldo(a, r, p, m) es decreciente con m si p es
os
mayor que el monto calculado en el inciso c). ¿Cómo es el comportamiento
cuando sólo varı́a a?, ¿y si sólo varı́a r?
r
fo
e) Hacer un programa de modo que dados r, a y p (p mayor que el monto en c))
calcule el número total de cuotas n. Aclaración: todas las cuotas deben ser
.m
y mucho = a?
g) El resultado anterior, p, en general no podrá pagarse en pesos y centavos
yc
funciones similares.
Por ejemplo, en (la versión inglesa de) Excel están las funciones:
7.3. Procedimientos
Los procedimientos y funciones son muy parecidos entre sı́, y a veces se
los engloba bajo el nombre común de rutinas 5 . De hecho, en lenguajes como
“C” no hay distinción entre ellos. En Pascal, la diferencia más obvia es que los
procedimientos no tienen “un resultado” visible.
Pascal nos permite mezclar funciones y procedimientos, con la única restric-
ción de que se deben declarar en el orden en que son usados: una función o
procedimiento no puede llamar a una función o procedimiento que no ha sido
aún declarada.
Nota: Una posibilidad intermedia el uso de “ forward ”, que no veremos.
Más aún, como veremos en el próximo problema, es posible poner una función
o procedimiento dentro de otra función o procedimiento.
Volvamos al problema 4.10 (pág. 27) donde hicimos una tabla del seno usan-
do el programa tablaseno (pág. 163). Podemos esquematizar ese programa por
m
medio de los siguientes pasos:
o
1. Poner carteles.
.c
2. Leer los datos, en este caso inicial , final e incremento.
os
3. Hacer e imprimir la tabla.
4. Señalar el fin del programa.
r
fo
Pascal nos permite poner cada uno de estos pasos como procedimiento, po-
.m
niendo en el cuerpo principal del programa6:
na
begin
cartelesiniciales;
tu
leerdatos;
imprimirtabla;
on
cartelfinal
end.
yc
plo de la tabla del seno, usamos sólo procedimientos puesto que no tenemos
asignaciones explı́citas.
w
procedure cartelesiniciales;
begin
writeln(’Hacer una tabla del seno dando valores’);
writeln(’inicial, final, y del incremento (en grados).’);
writeln
end;
m
procedure calculodepi;
o
begin pi := 4 * arctan(1) end;
.c
como primer procedimiento, incluyendo la sentencia “ calculodepi ” en el
os
cuerpo principal del programa. Compilar y ejecutar el programa con estas
modificaciones.
r
fo
d ) En realidad, el valor de pi se usa sólo para pasar de grados a radianes. Eli-
minar las declaraciones de pi , calculodepi y la sentencia “ calculodepi ” del
.m
var
on
grados: integer;
radianes, pi: real;
yc
procedure calculodepi;
dm
begin
.a
calculodepi;
w
writeln(’Angulo Seno’);
w
grados := inicial;
while (grados <= final) do begin
w
m
siguiendo un esquema como “ x := t; t := y; y := x ”.
Si queremos implementar este intercambio como procedimiento, el primer
o
impulso es poner (suponiendo variables enteras) un procedimiento como en el
.c
programa swap (pág. 178).
os
a) Observar la declaración de los argumentos en swapincorrecto: cuando hay
r
dos (o más) consecutivos del mismo tipo, en este caso “ integer ”, hemos
fo
colocado
.m
x, y: integer
na
x: integer; y: integer
on
guiente:
w
m
y nos preguntamos el valor final del arreglo a.
o
Sustitución por valor : La variable x en el procedimiento P tiene valor
.c
inicial 10 = a1 . El valor final de a es (10, 20).
os
Sustitución por referencia: En el procedimiento P , x = a1 . La sentencia
r
“ x := x + 2 ” significa “ a[1] := a[1] + 2 ”. El valor final de a es
fo
(12, 20).
.m
Nota: Hay una tercera forma de sustitución, llamada por nombre, que en Pascal
no está implementada. En este tipo de sustitución, el parámetro real sustituye
na
.
.
.
w
.
.
.
begin (* parte principal *)
.
.
.
P(1);
.
.
.
end.
son incorrectas.
• Si hay varios parámetros, algunos pueden pasarse por valor y otros por
referencia (con “ var ”). Si hay una lista de parámetros separados por “ , ”,
como en “ proc (var a, b: integer; c, d: integer) ”, entonces los
Problema 7.8. En los siguientes programas determinar los valores de los pará-
metros de las sentencias “ writeln ” y luego comprobar las respuestas eje-
cutándolos (agregando encabezado):
a) var a, b, c: integer;
begin a:= 5; b := 8; c := 3;
P(a, b, c); P(7, a + b + c, a); P( a * b, a div b, c)
m
end.
o
b) var i, j, k: integer;
.c
os
procedure P(var i: integer);
begin i := i + 1; writeln(i, j, k) end;
r
fo
procedure Q( h: integer; var j: integer);
.m
var i: integer;
na
procedure R;
begin i := i + 1 end;
tu
on
begin i := j;
if (h = 0) then P(j) else if h = 1 then P(i) else R;
yc
writeln( i, j, k)
end;
dm
end.
w
y desconocido globalmente. ✄
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 8
o m
.c
8.1. Definiendo nuestros propios tipos de datos
os
Hemos visto cuatro tipos de datos elementales —“ boolean ”, “ char ”, “ in-
r
fo
teger ” y “ real ”— pero Pascal también nos permite crear nuevos tipos me-
diante la palabra “ type ”. Esto es especialmente conveniente cuando trabajamos
.m
con varios arreglos de las mismas caracterı́sticas (y como veremos más tarde,
también para otras estructuras).
na
declarados como
on
variables.
w
a, b: arregloentero
lo que nos permite hacer asignaciones de la forma “ a := b ”, y no tener que
hacer un lazo para la asignación1. Más importante, es altamente conveniente y
recomendable usar como argumentos de funciones o procedimientos únicamente
parámetros de los tipos elementales o declarados con “ type ”, a fin de evitar
errores.
La estructura de un programa Pascal —a la que ya no haremos modifica-
ciones— toma entonces la forma del cuadro 8.1, y la estructura de funciones
y procedimientos es similar (pueden tener constantes, tipos, etc. propios), sólo
que terminan con “ end; ” en vez de “ end. ”.
1 No obstante, no es posible hacer la comparación “ a = b ”. En este caso habrá que usar
un lazo.
m
Ahora que contamos con funciones, es un buen momento para repasar la
o
entrada de datos del programa busquedalineal del problema 6.2.
.c
Problema 8.1 (Ingreso de arreglos). Supongamos que queremos ingresar
os
un arreglo a = (a1 , a2 , . . . , an ) de datos (de algún tipo), ingresando un dato
r
por renglón, poniendo los carteles en cada paso, y terminando la entrada con
fo
“doble <retorno>”. Siguiendo el esquema del programa busquedalineal, e in-
.m
corporando el control del número de datos, de ahora en más vamos a usar un
procedimiento para englobar esta acción.
na
miento:
dm
n := 1; findatos := false;
repeat
if (n > MAXN) then findatos := true
else begin
write(’Entrar el dato ’, n:2);
write(’ (fin = <retorno>): ’);
if (eoln) then begin findatos := true; readln end
else begin readln(a[n]); n := n + 1 end
end
until findatos;
n := n - 1
end;
Podemos también copiar la impresión de arreglos del mismo programa, con
m
begin
o
writeln(’** Prueba de lectura e impresion de arreglos’);
.c
writeln;
os
leerarreglo( arreglo, ndatos);
writeln(’El arreglo leido es:’);
escribirarreglo( arreglo, ndatos);
r
fo
writeln; writeln(’** Fin **’)
.m
end.
na
c) max supone que long > 0. Dar un ejemplo donde es 0 y modificar el pro-
grama para considerar este caso.
d ) Modificarlo de modo de encontrar también el mı́nimo, usando un nuevo
procedimiento.
e) Modificarlo de modo que se intercambien las posiciones del máximo y el
último elemento, imprimiendo el nuevo renglón. ✄
Problema 8.3. Desarrollar un programa que, dado un arreglo de enteros en-
trado como en el problema 8.1, y sin usar otro arreglo adicional, lo invierta, i.e.
si el arreglo inicialmente es (a1 , a2 , . . . , an ), el arreglo final es (an , . . . , a2 , a1 ).
Sugerencia: usar “ swap ” para intercambiar a1 ↔ an , a2 ↔ an−1 ,. . . ✄
Problema 8.4. Desarrollar un programa que leyendo un renglón (como en el
programa maximo), lo escriba al revés, e.g. si la entrada es
“ dabale arroz a la zorra el abad ”
m
el programa escriba
o
“ daba le arroz al a zorra elabad ”
.c
Sugerencia: ver el problema anterior. ✄
r os
fo
8.3. La caja de herramientas
.m
arreglos.
tu
Es una buena idea armar una “caja de herramientas” formada por archivos
de texto en el disco, con las funciones o procedimientos que nos parecen más
on
vez.
Nota: Los sistemas de programación más avanzados incluyen en bibliotecas es-
.a
herramientas”.
w
nos dan una “plantilla” (o “template”) para leer o imprimir arreglos de números
eventualmente modificando el ı́ndice inicial, e.g. “ array[1..MAXN] ”, o el tipo
de dato del arreglo, e.g “ real ” en vez de “ integer ”.
Problema 8.5 (La Caja de Herramientas). Copiar en sendos archivos de
texto2 , los procedimientos leerarreglo y escribirarreglo del problema 8.1, y prac-
ticar incorporarlos en un programa para leer y escribir un arreglo de enteros
(como en el problema 8.1). ✄
Seguramente habrá muchos procedimientos o funciones que querrás incorpo-
rar a la caja de herramientas, algunos que ya hemos visto como el método de la
bisección (sección 7.3), y otros que veremos más adelante, como alguno de los
métodos de búsqueda y clasificación del capı́tulo 10.
2 En algunos sistemas operativos, es conveniente que tengan la extensión “ .txt ”.
m
y accedemos al elemento (i, j) usando indistintamente “ m[i][j] ” o “ m[i,j] ”.
Las matrices son un ejemplo de estructura de dos dimensiones, pero no hay
o
inconvenientes en considerar estructuras con cualquier número de dimensiones.
.c
Si en vez de números guardamos caracteres, las filas pueden pensarse como
os
renglones, como hacemos en el siguiente problema.
r
fo
Problema 8.6. En este problema consideraremos un texto como una serie de
renglones o lı́neas no vacı́as (cada una tiene al menos un carácter). Declaramos
.m
const
na
type
tiporenglon = array[1..MAXC] of char;
yc
var
.a
texto: tipotexto;
w
cenr: caracteresenrenglon;
eventualmente recordando la Nota en la pág. 75.
a) Desarrollar un procedimiento o función para leer no más de MAXR renglo-
nes, con no más de MAXC caracteres por renglón, dando como señal de fin
de entrada un “renglón vacı́o”.
Sugerencia: copiar las ideas del programa maximo.
Sugerencia si la anterior no alcanza:
nr := 0; (* numero de renglon leido, al ppo. ninguno *)
while (not eoln) do begin
(* mientras el renglon a leer no sea vacio... *)
nr := nr + 1; (* hay un renglon mas *)
nc := 0; (* que todavia no tiene caracteres leidos *)
8.5. “Strings”
o m
Problema 8.7. El tipo “ string ” no es estándar en Pascal, pero existe en casi
.c
todos los compiladores, en particular en Turbo Pascal. Probar el comportamien-
os
to del compilador con un programa que lee un renglón entrado por terminal, vı́a
“ readln(s) ”, donde se declara s como “ string[100] ”, indicando su longitud,
y/o simplemente como “ string ”.
r
fo
En caso de aceptar alguna de estas variantes, agregar al programa las ins-
.m
trucciones:
na
el tipo “ string ”.
dm
A veces resulta útil guardar los resultados obtenidos por un programa, para
después leerlos, imprimirlos o utilizarlos en otros programas. Dado el tamaño
de las “salidas” de los programas con los que trabajamos, será suficiente para
nosotros trabajar con archivos de texto, es decir, archivos que podemos abrir
(para leer o modificar) con un procesador de texto e imprimir directamente.
El lenguaje Pascal no es especialmente apto para leer y escribir archivos, y
es por ello que se han realizado muchas extensiones (no estándar) tratando de
mejorarlo, pero trataremos de ceñirnos al estándar. En él se establece otro tipo
elemental de datos, además de los que ya hemos visto: el tipo “ text ”, o archivo
de texto.
Dos estructuras fundamentales para el manejo de archivos son: copiar de
consola a archivo, y copiar de archivo a consola, que mostramos en los programas
m
Ha de tenerse cuidado con “ rewrite ”, pues si el archivo no existe, se crea
o
uno nuevo, pero si ya existe, sus contenidos son borrados.
.c
Nota: En compiladores que siguen el modelo de Turbo Pascal para en-
os
trada/salida, hay que modificar los programas, ya que no aceptan el
estándar.
r
fo
Para escribir un archivo, hay que cambiar el renglón
.m
rewrite(archivo, nombre);
por
na
por
dm
m
Nota: El “directorio” en el cual el programa creará o buscará el archivo
o
depende de la instalación del compilador. Las posibilidades son: dar una
.c
ubicación “absoluta”, indicando el “camino” completo; o crear un archivo
os
y encontrar dónde se instaló, a veces el mismo compilador permite deter-
minar el directorio donde se trabajará.
r
fo
Nota: En algunos sistemas operativos es conveniente poner la extensión
“ .txt ” a los archivos de texto.
.m
Problema 8.9.
dm
a) Hacer un programa para crear un archivo con la lista de los 1000 prime-
ros primos, esencialmente modificando la salida del programa eratostenes
.a
si mcd(a, b) = 1 (lo que implica el resultado de Euclides sobre que hay infinitos
primos). En el inciso b), si pensamos que b = 10, y a es cualquier número que
no tenga como factores a 2 ni a 5, el teorema de Dirichlet dice que hay infinitos
primos que terminan en 1, infinitos que terminan en 3, etc. ✄
m
Problema 8.11 (Polinomios interpoladores de Lagrange). Un polinomio
o
P (x) = an xn + an−1 xn−1 + · · · + a1 x + a0 , de grado a lo sumo n, está determi-
.c
nado por los n + 1 coeficientes. Supongamos que no conocemos los coeficientes,
os
pero podemos conocer los valores de P (x) en algunos puntos, ¿cuántos puntos
necesitaremos para determinar los coeficientes? Como hay n + 1 coeficientes,
r
fo
es natural pensar que quedarán determinados por n + 1 ecuaciones, i.e. que
bastarán n + 1 puntos.
.m
ecuaciones lineales, y viendo que su determinante (de Van der Monde que se
estudia en Álgebra Lineal) es no nulo.
tu
X
n+1 Y x − xj
yc
P (x) = yi . (8.1)
xi − xj
dm
i=1 j6=i
res.
a) Ver que efectivamente, P (x) definido por la ecuación (8.1) satisface P (xi ) =
yi para i = 1, . . . , n + 1.
0.5
π π π
6 4 2
π
o m
Dos jugadores, A y B, colocan un número arbitrario de fósforos sobre
.c
una mesa, separados en filas o grupos. El número de filas y el número
os
de fósforos en cada fila también son arbitrarios. El primer jugador,
A, toma cualquier número de fósforos de un fila, pudiendo tomar
r
uno, dos o hasta toda la fila, pero sólo debe modificar una fila. El
fo
jugador B juega de manera similar con los fósforos que quedan, y
.m
siempre una estrategia ganadora. Para esto, digamos que una disposición de los
on
jugar de forma tal de ganar el juego. Por ejemplo, la posición en la cual hay dos
filas con dos fósforos cada una, es ganadora: si A deja esta posición a B, B debe
dm
tomar uno o dos fósforos de una fila. Si B toma 2, A toma los dos restantes. Si
B toma uno, A toma uno de la otra fila. En cualquier caso, A gana.
.a
es ganadora.
w
número de fósforos de cada fila en binario, uno bajo el otro, poniendo en una
fila final “ P ” si la suma total de la columna correspondiente es par, e “ I ” en
otro caso. Por ejemplo, en las posiciones anteriores harı́amos:
0 1
1 0
1 0
1 0 y
1 1
P P
P P
En el último ejemplo, es conveniente poner 01 en vez de sólo 1 en la primer
fila a fin de tener todas las filas con igual longitud.
3 Tomado de G. H. Hardy y E. M. Wright, An Introduction to the Theory of Numbers, 4.a
| = 0 0 1
| | | = 0 1 1
| | | | = 1 0 0
I I P
b) En este inciso, veremos que una posición en Nim es ganadora si y sólo si es
correcta.
i) Si ninguna fila tiene más de un fósforo, la posición es ganadora ⇔ hay
en total un número par de fósforos ⇔ la posición es correcta.
ii) Si un número a ∈ N, expresado en binario por (an , an−1 , . . . , a1 , a0 )
(permitiendo an = 0) es reemplado por otro menor b (entero ≥ 0),
m
expresado en binario como (bn , bn−1 , . . . , b1 , b0 ), entonces para algún
i, 0 ≤ i ≤ n, las paridades de ai y bi son distintas.
o
iii) Por lo tanto, si el jugador X recibe una posición correcta, necesaria-
.c
mente la transforma en incorrecta.
os
iv ) Supongamos que estamos en una posición incorrecta, es decir, al menos
r
la suma de una columna es impar. Para fijar ideas supongamos que las
fo
paridades de las columnas son
.m
P P I P I P
na
- -
0 1 1 1 0 1
yc
de suma impar. Cambiando 0’s y 1’s por 1’s y 0’s en las posiciones
marcadas, obtenemos el número (menor que el original, expresado en
.a
binario):
w
- -
w
0 1 0 1 1 1
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 9
Números Aleatorios y
Simulación
o m
.c
Muchas veces se piensa que en matemáticas las respuestas son “siempre exac-
os
tas”, olvidando que las probabilidades forman parte de ella y que son muchas
las aplicaciones de esta teorı́a.
r
fo
Una de estas aplicaciones es la simulación, técnica muy usada por fı́sicos,
ingenieros y economistas cuando es difı́cil llegar a una fórmula que describa el
.m
sistema o proceso. Ası́, simulación es usada para cosas tan diversas como el
estudio de las colisiones de partı́culas en fı́sica nuclear y el estudio de cuántos
na
ros generados por la computadora que reciben el nombre de “aleatorios” (o, más
w
cripción de qué son estos números o cómo se obtienen: basta con la idea intuitiva
de que la computadora nos da un número en cierto rango, y cualquier número
del rango tiene la misma probabilidad de ser elegido por la computadora.
En general, los números aleatorios se obtienen a partir de un valor inicial o
semilla (“seed” en inglés); de modo que si no se indica lo contrario —cambiando
la semilla— siempre obtenemos la misma sucesión de números aleatorios. Un
método eficaz para obtener “números verdaderamente aleatorios”, es cambiar
la semilla de acuerdo a la hora que indica el reloj de la computadora.
Lamentablemente, en el estándar Pascal no está definida una función para
generar números aleatorios. Aún cuando un compilador tenga implementada
una tal función, el nombre con la que se llama y aún el tipo de resultado —por
ejemplo si son números reales entre 0 y 1 o enteros entre 0 y maxint —, dependen
también del compilador.
9.2. Aplicaciones
m
Problema 9.1. El programa dado (pág. 181) hace una simulación de tirar un
dado mediante números aleatorios obtenidos con la sentencia “ random ”.
o
.c
a) La sentencia “ randomize ” sirve para comenzar una nueva serie de números
aleatorios. Eliminarla, comentándola, ejecutar repetidas veces el programa
os
y comprobar que siempre se obtienen los mismos resultados (o sea no es
muy al azar).
r
fo
Nota: Salvo para programas que tienen un tiempo de ejecución grande, no
.m
debe hacerse más de una llamada a “ randomize ”.
b) Modificar el programa para simular tirar una moneda con resultados “cara”
na
o ”ceca”. ✄
tu
Problema 9.2. El programa dados (pág. 182) hace una simulación para en-
on
contrar la cantidad de veces que se necesita tirar un dado hasta que aparezca un
número prefijado, entrado por el usuario. Gracias a la sentencia “ randomize ”, el
yc
resultado en general será distinto con cada ejecución. Observar que si el usuario
entra un número menor que 1 o mayor que 6, el programa no termina nunca.
dm
a) Ejecutar el programa repetidas veces, para tener una idea de cuánto tarda
en aparecer un número.
.a
prefijado, o que nunca salga k veces consecutivas. Sin embargo, se puede demos-
trar matemáticamente que la probabilidad de que esto suceda es 0 (suponiendo
que el generador de números aleatorios sea correcto). ✄
Problema 9.4. El compilador de Pascal de Ana tiene la sentencia “ aleat ”,
que obtiene números aletorios r ∈ R con 0 ≤ r < 1, i.e. como la sentencia
“ random ” de Turbo Pascal sin argumentos.
a) ¿Qué instrucción debe poner Ana en vez de “ random(n) ” (que no es re-
conocida por su compilador) para hacer los problemas anteriores sobre los
dados? Sugerencia: multiplicar y usar “ trunc ”.
b) El profesor le ha dado un problema donde tiene que obtener 1 o −1 con
igual probabilidad. ¿Qué sentencias deberı́a usar?
c) ¿Y si necesitara números reales entre a y b (que pueden ser a pero no b),
donde a < b? ✄
m
Existen muchos métodos, que reciben el nombre de métodos de Monte-Carlo,
para aproximar cantidades determinı́sticas, i.e. no aleatorias, mediante proba-
o
bilidades. El próximo problema es un ejemplo de la técnica.
.c
os
Problema 9.5. Hacer un programa para calcular π tomando n pares de núme-
ros aleatorios (a, b), con a y b entre −1 y 1, contar cuántos de ellos están dentro
r
del cı́rculo unidad, i.e. a2 + b2 < 1. El cociente entre este número y n (ingresado
fo
por el usuario), es aproximadamente la proporción entre las áreas del cı́rculo de
.m
Problema 9.6.
a) Desarrollar un programa para hacer una lista de r números enteros, elegidos
tu
por el usuario.
b) Modificar el programa de modo que, recorriendo linealmente la lista genera-
yc
Problema 9.7 (Dos con el mismo cumpleaños). Mucha gente suele sor-
w
prenderse cuando en un grupo de personas hay dos con el mismo dı́a de cum-
w
m
usar la máquina muchas veces, la cantidad promedio de números emitidos es
o
aproximadamente e = 2.71828 . . ..
.c
Nota: Éste es otro ejemplo del método de Monte-Carlo, esta vez para aproximar
e. ✄
os
Problema 9.9 (El Show de Televisión). Un problema que causó gran re-
r
fo
vuelo hacia 1990 en Estados Unidos es el siguiente:
.m
de una de las tres puertas que ve hay un auto 0 km, no habiendo nada
detrás de las otras dos. El locutor le pide al participante que elija
tu
finalmente.
.a
a) Intuitivamente y sin dar una justificación rigurosa, ¿es más conveniente para
el participante mantener su primera elección, cambiarla o es indiferente?
w
b) Hacer un programa que simule el juego para tener mejor idea: i) el programa
w
elige una de las puertas donde estará el auto (sin que nosotros sepamos
w
el resultado); ii) nosotros hagamos una elección; iii) que en base a esta
elección, el “locutor” elija una segunda puerta detrás de la cual no está el
auto, al azar si hay dos posibilidades; iv ) que nosotros mantengamos o
cambiemos la elección; y v ) que diga si ganamos o no el auto.
c) Modificar el programa anterior de modo de hacer automáticamente n jue-
gos en los que siempre elegimos una puerta al azar y luego cambiamos,
retornando el número de veces que ganamos. Correrlo para n = 1000 (¡pri-
mero hacerlo para n = 5 para ver si está funcionando bien!). Comparar el
resultado con la respuesta en a). ✄
programa, que el promedio de veces que la aguja cruzará una de las lı́neas es
aproximadamente 2l/π.
Georges Louis Leclerc, Conde de Buffon (1707–1788) planteó este problema
en 1733, dando vigor a la teorı́a de probabilidad geométrica. El problema es
más que nada de interés teórico e introductorio al tema, pues las aproximacio-
nes experimentales de π no son buenas.
m
cartas de un mismo palo (matemáticamente, obtener una permutación aleatoria
o
de los números entre 1 y n).
.c
Presentamos varias posibilidades:
os
a) Imaginando que empezamos con la cartas numeradas de 1 a n, ordenadas
crecientemente, hacemos el siguiente procedimiento: sacamos una carta al
r
fo
azar entre 1 y n y la separamos, de las restantes sacamos una al azar (entre
1 y n − 1) y la separamos, y ası́ sucesivamente. Sin embargo, computacio-
.m
m
Por ejemplo, si los datos son 8, 0, 4, 6, 7, 8, 3, 2, 7, 4, la media es 4.9, la mediana
o
es 5, y las modas son 4, 7 y 8.
.c
Hacer un programa para generar aleatoriamente un arreglo de longitud n
os
de números entre 0 y 9, y luego encontrar la media, mediana y moda/s (n es
ingresado por el usuario). ✄
r
fo
Problema 9.15. Desarrollar un procedimiento que, dado un arreglo de núme-
.m
ros reales, encuentre el arreglo de sumas parciales o acumuladas, i.e. si el arreglo
inicial es a = (a1 , a2 , . . . , an ), encuentre (a1 , a1 + a2 , a1 + a2 + a3 , . . . ). ✄
na
Problema 9.16.
tu
m
end;
o
.c
9.4.2. Función “random”
r os
function random: real;
fo
const
.m
p1 = 30269; m1 = 171;
p2 = 30307; m2 = 172;
na
p3 = 30323; m3 = 170;
tu
var
on
begin
dm
(* ajustar semillas *)
randsemilla1 := randsemilla1 mod p1;
.a
(* primer generador *)
x1 := m1*(randsemilla1 mod 177) - 2*(randsemilla1 div 177);
if x1 < 0 then x1 := x1 + p1;
(* segundo generador *)
x2 := m2*(randsemilla2 mod 176) - 35*(randsemilla2 div 176);
if x2 < 0 then x2 := x2 + p2;
(* tercer generador *)
m
• randsemilla1 , randsemilla2 y randsemilla3 deben declararse como varia-
o
bles globales de tipo “ integer ”.
.c
• La llamada a randinic debe ir al principio del cuerpo principal, también a
os
fin de evitar problemas.
r
fo
• random da un número aleatorio r, con 0 ≤ r < 1. Si se desea imitar la
llamada “ random(n) ” de Turbo Pascal, habrá que cambiar el encabezado
.m
de random, poniendo
na
random := trunc((temp-trunc(temp)) * n)
yc
procedure cambiarsemilla;
w
begin
w
Capı́tulo 10
Búsqueda y clasificación
m
Siempre estamos buscando algo y es mucho más fácil encontrarlo si los datos
están clasificados u ordenados. No es sorpresa que búsqueda y clasificación sean
o
.c
temas centrales en informática y que haya una enorme cantidad de material
escrito al respecto. Por ejemplo, en sus clásicos libros [4] Knuth dedica al tema
os
todo el Volumen 3 de (que por supuesto, usa material de los volúmenes anterio-
res). Acá hacemos una introducción al tema siguiendo, en mı́nima proporción,
r
fo
la presentación de Wirth en [6].
.m
na
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 *) ✄
m
Observamos que siempre termina, ya sea porque x es un elemento del arreglo,
o
.c
en cuyo caso i es el lugar que ocupa, o bien porque no se encontró, en cuyo caso
i = 0.
os
Nota: No hay nada misterioso en ir desde atrás hacia adelante, podrı́amos haber
r
puesto el “centinela” en la posición n + 1 y recorrer de adelante hacia atrás.
fo
Problema 10.2 (Búsqueda lineal con centinela). Hacer una implementa-
.m
ción con ambas variantes (con y sin centinela) como procedimientos, incluyendo
na
que al fin del procedimiento sea a = (1, 2, 5, 6, 9). En este caso podemos
poner algo como
.a
var i, m: integer;
w
begin
m := 1; (* a[1],...,a[m] son los elementos sin repetir *)
for i := 2 to n do
(* incluir a[i] si no es a[m] *)
if (a[m] < a[i]) then begin
m := m + 1; a[m] := a[i] end;
n := m
end;
Hacer un programa para verificar el comportamiento de este procedi-
miento.
b) Hacer un procedimiento (sin usar un arreglo auxiliar) para el caso general,
preservando en la salida el orden en que aparecen los elementos originalmen-
m
until ((i > n) or (j > m));
o
while (i <= n) do begin (* copiar el resto de a *)
.c
k := k + 1; c[k] := a[i]; i := i + 1 end;
os
while (j <= m) do begin (* copiar el resto de b *)
k := k + 1; c[k] := b[j]; j := j + 1 end
r
fo
Nota: El esquema se podrı́a mejorar usando centinelas, evitando algunas com-
.m
paraciones. ✄
na
las mitades.
w
obtenemos
m
else mucho := medio
o
end;
.c
(* a continuación comparar x
os
con a[mucho] y con a[poco] *)
¿serı́a ahora correcto?
r
fo
c) Si en vez de considerar poco + 1 en la condición del lazo del inciso anterior,
.m
poco := 1; mucho := n;
while (poco < mucho) do begin
tu
end
(* a continuación comparar x con a[mucho] *)
dm
similar al hecho con el esquema presentado. Suponemos que los datos son el
arreglo a = (a1 , a2 , . . . , an ) ordenado no-decrecientemente y x, y que al terminar
se hace la comparación entre ak y x.
a) i := 1; j := n;
repeat
k := (i + j) div 2;
if (a[k] < x) then i := k else j := k
until ((a[k] = x) or (i >= j))
b) i := 1; j := n;
repeat
k := (i + j) div 2;
if (x <= a[k]) then j := k - 1;
if (a[k] <= x) then i := k + 1
until (i > j)
m
c) i := 1; j := n;
o
repeat
.c
k := (i + j) div 2;
if (x < a[k]) then j := k else i := k + 1
os
until (i >= j)
r
d ) Muchos autores presentan un algoritmo un poco más ineficiente que el del
fo
problema 10.5.c), comparando x con amedio en cada paso de modo que hay
.m
dos comparaciones por “vuelta” de lazo:
na
end;
Ver que el esquema es correcto. ✄
.a
puede hacer un pozo en cualquier lugar para comprobar si hasta allı́ el cable
w
está sano, y bastará con detectar el lugar de la falla con una precisión de 5m.
Por supuesto, una posibilidad es ir haciendo pozos cada 5m, pero el encar-
gado no está muy entusiasmado con la idea de hacer tantos pozos, porque hacer
(y después tapar) los pozos cuesta tiempo y dinero, y los vecinos siempre se
quejan por el tránsito, que no tienen luz, etc.
¿Qué le podrı́as sugerir al encargado? ✄
m
búsqueda se orienta dando direcciones hacia donde está el regalo: N, NE, E, SE,
o
S, SO, O, NO.
.c
Recordando el problema del regalo en las cajas, ¿cuántas oportunidades
oportunidades habrá que dar en el caso de las m × n casillas? ✄
r os
fo
10.3. Métodos elementales de clasificación
.m
con sus elementos ordenados de menor a mayor. Para ello veremos tres méto-
dos elementales para clasificar que podemos relacionar con la forma con que
on
for i := 2 to n do begin
w
x := a[i]; a[0] := x; j := i;
w
for j := i+1 to n do
if (x > a[j]) then begin k := j; x := a[k] end;
a[k] := a[i]; a[i] := x
end
Nota: Comparar con el inciso e) del problema 8.2.
Intercambio directo o burbujeo: Aquı́ también levantamos las cartas al
mismo tiempo y las abrimos en abanico, pero vamos mirando de iz-
quierda a derecha (o de derecha a izquierda) buscando un par de
cartas consecutivas fuera de orden y cuando lo encontramos lo inver-
timos. Seguimos recorriendo con la mirada el abanico hasta que no
haya ningún par fuera de orden. Un posible esquema es:
for i := 2 to n do
for j := n downto i do
if (a[j-1] > a[j]) then begin (* swap *)
m
x := a[j-1]; a[j-1] := a[j]; a[j] := x
end
o
.c
Nota: Observar la similitud entre la clasificación por selección directa o inser-
ción directa con la obtención de “manos” aleatorias, como en el problema 9.12,
os
incisos b) y a) respectivamente. Esto permite estudiar estos métodos teórica-
mente.
r
fo
Problema 10.10 (Métodos elementales de clasificación). Implementar
.m
indica que la fracción de segundo es menor a 1/60 = 0.0166 . . .). Se contaron las
w
ı́ndices).
Observamos en la tabla que tanto selección directa como intercambio directo
realizan siempre el mismo número de comparaciones, n(n − 1)/2, y que aún
cuando no se hacen asignaciones (como en el caso de intercambio directo, cuando
el arreglo está ordenado), la enorme cantidad de comparaciones lleva tiempo.
En cuanto a las asignaciones, hemos de destacar que hemos tomado arreglos
de enteros, por lo que individualmente no llevan demasiado tiempo, y en el
caso de inserción directa con un arreglo aleatorio, hay muchas asignaciones pero
—comparando con los otros métodos elementales— no llevan tanto tiempo. Serı́a
distinto si los elementos fueran arreglos (o “strings”) al clasificar palabras).
Algunos autores usan búsqueda binaria en vez de búsqueda lineal en inser-
ción directa, disminuyendo en principio el número de comparaciones. Pero las
ventajas son marginales y aún pueden empeorar el algoritmo (ver [6, pág. 87]).
Arreglo ya ordenado
mergesort selección intercambio inserción
comparaciones 71 712 49 995 000 49 995 000 9 999
asignaciones 140 000 29 997 0 29 997
tiempo (segs.) 0.00 1.10 1.43 0.00
Arreglo aleatorio
mergesort selección intercambio inserción
m
comparaciones 123 582 49 995 000 49 995 000 25 023 757
o
asignaciones 140 000 103 894 75 041 274 25 043 755
.c
tiempo (segs.) 0.00 1.10 1.75 0.72
r os
Cuadro 10.1: comparación de algoritmos de clasificación.
fo
.m
que existe y que es bastante malo: nosotros casi siempre elegiremos el de se-
yc
pequeño. Por ejemplo, supongamos que los elementos de a son enteros entre 1 y
w
m
petidos, es más sencillo, y desde el punto de vista de la modularidad más
razonable, el llamar a dos procedimientos separados, uno para clasificar y
o
otro para “purgar”. Ası́, se puede cambiar, por ejemplo, el procedimien-
.c
to de clasificación por uno más conveniente sin tener que revisar todo el
os
procedimiento de este inciso.
Por otro lado, con métodos más avanzados es más eficiente primero
clasificar y luego “purgar” que al revés.
r
fo
b) Dados dos arreglos a = (a1 , a2 , . . . , an ) y b = (b1 , b2 , . . . , bm ), considerados
.m
i := 1; j := 1; r := 0;
yc
repeat
if (a[i] > b[j]) then j := j + 1
dm
r := r + 1;
c[r] := a[i];
w
i := i + 1;
w
j := j + 1
end
w
m
la forma a + bi, con a, b ∈ R, podemos poner:
o
.c
type complejo = record re, im: real end;
os
var z: complejo;
Nota: Observar que la declaración del registro termina con “ end; ”, pero no
r
fo
hay un “ begin ”.
.m
Para acceder a una componente del registro, el nombre del registro es se-
guido por un punto (“ . ”) y el correspondiente identificador del campo. Ası́, si
na
z.re := 3; z.im := 5;
on
zp := z;
mientras que la suma z 00 = z + z 0 puede expresarse como:
dm
m
tros, en cada uno de los cuales se guarda un nombre y un número de identifica-
ción. Declaramos
o
.c
const MAXN = 20;
os
type
r
tipoinfo = record nombre: string; nroid: integer end;
fo
tipoarreglo = array[0..MAXN] of tipoinfo;
.m
Una variante del procedimiento leerarreglo (capı́tulo 8, pág. 74), para leer
datos es:
na
writeln;
write(’Entrar el dato ’, n:2);
.a
else begin
w
begin
writeln(’** Entrada de datos’);
n := 1;
while (nuevodato) do n := n + 1;
n := n - 1
end;
donde la variable findatos es de tipo “ boolean ”.
Hacer un procedimiento para escribir arreglos de este tipo, y hacer un pro-
grama incorporando ambos procedimientos para probarlos. ✄
Problema 10.15 (Clasificación de arreglos de registros). Supongamos
que hemos hecho las declaraciones del problema 10.14, y queremos clasificar el
arreglo a de tipo “ tipoarreglo ”, que puede ser según nombre o según nroid .
La componente por la cual se clasifica se denomina llave o clave (en inglés
“key”). Por ejemplo si tenemos el registro x con el nombre “ Geri ” y el número
de identidad “ 5 ”, y el registro y con el nombre “ Ana ” y número “ 10 ”, or-
denándolos (de menor a mayor) por nombre vendrá primero y antes que x, pero
si los ordenamos por nroid vendrá primero x.
m
Nota: La comparación de strings se hace como la comparación entre números
o
(en Pascal), e.g. “ s1 <= s2 ”, obteniendo un valor booleano (verdadero o falso).
.c
a) Hacer una función “ mayor(x, y, llave) ” que retorna un valor lógico, y
os
donde x, y son del tipo “ info ” y llave es del tipo “ integer ”, tomando los
valores ‘ 1 ’ o ‘ 2 ’, de modo que el resultado de mayor sea el valor de “ x.z
r
fo
> y.z ” donde z puede ser nombre o nroid según el valor de llave.
b) Modificar alguno de los métodos de clasificación vistos, de modo de poder
.m
Problema 10.16. Una variante del juego de las cajas (problema 10.8) es la
siguiente: Dado un número (entero) entre 1 y 100 (generado aleatoriamente),
tratar de encontrarlo mediante “encajonamiento”, o sea vamos dando números a
.a
un programa para este juego, y pensar una estrategia para encontrar el número
en “pocas” jugadas. ¿Podrı́as relacionar una estrategia para este juego con el
método de la bisección (problema 7.3)? ✄
Problema
√ Dado n ∈ N, queremos encontrar el mayor m ∈ N tal que
10.17. √
m ≤ n, i.e. m = b nc, usando sólo operaciones entre enteros.
a) Ver que el siguiente esquema, siguiendo las ideas de búsqueda binaria, en-
cuentra m:
poco := 2; if (n = 1) then mucho := 2 else mucho := n;
while (poco < mucho) do begin
medio := (poco + mucho) div 2;
if ((medio * medio) <= n) then poco := medio + 1
else mucho := medio
end;
m := mucho - 1;
Sugerencia: realizar un análisis como en el hecho para Búsqueda Binaria,
viendo que i) el algoritmo
√ termina, ii) que al terminar poco = mucho, y iii)
siempre es poco − 1 ≤ b nc < mucho.
b) Hacer un procedimiento y programa implementándolo. Comprobar la co-
rrección comparando el valor obtenido con “ trunc(sqrt(n)) ”.
√
c) ¿Qué pasa si b(n + 1)/2c > maxint en el programa anterior?, ¿por qué?,
¿podrı́a arreglarse el esquema en a)?, ¿podrı́a usarse un escalamiento como
en el problema 5.9 sobre el método babilónico, para evitar que mucho ×
mucho > maxint?
Nota: El método babilónico también hace sólo operaciones aritméticas elemen-
tales y “acorta al menos en dos la distancia” en cada paso. El esquema
m
m := 1;
repeat m := (m + (n div m)) div 2 until ((m * m) <= n)
o
es correcto, excepto si m2 > maxint en algún momento. Es decir, valen las
.c
mismas
√ objeciones hechas en c) al esquema en a): es correcto si n + 1 ≤
os
2 maxint. ✄
r
Problema 10.18 (Estabilidad para clasificación). Un método de clasifica-
fo
ción se llama estable si el orden relativo de elementos con llaves iguales perma-
.m
nece inalterado en el proceso de clasificación.
na
ciones. Entre los más “populares” de este tipo podemos mencionar al heapsort
w
6. 1234567 y se fusionan.
A fin de implementar este algoritmo, usamos el procedimiento clasificar que
llama, a su vez, al fusionar :
procedure fusionar(
var a, b: arreglo; (* entra a, sale b *)
inic, medio, fin: integer (* partes a fusionar *)
);
var
i, j, k: integer;
begin
i := inic; j := medio + 1; k := inic - 1;
repeat
k := k + 1;
if (a[i] <= a[j]) then begin
m
b[k] := a[i]; i := i + 1 end
o
else begin
.c
b[k] := a[j]; j := j + 1 end
until ((i > medio) or (j > fin));
os
(* copiar lo que falta *)
while (j <= fin) do begin
r
fo
k := k + 1; b[k] := a[j]; j := j + 1 end;
.m
while (i <= medio) do begin
k := k + 1; b[k] := a[i]; i := i + 1 end
na
end;
tu
var
i, m, f, k, k2, veces, j: integer;
yc
a2: arreglo;
begin
dm
k2 := 2 * k;
w
i := 1; m := k; f := k2;
w
m
La versión de mergesort presentada no es muy “pulida” a fin de resaltar el
o
algoritmo. Entre otras cosas, usamos un arreglo auxiliar, violando una de las
.c
condiciones que habı́amos impuesto. Aunque nuestra versión no es demasiado
os
“competitiva” con otros algoritmos avanzados y otras implementaciones de mer-
gesort, es bastante mejor que otros métodos elementales, como puede verse en
el cuadro 10.1 (pág. 100). r
fo
.m
a) ¿Cuál es el valor de k, en términos de n, al terminar el procedimiento
clasificar ? Sugerencia: comparar con log2 n.
na
chicos). ✄
.a
w
w
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 11
Recursión
m
Supongamos que queremos encontrar todas las sumas
o
s1 = 1, s2 = 1 + 2, s3 = 1 + 2 + 3, ... sn = 1 + 2 + · · · + n, ...
.c
os
Podrı́amos calcular cada una de ellas separadamente, por ejemplo usando la
fórmula de Gauss an = n × (n + 1)/2, pero también podrı́amos poner
r
fo
s1 = 1, s2 = s1 + 2, s3 = s2 + 3, ... sn = sn−1 + n, ...
.m
d0 = a y
w
Nota: Observar que la variable “sobre la que se hace la recursión”, en este caso
n, no tiene antepuesta la palabra “ var ” en la definición de la función. No tiene
sentido hacerlo, pues cuando llamamos factorial (n − 1), n − 1 no tiene asignado
un lugar (aunque lo tenga n): n − 1 tiene que pasarse por valor y no referencia.
Nota: Ahora podemos apreciar un poco más por qué la función (o procedimien-
to) no es una variable local a la función.
a) Hacer un programa para calcular n! usando la función anterior.
b) Obtener el máximo valor de n para el cual se puede calcular n! (i.e. n! ≤
maxint ) en la versión del inciso a).
m
Nota: En vista de este resultado, es conveniente cambiar el tipo de los
o
valores de factorial de “ integer ” a “ real ” en la función en a), como
.c
hemos hecho en los problemas 4.11 y 4.12.
os
c) La fórmula de Stirling establece que cuando n es bastante grande,
√
n! ≈ nn e−n 2πn.
r
fo
Hacer un programa para calcular esta aproximación, y probarla con n =
.m
10, 100, 1000. Comparar con los resultados obtenidos en a) (habiendo hecho
✄
na
a sus variables locales, y también el código (las instrucciones) que debe seguir.
Cuando la función o procedimiento se llama a sı́ misma, podemos pensar que
el programa automáticamente genera copias de la función (tantas como llama-
.a
das se hagan), con sus propios lugares para código y variables locales. Cuando
w
de compilar (como sucede con los arreglos), pues no puede saber de antemano
cuántas veces se usará la recursión. Por ejemplo, para calcular n!, se necesitan
unas n copias de la función: cambiando n cambiamos el número de copias ne-
cesarias. Este espacio de memoria especial se llama “stack” o “pila”, estructura
que veremos también en el problema 11.6 y un poco más formalmente en la
sección 12.1.
Ası́ como hemos usado recursión para una función con un único argumento,
no hay problemas en usar recursión con procedimientos o con varios argumentos,
como en los siguientes dos problemas.
Problema 11.2. Usando recursión, reescribir el algoritmo de Euclides (sec-
ción 5.2.2) para encontrar el máximo común divisor entre dos enteros positivos.
Sugerencia: mcd(a, b) = mcd(a − b, b) si a > b. ✄
m
Hacer un programa para escribir los factores de n ∈ N, usando este procedi-
o
.c
miento y comenzando con la llamada factorizar(n, 2).
Nota: Refiriéndonos a comentarios anteriores, observar que en el procedimiento
os
n y inic son llamados por valor y no referencia.
Nota: Comparar con los problemas 5.17 y 5.18.
r ✄
fo
.m
Problema 11.4. Hacer un programa implementando la clasificación por selec-
ción directa (pág. 98) como un procedimiento recursivo. Sugerencia: declarar
na
al procedimiento con n − 1. ✄
on
mapa, donde los segmentos son calles con direcciones oeste–este o sur–norte, y
dm
1 4 10 20 35
.a
w
w
1 3 6 10 15
w
1 2 3 4 5
1 1 1 1
Para resolver el problema, podemos pensar que para llegar a una intersección
hay que hacerlo desde el oeste o desde el sur (salvo cuando la intersección está en
el borde oeste o sur), y por lo tanto la cantidad de caminos para llegar a la
intersección es la suma de la cantidad de caminos llegando desde el oeste (si
se puede) más la cantidad de caminos llegando desde el sur (si se puede). Los
números en la figura 11.1 indican, para cada intersección, la cantidad de caminos
para llegar allı́ desde (0, 0) mediante movimientos permitidos.
a) Hacer un programa para calcular la cantidad de caminos para llegar desde
(0, 0) a (m, n), donde m y n son ingresados por el usuario.
b) En cursos de matemática discreta se demuestra que el número de caminos
es
(m + n)! (m + n) × (m + n − 1) × · · · × (m + 1)
c(m, n) = = .
m! n! n × (n − 1) × · · · × 1
m
Incorporar al programa del inciso anterior una función con el cálculo de
c(m, n) (por ejemplo, con un lazo) y comparar con el obtenido anteriormen-
o
te.
.c
c) Modificar el programa del inciso a) de modo de calcular la cantidad de
os
caminos cuando la intersección (r, s) está bloqueada y no se puede pasar
r
por allı́, donde r y s son ingresados por el usuario (0 < r < m y 0 < s < n).
fo
Sugerencia: poner c(r, s) = 0.
.m
d ) Supongamos ahora que, al revés del inciso anterior, para ir de (0, 0) a (m, n)
tenemos que pasar por (r, s) (por ejemplo, para llevar a (m, n) la pizza
na
que compramos en la esquina (r, s)). Hacer un programa para esta nueva
posibilidad. Sugerencia: puedo armar un camino de (0, 0) a (m, n) tomando
tu
(m, n).
e) De acuerdo al inciso b), la cantidad de caminos en el inciso d ) es c(r, s) ×
yc
m
disco más grande en la aguja b, habrá que poner los n − 1 restantes en la aguja
c primero, después colocar la n-ésima en la aguja b, y volver a poner los n − 1
o
.c
en la aguja b. Para mover los n − 1 de la aguja a a la aguja c, habrá que mover
n − 2 a la aguja b, pasar el n − 1 a la aguja c,. . .
os
a) Definir una función pasar (k, x, y, z) que pase k discos (con movimientos
r
fo
permitidos2 ) de la aguja x a la y usando la z, imprimiendo los discos en
cada aguja al terminar.
.m
Sugerencia: para k = 1 sólo hay que mover la aguja que está en la cima de
x hacia y, y para k > 1, podemos poner pasar (k, x, y, z) como consistente
na
begin
if (k > 1) then begin
.a
pasar(k-1, x, z, y);
pasar(1, x, y, z);
w
pasar(k-1, z, y, x)
w
end
else begin (* pasar una de x a y *)
w
m
Nota: La suposición que lo que tarda la computadora por movimiento es
o
independiente de n es una aproximación muy buena. Si es posible, compro-
.c
barla (tomando por ejemplo n = 19, 20, . . . ), sin imprimir los movimientos,
os
y estimar lo que tarda la computadora por movimiento en cada caso (del
orden de micro segundo, micro = dividir por un millón).
r
fo
Nota: En el problema 11.8 se da una variante para resolver el problema de
las torres de Hanoi sin usar arreglos.
.m
Nota: La estructura que estamos usando para los arreglos, agregando atrás y
na
Hay muchas variantes del problema de las torres de Hanoi, por ejemplo:
¿qué pasa si los discos no están inicialmente todos sobre una misma aguja
(pero respetan la distribución de mayor a menor)?, ¿qué pasa si hay más de
yc
tres agujas? ✄
dm
a) Resolver el problema.
Los números que aparecen en la solución de este problema se conocen como
números de Fibonacci, definidos recursivamente como:
a := 1; b := 1;
for i := 3 to n do begin c := a + b; b := a; a := c end;
fibonacci := a
m
máximo n tal que fn ≤ maxint . ✄
o
Leonardo Bigollo es el verdadero nombre de Leonardo de Pisa (1180–1250),
.c
también conocido como Fibonacci (contracción de las palabras “hijo de Bonac-
os
ci”).
Jacques Binet (1786–1856) publicó la fórmula para los números de Fibo-
r
nacci en 1843, pero ya habı́a sido publicada por Leonhard Euler (1707–1783)
fo
en 1765.
.m
Mucho después de Fibonacci, se observó que los números fn aparecen en
muy diversos contextos, algunos insospechados como en la forma de las flores
na
del girasol, y son de importancia tanto en las aplicaciones prácticas como teóri-
cas aún en la actualidad. Por ejemplo, han sido usados para resolver problemas
tu
var n: integer;
m
write(’pasar el disco 1’);
o
writeln(’ de "’, x, ’" a "’, y,’"’)
.c
end;
os
begin
r
writeln(’** Solucion recursiva de las torres de Hanoi:’);
fo
writeln(’ Pasar n discos de la aguja "a" a la "b"’);
.m
write(’ usando la "c", mediante movimientos ’);
writeln(’ permitidos.’);
na
writeln;
tu
writeln;
yc
√ n
(1 + 5)
√ .
2n 5
¿A partir de qué n son iguales?, ¿podrı́as decir por qué? ✄
Problema 11.10. Un resultado de computación teórica dice que toda función o
procedimiento recursiva puede reescribirse con lazos “ while ” y arreglos sin usar
recursión (que se usan como “pilas” para ir guardando los datos intermedios).
De hecho, hemos visto que muchos de los problemas de este capı́tulo se
pueden reescribir sin recursión con lazos “ for ”, como el factorial, el algoritmo
de Euclides, factorizar, selección directa o números de Fibonacci. ¿Podrı́as hacer
el problema 12.1, para generar las cadenas de bits, sin usar recursión (para n
variable) usando lazos “ while ”, “ repeat ” y/o “ for ”? ✄
Capı́tulo 12
Generando objetos
combinatorios
o m
.c
Muchos de los problemas de “conteo” que pueden resolverse usando recursión
os
en programación, también pueden resolverse mediante el uso de lazos como
“ for ” o “ while ”, por ejemplo calcular 2n o n!, y nos quedamos con la impresión
r
de que recursión no es muy útil. Otro problema muy distinto al de contar objetos
fo
es generarlos, por ejemplo para encontrar alguno o todos los que satisfacen cierto
.m
criterio, y aquı́ es donde recursión muestra toda su potencia.
Puesto que el número de objetos a generar puede ser muy grande, como
na
Las pilas, como las de platos, y las colas, como las de supermercados, son dos
.a
estructuras familiares que trataremos de formalizar un poco aquı́, más que nada
para poner en perspectiva lo que estamos haciendo y no siendo estrictamente
w
rigurosos.
w
Tanto en pilas como en colas podemos pensar que tenemos una serie de
w
objetos —todos del mismo tipo— esperando en lı́nea a ser tratados. En el caso
de la pila, o cola lifo, por “last in first out”, el último en llegar es el primero en
ser tratado. En cambio en lo que denominamos comunmente cola (sin aditivos),
o más formalmente cola fifo, por “first in first out”, el primero en llegar es el
primero en ser tratado.
No es difı́cil imaginar distintas acciones comunes:
Crear o inicializar la cola, construyendo la cola “vacı́a”.
Agregar un elemento, ubicándolo al final y actualizando la cola.
Quitar un elemento, tomando, según corresponda, el primero o el último
y actualizando la cola.
Destruir la cola, cuando no la necesitamos más, limpiando lo que ensu-
ciamos.
m
o destruir la cola, agregarle o quitarle un elemento, etc.
Sin embargo, la implementación de una cola como “fifo” o “lifo” puede te-
o
.c
ner consecuencias decisivas en los algoritmos, como veremos en el capı́tulo 13.
Además, en lenguajes no tan abstractos como Pascal, debemos tener cuidado en
os
la implementación de TDA’s. En el capı́tulo 14 introduciremos punteros y listas
r
encadenadas de Pascal, que son más apropiados para implementar colas, pero a
fo
fin de no complicar la presentación, por ahora “nos arreglaremos con arreglos”.
.m
Como sabemos, si queremos trabajar con un arreglo tenemos que declararlo
al comienzo, dando su dimensión y el tipo de elementos. Por lo tanto, si vamos a
na
necesitemos más.
Suponiendo que hemos declarado el tipo “ dato ”, que puede ser un registro
yc
Crear la pila:
w
procedure inicializar;
w
Quitar un elemento:
function pop: dato;
begin
if (npila > 0) then begin
pop := pila[npila]; npila := npila - 1
end
end;
m
Para colas (fifo), las cosas son ligeramente diferentes. En vez de tener un ı́ndi-
o
ce como en el caso de la pila, mantenemos dos: uno, digamos ppocola, señalando
.c
el principio de la cola en el arreglo, y otro, digamos fincola, señalando el final.
os
fincola se incrementa al agregar un dato, mientras que ppocola aumenta cuando
se extrae un dato, de modo que los elementos “vivos” en principio estarán entre
ppcola y fincola (inclusivo en ambos casos). r
fo
Claro que si la cola está definida como un arreglo de MAXC elementos,
.m
por ejemplo en el programa maximo (pág. 178). También hemos visto un atisbo
dm
del uso de pilas en el problema 11.6 de las torres de Hanoi, donde las agujas
eran pilas en sı́.
En la próxima sección veremos que la técnica de mezclar recursión con pilas
.a
puede usarse para generar distintos objetos combinatorios, aunque las pilas no
w
aparecerán explı́citamente.
w
w
begin
for i := 0 to 1 do begin
a[k] := i; (* poner 0 o 1 en el lugar k *)
if (k < n) then cadena(k+1) else begin
(* imprimir la cadena cuando k = n *)
write(’ ’);
for i := 1 to n do write(a[i]:1);
writeln
end (* if *)
end (* for *)
end;
Observar que usamos al arreglo a como una pila. Para k fijo, el elemento
en la posición k tiene un valor de 0 cuando i = 0, que se mantiene para va-
lores superiores a k pero luego es cambiado para i = 1, y obviamente cambia
varias veces cuando es llamado desde valores inferiores a k. Sin embargo, k es
m
el parámetro del procedimiento y no una variable global.
o
.c
a) Hacer una prueba de escritorio del procedimiento cuando n = 3.
b) Hacer un programa que dado n ∈ N imprima todas las cadenas de bits de
os
longitud n, siguiendo las indicaciones anteriores.
r
c) Agregar un contador (global) para contar las cadenas de longitud n, y ver
fo
que la cantidad de cadenas es 2n .
.m
que n es global. Esto contrasta con el uso de recursión de, por ejemplo, el
factorial, donde vamos “hacia atrás” disminuyendo el valor del parámetro en
on
Nota: En el problema 12.7 vemos otra forma de encontrar las cadenas de bits,
sin usar recursión. ✄
.a
construyen todos los subconjuntos de {1, 2, . . . , n}, ya que las cadenas de bits de
w
mediante (
1 si i ∈ A,
bi =
0 si no.
Es claro que dos conjuntos distintos tienen vectores caracterı́sticos distintos,
y que por otro lado, dada una cadena de bits podemos encontrar un conjunto
A tal que b(A) sea esa cadena. Por lo tanto, hay una correspondencia biunı́voca
entre cadenas de bits de longitud n y subconjuntos de {1, 2, . . . , n}.
Modificar el programa del problema 12.1 para que en vez de imprimir cadenas
de bits, imprima el subconjunto correspondiente en {1, 2, . . . , n}, representando
al conjunto vacı́o con una raya “ - ”.
Por ejemplo, si n = 2 la salida deberı́a ser algo como:
- 1 2 1 2 ✄
Una vez que sabemos cómo generar una familia de objetos, es más senci-
llo generar o contar objetos de la familia con caracterı́sticas particulares. Por
ejemplo:
Problema 12.3. Supongamos que queremos contar la cantidad c(n) de cadenas
de bits que no contienen dos 0’s sucesivos:
a) Hacer un programa para calcular c(n) para n ∈ N, usando el programa
del problema 12.1 y una variante de búsqueda lineal para encontrar dos
0’s consecutivos en cada cadena construida, verificando si se trata de una
cadena válida antes de aumentar el contador.
b) Comparar el número c(n) obtenido anteriormente con los números de Fibo-
nacci, y hacer un nuevo programa para calcular c(n) directamente. ✄
m
Problema 12.4. En este problema imprimiremos todos los caminos que hemos
contado en el problema 11.5. Para ello consideramos una arreglo global camino
o
en el que guardaremos las “intersecciones” o “esquinas” por las que hay que
.c
pasar. Como éstas tienen dos coordenadas, digamos x y y, declaramos
os
type esquina = record x, y: integer end;
r
fo
y
.m
camino: array[1..MAXK] of esquina;
donde MAXK es una constante para la máxima longitud del camino. En el pro-
na
blema original todos los caminos tienen longitud m+n, pero podrı́an ser distintas
tu
MAXK .
También consideramos a los datos m y n como variables globales, y agrega-
yc
mos la variable global k que indicará la longitud del camino construido. Inicial-
dm
var r: integer;
w
begin
w
k := k + 1;
with camino[k] do begin x := i; y := j end;
if (i < m) then llegardesde(i+1,j);
if (j < n) then llegardesde(i,j+1);
if ((i = m) and (j = n)) then (* llegamos *)
begin
(* imprimir *)
write(’ ’);
for r := 1 to k do
with camino[r] do
write(’ (’, x:1, ’,’, y:1, ’)’);
writeln
end; (* if i = m and j = n *)
k := k - 1
end;
Estudiemos este procedimiento, haciendo una “prueba de escritorio” en todo
caso:
• k , inicialmente en 0, se incrementa en 1 al entrar al procedimiento, y se
incorpora la intersección (i, j) al camino en la (nueva) posición k .
Es decir, k indica la cantidad de objetos en la pila camino. Al comienzo,
la pila está vacı́a, y se van incorporando elementos atrás.
A diferencia con el problema 12.1, k es global y no el parámetro del
procedimiento.
• Si i < m o j < n, se puede continuar (hacia la derecha o hacia arriba), por
lo que llamamos al procedimiento incrementando los valores respectivos.
Observar que no hay un “ else ”: queremos que cada caso contribuya con
un camino distinto.
m
• Si i = m y j = n, hemos llegado a la esquina deseada, e imprimimos el
o
camino.
.c
• Una vez recorridos los caminos que siguen hacia el este, o el norte, o impre-
os
so el camino, debemos retornar, borrando nuestras huellas para permitir
que la posición k pueda ser ocupada por otra esquina. Por eso se pone la
r
instrucción “ k := k - 1 ” al terminar el procedimiento.
fo
En otras palabras, al terminar quitamos de la pila camino el elemento
.m
Hay 3 caminos
w
cuando m = 2 y n = 1. ✄
m
end (* if *)
o
end (* for *)
.c
else (* k = n: nueva permutacion *)
os
begin
(* buscar y agregar el que falta *)
j := 0;
r
fo
repeat j := j + 1 until falta[j];
.m
a[k] := j;
(* imprimir *)
na
write(’ ’);
for j := 1 to n do write(a[j]:3);
tu
writeln
on
end
end;
yc
para nosotros.
w
a) Usando las ideas anteriores, hacer un programa para generar todas las per-
mutaciones de n elementos.
b) En el procedimiento poner , al buscar los elementos que faltan se pone
if (k < n) then
for j := 1 to n do begin
if (falta[j]) then begin
.
.
.
end (* if *)
end (* for *)
m
¿Qué función cumple el par begin–end que encierra a “ if ” en este
o
.c
caso?, ¿es redundante?
¿Serı́a equivalente poner
os
if (k < n) then begin
for j := 1 to n do
r
fo
if (falta[j]) then begin
.m
.
.
.
na
end (* if *)
end (* if *) ?
tu
de orden.
Nota: Hay varios algoritmos para generar las permutaciones de {1, . . . , n}, algu-
.a
nos bastante diferentes al que presentamos, y otros más eficientes. Observar que
w
nivel 0
nivel 1
nivel 2
nivel 3
o m
.c
el peor caso n − 1 pasos. La diferencia entre estas dos estructuras, la de árbol
os
binario ordenado por un lado y la lineal, es la misma que entre búsqueda binaria
y búsqueda lineal.
r
Una estructura como la de la figura 12.1 se llama árbol binario ordenado, y
fo
el nodo de más arriba (en el nivel 0) se llama raı́z. Cada nodo está conectado
.m
Problema 12.6. El programa arbolbinario (pág. 182), que toma ideas de [6,
pág. 210] y de [3, pág. 153], muestra la construcción de un árbol binario ordenado
.a
usando arreglos.
w
• Cada nodo guardará cuatro datos: la llave, la cantidad de veces que apa-
reció, y cómo encontrar a sus hijos. Por lo tanto necesitamos un registro
con 4 campos.
m
binario que primero se fija si se está en una posición “vacı́a”, i.e. sin
datos, en cuyo caso se agrega un nodo al árbol, se guarda el dato, se
o
.c
coloca cuenta en 1, y se inicializan los hijos a nada pues están vacı́os.
os
Si la posición no está vacı́a, se pregunta si el dato es el mismo que se
guarda en ese nodo, en cuyo caso se incrementa cuenta, o si debe ir a la
izquierda en caso que sea menor o a la derecha.
r
fo
.m
los subárboles).
Créase o no, ya hemos visto y recorrido árboles binarios ordenados con an-
terioridad, aunque tenı́an una estructura particular que nos permitı́a ahorrar
espacio.
Por ejemplo, en el problema 12.1 construı́amos todas las cadenas de bits
de longitud n o, equivalentemente, los subconjuntos de {1, . . . , n}. Dada una
cadena c de bits de longitud k, podemos considerar el “hijo a izquierda” como
la cadena c0 de longitud k + 1 que se obtiene de c agregando a la derecha un 0, y
de la misma forma considerar el “hijo a derecha” como la cadena c1 de longitud
k + 1 que se obtiene agregando un 1. En la función recursiva del problema 12.1
el nivel que estábamos construyendo era precisamente k, y cuando llegábamos
a k = n, imprimı́amos la cadena.
Nota: También hay “árboles” en los problemas 12.4 y 12.5, sólo que los nodos
pueden tener más de dos hijos. A diferencia del problema de los subconjun-
tos o cadenas de bits, el “recorrido” del árbol es más complicado porque la
descripción de los nodos lo es.
La variable k del procedimiento llegardesde o en el procedimiento poner
indica también el nivel que estamos recorriendo. Las instrucciones “ k := k
- 1 ” y “ falta[j] := true ”, respectivamente, hacen que vayamos “un nivel
hacia arriba” antes de descender nuevamente, y por eso la técnica de “borrar las
huellas” que usamos en ambos casos es conocida como backtracking o “rastreo
inverso”.
m
la lista de coeficientes e imprimirla. ✄
o
.c
Problema 12.8 (Problema del Viajante). Supongamos que un viajante
os
tiene que recorrer n ciudades (exactamente) volviendo a la de partida, sin repetir
su visita a ninguna (salvo la inicial), y que el costo de viajar desde la ciudad i
r
a la ciudad j es cij ≥ 0. El problema del viajante es encontrar una permutación
fo
(a1 , a2 , . . . , an ) de (1, 2, . . . , n) de modo que el costo total del recorrido, ca1 a2 +
.m
ca2 a3 + · · · + can−1 an + can a1 , sea mı́nimo. Como se recorre un ciclo, es suficiente
tomar a1 = 1.
na
a) Usando una variante del programa del problema 12.5, hacer un programa
tu
y
Pponer
P inicialmente min a un valor muy grande e inalcanzable como 1 +
i j cij . A medida que se van recorriendo las permutaciones, calcular
el costo de la permutación y compararlo con min, cambiando min y opt
.a
b) Ver que en el caso i) del inciso anterior, el costo es el mismo para cualquier
w
permutación.
c) ¿Podrı́as decir cuál es el óptimo en el caso ii) para cualquier n? Sugerencia:
podemos pensar que los puntos 1, . . . , n están sobre una recta y los costos
son las distancias.
Nota: En los ejemplos i–iii) los costos son simétricos, i.e. cij = cji . Como
recorrer un ciclo en uno u otro sentido no cambia el costo, estamos trabajando
(al menos) el doble de lo necesario con la propuesta.
El problema del viajante y otros similares son sumamente importantes y
se aplican a problemas de recorridos en general. Por ejemplo, para “ruteo” de
vehı́culos, máquinas para perforar o atornillar, circuitos impresos y “chips”,
etc., donde el costo puede medirse en dinero, tiempo, longitud, etc. Conse-
cuentemente, hay toda una área de las matemáticas y computación dedicada al
estudio de su resolución eficiente. Hasta el momento no se conocen algoritmos
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 13
Grafos y árboles
m
Si tomamos un conjunto de ciudades y las carreteras que las unen, y re-
presentamos gráficamente a las ciudades como puntos y a las carreteras como
o
segmentos o curvas uniendo esos puntos, obtenemos una figura similar a la fi-
.c
gura 11.5, en la cual pensábamos que los segmentos eran calles y los puntos las
os
intersecciones.
La idea subyacente en ambos casos es la de grafo, un conjunto de nodos o
r
fo
vértices (las ciudades o intersecciones) que indicaremos por V , y un conjunto
.m
de aristas (las rutas o calles), E, cada una de las cuales queda definida por dos
nodos.
na
dos costas), y las islas y las costas se unı́an por siete puentes. Euler resolvió el
problema, demostrando que no se podı́an recorrer todos los puentes pasando
una única vez por ellos, demostrando el teorema que hoy llamamos “de grafos
eulerianos” y que mencionamos en el problema 13.7.
En este capı́tulo veremos algunas propiedades básicas y algunos algoritmos
elementales para grafos. Nos contentaremos con dar una simple, y seguramen-
te demasiado breve, descripción de los términos y propiedades que usaremos,
dejando para los cursos de matemática discreta las definiciones rigurosas y las
demostraciones.
Nota: Lamentablemente no hay una nomenclatura ni notación uniforme de los
muchos conceptos asociados a grafos, de modo que las nuestras pueden diferir
de las de otros autores.
A fin de distinguir los nodos entre sı́ es conveniente darles nombres, pero
E = {{1, 2}, {1, 3}, {2, 3}, {2, 6}, {3, 4}, {3, 6}, {4, 6}}.
m
2 1
o
.c
r os
fo
3 6
.m
na
tu
4 5
on
yc
Pág. 131
Sólo consideraremos grafos simples, para los que no hay una arista de un
nodo en sı́ mismo, ni aristas paralelas uniendo los mismos nodos. En este caso,
m
podemos relacionar n = |V | y m = |E|: si hay n elementos, hay c(n, 2) =
n(n − 1)/2 subconjuntos de 2 elementos, de modo que m ≤ c(n, 2).
o
.c
Si además el grafo es conexo (y simple), como se puede unir un nodo con los
n − 1 restantes, debe haber al menos n − 1 aristas. De modo que para un grafo
os
(simple) conexo, m tiene que estar básicamente entre n y n2 .
r
A veces se consideran grafos dirigidos o digrafos, en los que las aristas están
fo
orientadas, y por lo tanto se indican como (a, b), y se distingue entre (a, b) y
.m
(b, a). Nosotros no estudiaremos este tipo de grafos, aunque en realidad hemos
trabajado con el grafo de la figura 11.5 como si fuera un digrafo: las calles sólo
na
Como hemos dicho, un árbol es un grafo (simple) conexo y sin ciclos, pero hay
on
b) G es conexo y |E| = n − 1.
w
en el problema 12.6.
Por supuesto, podemos pensar que los nietos son hijos de los hijos, los hijos
padres de los nietos, etc., de modo que —en un árbol con raı́z— hablaremos
de padres, hijos, ascendientes y descendientes de un nodo. La raı́z será el único
nodo sin ascendientes, mientras que habrá uno o más nodos sin descendientes.
También es común referirse al conjunto de descendientes de un nodo (aunque
el nodo no sea la raı́z) como una rama del árbol.
m
capı́tulo nos limitaremos a dos: dar la lista de aristas, y dar la matriz de adya-
o
cencias, una matriz cuyas entradas son sólo 0 o 1 y de modo que la entrada ij
.c
es 1 si y sólo si {i, j} ∈ E.
os
La representación mediante matriz de adyacencias es cómoda, y relativa-
mente fácil de entender. Quizás serı́a más eficiente —para los algoritmos que
r
veremos— dar para cada nodo una lista de sus vecinos. Esta tercera forma
fo
puede implementarse mediante arreglos, pero es mucho más natural usar listas
.m
encadenadas que veremos en el próximo capı́tulo, especialmente en el proble-
ma 14.9.
na
const
MAXN = 20; (* maximo numero de nodos *)
on
type
dm
var
w
a) Hacer un procedimiento para leer las aristas ingresadas por terminal, donde
cada arista consta de dos números enteros, formando un arreglo de longitud
mgrafo.
Nota: Como es usual, supondremos que el usuario ingresa correctamente
los datos: los vértices de las aristas son enteros entre 1 y ngrafo, no hay
aristas repetidas o de la forma {i, i}, y no hay más de MAXM aristas.
b) Considerando el procedimiento dearistasaadyacencias ,
procedure dearistasaadyacencias;
var i, j, k: integer;
begin
for i := 1 to ngrafo do
for j := 1 to ngrafo do
adyacencias[i,j] := 0;
for k := 1 to mgrafo do
with aristasgrafo[k] do begin
m
adyacencias[i,j] := 1;
o
adyacencias[j,i] := 1
.c
end
os
end;
hacer un programa que lea el número de nodos, las aristas (como en el inciso
r
fo
anterior), calcule la matriz de adyacencias, e imprima para cada nodo, los
nodos que son adyacentes. Por ejemplo, si la entrada son las aristas {1, 5},
.m
Nodo Vecinos
1 5
tu
2 4 5
on
3
4 2
yc
5 1 2
dm
anterior. ✄
w
w
Problema 13.3 (Grado de vértices). Dado un grafo G = (V, E), para cada
nodo v ∈ V se define su grado o valencia, δ(v), como la cantidad de aristas que
w
m
asignación y con la indentación implı́citamente señalamos un par “begin-end” o
o
“comienzo-fin” para agrupar instrucciones.
.c
os
Algoritmo visitar
r
fo
Entrada: un grafo G = (V, E), y un nodo i0 ∈ V .
.m
Salida: el mismo grafo con los nodos que se pueden alcanzar des-
de i0 “visitados”.
na
comienzo
Q ← {i0 };
tu
mientras Q 6= ∅ hacer
on
sea i ∈ Q;
sacar i de Q;
yc
“visitar” i;
para todo j adyacente a i hacer
dm
si j no está “visitado” y j ∈
/ Q entonces agregar j a Q
fin
.a
w
Cuadro 13.1: esquema del algoritmo visitar para recorrer todos los nodos a los
w
m
En el recorrido “a lo ancho” o “ancho primero”, visitamos primero el nodo
o
i0 , luego sus vecinos, luego los vecinos de los vecinos, etc., sin volver a visitar a
.c
alguien ya visitado. O sea que, pensando en el árbol que se formará, visitaremos
os
primero la raı́z, después todos sus hijos, después todos sus nietos, etc., en el mis-
mo orden en que los vamos encontrando. Es entonces conveniente implementar
r
fo
la cola Q como cola fifo (como en la sección 12.1): el que llegó antes sale antes.
.m
Problema 13.4 (Recorrido a lo ancho). Implementamos el algoritmo en el
programa anchoprimero (pág. 185), y observamos que:
na
ngrafo.
w
m
En general, aún cuando el grafo sea un árbol binario ordenado (y con raı́z
o
.c
1), el recorrido a lo ancho es distinto de los recorridos “en orden”, “pre orden” o
“post orden” que hemos visto en el problema 12.6, pues ahora visitamos los veci-
os
nos de la raı́z, luego los vecinos de los vecinos, etc., en otras palabras, visitamos
el árbol por niveles.
r
fo
Problema 13.5. Agregar instrucciones al programa arbolbinario de modo de
.m
mentamos como lifo (pila), visitaremos primero los nodos que se han incorpo-
rando más recientemente a la pila. Si el grafo fuera ya un árbol, resultará que
yc
primero visitaremos toda una rama hasta el fin antes de recorrer otra3 , lo que
dm
pila (cola lifo) en vez de una cola fifo, para realizar el recorrido en profundidad
primero. Si la entrada es la misma que en el problema 13.4 (agregando las aristas
w
{3, 5} y {4, 5}), y se sigue el orden de los naturales (al listar los vecinos de un
nodo), la salida debe ser 1, 2, 6, 4, 5, 3. ✄
Problema 13.7. Un célebre teorema de Euler dice que un grafo tiene un ciclo
que pasa por todas las aristas exactamente una vez, llamado ciclo de Euler,
si y sólo si el grafo es conexo y el grado de cada vértice es par (recordar el
problema 13.3).
Modificar el programa anchoprimero para que a la salida determine también
si el grafo tiene o no un ciclo de Euler usando el teorema.
Nota: Un problema muy distinto es encontrar un ciclo de Euler en caso de
existir. Esto es lo que hacemos en el problema 14.10. ✄
3 Bah, que nos vamos por las ramas.
m
2 3 Arista e peso we
{1,2} 2
o
{1,4}
.c
2 1
3
1 {1,5} 8
os
2
1 {2,3} 2
{2,4}
1 1 6
r 1
fo
{3,4} 1
{3,5}
.m
2
3 3 {3,6} 1
na
{4,6} 1
8
4 5 {5,6} 3
tu
on
más barata). Éste es el problema del camino más corto: en un grafo pesado, y
dados dos nodos s, t ∈ V , encontrar un camino (v0 = s, v1 , . . . , vk = t) con peso
.a
X
k
w
i=1
Observar que el valor de k no está fijo: no nos interesa si tenemos que usar
una ruta o cien, sólo nos interesa que la distancia total para ir de s a t sea
mı́nima.
Por ejemplo, en el grafo de la figura 13.3 podemos usar varios caminos para
ir del vértice 1 al 5: el camino (1, 5) usa una única arista (k = 1) y tiene peso 8,
el camino (1, 2, 3, 5) usa 3 aristas y tiene costo total 2 + 2 + 1 = 5, y en realidad
no hay otro con menor costo.
Tal vez el algoritmo más conocido para resolver este problema sea el de
Dijkstra, que sigue la estructura del algoritmo visitar : se comienza desde un
nodo, en este caso s, se lo coloca en una cola, y se visitan los nodos de la cola.
E. W. Dijkstra (1930–2002) nació y murió en Holanda. Fue uno de los más
grandes intelectos que contribuyeron a la lógica matemática subyacente en los
m
Al visitar un nodo i y examinar un nodo vecino j, verificamos si
o
di + w{i,j} < dj . (13.1)
.c
os
Si esta desigualdad es válida, quiere decir que el camino más corto para ir desde
s a j (sólo por nodos ya visitados) es ir desde s a i con el camino para i, y luego
r
usar la arista {i, j}. Por lo tanto, actualizamos dj poniendo dj = di + w{i,j} .
fo
También agregaremos j a la cola si no se ha agregado aún.
.m
usa de forma esencial que we > 0 para todo e ∈ E. Nosotros dejaremos estas
propiedades para cursos de matemática discreta o teorı́a de grafos.
yc
repetir
sea i ∈ Q tal que di = mı́nj∈Q dj ;
w
si i 6= t entonces
w
sacar i de Q;
w
dj ← di + w{i,j} ;
• Las aristas se guardan, como antes, en registros que ahora tienen un campo
más, w , para guardar el peso correspondiente.
o m
• La distancia desde s al nodo i, usando como nodos intermedios sólo nodos
.c
ya visitados, se indica por dist i .
os
• Usaremos una matriz costos, similar a la de adyacencias y que se construye
en forma análoga, poniendo
r
fo
(
w{i,j} si {i, j} ∈ E,
.m
costos ij =
∞ en otro caso.
na
(
0 si i = s,
w
dist i =
∞ en otro caso,
w
w
y (
s si i = s,
padre i =
0 en otro caso.
2. La cola de nodos a visitar se guarda en el arreglo avisitar , que inicial-
mente sólo contiene al nodo s.
3. En el lazo principal, se busca en la cola y se coloca al final el vértice
con menor valor de dist , que será el próximo nodo a visitar. El pro-
cedimiento para encontrar el mı́nimo y modificar la cola es similar al
proceso de selección directa (pág. 98, ver también el problema 8.2.e)).
4. Una vez que determinamos el nodo a visitar, i, lo comparamos con t,
el nodo al cual queremos llegar. Si i = t, hemos llegado y se termina
el procedimiento.
m
ra 13.3, nodo de partida 1 y nodo de llegada 4.
o
b) Cambiar el programa de modo que calcule las distancias de s a todos los
.c
otros nodos del grafo.
os
c) Modificarlo de modo que calcule todas las distancias de i a j, para 1 ≤ i <
j < ngrafo.
r
fo
Nota: Técnicamente el tipo de cola que usamos en los programas dijkstra y prim
.m
—en la próxima sección— se llama cola de prioridad, en vez de “lifo” o “fifo”:
no se elige ni el primero ni el último sino que se usa otro criterio “de valor” o
na
“prioridad” en la elección.
Nuestra implementación de este tipo de colas es un tanto rudimentaria a
tu
elección.
dm
y prim— lo más parecidas entre sı́ a fin de resaltar las semejanzas, lo que tam-
w
bién afecta (un poquito) a la eficiencia. Es muy posible que el lector los vea
con un “disfraz” bastante distinto en otras referencias. ✄
w
las aristas que sacamos son bastante arbitrarias, en general hay muchos árboles
generadores de un mismo grafo.
Cuando hay pesos (o costos) asociados a las aristas, como en el caso de la
construcción de las carreteras, nos interesa encontrar entre todos los árboles ge-
neradores uno que minimice la suma de los pesos de las aristas que lo componen,
X
we ,
e en el árbol
llamado árbol generador mı́nimo (es posible que haya más de un árbol con esta
propiedad, por ejemplo si los costos de todas las aristas son 1).
Por ejemplo, volviendo al grafo de la figura 13.3, podemos formar un árbol
generador con las aristas {1, 2}, {1, 4}, {2, 3}, {3, 6}, {6, 5} (siempre un árbol ge-
nerador debe tener n − 1 aristas), con peso total 2 + 3 + 2 + 1 + 3 = 11, y si
reemplazamos cambiamos la arista {5, 6} por la arista {3, 5}, reducimos el costo
m
en 1.
Hay varios algoritmos para encontrar un árbol generador mı́nimo, y nosotros
o
.c
veremos aquı́ el debido a Prim, pues sigue la estructura del algoritmo visitar,
siendo por lo tanto muy similar al recorrido a lo ancho o al algoritmo de Dijkstra
os
para el camino más corto. Otro algoritmo, más eficiente en la mayorı́a de los
r
casos prácticos, es el de Kruskal, que dejamos para la sección de Problemas
fo
Adicionales pues su implementación es más elaborada.
.m
El algoritmo de Kruskal fue publicado en 1956, mientras que el de Prim fue
publicado en 1959. Kruskal también obtuvo en forma independiente el algoritmo
na
Recordemos que en el algoritmo visitar mantenı́amos una cola con los nodos
on
a visitar, y se formaban tres clases de nodos: los visitados, los que estaban en
la cola, y los que nunca habı́an ingresado en la cola.
yc
nodos de la cola. De modo que hay que hacer pocas modificaciones al algoritmo
de Dijkstra: cambiar la definición de la distancia y, en vez de construir el cami-
.a
tenemos que mantener información sobre los nodos visitados. Como el arreglo
w
padre se irá modificando en los sucesivos pasos (en el Dijkstra también, pero
no en el recorrido a lo ancho), introducimos un nuevo vector con valores lógicos
w
visitado.
Nota: Como con el algoritmo de Dijkstra, se necesita que we > 0 para todo
e ∈ E. Asimismo, dejamos la demostración de que el algoritmo da efectivamente
un árbol generador mı́nimo para los cursos de matemática discreta o teorı́a de
grafos.
procedure arbolminimo;
var
i, j, k, kmin, hay: integer;
d, dmin: costo;
avisitar: arreglodevertices;
visitado: array[1..MAXN] of boolean;
o m
begin
.c
(* inicializacion *)
dearistasacostos;
os
for i := 1 to ngrafo do begin
padre[i] := 0;
r
fo
dist[i] := infinito;
.m
visitado[i] := false
end;
na
(* 1 es la "raiz" *)
tu
avisitar[1] := 1;
yc
j := avisitar[k]; d := dist[j];
if (d < dmin) then begin
w
end;
w
(* examinar vecinos de i *)
for j := 1 to ngrafo do
if ((not visitado[j]) and
(costos[i,j] < dist[j])) then begin
(* si j no se agrego, agregarlo a la cola *)
m
• A la salida, debemos imprimir el árbol encontrado y un cartel en caso que
o
el grafo original no sea conexo. Esto puede hacerse modificando las partes
.c
correspondientes de los programas anchoprimero o dijkstra.
os
a) Hacer un programa para implementar el algoritmo de Prim usando las ideas
r
fo
expuestas. En el grafo de la figura 13.3, se obtiene el árbol
b) Incluir también instrucciones a fin de imprimir el peso total del árbol resul-
.m
tante.
na
se aparta un poco del esquema del algoritmo visitar, y más bien se acerca al
w
denominado algoritmo del codicioso pues busca incorporar la mejor arista de las
que quedan.
Antes de describir el algoritmo es conveniente introducir el concepto de com-
ponente conexa (o simplemente componente cuando quede claro que es la co-
nexa): dado un grafo G = (V, E), vamos a dividir, o más correctamente, par-
ticionar, el conjunto de nodos V en subconjuntos, digamos A1 , A2 , . . . , Ac , que
llamaremos componentes conexas de G, de modo que dos nodos u, v ∈ V están
en la misma componente si (y sólo si) existe un camino de u a v. De esta forma
resulta que las componentes A1 , A2 , . . . , Ac satisfacen:
1. V = ∪1≤i≤c Ai .
Dado u ∈ V , podemos encontrar todos los nodos que se conectan con él
mediante el algoritmo visitar (pág. 134), encontrando ası́ la componente a la
cual pertenece. Claro que si el grafo G es conexo, hay una única componente
(c = 1), y recı́procamente.
Nota: A fin de describir el algoritmo de Kruskal, consideramos que las compo-
nentes conexas son conjuntos de nodos. En otras variantes de la definición de
componente conexa, también se incluyen las aristas que unen a los nodos.
En el algoritmo de Kruskal, que esquematizamos en el cuadro 13.2, dado el
grafo G = (V, E) vamos construyendo por etapas un grafo (V, F ), con F ⊂ E,
examinando ordenadamente cada arista de E para decidir si la agregaremos o
no a F .
En el algoritmo, conservamos en cada etapa las componentes conexas de
(V, F ) en C, e indicamos mediante mediante Ci a la componente conexa que tiene
al nodo i. Inicialmente no habrá aristas, es decir, tendremos F = ∅, Ci = {i}
para i ∈ V , y C = {Ci }i∈V .
m
Todas las aristas en el grafo original se ponen en una cola, Q, y en cada paso
o
extraemos de Q la arista de menor peso, digamos e = {i, j}. Si i y j están en
.c
la misma componente conexa (hasta ese momento), i.e. si Ci = Cj , pasamos a
la próxima arista. En cambio, si Ci 6= Cj , podemos unir las componentes Ci y
os
Cj mediante e formando una nueva componente conexa, y guardamos e en el
conjunto F .
r
fo
En principio terminaremos cuando no haya más aristas en la cola Q, pero si
.m
(V, F ) serán las mismas, C, y H será el grafo de menor peso con esta propiedad.
on
Claro que si hay una única componente conexa (de G o de H porque coinciden),
H será un árbol generador mı́nimo de G.
yc
Algoritmo de Kruskal
m
sea e = {i, j} una arista de menor peso en Q;
o
sacar e de Q;
si Ci 6= Cj entonces
.c
C ← Ci ∪ Cj ;
os
eliminar Ci y Cj de C y agregar C a C;
Ci ← C; Cj ← C;
r
fo
F ← F ∪ {e}
.m
fin
na
g(r0 ) = r. Observemos que una vez que un nodo i deja de ser representante
dm
na otra, o bien cada vez que se unió con otra resultó que elegimos a i como
w
m
mente si tenemos en cuenta que g se usará para buscar, dado i, al representante
de Ci con el procedimiento iterativo de find , y nos interesa mantener el número
o
.c
de iteraciones lo más bajo posible.
Si una componente C tiene como representante a r, llamemos `(r) al número
os
máximo de iteraciones (del lazo “ while ” en el procedimiento find) para cual-
r
quier nodo de esa componente C. Inicialmente tendremos `(r) = 0 para todo r,
fo
pero este valor se irá modificando a medida que fusionemos componentes.
Cuando se juntan las componentes C y C 0 con representantes r y r0 , y
.m
(
g(r0 ) = g(r) si `(r0 ) < `(r),
w
Nota: Esta elección hace que el procedimiento find no haga más de dlog 2 |V |e
iteraciones.
Manteniendo el arreglo long (definido globalmente por simplicidad) para
guardar los valores de `(r), podemos escribir el procedimiento para juntar dos
componentes como:
procedure union(r, rp: integer);
var l, lp: integer;
begin
l := long[r]; lp := long[rp];
if (lp < l) then begin
repre[rp] := r;
if (l < lp + 1) then long[r] := lp + 1
end
else begin
repre[r] := rp;
if (lp < l + 1) then long[rp] := l + 1
end
end;
Por completitud, incluimos la inicialización de la estructura “union-find”:
procedure inicializaruf;
var i: integer;
begin
for i := 1 to ngrafo do begin
repre[i] := i; long[i] := 0 end
end;
En resumen, para implementar “union-find” necesitamos tres funciones o
m
procedimientos, inicializaruf , find y union, y dos arreglos, repre y long, que
o
por simplicidad hemos considerado como globales.
.c
Ya estamos en condiciones de implementar el algoritmo, con las siguientes
os
observaciones
• Luego habrá que declarar las funciones de “union-find”, como hemos des-
w
cripto anteriormente.
procedure arbolminimo;
var
hay, ri, rj, k, kmin: integer;
wmin : costo;
e: tipoarista;
begin
(* inicializacion *)
inicializaruf; (* inicializar union-find *)
nconexas := ngrafo; (* numero de componentes *)
marbol := 0; (* en el bosque no hay aristas *)
hay := mgrafo; (* cantidad de aristas a examinar *)
m
y sacarla de la pila *)
o
kmin := hay;
.c
e := aristasgrafo[hay];
wmin := e.w;
os
for k := 1 to hay - 1 do
with aristasgrafo[k] do
r
fo
if (w < wmin) then begin
.m
kmin := k; wmin := w end;
if (kmin < hay) then begin
na
e := aristasgrafo[kmin];
aristasgrafo[kmin] := aristasgrafo[hay]
tu
end;
on
hay := hay - 1;
yc
(* distintas componentes *)
(* agregar arista a arbol *)
w
marbol := marbol + 1;
w
aristasarbol[marbol] := e;
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Capı́tulo 14
Punteros y listas
encadenadas
o m
.c
La lista encadenada surge de la necesidad de evitar la rigidez de la estructura
os
de arreglo. Recordemos que los arreglos tienen una dimensión predefinida (en
el programa fuente) que a veces resultará excesiva o escasa, dependiendo de la
r
aplicación. También es muy difı́cil eliminar o agregar un elemento intermedio,
fo
pues debemos hacer un corrimiento de varios valores del arreglo (por ejemplo,
.m
14.1. Punteros
dm
Recordemos que las variables que hemos usado hasta ahora ocupan un lugar
de memoria, que será más grande o más chico dependiendo de su tipo, y que
.a
pensamos como “cajas”. Para reconocer una variable le hemos dado un nombre,
w
su identificador, que tienen sentido para nosotros, pero para la máquina —una
w
p := nil
Podemos interpretar a nil como “un cable a tierra”, que indica que no hay
“nada”. Recordemos que justamente hemos usado con el mismo sentido a la
variable nada en el programa arbolbinario (pág. 182).
A pesar de que, habiéndolo declarado, p ocupa un lugar de memoria, ese
lugar es sólo para guardar direcciones, y puede no coincidir con la cantidad de
espacio necesaria para guardar, por ejemplo, un entero. Si además queremos
reservar un lugar para guardar un entero, usamos “ new ”:
new(p)
m
Esta instrucción hace que se reserve un lugar en memoria —la famosa “ca-
ja”— para alojar un dato de tipo entero (en este caso), y guarda en p la dirección
o
de ese lugar, dejando a la caja disponible pero sin valor asignado.
.c
os
Nota: Es posible que no haya más memoria para reservar, cosa improbable con
las computadoras actuales y los programas que nosotros usaremos, por lo que
r
no nos preocuparemos por esta eventualidad. En programación más “seria”
fo
también debemos preocuparnos por dejar libres los lugares obtenidos mediante
.m
“ new ”, que en Pascal se hace mediante “ dispose ” (que no veremos).
La variable (entera) a la que ahora “apunta” p se indica por pˆ. Ası́, si
na
p^ := 3;
on
p := n (* incorrecto! *)
w
pero si p y q son punteros del mismo tipo (declarado mediante “ type ”), es legal
hacer
q := p
aún cuando p o q no referencien dato alguno, ya que estamos copiando en q la
dirección guardada en p. Por otro lado, supuesto que p y q sean del mismo tipo
y que ambos se hayan “inicializado” con new , podemos copiar los contenidos de
una caja a la otra mediante
q^ := p^
Nuestro interés en este tipo de variables es en la creación de listas encade-
nadas.
m
el tipo “ ptrnodo ” sólo puede definirse si está definido el tipo “ nodo ”, que a
su vez sólo puede definirse si está definido el tipo “ ptrnodo ”. Sin embargo esta
o
recursión es un tanto aparente pues las direcciones de memoria siempre ocupan
.c
el mismo lugar (que dependerá de la computadora y sistema operativo). Ası́, la
os
reserva de espacio para “ ptrnodo ” es siempre la misma, independientemente
del espacio que deba reservarse para una variable de tipo “ nodo ”.
r
fo
Las listas encadenadas tienen asociados un puntero donde se guarda la di-
rección donde comienza la lista. En nuestra implementación consideraremos que
.m
todos los nodos de la lista tienen información3 , por lo que la lista “vacı́a” tiene
na
Nota: Para una descripción más detallada, ver [6, pág. 194].
Suponiendo que lista es la variable declarada como “ ptrnodo ” que guarda
dm
lista := nil
w
w
pero puede ser que querramos construir la lista de otra forma, por ejemplo
si queremos que esté ordenada.
Si queremos agregar siempre atrás, es conveniente guardar también la di-
rección del último nodo de la lista, y debe preverse en la inicialización.
1 A veces, para confundir, también hablamos de listas cuando nos referimos a arreglos.
2 Como en grafos. En realidad, las listas encadenadas se pueden pensar como grafos diri-
gidos.
3 Nuestra presentación difiere de otras donde siempre hay al menos un nodo.
o m
No podemos eliminar el nodo pˆ si no conocemos su antecesor (salvo que
.c
sea el primero de la lista). En este caso habrá que recorrer la lista hasta
encontrar pˆ, guardando información sobre los antecesores.
os
Recorrer la lista. Es bastante sencillo en nuestro caso (donde no hay nodos
r
fo
especiales para el principio o fin de lista):
.m
p := lista;
while (p <> nil) do begin
na
p := p^.siguiente
end
on
yc
formar una lista ordenada por id , y el recorrido de una lista, para buscar una
información o imprimir. Al final del listado del programa damos un ejemplo de
.a
Problema 14.3. Hacer un procedimiento para construir una copia de una lista
encadenada, es decir, una lista con tantos nodos como la original, y en la cual
la información se guarda en el mismo orden que la lista original.
Nota: Observar que si a y b son arreglos del mismo tipo (que se ha declarado
con “ type ”), para copiar a en b se puede hacer simplemente la asignación “ b
:= a ”, pero con listas encadenadas tenemos que recorrer todos los nodos. ✄
m
que el nodo que estaba en primer lugar esté al final en la nueva lista y viceversa,
o
etc., sin crear nuevos nodos (i.e. sin usar “ new ”).
.c
Sugerencia: ir formando una nueva lista en la que se incorpora el primer nodo
os
de la vieja, borrándolo de la vieja, y continuar hasta terminar la lista original.
Sugerencia si la anterior no alcanza: usar el esquema
r
fo
procedure darvuelta(var lista: ptrnodo);
.m
var p, q: ptrnodo;
begin
na
p := q; q := q^.siguiente;
p^.siguiente := lista; lista := p
on
end
end;
yc
Nota: El problema 8.3 hacı́a lo mismo para un vector, pero la técnica es muy
dm
imprimir.
w
Problema 14.5 (El problema de Flavio Josefo II). Implementar una solu-
w
ción a este problema (problema 6.5) usando en vez de arreglos una lista circular,
w
i.e. el “último” nodo se encadena al “primero”, donde cada nodo representa una
persona, y la eliminación se traduce en eliminar el nodo de la lista. El programa
debe imprimir la permutación de Flavio Josefo.
Sugerencia: crear una lista quedan de los que “quedan”, inicialmente con los va-
lores 1, . . . , n, donde el último nodo apunta al primero en vez de a nil . A medida
que se eliminan nodos de quedan, ir formando la permutación de Flavio Josefo.
En cada paso de “eliminación”, guardar información sobre el nodo anterior al
eliminado, para eliminar el nodo y poder contar m de los que quedan a partir
de él. ✄
Problema 14.6. Siguiendo las ideas del problema 14.4 y del procedimiento
ordenada en el programa listas (pág. 192), hacer un procedimiento para ordenar
(por alguna llave) una lista ya ingresada sin generar nuevos nodos. ✄
m
de señalar a otras listas, que pueden tener el mismo tipo de registros o no.
Si por ejemplo en el programa listas en vez de tener los nodos un campo
o
para punteros, tenemos dos, y llamamos al primero “izquierdo” y al segundo
.c
“derecho”, obtenemos otra implementación de los árboles binarios ordenados
os
que vimos en la sección 12.6, reemplazando la “pila” del arreglo por listas en-
cadenadas.
r
fo
Problema 14.8.
.m
a) Declarando
na
type
tu
ptrnodo = ^nodo;
nodo = record
on
Nota: Para eliminar este problema, se han diseñado una serie de variantes,
como árboles balanceados, árboles “B”, etc. Observar que sacar nodos de
un árbol es un proceso bastante más complicado que sacar nodos de una
lista (ver [6, Cap. 4]). ✄
renglón. Los editores de texto trabajan con una estructura similar (escribiendo
luego secuencialmente en el disco cuando guardamos el archivo).
Un problema matemático donde se puede usar una idea similar es en grafos,
al tener la descripción por lista de aristas o por nodos adyacentes a un nodo
dado.
Problema 14.9. Como hemos visto en el capı́tulo anterior, un grafo G consiste
de un conjunto de nodos o vértices V = {1, 2, . . . , n}, y un conjunto de aristas
E, donde cada arista es de la forma {u, v} con u, v ∈ V , u 6= v (y supondremos
que no hay aristas repetidas).
Es usual representar los nodos mediante puntos y las aristas con segmentos
o curvas que unen los nodos, como se muestra en la figura 13.1 (pág. 130), en
donde n = 6 y E = {{1, 2}, {1, 3}, {2, 3}, {2, 6}, {3, 4}, {3, 6}, {4, 6}}.
Por otra parte, en vez de describir G mediante E (y V ), podrı́amos hacerlo
a partir de las listas de adyacencias o vecinos: para cada u ∈ V el conjunto de
nodos {v ∈ V : {u, v} ∈ E}. En el ejemplo anterior, podrı́amos poner:
o m
1 → {2, 3}, 2 → {1, 3, 6}, 3 → {1, 2, 4, 6}, 4 → {3, 6}, 5 → ∅, 6 → {2, 3, 4}.
.c
Para representar el grafo en la computadora usando listas encadenadas4,
os
podemos recurrir a las estructuras:
r
fo
Para lista de aristas: Una lista de aristas, donde cada arista se re-
presenta mediante
.m
ptrarista = ^arista;
na
ptrvecino = ^vecino;
yc
ptrvertice = ^vertice;
vertice = record
.a
id: integer;
w
s: ptrvertice;
vecinos: ptrvecino
w
end;
w
Para demostrar que un grafo conexo con todos los nodos de grado par tiene
un ciclo de Euler, se comienza a partir de un nodo y se van recorriendo aristas y
borrándolas hasta que volvamos al nodo, formando un ciclo. Esto debe suceder
porque todos los nodos tienen grado par. Puede ser que el ciclo no cubra a
todas las aristas, pero como el grafo es conexo, debe haber un vértice en el ciclo
construido que tenga una arista (aún no eliminada) incidente en él, a partir del
cual podemos formar un nuevo ciclo, agregarlo al anterior y continuar con el
procedimiento hasta haber eliminado recorrido todas las aristas.
Esta demostración es bien constructiva, y podemos implementar los ciclos
como listas encadenadas. La implementación de aristas, etc. puede hacerse con
arreglos o matrices.
Hacer un programa para decidir si un grafo entrado por n, la cantidad de
vértices, y en caso de ser conexo y con todos los vértices de grado par, imprimir
un ciclo de Euler. ✄
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Apéndice A
Programas mencionados
m
Problema 2.2: holamundo (pág. 7)
o
program holamundo(input, output);
.c
(* primer programa *)
os
begin
r
fo
writeln(’Hola Mundo!’);
writeln;
.m
writeln(’y Chau!’)
na
end.
tu
var a, b: integer;
.a
begin
writeln(’** Programa para sumar dos numeros enteros’);
w
writeln;
w
a := 1;
w
b := 2;
write(’La suma de ’, a);
write(’ y ’, b);
writeln(’ es ’, a + b);
writeln; writeln(’** Fin **’)
end.
var a: integer;
begin
writeln(’** Programa para leer un dato entero’);
writeln;
write(’Entrar un entero: ’); readln(a);
writeln;
writeln(’El entero leido es ’, a);
writeln; writeln(’** Fin **’)
end.
m
Obtener la raiz cuadrada de un numero real x.
o
Se supone que x no es negativo.
.c
*)
os
var x, y: real;
r
fo
begin
.m
writeln(’** Calcular la raiz cuadrada de x **’);
writeln;
na
writeln;
on
end.
dm
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.
begin
write(’** Programa para pasar de segundos’);
writeln(’ a horas, minutos y segundos **’);
writeln;
write(’Entrar la cantidad de segundos: ’); readln(segs);
writeln;
writeln(segs, ’ segundos son equivalentes a ’);
mins := segs div 60; segs := segs mod 60;
m
hs := mins div 60; mins := mins mod 60;
writeln(hs, ’ hs, ’, mins, ’ mins, ’, segs, ’ segs.’);
o
writeln; writeln(’** Fin **’)
.c
end.
r os
Problema 3.16: positivo (pág. 17)
fo
.m
program positivo(input, output);
(* dice si un numero entero es positivo *)
na
var a: integer;
tu
on
begin
writeln(’Ver si un numero entero es positivo’);
yc
writeln;
write(’Entrar un numero: ’); readln(a);
dm
writeln;
writeln(’es positivo?: ’, a > 0);
.a
begin
writeln(’ ** Pasar de caracter a numero y viceversa **’);
write(’ Entrar un caracter: ’); readln(c);
writeln;
i := ord(c);
writeln(’ El numero de orden de ’, c, ’ es ’, i:1);
(* i:1 indica que escribira i en un solo espacio,
var x, y: real;
begin
m
writeln(’** Encontrar el valor absoluto de un numero real’);
o
writeln;
.c
write(’Entrar el numero: ’); readln(x);
os
if (x >= 0) then y := x else y := -x;
writeln;
writeln(’El valor absoluto de ’, x, ’ es ’, y);
r
fo
writeln; writeln(’** Fin **’)
.m
end.
na
var a, b: integer;
dm
begin
.a
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.
var c: char;
begin
write(’** Decide si un caracter es una letra ’);
writeln(’mayuscula o minuscula o no es letra **’);
write(’ Entrar un caracter: ’); readln(c); writeln;
write(c);
if (’a’ <= c) and (c <= ’z’) then
writeln(’ es una letra minuscula’)
else if (’A’ <= c) and (c <= ’Z’) then
writeln(’ es una letra mayuscula’)
else
m
writeln(’ no es una letra’);
o
writeln; writeln(’** Fin **’)
.c
end.
os
Problema 4.9: resto (pág. 26)
r
fo
program resto(input, output);
.m
var a, b, r: integer;
on
begin
writeln(’** Hallar el resto de la division entre a y b,’);
yc
writeln;
r := a;
w
while (r >= b) do r := r - b;
writeln;
write(’El resto de dividir ’, a:1);
writeln(’ por ’, b:1, ’ es ’, r:1);
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;
m
write(’Entrar el valor final (en grados): ’);
o
readln(final);
.c
write(’Entrar el valor de incremento (en grados): ’);
readln(incremento);
os
writeln;
r
fo
writeln(’Angulo Seno’);
.m
grados := inicial;
while (grados <= final) do begin
na
end;
yc
(* sumar 1 + 2 +...+ n *)
w
w
var
n: integer;
suma: real; (* ponemos real por si es muy grande *)
begin
writeln(’** Sumar 1 + 2 +...+ n **’);
writeln;
write(’ Entrar n (maxint = ’, maxint, ’): ’); readln(n);
suma := 0;
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 *)
m
E.g. 246 -> 3, -908 -> 3, pero 0 -> 1 *)
o
.c
var a, b, c: integer;
os
begin
r
fo
writeln(’Determina la cantidad de cifras de un entero’);
writeln(’(sin tener en cuenta el signo)’);
.m
writeln;
na
c := 0;
yc
repeat
dm
c := c + 1;
b := b div 10
until b = 0;
.a
w
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);
m
Problema 4.15: potencia (pág. 30)
o
.c
program potencia(input, output);
os
(* calculo de potencias con exponente natural *)
r
var
fo
x, pot: real;
.m
n, i: integer;
na
begin
tu
pot := 1;
for i := 1 to n do pot := pot * x;
.a
w
writeln;
w
var a: integer;
begin
writeln(’** Prueba del funcionamiento de eoln’);
writeln;
m
var
o
s, x: real;
.c
findatos: boolean;
os
begin
r
fo
writeln(’** Calcular la suma de los datos entrados’);
writeln(’indicando el fin de datos con doble <retorno>’);
.m
writeln;
na
repeat
write(’Entrar un dato (fin = <retorno>): ’);
on
until (findatos);
dm
writeln;
writeln(’La suma de los datos es ’, s);
.a
w
end.
w
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 de un numero’);
writeln(’ real positivo "a" **’); writeln;
(* datos de entrada *)
write(’ Entrar a: ’); readln(a); writeln;
(* inicializacion *)
m
it := 1; x := x0; y := (x + a/x)/2;
o
.c
(* lazo principal *)
while ((it <= itmax) and (abs(x - y) > tol)) do
os
begin x := y; y := (x + a/x)/2; it := it + 1 end;
r
fo
(* salida *)
.m
writeln(’Iteraciones realizadas: ’, it);
if (it > itmax) then
na
(* fin *)
writeln; writeln(’** Fin **’)
yc
end.
dm
*)
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);
(* lazo principal *)
while (b <> 0) do begin r := a mod b; a := b; b := r end;
(* salida *)
writeln(’El maximo comun divisor es: ’, a:2);
(* fin *)
writeln; writeln(’** Fin **’)
end.
o m
Problema 5.18: factoresprimos (pág. 46)
.c
os
program factoresprimos(input, output);
r
(* Encontrar factores primos de un entero positivo *)
fo
.m
var
a, b, s, t: integer;
na
tesprimo: boolean;
tu
begin
on
(* carteles *)
writeln(’Encontrar los factores primos del entero a > 1’);
yc
writeln;
dm
(* datos de entrada *)
write(’Entrar a (a > 1): ’); readln(a); writeln;
.a
t := a; tesprimo := false; b := 2;
w
repeat
w
s := trunc(sqrt(t));
while (((t mod b) <> 0) and (b <= s)) do b := b+1;
if (b > s) then tesprimo := true
else begin (* b es factor de t y de a *)
write(b:1, ’, ’);
t := t div b
end;
until tesprimo;
writeln(t:1);
(* fin *)
writeln; writeln(’** Fin **’)
end.
var
dato, digito: integer;
terminaen: array[0..9] of integer;
begin
writeln(’** Contar cuantos de los numeros ingresados’);
writeln(’ terminan en 0,1,...,9’);
m
writeln;
o
(* inicializar el arreglo *)
.c
for digito := 0 to 9 do terminaen[digito] := 0;
os
(* entrar datos *)
r
fo
write(’Entrar un entero (fin = <retorno>): ’);
while (not eoln) do begin
.m
readln(dato);
na
readln;
yc
(* salida *)
dm
writeln;
writeln(’ Digito numero de datos’);
.a
for digito := 0 to 9 do
writeln( digito:4, terminaen[digito]:20);
w
w
end.
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
m
write(’Entrar el dato ’, ndatos:1);
o
write(’ (fin = <retorno>): ’);
.c
if (eoln) then begin (* no hay mas datos *)
findatos := true; readln end
os
else begin (* nuevo dato *)
r
readln(a[ndatos]); (* incorporarlo al arreglo *)
fo
ndatos := ndatos + 1 (* para el proximo *)
.m
end
until (findatos);
na
ndatos := ndatos - 1;
tu
(* imprimir el arreglo *)
on
write(a[i]:10);
if ((i mod 5) = 0) then writeln
dm
end;
if ((ndatos mod 5) <> 0) then writeln;
.a
writeln;
w
(* elemento a buscar *)
w
writeln;
(* lazo principal *)
i := 0;
repeat
i := i + 1;
seencontro := (a[i] = x)
until (seencontro or (i = ndatos));
(* resultados y fin *)
if (seencontro)
then writeln(’Se encontro en la posicion ’, i:1)
else
writeln(’No se encontro’);
var
p, i, cuenta, k: integer;
esprimo: array[2..MAXP] of boolean;
o m
begin
.c
writeln(’** Criba de Eratostenes para encontrar’);
os
writeln(’ los primos entre 1 y ’, MAXP:1);
writeln;
r
fo
(* inicializacion *)
.m
(* lazo principal *)
for i := 2 to MAXP do
on
k := p + p;
while (k <= MAXP) do begin
esprimo[k] := false;
.a
k := k + p
w
end
w
end;
w
(* salida y fin *)
write(’ Se encontraron ’, cuenta:1);
writeln(’ primos que no superan ’, MAXP:2);
writeln; writeln(’** Fin **’)
end.
var
m, n, i, vuelta, cuenta: integer;
vive: array[1..MAXN] of boolean;
flavio: array[1..MAXN] of integer;
begin
(* carteles *)
writeln(’** Problema de Flavio Josefo **’); writeln;
writeln(’En un circulo de n, se van eliminando de a m’);
writeln;
(* datos *)
write(’Entrar n: ’); readln(n);
write(’Entrar m: ’); readln(m); writeln;
o m
(* inicializacion *)
.c
for i := 1 to n do vive[i] := true; (* aun vive *)
i := 0;
r os
(* parte principal *)
fo
for vuelta := 1 to n-1 do begin
.m
(* en cada vuelta, i es el ultimo eliminado *)
cuenta := 0; (* contamos m sobrevivientes desde i *)
na
repeat
if (i <> n) then i := i + 1 else i := 1;
tu
flavio[vuelta] := i
end;
dm
(* encontrar el sobreviviente *)
.a
(* salida y fin *)
w
var
n, m, k: integer;
x, y, xn, ym: real;
begin
(* carteles iniciales *)
writeln(’** Comparar x a la n con y a la m’);
writeln;
(* entrar datos *)
write(’Entrar x (real): ’); readln(x);
write(’Entrar n (entero): ’); readln(n);
write(’Entrar y (real): ’); readln(y);
write(’Entrar m (entero): ’); readln(m);
o m
(* calcular x a la n, y a la m *)
.c
xn := 1; for k := 1 to n do xn := xn * x;
ym := 1; for k := 1 to m do ym := ym * y;
r os
(* comparar e imprimir *)
fo
write(x:10:5, ’ a la ’, n:1);
.m
if (xn < ym) then write(’ es menor que ’)
else if (xn > ym) then write(’ es mayor que ’)
na
(* cartel final *)
writeln; writeln(’** Fin **’)
yc
end.
dm
x a la n con y a la m.
*)
var
n, m: integer;
x, y, xn, ym: real;
end;
begin
(* carteles iniciales *)
writeln(’** Comparar x a la n con y a la m’);
writeln;
(* entrar datos *)
write(’Entrar x (real): ’); readln(x);
write(’Entrar n (entero): ’); readln(n);
write(’Entrar y (real): ’); readln(y);
write(’Entrar m (entero): ’); readln(m);
(* calcular x a la n, y a la m *)
xn := potencia(x,n); ym := potencia(y,m);
o m
(* comparar e imprimir *)
.c
write(x:10:5, ’ a la ’, n:1);
if (xn < ym) then write(’ es menor que ’)
os
else if (xn > ym) then write(’ es mayor que ’)
else write(’ es igual a ’);
r
fo
writeln(y:10:5, ’ a la ’, m:1);
.m
(* cartel final *)
na
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;
begin
(* carteles *)
writeln(’* Metodo de biseccion para encontrar raices *’);
writeln;
write(’Implementacion para ’);
writeln(’f(x) = x (x + 1) (x + 2) (x - 4/3)’);
writeln;
o m
(* datos de entrada *)
.c
write(’ Entrar cota inferior para x: ’); readln(poco);
write(’ Entrar cota superior para x: ’); readln(mucho);
os
writeln;
r
fo
(* inicializacion *)
.m
ypoco := f(poco); ymucho := f(mucho);
seguir := (ypoco * ymucho < 0);
na
iter := 0;
tu
(* lazo principal *)
on
x := (poco + mucho) / 2;
y := f(x);
dm
end;
w
w
(* salida *)
if (iter > 0) then (* hay una solucion, aceptable o no *)
begin
writeln(’ Solucion obtenida: ’, x);
writeln(’ Valor de f: ’, y);
writeln(’ Iteraciones realizadas: ’, iter:1);
if (abs(y) > dify) then begin
writeln;
write(’* La respuesta puede no estar ’);
writeln(’cerca de una raiz *’)
end
end;
(* fin *)
writeln; writeln(’** Fin **’)
end.
const pi = 3.14159265;
procedure cartelesiniciales;
m
begin
o
writeln(’Hacer una tabla del seno dando valores’);
.c
writeln(’inicial, final, y del incremento (en grados).’);
writeln
os
end;
r
fo
procedure leerdatos;
.m
begin
write(’Entrar el valor inicial (en grados): ’);
na
readln(inicial);
write(’Entrar el valor final (en grados): ’);
tu
readln(final);
on
writeln
end;
dm
procedure imprimirtabla;
.a
var
grados: integer;
w
radianes: real;
w
begin
w
writeln(’Angulo Seno’);
grados := inicial;
while (grados <= final) do begin
radianes := grados * pi/180;
writeln(grados:5, sin(radianes):15:5);
grados := grados + incremento
end
end;
procedure cartelfinal;
begin writeln; writeln(’** Fin **’) end;
begin
cartelesiniciales;
leerdatos;
imprimirtabla;
cartelfinal
end.
var a, b: integer;
m
var t: integer;
o
begin t := x; x := y; y := t end;
.c
os
begin
a := 5; b := 2;
r
fo
writeln(’Inicialmente: a = ’, a:2, ’, b = ’, b:2);
swapincorrecto(a,b);
.m
end.
tu
on
var
w
p, (* posicion en el renglon *)
n (* numero de caracteres en el renglon *)
: integer;
t (* el caracter de maximo ordinal *)
: char;
renglon (* el renglon a procesar *)
: tiporenglon;
n := 0;
while ((not eoln) and (n < MAXC)) do begin
n := n + 1; read(s[n]) (* aca es read y no readln *)
end;
readln (* para leer el ultimo fin de linea *)
end;
procedure max(
m
var t: tiporenglon;
o
long: integer;
.c
var tmax: char;
var indice: integer);
os
var i: integer;
begin
r
(* suponemos long positivo *)
fo
tmax := t[1]; indice := 1;
.m
for i := 2 to long do
if (t[i] > tmax) then begin
na
begin
(* titulo *)
yc
writeln;
.a
(* entrada *)
leerrenglon(renglon, n);
w
w
(* verificacion *)
w
(* procesamiento *)
max(renglon, n, t, p);
(* salida y fin *)
writeln; write(’El maximo es "’, t, ’",’);
writeln(’ alcanzado en la posicion ’, p:1);
var
archivo: text;
c: char;
nombre: string;
begin
m
(* carteles *)
writeln(’** Copiar de consola a archivo **’);
o
writeln;
.c
os
(* nombre de archivo a escribir *)
writeln(’ Entrar el nombre del archivo a escribir’);
r
fo
writeln(’ Atencion: si ya existe se borra!’);
write(’ Archivo: ’); readln(nombre);
.m
rewrite(archivo, nombre);
na
writeln;
write(’Entrar datos en ’, nombre);
on
until eoln;
readln; writeln(archivo)
.a
end;
w
close(archivo);
w
w
(* Fin *)
writeln; writeln(’** Fin **’)
end.
var
archivo: text;
c: char;
nombre: string;
begin
(* carteles *)
writeln(’** Copiar de archivo a consola **’);
writeln;
m
while not eof(archivo) do
o
if (eoln(archivo)) then begin
.c
readln(archivo); writeln end
else begin
os
read(archivo, c); write(c) end;
r
fo
close(archivo);
.m
(* Fin *)
na
(*
Simular tirar un dado.
.a
*)
w
w
var d: integer;
begin
writeln(’** Simular tirar un dado **’);
writeln;
randomize;
d := 1 + random(6);
writeln(’El dado que salio es ’, d:1);
writeln;
writeln(’** <retorno> para Fin **’); readln
end.
begin
writeln(’** Contar las veces que se tira un dado’);
writeln(’ hasta que aparece un numero prefijado **’);
m
writeln;
o
write(’Entrar el valor que debe aparecer ’);
.c
write(’(entre 1 y 6): ’);
os
readln(numero);
r
fo
randomize;
tiros := 0;
.m
writeln;
write(’El numero ’, numero:1);
on
writeln;
dm
(*
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.
const
MAXN = 100; (* maximo numero de datos a guardar *)
nada = 0; (* para cuando no hay hijos *)
type
indice = integer; (* para senialar los arreglos *)
tipodato = integer; (* el tipo de datos a ingresar *)
nodo = record
llave: tipodato; (* dato *)
cuenta: integer; (* veces que aparecio *)
izquierda: indice; (* hijo a la izquierda *)
derecha: indice (* hijo a la derecha *)
end;
tipoarbol = array[0..MAXN] of nodo;
var
narbol: integer; (* numero de nodos en el arbol *)
arbol: tipoarbol; (* el arbol! *)
m
raiz: indice; (* donde empieza el arbol *)
o
dato: tipodato; (* dato leido *)
.c
function entrardatos(var n: tipodato): boolean;
os
begin
r
write(’Entrar el numero (fin = <retorno>): ’);
fo
if (eoln) then
.m
begin entrardatos := false; readln end
else
na
begin
if (p = nada) then begin (* nueva entrada *)
dm
narbol := narbol + 1;
p := narbol;
.a
end
w
end
else begin
mayormenor := x - arbol[p].llave;
if (mayormenor = 0) then (* dato existente *)
arbol[p].cuenta := arbol[p].cuenta + 1
else if (mayormenor < 0) then (* ir a la izquierda *)
binario(x, arbol[p].izquierda)
else (* ir a la derecha *)
binario(x, arbol[p].derecha)
end
end;
begin
if (w <> nada) then
with arbol[w] do begin
enorden(izquierda);
writeln(llave:10, cuenta:20);
enorden(derecha)
end
end;
begin
(* carteles *)
writeln(’** Arbol binario **’); writeln;
write(’Entrar numeros enteros y contar’);
writeln(’ las veces que aparecieron’);
writeln;
o m
(* inicializacion *)
.c
narbol := 0;
raiz := nada;
r os
(* lectura de datos y construccion del arbol *)
fo
while (entrardatos(dato)) do binario(dato, raiz);
.m
(* salida y fin *)
na
enorden(raiz);
yc
Ejemplo de Salida
.a
** Arbol binario **
w
w
Impresion en orden:
3 1
** Fin **
m
arista, es un vertice aislado.
o
.c
Salida: las aristas del arbol y el orden en que
fueron visitados los nodos.
os
Si el arbol no es conexo, se imprime un mensaje.
r
fo
Implementacion con una cola fifo de nodos a visitar.
.m
*)
na
const
MAXN = 20; (* maximo numero de nodos *)
tu
type
yc
var
w
procedure entrardatos;
begin
(* nodos *)
writeln;
write(’Entrar el numero de nodos: ’); readln(ngrafo);
(* aristas *)
writeln; writeln(’Entrar las aristas:’);
mgrafo := 1;
m
while (nuevodato) do mgrafo := mgrafo + 1;
o
mgrafo := mgrafo - 1
.c
end;
os
procedure escribiraristas( a: arreglodearistas; m: integer);
r
const maxrenglon = 10; (* maxima cantidad por renglon *)
fo
var k: integer;
.m
begin
for k := 1 to m do begin
na
end;
on
procedure dearistasaadyacencias;
dm
var i, j, k: integer;
begin
.a
for i := 1 to ngrafo do
for j := 1 to ngrafo do
w
adyacencias[i,j] := 0;
w
for k := 1 to mgrafo do
w
procedure ancho;
var
i, j, ppo, fin: integer;
avisitar: arreglodevertices;
begin
(* inicializacion *)
dearistasaadyacencias;
m
(* examinar vecinos, preservando el orden *)
o
for j := 1 to ngrafo do
.c
if ((adyacencias[i,j] > 0) and (padre[j] = 0)) then
begin (* agregar j a la cola *)
os
padre[j] := i;
fin := fin + 1;
r
fo
avisitar[fin] := j;
.m
end (* if adyacencias > 0 *)
end (* while *)
na
end;
tu
procedure hacerarbol;
on
begin
marbol := 0;
dm
marbol := marbol + 1;
with aristasarbol[marbol] do begin
w
i := k; j := padre[k] end
w
end
w
end;
procedure escribirorden;
const maxrenglon = 10; (* maxima cantidad por renglon *)
var i: integer;
begin
for i := 1 to narbol do begin
write(orden[i]:5);
if ((i mod maxrenglon) = 0) then writeln
end;
if ((narbol mod maxrenglon) <> 0) then writeln
end;
begin
(* Carteles *)
writeln(’** Recorrer un grafo a lo ancho para determinar’);
writeln(’ un arbol de expansion entrando las aristas.’);
writeln(’ La busqueda se hace en el orden de los enteros,’);
writeln(’ tomando como raiz a 1.’); writeln;
(* Datos e inicializacion *)
entrardatos;
writeln; writeln(’ Las aristas originales son:’);
escribiraristas( aristasgrafo, mgrafo);
(* Procesamiento y salida *)
ancho;
hacerarbol;
m
writeln; writeln(’ Las aristas del arbol resultante son:’);
o
escribiraristas(aristasarbol, marbol);
.c
writeln;
writeln(’Los nodos se visitaron en el siguiente orden:’);
os
escribirorden;
if (narbol < ngrafo) then begin
r
fo
writeln; writeln(’** El grafo no es conexo **’) end;
.m
(* Fin *)
na
en un grafo pesado.
w
const
MAXN = 20; (* maximo numero de nodos *)
type
costo = integer; (* podria ser real *)
arreglodevertices = array[1..MAXN] of integer;
tipoarista = record
i, j: integer; (* los extremos *)
w: costo (* el costo *)
end;
arreglodearistas = array[1..MAXM] of tipoarista;
matrizNN = array[1..MAXN,1..MAXN] of costo;
var
ngrafo, mgrafo, s, t: integer;
infinito: costo;
m
padre: arreglodevertices;
o
dist: array[1..MAXN] of costo;
.c
aristasgrafo: arreglodearistas;
costos: matrizNN;
r os
function nuevodato: boolean;
fo
begin
.m
writeln;
write(’ Entrar la arista ’, mgrafo:2);
na
nuevodato := true;
with aristasgrafo[mgrafo] do begin
yc
read(i);
write(’ Entrar el segundo vertice: ’);
dm
read(j);
write(’ Entrar el costo (entero > 0): ’);
.a
readln(w);
infinito := infinito + w
w
end (* with *)
w
procedure entrardatos;
begin
(* nodos *)
writeln;
write(’Entrar el numero de nodos: ’);
readln(ngrafo);
(* aristas *)
writeln;
writeln(’Entrar las aristas:’);
mgrafo := 1; infinito := 1;
m
end;
o
.c
procedure dearistasacostos;
var i, j, k: integer;
os
begin
for i := 1 to ngrafo do
r
fo
for j := 1 to ngrafo do
.m
costos[i,j] := infinito;
for k := 1 to mgrafo do
na
end
on
end;
yc
procedure mascorto;
var
dm
avisitar: arreglodevertices;
w
begin
w
(* inicializacion *)
w
dearistasacostos;
for i := 1 to ngrafo do begin
padre[i] := 0; dist[i] := infinito end;
(* examinar vecinos de i *)
for j := 1 to ngrafo do
if (dist[i] + costos[i,j] < dist[j]) then begin
(* si j no se agrego, agregarlo a la cola *)
if (dist[j] = infinito) then begin
hay := hay + 1; avisitar[hay] := j end;
(* actualizar dist y padre *)
m
dist[j] := dist[i] + costos[i,j];
o
padre[j] := i
.c
end
end (* if i <> t *)
os
until ((i = t) or (hay = 0))
end;
r
fo
.m
procedure hacercamino;
(* Construir e imprimir el camino mas corto usando padre. *)
na
i, ncamino: integer;
on
camino: arreglodevertices;
begin
yc
ncamino := 1; camino[1] := t; i := t;
repeat
dm
i := padre[i];
ncamino := ncamino + 1;
.a
camino[ncamino] := i
until (i = s);
w
begin
(* Carteles *)
writeln(’** Busqueda del camino mas corto entre dos’);
writeln(’ vertices con el algoritmo de Dijkstra.’);
writeln(’ El grafo se entra mediante lista de aristas.’);
(* Datos e inicializacion *)
entrardatos;
writeln; writeln(’Las aristas del grafo son:’);
escribiraristas(aristasgrafo, mgrafo);
writeln; writeln(’Los nodos a unir son:’);
writeln(’ partida: ’, s:1);
writeln(’ llegada: ’, t:1);
(* Procesamiento y salida *)
mascorto;
writeln;
if (padre[t] > 0) then begin
writeln(’ La longitud del camino es ’, dist[t]:1);
hacercamino
end
m
else begin
o
writeln;
.c
writeln(’*** ’, s:1, ’ y ’ , t:1, ’ no se conectan ***’)
end;
r os
(* Fin *)
fo
writeln; writeln(’** Fin **’)
.m
end.
na
(*
Listas encadenadas con punteros: con los datos ingresados
yc
type
info = record
nombre: string;
id: integer
end;
ptrnodo = ^nodo;
nodo = record
info: info;
siguiente: ptrnodo
end;
var
nuevainfo: info;
listaadelante, listaordenada: ptrnodo;
m
with datos do begin
o
readln(nombre);
.c
write(’Entrar numero de id para "’, nombre, ’": ’);
readln(id)
os
end
end
r
fo
else begin entrardatos := false; readln end
.m
end;
na
var p: ptrnodo;
begin
dm
new(p);
p^.info := nuevainfo; p^.siguiente := lista;
.a
lista := p
end;
w
w
m
(* cambiar a < para antes *)
o
do p := p^.siguiente;
.c
if (p^.info.id > nuevainfo.id)
(* cambiar a >= para antes *)
os
then insertaradelante(p, r)
else (* p es el ultimo de la lista *)
r
fo
insertardetras(p, r)
.m
end
end;
na
var p: ptrnodo;
on
begin
if (lista = nil) then writeln(’ Lista vacia’);
yc
p := lista;
while (p <> nil) do begin
dm
end
end;
w
w
begin
w
(* carteles *)
writeln(’** Prueba de listas encadenadas **’);
writeln;
(* inicializacion de listas *)
listaadelante := nil;
listaordenada := nil;
(* imprimir listas *)
writeln; writeln(’Las listas son:’); writeln;
(* Fin *)
writeln; writeln(’** Fin **’)
end.
Ejemplo de Salida
m
** Prueba de listas encadenadas **
o
.c
Entrar el nombre (fin = <retorno>): Geri
os
Entrar numero de id para "Geri": 2
Entrar el nombre (fin = <retorno>): Pablito
r
fo
Entrar numero de id para "Pablito": 1
Entrar el nombre (fin = <retorno>): Guille
.m
Robin Hood 3
Guille 2
.a
Pablito 1
Geri 2
w
w
Pablito 1
Geri 2
Guille 2
Robin Hood 3
** Fin **
listas de vecinos.
Siempre n es dato.
type
ptrarista = ^arista;
arista = record u, v: integer; s: ptrarista end;
ptrvecino = ^vecino;
vecino = record id: integer; s: ptrvecino end;
ptrvertice = ^vertice;
m
vertice = record
o
id: integer;
.c
s: ptrvertice;
vecinos: ptrvecino
os
end;
r
fo
var
.m
ngrafo: integer;
aristas: ptrarista;
na
vertices: ptrvertice;
tu
(***************************************
on
Aristas
***************************************)
yc
begin
writeln;
.a
nuevaarista := true;
w
read(x);
write(’ Entrar el segundo vertice: ’);
readln(y)
end (* if not eoln *)
else begin nuevaarista := false; readln end
end;
aristas := parista
end;
procedure entrararistas;
(* inicializar e ingresar aristas *)
var x, y: integer;
begin
aristas := nil;
while nuevaarista(x,y) do agregararista(x,y)
end;
procedure escribiraristas;
const maxrenglon = 10; (* maxima cantidad por renglon *)
var
k: integer;
m
p: ptrarista;
o
begin
.c
if (aristas = nil) then writeln(’** lista vacia’)
else begin
os
p := aristas; k := 0;
repeat
r
fo
k := k + 1;
.m
with p^ do write(’ (’, u:1, ’,’, v:1, ’)’);
if ((k mod maxrenglon) = 0) then writeln;
na
p := p^.s
until (p = nil)
tu
end;
on
(***************************************
dm
procedure inicializarvecinos;
w
var
w
k: integer;
w
p: ptrvertice;
begin
vertices := nil;
(* la construimos "al reves" para que quede ordenada *)
for k := ngrafo downto 1 do begin
new(p);
with p^ do begin
id := k; s := vertices; vecinos := nil end;
vertices := p
end (* for k *)
end;
m
procedure escribirvecinos;
o
var
.c
pvertice: ptrvertice;
pvecino: ptrvecino;
os
begin
(* adyacencias *)
r
fo
pvertice := vertices;
.m
while (pvertice <> nil) do begin
write(pvertice^.id:5, ’ -> ’);
na
pvecino := pvertice^.vecinos;
if (pvecino = nil) then write(’ {}’)
tu
else
on
repeat
write(pvecino^.id:3);
yc
pvecino := pvecino^.s
until (pvecino = nil);
dm
writeln;
pvertice := pvertice^.s
.a
end
end;
w
w
(***************************************
w
De aristas a vertices
***************************************)
procedure dearistasaadyacencias;
(* construir listas de adyacencias
recorriendo la lista de aristas *)
var parista: ptrarista;
begin
parista := aristas;
while parista <> nil do begin
with parista^ do begin
agregarvecino(u, v);
agregarvecino(v, u)
end;
(* proxima arista *)
parista := parista^.s
end
end;
(**************************************
Parte principal
**************************************)
begin
(* carteles *)
writeln(’** Pasar de aristas a adyacencias usando’);
writeln(’ listas encadenadas.’); writeln;
m
(* Inicializacion y entrada de datos *)
o
(* vertices *)
.c
write(’ Entrar n (la cantidad de vertices): ’);
readln(ngrafo);
os
inicializarvecinos; writeln;
r
fo
(* aristas *)
.m
entrararistas;
na
(* imprimir resultados *)
writeln; writeln(’** Resultados **’); writeln;
yc
(* fin *)
w
end.
w
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Apéndice B
m
B.1. Resumen de operadores
o
.c
B.1.1. Operadores aritméticos
os
Operador operación operando resultado
+ (unitario) identidad
r
entero o real mismo que operando
fo
- (unitario) inversión de signo entero o real mismo que operando
.m
+ (binario) suma entero o real entero o real
- (binario) resta entero o real entero o real
na
m
odd (∗) si impar entero lógico
o
round (†) redondeo real entero
.c
sin seno real o entero real
os
sqr cuadrado real o entero real o entero
sqrt raı́z cuadrada real o entero (≥ 0) real
trunc (‡) r
truncar
fo
real entero
.m
(∗)
odd (x) es verdadero si x es impar.
na
(†)
round (x) = trunc(x + 0.5).
(
bxc si x ≥ 0,
tu
(‡)
trunc(x ) =
dxe si x < 0.
on
Otras funciones
w
Procedimientos
w
Apéndice C
Algunas notaciones y
sı́mbolos usados
o m
.c
Ponemos aquı́ algunas notaciones, abreviaturas y expresiones usadas (que
os
pueden diferir de algunas ya conocidas), sólo como referencia: el lector deberı́a
mirarlo rápidamente y volver cuando surja alguna duda.
r
fo
C.1. Lógica
.m
√
na
⇔ si y sólo si. Significa que las condiciones a ambos lados son equivalentes.
on
¬p es verdadera ⇔ p es falsa.
w
w
C.2. Conjuntos
∈ pertenece. x ∈ A significa que x es un elemento de A.
\ diferencia de conjuntos, A \ B = {x : x ∈ A y x ∈
/ B}.
| |,
# cardinal, |A|, o a veces #(A), es la cantidad de elementos en el conjunto
A.
∅ El conjunto vacı́o, #(∅) = 0.
C.3. Números
N El conjunto de números naturales, N = {1, 2, 3, . . . }. Observar que para
nosotros 0 ∈
/ N.
m
no tienen una expresión decimal periódica.
o
R+ Los reales positivos, R+ = {x ∈ R : x > 0}.
.c
C √ z = a + bi, con a, b ∈ R y donde i es la
os
Los complejos, de la forma
unidad imaginaria, “ i = −1 ”.
r
fo
| Para m, n ∈ Z, m | n se lee m divide a n, o n es múltiplo de m, y significa
que existe k ∈ Z tal que n = km.
.m
|z| = a2 + b2 .
dm
ción.
w
sen x,
sin x Las función trigonométrica seno, definida para x ∈ R.
o m
C.4. Números importantes en programación
.c
os
maxint El máximo entero que admite el compilador.
r
εmin
fo
El menor número positivo que admite el compilador.
.m
εmaq El menor número positivo que sumado a 1 da mayor que 1. Su valor
depende del compilador.
na
tu
C.5. Generales
on
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Apéndice D
o m
.c
Suponiendo que se tenga acceso a una computadora, el próximo problema es
os
usar un compilador Pascal. Si bien los compiladores comerciales tienen menos
problemas y tienen documentación adecuada, afortunadamente hay varias posi-
r
bilidades de compiladores disponibles gratis de internet, y pasamos a presentar
fo
algunas1 :
.m
“Turbo Pascal” de Borland (para DOS) es muy “robusto” (no tiene proble-
mas), y su popularidad ha hecho que muchos otros compiladores copiaran
tu
su sintaxis.
on
La versión 5.5 de este compilador se puede conseguir gratis para uso per-
sonal en Borland Museum2 .
yc
pero para Windows), también para uso personal pero en francés, pueden
conseguirse en Borland Francia3 .
.a
glés).
7 http://www.pascal-central.com/ (la página está en inglés).
o m
.c
r os
fo
.m
na
tu
on
yc
dm
.a
w
w
w
Bibliografı́a
m
[3] B. W. Kernighan y D. M. Ritchie: El lenguaje de programación C
o
(2.a ed.), Prentice-Hall Hispanoamericana, 1991.
.c
[4] D. E. Knuth: The Art of Computer Programming, Addison-Wesley. Vol. 1,
os
3.a ed., 1997; vol. 2, 3.a ed., 1997; vol. 3, 2.a ed., 1997.
r
fo
[5] N. Wirth: Introducción a la Programación Sistemática, El Ateneo, 1984.
.m